mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Arnaldo Carvalho de Melo <acme@kernel.org>
To: Ian Rogers <irogers@google.com>
Cc: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	Namhyung Kim <namhyung@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
	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: Re: [PATCH v1 09/13] perf test: Add a test for the treport script
Date: Sat, 3 Oct 2026 11:35:44 +0200	[thread overview]
Message-ID: <asDMcH6oLeO4ABTS@x2> (raw)
In-Reply-To: <20261002182624.3259797-10-irogers@google.com>

On Fri, Oct 02, 2026 at 11:26:18AM -0700, Ian Rogers wrote:
> 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.

+        tabs.active = "flame"
+        await pilot.pause()
+        await pilot.press("down", "down", "right", "enter", "escape", "up", "left")
+        await pilot.pause()
+        print("headless UI ok")

This is super great, testing a TUI app in 'perf test', well done.

- Arnaldo
 
> 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

  reply	other threads:[~2026-10-03  9:35 UTC|newest]

Thread overview: 19+ 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-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  9:35   ` Arnaldo Carvalho de Melo [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
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=asDMcH6oLeO4ABTS@x2 \
    --to=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=irogers@google.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®