Skip to content

Commit 718d0d1

Browse files
committed
test: Tests for failing commands in live mode
1 parent f975d58 commit 718d0d1

File tree

1 file changed

+74
-0
lines changed
  • Lib/test/test_profiling/test_sampling_profiler

1 file changed

+74
-0
lines changed

Lib/test/test_profiling/test_sampling_profiler/test_cli.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"""Tests for sampling profiler CLI argument parsing and functionality."""
22

3+
import functools
34
import io
45
import subprocess
56
import sys
7+
import tempfile
68
import unittest
79
from unittest import mock
810

@@ -17,6 +19,7 @@
1719

1820
from profiling.sampling.cli import main
1921
from profiling.sampling.errors import SamplingScriptNotFoundError, SamplingModuleNotFoundError, SamplingUnknownProcessError
22+
from profiling.sampling.live_collector import MockDisplay, LiveStatsCollector
2023

2124

2225
class TestSampleProfilerCLI(unittest.TestCase):
@@ -722,3 +725,74 @@ def test_cli_attach_nonexistent_pid(self):
722725
main()
723726

724727
self.assertIn(fake_pid, str(cm.exception))
728+
729+
730+
class TestLiveModeErrors(unittest.TestCase):
731+
"""Tests running error commands in the live mode fails gracefully."""
732+
733+
def mock_curses_wrapper(self, func):
734+
func(mock.MagicMock())
735+
736+
def mock_init_curses_side_effect(self, n_times, mock_self, stdscr):
737+
mock_self.display = MockDisplay()
738+
# Allow the loop to run for a bit (approx 0.5s) before quitting
739+
# This ensures we don't exit too early while the subprocess is
740+
# still failing
741+
for _ in range(n_times):
742+
mock_self.display.simulate_input(-1)
743+
if n_times >= 500:
744+
mock_self.display.simulate_input(ord('q'))
745+
746+
@unittest.skipIf(is_emscripten, "subprocess not available")
747+
def test_run_failed_module_live(self):
748+
"""Test that running a existing module that fails exists with clean error."""
749+
750+
args = [
751+
"profiling.sampling.cli", "run", "--live", "-m", "test",
752+
"test_asdasd"
753+
]
754+
755+
with (
756+
mock.patch(
757+
'profiling.sampling.live_collector.collector.LiveStatsCollector.init_curses',
758+
autospec=True,
759+
side_effect=functools.partial(self.mock_init_curses_side_effect, 750)
760+
),
761+
mock.patch('curses.wrapper', side_effect=self.mock_curses_wrapper),
762+
mock.patch("sys.argv", args),
763+
mock.patch('sys.stderr', new=io.StringIO()) as fake_stderr
764+
):
765+
main()
766+
self.assertStartsWith(
767+
fake_stderr.getvalue(),
768+
'\x1b[31mtest test_asdasd crashed -- Traceback (most recent call last):'
769+
)
770+
771+
@unittest.skipIf(is_emscripten, "subprocess not available")
772+
def test_run_failed_script_live(self):
773+
"""Test that running a failing script exits with clean error."""
774+
script = tempfile.NamedTemporaryFile(suffix=".py")
775+
script.write(b'1/0\n')
776+
script.seek(0)
777+
778+
args = ["profiling.sampling.cli", "run", "--live", script.name]
779+
780+
with (
781+
mock.patch(
782+
'profiling.sampling.live_collector.collector.LiveStatsCollector.init_curses',
783+
autospec=True,
784+
side_effect=functools.partial(self.mock_init_curses_side_effect, 200)
785+
),
786+
mock.patch('curses.wrapper', side_effect=self.mock_curses_wrapper),
787+
mock.patch("sys.argv", args),
788+
mock.patch('sys.stderr', new=io.StringIO()) as fake_stderr
789+
):
790+
main()
791+
stderr = fake_stderr.getvalue()
792+
self.assertIn(
793+
'sample(s) collected (minimum 200 required for TUI)', stderr
794+
)
795+
self.assertEndsWith(
796+
stderr,
797+
'ZeroDivisionError\x1b[0m: \x1b[35mdivision by zero\x1b[0m\n\n'
798+
)

0 commit comments

Comments
 (0)