1""" 2Python Markdown 3 4A Python implementation of John Gruber's Markdown. 5 6Documentation: https://python-markdown.github.io/ 7GitHub: https://github.com/Python-Markdown/markdown/ 8PyPI: https://pypi.org/project/Markdown/ 9 10Started by Manfred Stienstra (http://www.dwerg.net/). 11Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). 12Currently maintained by Waylan Limberg (https://github.com/waylan), 13Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). 14 15Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) 16Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) 17Copyright 2004 Manfred Stienstra (the original version) 18 19License: BSD (see LICENSE.md for details). 20""" 21 22import os 23import sys 24import unittest 25import textwrap 26from . import markdown, Markdown, util 27 28try: 29 import tidylib 30except ImportError: 31 tidylib = None 32 33__all__ = ['TestCase', 'LegacyTestCase', 'Kwargs'] 34 35 36class TestCase(unittest.TestCase): 37 """ 38 A unittest.TestCase subclass with helpers for testing Markdown output. 39 40 Define `default_kwargs` as a dict of keywords to pass to Markdown for each 41 test. The defaults can be overridden on individual tests. 42 43 The `assertMarkdownRenders` method accepts the source text, the expected 44 output, and any keywords to pass to Markdown. The `default_kwargs` are used 45 except where overridden by `kwargs`. The output and expected output are passed 46 to `TestCase.assertMultiLineEqual`. An AssertionError is raised with a diff 47 if the actual output does not equal the expected output. 48 49 The `dedent` method is available to dedent triple-quoted strings if 50 necessary. 51 52 In all other respects, behaves as unittest.TestCase. 53 """ 54 55 default_kwargs = {} 56 57 def assertMarkdownRenders(self, source, expected, expected_attrs=None, **kwargs): 58 """ 59 Test that source Markdown text renders to expected output with given keywords. 60 61 `expected_attrs` accepts a dict. Each key should be the name of an attribute 62 on the `Markdown` instance and the value should be the expected value after 63 the source text is parsed by Markdown. After the expected output is tested, 64 the expected value for each attribute is compared against the actual 65 attribute of the `Markdown` instance using `TestCase.assertEqual`. 66 """ 67 68 expected_attrs = expected_attrs or {} 69 kws = self.default_kwargs.copy() 70 kws.update(kwargs) 71 md = Markdown(**kws) 72 output = md.convert(source) 73 self.assertMultiLineEqual(output, expected) 74 for key, value in expected_attrs.items(): 75 self.assertEqual(getattr(md, key), value) 76 77 def dedent(self, text): 78 """ 79 Dedent text. 80 """ 81 82 # TODO: If/when actual output ends with a newline, then use: 83 # return textwrap.dedent(text.strip('/n')) 84 return textwrap.dedent(text).strip() 85 86 87class recursionlimit: 88 """ 89 A context manager which temporarily modifies the Python recursion limit. 90 91 The testing framework, coverage, etc. may add an arbitrary number of levels to the depth. To maintain consistency 92 in the tests, the current stack depth is determined when called, then added to the provided limit. 93 94 Example usage: 95 96 with recursionlimit(20): 97 # test code here 98 99 See https://stackoverflow.com/a/50120316/866026 100 """ 101 102 def __init__(self, limit): 103 self.limit = util._get_stack_depth() + limit 104 self.old_limit = sys.getrecursionlimit() 105 106 def __enter__(self): 107 sys.setrecursionlimit(self.limit) 108 109 def __exit__(self, type, value, tb): 110 sys.setrecursionlimit(self.old_limit) 111 112 113######################### 114# Legacy Test Framework # 115######################### 116 117 118class Kwargs(dict): 119 """ A dict like class for holding keyword arguments. """ 120 pass 121 122 123def _normalize_whitespace(text): 124 """ Normalize whitespace for a string of html using tidylib. """ 125 output, errors = tidylib.tidy_fragment(text, options={ 126 'drop_empty_paras': 0, 127 'fix_backslash': 0, 128 'fix_bad_comments': 0, 129 'fix_uri': 0, 130 'join_styles': 0, 131 'lower_literals': 0, 132 'merge_divs': 0, 133 'output_xhtml': 1, 134 'quote_ampersand': 0, 135 'newline': 'LF' 136 }) 137 return output 138 139 140class LegacyTestMeta(type): 141 def __new__(cls, name, bases, dct): 142 143 def generate_test(infile, outfile, normalize, kwargs): 144 def test(self): 145 with open(infile, encoding="utf-8") as f: 146 input = f.read() 147 with open(outfile, encoding="utf-8") as f: 148 # Normalize line endings 149 # (on Windows, git may have altered line endings). 150 expected = f.read().replace("\r\n", "\n") 151 output = markdown(input, **kwargs) 152 if tidylib and normalize: 153 try: 154 expected = _normalize_whitespace(expected) 155 output = _normalize_whitespace(output) 156 except OSError: 157 self.skipTest("Tidylib's c library not available.") 158 elif normalize: 159 self.skipTest('Tidylib not available.') 160 self.assertMultiLineEqual(output, expected) 161 return test 162 163 location = dct.get('location', '') 164 exclude = dct.get('exclude', []) 165 normalize = dct.get('normalize', False) 166 input_ext = dct.get('input_ext', '.txt') 167 output_ext = dct.get('output_ext', '.html') 168 kwargs = dct.get('default_kwargs', Kwargs()) 169 170 if os.path.isdir(location): 171 for file in os.listdir(location): 172 infile = os.path.join(location, file) 173 if os.path.isfile(infile): 174 tname, ext = os.path.splitext(file) 175 if ext == input_ext: 176 outfile = os.path.join(location, tname + output_ext) 177 tname = tname.replace(' ', '_').replace('-', '_') 178 kws = kwargs.copy() 179 if tname in dct: 180 kws.update(dct[tname]) 181 test_name = 'test_%s' % tname 182 if tname not in exclude: 183 dct[test_name] = generate_test(infile, outfile, normalize, kws) 184 else: 185 dct[test_name] = unittest.skip('Excluded')(lambda: None) 186 187 return type.__new__(cls, name, bases, dct) 188 189 190class LegacyTestCase(unittest.TestCase, metaclass=LegacyTestMeta): 191 """ 192 A `unittest.TestCase` subclass for running Markdown's legacy file-based tests. 193 194 A subclass should define various properties which point to a directory of 195 text-based test files and define various behaviors/defaults for those tests. 196 The following properties are supported: 197 198 location: A path to the directory of test files. An absolute path is preferred. 199 exclude: A list of tests to exclude. Each test name should comprise the filename 200 without an extension. 201 normalize: A boolean value indicating if the HTML should be normalized. 202 Default: `False`. 203 input_ext: A string containing the file extension of input files. Default: `.txt`. 204 ouput_ext: A string containing the file extension of expected output files. 205 Default: `html`. 206 default_kwargs: A `Kwargs` instance which stores the default set of keyword 207 arguments for all test files in the directory. 208 209 In addition, properties can be defined for each individual set of test files within 210 the directory. The property should be given the name of the file without the file 211 extension. Any spaces and dashes in the filename should be replaced with 212 underscores. The value of the property should be a `Kwargs` instance which 213 contains the keyword arguments that should be passed to `Markdown` for that 214 test file. The keyword arguments will "update" the `default_kwargs`. 215 216 When the class instance is created, it will walk the given directory and create 217 a separate unitttest for each set of test files using the naming scheme: 218 `test_filename`. One unittest will be run for each set of input and output files. 219 """ 220 pass 221