diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/README.rst | 92 | ||||
| -rw-r--r-- | tests/__init__.py | 0 | ||||
| -rw-r--r-- | tests/base.py | 109 | ||||
| -rw-r--r-- | tests/data/translated_titles/conf.py | 11 | ||||
| -rw-r--r-- | tests/import_wordpress_and_build_workflow.py | 5 | ||||
| -rw-r--r-- | tests/test_command_import_wordpress.py | 90 | ||||
| -rw-r--r-- | tests/test_command_init.py | 12 | ||||
| -rw-r--r-- | tests/test_compile_markdown.py | 10 | ||||
| -rw-r--r-- | tests/test_integration.py | 221 | ||||
| -rw-r--r-- | tests/test_locale.py | 241 | ||||
| -rw-r--r-- | tests/test_plugin_importing.py | 8 | ||||
| -rw-r--r-- | tests/test_rss_feeds.py | 28 | ||||
| -rw-r--r-- | tests/test_rst_compiler.py (renamed from tests/test_rst_extensions.py) | 208 | ||||
| -rw-r--r-- | tests/test_scheduling.py | 126 | ||||
| -rw-r--r-- | tests/wordpress_export_example.xml | 59 |
15 files changed, 1087 insertions, 133 deletions
diff --git a/tests/README.rst b/tests/README.rst new file mode 100644 index 0000000..2b3afb8 --- /dev/null +++ b/tests/README.rst @@ -0,0 +1,92 @@ +.. title: The Nikola Test Suite +.. slug: tests +.. date: 2012/03/30 23:00 + +The Nikola Test Suite +===================== + +Nikola, like many software projects, has a test suite. There are over 100 +tests. + +Tests (in alphabetical order) +----------------------------- + +* ``test_command_import_wordpress`` tests the WordPress importer for + Nikola. +* ``test_command_init`` checks whether new sites are created properly via the + ``init`` command. +* ``test_compile_markdown`` exercises the Markdown compiler plugin of Nikola. +* ``test_integration`` are used to validate that sites actually build. +* ``test_locale`` tests the locale support of Nikola. +* ``test_plugin_importing`` checks three basic plugins to know whether they + get imported properly. +* ``test_rss_feeds`` asserts that RSS created by Nikola is sane. +* ``test_rst_compiler`` exercises the reStructuredText compiler plugin of + Nikola. +* ``test_scheduling`` performs tests on post scheduling rules. +* ``test_utils`` test various Nikola utilities. + +Requirements to run the tests +----------------------------- + +You need: + +* ``pip install -r requirements-tests.txt`` +* a few minutes’ time +* appropriate locale settings + +How to set the locale for Nikola tests? +--------------------------------------- + +For testing nikola needs to specify two languages, each one with a supported locale. By default, the test suite uses ``en`` and ``es`` as languages, and their respective default locale for them. + +You can set the language - locale pairs by exporting two shell variables, like in:: + + export NIKOLA_LOCALE_DEFAULT=en,en_US.utf8 + export NIKOLA_LOCALE_OTHER=es,es_ES.utf8 + +In Windows that would be:: + + set NIKOLA_LOCALE_DEFAULT=en,English + set NIKOLA_LOCALE_OTHER=es,Spanish + +Replace the part before the comma with a Nikola translation selector (see ``nikola/conf.py.in`` for details), and the part after the comma with an *installed* glibc locale. + +To check if the desired locale is supported in your host you can, in a python console:: + + import locale + locale.setlocale(locale.LC_ALL, 'locale_name') + # by example, 'en_US.utf8' (posix) 'English' (windows) + # if it does not traceback, then python can use that locale + +Alternatively, if you have some disk space to spare, you can install +the two default locales. Here is how to do that in Ubuntu:: + + sudo apt-get install language-pack-en language-pack-es + + +How to execute the tests +------------------------ + +The command to execute tests is:: + + nosetests --with-coverage --cover-package=nikola --with-doctest --doctest-options=+NORMALIZE_WHITESPACE --logging-filter=-yapsy + +However, this command may change at any given moment. Check the +``/.travis.yml`` file to get the current command. + +In Windows you want to drop the doctests parts, they fail over trivial differences in OS details. + +It is also recommended to run ``nikola help`` to see if Nikola actually +works. + +If you are committing code, make sure to run ``flake8 --ignore=E501 .`` to see if you comply with the PEP 8 style guide and do not have basic code mistakes (we ignore the 79-characters-per-line rule). + +In windows ignore the two flake8 diagnostics about messages_sl_si.py , they are artifacts of (symlinks + git + windows). + + +Travis CI +--------- + +We also run our tests on `Travis CI <https://travis-ci.org/>`_. +You can check the `current build status <https://travis-ci.org/getnikola/nikola>`_ there. diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/__init__.py diff --git a/tests/base.py b/tests/base.py index 92576c7..00f7486 100644 --- a/tests/base.py +++ b/tests/base.py @@ -6,12 +6,21 @@ """ Base class for Nikola test cases """ -__all__ = ["BaseTestCase"] - +__all__ = ["BaseTestCase", "cd", "LocaleSupportInTesting"] +from contextlib import contextmanager +import locale +import os import sys import unittest +import logbook + +# Make logbook shutup +import nikola.utils + +nikola.utils.LOGGER.handlers.append(logbook.TestHandler()) + if sys.version_info < (2, 7): @@ -56,3 +65,99 @@ if sys.version_info < (2, 7): else: BaseTestCase = unittest.TestCase + + +@contextmanager +def cd(path): + old_dir = os.getcwd() + os.chdir(path) + yield + os.chdir(old_dir) + + +class LocaleSupportInTesting(object): + """ + Nikola needs two pairs of valid (language, locale_n) to test multilingual sites. + + As languages of interest and installed OS support varies from host to host + we allow to specify two such pairs. + + A valid pair complies + 'languaje' one of the names of nikola translations ('en', 'es', ...) + 'locale_n' is a string that python accepts to set a locale, like in + import locale + locale.setlocale(locale.LC_ALL, str(locale_n)) + + You specify the custom pairs to use with two environment variables + NIKOLA_LOCALE_DEFAULT (lang and locale to use as nikola's DEFAULT_LANG) + NIKOLA_LOCALE_OTHER + + The value of the pair is lang (as in keys of Nikola's TRANSLATIONS), followed + by coma, followed by the locale. + """ + + @classmethod + def initialize(cls): + """Determines and diagnoses the two (lang, locale) pairs to use in testing + + While it only needs to run once at the beginning of the testing session, + calling multiple times is fine. + """ + if hasattr(cls, 'langlocales'): + return + defaults = { + 'linux': { + # non-windows defaults, must be two locales suported by .travis.yml + 'default': ("en", str("en_US.utf8")), + 'other': ("es", str("es_ES.utf8")), + }, + 'windows': { + # windows defaults + 'default': ("en", str("English")), + 'other': ("es", str("Spanish")), + }, + } + os_id = 'windows' if sys.platform == 'win32' else 'linux' + langlocales = {} + for suffix in ['other', 'default']: + try: + envar = 'NIKOLA_LOCALE_' + suffix.upper() + s = os.environ[envar] + parts = s.split(',') + lang = parts[0].strip() + try: + locale_n = str(parts[1].strip()) + locale.setlocale(locale.LC_ALL, locale_n) + except Exception: + msg = ("Environment variable {0} fails to specify a valid <lang>,<locale>." + + "Check your syntax, check that python supports that locale in your host.") + nikola.utils.LOGGER.error(msg.format(envar)) + sys.exit(1) + except KeyError: + lang, locale_n = defaults[os_id][suffix] + langlocales[suffix] = (lang, locale_n) + if (langlocales['default'][0] == langlocales['other'][0] or + langlocales['default'][1] == langlocales['other'][1]): # NOQA + # the mix of defaults and enviro is not good + msg = ('Locales for testing should differ in lang and locale, else ' + + 'the test would we weak. Check your environment settings for ' + + 'NIKOLA_LOCALE_DEFAULT and NIKOLA_LOCALE_OTHER') + nikola.utils.LOGGER.error(msg) + setattr(cls, 'langlocales', langlocales) + + @classmethod + def initialize_locales_for_testing(cls, variant): + """initializes nikola.utils.LocaleBorg""" + if not hasattr(cls, 'langlocales'): + cls.initialize() + default_lang = cls.langlocales['default'][0] + locales = {} + locales[default_lang] = cls.langlocales['default'][1] + if variant == 'unilingual': + pass + elif variant == 'bilingual': + locales[cls.langlocales['other'][0]] = cls.langlocales['other'][1] + else: + raise ValueError('Unknown locale variant') + nikola.utils.LocaleBorg.reset() + nikola.utils.LocaleBorg.initialize(locales, default_lang) diff --git a/tests/data/translated_titles/conf.py b/tests/data/translated_titles/conf.py index 69c7bc7..b445ba9 100644 --- a/tests/data/translated_titles/conf.py +++ b/tests/data/translated_titles/conf.py @@ -1,4 +1,3 @@ - # -*- coding: utf-8 -*- from __future__ import unicode_literals import time @@ -72,7 +71,7 @@ SIDEBAR_LINKS = { # The wildcard is used to generate a list of reSt source files # (whatever/thing.txt). # That fragment must have an associated metadata file (whatever/thing.meta), -# and opcionally translated files (example for spanish, with code "es"): +# and optionally translated files (example for spanish, with code "es"): # whatever/thing.txt.es and whatever/thing.meta.es # # From those files, a set of HTML fragment files will be generated: @@ -106,7 +105,7 @@ post_pages = ( # 'rest' is reStructuredText # 'markdown' is MarkDown # 'html' assumes the file is html and just copies it -post_compilers = { +COMPILERS = { "rest": ('.txt', '.rst'), "markdown": ('.md', '.mdown', '.markdown'), "textile": ('.textile',), @@ -340,10 +339,14 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL, # external resources. # USE_CDN = False -# Google analytics or whatever else you use. Added to the bottom of <body> +# Google analytics script or whatever else you use. Added to the bottom of <body> # in the default template (base.tmpl). # ANALYTICS = "" +# HTML snippet that will be added at the bottom of body of <body> +# in the default template (base.tmpl). +# SOCIAL_BUTTONS_CODE = "" + # The possibility to extract metadata from the filename by using a # regular expression. # To make it work you need to name parts of your regular expression. diff --git a/tests/import_wordpress_and_build_workflow.py b/tests/import_wordpress_and_build_workflow.py index 90cb6a8..bc04a1f 100644 --- a/tests/import_wordpress_and_build_workflow.py +++ b/tests/import_wordpress_and_build_workflow.py @@ -26,12 +26,13 @@ def main(import_directory=None): test_directory = os.path.dirname(__file__) package_directory = os.path.abspath(os.path.join(test_directory, '..')) - os.system('echo "y" | pip uninstall Nikola') + os.system('pip uninstall -y Nikola') os.system('pip install %s' % package_directory) os.system('nikola') import_file = os.path.join(test_directory, 'wordpress_export_example.xml') os.system( - 'nikola import_wordpress -f %s -o %s' % (import_file, import_directory)) + 'nikola import_wordpress -o {folder} {file}'.format(file=import_file, + folder=import_directory)) assert os.path.exists( import_directory), "The directory %s should be existing." diff --git a/tests/test_command_import_wordpress.py b/tests/test_command_import_wordpress.py index 3be2ad9..f215705 100644 --- a/tests/test_command_import_wordpress.py +++ b/tests/test_command_import_wordpress.py @@ -1,15 +1,19 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import -from context import nikola +from .context import nikola import os import unittest import mock +import nikola.plugins.command.import_wordpress +from .base import BaseTestCase -class BasicCommandImportWordpress(unittest.TestCase): + +class BasicCommandImportWordpress(BaseTestCase): def setUp(self): - self.import_command = nikola.plugins.command_import_wordpress.CommandImportWordpress() + self.module = nikola.plugins.command.import_wordpress + self.import_command = self.module.CommandImportWordpress() self.import_filename = os.path.abspath(os.path.join( os.path.dirname(__file__), 'wordpress_export_example.xml')) @@ -51,11 +55,11 @@ class CommandImportWordpressRunTest(BasicCommandImportWordpress): site_generation_patch = mock.patch('os.system', self.site_generation) data_import_patch = mock.patch( - 'nikola.plugins.command_import_wordpress.CommandImportWordpress.import_posts', self.data_import) + 'nikola.plugins.command.import_wordpress.CommandImportWordpress.import_posts', self.data_import) write_urlmap_patch = mock.patch( - 'nikola.plugins.command_import_wordpress.CommandImportWordpress.write_urlmap_csv', self.write_urlmap) + 'nikola.plugins.command.import_wordpress.CommandImportWordpress.write_urlmap_csv', self.write_urlmap) write_configuration_patch = mock.patch( - 'nikola.plugins.command_import_wordpress.CommandImportWordpress.write_configuration', self.write_configuration) + 'nikola.plugins.command.import_wordpress.CommandImportWordpress.write_configuration', self.write_configuration) self.patches = [site_generation_patch, data_import_patch, write_urlmap_patch, write_configuration_patch] @@ -117,7 +121,7 @@ class CommandImportWordpressTest(BasicCommandImportWordpress): self.import_filename) context = self.import_command.populate_context(channel) - for required_key in ('POST_PAGES', 'POST_COMPILERS'): + for required_key in ('POSTS', 'PAGES', 'COMPILERS'): self.assertTrue(required_key in context) self.assertEqual('de', context['DEFAULT_LANG']) @@ -133,33 +137,37 @@ class CommandImportWordpressTest(BasicCommandImportWordpress): self.import_filename) self.import_command.context = self.import_command.populate_context( channel) - self.import_command.url_map = {} # For testing we use an empty one. self.import_command.output_folder = 'new_site' self.import_command.squash_newlines = True self.import_command.no_downloads = False + # Ensuring clean results + self.import_command.url_map = {} + self.module.links = {} + write_metadata = mock.MagicMock() write_content = mock.MagicMock() download_mock = mock.MagicMock() - with mock.patch('nikola.plugins.command_import_wordpress.CommandImportWordpress.write_content', write_content): - with mock.patch('nikola.plugins.command_import_wordpress.CommandImportWordpress.write_metadata', write_metadata): - with mock.patch('nikola.plugins.command_import_wordpress.CommandImportWordpress.download_url_content_to_file', download_mock): - with mock.patch('nikola.plugins.command_import_wordpress.os.makedirs'): + with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.write_content', write_content): + with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.write_metadata', write_metadata): + with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.download_url_content_to_file', download_mock): + with mock.patch('nikola.plugins.command.import_wordpress.os.makedirs'): self.import_command.import_posts(channel) self.assertTrue(download_mock.called) + qpath = 'new_site/files/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png' download_mock.assert_any_call( 'http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png', - 'new_site/files/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png') + qpath.replace('/', os.sep)) self.assertTrue(write_metadata.called) write_metadata.assert_any_call( - 'new_site/stories/kontakt.meta', 'Kontakt', + 'new_site/stories/kontakt.meta'.replace('/', os.sep), 'Kontakt', 'kontakt', '2009-07-16 20:20:32', None, []) self.assertTrue(write_content.called) - write_content.assert_any_call('new_site/posts/200704hoert.wp', + write_content.assert_any_call('new_site/posts/200704hoert.wp'.replace('/', os.sep), """An image. <img class="size-full wp-image-16" title="caption test" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="caption test" width="739" height="517" /> @@ -179,11 +187,11 @@ The end. """) write_content.assert_any_call( - 'new_site/posts/200807arzt-und-pfusch-s-i-c-k.wp', + 'new_site/posts/200807arzt-und-pfusch-s-i-c-k.wp'.replace('/', os.sep), '''<img class="size-full wp-image-10 alignright" title="Arzt+Pfusch - S.I.C.K." src="http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png" alt="Arzt+Pfusch - S.I.C.K." width="210" height="209" />Arzt+Pfusch - S.I.C.K.Gerade bin ich \xfcber das Album <em>S.I.C.K</em> von <a title="Arzt+Pfusch" href="http://www.arztpfusch.com/" target="_blank">Arzt+Pfusch</a> gestolpert, welches Arzt+Pfusch zum Download f\xfcr lau anbieten. Das Album steht unter einer Creative Commons <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/de/">BY-NC-ND</a>-Lizenz. Die Ladung <em>noisebmstupidevildustrial</em> gibts als MP3s mit <a href="http://www.archive.org/download/dmp005/dmp005_64kb_mp3.zip">64kbps</a> und <a href="http://www.archive.org/download/dmp005/dmp005_vbr_mp3.zip">VBR</a>, als Ogg Vorbis und als FLAC (letztere <a href="http://www.archive.org/details/dmp005">hier</a>). <a href="http://www.archive.org/download/dmp005/dmp005-artwork.zip">Artwork</a> und <a href="http://www.archive.org/download/dmp005/dmp005-lyrics.txt">Lyrics</a> gibts nochmal einzeln zum Download.''') write_content.assert_any_call( - 'new_site/stories/kontakt.wp', """<h1>Datenschutz</h1> + 'new_site/stories/kontakt.wp'.replace('/', os.sep), """<h1>Datenschutz</h1> Ich erhebe und speichere automatisch in meine Server Log Files Informationen, die dein Browser an mich \xfcbermittelt. Dies sind: <ul> @@ -209,15 +217,36 @@ Diese Daten sind f\xfcr mich nicht bestimmten Personen zuordenbar. Eine Zusammen self.import_command.url_map['http://some.blog/kontakt/'], 'http://some.blog/stories/kontakt.html') + image_thumbnails = [ + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-64x64.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-300x175.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-36x36.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-24x24.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-48x48.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png', + 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-150x150.png' + ] + + for link in image_thumbnails: + self.assertTrue( + link in self.module.links, + 'No link to "{0}" found in {map}.'.format( + link, + map=self.module.links + ) + ) + def test_transforming_content(self): """Applying markup conversions to content.""" transform_sourcecode = mock.MagicMock() transform_caption = mock.MagicMock() transform_newlines = mock.MagicMock() - with mock.patch('nikola.plugins.command_import_wordpress.CommandImportWordpress.transform_sourcecode', transform_sourcecode): - with mock.patch('nikola.plugins.command_import_wordpress.CommandImportWordpress.transform_caption', transform_caption): - with mock.patch('nikola.plugins.command_import_wordpress.CommandImportWordpress.transform_multiple_newlines', transform_newlines): + with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_sourcecode', transform_sourcecode): + with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_caption', transform_caption): + with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_multiple_newlines', transform_newlines): self.import_command.transform_content("random content") self.assertTrue(transform_sourcecode.called) @@ -327,7 +356,7 @@ newlines. config_path_with_timestamp = self.import_command.get_configuration_output_path( ) self.assertNotEqual(default_config_path, config_path_with_timestamp) - self.assertTrue('wordpress_import' in config_path_with_timestamp) + self.assertTrue(self.import_command.name in config_path_with_timestamp) def test_write_content_does_not_detroy_text(self): content = b"""<h1>Installation</h1> @@ -338,7 +367,7 @@ There are many plugins. <h2>Violations</h2> You can use the <a title="Jenkins Plugin: Violations" href="https://wiki.jenkins-ci.org/display/JENKINS/Violations">Violations</a> plugin.""" open_mock = mock.mock_open() - with mock.patch('nikola.plugins.command_import_wordpress.open', open_mock, create=True): + with mock.patch('nikola.plugins.basic_import.open', open_mock, create=True): self.import_command.write_content('some_file', content) open_mock.assert_called_once_with('some_file', 'wb+') @@ -346,5 +375,20 @@ You can use the <a title="Jenkins Plugin: Violations" href="https://wiki.jenkins call_context.write.assert_called_once_with( content.join([b'<html><body>', b'</body></html>'])) + def test_configure_redirections(self): + """ + Testing the configuration of the redirections. + + We need to make sure that we have valid sources and target links. + """ + url_map = { + '/somewhere/else': 'http://foo.bar/posts/somewhereelse.html' + } + + redirections = self.import_command.configure_redirections(url_map) + + self.assertEqual(1, len(redirections)) + self.assertTrue(('somewhere/else/index.html', '/posts/somewhereelse.html') in redirections) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_command_init.py b/tests/test_command_init.py index 1904fa1..a9ec208 100644 --- a/tests/test_command_init.py +++ b/tests/test_command_init.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import -from context import nikola +from .context import nikola import unittest import mock @@ -12,18 +12,18 @@ class CommandInitCallTest(unittest.TestCase): self.create_configuration = mock.MagicMock() self.create_empty_site = mock.MagicMock() copy_sample_site_patch = mock.patch( - 'nikola.plugins.command_init.CommandInit.copy_sample_site', self.copy_sample_site) + 'nikola.plugins.command.init.CommandInit.copy_sample_site', self.copy_sample_site) create_configuration_patch = mock.patch( - 'nikola.plugins.command_init.CommandInit.create_configuration', self.create_configuration) + 'nikola.plugins.command.init.CommandInit.create_configuration', self.create_configuration) create_empty_site_patch = mock.patch( - 'nikola.plugins.command_init.CommandInit.create_empty_site', self.create_empty_site) + 'nikola.plugins.command.init.CommandInit.create_empty_site', self.create_empty_site) self.patches = [copy_sample_site_patch, create_configuration_patch, create_empty_site_patch] for patch in self.patches: patch.start() - self.init_commad = nikola.plugins.command_init.CommandInit() + self.init_commad = nikola.plugins.command.init.CommandInit() def tearDown(self): for patch in self.patches: diff --git a/tests/test_compile_markdown.py b/tests/test_compile_markdown.py index a1f8591..a8252d8 100644 --- a/tests/test_compile_markdown.py +++ b/tests/test_compile_markdown.py @@ -6,7 +6,14 @@ import tempfile import unittest from os import path -from nikola.plugins.compile_markdown import CompileMarkdown +from nikola.plugins.compile.markdown import CompileMarkdown + + +class FakeSite(object): + config = { + "MARKDOWN_EXTENSIONS": ['fenced_code', 'codehilite'], + "LOGGING_HANDLERS": {'stderr': {'loglevel': 'WARNING', 'bubble': True}} + } class CompileMarkdownTests(unittest.TestCase): @@ -16,6 +23,7 @@ class CompileMarkdownTests(unittest.TestCase): self.output_path = path.join(self.tmp_dir, 'output.html') self.compiler = CompileMarkdown() + self.compiler.set_site(FakeSite()) def compile(self, input_string): with codecs.open(self.input_path, "w+", "utf8") as input_file: diff --git a/tests/test_integration.py b/tests/test_integration.py index 802dcc7..fdb2494 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,44 +1,40 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import unicode_literals, print_function, absolute_import import codecs -from contextlib import contextmanager import locale import os import shutil -import subprocess # NOQA +import subprocess +import sys import tempfile import unittest import lxml.html from nose.plugins.skip import SkipTest -from context import nikola from nikola import main +import nikola +import nikola.plugins.command +import nikola.plugins.command.init +from .base import BaseTestCase, cd -@contextmanager -def cd(path): - old_dir = os.getcwd() - os.chdir(path) - yield - os.chdir(old_dir) - -class EmptyBuildTest(unittest.TestCase): +class EmptyBuildTest(BaseTestCase): """Basic integration testcase.""" dataname = None @classmethod - def setUpClass(self): + def setUpClass(cls): """Setup a demo site.""" - self.tmpdir = tempfile.mkdtemp() - self.target_dir = os.path.join(self.tmpdir, "target") - self.init_command = nikola.plugins.command_init.CommandInit() - self.fill_site() - self.patch_site() - self.build() + cls.tmpdir = tempfile.mkdtemp() + cls.target_dir = os.path.join(cls.tmpdir, "target") + cls.init_command = nikola.plugins.command.init.CommandInit() + cls.fill_site() + cls.patch_site() + cls.build() @classmethod def fill_site(self): @@ -69,7 +65,13 @@ class EmptyBuildTest(unittest.TestCase): @classmethod def tearDownClass(self): """Remove the demo site.""" - shutil.rmtree(self.tmpdir) + # ignore_errors=True for windows by issue #782 + shutil.rmtree(self.tmpdir, ignore_errors=(sys.platform == 'win32')) + # Fixes Issue #438 + try: + del sys.modules['conf'] + except KeyError: + pass def test_build(self): """Ensure the build did something.""" @@ -91,9 +93,82 @@ class DemoBuildTest(EmptyBuildTest): outf.write( ".. title: foobar\n" ".. slug: foobar\n" - ".. date: 2013/03/06 19:08:15\n" + ".. date: 2013-03-06 19:08:15\n" + ) + + def test_index_in_sitemap(self): + sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml") + sitemap_data = codecs.open(sitemap_path, "r", "utf8").read() + self.assertTrue('<loc>http://getnikola.com/index.html</loc>' in sitemap_data) + + def test_avoid_double_slash_in_rss(self): + rss_path = os.path.join(self.target_dir, "output", "rss.xml") + rss_data = codecs.open(rss_path, "r", "utf8").read() + self.assertFalse('http://getnikola.com//' in rss_data) + + +class RepeatedPostsSetting(DemoBuildTest): + """Duplicate POSTS, should not read each post twice, which causes conflicts.""" + @classmethod + def patch_site(self): + """Set the SITE_URL to have a path""" + conf_path = os.path.join(self.target_dir, "conf.py") + with codecs.open(conf_path, "ab", "utf8") as outf: + outf.write('\nPOSTS = (("posts/*.txt", "posts", "post.tmpl"),("posts/*.txt", "posts", "post.tmpl"))\n') + + +class FuturePostTest(EmptyBuildTest): + """Test a site with future posts.""" + + @classmethod + def fill_site(self): + import datetime + from nikola.utils import current_time + self.init_command.copy_sample_site(self.target_dir) + self.init_command.create_configuration(self.target_dir) + + # Change COMMENT_SYSTEM_ID to not wait for 5 seconds + with codecs.open(os.path.join(self.target_dir, 'conf.py'), "ab+", "utf8") as outf: + outf.write('\nCOMMENT_SYSTEM_ID = "nikolatest"\n') + + with codecs.open(os.path.join(self.target_dir, 'posts', 'empty1.txt'), "wb+", "utf8") as outf: + outf.write( + ".. title: foo\n" + ".. slug: foo\n" + ".. date: %s\n" % (current_time() + datetime.timedelta(-1)).strftime('%Y-%m-%d %H:%M:%S') ) + with codecs.open(os.path.join(self.target_dir, 'posts', 'empty2.txt'), "wb+", "utf8") as outf: + outf.write( + ".. title: bar\n" + ".. slug: bar\n" + ".. date: %s\n" % (current_time() + datetime.timedelta(1)).strftime('%Y-%m-%d %H:%M:%S') + ) + + def test_future_post(self): + """ Ensure that the future post is not present in the index and sitemap.""" + index_path = os.path.join(self.target_dir, "output", "index.html") + sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml") + foo_path = os.path.join(self.target_dir, "output", "posts", "foo.html") + bar_path = os.path.join(self.target_dir, "output", "posts", "bar.html") + self.assertTrue(os.path.isfile(index_path)) + self.assertTrue(os.path.isfile(foo_path)) + self.assertTrue(os.path.isfile(bar_path)) + index_data = codecs.open(index_path, "r", "utf8").read() + sitemap_data = codecs.open(sitemap_path, "r", "utf8").read() + self.assertTrue('foo.html' in index_data) + self.assertFalse('bar.html' in index_data) + self.assertTrue('foo.html' in sitemap_data) + self.assertFalse('bar.html' in sitemap_data) + + # Run deploy command to see if future post is deleted + with cd(self.target_dir): + main.main(["deploy"]) + + self.assertTrue(os.path.isfile(index_path)) + self.assertTrue(os.path.isfile(foo_path)) + self.assertFalse(os.path.isfile(bar_path)) + class TranslatedBuildTest(EmptyBuildTest): """Test a site with translated content.""" @@ -132,8 +207,8 @@ class RelativeLinkTest(DemoBuildTest): conf_path = os.path.join(self.target_dir, "conf.py") with codecs.open(conf_path, "rb", "utf-8") as inf: data = inf.read() - data = data.replace('SITE_URL = "http://nikola.ralsina.com.ar"', - 'SITE_URL = "http://nikola.ralsina.com.ar/foo/bar/"') + data = data.replace('SITE_URL = "http://getnikola.com/"', + 'SITE_URL = "http://getnikola.com/foo/bar/"') with codecs.open(conf_path, "wb+", "utf8") as outf: outf.write(data) @@ -151,36 +226,51 @@ class RelativeLinkTest(DemoBuildTest): # But I also need to be sure it is there! self.assertTrue(flag) + def test_index_in_sitemap(self): + """Test that the correct path is in sitemap, and not the wrong one.""" + sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml") + sitemap_data = codecs.open(sitemap_path, "r", "utf8").read() + self.assertFalse('<loc>http://getnikola.com/</loc>' in sitemap_data) + self.assertTrue('<loc>http://getnikola.com/foo/bar/index.html</loc>' in sitemap_data) + -#class TestCheck(DemoBuildTest): - #"""The demo build should pass 'nikola check'""" +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + check_output = subprocess.check_call +else: + check_output = subprocess.check_output - #def test_check_links(self): - #with cd(self.target_dir): - #r = subprocess.call("nikola check -l", shell=True) - #self.assertEqual(r, 0) - #def test_check_files(self): - #with cd(self.target_dir): - #r = subprocess.call("nikola check -f", shell=True) - #self.assertEqual(r, 0) +class TestCheck(DemoBuildTest): + """The demo build should pass 'nikola check'""" + def test_check_links(self): + with cd(self.target_dir): + check_output("nikola check -l", shell=True, stderr=subprocess.STDOUT) -#class TestCheckFailure(DemoBuildTest): - #"""The demo build should pass 'nikola check'""" + def test_check_files(self): + with cd(self.target_dir): + check_output("nikola check -f", shell=True, stderr=subprocess.STDOUT) - #def test_check_links_fail(self): - #with cd(self.target_dir): - #os.unlink(os.path.join("output", "archive.html")) - #rv = subprocess.call("nikola check -l", shell=True) - #self.assertEqual(rv, 1) - #def test_check_files_fail(self): - #with cd(self.target_dir): - #with codecs.open(os.path.join("output", "foobar"), "wb+", "utf8") as outf: - #outf.write("foo") - #rv = subprocess.call("nikola check -f", shell=True) - #self.assertEqual(rv, 1) +class TestCheckFailure(DemoBuildTest): + """The demo build should pass 'nikola check'""" + + def test_check_links_fail(self): + with cd(self.target_dir): + os.unlink(os.path.join("output", "archive.html")) + self.assertRaises( + subprocess.CalledProcessError, + check_output, "nikola check -f", shell=True + ) + + def test_check_files_fail(self): + with cd(self.target_dir): + with codecs.open(os.path.join("output", "foobar"), "wb+", "utf8") as outf: + outf.write("foo") + self.assertRaises( + subprocess.CalledProcessError, + check_output, "nikola check -f", shell=True + ) class RelativeLinkTest2(DemoBuildTest): @@ -192,8 +282,10 @@ class RelativeLinkTest2(DemoBuildTest): conf_path = os.path.join(self.target_dir, "conf.py") with codecs.open(conf_path, "rb", "utf-8") as inf: data = inf.read() - data = data.replace('("stories/*.txt", "stories", "story.tmpl", False),', - '("stories/*.txt", "", "story.tmpl", False),') + data = data.replace('("stories/*.txt", "stories", "story.tmpl"),', + '("stories/*.txt", "", "story.tmpl"),') + data = data.replace('("stories/*.rst", "stories", "story.tmpl"),', + '("stories/*.rst", "", "story.tmpl"),') data = data.replace('# INDEX_PATH = ""', 'INDEX_PATH = "blog"') with codecs.open(conf_path, "wb+", "utf8") as outf: @@ -202,8 +294,6 @@ class RelativeLinkTest2(DemoBuildTest): def test_relative_links(self): """Check that the links in a story are correct""" - conf_path = os.path.join(self.target_dir, "conf.py") - data = open(conf_path).read() test_path = os.path.join(self.target_dir, "output", "about-nikola.html") flag = False with open(test_path, "rb") as inf: @@ -215,3 +305,34 @@ class RelativeLinkTest2(DemoBuildTest): flag = True # But I also need to be sure it is there! self.assertTrue(flag) + + def test_index_in_sitemap(self): + """Test that the correct path is in sitemap, and not the wrong one.""" + sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml") + sitemap_data = codecs.open(sitemap_path, "r", "utf8").read() + self.assertFalse('<loc>http://getnikola.com/</loc>' in sitemap_data) + self.assertTrue('<loc>http://getnikola.com/blog/index.html</loc>' in sitemap_data) + + +class MonthlyArchiveTest(DemoBuildTest): + """Check that the monthly archives build and are correct.""" + + @classmethod + def patch_site(self): + """Set the SITE_URL to have a path""" + conf_path = os.path.join(self.target_dir, "conf.py") + with codecs.open(conf_path, "rb", "utf-8") as inf: + data = inf.read() + data = data.replace('# CREATE_MONTHLY_ARCHIVE = False', + 'CREATE_MONTHLY_ARCHIVE = True') + with codecs.open(conf_path, "wb+", "utf8") as outf: + outf.write(data) + outf.flush() + + def test_monthly_archive(self): + """See that it builds""" + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'target', 'output', '2012', '03', 'index.html'))) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_locale.py b/tests/test_locale.py new file mode 100644 index 0000000..93df2ae --- /dev/null +++ b/tests/test_locale.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import sys + +# needed if @unittest.expectedFailure is used +try: + import unittest2 as unittest +except: + import unittest + +import nikola.nikola +import nikola.utils +from .base import LocaleSupportInTesting + +LocaleSupportInTesting.initialize_locales_for_testing('bilingual') +lang_11, loc_11 = LocaleSupportInTesting.langlocales['default'] +lang_22, loc_22 = LocaleSupportInTesting.langlocales['other'] + + +# these are candidates to hardcoded locales, using str() for py2x setlocale +loc_C = str('C') +loc_Cutf8 = str('C.utf8') + +if sys.platform != 'win32': + nikola.nikola.workaround_empty_LC_ALL_posix() + + +class TestHarcodedFallbacks(unittest.TestCase): + def test_hardcoded_fallbacks_work(self): + # keep in sync with nikola.valid_locale_fallback + if sys.platform == 'win32': + self.assertTrue(nikola.nikola.is_valid_locale(str('English'))) + self.assertTrue(nikola.nikola.is_valid_locale(str('C'))) + else: + # the 1st is desired in Travis, not a problem if fails in user host + self.assertTrue(nikola.nikola.is_valid_locale(str('en_US.utf8'))) + # this is supposed to be always valid, and we need an universal + # fallback. Failure is not a problem in user host if he / she + # sets a valid (in his host) locale_fallback. + self.assertTrue(nikola.nikola.is_valid_locale(str('C'))) + + +class TestConfigLocale(unittest.TestCase): + + def test_implicit_fallback(self): + locale_fallback = None + sanitized_fallback = nikola.nikola.valid_locale_fallback( + desired_locale=locale_fallback) + self.assertTrue(nikola.nikola.is_valid_locale(sanitized_fallback)) + + def test_explicit_good_fallback(self): + locale_fallback = loc_22 + sanitized_fallback = nikola.nikola.valid_locale_fallback( + desired_locale=locale_fallback) + self.assertEquals(sanitized_fallback, locale_fallback) + + def test_explicit_bad_fallback(self): + locale_fallback = str('xyz') + sanitized_fallback = nikola.nikola.valid_locale_fallback( + desired_locale=locale_fallback) + self.assertTrue(nikola.nikola.is_valid_locale(sanitized_fallback)) + + def test_explicit_good_default(self): + locale_fallback, locale_default, LOCALES, translations = ( + loc_22, + loc_11, + {}, + {lang_11: ''}, + ) + fallback, default, locales = nikola.nikola.sanitized_locales( + locale_fallback, + locale_default, + LOCALES, + translations) + self.assertEquals(fallback, locale_fallback) + self.assertEquals(default, locale_default) + + def test_explicit_bad_default(self): + locale_fallback, locale_default, LOCALES, translations = ( + loc_22, + str('xyz'), + {}, + {lang_11: ''}, + ) + fallback, default, locales = nikola.nikola.sanitized_locales( + locale_fallback, + locale_default, + LOCALES, + translations) + self.assertEquals(fallback, locale_fallback) + self.assertEquals(default, fallback) + + def test_extra_locales_deleted(self): + locale_fallback, locale_default, LOCALES, translations = ( + loc_22, + None, + {'@z': loc_22}, + {lang_11: ''}, + ) + fallback, default, locales = nikola.nikola.sanitized_locales( + locale_fallback, + locale_default, + LOCALES, + translations) + self.assertTrue('@z' not in locales) + + def test_explicit_good_locale_retained(self): + locale_fallback, locale_default, LOCALES, translations = ( + loc_22, + loc_22, + {lang_11: loc_11}, + {lang_11: ''}, + ) + fallback, default, locales = nikola.nikola.sanitized_locales( + locale_fallback, + locale_default, + LOCALES, + translations) + self.assertEquals(locales[lang_11], str(LOCALES[lang_11])) + + def test_explicit_bad_locale_replaced_with_fallback(self): + locale_fallback, locale_default, LOCALES, translations = ( + loc_22, + loc_11, + {lang_11: str('xyz')}, + {lang_11: ''}, + ) + fallback, default, locales = nikola.nikola.sanitized_locales( + locale_fallback, + locale_default, + LOCALES, + translations) + self.assertEquals(locales['en'], locale_fallback) + + def test_impicit_locale_when_default_locale_defined(self): + locale_fallback, locale_default, LOCALES, translations = ( + loc_11, + loc_22, + {}, + {lang_11: ''}, + ) + fallback, default, locales = nikola.nikola.sanitized_locales( + locale_fallback, + locale_default, + LOCALES, + translations) + self.assertEquals(locales['en'], locale_default) + + def test_impicit_locale_when_default_locale_is_not_defined(self): + # legacy mode, compat v6.0.4 : guess locale from lang + locale_fallback, locale_default, LOCALES, translations = ( + loc_22, + None, + {}, + {lang_11: ''}, + ) + fallback, default, locales = nikola.nikola.sanitized_locales( + locale_fallback, + locale_default, + LOCALES, + translations) + if sys.platform == 'win32': + guess_locale_for_lang = nikola.nikola.guess_locale_from_lang_windows + else: + guess_locale_for_lang = nikola.nikola.guess_locale_from_lang_posix + + self.assertEquals(locales[lang_11], guess_locale_for_lang(lang_11)) + + +class TestCalendarRelated(unittest.TestCase): + def test_type_of_month_name(self): + """validate assumption calendar month name is of type str + + Yes, both in windows and linuxTravis, py 26, 27, 33 + """ + import calendar + if sys.version_info[0] == 3: # Python 3 + with calendar.different_locale(loc_11): + s = calendar.month_name[1] + else: # Python 2 + with calendar.TimeEncoding(loc_11): + s = calendar.month_name[1] + self.assertTrue(type(s) == str) + + +class TestLocaleBorg(unittest.TestCase): + def test_initial_lang(self): + lang_11, loc_11 = LocaleSupportInTesting.langlocales['default'] + lang_22, loc_22 = LocaleSupportInTesting.langlocales['other'] + + locales = {lang_11: loc_11, lang_22: loc_22} + initial_lang = lang_22 + nikola.utils.LocaleBorg.initialize(locales, initial_lang) + self.assertEquals(initial_lang, nikola.utils.LocaleBorg().current_lang) + + def test_remembers_last_lang(self): + lang_11, loc_11 = LocaleSupportInTesting.langlocales['default'] + lang_22, loc_22 = LocaleSupportInTesting.langlocales['other'] + + locales = {lang_11: loc_11, lang_22: loc_22} + initial_lang = lang_22 + nikola.utils.LocaleBorg.initialize(locales, initial_lang) + + nikola.utils.LocaleBorg().set_locale(lang_11) + self.assertTrue(nikola.utils.LocaleBorg().current_lang, lang_11) + + def test_services_ensure_initialization(self): + nikola.utils.LocaleBorg.reset() + self.assertRaises(Exception, nikola.utils.LocaleBorg) + + def test_services_reject_dumb_wrong_call(self): + lang_11, loc_11 = LocaleSupportInTesting.langlocales['default'] + nikola.utils.LocaleBorg.reset() + self.assertRaises(Exception, nikola.utils.LocaleBorg.set_locale, lang_11) + self.assertRaises(Exception, getattr, nikola.utils.LocaleBorg, 'current_lang') + + def test_set_locale_raises_on_invalid_lang(self): + lang_11, loc_11 = LocaleSupportInTesting.langlocales['default'] + lang_22, loc_22 = LocaleSupportInTesting.langlocales['other'] + + locales = {lang_11: loc_11, lang_22: loc_22} + initial_lang = lang_22 + nikola.utils.LocaleBorg.initialize(locales, initial_lang) + self.assertRaises(KeyError, nikola.utils.LocaleBorg().set_locale, '@z') + + +class TestTestPreconditions(unittest.TestCase): + """If this fails the other test in this module are mostly nonsense, and + probably same for tests of multilingual features. + + Failure probably means the OS support for the failing locale is not + instaled or environmet variables NIKOLA_LOCALE_DEFAULT or + NIKOLA_LOCALE_OTHER with bad values. + """ + def test_langlocale_default_availability(self): + msg = "META ERROR: The pair lang, locale : {0} {1} is invalid" + self.assertTrue(nikola.nikola.is_valid_locale(loc_11), msg.format(lang_11, loc_11)) + + def test_langlocale_other_availability(self): + msg = "META ERROR: The pair lang, locale : {0} {1} is invalid" + self.assertTrue(nikola.nikola.is_valid_locale(loc_22), msg.format(lang_22, loc_22)) diff --git a/tests/test_plugin_importing.py b/tests/test_plugin_importing.py index 677dde8..5009f88 100644 --- a/tests/test_plugin_importing.py +++ b/tests/test_plugin_importing.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import -from context import nikola # NOQA +from .context import nikola # NOQA import unittest class ImportPluginsTest(unittest.TestCase): def test_importing_command_import_wordpress(self): - import nikola.plugins.command_import_wordpress # NOQA + import nikola.plugins.command.import_wordpress # NOQA def test_importing_compile_rest(self): - import nikola.plugins.compile_rest # NOQA + import nikola.plugins.compile.rest # NOQA def test_importing_plugin_compile_markdown(self): - import nikola.plugins.compile_markdown # NOQA + import nikola.plugins.compile.markdown # NOQA diff --git a/tests/test_rss_feeds.py b/tests/test_rss_feeds.py index 5b9b981..169e1e7 100644 --- a/tests/test_rss_feeds.py +++ b/tests/test_rss_feeds.py @@ -1,25 +1,37 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals -import unittest +from __future__ import unicode_literals, absolute_import +from collections import defaultdict +from io import StringIO import os import re -from io import StringIO +import unittest import mock -from context import nikola +from .context import nikola # NOQA from lxml import etree +from .base import LocaleSupportInTesting + + +fake_conf = defaultdict(str) +fake_conf['TIMEZONE'] = None +fake_conf['DEFAULT_LANG'] = 'en' +fake_conf['TRANSLATIONS'] = {'en': ''} +fake_conf['BASE_URL'] = 'http://some.blog/' class RSSFeedTest(unittest.TestCase): def setUp(self): + LocaleSupportInTesting.initialize_locales_for_testing('unilingual') + self.blog_url = "http://some.blog" with mock.patch('nikola.post.get_meta', mock.Mock(return_value=({'title': 'post title', 'slug': 'awesome_article', 'date': '2012-10-01 22:41', + 'author': None, 'tags': 'tags', 'link': 'link', 'description': 'description'}))): @@ -29,14 +41,12 @@ class RSSFeedTest(unittest.TestCase): mock.Mock(return_value='some long text')): example_post = nikola.nikola.Post('source.file', - 'cache', + fake_conf, 'blog_folder', True, {'en': ''}, - 'en', - self.blog_url, - 'unused message.', - 'post.tmpl') + 'post.tmpl', + lambda *a: None) opener_mock = mock.mock_open() diff --git a/tests/test_rst_extensions.py b/tests/test_rst_compiler.py index 845d6a7..c5db8dd 100644 --- a/tests/test_rst_extensions.py +++ b/tests/test_rst_compiler.py @@ -25,36 +25,134 @@ always unquoted. """ -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import +import codecs try: from io import StringIO except ImportError: from StringIO import StringIO # NOQA +import os +import sys +import tempfile -from docutils.core import publish_parts +import docutils from lxml import html -import mock - +from nose.plugins.skip import SkipTest import unittest -import nikola.plugins.compile_rest -from nikola.utils import _reload -from base import BaseTestCase +from yapsy.PluginManager import PluginManager + +from nikola import utils +import nikola.plugins.compile.rest +from nikola.plugins.compile.rest import gist +from nikola.plugins.compile.rest import vimeo +import nikola.plugins.compile.rest.listing +from nikola.plugins.compile.rest.doc import Plugin as DocPlugin +from nikola.utils import _reload, STDERR_HANDLER +from nikola.plugin_categories import ( + Command, + Task, + LateTask, + TemplateSystem, + PageCompiler, + TaskMultiplier, + RestExtension, +) +from .base import BaseTestCase + + +class FakePost(object): + + def __init__(self, title, slug): + self._title = title + self._slug = slug + self._meta = {'slug': slug} + + def title(self): + return self._title + + def meta(self, key): + return self._meta[key] + + def permalink(self): + return '/posts/' + self._slug + + +class FakeSite(object): + def __init__(self): + self.template_system = self + self.config = { + 'DISABLED_PLUGINS': [], + 'EXTRA_PLUGINS': [], + 'DEFAULT_LANG': 'en', + } + self.EXTRA_PLUGINS = self.config['EXTRA_PLUGINS'] + self.plugin_manager = PluginManager(categories_filter={ + "Command": Command, + "Task": Task, + "LateTask": LateTask, + "TemplateSystem": TemplateSystem, + "PageCompiler": PageCompiler, + "TaskMultiplier": TaskMultiplier, + "RestExtension": RestExtension, + }) + self.loghandlers = [STDERR_HANDLER] + self.plugin_manager.setPluginInfoExtension('plugin') + if sys.version_info[0] == 3: + places = [ + os.path.join(os.path.dirname(utils.__file__), 'plugins'), + ] + else: + places = [ + os.path.join(os.path.dirname(utils.__file__), utils.sys_encode('plugins')), + ] + self.plugin_manager.setPluginPlaces(places) + self.plugin_manager.collectPlugins() + + self.timeline = [ + FakePost(title='Fake post', + slug='fake-post') + ] + + def render_template(self, name, _, context): + return('<img src="IMG.jpg">') class ReSTExtensionTestCase(BaseTestCase): """ Base class for testing ReST extensions """ - sample = None + sample = 'foo' + deps = None def setUp(self): + self.compiler = nikola.plugins.compile.rest.CompileRest() + self.compiler.set_site(FakeSite()) + return super(ReSTExtensionTestCase, self).setUp() + + def basic_test(self): """ Parse cls.sample into a HTML document tree """ - super(ReSTExtensionTestCase, self).setUp() self.setHtmlFromRst(self.sample) def setHtmlFromRst(self, rst): """ Create html output from rst string """ - self.html = publish_parts(rst, writer_name="html")["body"] + tmpdir = tempfile.mkdtemp() + inf = os.path.join(tmpdir, 'inf') + outf = os.path.join(tmpdir, 'outf') + depf = os.path.join(tmpdir, 'outf.dep') + with codecs.open(inf, 'wb+', 'utf8') as f: + f.write(rst) + self.html = self.compiler.compile_html(inf, outf) + with codecs.open(outf, 'r', 'utf8') as f: + self.html = f.read() + os.unlink(inf) + os.unlink(outf) + if os.path.isfile(depf): + with codecs.open(depf, 'r', 'utf8') as f: + self.assertEqual(self.deps, f.read()) + os.unlink(depf) + else: + self.assertEqual(self.deps, None) + os.rmdir(tmpdir) self.html_doc = html.parse(StringIO(self.html)) def assertHTMLContains(self, element, attributes=None, text=None): @@ -65,7 +163,7 @@ class ReSTExtensionTestCase(BaseTestCase): try: tag = next(self.html_doc.iter(element)) except StopIteration: - raise Exception("<{}> not in {}".format(element, self.html)) + raise Exception("<{0}> not in {1}".format(element, self.html)) else: if attributes: arg_attrs = set(attributes.items()) @@ -81,29 +179,42 @@ class ReSTExtensionTestCaseTestCase(ReSTExtensionTestCase): sample = '.. raw:: html\n\n <iframe src="foo" height="bar">spam</iframe>' def test_test(self): + self.basic_test() self.assertHTMLContains("iframe", attributes={"src": "foo"}, text="spam") self.assertRaises(Exception, self.assertHTMLContains, "eggs", {}) +class MathTestCase(ReSTExtensionTestCase): + sample = ':math:`e^{ix} = \cos x + i\sin x`' + + def test_mathjax(self): + """ Test that math is outputting MathJax.""" + self.basic_test() + self.assertHTMLContains("span", attributes={"class": "math"}, + text="\(e^{ix} = \cos x + i\sin x\)") + + class GistTestCase(ReSTExtensionTestCase): """ Test GitHubGist. We will replace get_raw_gist() and get_raw_gist_with_filename() monkeypatching the GitHubGist class for avoiding network dependency """ - gist_type = nikola.plugins.compile_rest.GitHubGist + gist_type = gist.GitHubGist sample = '.. gist:: fake_id\n :file: spam.py' sample_without_filename = '.. gist:: fake_id2' def setUp(self): """ Patch GitHubGist for avoiding network dependency """ + super(GistTestCase, self).setUp() self.gist_type.get_raw_gist_with_filename = lambda *_: 'raw_gist_file' self.gist_type.get_raw_gist = lambda *_: "raw_gist" - _reload(nikola.plugins.compile_rest) + _reload(nikola.plugins.compile.rest) def test_gist(self): """ Test the gist directive with filename """ + raise SkipTest self.setHtmlFromRst(self.sample) output = 'https://gist.github.com/fake_id.js?file=spam.py' self.assertHTMLContains("script", attributes={"src": output}) @@ -111,6 +222,7 @@ class GistTestCase(ReSTExtensionTestCase): def test_gist_without_filename(self): """ Test the gist directive without filename """ + raise SkipTest self.setHtmlFromRst(self.sample_without_filename) output = 'https://gist.github.com/fake_id2.js' self.assertHTMLContains("script", attributes={"src": output}) @@ -126,6 +238,7 @@ class GistIntegrationTestCase(ReSTExtensionTestCase): def test_gist_integration(self): """ Fetch contents of the gist from GH and render in a noscript tag """ + self.basic_test() text = ('Be alone, that is the secret of invention: be alone, that is' ' when ideas are born. -- Nikola Tesla') self.assertHTMLContains('pre', text=text) @@ -138,6 +251,7 @@ class SlidesTestCase(ReSTExtensionTestCase): def test_slides(self): """ Test the slides js generation and img tag creation """ + self.basic_test() self.assertHTMLContains("img", attributes={"src": "IMG.jpg"}) @@ -148,6 +262,7 @@ class SoundCloudTestCase(ReSTExtensionTestCase): def test_soundcloud(self): """ Test SoundCloud iframe tag generation """ + self.basic_test() self.assertHTMLContains("iframe", attributes={"src": ("https://w.soundcloud.com" "/player/?url=http://" @@ -166,12 +281,13 @@ class VimeoTestCase(ReSTExtensionTestCase): def setUp(self): """ Disable query of the vimeo api over the wire """ - nikola.plugins.compile_rest.Vimeo.request_size = False + vimeo.Vimeo.request_size = False super(VimeoTestCase, self).setUp() - _reload(nikola.plugins.compile_rest) + _reload(nikola.plugins.compile.rest) def test_vimeo(self): """ Test Vimeo iframe tag generation """ + self.basic_test() self.assertHTMLContains("iframe", attributes={"src": ("http://player.vimeo.com/" "video/VID"), @@ -185,6 +301,7 @@ class YoutubeTestCase(ReSTExtensionTestCase): def test_youtube(self): """ Test Youtube iframe tag generation """ + self.basic_test() self.assertHTMLContains("iframe", attributes={"src": ("http://www.youtube.com/" "embed/YID?rel=0&hd=1&" @@ -195,28 +312,57 @@ class YoutubeTestCase(ReSTExtensionTestCase): class ListingTestCase(ReSTExtensionTestCase): """ Listing test case and CodeBlock alias tests """ - sample = '.. listing:: nikola.py python' + deps = None + sample1 = '.. listing:: nikola.py python\n\n' sample2 = '.. code-block:: python\n\n import antigravity' sample3 = '.. sourcecode:: python\n\n import antigravity' - opener_mock = mock.mock_open(read_data="import antigravity\n") - opener_mock.return_value.readlines.return_value = "import antigravity\n" - - def setUp(self): - """ Inject a mock open function for not generating a test site """ - self.f = StringIO("import antigravity\n") - #_reload(nikola.plugins.compile_rest) - - def test_listing(self): - """ Test that we can render a file object contents without errors """ - with mock.patch("nikola.plugins.compile_rest.listing.codecs_open", self.opener_mock, create=True): - self.setHtmlFromRst(self.sample) + #def test_listing(self): + ##""" Test that we can render a file object contents without errors """ + ##with cd(os.path.dirname(__file__)): + #self.deps = 'listings/nikola.py' + #self.setHtmlFromRst(self.sample1) def test_codeblock_alias(self): """ Test CodeBlock aliases """ - with mock.patch("nikola.plugins.compile_rest.listing.codecs_open", self.opener_mock, create=True): - self.setHtmlFromRst(self.sample2) - self.setHtmlFromRst(self.sample3) + self.deps = None + self.setHtmlFromRst(self.sample2) + self.setHtmlFromRst(self.sample3) + + +class DocTestCase(ReSTExtensionTestCase): + """ Ref role test case """ + + sample = 'Sample for testing my :doc:`doesnt-exist-post`' + sample1 = 'Sample for testing my :doc:`fake-post`' + sample2 = 'Sample for testing my :doc:`titled post <fake-post>`' + + def setUp(self): + # Initialize plugin, register role + self.plugin = DocPlugin() + self.plugin.set_site(FakeSite()) + # Hack to fix leaked state from integration tests + try: + f = docutils.parsers.rst.roles.role('doc', None, None, None)[0] + f.site = FakeSite() + except AttributeError: + pass + return super(DocTestCase, self).setUp() + + def test_doc_doesnt_exist(self): + self.assertRaises(Exception, self.assertHTMLContains, 'anything', {}) + + def test_doc(self): + self.setHtmlFromRst(self.sample1) + self.assertHTMLContains('a', + text='Fake post', + attributes={'href': '/posts/fake-post'}) + + def test_doc_titled(self): + self.setHtmlFromRst(self.sample2) + self.assertHTMLContains('a', + text='titled post', + attributes={'href': '/posts/fake-post'}) if __name__ == "__main__": diff --git a/tests/test_scheduling.py b/tests/test_scheduling.py new file mode 100644 index 0000000..264d1c5 --- /dev/null +++ b/tests/test_scheduling.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import +from .base import BaseTestCase +import datetime +from nose.plugins.skip import SkipTest +try: + from freezegun import freeze_time + _freeze_time = True +except ImportError: + _freeze_time = False + freeze_time = lambda x: lambda y: y + +FMT = '%Y/%m/%d %H:%M:%S' +NOW = '2013/08/22 10:00:00' # Thursday +TODAY = datetime.datetime.strptime(NOW, FMT) +RULE_TH = 'RRULE:FREQ=WEEKLY;BYDAY=TH' +RULE_FR = 'RRULE:FREQ=WEEKLY;BYDAY=FR' + + +class TestScheduling(BaseTestCase): + + @classmethod + def setUp(self): + if not _freeze_time: + raise SkipTest('freezegun not installed') + + @freeze_time(NOW) + def test_get_date(self): + from nikola.plugins.command.new_post import get_date + + #### NOW does not match rule ######################################### + ## No last date + expected = TODAY.replace(day=23).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_FR)) + self.assertEqual(expected, get_date(True, RULE_FR, force_today=True)) + + ## Last date in the past; doesn't match rule + date = TODAY.replace(hour=7) + expected = TODAY.replace(day=23, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_FR, date)) + self.assertEqual(expected, get_date(True, RULE_FR, date, True)) + + ## Last date in the future; doesn't match rule + date = TODAY.replace(day=24, hour=7) + expected = TODAY.replace(day=30, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_FR, date)) + self.assertEqual(expected, get_date(True, RULE_FR, date, True)) + + ## Last date in the past; matches rule + date = TODAY.replace(day=16, hour=8) + expected = TODAY.replace(day=23, hour=8).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_FR, date)) + self.assertEqual(expected, get_date(True, RULE_FR, date, True)) + + ## Last date in the future; matches rule + date = TODAY.replace(day=23, hour=18) + expected = TODAY.replace(day=30, hour=18).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_FR, date)) + self.assertEqual(expected, get_date(True, RULE_FR, date, True)) + + #### NOW matches rule ################################################ + ## Not scheduling should return NOW + self.assertEqual(NOW, get_date(False, RULE_TH)) + ## No last date + self.assertEqual(NOW, get_date(True, RULE_TH)) + self.assertEqual(NOW, get_date(True, RULE_TH, force_today=True)) + + ## Last date in the past; doesn't match rule + ### Corresponding time has already passed, today + date = TODAY.replace(day=21, hour=7) + expected = TODAY.replace(day=29, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + expected = TODAY.replace(day=22, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date, True)) + ### Corresponding time has not passed today + date = TODAY.replace(day=21, hour=18) + expected = TODAY.replace(day=22, hour=18).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + self.assertEqual(expected, get_date(True, RULE_TH, date, True)) + + ## Last date in the future; doesn't match rule + ### Corresponding time has already passed, today + date = TODAY.replace(day=24, hour=7) + expected = TODAY.replace(day=29, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + self.assertEqual(expected, get_date(True, RULE_TH, date, True)) + ### Corresponding time has not passed today + date = TODAY.replace(day=24, hour=18) + expected = TODAY.replace(day=29, hour=18).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + self.assertEqual(expected, get_date(True, RULE_TH, date, True)) + + ## Last date in the past; matches rule + ### Corresponding time has already passed, today + date = TODAY.replace(day=15, hour=7) + expected = TODAY.replace(day=29, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + expected = TODAY.replace(day=22, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date, True)) + ### Corresponding time has already passed, today; rule specifies HOUR + date = TODAY.replace(day=15, hour=7) + expected = TODAY.replace(day=29, hour=9).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH + ';BYHOUR=9', date)) + expected = TODAY.replace(day=22, hour=9).strftime(FMT) + self.assertEqual(expected, + get_date(True, RULE_TH + ';BYHOUR=9', date, True)) + ### Corresponding time has not passed today + date = TODAY.replace(day=15, hour=18) + expected = TODAY.replace(day=22, hour=18).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + self.assertEqual(expected, get_date(True, RULE_TH, date, True)) + + ## Last date in the future; matches rule + ### Corresponding time has already passed, today + date = TODAY.replace(day=29, hour=7) + expected = TODAY.replace(day=5, month=9, hour=7).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + ### Corresponding time has not passed today + date = TODAY.replace(day=22, hour=18) + expected = TODAY.replace(day=29, hour=18).strftime(FMT) + self.assertEqual(expected, get_date(True, RULE_TH, date)) + self.assertEqual(expected, get_date(True, RULE_TH, date, True)) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/wordpress_export_example.xml b/tests/wordpress_export_example.xml index e697a5b..5fd0a90 100644 --- a/tests/wordpress_export_example.xml +++ b/tests/wordpress_export_example.xml @@ -230,5 +230,62 @@ A listing with another listing inside. </wp:postmeta> </item> -</channel> + <item> + <title>Screenshot - 2012-12-19</title> + <link>http://some.blog/2012/12/wintermodus/2012-12-19-1355925145_1024x600_scrot/</link> + <pubDate>Wed, 19 Dec 2012 13:53:19 +0000</pubDate> + <dc:creator>Niko</dc:creator> + <guid isPermaLink="false">http://some.blog/wp-content/uploads/2012/12/2012-12-19-355925145_1024x600_scrot.png</guid> + <description></description> + <content:encoded><![CDATA[]]></content:encoded> + <excerpt:encoded><![CDATA[]]></excerpt:encoded> + <wp:post_id>2271</wp:post_id> + <wp:post_date>2012-12-19 14:53:19</wp:post_date> + <wp:post_date_gmt>2012-12-19 13:53:19</wp:post_date_gmt> + <wp:comment_status>open</wp:comment_status> + <wp:ping_status>open</wp:ping_status> + <wp:post_name>2012-12-19-1355925145_1024x600_scrot</wp:post_name> + <wp:status>inherit</wp:status> + <wp:post_parent>2270</wp:post_parent> + <wp:menu_order>0</wp:menu_order> + <wp:post_type>attachment</wp:post_type> + <wp:post_password></wp:post_password> + <wp:is_sticky>0</wp:is_sticky> + <wp:attachment_url>http://some.blog/wp-content/uploads/2012/12/2012-12-19-355925145_1024x600_scrot.png</wp:attachment_url> + <wp:postmeta> + <wp:meta_key>_wp_attached_file</wp:meta_key> + <wp:meta_value><![CDATA[2012/12/2012-12-19-1355925145_1024x600_scrot.png]]></wp:meta_value> + </wp:postmeta> + <wp:postmeta> + <wp:meta_key>_wp_attachment_metadata</wp:meta_key> + <wp:meta_value><![CDATA[a:5:{s:5:"width";i:1024;s:6:"height";i:600;s:4:"file";s:48:"2012/12/2012-12-19-1355925145_1024x600_scrot.png";s:5:"sizes";a:9:{s:9:"thumbnail";a:4:{s:4:"file";s:48:"2012-12-19-1355925145_1024x600_scrot-150x150.png";s:5:"width";i:150;s:6:"height";i:150;s:9:"mime-type";s:9:"image/png";}s:6:"medium";a:4:{s:4:"file";s:48:"2012-12-19-1355925145_1024x600_scrot-300x175.png";s:5:"width";i:300;s:6:"height";i:175;s:9:"mime-type";s:9:"image/png";}s:12:"mosaic-thumb";a:4:{s:4:"file";s:46:"2012-12-19-1355925145_1024x600_scrot-96x96.png";s:5:"width";i:96;s:6:"height";i:96;s:9:"mime-type";s:9:"image/png";}s:13:"gallery-thumb";a:4:{s:4:"file";s:46:"2012-12-19-1355925145_1024x600_scrot-96x96.png";s:5:"width";i:96;s:6:"height";i:96;s:9:"mime-type";s:9:"image/png";}s:9:"widget-24";a:4:{s:4:"file";s:46:"2012-12-19-1355925145_1024x600_scrot-24x24.png";s:5:"width";i:24;s:6:"height";i:24;s:9:"mime-type";s:9:"image/png";}s:9:"widget-32";a:4:{s:4:"file";s:46:"2012-12-19-1355925145_1024x600_scrot-36x36.png";s:5:"width";i:36;s:6:"height";i:36;s:9:"mime-type";s:9:"image/png";}s:9:"widget-48";a:4:{s:4:"file";s:46:"2012-12-19-1355925145_1024x600_scrot-48x48.png";s:5:"width";i:48;s:6:"height";i:48;s:9:"mime-type";s:9:"image/png";}s:9:"widget-64";a:4:{s:4:"file";s:46:"2012-12-19-1355925145_1024x600_scrot-64x64.png";s:5:"width";i:64;s:6:"height";i:64;s:9:"mime-type";s:9:"image/png";}s:9:"widget-96";a:4:{s:4:"file";s:46:"2012-12-19-1355925145_1024x600_scrot-96x96.png";s:5:"width";i:96;s:6:"height";i:96;s:9:"mime-type";s:9:"image/png";}}s:10:"image_meta";a:10:{s:8:"aperture";i:0;s:6:"credit";s:0:"";s:6:"camera";s:0:"";s:7:"caption";s:0:"";s:17:"created_timestamp";i:0;s:9:"copyright";s:0:"";s:12:"focal_length";i:0;s:3:"iso";i:0;s:13:"shutter_speed";i:0;s:5:"title";s:0:"";}}]]></wp:meta_value> + </wp:postmeta> + </item> + + <item> + <title>Image Link Rewriting</title> + <link>http://some.blog/2012/12/wintermodus/</link> + <pubDate>Wed, 19 Dec 2012 13:55:10 +0000</pubDate> + <dc:creator>Niko</dc:creator> + <guid isPermaLink="false">http://some.blog/?p=2270</guid> + <description></description> + <content:encoded><![CDATA[Some image upload. The links to this and the src of the img-tag should be rewritten correctly. + + <a href="http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot.png"><img class="aligncenter size-medium wp-image-2271" alt="Netbook Screenshot - 2012-12-19" src="http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-300x175.ng" width="300" height="175" /></a>]]></content:encoded> + <excerpt:encoded><![CDATA[]]></excerpt:encoded> + <wp:post_id>2270</wp:post_id> + <wp:post_date>2012-12-19 14:55:10</wp:post_date> + <wp:post_date_gmt>2012-12-19 13:55:10</wp:post_date_gmt> + <wp:comment_status>open</wp:comment_status> + <wp:ping_status>open</wp:ping_status> + <wp:post_name>image-link-rewriting</wp:post_name> + <wp:status>publish</wp:status> + <wp:post_parent>0</wp:post_parent> + <wp:menu_order>0</wp:menu_order> + <wp:post_type>post</wp:post_type> + <wp:post_password></wp:post_password> + <wp:is_sticky>0</wp:is_sticky> + <category domain="category" nicename="linux"><![CDATA[Linux]]></category> + </item> + </channel> </rss> |
