summaryrefslogtreecommitdiff
path: root/markdown/test_tools.py
blob: 9324bd4e25415d52ce6233b450bb8c65f3df922a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
from __future__ import absolute_import
import os
import io
import unittest
import textwrap
from . import markdown

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 ouput and expected ouput 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, **kwargs):
        """
        Test that source Markdown text renders to expected output with given keywords.
        """

        kws = self.default_kwargs.copy()
        kws.update(kwargs)
        output = markdown(source, **kws)
        self.assertMultiLineEqual(output, expected)

    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()


#########################
# 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 io.open(infile, encoding="utf-8") as f:
                    input = f.read()
                with io.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:
                    expected = _normalize_whitespace(expected)
                    output = _normalize_whitespace(output)
                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)


# Define LegacyTestCase class with metaclass in Py2 & Py3 compatable way.
# See https://stackoverflow.com/a/38668373/866026
# TODO: If/when py2 support is dropped change to:
# class LegacyTestCase(unittest.Testcase, metaclass=LegacyTestMeta)


class LegacyTestCase(LegacyTestMeta('LegacyTestCase', (unittest.TestCase,), {'__slots__': ()})):
    """
    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 fo test files. An absolute path is prefered.
    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`.
    ouput_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 wihtout 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 seperate 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