Bug 2283519 - python-pexpect fails to build with Python 3.13: FAILED tests/test_performance.py::PerformanceTestCase::test_100000, tests/test_replwrap.py::REPLWrapTestCase::test_no_change_prompt, tests/test_replwrap.py::REPLWrapTestCase::test_python
Summary: python-pexpect fails to build with Python 3.13: FAILED tests/test_performance...
Keywords:
Status: CLOSED RAWHIDE
Alias: None
Product: Fedora
Classification: Fedora
Component: python-pexpect
Version: rawhide
Hardware: Unspecified
OS: Unspecified
urgent
urgent
Target Milestone: ---
Assignee: Miro Hrončok
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks: PYTHON3.13
TreeView+ depends on / blocked
 
Reported: 2024-05-27 15:28 UTC by Karolina Surma
Modified: 2024-05-29 13:00 UTC (History)
7 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed: 2024-05-29 13:00:27 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Fedora Package Sources python-pexpect pull-request 11 0 None None None 2024-05-29 10:29:45 UTC
Github pexpect pexpect pull 794 0 None open Force NO_COLOR=1 to fix test failures with Python 3.13+ REPL 2024-05-29 10:27:23 UTC

Description Karolina Surma 2024-05-27 15:28:40 UTC
python-pexpect fails to build with Python 3.13.0b1.

=================================== FAILURES ===================================
_______________________ PerformanceTestCase.test_100000 ________________________

self = <tests.test_performance.PerformanceTestCase testMethod=test_100000>

    def test_100000(self):
        if platform.python_implementation() == 'PyPy':
            raise unittest.SkipTest("This test fails on PyPy because of REPL differences")
        print()
        start_time = time.time()
        self.plain_range (100000)
        print("100000 calls to plain_range:", (time.time() - start_time))
        start_time = time.time()
>       self.window_range(100000)

tests/test_performance.py:93: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/test_performance.py:61: in window_range
    self.assertEqual(e.expect([b'inquisition', '%d' % n], searchwindowsize=20), 1)
pexpect/spawnbase.py:354: in expect
    return self.expect_list(compiled_pattern_list,
pexpect/spawnbase.py:383: in expect_list
    return exp.expect_loop(timeout)
pexpect/expect.py:181: in expect_loop
    return self.timeout(e)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pexpect.expect.Expecter object at 0x7f09369af380>
err = TIMEOUT('Timeout exceeded.')

    def timeout(self, err=None):
        spawn = self.spawn
    
        spawn.before = spawn._before.getvalue()
        spawn.after = TIMEOUT
        index = self.searcher.timeout_index
        if index >= 0:
            spawn.match = TIMEOUT
            spawn.match_index = index
            return index
        else:
            spawn.match = None
            spawn.match_index = None
            msg = str(spawn)
            msg += '\nsearcher: %s' % self.searcher
            if err is not None:
                msg = str(err) + '\n' + msg
    
            exc = TIMEOUT(msg)
            exc.__cause__ = None    # in Python 3.x we can use "raise exc from None"
>           raise exc
E           pexpect.exceptions.TIMEOUT: Timeout exceeded.
E           <pexpect.pty_spawn.spawn object at 0x7f0936ad7b10>
E           command: /usr/bin/python3
E           args: ['/usr/bin/python3']
E           buffer (last 100 chars): b';35m>>> \x1b[0m\x1b[4D\x1b[4C'
E           before (last 100 chars): b'99993\r\n99994\r\n99995\r\n99996\r\n99997\r\n99998\r\n99999\r\n100000\r\n\x1b[?2004h\x1b[?1h\x1b=\x1b[1A\n\x1b[1;35m>>> \x1b[0m\x1b[4D\x1b[4C'
E           after: <class 'pexpect.exceptions.TIMEOUT'>
E           match: None
E           match_index: None
E           exitstatus: None
E           flag_eof: False
E           pid: 596
E           child_fd: 18
E           closed: False
E           timeout: 100
E           delimiter: <class 'pexpect.exceptions.EOF'>
E           logfile: None
E           logfile_read: None
E           logfile_send: None
E           maxread: 2000
E           ignorecase: False
E           searchwindowsize: None
E           delaybeforesend: 0.05
E           delayafterclose: 0.1
E           delayafterterminate: 0.1
E           searcher: searcher_re:
E               0: re.compile(b'inquisition')
E               1: re.compile(b'100000')

pexpect/expect.py:144: TIMEOUT
----------------------------- Captured stdout call -----------------------------

 tests.test_performance.PerformanceTestCase.test_100000 
100000 calls to plain_range: 0.9722907543182373
____________________ REPLWrapTestCase.test_no_change_prompt ____________________

self = <tests.test_replwrap.REPLWrapTestCase testMethod=test_no_change_prompt>

    def test_no_change_prompt(self):
        if platform.python_implementation() == 'PyPy':
            raise unittest.SkipTest(skip_pypy)
    
        child = pexpect.spawn(sys.executable, echo=False, timeout=5, encoding='utf-8')
        # prompt_change=None should mean no prompt change
        py = replwrap.REPLWrapper(child, u">>> ", prompt_change=None,
                                  continuation_prompt=u"... ")
        assert py.prompt == ">>> "
    
        res = py.run_command("for a in range(3): print(a)\n")
>       assert res.strip().splitlines() == ['0', '1', '2']
E       AssertionError: assert ['\x1b[0m\x1b...5D\x1b[1;35m'] == ['0', '1', '2']
E         At index 0 diff: '\x1b[0m\x1b[4D\x1b[4C\x1b[4D\x1b[1;35m\x1b[0mf\x1b[5D\x1b[5C\x1b[5D\x1b[1;35m' != '0'
E         Right contains 2 more items, first extra item: '1'
E         Full diff:
E         - ['0', '1', '2']
E         + ['\x1b[0m\x1b[4D\x1b[4C\x1b[4D\x1b[1;35m\x1b[0mf\x1b[5D\x1b[5C\x1b[5D\x1b[1;35m']

/builddir/build/BUILD/pexpect-4.9.0/tests/test_replwrap.py:134: AssertionError
_________________________ REPLWrapTestCase.test_python _________________________

self = <tests.test_replwrap.REPLWrapTestCase testMethod=test_python>

    def test_python(self):
        if platform.python_implementation() == 'PyPy':
            raise unittest.SkipTest(skip_pypy)
    
        p = replwrap.python()
        res = p.run_command('4+7')
>       assert res.strip() == '11'
E       assert "\x1b[41D\x1b...ys; sys.ps1='" == '11'
E         - 11
E         + >>> import sys; sys.ps1='

/builddir/build/BUILD/pexpect-4.9.0/tests/test_replwrap.py:118: AssertionError

=========================== short test summary info ============================
FAILED tests/test_performance.py::PerformanceTestCase::test_100000 - pexpect.exceptions.TIMEOUT: Timeout exceeded.
<pexpect.pty_spawn.spawn object at 0x7f0936ad7b10>
command: /usr/bin/python3
args: ['/usr/bin/python3']
buffer (last 100 chars): b';35m>>> \x1b[0m\x1b[4D\x1b[4C'
before (last 100 chars): b'99993\r\n99994\r\n99995\r\n99996\r\n99997\r\n99998\r\n99999\r\n100000\r\n\x1b[?2004h\x1b[?1h\x1b=\x1b[1A\n\x1b[1;35m>>> \x1b[0m\x1b[4D\x1b[4C'
after: <class 'pexpect.exceptions.TIMEOUT'>
match: None
match_index: None
exitstatus: None
flag_eof: False
pid: 596
child_fd: 18
closed: False
timeout: 100
delimiter: <class 'pexpect.exceptions.EOF'>
logfile: None
logfile_read: None
logfile_send: None
maxread: 2000
ignorecase: False
searchwindowsize: None
delaybeforesend: 0.05
delayafterclose: 0.1
delayafterterminate: 0.1
searcher: searcher_re:
    0: re.compile(b'inquisition')
    1: re.compile(b'100000')
FAILED tests/test_replwrap.py::REPLWrapTestCase::test_no_change_prompt - AssertionError: assert ['\x1b[0m\x1b...5D\x1b[1;35m'] == ['0', '1', '2']
  At index 0 diff: '\x1b[0m\x1b[4D\x1b[4C\x1b[4D\x1b[1;35m\x1b[0mf\x1b[5D\x1b[5C\x1b[5D\x1b[1;35m' != '0'
  Right contains 2 more items, first extra item: '1'
  Full diff:
  - ['0', '1', '2']
  + ['\x1b[0m\x1b[4D\x1b[4C\x1b[4D\x1b[1;35m\x1b[0mf\x1b[5D\x1b[5C\x1b[5D\x1b[1;35m']
FAILED tests/test_replwrap.py::REPLWrapTestCase::test_python - assert "\x1b[41D\x1b...ys; sys.ps1='" == '11'
  - 11
  + >>> import sys; sys.ps1='
============ 3 failed, 254 passed, 85 warnings in 339.06s (0:05:39) ============

https://docs.python.org/3.13/whatsnew/3.13.html

For the build logs, see:
https://copr-be.cloud.fedoraproject.org/results/@python/python3.13/fedora-rawhide-x86_64/07495770-python-pexpect/

For all our attempts to build python-pexpect with Python 3.13, see:
https://copr.fedorainfracloud.org/coprs/g/python/python3.13/package/python-pexpect/

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.13:
https://copr.fedorainfracloud.org/coprs/g/python/python3.13/

Let us know here if you have any questions.

Python 3.13 is planned to be included in Fedora 41.
To make that update smoother, we're building Fedora packages with all pre-releases of Python 3.13.
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 Scott Talbert 2024-05-29 02:19:26 UTC
Confirmed, I can reproduce the second two failures and see what the problem is - it seems Python 3.13 introduced color support in the interactive interpreter, which causes some problems.  Not entirely sure how to fix yet but doesn't seem too bad.

Comment 2 Miro Hrončok 2024-05-29 10:02:16 UTC
Perhaps setting NO_COLOR=1 in %check might help.

Anyway, this is blocking pytest-xdist so we need to solve this problem this week.

Comment 3 Miro Hrončok 2024-05-29 10:16:06 UTC
This makes one of the failures go away:

diff --git a/tests/test_replwrap.py b/tests/test_replwrap.py
index ddafa5d..5ac782a 100644
--- a/tests/test_replwrap.py
+++ b/tests/test_replwrap.py
@@ -124,7 +124,7 @@ class REPLWrapTestCase(unittest.TestCase):
         if platform.python_implementation() == 'PyPy':
             raise unittest.SkipTest(skip_pypy)
 
-        child = pexpect.spawn(sys.executable, echo=False, timeout=5, encoding='utf-8')
+        child = pexpect.spawn(sys.executable, echo=False, timeout=5, encoding='utf-8', env={'NO_COLOR': '1'})
         # prompt_change=None should mean no prompt change
         py = replwrap.REPLWrapper(child, u">>> ", prompt_change=None,
                                   continuation_prompt=u"... ")

Comment 4 Miro Hrončok 2024-05-29 10:18:50 UTC
This helps with the other test:

diff --git a/pexpect/replwrap.py b/pexpect/replwrap.py
index 08dbd5e..c8714a2 100644
--- a/pexpect/replwrap.py
+++ b/pexpect/replwrap.py
@@ -35,7 +35,7 @@ class REPLWrapper(object):
                  continuation_prompt=PEXPECT_CONTINUATION_PROMPT,
                  extra_init_cmd=None):
         if isinstance(cmd_or_spawn, basestring):
-            self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8')
+            self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8', env={'NO_COLOR': '1'})
         else:
             self.child = cmd_or_spawn
         if self.child.echo:

Comment 5 Miro Hrončok 2024-05-29 10:27:23 UTC
https://github.com/pexpect/pexpect/pull/794

Comment 7 Miro Hrončok 2024-05-29 10:59:52 UTC
It seems test_100000 from test_performance.py is just flaky in copr. The test has a hardcoded timeout and we might need to increase it a bit but there is no simple way (rather than a sed/patch).


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