All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	 Arnaldo Carvalho de Melo <acme@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
	 Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	 James Clark <james.clark@linaro.org>,
	Thomas Falcon <thomas.falcon@intel.com>,
	 Alice Rogers <alice.mei.rogers@gmail.com>,
	Changbin Du <changbin.du@huawei.com>,
	 Tengda Wu <wutengda@huaweicloud.com>, tanze <tanze@kylinos.cn>,
	 Athira Rajeev <atrajeev@linux.ibm.com>,
	Dapeng Mi <dapeng1.mi@linux.intel.com>,
	 linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org
Subject: [PATCH v1 07/13] perf test: Add a test for the ilist script
Date: Fri,  2 Oct 2026 11:26:16 -0700	[thread overview]
Message-ID: <20261002182624.3259797-8-irogers@google.com> (raw)
In-Reply-To: <20261002182624.3259797-1-irogers@google.com>

Test launching ilist through perf script and perf list --tui, and drive
the textual app headless: check the tree of PMUs and metrics, search for
and select the software task-clock event checking its counters are
shown, check a failed search shows an error and select a metric. Failing
to open counters, for example for want of permissions, is tolerated as
an error dialog is shown.

Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/shell/test_ilist_python.sh | 181 ++++++++++++++++++++
 1 file changed, 181 insertions(+)
 create mode 100755 tools/perf/tests/shell/test_ilist_python.sh

diff --git a/tools/perf/tests/shell/test_ilist_python.sh b/tools/perf/tests/shell/test_ilist_python.sh
new file mode 100755
index 000000000000..83048474710d
--- /dev/null
+++ b/tools/perf/tests/shell/test_ilist_python.sh
@@ -0,0 +1,181 @@
+#!/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
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


  parent reply	other threads:[~2026-10-02 18:26 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-02 18:26 [PATCH v1 00/13] perf timechart/list/treport: Interactive Textual TUIs and perf python session improvements Ian Rogers
2026-10-02 18:26 ` [PATCH v1 01/13] perf session: Don't flush remaining events once processing is done Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 02/13] perf python: Quietly stop processing events when a callback raises Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 03/13] perf python: Lazily copy events and samples from process_events Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 04/13] perf python: Lazily resolve sample callchains Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 05/13] perf python: Release the GIL while processing session events Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 06/13] perf list: Add a --tui option to launch ilist Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` Ian Rogers [this message]
2026-10-03  1:33   ` [PATCH v1 07/13] perf test: Add a test for the ilist script sashiko-bot
2026-10-02 18:26 ` [PATCH v1 08/13] perf treport: Show the profile while it loads Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-03  9:31   ` Arnaldo Carvalho de Melo
2026-10-02 18:26 ` [PATCH v1 09/13] perf test: Add a test for the treport script Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-03  9:35   ` Arnaldo Carvalho de Melo
2026-10-02 18:26 ` [PATCH v1 10/13] perf timechart: Add an interactive --tui mode Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 11/13] perf test: Add a test for perf timechart --tui Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 12/13] perf timechart: Add a --live mode to the TUI Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 13/13] perf test: Test perf timechart --live Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-03  9:10 ` [PATCH v1 00/13] perf timechart/list/treport: Interactive Textual TUIs and perf python session improvements Arnaldo Carvalho de Melo
2026-10-03  9:12   ` Arnaldo Carvalho de Melo
2026-10-03  9:15     ` Arnaldo Carvalho de Melo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261002182624.3259797-8-irogers@google.com \
    --to=irogers@google.com \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alice.mei.rogers@gmail.com \
    --cc=atrajeev@linux.ibm.com \
    --cc=changbin.du@huawei.com \
    --cc=dapeng1.mi@linux.intel.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=peterz@infradead.org \
    --cc=tanze@kylinos.cn \
    --cc=thomas.falcon@intel.com \
    --cc=wutengda@huaweicloud.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.