#!/bin/bash # perf script ilist and perf list --tui test # SPDX-License-Identifier: GPL-2.0 set -e shelldir=$(dirname "$0") # shellcheck source=lib/setup_python.sh . "${shelldir}"/lib/setup_python.sh if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then echo "Skipping test, perf python module not found" exit 2 fi if ! "$PYTHON" -c 'import textual' > /dev/null 2>&1; then echo "Skipping test, python textual library not found" exit 2 fi err=0 output=$(mktemp /tmp/__perf_ilist_test.output.XXXXX) cleanup() { rm -f "${output}" trap - EXIT TERM INT } trap_cleanup() { echo "Unexpected signal in ${FUNCNAME[1]}" cleanup exit 1 } trap trap_cleanup EXIT TERM INT test_plumbing() { echo "ilist plumbing test" # The script should be found and run by perf script, --help doesn't # need a terminal. if ! perf script ilist --help > "${output}" 2>&1; then echo "ilist plumbing test [Failed: script failed]" cat "${output}" err=1 return fi if ! grep -q -- "--interval" "${output}"; then echo "ilist plumbing test [Failed: script not launched]" cat "${output}" err=1 return fi echo "ilist plumbing test [Success]" } test_list_tui() { echo "perf list --tui plumbing test" # Arguments after -- are passed to the script launched by perf list. if ! perf list --tui -- --help > "${output}" 2>&1; then echo "perf list --tui plumbing test [Failed: script failed]" cat "${output}" err=1 return fi if ! grep -q -- "--interval" "${output}"; then echo "perf list --tui plumbing test [Failed: script not launched]" cat "${output}" err=1 return fi echo "perf list --tui plumbing test [Success]" } test_headless_ui() { echo "ilist headless UI test" # Drive the textual app without a terminal. Opening counters may fail # for want of permissions, in which case an error dialog is shown. if ! "$PYTHON" > "${output}" 2>&1 <<'EOF' import asyncio import ilist from textual.widgets import Label, Tree async def dismiss_error(app, pilot) -> bool: """Close the error dialog if shown, returning whether it was.""" if not isinstance(app.screen, ilist.ErrorScreen): return False await pilot.click("#error") await pilot.pause() if isinstance(app.screen, ilist.ErrorScreen): raise RuntimeError("Error dialog not dismissed") return True async def search(pilot, text: str) -> None: await pilot.press("s") await pilot.pause() await pilot.press(*text, "enter") await pilot.pause() def find_leaf(node, cls): """Find the first node whose data is of type cls.""" if isinstance(node.data, cls): return node for child in node.children: found = find_leaf(child, cls) if found: return found return None async def check_counting(app, pilot, name: str) -> None: """Check the selected event is shown and counting, or that it failed to open.""" await pilot.pause(0.5) if await dismiss_error(app, pilot): print(f"{name}: failed to open") return shown = str(app.query_one("#event_name", Label).render()) if shown != name: raise RuntimeError(f"Selected {shown} rather than {name}") if not app.query(ilist.CounterSparkline) or not app.query("#counter_total"): raise RuntimeError(f"No counters shown for {name}") print(f"{name}: total {app.query_one('#counter_total', Label).render()}") async def run() -> None: app = ilist.IListApp(0.1) async with app.run_test(size=(120, 40)) as pilot: await pilot.pause() tree = app.query_one("#root", Tree) top = [str(node.label) for node in tree.root.children] if top != ["PMUs", "Metrics"]: raise RuntimeError(f"Unexpected tree: {top}") pmus = [str(node.label) for node in tree.root.children[0].children] if "software" not in pmus: raise RuntimeError(f"No software PMU in: {pmus}") # Search for a software event, which exists everywhere. The search # result is selected which opens the event. await search(pilot, "task-clock") found = app.cur_search_result if not found or not isinstance(found.data, ilist.PmuEvent) or \ "task-clock" not in str(found.label): raise RuntimeError(f"Search didn't find task-clock: {found}") await check_counting(app, pilot, found.data.name()) if not app.query_one("#active_search", Label).display: raise RuntimeError("Active search not shown") await pilot.press("n", "p", "down", "up", "c") await pilot.pause(0.2) await dismiss_error(app, pilot) # Searching for something that doesn't exist shows an error. await search(pilot, "no-such-event-xyzzy") if not await dismiss_error(app, pilot): raise RuntimeError("No error for a failed search") # Select a metric, if there are any. metric = find_leaf(tree.root, ilist.Metric) if metric: app.expand_and_select(metric) await check_counting(app, pilot, metric.data.name()) print("headless UI ok") asyncio.run(run()) EOF then echo "ilist headless UI test [Failed: exception]" cat "${output}" err=1 return fi if ! grep -q "headless UI ok" "${output}"; then echo "ilist headless UI test [Failed: unexpected output]" cat "${output}" err=1 return fi echo "ilist headless UI test [Success]" } test_plumbing test_list_tui test_headless_ui cleanup exit $err