aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/parso/py3/tests/conftest.py
blob: 7ac062f4339044db07a5be9fe248255e51296717 (plain) (blame)
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
import re
import tempfile
import shutil
import logging
import os
from pathlib import Path

import pytest
import yatest.common

import parso
from parso import cache
from parso.utils import parse_version_string

collect_ignore = ["setup.py"]

_SUPPORTED_VERSIONS = '3.6', '3.7', '3.8', '3.9', '3.10'


@pytest.fixture(scope='session')
def clean_parso_cache():
    """
    Set the default cache directory to a temporary directory during tests.

    Note that you can't use built-in `tmpdir` and `monkeypatch`
    fixture here because their scope is 'function', which is not used
    in 'session' scope fixture.

    This fixture is activated in ../pytest.ini.
    """
    old = cache._default_cache_path
    tmp = tempfile.mkdtemp(prefix='parso-test-')
    cache._default_cache_path = Path(tmp)
    yield
    cache._default_cache_path = old
    shutil.rmtree(tmp)


def pytest_addoption(parser):
    parser.addoption("--logging", "-L", action='store_true',
                     help="Enables the logging output.")


def pytest_generate_tests(metafunc):
    if 'normalizer_issue_case' in metafunc.fixturenames:
        base_dir = os.path.join(yatest.common.test_source_path(), 'normalizer_issue_files')

        cases = list(colllect_normalizer_tests(base_dir))
        metafunc.parametrize(
            'normalizer_issue_case',
            cases,
            ids=[c.name for c in cases]
        )
    elif 'each_version' in metafunc.fixturenames:
        metafunc.parametrize('each_version', _SUPPORTED_VERSIONS)
    elif 'version_ge_py38' in metafunc.fixturenames:
        ge38 = set(_SUPPORTED_VERSIONS) - {'3.6', '3.7'}
        metafunc.parametrize('version_ge_py38', sorted(ge38))


class NormalizerIssueCase:
    """
    Static Analysis cases lie in the static_analysis folder.
    The tests also start with `#!`, like the goto_definition tests.
    """
    def __init__(self, path):
        self.path = path
        self.name = os.path.basename(path)
        match = re.search(r'python([\d.]+)\.py', self.name)
        self.python_version = match and match.group(1)


def colllect_normalizer_tests(base_dir):
    for f_name in os.listdir(base_dir):
        if f_name.endswith(".py"):
            path = os.path.join(base_dir, f_name)
            yield NormalizerIssueCase(path)


def pytest_configure(config):
    if config.option.logging:
        root = logging.getLogger()
        root.setLevel(logging.DEBUG)

        #ch = logging.StreamHandler(sys.stdout)
        #ch.setLevel(logging.DEBUG)
        #formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        #ch.setFormatter(formatter)

        #root.addHandler(ch)


class Checker:
    def __init__(self, version, is_passing):
        self.version = version
        self._is_passing = is_passing
        self.grammar = parso.load_grammar(version=self.version)

    def parse(self, code):
        if self._is_passing:
            return parso.parse(code, version=self.version, error_recovery=False)
        else:
            self._invalid_syntax(code)

    def _invalid_syntax(self, code):
        with pytest.raises(parso.ParserSyntaxError):
            module = parso.parse(code, version=self.version, error_recovery=False)
            # For debugging
            print(module.children)

    def get_error(self, code):
        errors = list(self.grammar.iter_errors(self.grammar.parse(code)))
        assert bool(errors) != self._is_passing
        if errors:
            return errors[0]

    def get_error_message(self, code):
        error = self.get_error(code)
        if error is None:
            return
        return error.message

    def assert_no_error_in_passing(self, code):
        if self._is_passing:
            module = self.grammar.parse(code)
            assert not list(self.grammar.iter_errors(module))


@pytest.fixture
def works_not_in_py(each_version):
    return Checker(each_version, False)


@pytest.fixture
def works_in_py(each_version):
    return Checker(each_version, True)


@pytest.fixture
def works_ge_py38(each_version):
    version_info = parse_version_string(each_version)
    return Checker(each_version, version_info >= (3, 8))


@pytest.fixture
def works_ge_py39(each_version):
    version_info = parse_version_string(each_version)
    return Checker(each_version, version_info >= (3, 9))