diff --git a/NEWS b/NEWS index 8c84111d..b741899c 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ unreleased ========== +Add support for fine-grained error location lines in Python tracebacks 0.39 ========== diff --git a/lib/python_frame.c b/lib/python_frame.c index bbb91746..a454127e 100644 --- a/lib/python_frame.c +++ b/lib/python_frame.c @@ -207,6 +207,39 @@ sr_python_frame_parse(const char **input, { const char *local_input = *input; + /* + * Skip fine-grained error location lines (See: PEP 657), + * and syntax error lines. Such lines only contain '^' and '~' + * characters. + * + * Example: + * + * Traceback (most recent call last): + * File "test.py", line 2, in + * x['a']['b']['c']['d'] = 1 + * ~~~~~~~~~~~^^^^^ + * TypeError: 'NoneType' object is not subscriptable + */ + bool is_error_location_line = true; + const char *tmp_input = local_input; + while (*tmp_input != '\n' && *tmp_input != '\0') + { + if (*tmp_input != ' ' && *tmp_input != '^' && *tmp_input != '~') + { + is_error_location_line = false; + break; + } + ++tmp_input; + } + + if (is_error_location_line) + { + /* Skip the error location line */ + sr_skip_char_cspan(&local_input, "\n"); + ++local_input; + *input = local_input; + } + if (0 == sr_skip_string(&local_input, " File \"")) { location->message = g_strdup_printf("Frame header not found."); diff --git a/lib/python_stacktrace.c b/lib/python_stacktrace.c index 55c6c1d5..ce913a13 100644 --- a/lib/python_stacktrace.c +++ b/lib/python_stacktrace.c @@ -215,27 +215,6 @@ sr_python_stacktrace_parse(const char **input, return NULL; } - bool invalid_syntax_pointer = true; - const char *tmp_input = local_input; - while (*tmp_input != '\n' && *tmp_input != '\0') - { - if (*tmp_input != ' ' && *tmp_input != '^') - { - invalid_syntax_pointer = false; - break; - } - ++tmp_input; - } - - if (invalid_syntax_pointer) - { - /* Skip line " ^" pointing to the invalid code */ - sr_skip_char_cspan(&local_input, "\n"); - ++local_input; - ++location->line; - location->column = 1; - } - /* Parse exception name. */ if (!sr_parse_char_cspan(&local_input, ":\n", &stacktrace->exception_name)) { diff --git a/tests/python/python.py b/tests/python/python.py index b3cf542a..0f118bb0 100755 --- a/tests/python/python.py +++ b/tests/python/python.py @@ -181,6 +181,22 @@ def test_invalid_syntax_imported_file(self): self.assertTrue(f.special_function) self.assertFalse(f.special_file) + def test_fine_grained_error_location(self): + trace = load_input_contents('../python_stacktraces/python-06') + trace = satyr.PythonStacktrace(trace) + + self.assertEqual(len(trace.frames), 1) + self.assertEqual(trace.exception_name, 'ZeroDivisionError') + + f = trace.frames[0] + self.assertEqual(f.file_name, '/usr/bin/will_python3_raise') + self.assertEqual(f.function_name, "module") + self.assertEqual(f.file_line, 3) + self.assertEqual(f.line_contents, '0/0') + self.assertTrue(f.special_function) + self.assertFalse(f.special_file) + + class TestPythonFrame(BindingsTestCase): def setUp(self): self.frame = satyr.PythonStacktrace(contents).frames[-1] diff --git a/tests/python_stacktraces/python-06 b/tests/python_stacktraces/python-06 new file mode 100644 index 00000000..ff6b7f04 --- /dev/null +++ b/tests/python_stacktraces/python-06 @@ -0,0 +1,18 @@ +will_python3_raise:3::ZeroDivisionError: division by zero + +Traceback (most recent call last): + File "/usr/bin/will_python3_raise", line 3, in + 0/0 + ~^~ +ZeroDivisionError: division by zero + +Local variables in innermost frame: +__name__: '__main__' +__doc__: None +__package__: None +__loader__: <_frozen_importlib_external.SourceFileLoader object at 0x7fd295c62c50> +__spec__: None +__annotations__: {} +__builtins__: +__file__: '/usr/bin/will_python3_raise' +__cached__: None