Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing Input Widgets is incredibly slow #5065

Closed
sealor opened this issue Sep 27, 2024 · 2 comments
Closed

Testing Input Widgets is incredibly slow #5065

sealor opened this issue Sep 27, 2024 · 2 comments

Comments

@sealor
Copy link

sealor commented Sep 27, 2024

I wrote some custom Input widgets. Unfortunately, testing them is really really slow.

Typing in a simple text like This is my text! needs ~15 seconds on my machine. I increasingly think about removing those test. But, of cause this would lead to other issues ... Could you please improve the execution time?

Example:
performance_issue_app.py

import cProfile
import pstats
import unittest

import pytest
from textual.app import App, ComposeResult
from textual.pilot import Pilot
from textual.widgets import Input


class PerformanceIssueApp(App):
    def compose(self) -> ComposeResult:
        yield Input().focus()


class PerformanceIssueAppTest(unittest.IsolatedAsyncioTestCase):
    async def test_type_text(self):
        app = PerformanceIssueApp()
        async with app.run_test() as pilot:  # type:Pilot
            await pilot.press(*[c for c in "This is my text!"])

            input_widget = pilot.app.query_exactly_one(Input)
            self.assertEqual("This is my text!", input_widget.value)


if __name__ == "__main__":
    with cProfile.Profile() as pr:
        try:
            pytest.main(["-v", __file__])
        finally:
            pstats.Stats(pr).sort_stats(pstats.SortKey.CUMULATIVE).print_stats(50)

Output:

============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-8.3.3, pluggy-1.5.0 -- /home/stefan/test/.venv/bin/python
cachedir: .pytest_cache
rootdir: /home/stefan/test
configfile: pyproject.toml
collecting ... collected 1 item

performance_app.py::PerformanceIssueAppTest::test_type_text PASSED       [100%]

============================== 1 passed in 14.99s ==============================
         1195031 function calls (1186548 primitive calls) in 15.045 seconds

   Ordered by: cumulative time
   List reduced from 2572 to 50 due to restriction <50>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   15.048   15.048 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/config/__init__.py:139(main)
   251/71    0.000    0.000   15.033    0.212 /home/stefan/test/.venv/lib/python3.12/site-packages/pluggy/_manager.py:111(_hookexec)
   251/71    0.001    0.000   15.033    0.212 /home/stefan/test/.venv/lib/python3.12/site-packages/pluggy/_callers.py:53(_multicall)
     94/2    0.000    0.000   15.032    7.516 /home/stefan/test/.venv/lib/python3.12/site-packages/pluggy/_hooks.py:498(__call__)
        1    0.000    0.000   15.006   15.006 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/main.py:329(pytest_cmdline_main)
        1    0.000    0.000   15.006   15.006 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/main.py:270(wrap_session)
        1    0.000    0.000   14.993   14.993 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/main.py:333(_main)
      7/6    0.000    0.000   14.991    2.499 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/runner.py:319(from_call)
        1    0.000    0.000   14.985   14.985 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/main.py:350(pytest_runtestloop)
        1    0.000    0.000   14.985   14.985 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/runner.py:110(pytest_runtest_protocol)
        1    0.000    0.000   14.984   14.984 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/runner.py:118(runtestprotocol)
        3    0.000    0.000   14.984    4.995 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/runner.py:226(call_and_report)
        3    0.000    0.000   14.984    4.995 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/runner.py:242(<lambda>)
        1    0.000    0.000   14.983   14.983 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/runner.py:163(pytest_runtest_call)
        1    0.000    0.000   14.983   14.983 /home/stefan/test/.venv/lib/python3.12/site-packages/_pytest/unittest.py:322(runtest)
        1    0.000    0.000   14.983   14.983 /usr/lib/python3.12/unittest/case.py:689(__call__)
        1    0.000    0.000   14.983   14.983 /usr/lib/python3.12/unittest/async_case.py:128(run)
        1    0.000    0.000   14.982   14.982 /usr/lib/python3.12/unittest/case.py:599(run)
        3    0.000    0.000   14.982    4.994 /usr/lib/python3.12/asyncio/runners.py:86(run)
        5    0.000    0.000   14.980    2.996 /usr/lib/python3.12/asyncio/base_events.py:651(run_until_complete)
        5    0.004    0.001   14.980    2.996 /usr/lib/python3.12/asyncio/base_events.py:627(run_forever)
        1    0.000    0.000   14.980   14.980 /usr/lib/python3.12/unittest/async_case.py:89(_callTestMethod)
        1    0.000    0.000   14.980   14.980 /usr/lib/python3.12/unittest/async_case.py:109(_callMaybeAsync)
     1628    0.059    0.000   14.976    0.009 /usr/lib/python3.12/asyncio/base_events.py:1910(_run_once)
     1628    0.014    0.000   13.140    0.008 /usr/lib/python3.12/selectors.py:451(select)
     1628   13.119    0.008   13.119    0.008 {method 'poll' of 'select.epoll' objects}
     1724    0.011    0.000    1.752    0.001 /usr/lib/python3.12/asyncio/events.py:86(_run)
     1726    0.011    0.000    1.741    0.001 {method 'run' of '_contextvars.Context' objects}
     2641    0.015    0.000    1.128    0.000 /usr/lib/python3.12/traceback.py:377(extract)
     2641    0.367    0.000    1.113    0.000 /usr/lib/python3.12/traceback.py:399(_extract_from_extended_frame_gen)
     1460    0.012    0.000    1.071    0.001 /usr/lib/python3.12/asyncio/tasks.py:653(sleep)
      670    0.002    0.000    1.063    0.002 /home/stefan/test/performance_app.py:16(test_type_text)
      666    0.001    0.000    1.050    0.002 /home/stefan/test/.venv/lib/python3.12/site-packages/textual/pilot.py:75(press)
      665    0.003    0.000    1.048    0.002 /home/stefan/test/.venv/lib/python3.12/site-packages/textual/app.py:1636(_press_keys)
      696    0.028    0.000    1.039    0.001 /home/stefan/test/.venv/lib/python3.12/site-packages/textual/_wait.py:8(wait_for_idle)
      931    0.007    0.000    0.751    0.001 /usr/lib/python3.12/traceback.py:221(extract_stack)
      890    0.007    0.000    0.744    0.001 /usr/lib/python3.12/asyncio/base_events.py:447(create_future)
     1711    0.014    0.000    0.421    0.000 /usr/lib/python3.12/asyncio/events.py:36(__init__)
     1710    0.015    0.000    0.402    0.000 /usr/lib/python3.12/asyncio/format_helpers.py:62(extract_stack)
      892    0.007    0.000    0.342    0.000 {method 'set_result' of '_asyncio.Future' objects}
      961    0.009    0.000    0.341    0.000 /usr/lib/python3.12/asyncio/base_events.py:785(call_soon)
      729    0.004    0.000    0.321    0.000 /usr/lib/python3.12/asyncio/futures.py:311(_set_result_unless_cancelled)
    25370    0.055    0.000    0.287    0.000 /usr/lib/python3.12/linecache.py:52(checkcache)
      978    0.010    0.000    0.275    0.000 /usr/lib/python3.12/asyncio/base_events.py:814(_call_soon)
    24953    0.227    0.000    0.228    0.000 {built-in method posix.stat}
      732    0.007    0.000    0.207    0.000 /usr/lib/python3.12/asyncio/base_events.py:743(call_later)
      732    0.008    0.000    0.198    0.000 /usr/lib/python3.12/asyncio/base_events.py:767(call_at)
      106    0.000    0.000    0.181    0.002 /home/stefan/test/.venv/lib/python3.12/site-packages/textual/timer.py:142(_run_timer)
      106    0.004    0.000    0.181    0.002 /home/stefan/test/.venv/lib/python3.12/site-packages/textual/timer.py:149(_run)
      734    0.006    0.000    0.177    0.000 /usr/lib/python3.12/asyncio/events.py:155(cancel)

Textual Diagnostics

Versions

Name Value
Textual 0.80.1
Rich 13.8.1

Python

Name Value
Version 3.12.3
Implementation CPython
Compiler GCC 13.2.0
Executable /home/stefan/test/.venv/bin/python

Operating System

Name Value
System Linux
Release 6.8.0-45-generic
Version #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:02:04 UTC 2024

Terminal

Name Value
Terminal Application Unknown
TERM xterm-256color
COLORTERM Not set
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=221, height=26
legacy_windows False
min_width 1
max_width 221
is_terminal True
encoding utf-8
max_height 26
justify None
overflow None
no_wrap False
highlight None
markup None
height None
Copy link

We found the following entry in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

@TomJGooding
Copy link
Contributor

I'm not so familiar with unittest, but this test only takes a second to run on my machine just using pytest along with the pytest-asyncio plugin:

from textual.app import App, ComposeResult
from textual.widgets import Input


class PerformanceIssueApp(App):
    def compose(self) -> ComposeResult:
        yield Input()


async def test_type_text() -> None:
    app = PerformanceIssueApp()
    async with app.run_test() as pilot:
        await pilot.press(*"This is my text!")

        input_widget = pilot.app.query_exactly_one(Input)
        assert input_widget.value == "This is my text!"

@Textualize Textualize locked and limited conversation to collaborators Sep 27, 2024
@willmcgugan willmcgugan converted this issue into discussion #5068 Sep 27, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants