Skip to content

Conversation

@mgmacias95
Copy link
Contributor

@mgmacias95 mgmacias95 commented Dec 23, 2025

This script shows the error trace when a profiled script (or python module) in the live mode fails.

Some output examples:

When running a script that fails inmediately, the live mode won't even start and you'll see the traceback directly instead:

mgmacias@mgmacias:~/cpython$ cat failing_script.py 
1/0
mgmacias@mgmacias:~/cpython$ ./python -m profiling.sampling run --live -d999999 failing_script.py 
No samples collected - process 1464940 exited before profiling could begin.
Traceback (most recent call last):
  File "<frozen runpy>", line 196, in _run_module_as_main
  File "<frozen runpy>", line 87, in _run_code
  File "/home/mgmacias/cpython/Lib/profiling/sampling/_sync_coordinator.py", line 256, in <module>
    main()
    ~~~~^^
  File "/home/mgmacias/cpython/Lib/profiling/sampling/_sync_coordinator.py", line 249, in main
    _execute_script(script_path, script_args, cwd)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mgmacias/cpython/Lib/profiling/sampling/_sync_coordinator.py", line 196, in _execute_script
    exec(code, main_module.__dict__)
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mgmacias/cpython/failing_script.py", line 1, in <module>
    1/0
    ~^~
ZeroDivisionError: division by zero

When running a test that doesn't exist, you will see a brief profiled information and when exiting the live mode you'll see this traceback:

mgmacias@mgmacias:~/cpython$ ./python -m profiling.sampling run --live -d999999 -m test test_dfgdfg
test test_dfgdfg crashed -- Traceback (most recent call last):
  File "/home/mgmacias/cpython/Lib/test/libregrtest/single.py", line 210, in _runtest_env_changed_exc
    _load_run_test(result, runtests)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/home/mgmacias/cpython/Lib/test/libregrtest/single.py", line 155, in _load_run_test
    test_mod = importlib.import_module(module_name)
  File "/home/mgmacias/cpython/Lib/importlib/__init__.py", line 88, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1303, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1276, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1240, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'test.test_dfgdfg'

Copy link
Member

@pablogsal pablogsal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to rebase/merge main to fix conflicts

Copy link
Member

@pablogsal pablogsal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we have some problems with macOS and unclosed files (I think your subprocess call needs to be a context manager).

@pablogsal
Copy link
Member

This may be enough (didn;t check):

diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py
index 4d2d5b37453..bb7e629a993 100644
--- a/Lib/profiling/sampling/cli.py
+++ b/Lib/profiling/sampling/cli.py
@@ -1061,13 +1061,14 @@ def _handle_live_run(args):
         process.wait()
         # Read any stderr output (tracebacks, errors, etc.)
         if process.stderr:
-            try:
-                stderr = process.stderr.read()
-                if stderr:
-                    print(stderr.decode(), file=sys.stderr)
-            except (OSError, ValueError):
-                # Ignore errors if pipe is already closed
-                pass
+            with process.stderr:
+                try:
+                    stderr = process.stderr.read()
+                    if stderr:
+                        print(stderr.decode(), file=sys.stderr)
+                except (OSError, ValueError):
+                    # Ignore errors if pipe is already closed
+                    pass
 
 
 def _handle_replay(args):
diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py
index b492b471f82..dec850a83e9 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py
@@ -839,7 +839,7 @@ def mock_init_curses_side_effect(self, n_times, mock_self, stdscr):
 
     @unittest.skipIf(is_emscripten, "subprocess not available")
     def test_run_failed_module_live(self):
-        """Test that running a existing module that fails exists with clean error."""
+        """Test that running a existing module that fails exits with clean error."""
 
         args = [
             "profiling.sampling.cli", "run", "--live", "-m", "test",
@@ -857,18 +857,19 @@ def test_run_failed_module_live(self):
             mock.patch('sys.stderr', new=io.StringIO()) as fake_stderr
         ):
             main()
-            self.assertStartsWith(
-                fake_stderr.getvalue(),
-                '\x1b[31mtest test_asdasd crashed -- Traceback (most recent call last):'
-            )
+            stderr = fake_stderr.getvalue()
+            # Check that error output contains the crash message and traceback
+            # (without checking exact ANSI codes which vary by environment)
+            self.assertIn('test_asdasd', stderr)
+            self.assertIn('Traceback (most recent call last):', stderr)
 
     @unittest.skipIf(is_emscripten, "subprocess not available")
     def test_run_failed_script_live(self):
         """Test that running a failing script exits with clean error."""
-        script = tempfile.NamedTemporaryFile(suffix=".py")
+        script = tempfile.NamedTemporaryFile(suffix=".py", delete=False)
         self.addCleanup(close_and_unlink, script)
         script.write(b'1/0\n')
-        script.seek(0)
+        script.flush()
 
         args = ["profiling.sampling.cli", "run", "--live", script.name]
 
@@ -884,13 +885,10 @@ def test_run_failed_script_live(self):
         ):
             main()
             stderr = fake_stderr.getvalue()
-            self.assertIn(
-                'sample(s) collected (minimum 200 required for TUI)', stderr
-            )
-            self.assertEndsWith(
-                stderr,
-                'ZeroDivisionError\x1b[0m: \x1b[35mdivision by zero\x1b[0m\n\n'
-            )
+            # Check that output contains the error information
+            # (without checking exact ANSI codes which vary by environment)
+            self.assertIn('ZeroDivisionError', stderr)
+            self.assertIn('division by zero', stderr)
 
 
 if __name__ == "__main__":

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants