You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
9.1 KiB
264 lines
9.1 KiB
# -*- coding: utf-8 -*-
|
|
from __future__ import unicode_literals, print_function
|
|
|
|
import filecmp
|
|
import locale
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
|
|
from extended_sitemap import ConfigurationError
|
|
|
|
from functools import wraps
|
|
|
|
from tempfile import mkdtemp
|
|
|
|
from pelican import Pelican
|
|
from pelican.settings import read_settings
|
|
|
|
from six import StringIO
|
|
|
|
# used paths
|
|
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
CONTENT_DIR = os.path.abspath(os.path.join(CURRENT_DIR, 'content'))
|
|
EXPECTED_DIR = os.path.abspath(os.path.join(CURRENT_DIR, 'expected'))
|
|
OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output'))
|
|
|
|
|
|
def isplit(s, sep=None):
|
|
"""Behaves like str.split but returns a generator instead of a list.
|
|
>>> list(isplit('\tUse the force\n')) == '\tUse the force\n'.split()
|
|
True
|
|
>>> list(isplit('\tUse the force\n')) == ['Use', 'the', 'force']
|
|
True
|
|
>>> (list(isplit('\tUse the force\n', "e"))
|
|
== '\tUse the force\n'.split("e"))
|
|
True
|
|
>>> list(isplit('Use the force', "e")) == 'Use the force'.split("e")
|
|
True
|
|
>>> list(isplit('Use the force', "e")) == ['Us', ' th', ' forc', '']
|
|
True
|
|
"""
|
|
sep, hardsep = r'\s+' if sep is None else re.escape(sep), sep is not None
|
|
exp, pos, l = re.compile(sep), 0, len(s)
|
|
while True:
|
|
m = exp.search(s, pos)
|
|
if not m:
|
|
if pos < l or hardsep:
|
|
# ^ mimic "split()": ''.split() returns []
|
|
yield s[pos:]
|
|
break
|
|
start = m.start()
|
|
if pos < start or hardsep:
|
|
# ^ mimic "split()": includes trailing empty string
|
|
yield s[pos:start]
|
|
pos = m.end()
|
|
|
|
|
|
def mute(returns_output=False):
|
|
"""Decorate a function that prints to stdout, intercepting the output.
|
|
If "returns_output" is True, the function will return a generator
|
|
yielding the printed lines instead of the return values.
|
|
The decorator literally hijack sys.stdout during each function
|
|
execution, so be careful with what you apply it to.
|
|
>>> def numbers():
|
|
print "42"
|
|
print "1984"
|
|
...
|
|
>>> numbers()
|
|
42
|
|
1984
|
|
>>> mute()(numbers)()
|
|
>>> list(mute(True)(numbers)())
|
|
['42', '1984']
|
|
"""
|
|
|
|
def decorator(func):
|
|
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
|
|
saved_stdout = sys.stdout
|
|
sys.stdout = StringIO()
|
|
|
|
try:
|
|
out = func(*args, **kwargs)
|
|
if returns_output:
|
|
out = isplit(sys.stdout.getvalue().strip())
|
|
finally:
|
|
sys.stdout = saved_stdout
|
|
|
|
return out
|
|
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
class FileComparisonTest(unittest.TestCase):
|
|
"""
|
|
Unittest class with possibility to assert equal file contents.
|
|
"""
|
|
|
|
def assertFileContentEquals(self, path_file_expected, path_file_test):
|
|
"""
|
|
Asserts the file contents to be equal.
|
|
:param path_file_expected: path to the file with the expected content
|
|
:type path_file_expected: str
|
|
:param path_file_test: path to the file to test
|
|
:type path_file_test: str
|
|
"""
|
|
if not filecmp.cmp(path_file_expected, path_file_test):
|
|
msg_fail = 'File content of %(filename)s does not match expected content!' % {'filename': path_file_test}
|
|
|
|
# if there is git and git diff works for both files, append the file diff to the fail message
|
|
try:
|
|
out, err = subprocess.Popen(
|
|
['git', 'diff', '--minimal', '--no-color', '--no-ext-diff', '--exit-code', '-w', path_file_expected, path_file_test],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
).communicate()
|
|
if len(err) == 0:
|
|
msg_fail += '\n\n' + out.decode('utf-8')
|
|
except OSError:
|
|
# if there is no git, just don't output the diff
|
|
pass
|
|
|
|
self.fail(msg_fail)
|
|
|
|
|
|
class ExtendedSitemapTest(FileComparisonTest):
|
|
|
|
def setUp(self):
|
|
self.path_temp = mkdtemp(prefix='extended_sitemap_tests.')
|
|
self.path_cache = mkdtemp(prefix='extended_sitemap_cache.')
|
|
|
|
# default minimal configuration for pelican in test context
|
|
self.settings_default = {
|
|
'PAGE_DIR': os.path.join(CONTENT_DIR, 'pages'),
|
|
'ARTICLE_DIR': os.path.join(CONTENT_DIR, 'articles'),
|
|
'PATH': CONTENT_DIR,
|
|
'OUTPUT_PATH': self.path_temp,
|
|
'CACHE_PATH': self.path_cache,
|
|
'LOCALE': locale.normalize('en_US'),
|
|
'SITEURL': 'http://example.com',
|
|
'PLUGIN_PATH': os.path.join(CURRENT_DIR, '..'),
|
|
'PLUGINS': ['extended_sitemap'],
|
|
}
|
|
|
|
def __execute_pelican(self, settings_override=None):
|
|
"""
|
|
Executes pelican. Uses the minimal config of self.settings_default (that will fail!) merged with the given additional settings.
|
|
:param settings_override: dictionary with pelican setting values to set
|
|
:type settings_override: dict
|
|
"""
|
|
if not settings_override:
|
|
settings_override = {}
|
|
|
|
settings = self.settings_default.copy()
|
|
settings.update(settings_override)
|
|
|
|
pelican_settings = read_settings(
|
|
path=None,
|
|
override=settings
|
|
)
|
|
pelican = Pelican(settings=pelican_settings)
|
|
pelican.run()
|
|
|
|
def test_timezone_missing(self):
|
|
"""
|
|
As the TIMEZONE settings is necessary to create timezone based date value, ensure the configuration exception is raised if it is not configured.
|
|
"""
|
|
self.assertRaises(ConfigurationError, self.__execute_pelican)
|
|
|
|
def test_sitemap_structure(self):
|
|
"""
|
|
Tests basic structure of generated sitemap.
|
|
"""
|
|
self.__execute_pelican(
|
|
settings_override={
|
|
'TIMEZONE': 'Europe/Berlin',
|
|
}
|
|
)
|
|
self.assertFileContentEquals(
|
|
os.path.join(EXPECTED_DIR, 'test_sitemap_structure.xml'),
|
|
os.path.join(self.path_temp, 'sitemap.xml')
|
|
)
|
|
|
|
def test_sitemap_structure_subpaths(self):
|
|
"""
|
|
Tests basic structure of generated sitemap with subpath in domain.
|
|
"""
|
|
# issue #2
|
|
self.__execute_pelican(
|
|
settings_override={
|
|
'TIMEZONE': 'Europe/Berlin',
|
|
'SITEURL': 'http://example.com/subpath',
|
|
}
|
|
)
|
|
self.assertFileContentEquals(
|
|
os.path.join(EXPECTED_DIR, 'test_sitemap_structure_subpath.xml'),
|
|
os.path.join(self.path_temp, 'sitemap.xml')
|
|
)
|
|
|
|
def test_sitemap_structure_custom_article_url(self):
|
|
"""
|
|
Tests basic structure of generated sitemap with customized ARTICLE_URL and ARTICLE_SAVE_AS settings.
|
|
"""
|
|
# issue #2
|
|
self.__execute_pelican(
|
|
settings_override={
|
|
'TIMEZONE': 'Europe/Berlin',
|
|
'ARTICLE_URL': 'customarticles/{date:%Y}/{date:%b}/{date:%d}/{slug}/',
|
|
'ARTICLE_SAVE_AS': '{slug}.custom.html',
|
|
}
|
|
)
|
|
self.assertFileContentEquals(
|
|
os.path.join(EXPECTED_DIR, 'test_sitemap_structure_custom_article_url.xml'),
|
|
os.path.join(self.path_temp, 'sitemap.xml')
|
|
)
|
|
|
|
def test_sitemap_structure_with_custom_direct_templates_filenames(self):
|
|
"""
|
|
Tests sitemap structure with custom %s_SAVE_AS values for DIRECT_TEMPLATES.
|
|
Source: https://github.com/dArignac/pelican-extended-sitemap/issues/14
|
|
"""
|
|
self.__execute_pelican(
|
|
settings_override={
|
|
'TIMEZONE': 'Europe/Berlin',
|
|
'TAGS_SAVE_AS': 'abc/tags.html',
|
|
'CATEGORIES_SAVE_AS': 'cats/meow/something.txt',
|
|
'AUTHORS_SAVE_AS': 'those-writers.html',
|
|
'ARCHIVES_SAVE_AS': 'our-curated-library.html',
|
|
}
|
|
)
|
|
self.assertFileContentEquals(
|
|
os.path.join(EXPECTED_DIR, 'test_sitemap_structure_direct_templates_1.xml'),
|
|
os.path.join(self.path_temp, 'sitemap.xml')
|
|
)
|
|
|
|
def test_sitemap_structure_with_custom_direct_templates_urls(self):
|
|
"""
|
|
Tests sitemap structure with custom %s_URL values for DIRECT_TEMPLATES.
|
|
Source: https://github.com/dArignac/pelican-extended-sitemap/issues/15
|
|
"""
|
|
self.__execute_pelican(
|
|
settings_override={
|
|
'TIMEZONE': 'Europe/Berlin',
|
|
'TAGS_URL': 'abc/tags',
|
|
'CATEGORIES_URL': 'cats/meow',
|
|
'AUTHORS_URL': 'authors/all',
|
|
'ARCHIVES_URL': 'lib/the-archive/list/',
|
|
# also define the SAVE_AS to test correct resolution sorting
|
|
'TAGS_SAVE_AS': 'abc/tags.html',
|
|
'CATEGORIES_SAVE_AS': 'cats/meow/something.txt',
|
|
'AUTHORS_SAVE_AS': 'those-writers.html',
|
|
'ARCHIVES_SAVE_AS': 'our-curated-library.html',
|
|
}
|
|
)
|
|
self.assertFileContentEquals(
|
|
os.path.join(EXPECTED_DIR, 'test_sitemap_structure_direct_templates_2.xml'),
|
|
os.path.join(self.path_temp, 'sitemap.xml')
|
|
)
|