Note: This is a public test instance of Red Hat Bugzilla. The data contained within is a snapshot of the live data so any changes you make will not be reflected in the production Bugzilla. Email is disabled so feel free to test any aspect of the site that you want. File any problems you find or give feedback at bugzilla.redhat.com.
Bug 1952351 - python-parso fails to build with Python 3.10: Changed enum repr causes test failure
Summary: python-parso fails to build with Python 3.10: Changed enum repr causes test f...
Keywords:
Status: CLOSED RAWHIDE
Alias: None
Product: Fedora
Classification: Fedora
Component: python-parso
Version: rawhide
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: ---
Assignee: Miro Hrončok
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks: PYTHON3.10
TreeView+ depends on / blocked
 
Reported: 2021-04-22 06:26 UTC by Tomáš Hrnčiar
Modified: 2021-04-23 10:43 UTC (History)
4 users (show)

Fixed In Version:
Doc Type: If docs needed, set a value
Doc Text:
Clone Of:
Environment:
Last Closed: 2021-04-23 10:43:47 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Github davidhalter parso pull 186 0 None open Relax a test regex to match new enum repr in Python 3.10.0a7+ 2021-04-22 10:28:46 UTC
Github pytest-dev pytest pull 8227 0 None open Make code.FormattedExcinfo.get_source more defensive 2021-04-22 06:26:28 UTC

Description Tomáš Hrnčiar 2021-04-22 06:26:28 UTC
python-parso fails to build with Python 3.10.0a7.

test/test_pgen2.py::test_ambiguities[foo: bar | baz\nbar: NAME\nbaz: NAME\n-foo is ambiguous.*given a PythonTokenTypes\\.NAME.*bar or baz] 
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/main.py", line 269, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/main.py", line 323, in _main
INTERNALERROR>     config.hook.pytest_runtestloop(session=session)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/hooks.py", line 286, in __call__
INTERNALERROR>     return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/manager.py", line 84, in <lambda>
INTERNALERROR>     self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 208, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 80, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/main.py", line 348, in pytest_runtestloop
INTERNALERROR>     item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/hooks.py", line 286, in __call__
INTERNALERROR>     return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/manager.py", line 84, in <lambda>
INTERNALERROR>     self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 208, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 80, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/runner.py", line 109, in pytest_runtest_protocol
INTERNALERROR>     runtestprotocol(item, nextitem=nextitem)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/runner.py", line 126, in runtestprotocol
INTERNALERROR>     reports.append(call_and_report(item, "call", log))
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/runner.py", line 217, in call_and_report
INTERNALERROR>     report: TestReport = hook.pytest_runtest_makereport(item=item, call=call)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/hooks.py", line 286, in __call__
INTERNALERROR>     return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/manager.py", line 84, in <lambda>
INTERNALERROR>     self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 203, in _multicall
INTERNALERROR>     gen.send(outcome)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/skipping.py", line 272, in pytest_runtest_makereport
INTERNALERROR>     rep = outcome.get_result()
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 80, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/pluggy/callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/runner.py", line 337, in pytest_runtest_makereport
INTERNALERROR>     return TestReport.from_item_and_call(item, call)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/reports.py", line 322, in from_item_and_call
INTERNALERROR>     longrepr = item.repr_failure(excinfo)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/python.py", line 1677, in repr_failure
INTERNALERROR>     return self._repr_failure_py(excinfo, style=style)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/nodes.py", line 398, in _repr_failure_py
INTERNALERROR>     return excinfo.getrepr(
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/_code/code.py", line 648, in getrepr
INTERNALERROR>     return fmt.repr_excinfo(self)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/_code/code.py", line 905, in repr_excinfo
INTERNALERROR>     reprtraceback = self.repr_traceback(excinfo_)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/_code/code.py", line 846, in repr_traceback
INTERNALERROR>     reprentry = self.repr_traceback_entry(entry, einfo)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/_code/code.py", line 798, in repr_traceback_entry
INTERNALERROR>     s = self.get_source(source, line_index, excinfo, short=short)
INTERNALERROR>   File "/usr/lib/python3.10/site-packages/_pytest/_code/code.py", line 731, in get_source
INTERNALERROR>     lines.append(self.flow_marker + "   " + source.lines[line_index])
INTERNALERROR> IndexError: list index out of range

This seems like an error in pytest. It's been already reported in pytest upstream. 

For the build logs, see:
https://copr-be.cloud.fedoraproject.org/results/@python/python3.10/fedora-rawhide-x86_64/02141516-python-parso/

For all our attempts to build python-parso with Python 3.10, see:
https://copr.fedorainfracloud.org/coprs/g/python/python3.10/package/python-parso/

Testing and mass rebuild of packages is happening in copr. You can follow these instructions to test locally in mock if your package builds with Python 3.10:
https://copr.fedorainfracloud.org/coprs/g/python/python3.10/

Let us know here if you have any questions.

Python 3.10 will be included in Fedora 35. To make that update smoother, we're building Fedora packages with early pre-releases of Python 3.10.
A build failure prevents us from testing all dependent packages (transitive [Build]Requires), so if this package is required a lot, it's important for us to get it fixed soon.
We'd appreciate help from the people who know this package best, but if you don't want to work on this now, let us know so we can try to work around it on our side.

Comment 1 Miro Hrončok 2021-04-22 09:10:18 UTC
Note that the INTERNALERROR is tracked in https://github.com/pytest-dev/pytest/pull/8227 but it IMHO only happens for tests that fail.

Comment 2 Miro Hrončok 2021-04-22 10:11:08 UTC
Error with patched pytest:


=================================== FAILURES ===================================
_ test_ambiguities[foo: bar | baz\nbar: NAME\nbaz: NAME\n-foo is ambiguous.*given a PythonTokenTypes\\.NAME.*bar or baz] _

grammar = 'foo: bar | baz\nbar: NAME\nbaz: NAME\n'
error_match = 'foo is ambiguous.*given a PythonTokenTypes\\.NAME.*bar or baz'

    @pytest.mark.parametrize(
        'grammar, error_match', [
            ['foo: bar | baz\nbar: NAME\nbaz: NAME\n',
             r"foo is ambiguous.*given a PythonTokenTypes\.NAME.*bar or baz"],
            ['''foo: bar | baz\nbar: 'x'\nbaz: "x"\n''',
             r"foo is ambiguous.*given a ReservedString\(x\).*bar or baz"],
            ['''foo: bar | 'x'\nbar: 'x'\n''',
             r"foo is ambiguous.*given a ReservedString\(x\).*bar or foo"],
            # An ambiguity with the second (not the first) child of a production
            ['outer: "a" [inner] "b" "c"\ninner: "b" "c" [inner]\n',
             r"outer is ambiguous.*given a ReservedString\(b\).*inner or outer"],
            # An ambiguity hidden by a level of indirection (middle)
            ['outer: "a" [middle] "b" "c"\nmiddle: inner\ninner: "b" "c" [inner]\n',
             r"outer is ambiguous.*given a ReservedString\(b\).*middle or outer"],
        ]
    )
    def test_ambiguities(grammar, error_match):
        with pytest.raises(ValueError, match=error_match):
>           generate_grammar(grammar, tokenize.PythonTokenTypes)

test/test_pgen2.py:357: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

bnf_grammar = 'foo: bar | baz\nbar: NAME\nbaz: NAME\n'
token_namespace = <enum 'PythonTokenTypes'>

    def generate_grammar(bnf_grammar: str, token_namespace) -> Grammar:
        """
        ``bnf_text`` is a grammar in extended BNF (using * for repetition, + for
        at-least-once repetition, [] for optional parts, | for alternatives and ()
        for grouping).
    
        It's not EBNF according to ISO/IEC 14977. It's a dialect Python uses in its
        own parser.
        """
        rule_to_dfas = {}
        start_nonterminal = None
        for nfa_a, nfa_z in GrammarParser(bnf_grammar).parse():
            # _dump_nfa(nfa_a, nfa_z)
            dfas = _make_dfas(nfa_a, nfa_z)
            # _dump_dfas(dfas)
            # oldlen = len(dfas)
            _simplify_dfas(dfas)
            # newlen = len(dfas)
            rule_to_dfas[nfa_a.from_rule] = dfas
            # print(nfa_a.from_rule, oldlen, newlen)
    
            if start_nonterminal is None:
                start_nonterminal = nfa_a.from_rule
    
        reserved_strings: Mapping[str, ReservedString] = {}
        for nonterminal, dfas in rule_to_dfas.items():
            for dfa_state in dfas:
                for terminal_or_nonterminal, next_dfa in dfa_state.arcs.items():
                    if terminal_or_nonterminal in rule_to_dfas:
                        dfa_state.nonterminal_arcs[terminal_or_nonterminal] = next_dfa
                    else:
                        transition = _make_transition(
                            token_namespace,
                            reserved_strings,
                            terminal_or_nonterminal
                        )
                        dfa_state.transitions[transition] = DFAPlan(next_dfa)
    
>       _calculate_tree_traversal(rule_to_dfas)

parso/pgen2/generator.py:278: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

nonterminal_to_dfas = {'bar': [<DFAState: bar is_final=False>, <DFAState: bar is_final=True>], 'baz': [<DFAState: baz is_final=False>, <DFAState: baz is_final=True>], 'foo': [<DFAState: foo is_final=False>, <DFAState: foo is_final=True>]}

    def _calculate_tree_traversal(nonterminal_to_dfas):
        """
        By this point we know how dfas can move around within a stack node, but we
        don't know how we can add a new stack node (nonterminal transitions).
        """
        # Map from grammar rule (nonterminal) name to a set of tokens.
        first_plans = {}
    
        nonterminals = list(nonterminal_to_dfas.keys())
        nonterminals.sort()
        for nonterminal in nonterminals:
            if nonterminal not in first_plans:
                _calculate_first_plans(nonterminal_to_dfas, first_plans, nonterminal)
    
        # Now that we have calculated the first terminals, we are sure that
        # there is no left recursion.
    
        for dfas in nonterminal_to_dfas.values():
            for dfa_state in dfas:
                transitions = dfa_state.transitions
                for nonterminal, next_dfa in dfa_state.nonterminal_arcs.items():
                    for transition, pushes in first_plans[nonterminal].items():
                        if transition in transitions:
                            prev_plan = transitions[transition]
                            # Make sure these are sorted so that error messages are
                            # at least deterministic
                            choices = sorted([
                                (
                                    prev_plan.dfa_pushes[0].from_rule
                                    if prev_plan.dfa_pushes
                                    else prev_plan.next_dfa.from_rule
                                ),
                                (
                                    pushes[0].from_rule
                                    if pushes else next_dfa.from_rule
                                ),
                            ])
>                           raise ValueError(
                                "Rule %s is ambiguous; given a %s token, we "
                                "can't determine if we should evaluate %s or %s."
                                % (
                                    (
                                        dfa_state.from_rule,
                                        transition,
                                    ) + tuple(choices)
                                )
                            )
E                           ValueError: Rule foo is ambiguous; given a NAME token, we can't determine if we should evaluate bar or baz.

parso/pgen2/generator.py:339: ValueError

During handling of the above exception, another exception occurred:

grammar = 'foo: bar | baz\nbar: NAME\nbaz: NAME\n'
error_match = 'foo is ambiguous.*given a PythonTokenTypes\\.NAME.*bar or baz'

>   ???
E   AssertionError: Regex pattern 'foo is ambiguous.*given a PythonTokenTypes\\.NAME.*bar or baz' does not match "Rule foo is ambiguous; given a NAME token, we can't determine if we should evaluate bar or baz.".

test/test_pgen2.py:-1: AssertionError
=========================== short test summary info ============================
FAILED test/test_pgen2.py::test_ambiguities[foo: bar | baz\nbar: NAME\nbaz: NAME\n-foo is ambiguous.*given a PythonTokenTypes\\.NAME.*bar or baz]

Comment 3 Miro Hrončok 2021-04-22 10:13:11 UTC
There were some changes in repr() in latest alpha of Python 3.10.
https://docs.python.org/3.10/whatsnew/changelog.html#python-3-10-0-alpha-7

bpo-40066: Enum: adjust repr() to show only enum and member name (not value,
nor angle brackets) and str() to show only member name. Update and improve
documentation to match.

bpo-40066: Enum’s repr() and str() have changed: repr() is now
EnumClass.MemberName and str() is MemberName. Additionally, stdlib Enum’s whose
contents are available as module attributes, such as RegexFlag.IGNORECASE, have
their repr() as module.name, e.g. re.IGNORECASE.
https://bugs.python.org/issue40066

Comment 4 Miro Hrončok 2021-04-22 10:28:50 UTC
Patched in copr with https://github.com/davidhalter/parso/pull/186

Will give upstream some time for feedback before backporting.


Note You need to log in before you can comment on or make changes to this bug.