blob: 46444311f9a61c8cfdc72603c78cbb8c1d08bcc0 [file]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import importlib.resources
import time
import unittest
import unittest.mock
from tp_shell import PerfettoTraceProcessor
class TpShellTest(unittest.TestCase):
def test_single_query_and_cleanup(self) -> None:
"""Tests that a single query works and the background process is cleaned up."""
source = importlib.resources.files("test_data").joinpath(
"perfetto_golden.fxt"
)
with importlib.resources.as_file(source) as trace_path:
with PerfettoTraceProcessor(str(trace_path)) as tp:
# 1. Run query
results = tp.run_query("SELECT count(*) as cnt FROM slice")
self.assertEqual(len(results), 1)
self.assertGreater(results[0]["cnt"], 0)
# 2. Check that process is alive inside the context
self.assertTrue(tp._finalizer.alive)
# 3. Check that process is cleaned up after exiting context
self.assertFalse(tp._finalizer.alive)
with self.assertRaises(RuntimeError):
tp.run_query("SELECT count(*) as cnt FROM slice")
def test_multiple_queries_are_fast(self) -> None:
"""Tests that running multiple queries is fast, proving a persistent connection."""
source = importlib.resources.files("test_data").joinpath(
"perfetto_golden.fxt"
)
with importlib.resources.as_file(source) as trace_path:
with PerfettoTraceProcessor(str(trace_path)) as tp:
# Warmup query
tp.run_query("SELECT count(*) as cnt FROM slice")
# Measure 3 queries
start_time = time.time()
for _ in range(3):
tp.run_query("SELECT count(*) as cnt FROM slice")
elapsed = time.time() - start_time
# Spawning a new trace_processor_shell subprocess and loading the
# perfetto_golden.fxt and performning one query would take under 500ms.
# In this test we are doing 4 queries, and the last 3 should be more or less "free"
# since they do not re-parse and load the trace file.
self.assertLess(
elapsed,
0.5,
f"Expected queries to be fast, took {elapsed:.3f}s",
)
def test_duration_events_over_50ms(self) -> None:
"""Tests extracting duration events > 50ms."""
source = importlib.resources.files("test_data").joinpath(
"perfetto_golden.fxt"
)
with importlib.resources.as_file(source) as trace_path:
with PerfettoTraceProcessor(str(trace_path)) as tp:
# 50ms = 50,000,000 ns
results = tp.run_query(
"SELECT name, dur FROM slice WHERE dur > 50000000 ORDER BY ts"
)
self.assertEqual(len(results), 20)
event_names = [r["name"] for r in results]
expected_names = ["example_duration"] * 20
self.assertEqual(event_names, expected_names)
@unittest.mock.patch("urllib.request.urlopen")
def test_http_uri_resolver_timeout(
self, mock_urlopen: unittest.mock.MagicMock
) -> None:
"""Tests that HttpUriResolver.resolve() calls urlopen with a timeout."""
from tp_shell.tp_utils import HttpUriResolver
mock_response = unittest.mock.MagicMock()
mock_response.read.side_effect = [b"data", b""]
mock_urlopen.return_value = mock_response
resolver = HttpUriResolver("http://example.com/trace.fxt")
resolver.resolve()
mock_urlopen.assert_called_once()
_, kwargs = mock_urlopen.call_args
self.assertEqual(kwargs.get("timeout"), 120)
@unittest.mock.patch("urllib.request.urlopen")
@unittest.mock.patch("tp_shell.tp_utils.resolve_trace_url")
def test_http_uri_resolver_permalink(
self,
mock_resolve_trace_url: unittest.mock.MagicMock,
mock_urlopen: unittest.mock.MagicMock,
) -> None:
"""Tests that HttpUriResolver resolves perfetto.dev permalinks."""
from tp_shell.tp_utils import HttpUriResolver
mock_resolve_trace_url.return_value = (
"http://example.com/resolved_trace.fxt"
)
mock_response = unittest.mock.MagicMock()
mock_response.read.side_effect = [b"data", b""]
mock_urlopen.return_value = mock_response
resolver = HttpUriResolver("https://ui.perfetto.dev/#!/?s=123456789")
resolver.resolve()
mock_resolve_trace_url.assert_called_once_with(
"https://ui.perfetto.dev/#!/?s=123456789"
)
mock_urlopen.assert_called_once_with(
"http://example.com/resolved_trace.fxt",
context=unittest.mock.ANY,
timeout=120,
)
if __name__ == "__main__":
unittest.main()