This commit is contained in:
Will McGugan
2024-09-07 17:52:42 +01:00
parent 03ad3af83f
commit be1f2ae78b
2 changed files with 144 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
from functools import partial
from textual._callback import count_parameters
def test_functions() -> None:
"""Test count parameters of functions."""
def foo(): ...
def bar(a): ...
def baz(a, b): ...
# repeat to allow for caching
for _ in range(3):
assert count_parameters(foo) == 0
assert count_parameters(bar) == 1
assert count_parameters(baz) == 2
def test_methods() -> None:
"""Test count parameters of methods."""
class Foo:
def foo(self): ...
def bar(self, a): ...
def baz(self, a, b): ...
foo = Foo()
# repeat to allow for caching
for _ in range(3):
assert count_parameters(foo.foo) == 0
assert count_parameters(foo.bar) == 1
assert count_parameters(foo.baz) == 2
def test_partials() -> None:
"""Test count parameters of partials."""
class Foo:
def method(self, a, b, c, d): ...
foo = Foo()
partial0 = partial(foo.method)
partial1 = partial(foo.method, 10)
partial2 = partial(foo.method, b=10, c=20)
for _ in range(3):
assert count_parameters(partial0) == 4
assert count_parameters(partial0) == 4
assert count_parameters(partial1) == 3
assert count_parameters(partial1) == 3
assert count_parameters(partial2) == 2
assert count_parameters(partial2) == 2

87
tests/test_gc.py Normal file
View File

@@ -0,0 +1,87 @@
import asyncio
import gc
import pytest
from textual.app import App, ComposeResult
from textual.containers import Vertical
from textual.widgets import Footer, Header, Label
def count_nodes() -> int:
"""Count number of references to DOMNodes."""
dom_nodes = [
obj
for obj in gc.get_objects()
if any(cls.__name__ == "DOMNode" for cls in obj.__class__.__mro__)
]
print(dom_nodes)
return len(dom_nodes)
async def run_app() -> None:
"""Run a dummy app."""
class DummyApp(App):
"""Dummy app with a few widgets."""
def compose(self) -> ComposeResult:
yield Header()
with Vertical():
yield Label("foo")
yield Label("bar")
yield Footer()
app = DummyApp()
async with app.run_test() as pilot:
# We should have a bunch of DOMNodes while the test is running
assert count_nodes() > 0
await pilot.press("ctrl+c")
assert not app._running
# Force a GC collection
gc.collect()
# After the test, all DOMNodes will have been torn down
assert count_nodes() == 1
async def _count_app_nodes() -> None:
"""Regression test for https://github.com/Textualize/textual/issues/4959"""
# Should be no DOMNodes yet
assert count_nodes() == 0
await run_app()
await asyncio.sleep(0)
gc.collect()
nodes_remaining = count_nodes()
if nodes_remaining:
print("NODES REMAINING")
import objgraph
objgraph.show_backrefs(
[
obj
for obj in gc.get_objects()
if any(cls.__name__ == "App" for cls in obj.__class__.__mro__)
],
filename="graph.png",
max_depth=15,
)
assert nodes_remaining == 0
# It looks like PyTest holds on to references to DOMNodes
# So this will only pass if ran in isolation
@pytest.mark.xfail
async def test_gc():
"""Regression test for https://github.com/Textualize/textual/issues/4959"""
await _count_app_nodes()