2012-05-19 23:44:37 +02:00
|
|
|
import locale
|
2012-11-30 15:10:51 +01:00
|
|
|
import logging
|
2015-06-16 09:25:09 +02:00
|
|
|
import os
|
2013-07-05 01:08:45 +02:00
|
|
|
import subprocess
|
2015-06-16 09:25:09 +02:00
|
|
|
import sys
|
2022-09-15 16:51:34 -07:00
|
|
|
import unittest
|
2019-11-17 19:19:37 +03:00
|
|
|
from collections.abc import Sequence
|
2015-06-16 09:25:09 +02:00
|
|
|
from shutil import rmtree
|
|
|
|
|
from tempfile import mkdtemp
|
2012-03-11 01:59:58 +01:00
|
|
|
|
|
|
|
|
from pelican import Pelican
|
2014-10-18 13:11:59 -07:00
|
|
|
from pelican.generators import StaticGenerator
|
2012-03-11 01:59:58 +01:00
|
|
|
from pelican.settings import read_settings
|
2022-09-15 16:51:34 -07:00
|
|
|
from pelican.tests.support import (
|
|
|
|
|
LoggedTestCase,
|
|
|
|
|
diff_subproc,
|
|
|
|
|
locale_available,
|
2023-10-28 14:49:55 +03:00
|
|
|
mute,
|
|
|
|
|
skipIfNoExecutable,
|
2022-09-15 16:51:34 -07:00
|
|
|
)
|
2012-03-11 01:59:58 +01:00
|
|
|
|
2012-03-30 13:44:57 +02:00
|
|
|
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
2013-01-04 14:46:17 -05:00
|
|
|
SAMPLES_PATH = os.path.abspath(os.path.join(
|
2015-06-16 09:25:09 +02:00
|
|
|
CURRENT_DIR, os.pardir, os.pardir, 'samples'))
|
2013-01-04 14:46:17 -05:00
|
|
|
OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output'))
|
2012-03-11 01:59:58 +01:00
|
|
|
|
|
|
|
|
INPUT_PATH = os.path.join(SAMPLES_PATH, "content")
|
|
|
|
|
SAMPLE_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf.py")
|
2014-06-26 00:51:45 -04:00
|
|
|
SAMPLE_FR_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf_FR.py")
|
2012-03-11 01:59:58 +01:00
|
|
|
|
|
|
|
|
|
2012-10-09 00:56:15 +02:00
|
|
|
def recursiveDiff(dcmp):
|
|
|
|
|
diff = {
|
2015-06-16 09:25:09 +02:00
|
|
|
'diff_files': [os.path.join(dcmp.right, f) for f in dcmp.diff_files],
|
|
|
|
|
'left_only': [os.path.join(dcmp.right, f) for f in dcmp.left_only],
|
|
|
|
|
'right_only': [os.path.join(dcmp.right, f) for f in dcmp.right_only],
|
|
|
|
|
}
|
2012-10-09 00:56:15 +02:00
|
|
|
for sub_dcmp in dcmp.subdirs.values():
|
2013-01-11 02:57:43 +01:00
|
|
|
for k, v in recursiveDiff(sub_dcmp).items():
|
2012-10-09 00:56:15 +02:00
|
|
|
diff[k] += v
|
|
|
|
|
return diff
|
|
|
|
|
|
|
|
|
|
|
2013-01-18 07:27:35 -05:00
|
|
|
class TestPelican(LoggedTestCase):
|
2012-03-11 01:59:58 +01:00
|
|
|
# general functional testing for pelican. Basically, this test case tries
|
|
|
|
|
# to run pelican in different situations and see how it behaves
|
|
|
|
|
|
2012-05-12 02:03:18 +02:00
|
|
|
def setUp(self):
|
2019-11-18 20:28:48 +03:00
|
|
|
super().setUp()
|
2013-03-26 00:38:07 -05:00
|
|
|
self.temp_path = mkdtemp(prefix='pelicantests.')
|
2014-02-15 21:20:51 +01:00
|
|
|
self.temp_cache = mkdtemp(prefix='pelican_cache.')
|
2013-03-10 20:11:36 -07:00
|
|
|
self.maxDiff = None
|
2014-04-15 16:36:29 +02:00
|
|
|
self.old_locale = locale.setlocale(locale.LC_ALL)
|
2020-04-26 09:55:08 +02:00
|
|
|
locale.setlocale(locale.LC_ALL, 'C')
|
2012-05-12 02:03:18 +02:00
|
|
|
|
|
|
|
|
def tearDown(self):
|
2020-04-16 11:11:24 +03:00
|
|
|
read_settings() # cleanup PYGMENTS_RST_OPTIONS
|
2012-05-12 02:03:18 +02:00
|
|
|
rmtree(self.temp_path)
|
2014-02-15 21:20:51 +01:00
|
|
|
rmtree(self.temp_cache)
|
2012-05-19 23:44:37 +02:00
|
|
|
locale.setlocale(locale.LC_ALL, self.old_locale)
|
2019-11-18 20:28:48 +03:00
|
|
|
super().tearDown()
|
2012-05-12 02:03:18 +02:00
|
|
|
|
2022-09-15 16:51:34 -07:00
|
|
|
def assertDirsEqual(self, left_path, right_path, msg=None):
|
|
|
|
|
"""
|
|
|
|
|
Check if the files are the same (ignoring whitespace) below both paths.
|
|
|
|
|
"""
|
|
|
|
|
proc = diff_subproc(left_path, right_path)
|
|
|
|
|
|
|
|
|
|
out, err = proc.communicate()
|
|
|
|
|
if proc.returncode != 0:
|
|
|
|
|
msg = self._formatMessage(
|
|
|
|
|
msg,
|
2023-10-28 14:49:55 +03:00
|
|
|
"%s and %s differ:\nstdout:\n%s\nstderr\n%s" %
|
|
|
|
|
(left_path, right_path, out, err)
|
2022-09-15 16:51:34 -07:00
|
|
|
)
|
|
|
|
|
raise self.failureException(msg)
|
2013-07-05 01:08:45 +02:00
|
|
|
|
2014-10-18 13:11:59 -07:00
|
|
|
def test_order_of_generators(self):
|
2014-10-31 17:21:15 -07:00
|
|
|
# StaticGenerator must run last, so it can identify files that
|
|
|
|
|
# were skipped by the other generators, and so static files can
|
|
|
|
|
# have their output paths overridden by the {attach} link syntax.
|
2014-10-18 13:11:59 -07:00
|
|
|
|
|
|
|
|
pelican = Pelican(settings=read_settings(path=None))
|
2020-11-22 16:13:51 +00:00
|
|
|
generator_classes = pelican._get_generator_classes()
|
2014-10-18 13:11:59 -07:00
|
|
|
|
2015-06-16 09:25:09 +02:00
|
|
|
self.assertTrue(
|
|
|
|
|
generator_classes[-1] is StaticGenerator,
|
2014-10-18 13:11:59 -07:00
|
|
|
"StaticGenerator must be the last generator, but it isn't!")
|
2015-06-16 09:25:09 +02:00
|
|
|
self.assertIsInstance(
|
2019-11-17 19:19:37 +03:00
|
|
|
generator_classes, Sequence,
|
2020-11-22 16:13:51 +00:00
|
|
|
"_get_generator_classes() must return a Sequence to preserve order")
|
2014-10-18 13:11:59 -07:00
|
|
|
|
2023-10-28 14:49:55 +03:00
|
|
|
@skipIfNoExecutable(['git', '--version'])
|
2012-03-11 01:59:58 +01:00
|
|
|
def test_basic_generation_works(self):
|
|
|
|
|
# when running pelican without settings, it should pick up the default
|
2012-11-24 00:46:03 +01:00
|
|
|
# ones and generate correct output without raising any exception
|
2013-01-04 11:22:49 -05:00
|
|
|
settings = read_settings(path=None, override={
|
2012-11-24 00:46:03 +01:00
|
|
|
'PATH': INPUT_PATH,
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
2014-04-27 08:53:56 +02:00
|
|
|
'CACHE_PATH': self.temp_cache,
|
2013-01-11 21:24:04 +01:00
|
|
|
'LOCALE': locale.normalize('en_US'),
|
2015-06-16 09:25:09 +02:00
|
|
|
})
|
2012-11-24 00:46:03 +01:00
|
|
|
pelican = Pelican(settings=settings)
|
2013-04-19 13:35:20 -04:00
|
|
|
mute(True)(pelican.run)()
|
2015-06-16 09:25:09 +02:00
|
|
|
self.assertDirsEqual(
|
2022-09-15 16:51:34 -07:00
|
|
|
self.temp_path, os.path.join(OUTPUT_PATH, 'basic')
|
|
|
|
|
)
|
2013-01-18 07:27:35 -05:00
|
|
|
self.assertLogCountEqual(
|
2018-07-11 15:54:47 +02:00
|
|
|
count=1,
|
2012-11-30 15:10:51 +01:00
|
|
|
msg="Unable to find.*skipping url replacement",
|
2013-01-18 07:27:35 -05:00
|
|
|
level=logging.WARNING)
|
2012-05-12 02:03:18 +02:00
|
|
|
|
2023-10-28 14:49:55 +03:00
|
|
|
@skipIfNoExecutable(['git', '--version'])
|
2012-05-12 02:03:18 +02:00
|
|
|
def test_custom_generation_works(self):
|
|
|
|
|
# the same thing with a specified set of settings should work
|
2013-01-04 11:22:49 -05:00
|
|
|
settings = read_settings(path=SAMPLE_CONFIG, override={
|
2012-10-16 01:35:35 +02:00
|
|
|
'PATH': INPUT_PATH,
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
2014-04-27 08:53:56 +02:00
|
|
|
'CACHE_PATH': self.temp_cache,
|
2020-04-12 09:38:35 -05:00
|
|
|
'LOCALE': locale.normalize('en_US.UTF-8'),
|
2015-06-16 09:25:09 +02:00
|
|
|
})
|
2012-10-16 01:35:35 +02:00
|
|
|
pelican = Pelican(settings=settings)
|
2013-04-19 13:35:20 -04:00
|
|
|
mute(True)(pelican.run)()
|
2015-06-16 09:25:09 +02:00
|
|
|
self.assertDirsEqual(
|
2022-09-15 16:51:34 -07:00
|
|
|
self.temp_path, os.path.join(OUTPUT_PATH, 'custom')
|
|
|
|
|
)
|
2013-08-24 13:42:55 +01:00
|
|
|
|
2023-10-28 14:49:55 +03:00
|
|
|
@skipIfNoExecutable(['git', '--version'])
|
2014-04-27 10:25:57 +02:00
|
|
|
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
|
|
|
|
locale_available('French'), 'French locale needed')
|
|
|
|
|
def test_custom_locale_generation_works(self):
|
|
|
|
|
'''Test that generation with fr_FR.UTF-8 locale works'''
|
|
|
|
|
if sys.platform == 'win32':
|
2020-04-26 09:55:08 +02:00
|
|
|
our_locale = 'French'
|
2014-04-27 10:25:57 +02:00
|
|
|
else:
|
2020-04-26 09:55:08 +02:00
|
|
|
our_locale = 'fr_FR.UTF-8'
|
2014-04-27 10:25:57 +02:00
|
|
|
|
|
|
|
|
settings = read_settings(path=SAMPLE_FR_CONFIG, override={
|
|
|
|
|
'PATH': INPUT_PATH,
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
|
|
|
|
'CACHE_PATH': self.temp_cache,
|
|
|
|
|
'LOCALE': our_locale,
|
2015-06-16 09:25:09 +02:00
|
|
|
})
|
2014-04-27 10:25:57 +02:00
|
|
|
pelican = Pelican(settings=settings)
|
|
|
|
|
mute(True)(pelican.run)()
|
2015-06-16 09:25:09 +02:00
|
|
|
self.assertDirsEqual(
|
2022-09-15 16:51:34 -07:00
|
|
|
self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale')
|
|
|
|
|
)
|
2014-04-27 10:25:57 +02:00
|
|
|
|
2013-08-24 13:42:55 +01:00
|
|
|
def test_theme_static_paths_copy(self):
|
|
|
|
|
# the same thing with a specified set of settings should work
|
|
|
|
|
settings = read_settings(path=SAMPLE_CONFIG, override={
|
|
|
|
|
'PATH': INPUT_PATH,
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
2014-04-27 08:53:56 +02:00
|
|
|
'CACHE_PATH': self.temp_cache,
|
2013-08-24 13:42:55 +01:00
|
|
|
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'),
|
2013-08-31 22:11:03 +01:00
|
|
|
os.path.join(SAMPLES_PATH, 'kinda'),
|
2015-06-16 09:25:09 +02:00
|
|
|
os.path.join(SAMPLES_PATH,
|
|
|
|
|
'theme_standard')]
|
|
|
|
|
})
|
2013-08-24 13:42:55 +01:00
|
|
|
pelican = Pelican(settings=settings)
|
|
|
|
|
mute(True)(pelican.run)()
|
|
|
|
|
theme_output = os.path.join(self.temp_path, 'theme')
|
|
|
|
|
extra_path = os.path.join(theme_output, 'exciting', 'new', 'files')
|
|
|
|
|
|
|
|
|
|
for file in ['a_stylesheet', 'a_template']:
|
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(theme_output, file)))
|
|
|
|
|
|
2013-08-31 22:11:03 +01:00
|
|
|
for file in ['wow!', 'boom!', 'bap!', 'zap!']:
|
2013-08-24 13:42:55 +01:00
|
|
|
self.assertTrue(os.path.exists(os.path.join(extra_path, file)))
|
2013-09-14 16:18:53 +01:00
|
|
|
|
|
|
|
|
def test_theme_static_paths_copy_single_file(self):
|
|
|
|
|
# the same thing with a specified set of settings should work
|
|
|
|
|
settings = read_settings(path=SAMPLE_CONFIG, override={
|
|
|
|
|
'PATH': INPUT_PATH,
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
2014-04-27 08:53:56 +02:00
|
|
|
'CACHE_PATH': self.temp_cache,
|
2015-06-16 09:25:09 +02:00
|
|
|
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH,
|
|
|
|
|
'theme_standard')]
|
|
|
|
|
})
|
2013-09-14 16:18:53 +01:00
|
|
|
|
|
|
|
|
pelican = Pelican(settings=settings)
|
|
|
|
|
mute(True)(pelican.run)()
|
|
|
|
|
theme_output = os.path.join(self.temp_path, 'theme')
|
|
|
|
|
|
|
|
|
|
for file in ['a_stylesheet', 'a_template']:
|
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(theme_output, file)))
|
2014-04-17 16:28:22 +02:00
|
|
|
|
|
|
|
|
def test_write_only_selected(self):
|
|
|
|
|
"""Test that only the selected files are written"""
|
|
|
|
|
settings = read_settings(path=None, override={
|
|
|
|
|
'PATH': INPUT_PATH,
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
2014-04-27 08:53:56 +02:00
|
|
|
'CACHE_PATH': self.temp_cache,
|
2014-04-17 16:28:22 +02:00
|
|
|
'WRITE_SELECTED': [
|
|
|
|
|
os.path.join(self.temp_path, 'oh-yeah.html'),
|
|
|
|
|
os.path.join(self.temp_path, 'categories.html'),
|
2015-06-16 09:25:09 +02:00
|
|
|
],
|
2014-04-17 16:28:22 +02:00
|
|
|
'LOCALE': locale.normalize('en_US'),
|
2015-06-16 09:25:09 +02:00
|
|
|
})
|
2014-04-17 16:28:22 +02:00
|
|
|
pelican = Pelican(settings=settings)
|
|
|
|
|
logger = logging.getLogger()
|
|
|
|
|
orig_level = logger.getEffectiveLevel()
|
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
|
|
mute(True)(pelican.run)()
|
|
|
|
|
logger.setLevel(orig_level)
|
|
|
|
|
self.assertLogCountEqual(
|
|
|
|
|
count=2,
|
2014-07-22 11:48:15 -04:00
|
|
|
msg="Writing .*",
|
2014-04-17 16:28:22 +02:00
|
|
|
level=logging.INFO)
|
2015-11-08 23:08:03 +01:00
|
|
|
|
2018-11-10 20:53:04 +01:00
|
|
|
def test_cyclic_intersite_links_no_warnings(self):
|
|
|
|
|
settings = read_settings(path=None, override={
|
|
|
|
|
'PATH': os.path.join(CURRENT_DIR, 'cyclic_intersite_links'),
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
|
|
|
|
'CACHE_PATH': self.temp_cache,
|
|
|
|
|
})
|
|
|
|
|
pelican = Pelican(settings=settings)
|
|
|
|
|
mute(True)(pelican.run)()
|
|
|
|
|
# There are four different intersite links:
|
|
|
|
|
# - one pointing to the second article from first and third
|
|
|
|
|
# - one pointing to the first article from second and third
|
|
|
|
|
# - one pointing to the third article from first and second
|
|
|
|
|
# - one pointing to a nonexistent from each
|
|
|
|
|
# If everything goes well, only the warning about the nonexistent
|
|
|
|
|
# article should be printed. Only two articles are not sufficient,
|
|
|
|
|
# since the first will always have _context['generated_content'] empty
|
|
|
|
|
# (thus skipping the link resolving) and the second will always have it
|
|
|
|
|
# non-empty, containing the first, thus always succeeding.
|
|
|
|
|
self.assertLogCountEqual(
|
|
|
|
|
count=1,
|
|
|
|
|
msg="Unable to find '.*\\.rst', skipping url replacement.",
|
|
|
|
|
level=logging.WARNING)
|
|
|
|
|
|
2016-03-14 20:37:54 +01:00
|
|
|
def test_md_extensions_deprecation(self):
|
|
|
|
|
"""Test that a warning is issued if MD_EXTENSIONS is used"""
|
2015-11-08 23:08:03 +01:00
|
|
|
settings = read_settings(path=None, override={
|
|
|
|
|
'PATH': INPUT_PATH,
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
|
|
|
|
'CACHE_PATH': self.temp_cache,
|
2016-03-14 20:37:54 +01:00
|
|
|
'MD_EXTENSIONS': {},
|
2015-11-08 23:08:03 +01:00
|
|
|
})
|
|
|
|
|
pelican = Pelican(settings=settings)
|
|
|
|
|
mute(True)(pelican.run)()
|
|
|
|
|
self.assertLogCountEqual(
|
|
|
|
|
count=1,
|
2016-03-14 20:37:54 +01:00
|
|
|
msg="MD_EXTENSIONS is deprecated use MARKDOWN instead.",
|
2015-11-08 23:08:03 +01:00
|
|
|
level=logging.WARNING)
|
2018-11-10 19:42:59 +01:00
|
|
|
|
|
|
|
|
def test_parse_errors(self):
|
|
|
|
|
# Verify that just an error is printed and the application doesn't
|
|
|
|
|
# abort, exit or something.
|
|
|
|
|
settings = read_settings(path=None, override={
|
|
|
|
|
'PATH': os.path.abspath(os.path.join(CURRENT_DIR, 'parse_error')),
|
|
|
|
|
'OUTPUT_PATH': self.temp_path,
|
|
|
|
|
'CACHE_PATH': self.temp_cache,
|
|
|
|
|
})
|
|
|
|
|
pelican = Pelican(settings=settings)
|
|
|
|
|
mute(True)(pelican.run)()
|
|
|
|
|
self.assertLogCountEqual(
|
|
|
|
|
count=1,
|
|
|
|
|
msg="Could not process .*parse_error.rst",
|
|
|
|
|
level=logging.ERROR)
|
2019-06-12 17:02:49 +10:00
|
|
|
|
|
|
|
|
def test_module_load(self):
|
|
|
|
|
"""Test loading via python -m pelican --help displays the help"""
|
|
|
|
|
output = subprocess.check_output([
|
|
|
|
|
sys.executable, '-m', 'pelican', '--help'
|
|
|
|
|
]).decode('ascii', 'replace')
|
|
|
|
|
assert 'usage:' in output
|