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.
220 lines
8.2 KiB
220 lines
8.2 KiB
""" |
|
Python Markdown |
|
|
|
A Python implementation of John Gruber's Markdown. |
|
|
|
Documentation: https://python-markdown.github.io/ |
|
GitHub: https://github.com/Python-Markdown/markdown/ |
|
PyPI: https://pypi.org/project/Markdown/ |
|
|
|
Started by Manfred Stienstra (http://www.dwerg.net/). |
|
Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). |
|
Currently maintained by Waylan Limberg (https://github.com/waylan), |
|
Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). |
|
|
|
Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) |
|
Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) |
|
Copyright 2004 Manfred Stienstra (the original version) |
|
|
|
License: BSD (see LICENSE.md for details). |
|
""" |
|
|
|
import os |
|
import sys |
|
import unittest |
|
import textwrap |
|
from . import markdown, Markdown, util |
|
|
|
try: |
|
import tidylib |
|
except ImportError: |
|
tidylib = None |
|
|
|
__all__ = ['TestCase', 'LegacyTestCase', 'Kwargs'] |
|
|
|
|
|
class TestCase(unittest.TestCase): |
|
""" |
|
A `unittest.TestCase` subclass with helpers for testing Markdown output. |
|
|
|
Define `default_kwargs` as a dict of keywords to pass to Markdown for each |
|
test. The defaults can be overridden on individual tests. |
|
|
|
The `assertMarkdownRenders` method accepts the source text, the expected |
|
output, and any keywords to pass to Markdown. The `default_kwargs` are used |
|
except where overridden by `kwargs`. The output and expected output are passed |
|
to `TestCase.assertMultiLineEqual`. An `AssertionError` is raised with a diff |
|
if the actual output does not equal the expected output. |
|
|
|
The `dedent` method is available to dedent triple-quoted strings if |
|
necessary. |
|
|
|
In all other respects, behaves as `unittest.TestCase`. |
|
""" |
|
|
|
default_kwargs = {} |
|
|
|
def assertMarkdownRenders(self, source, expected, expected_attrs=None, **kwargs): |
|
""" |
|
Test that source Markdown text renders to expected output with given keywords. |
|
|
|
`expected_attrs` accepts a dict. Each key should be the name of an attribute |
|
on the `Markdown` instance and the value should be the expected value after |
|
the source text is parsed by Markdown. After the expected output is tested, |
|
the expected value for each attribute is compared against the actual |
|
attribute of the `Markdown` instance using `TestCase.assertEqual`. |
|
""" |
|
|
|
expected_attrs = expected_attrs or {} |
|
kws = self.default_kwargs.copy() |
|
kws.update(kwargs) |
|
md = Markdown(**kws) |
|
output = md.convert(source) |
|
self.assertMultiLineEqual(output, expected) |
|
for key, value in expected_attrs.items(): |
|
self.assertEqual(getattr(md, key), value) |
|
|
|
def dedent(self, text): |
|
""" |
|
Dedent text. |
|
""" |
|
|
|
# TODO: If/when actual output ends with a newline, then use: |
|
# return textwrap.dedent(text.strip('/n')) |
|
return textwrap.dedent(text).strip() |
|
|
|
|
|
class recursionlimit: |
|
""" |
|
A context manager which temporarily modifies the Python recursion limit. |
|
|
|
The testing framework, coverage, etc. may add an arbitrary number of levels to the depth. To maintain consistency |
|
in the tests, the current stack depth is determined when called, then added to the provided limit. |
|
|
|
Example usage: |
|
|
|
with recursionlimit(20): |
|
# test code here |
|
|
|
See https://stackoverflow.com/a/50120316/866026 |
|
""" |
|
|
|
def __init__(self, limit): |
|
self.limit = util._get_stack_depth() + limit |
|
self.old_limit = sys.getrecursionlimit() |
|
|
|
def __enter__(self): |
|
sys.setrecursionlimit(self.limit) |
|
|
|
def __exit__(self, type, value, tb): |
|
sys.setrecursionlimit(self.old_limit) |
|
|
|
|
|
######################### |
|
# Legacy Test Framework # |
|
######################### |
|
|
|
|
|
class Kwargs(dict): |
|
""" A dict like class for holding keyword arguments. """ |
|
pass |
|
|
|
|
|
def _normalize_whitespace(text): |
|
""" Normalize whitespace for a string of html using `tidylib`. """ |
|
output, errors = tidylib.tidy_fragment(text, options={ |
|
'drop_empty_paras': 0, |
|
'fix_backslash': 0, |
|
'fix_bad_comments': 0, |
|
'fix_uri': 0, |
|
'join_styles': 0, |
|
'lower_literals': 0, |
|
'merge_divs': 0, |
|
'output_xhtml': 1, |
|
'quote_ampersand': 0, |
|
'newline': 'LF' |
|
}) |
|
return output |
|
|
|
|
|
class LegacyTestMeta(type): |
|
def __new__(cls, name, bases, dct): |
|
|
|
def generate_test(infile, outfile, normalize, kwargs): |
|
def test(self): |
|
with open(infile, encoding="utf-8") as f: |
|
input = f.read() |
|
with open(outfile, encoding="utf-8") as f: |
|
# Normalize line endings |
|
# (on Windows, git may have altered line endings). |
|
expected = f.read().replace("\r\n", "\n") |
|
output = markdown(input, **kwargs) |
|
if tidylib and normalize: |
|
try: |
|
expected = _normalize_whitespace(expected) |
|
output = _normalize_whitespace(output) |
|
except OSError: |
|
self.skipTest("Tidylib's c library not available.") |
|
elif normalize: |
|
self.skipTest('Tidylib not available.') |
|
self.assertMultiLineEqual(output, expected) |
|
return test |
|
|
|
location = dct.get('location', '') |
|
exclude = dct.get('exclude', []) |
|
normalize = dct.get('normalize', False) |
|
input_ext = dct.get('input_ext', '.txt') |
|
output_ext = dct.get('output_ext', '.html') |
|
kwargs = dct.get('default_kwargs', Kwargs()) |
|
|
|
if os.path.isdir(location): |
|
for file in os.listdir(location): |
|
infile = os.path.join(location, file) |
|
if os.path.isfile(infile): |
|
tname, ext = os.path.splitext(file) |
|
if ext == input_ext: |
|
outfile = os.path.join(location, tname + output_ext) |
|
tname = tname.replace(' ', '_').replace('-', '_') |
|
kws = kwargs.copy() |
|
if tname in dct: |
|
kws.update(dct[tname]) |
|
test_name = 'test_%s' % tname |
|
if tname not in exclude: |
|
dct[test_name] = generate_test(infile, outfile, normalize, kws) |
|
else: |
|
dct[test_name] = unittest.skip('Excluded')(lambda: None) |
|
|
|
return type.__new__(cls, name, bases, dct) |
|
|
|
|
|
class LegacyTestCase(unittest.TestCase, metaclass=LegacyTestMeta): |
|
""" |
|
A `unittest.TestCase` subclass for running Markdown's legacy file-based tests. |
|
|
|
A subclass should define various properties which point to a directory of |
|
text-based test files and define various behaviors/defaults for those tests. |
|
The following properties are supported: |
|
|
|
`location`: A path to the directory of test files. An absolute path is preferred. |
|
`exclude`: A list of tests to exclude. Each test name should comprise the filename |
|
without an extension. |
|
`normalize`: A boolean value indicating if the HTML should be normalized. |
|
Default: `False`. |
|
`input_ext`: A string containing the file extension of input files. Default: `.txt`. |
|
`output_ext`: A string containing the file extension of expected output files. |
|
Default: `html`. |
|
`default_kwargs`: A `Kwargs` instance which stores the default set of keyword |
|
arguments for all test files in the directory. |
|
|
|
In addition, properties can be defined for each individual set of test files within |
|
the directory. The property should be given the name of the file without the file |
|
extension. Any spaces and dashes in the filename should be replaced with |
|
underscores. The value of the property should be a `Kwargs` instance which |
|
contains the keyword arguments that should be passed to `Markdown` for that |
|
test file. The keyword arguments will "update" the `default_kwargs`. |
|
|
|
When the class instance is created, it will walk the given directory and create |
|
a separate `Unitttest` for each set of test files using the naming scheme: |
|
`test_filename`. One `Unittest` will be run for each set of input and output files. |
|
""" |
|
pass
|
|
|