Add debug info

This commit is contained in:
Darren Burns
2022-09-21 15:18:53 +01:00
parent 53ac7b4fa4
commit 04a513ff9f
4 changed files with 118 additions and 25 deletions

View File

@@ -6,7 +6,7 @@ class CenterLayoutExample(App):
CSS_PATH = "center_layout.css" CSS_PATH = "center_layout.css"
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Static("One", id="bottom") yield Static("Onee", id="bottom")
yield Static("Two", id="middle") yield Static("Two", id="middle")
yield Static("Three", id="top") yield Static("Three", id="top")

View File

@@ -4,6 +4,7 @@ import os
import shlex import shlex
from typing import Iterable from typing import Iterable
from textual.app import App
from textual._import_app import import_app from textual._import_app import import_app
# This module defines our "Custom Fences", powered by SuperFences # This module defines our "Custom Fences", powered by SuperFences
@@ -25,7 +26,9 @@ def format_svg(source, language, css_class, options, md, attrs, **kwargs) -> str
try: try:
rows = int(attrs.get("lines", 24)) rows = int(attrs.get("lines", 24))
columns = int(attrs.get("columns", 80)) columns = int(attrs.get("columns", 80))
svg = take_svg_screenshot(path, press, title, terminal_size=(rows, columns)) svg = take_svg_screenshot(
None, path, press, title, terminal_size=(rows, columns)
)
finally: finally:
os.chdir(cwd) os.chdir(cwd)
@@ -39,8 +42,9 @@ def format_svg(source, language, css_class, options, md, attrs, **kwargs) -> str
def take_svg_screenshot( def take_svg_screenshot(
app_path: str, app: App | None = None,
press: Iterable[str] = ("_", "_"), app_path: str | None = None,
press: Iterable[str] = ("_",),
title: str | None = None, title: str | None = None,
terminal_size: tuple[int, int] = (24, 80), terminal_size: tuple[int, int] = (24, 80),
) -> str: ) -> str:
@@ -48,7 +52,10 @@ def take_svg_screenshot(
os.environ["COLUMNS"] = str(columns) os.environ["COLUMNS"] = str(columns)
os.environ["LINES"] = str(rows) os.environ["LINES"] = str(rows)
app = import_app(app_path)
if app is None:
app = import_app(app_path)
app.console.legacy_windows = False app.console.legacy_windows = False
if title is None: if title is None:
title = app.title title = app.title

View File

@@ -1,4 +1,6 @@
import difflib import difflib
import os
import pprint
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from functools import partial from functools import partial
@@ -13,15 +15,19 @@ from _pytest.fixtures import FixtureRequest
from _pytest.main import Session from _pytest.main import Session
from _pytest.terminal import TerminalReporter from _pytest.terminal import TerminalReporter
from jinja2 import Template from jinja2 import Template
from rich import inspect
from rich.console import Console from rich.console import Console
from rich.panel import Panel from rich.panel import Panel
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from textual.app import App
from textual._doc import take_svg_screenshot from textual._doc import take_svg_screenshot
from textual._import_app import import_app
snapshot_svg_key = pytest.StashKey[str]() TEXTUAL_SNAPSHOT_SVG_KEY = pytest.StashKey[str]()
actual_svg_key = pytest.StashKey[str]() TEXTUAL_ACTUAL_SVG_KEY = pytest.StashKey[str]()
snapshot_pass = pytest.StashKey[bool]() TEXTUAL_SNAPSHOT_PASS = pytest.StashKey[bool]()
TEXTUAL_APP_KEY = pytest.StashKey[App]()
@pytest.fixture @pytest.fixture
@@ -36,15 +42,17 @@ def snap_compare(
def compare(app_path: str, snapshot: SnapshotAssertion) -> bool: def compare(app_path: str, snapshot: SnapshotAssertion) -> bool:
node = request.node node = request.node
actual_screenshot = take_svg_screenshot(app_path) app = import_app(app_path)
actual_screenshot = take_svg_screenshot(app=app)
result = snapshot == actual_screenshot result = snapshot == actual_screenshot
if result is False: if result is False:
# The split and join below is a mad hack, sorry... # The split and join below is a mad hack, sorry...
node.stash[snapshot_svg_key] = "\n".join(str(snapshot).splitlines()[1:-1]) node.stash[TEXTUAL_SNAPSHOT_SVG_KEY] = "\n".join(str(snapshot).splitlines()[1:-1])
node.stash[actual_svg_key] = actual_screenshot node.stash[TEXTUAL_ACTUAL_SVG_KEY] = actual_screenshot
node.stash[TEXTUAL_APP_KEY] = app
else: else:
node.stash[snapshot_pass] = True node.stash[TEXTUAL_SNAPSHOT_PASS] = True
return result return result
@@ -59,6 +67,8 @@ class SvgSnapshotDiff:
file_similarity: float file_similarity: float
path: PathLike path: PathLike
line_number: int line_number: int
app: App
environment: dict
def pytest_sessionfinish( def pytest_sessionfinish(
@@ -73,22 +83,28 @@ def pytest_sessionfinish(
diffs: List[SvgSnapshotDiff] = [] diffs: List[SvgSnapshotDiff] = []
num_snapshots_passing = 0 num_snapshots_passing = 0
for item in session.items: for item in session.items:
num_snapshots_passing += int(item.stash.get(snapshot_pass, False))
snapshot_svg = item.stash.get(snapshot_svg_key, None) # Grab the data our fixture attached to the pytest node
actual_svg = item.stash.get(actual_svg_key, None) num_snapshots_passing += int(item.stash.get(TEXTUAL_SNAPSHOT_PASS, False))
if snapshot_svg and actual_svg: snapshot_svg = item.stash.get(TEXTUAL_SNAPSHOT_SVG_KEY, None)
actual_svg = item.stash.get(TEXTUAL_ACTUAL_SVG_KEY, None)
app = item.stash.get(TEXTUAL_APP_KEY, None)
if snapshot_svg and actual_svg and app:
path, line_index, name = item.reportinfo() path, line_index, name = item.reportinfo()
diffs.append( diffs.append(
SvgSnapshotDiff( SvgSnapshotDiff(
snapshot=str(snapshot_svg), snapshot=str(snapshot_svg),
actual=str(actual_svg), actual=str(actual_svg),
file_similarity=100 file_similarity=100
* difflib.SequenceMatcher( * difflib.SequenceMatcher(
a=str(snapshot_svg), b=str(actual_svg) a=str(snapshot_svg), b=str(actual_svg)
).ratio(), ).ratio(),
test_name=name, test_name=name,
path=path, path=path,
line_number=line_index + 1, line_number=line_index + 1,
app=app,
environment=dict(os.environ),
) )
) )

View File

@@ -8,6 +8,8 @@
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous"> integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-4" style="background-color:#F4F8F7;"> <div class="row mb-4" style="background-color:#F4F8F7;">
<div class="col-8 p-4"> <div class="col-8 p-4">
@@ -51,14 +53,18 @@
</strong> </strong>
<span class="text-muted">({{ "%.2f"|format(diff.file_similarity) }}% source similarity)</span> <span class="text-muted">({{ "%.2f"|format(diff.file_similarity) }}% source similarity)</span>
</div> </div>
<span class="text-muted">{{ diff.path }}:{{ diff.line_number }}</span> <div class="text-muted">
{{ diff.path }}:{{ diff.line_number }}
</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
{{ diff.actual }} {{ diff.actual }}
<div class="w-100 d-flex justify-content-center mt-1"> <div class="w-100 d-flex justify-content-center mt-1">
<span class="small">Output from test</span> <span class="small">Output from test (<a href="#" class="link-primary mb-0"
data-bs-toggle="modal"
data-bs-target="#environmentModal">More info</a>)</span>
</div> </div>
</div> </div>
<div class="col"> <div class="col">
@@ -69,6 +75,63 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal modal-lg fade" id="environmentModal" tabindex="-1"
aria-labelledby="environmentModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="environmentModalLabel">More info for <span
class="font-monospace">{{ diff.test_name }}</span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body overflow-auto">
<h5>Textual App State</h5>
<table class="table mb-4">
<thead>
<tr>
<th scope="col">Variable</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
<tr>
<td class="font-monospace">app.console.legacy_windows</td>
<td class="font-monospace">{{ diff.app.console.legacy_windows }}</td>
</tr>
<tr>
<td class="font-monospace">app.console.size</td>
<td class="font-monospace">{{ diff.app.console.size }}</td>
</tr>
</tbody>
</table>
<h5>Environment (<span class="font-monospace">os.environ</span>)</h5>
<table class="table">
<thead>
<tr>
<th scope="col">Variable</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
{% for key, value in diff.environment.items() %}
<tr>
<td class="font-monospace">{{ key }}</td>
<td class="font-monospace">{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -78,21 +141,28 @@
<div class="col"> <div class="col">
<div class="card bg-light"> <div class="card bg-light">
<div class="card-body"> <div class="card-body">
<p class="card-text">If you're happy with the change, run pytest with the <span class="font-monospace text-primary">--snapshot-update</span> flag to update the snapshot.</p> <p class="card-text">If you're happy with the test output, run pytest with the <span
class="font-monospace text-primary">--snapshot-update</span> flag to update the snapshot.
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="w-100 d-flex p-4 justify-content-center"> <div class="w-100 d-flex p-4 justify-content-center">
<p class="text-muted">Report generated at UTC {{ now }}.</p> <p class="text-muted">Report generated at UTC {{ now }}.</p>
</div>
</div> </div>
</div> </div>
</div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
</body> </body>
</html> </html>