mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #1615 from Textualize/test-freeze
Fix for test freeze
This commit is contained in:
@@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Fixed TextLog wrapping issue https://github.com/Textualize/textual/issues/1554
|
||||
- Fixed issue with TextLog not writing anything before layout https://github.com/Textualize/textual/issues/1498
|
||||
- Fixed an exception when populating a child class of `ListView` purely from `compose` https://github.com/Textualize/textual/issues/1588
|
||||
- Fixed freeze in tests https://github.com/Textualize/textual/issues/1608
|
||||
|
||||
## [0.9.1] - 2022-12-30
|
||||
|
||||
|
||||
23
src/textual/_asyncio.py
Normal file
23
src/textual/_asyncio.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Compatibility layer for asyncio.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
__all__ = ["create_task"]
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from asyncio import create_task
|
||||
|
||||
else:
|
||||
import asyncio
|
||||
from asyncio import create_task as _create_task
|
||||
from typing import Awaitable
|
||||
|
||||
def create_task(coroutine: Awaitable, *, name: str | None = None) -> asyncio.Task:
|
||||
"""Schedule the execution of a coroutine object in a spawn task."""
|
||||
return _create_task(coroutine)
|
||||
@@ -1,8 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from concurrent.futures import Future
|
||||
from functools import partial
|
||||
import inspect
|
||||
import io
|
||||
import os
|
||||
@@ -12,8 +10,10 @@ import threading
|
||||
import unicodedata
|
||||
import warnings
|
||||
from asyncio import Task
|
||||
from concurrent.futures import Future
|
||||
from contextlib import asynccontextmanager, redirect_stderr, redirect_stdout
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from pathlib import Path, PurePath
|
||||
from queue import Queue
|
||||
from time import perf_counter
|
||||
@@ -41,9 +41,10 @@ from rich.protocol import is_renderable
|
||||
from rich.segment import Segment, Segments
|
||||
from rich.traceback import Traceback
|
||||
|
||||
from . import actions, Logger, LogGroup, LogVerbosity, events, log, messages
|
||||
from . import Logger, LogGroup, LogVerbosity, actions, events, log, messages
|
||||
from ._animator import DEFAULT_EASING, Animatable, Animator, EasingFunction
|
||||
from ._ansi_sequences import SYNC_END, SYNC_START
|
||||
from ._asyncio import create_task
|
||||
from ._callback import invoke
|
||||
from ._context import active_app
|
||||
from ._event_broker import NoHandler, extract_handler_actions
|
||||
@@ -69,7 +70,6 @@ from .renderables.blank import Blank
|
||||
from .screen import Screen
|
||||
from .widget import AwaitMount, Widget
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .devtools.client import DevtoolsClient
|
||||
from .pilot import Pilot
|
||||
@@ -859,7 +859,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
)
|
||||
|
||||
# Launch the app in the "background"
|
||||
app_task = asyncio.create_task(run_app(app))
|
||||
app_task = create_task(run_app(app), name=f"run_test {app}")
|
||||
|
||||
# Wait until the app has performed all startup routines.
|
||||
await app_ready_event.wait()
|
||||
@@ -914,7 +914,9 @@ class App(Generic[ReturnType], DOMNode):
|
||||
raise
|
||||
|
||||
pilot = Pilot(app)
|
||||
auto_pilot_task = asyncio.create_task(run_auto_pilot(auto_pilot, pilot))
|
||||
auto_pilot_task = create_task(
|
||||
run_auto_pilot(auto_pilot, pilot), name=repr(pilot)
|
||||
)
|
||||
|
||||
try:
|
||||
await app._process_messages(
|
||||
@@ -1653,6 +1655,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
return []
|
||||
|
||||
new_widgets = list(widgets)
|
||||
|
||||
if before is not None or after is not None:
|
||||
# There's a before or after, which means there's going to be an
|
||||
# insertion, so make it easier to get the new things in the
|
||||
@@ -1668,6 +1671,11 @@ class App(Generic[ReturnType], DOMNode):
|
||||
if widget.children:
|
||||
self._register(widget, *widget.children)
|
||||
apply_stylesheet(widget)
|
||||
|
||||
if not self._running:
|
||||
# If the app is not running, prevent awaiting of the widget tasks
|
||||
return []
|
||||
|
||||
return list(widgets)
|
||||
|
||||
def _unregister(self, widget: Widget) -> None:
|
||||
@@ -2111,7 +2119,9 @@ class App(Generic[ReturnType], DOMNode):
|
||||
removed_widgets = self._detach_from_dom(widgets)
|
||||
|
||||
finished_event = asyncio.Event()
|
||||
asyncio.create_task(prune_widgets_task(removed_widgets, finished_event))
|
||||
create_task(
|
||||
prune_widgets_task(removed_widgets, finished_event), name="prune nodes"
|
||||
)
|
||||
|
||||
return AwaitRemove(finished_event)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Iterable
|
||||
from weakref import WeakSet
|
||||
|
||||
from . import Logger, events, log, messages
|
||||
from ._asyncio import create_task
|
||||
from ._callback import invoke
|
||||
from ._context import NoActiveAppError, active_app
|
||||
from ._time import time
|
||||
@@ -304,7 +305,12 @@ class MessagePump(metaclass=MessagePumpMeta):
|
||||
def _start_messages(self) -> None:
|
||||
"""Start messages task."""
|
||||
if self.app._running:
|
||||
self._task = asyncio.create_task(self._process_messages())
|
||||
self._task = create_task(
|
||||
self._process_messages(), name=f"message pump {self}"
|
||||
)
|
||||
else:
|
||||
self._closing = True
|
||||
self._closed = True
|
||||
|
||||
async def _process_messages(self) -> None:
|
||||
self._running = True
|
||||
|
||||
@@ -7,7 +7,6 @@ Timer objects are created by [set_interval][textual.message_pump.MessagePump.set
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import weakref
|
||||
from asyncio import CancelledError, Event, Task
|
||||
from typing import Awaitable, Callable, Union
|
||||
@@ -15,6 +14,7 @@ from typing import Awaitable, Callable, Union
|
||||
from rich.repr import Result, rich_repr
|
||||
|
||||
from . import _clock, events
|
||||
from ._asyncio import create_task
|
||||
from ._callback import invoke
|
||||
from ._context import active_app
|
||||
from ._time import sleep
|
||||
@@ -89,7 +89,7 @@ class Timer:
|
||||
Returns:
|
||||
A Task instance for the timer.
|
||||
"""
|
||||
self._task = asyncio.create_task(self._run_timer())
|
||||
self._task = create_task(self._run_timer(), name=self.name)
|
||||
return self._task
|
||||
|
||||
def stop_no_wait(self) -> None:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import Lock, wait
|
||||
from asyncio import Lock, create_task, wait
|
||||
from collections import Counter
|
||||
from fractions import Fraction
|
||||
@@ -35,6 +36,7 @@ from rich.text import Text
|
||||
from rich.traceback import Traceback
|
||||
|
||||
from . import errors, events, messages
|
||||
from ._asyncio import create_task
|
||||
from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction
|
||||
from ._arrange import DockArrangeResult, arrange
|
||||
from ._context import active_app
|
||||
@@ -93,7 +95,7 @@ class AwaitMount:
|
||||
async def await_mount() -> None:
|
||||
if self._widgets:
|
||||
aws = [
|
||||
create_task(widget._mounted_event.wait())
|
||||
create_task(widget._mounted_event.wait(), name="await mount")
|
||||
for widget in self._widgets
|
||||
]
|
||||
if aws:
|
||||
|
||||
26
tests/test_freeze.py
Normal file
26
tests/test_freeze.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import pytest
|
||||
|
||||
from textual.app import App
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Footer, Header, Input
|
||||
|
||||
|
||||
class MyScreen(Screen):
|
||||
def compose(self):
|
||||
yield Header()
|
||||
yield Input()
|
||||
yield Footer()
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def on_mount(self):
|
||||
self.install_screen(MyScreen(), "myscreen")
|
||||
self.push_screen("myscreen")
|
||||
|
||||
|
||||
async def test_freeze():
|
||||
"""Regression test for https://github.com/Textualize/textual/issues/1608"""
|
||||
app = MyApp()
|
||||
with pytest.raises(Exception):
|
||||
async with app.run_test():
|
||||
raise Exception("never raised")
|
||||
Reference in New Issue
Block a user