mirror of https://lore.kernel.org/lkml/
 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 09/13] perf test: Add a test for the treport script
Date: Fri,  2 Oct 2026 11:26:18 -0700	[thread overview]
Message-ID: <20261002182624.3259797-10-irogers@google.com> (raw)
In-Reply-To: <20261002182624.3259797-1-irogers@google.com>

From: Alice Rogers <alice.mei.rogers@gmail.com>

Test launching treport through perf script, the error for a file that
isn't a perf.data file, drive the textual app headless checking the
profile matches one built without the UI and exercising the key
bindings, and that quitting while loading stops the background load.

Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Alice Rogers <alice.mei.rogers@gmail.com>
Co-developed-by: Ian Rogers <irogers@google.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/shell/test_treport_python.sh | 247 ++++++++++++++++++
 1 file changed, 247 insertions(+)
 create mode 100755 tools/perf/tests/shell/test_treport_python.sh

diff --git a/tools/perf/tests/shell/test_treport_python.sh b/tools/perf/tests/shell/test_treport_python.sh
new file mode 100755
index 000000000000..155993b8be79
--- /dev/null
+++ b/tools/perf/tests/shell/test_treport_python.sh
@@ -0,0 +1,247 @@
+#!/bin/bash
+# perf script treport 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
+perfdata=
+output=
+badfile=
+
+cleanup() {
+	[ -n "${perfdata}" ] && rm -f "${perfdata}"*
+	rm -f "${output}" "${badfile}"
+	trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+	echo "Unexpected signal in ${FUNCNAME[1]}"
+	cleanup
+	exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+perfdata=$(mktemp /tmp/__perf_treport_test.perf.data.XXXXX)
+output=$(mktemp /tmp/__perf_treport_test.output.XXXXX)
+badfile=$(mktemp /tmp/__perf_treport_test.bad.XXXXX)
+
+test_plumbing() {
+	echo "treport plumbing test"
+	# A missing input file should be reported by the script launched
+	# through perf script, this doesn't need a terminal.
+	if perf script treport -i "${perfdata}.missing" > "${output}" 2>&1; then
+		echo "treport plumbing test [Failed: missing file not an error]"
+		err=1
+		return
+	fi
+	if ! grep -q "not found" "${output}"; then
+		echo "treport plumbing test [Failed: script not launched]"
+		cat "${output}"
+		err=1
+		return
+	fi
+	echo "treport plumbing test [Success]"
+}
+
+test_bad_file() {
+	echo "treport bad file test"
+	echo "not a perf.data file" > "${badfile}"
+	# The app starts before the file is read, so the error is reported by
+	# the app exiting with a failure.
+	if ! "$PYTHON" - "${badfile}" > "${output}" 2>&1 <<'EOF'
+import asyncio
+import sys
+import treport
+
+async def run() -> None:
+    app = treport.ReportApp(sys.argv[1])
+    async with app.run_test(size=(120, 40)) as pilot:
+        for _ in range(100):
+            if app.return_code is not None:
+                break
+            await pilot.pause(0.1)
+    if app.return_code != 1:
+        raise RuntimeError(f"Unexpected return code {app.return_code}")
+    print("bad file ok")
+
+asyncio.run(run())
+EOF
+	then
+		echo "treport bad file test [Failed: exception]"
+		cat "${output}"
+		err=1
+		return
+	fi
+	if ! grep -q "bad file ok" "${output}"; then
+		echo "treport bad file test [Failed: unexpected output]"
+		cat "${output}"
+		err=1
+		return
+	fi
+	echo "treport bad file test [Success]"
+}
+
+record() {
+	# A software event with callchains that doesn't need privileges.
+	perf record -e task-clock -g -o "${perfdata}" -- perf test -w noploop \
+		> /dev/null 2>&1
+}
+
+test_headless_ui() {
+	echo "treport headless UI test"
+	# Drive the textual app without a terminal, check the profile shown
+	# matches one built without the UI and exercise the key bindings.
+	if ! "$PYTHON" - "${perfdata}" > "${output}" 2>&1 <<'EOF'
+import asyncio
+import sys
+import treport
+
+def labels(tree, tnode, out):
+    for child in tnode.children:
+        out.append(str(child.label))
+        if child.is_expanded:
+            labels(tree, child, out)
+    return out
+
+async def run() -> None:
+    expected = treport.ProfileBuilder()
+    expected.read(sys.argv[1])
+    if not expected.root.children:
+        raise RuntimeError("No samples in the profile")
+
+    # The app starts before the profile is built in a background thread.
+    app = treport.ReportApp(sys.argv[1])
+    async with app.run_test(size=(120, 40)) as pilot:
+        for _ in range(600):
+            if app.loaded:
+                break
+            await pilot.pause(0.1)
+        if not app.loaded:
+            raise RuntimeError("Timed out loading data")
+        await pilot.pause()
+        if app.sub_title.startswith("Loading") or "samples" not in app.sub_title:
+            raise RuntimeError(f"Unexpected sub-title: {app.sub_title}")
+        totals = {name: node.value for name, node in app.root.children.items()}
+        want = {name: node.value for name, node in expected.root.children.items()}
+        if totals != want:
+            raise RuntimeError(f"Profile {totals} differs from {want}")
+        tree = app.query_one(treport.ProfileTree)
+        shown = labels(tree, tree.root, [])
+        if not any("noploop" in label for label in shown):
+            raise RuntimeError(f"noploop not shown in: {shown}")
+        await pilot.press("down", "down", "enter", "up", "enter")
+        tabs = app.query_one(treport.TabbedContent)
+        tabs.active = "flame"
+        await pilot.pause()
+        await pilot.press("down", "down", "right", "enter", "escape", "up", "left")
+        await pilot.pause()
+        print("headless UI ok")
+
+asyncio.run(run())
+EOF
+	then
+		echo "treport headless UI test [Failed: exception]"
+		cat "${output}"
+		err=1
+		return
+	fi
+	if ! grep -q "headless UI ok" "${output}"; then
+		echo "treport headless UI test [Failed: unexpected output]"
+		cat "${output}"
+		err=1
+		return
+	fi
+	echo "treport headless UI test [Success]"
+}
+
+test_cancel() {
+	echo "treport cancel test"
+	# Quitting while loading should stop the background thread without a
+	# "processing failed" error from the session.
+	if ! "$PYTHON" - "${perfdata}" > "${output}" 2>&1 <<'EOF'
+import asyncio
+import sys
+import time
+import treport
+
+# Check for cancellation often and slow processing each sample so that the
+# load can't finish before it is cancelled.
+treport.ProfileBuilder.PROGRESS_INTERVAL = 1
+process_event = treport.ProfileBuilder.process_event
+
+def slow_process_event(self, sample) -> None:
+    time.sleep(0.001)
+    process_event(self, sample)
+
+treport.ProfileBuilder.process_event = slow_process_event
+
+async def run() -> None:
+    app = treport.ReportApp(sys.argv[1])
+    async with app.run_test(size=(120, 40)) as pilot:
+        for _ in range(100):
+            loading = app.loading
+            if loading is not None and loading.nr_samples > 0:
+                break
+            await pilot.pause(0.1)
+        if loading is None or app.loaded:
+            raise RuntimeError("Background load not in progress")
+        await pilot.press("ctrl+q")
+    for _ in range(100):
+        if app.loading is None:
+            break
+        time.sleep(0.1)
+    if app.loading is not None:
+        raise RuntimeError("Background load didn't stop")
+    if not loading.cancelled or app.loaded:
+        raise RuntimeError("Background load wasn't cancelled")
+    print("cancel ok")
+
+asyncio.run(run())
+EOF
+	then
+		echo "treport cancel test [Failed: exception]"
+		cat "${output}"
+		err=1
+		return
+	fi
+	if ! grep -q "cancel ok" "${output}" || grep -q "processing failed" "${output}"; then
+		echo "treport cancel test [Failed: unexpected output]"
+		cat "${output}"
+		err=1
+		return
+	fi
+	echo "treport cancel test [Success]"
+}
+
+test_plumbing
+test_bad_file
+
+if ! record; then
+	echo "Skipping remaining tests, failed to record samples"
+	if [ $err -eq 0 ]; then
+		err=2
+	fi
+	cleanup
+	exit $err
+fi
+
+test_headless_ui
+test_cancel
+
+cleanup
+exit $err
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


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

Thread overview: 14+ 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-02 18:26 ` [PATCH v1 02/13] perf python: Quietly stop processing events when a callback raises Ian Rogers
2026-10-02 18:26 ` [PATCH v1 03/13] perf python: Lazily copy events and samples from process_events Ian Rogers
2026-10-02 18:26 ` [PATCH v1 04/13] perf python: Lazily resolve sample callchains Ian Rogers
2026-10-02 18:26 ` [PATCH v1 05/13] perf python: Release the GIL while processing session events Ian Rogers
2026-10-02 18:26 ` [PATCH v1 06/13] perf list: Add a --tui option to launch ilist Ian Rogers
2026-10-02 18:26 ` [PATCH v1 07/13] perf test: Add a test for the ilist script Ian Rogers
2026-10-02 18:26 ` [PATCH v1 08/13] perf treport: Show the profile while it loads Ian Rogers
2026-10-02 18:26 ` Ian Rogers [this message]
2026-10-02 18:26 ` [PATCH v1 10/13] perf timechart: Add an interactive --tui mode Ian Rogers
2026-10-02 18:26 ` [PATCH v1 11/13] perf test: Add a test for perf timechart --tui Ian Rogers
2026-10-02 18:26 ` [PATCH v1 12/13] perf timechart: Add a --live mode to the TUI Ian Rogers
2026-10-02 18:26 ` [PATCH v1 13/13] perf test: Test perf timechart --live Ian Rogers

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-10-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®