Spaces:
Running
Running
# 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-2023 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). | |
""" A collection of tools for testing the Markdown code base and extensions. """ | |
from __future__ import annotations | |
import os | |
import sys | |
import unittest | |
import textwrap | |
from typing import Any | |
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: dict[str, Any] = {} | |
""" Default options to pass to Markdown for each test. """ | |
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: | |
``` python | |
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: | |
Attributes: | |
location (str): A path to the directory of test files. An absolute path is preferred. | |
exclude (list[str]): A list of tests to exclude. Each test name should comprise the filename | |
without an extension. | |
normalize (bool): A boolean value indicating if the HTML should be normalized. Default: `False`. | |
input_ext (str): A string containing the file extension of input files. Default: `.txt`. | |
output_ext (str): A string containing the file extension of expected output files. Default: `html`. | |
default_kwargs (Kwargs[str, Any]): 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 | |