Skip to content

Commit 2fabf38

Browse files
authored
Impl sys.audithook (RustPython#7960)
* Update tests * Add basic audit support * Add audit for `time.sleep` * Add some for `socket` * Some syslog * Some sys related audits * some marshal * monitoring callback * Mark failing tests * clippy * Clippy * clippy * mark failing test * mark more * Update `test_sys_setprofile.py` to 3.14.5 * Mark failing tests
1 parent d7d9365 commit 2fabf38

12 files changed

Lines changed: 1014 additions & 35 deletions

File tree

Lib/test/audit-tests.py

Lines changed: 681 additions & 0 deletions
Large diffs are not rendered by default.

Lib/test/test_audit.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def run_test_in_subprocess(self, *args):
2323
with subprocess.Popen(
2424
[sys.executable, "-X utf8", AUDIT_TESTS_PY, *args],
2525
encoding="utf-8",
26+
errors="backslashreplace",
2627
stdout=subprocess.PIPE,
2728
stderr=subprocess.PIPE,
2829
) as p:
@@ -57,6 +58,7 @@ def test_block_add_hook(self):
5758
def test_block_add_hook_baseexception(self):
5859
self.do_test("test_block_add_hook_baseexception")
5960

61+
@unittest.expectedFailure # TODO: RUSTPYTHON
6062
def test_marshal(self):
6163
import_helper.import_module("marshal")
6264

@@ -67,18 +69,33 @@ def test_pickle(self):
6769

6870
self.do_test("test_pickle")
6971

72+
@unittest.expectedFailure # TODO: RUSTPYTHON
7073
def test_monkeypatch(self):
7174
self.do_test("test_monkeypatch")
7275

76+
@unittest.expectedFailure # TODO: RUSTPYTHON
7377
def test_open(self):
7478
self.do_test("test_open", os_helper.TESTFN)
7579

80+
@unittest.expectedFailure # TODO: RUSTPYTHON
7681
def test_cantrace(self):
7782
self.do_test("test_cantrace")
7883

84+
@unittest.expectedFailure # TODO: RUSTPYTHON
7985
def test_mmap(self):
8086
self.do_test("test_mmap")
8187

88+
@unittest.expectedFailure # TODO: RUSTPYTHON
89+
def test_ctypes_call_function(self):
90+
import_helper.import_module("ctypes")
91+
self.do_test("test_ctypes_call_function")
92+
93+
@unittest.expectedFailure # TODO: RUSTPYTHON
94+
def test_posixsubprocess(self):
95+
import_helper.import_module("_posixsubprocess")
96+
self.do_test("test_posixsubprocess")
97+
98+
@unittest.expectedFailure # TODO: RUSTPYTHON
8299
def test_excepthook(self):
83100
returncode, events, stderr = self.run_python("test_excepthook")
84101
if not returncode:
@@ -100,6 +117,7 @@ def test_unraisablehook(self):
100117
"RuntimeError('nonfatal-error') Exception ignored for audit hook test",
101118
)
102119

120+
@unittest.expectedFailure # TODO: RUSTPYTHON
103121
def test_winreg(self):
104122
import_helper.import_module("winreg")
105123
returncode, events, stderr = self.run_python("test_winreg")
@@ -125,8 +143,9 @@ def test_socket(self):
125143
self.assertEqual(events[0][0], "socket.gethostname")
126144
self.assertEqual(events[1][0], "socket.__new__")
127145
self.assertEqual(events[2][0], "socket.bind")
128-
self.assertTrue(events[2][2].endswith("('127.0.0.1', 8080)"))
146+
self.assertEndsWith(events[2][2], "('127.0.0.1', 8080)")
129147

148+
@unittest.expectedFailure # TODO: RUSTPYTHON
130149
def test_gc(self):
131150
returncode, events, stderr = self.run_python("test_gc")
132151
if returncode:
@@ -156,6 +175,7 @@ def test_http(self):
156175
self.assertIn('HTTP', events[1][2])
157176

158177

178+
@unittest.expectedFailure # TODO: RUSTPYTHON
159179
def test_sqlite3(self):
160180
sqlite3 = import_helper.import_module("sqlite3")
161181
returncode, events, stderr = self.run_python("test_sqlite3")
@@ -200,6 +220,7 @@ def test_sys_getframemodulename(self):
200220
self.assertEqual(actual, expected)
201221

202222

223+
@unittest.expectedFailure # TODO: RUSTPYTHON
203224
def test_threading(self):
204225
returncode, events, stderr = self.run_python("test_threading")
205226
if returncode:
@@ -218,6 +239,7 @@ def test_threading(self):
218239
self.assertEqual(actual, expected)
219240

220241

242+
@unittest.expectedFailure # TODO: RUSTPYTHON
221243
def test_wmi_exec_query(self):
222244
import_helper.import_module("_wmi")
223245
returncode, events, stderr = self.run_python("test_wmi_exec_query")
@@ -231,6 +253,7 @@ def test_wmi_exec_query(self):
231253

232254
self.assertEqual(actual, expected)
233255

256+
@unittest.expectedFailure # TODO: RUSTPYTHON
234257
def test_syslog(self):
235258
syslog = import_helper.import_module("syslog")
236259

@@ -292,6 +315,7 @@ def test_sys_monitoring_register_callback(self):
292315

293316
self.assertEqual(actual, expected)
294317

318+
@unittest.expectedFailure # TODO: RUSTPYTHON
295319
def test_winapi_createnamedpipe(self):
296320
winapi = import_helper.import_module("_winapi")
297321

@@ -313,6 +337,14 @@ def test_assert_unicode(self):
313337
if returncode:
314338
self.fail(stderr)
315339

340+
@support.support_remote_exec_only
341+
@support.cpython_only
342+
def test_sys_remote_exec(self):
343+
returncode, events, stderr = self.run_python("test_sys_remote_exec")
344+
self.assertTrue(any(["sys.remote_exec" in event for event in events]))
345+
self.assertTrue(any(["cpython.remote_debugger_script" in event for event in events]))
346+
if returncode:
347+
self.fail(stderr)
316348

317349
if __name__ == "__main__":
318350
unittest.main()

Lib/test/test_bdb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ def test_until_in_caller_frame(self):
728728
with TracerRun(self) as tracer:
729729
tracer.runcall(tfunc_main)
730730

731+
@unittest.skipIf(hasattr(__import__("sys"), "addaudithook"), "TODO: RUSTPYTHON; Currently no conditional tracing toggle")
731732
@patch_list(sys.meta_path)
732733
def test_skip(self):
733734
# Check that tracing is skipped over the import statement in

Lib/test/test_sys_setprofile.py

Lines changed: 137 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ def callback(self, frame, event, arg):
3030
if (event == "call"
3131
or event == "return"
3232
or event == "exception"):
33-
self.add_event(event, frame)
33+
self.add_event(event, frame, arg)
3434

35-
def add_event(self, event, frame=None):
35+
def add_event(self, event, frame=None, arg=None):
3636
"""Add an event to the log."""
3737
if frame is None:
3838
frame = sys._getframe(1)
@@ -43,7 +43,7 @@ def add_event(self, event, frame=None):
4343
frameno = len(self.frames)
4444
self.frames.append(frame)
4545

46-
self.events.append((frameno, event, ident(frame)))
46+
self.events.append((frameno, event, ident(frame), arg))
4747

4848
def get_events(self):
4949
"""Remove calls to add_event()."""
@@ -89,11 +89,16 @@ def trace_pass(self, frame):
8989

9090

9191
class TestCaseBase(unittest.TestCase):
92-
def check_events(self, callable, expected):
92+
def check_events(self, callable, expected, check_args=False):
9393
events = capture_events(callable, self.new_watcher())
94-
if events != expected:
95-
self.fail("Expected events:\n%s\nReceived events:\n%s"
96-
% (pprint.pformat(expected), pprint.pformat(events)))
94+
if check_args:
95+
if events != expected:
96+
self.fail("Expected events:\n%s\nReceived events:\n%s"
97+
% (pprint.pformat(expected), pprint.pformat(events)))
98+
else:
99+
if [(frameno, event, ident) for frameno, event, ident, arg in events] != expected:
100+
self.fail("Expected events:\n%s\nReceived events:\n%s"
101+
% (pprint.pformat(expected), pprint.pformat(events)))
97102

98103

99104
class ProfileHookTestCase(TestCaseBase):
@@ -119,7 +124,7 @@ def f(p):
119124
def test_caught_exception(self):
120125
def f(p):
121126
try: 1/0
122-
except: pass
127+
except ZeroDivisionError: pass
123128
f_ident = ident(f)
124129
self.check_events(f, [(1, 'call', f_ident),
125130
(1, 'return', f_ident),
@@ -128,7 +133,7 @@ def f(p):
128133
def test_caught_nested_exception(self):
129134
def f(p):
130135
try: 1/0
131-
except: pass
136+
except ZeroDivisionError: pass
132137
f_ident = ident(f)
133138
self.check_events(f, [(1, 'call', f_ident),
134139
(1, 'return', f_ident),
@@ -151,9 +156,9 @@ def f(p):
151156
def g(p):
152157
try:
153158
f(p)
154-
except:
159+
except ZeroDivisionError:
155160
try: f(p)
156-
except: pass
161+
except ZeroDivisionError: pass
157162
f_ident = ident(f)
158163
g_ident = ident(g)
159164
self.check_events(g, [(1, 'call', g_ident),
@@ -164,6 +169,7 @@ def g(p):
164169
(1, 'return', g_ident),
165170
])
166171

172+
@unittest.expectedFailure # TODO: RUSTPYTHON
167173
def test_exception_propagation(self):
168174
def f(p):
169175
1/0
@@ -182,7 +188,7 @@ def g(p):
182188
def test_raise_twice(self):
183189
def f(p):
184190
try: 1/0
185-
except: 1/0
191+
except ZeroDivisionError: 1/0
186192
f_ident = ident(f)
187193
self.check_events(f, [(1, 'call', f_ident),
188194
(1, 'return', f_ident),
@@ -191,7 +197,7 @@ def f(p):
191197
def test_raise_reraise(self):
192198
def f(p):
193199
try: 1/0
194-
except: raise
200+
except ZeroDivisionError: raise
195201
f_ident = ident(f)
196202
self.check_events(f, [(1, 'call', f_ident),
197203
(1, 'return', f_ident),
@@ -255,6 +261,24 @@ def g(p):
255261
(1, 'return', g_ident),
256262
])
257263

264+
@unittest.expectedFailure # TODO: RUSTPYTHON
265+
def test_unfinished_generator(self):
266+
def f():
267+
for i in range(2):
268+
yield i
269+
def g(p):
270+
next(f())
271+
272+
f_ident = ident(f)
273+
g_ident = ident(g)
274+
self.check_events(g, [(1, 'call', g_ident, None),
275+
(2, 'call', f_ident, None),
276+
(2, 'return', f_ident, 0),
277+
(2, 'call', f_ident, None),
278+
(2, 'return', f_ident, None),
279+
(1, 'return', g_ident, None),
280+
], check_args=True)
281+
258282
def test_stop_iteration(self):
259283
def f():
260284
for i in range(2):
@@ -300,7 +324,7 @@ def f(p):
300324
def test_caught_exception(self):
301325
def f(p):
302326
try: 1/0
303-
except: pass
327+
except ZeroDivisionError: pass
304328
f_ident = ident(f)
305329
self.check_events(f, [(1, 'call', f_ident),
306330
(1, 'return', f_ident),
@@ -415,5 +439,104 @@ def show_events(callable):
415439
pprint.pprint(capture_events(callable))
416440

417441

442+
class TestEdgeCases(unittest.TestCase):
443+
444+
def setUp(self):
445+
self.addCleanup(sys.setprofile, sys.getprofile())
446+
sys.setprofile(None)
447+
448+
def test_reentrancy(self):
449+
def foo(*args):
450+
...
451+
452+
def bar(*args):
453+
...
454+
455+
class A:
456+
def __call__(self, *args):
457+
pass
458+
459+
def __del__(self):
460+
sys.setprofile(bar)
461+
462+
sys.setprofile(A())
463+
sys.setprofile(foo)
464+
self.assertEqual(sys.getprofile(), bar)
465+
466+
def test_same_object(self):
467+
def foo(*args):
468+
...
469+
470+
sys.setprofile(foo)
471+
del foo
472+
sys.setprofile(sys.getprofile())
473+
474+
def test_profile_after_trace_opcodes(self):
475+
def f():
476+
...
477+
478+
sys._getframe().f_trace_opcodes = True
479+
prev_trace = sys.gettrace()
480+
sys.settrace(lambda *args: None)
481+
f()
482+
sys.settrace(prev_trace)
483+
sys.setprofile(lambda *args: None)
484+
f()
485+
486+
@unittest.expectedFailure # TODO: RUSTPYTHON
487+
def test_method_with_c_function(self):
488+
# gh-122029
489+
# When we have a PyMethodObject whose im_func is a C function, we
490+
# should record both the call and the return. f = classmethod(repr)
491+
# is just a way to create a PyMethodObject with a C function.
492+
class A:
493+
f = classmethod(repr)
494+
events = []
495+
sys.setprofile(lambda frame, event, args: events.append(event))
496+
A().f()
497+
sys.setprofile(None)
498+
# The last c_call is the call to sys.setprofile
499+
self.assertEqual(events, ['c_call', 'c_return', 'c_call'])
500+
501+
class B:
502+
f = classmethod(max)
503+
events = []
504+
sys.setprofile(lambda frame, event, args: events.append(event))
505+
# Not important, we only want to trigger INSTRUMENTED_CALL_KW
506+
B().f(1, key=lambda x: 0)
507+
sys.setprofile(None)
508+
# The last c_call is the call to sys.setprofile
509+
self.assertEqual(
510+
events,
511+
['c_call',
512+
'call', 'return',
513+
'call', 'return',
514+
'c_return',
515+
'c_call'
516+
]
517+
)
518+
519+
# Test CALL_FUNCTION_EX
520+
events = []
521+
sys.setprofile(lambda frame, event, args: events.append(event))
522+
# Not important, we only want to trigger INSTRUMENTED_CALL_KW
523+
args = (1,)
524+
m = B().f
525+
m(*args, key=lambda x: 0)
526+
sys.setprofile(None)
527+
# The last c_call is the call to sys.setprofile
528+
# INSTRUMENTED_CALL_FUNCTION_EX has different behavior than the other
529+
# instrumented call bytecodes, it does not unpack the callable before
530+
# calling it. This is probably not ideal because it's not consistent,
531+
# but at least we get a consistent call stack (no unmatched c_call).
532+
self.assertEqual(
533+
events,
534+
['call', 'return',
535+
'call', 'return',
536+
'c_call'
537+
]
538+
)
539+
540+
418541
if __name__ == "__main__":
419542
unittest.main()

0 commit comments

Comments
 (0)