From 2e482b207b344fa7ec1b7f4bb047407440f62690 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 28 Apr 2020 23:29:44 +0300 Subject: [PATCH] Fix Windows tests * Unskip passable tests * Fix broken tests --- pelican/readers.py | 2 +- pelican/tests/support.py | 13 +++++++++++ pelican/tests/test_generators.py | 40 +++++++++++++++++--------------- pelican/tests/test_importer.py | 3 ++- pelican/tests/test_server.py | 3 +-- pelican/tests/test_settings.py | 2 ++ pelican/tests/test_utils.py | 15 +++++------- pelican/tools/pelican_import.py | 5 ++-- pelican/utils.py | 13 ++++++----- pelican/writers.py | 3 ++- 10 files changed, 58 insertions(+), 41 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 8174aa79..8c108510 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -696,7 +696,7 @@ def path_metadata(full_path, source_path, settings=None): # Enforce a trailing slash when checking for parent directories. # This prevents false positives when one file or directory's name # is a prefix of another's. - dirpath = os.path.join(path, '') + dirpath = posixize_path(os.path.join(path, '')) if source_path == path or source_path.startswith(dirpath): metadata.update(meta) diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 8a22052e..e52bc0ae 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -160,6 +160,19 @@ def locale_available(locale_): return True +def can_symlink(): + res = True + try: + with temporary_folder() as f: + os.symlink( + f, + os.path.join(f, 'symlink') + ) + except OSError: + res = False + return res + + def get_settings(**kwargs): """Provide tweaked setting dictionaries for testing diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 0fd84925..332d7e78 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1,5 +1,6 @@ import locale import os +import sys from shutil import copy, rmtree from tempfile import mkdtemp from unittest.mock import MagicMock @@ -7,7 +8,8 @@ from unittest.mock import MagicMock from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator, PelicanTemplateNotFound, StaticGenerator, TemplatePagesGenerator) -from pelican.tests.support import get_context, get_settings, unittest +from pelican.tests.support import (can_symlink, get_context, get_settings, + unittest) from pelican.writers import Writer @@ -1081,6 +1083,7 @@ class TestStaticGenerator(unittest.TestCase): self.generator.generate_output(None) self.assertTrue(os.path.samefile(self.startfile, self.endfile)) + @unittest.skipUnless(can_symlink(), 'No symlink privilege') def test_can_symlink_when_hardlink_not_possible(self): self.settings['STATIC_CREATE_LINKS'] = True with open(self.startfile, "w") as f: @@ -1088,40 +1091,29 @@ class TestStaticGenerator(unittest.TestCase): os.mkdir(os.path.join(self.temp_output, "static")) self.generator.fallback_to_symlinks = True self.generator.generate_context() - try: - self.generator.generate_output(None) - except OSError as e: - # On Windows, possibly others, due to not holding symbolic link - # privilege - self.skipTest(e) + self.generator.generate_output(None) self.assertTrue(os.path.islink(self.endfile)) + @unittest.skipUnless(can_symlink(), 'No symlink privilege') def test_existing_symlink_is_considered_up_to_date(self): self.settings['STATIC_CREATE_LINKS'] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) - try: - os.symlink(self.startfile, self.endfile) - except OSError as e: - # On Windows, possibly others - self.skipTest(e) + os.symlink(self.startfile, self.endfile) staticfile = MagicMock() staticfile.source_path = self.startfile staticfile.save_as = self.endfile requires_update = self.generator._file_update_required(staticfile) self.assertFalse(requires_update) + @unittest.skipUnless(can_symlink(), 'No symlink privilege') def test_invalid_symlink_is_overwritten(self): self.settings['STATIC_CREATE_LINKS'] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) - try: - os.symlink("invalid", self.endfile) - except OSError as e: - # On Windows, possibly others - self.skipTest(e) + os.symlink("invalid", self.endfile) staticfile = MagicMock() staticfile.source_path = self.startfile staticfile.save_as = self.endfile @@ -1131,8 +1123,18 @@ class TestStaticGenerator(unittest.TestCase): self.generator.generate_context() self.generator.generate_output(None) self.assertTrue(os.path.islink(self.endfile)) - self.assertEqual(os.path.realpath(self.endfile), - os.path.realpath(self.startfile)) + + # os.path.realpath is broken on Windows before python3.8 for symlinks. + # This is a (ugly) workaround. + # see: https://bugs.python.org/issue9949 + if os.name == 'nt' and sys.version_info < (3, 8): + def get_real_path(path): + return os.readlink(path) if os.path.islink(path) else path + else: + get_real_path = os.path.realpath + + self.assertEqual(get_real_path(self.endfile), + get_real_path(self.startfile)) def test_delete_existing_file_before_mkdir(self): with open(self.startfile, "w") as f: diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 709086c0..76feb9ce 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -1,6 +1,7 @@ import locale import os import re +from posixpath import join as posix_join from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, @@ -448,5 +449,5 @@ class TestWordpressXMLAttachements(unittest.TestCase): self.assertEqual(1, len(locations)) directory = locations[0] self.assertTrue( - directory.endswith(os.path.join('content', 'article.rst')), + directory.endswith(posix_join('content', 'article.rst')), directory) diff --git a/pelican/tests/test_server.py b/pelican/tests/test_server.py index 68747001..307a3e10 100644 --- a/pelican/tests/test_server.py +++ b/pelican/tests/test_server.py @@ -25,11 +25,10 @@ class TestServer(unittest.TestCase): os.chdir(self.temp_output) def tearDown(self): - rmtree(self.temp_output) os.chdir(self.old_cwd) + rmtree(self.temp_output) def test_get_path_that_exists(self): - handler = ComplexHTTPRequestHandler(MockRequest(), ('0.0.0.0', 8888), self.server) handler.base_path = self.temp_output diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 8f12f3fd..708c0981 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -136,6 +136,8 @@ class TestSettingsConfiguration(unittest.TestCase): settings['ARTICLE_DIR'] settings['PAGE_DIR'] + # locale.getdefaultlocale() is broken on Windows + # See: https://bugs.python.org/issue37945 @unittest.skipIf(platform == 'win32', "Doesn't work on Windows") def test_default_encoding(self): # Test that the default locale is set if not specified in settings diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index f0bc290d..e12c4378 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -757,35 +757,32 @@ class TestDateFormatter(unittest.TestCase): class TestSanitisedJoin(unittest.TestCase): - @unittest.skipIf(platform == 'win32', - "Different filesystem root on Windows") def test_detect_parent_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to /foo/test"): + "Attempted to break out of output directory to " + "(.*?:)?/foo/test"): # (.*?:)? accounts for Windows root utils.sanitised_join( "/foo/bar", "../test" ) - @unittest.skipIf(platform == 'win32', - "Different filesystem root on Windows") def test_detect_root_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to /test"): + "Attempted to break out of output directory to " + "(.*?:)?/test"): # (.*?:)? accounts for Windows root utils.sanitised_join( "/foo/bar", "/test" ) - @unittest.skipIf(platform == 'win32', - "Different filesystem root on Windows") def test_pass_deep_subpaths(self): self.assertEqual( utils.sanitised_join( "/foo/bar", "test" ), - os.path.join("/foo/bar", "test") + utils.posixize_path( + os.path.abspath(os.path.join("/foo/bar", "test"))) ) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index e288711e..b74da750 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -728,8 +728,9 @@ def download_attachments(output_path, urls): # Generate percent-encoded URL scheme, netloc, path, query, fragment = urlsplit(url) - path = quote(path) - url = urlunsplit((scheme, netloc, path, query, fragment)) + if scheme != 'file': + path = quote(path) + url = urlunsplit((scheme, netloc, path, query, fragment)) if not os.path.exists(full_path): os.makedirs(full_path) diff --git a/pelican/utils.py b/pelican/utils.py index 1f3d502c..64a308c1 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -27,8 +27,10 @@ logger = logging.getLogger(__name__) def sanitised_join(base_directory, *parts): - joined = os.path.abspath(os.path.join(base_directory, *parts)) - if not joined.startswith(os.path.abspath(base_directory)): + joined = posixize_path( + os.path.abspath(os.path.join(base_directory, *parts))) + base = posixize_path(os.path.abspath(base_directory)) + if not joined.startswith(base): raise RuntimeError( "Attempted to break out of output directory to {}".format( joined @@ -391,10 +393,9 @@ def get_relative_path(path): def path_to_url(path): """Return the URL corresponding to a given path.""" - if os.sep == '/': - return path - else: - return '/'.join(split_all(path)) + if path is not None: + path = posixize_path(path) + return path def posixize_path(rel_path): diff --git a/pelican/writers.py b/pelican/writers.py index 7b14714a..9b27a748 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -1,5 +1,6 @@ import logging import os +from posixpath import join as posix_join from urllib.parse import urljoin from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri @@ -25,7 +26,7 @@ class Writer: # See Content._link_replacer for details if self.settings['RELATIVE_URLS']: - self.urljoiner = os.path.join + self.urljoiner = posix_join else: self.urljoiner = lambda base, url: urljoin( base if base.endswith('/') else base + '/', url)