summaryrefslogtreecommitdiffstats
path: root/tests/conftest.py
blob: e3cb2c0cbee7ec422bac4048b2027506fd1946b6 (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
"""
    Generated lexer tests
    ~~~~~~~~~~~~~~~~~~~~~

    Checks that lexers output the expected tokens for each sample
    under snippets/ and examplefiles/.

    After making a change, rather than updating the samples manually,
    run `pytest --update-goldens <changed file>`.

    To add a new sample, create a new file matching this pattern.
    The directory must match the alias of the lexer to be used.
    Populate only the input, then just `--update-goldens`.

    :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

from pathlib import Path

import pytest

import pygments.lexers
from pygments.token import Error


def pytest_addoption(parser):
    parser.addoption('--update-goldens', action='store_true',
                     help='reset golden master benchmarks')


class LexerTestItem(pytest.Item):
    def __init__(self, name, parent):
        super().__init__(name, parent)
        self.lexer = Path(str(self.fspath)).parent.name
        self.actual = None

    @classmethod
    def _prettyprint_tokens(cls, tokens):
        for tok, val in tokens:
            if tok is Error and not cls.allow_errors:
                raise ValueError('generated Error token at {!r}'.format(val))
            yield '{!r:<13} {}'.format(val, str(tok)[6:])
            if val.endswith('\n'):
                yield ''

    def runtest(self):
        lexer = pygments.lexers.get_lexer_by_name(self.lexer)
        tokens = lexer.get_tokens(self.input)
        self.actual = '\n'.join(self._prettyprint_tokens(tokens)).rstrip('\n') + '\n'
        if not self.config.getoption('--update-goldens'):
            assert self.actual == self.expected

    def _test_file_rel_path(self):
        return Path(str(self.fspath)).relative_to(Path(__file__).parent.parent)

    def _prunetraceback(self, excinfo):
        excinfo.traceback = excinfo.traceback.cut(__file__).filter()

    def repr_failure(self, excinfo):
        if isinstance(excinfo.value, AssertionError):
            rel_path = self._test_file_rel_path()
            message = (
                'The tokens produced by the "{}" lexer differ from the '
                'expected ones in the file "{}".\n'
                'Run `pytest {} --update-goldens` to update it.'
            ).format(self.lexer, rel_path, Path(*rel_path.parts[:2]))
            diff = str(excinfo.value).split('\n', 1)[-1]
            return message + '\n\n' + diff
        else:
            return pytest.Item.repr_failure(self, excinfo)

    def reportinfo(self):
        return self.fspath, None, str(self._test_file_rel_path())

    def maybe_overwrite(self):
        if self.actual is not None and self.config.getoption('--update-goldens'):
            self.overwrite()


class LexerSeparateTestItem(LexerTestItem):
    allow_errors = False

    def __init__(self, name, parent):
        super().__init__(name, parent)

        self.input = self.fspath.read_text('utf-8')
        output_path = self.fspath + '.output'
        if output_path.check():
            self.expected = output_path.read_text(encoding='utf-8')
        else:
            self.expected = ''

    def overwrite(self):
        output_path = self.fspath + '.output'
        output_path.write_text(self.actual, encoding='utf-8')


class LexerInlineTestItem(LexerTestItem):
    allow_errors = True

    def __init__(self, name, parent):
        super().__init__(name, parent)

        content = self.fspath.read_text('utf-8')
        content, _, self.expected = content.partition('\n---tokens---\n')
        if content.startswith('---input---\n'):
            content = '\n' + content
        self.comment, _, self.input = content.rpartition('\n---input---\n')
        if not self.input.endswith('\n'):
            self.input += '\n'
        self.comment = self.comment.strip()

    def overwrite(self):
        with self.fspath.open('w', encoding='utf-8') as f:
            f.write(self.comment)
            if self.comment:
                f.write('\n\n')
            f.write('---input---\n')
            f.write(self.input)
            f.write('\n---tokens---\n')
            f.write(self.actual)


def pytest_runtest_teardown(item, nextitem):
    if isinstance(item, LexerTestItem):
        item.maybe_overwrite()