|
9 | 9 | import unittest |
10 | 10 | from collections import defaultdict |
11 | 11 |
|
| 12 | +from test.support import captured_stderr |
| 13 | + |
12 | 14 | try: |
13 | 15 | import _remote_debugging |
14 | 16 | from _remote_debugging import ( |
@@ -994,6 +996,74 @@ def test_writer_total_samples_after_close_returns_zero(self): |
994 | 996 | w.close() |
995 | 997 | self.assertEqual(w.total_samples, 0) |
996 | 998 |
|
| 999 | + def test_binary_collector_stops_gracefully_on_overflow(self): |
| 1000 | + """OverflowError from the writer stops collection via the running |
| 1001 | + protocol instead of propagating and corrupting the file. |
| 1002 | + See gh-151292.""" |
| 1003 | + with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f: |
| 1004 | + filename = f.name |
| 1005 | + self.temp_files.append(filename) |
| 1006 | + |
| 1007 | + collector = BinaryCollector(filename, 1000, compression="none") |
| 1008 | + self.assertTrue(collector.running) |
| 1009 | + |
| 1010 | + sample = [ |
| 1011 | + make_interpreter(0, [make_thread(1, [make_frame("a.py", 1, "f")])]) |
| 1012 | + ] |
| 1013 | + |
| 1014 | + # Collect real samples first, then hit the limit. |
| 1015 | + for i in range(3): |
| 1016 | + collector.collect(sample, timestamp_us=(i + 1) * 1000) |
| 1017 | + self.assertTrue(collector.running) |
| 1018 | + |
| 1019 | + real_writer = collector._writer |
| 1020 | + |
| 1021 | + class _OverflowingWriter: |
| 1022 | + def write_sample(self, stack_frames, timestamp_us): |
| 1023 | + raise OverflowError("too many samples for binary format") |
| 1024 | + |
| 1025 | + collector._writer = _OverflowingWriter() |
| 1026 | + with captured_stderr() as stderr: |
| 1027 | + collector.collect(sample, timestamp_us=4000) |
| 1028 | + |
| 1029 | + self.assertFalse(collector.running) |
| 1030 | + self.assertIn("too many samples", stderr.getvalue()) |
| 1031 | + |
| 1032 | + # The real writer can still be finalized into a valid file that |
| 1033 | + # keeps the samples collected before the limit was hit. |
| 1034 | + collector._writer = real_writer |
| 1035 | + collector.export(None) |
| 1036 | + reader_collector = RawCollector() |
| 1037 | + with BinaryReader(filename) as reader: |
| 1038 | + self.assertEqual(reader.replay_samples(reader_collector), 3) |
| 1039 | + |
| 1040 | + def test_interpreter_id_overflow_rejected(self): |
| 1041 | + """An interpreter_id wider than u32 raises OverflowError before any |
| 1042 | + writer state is mutated: subsequent valid samples are still accepted |
| 1043 | + and finalize produces a readable file.""" |
| 1044 | + with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f: |
| 1045 | + filename = f.name |
| 1046 | + self.temp_files.append(filename) |
| 1047 | + |
| 1048 | + good = [ |
| 1049 | + make_interpreter(0, [make_thread(1, [make_frame("a.py", 1, "f")])]) |
| 1050 | + ] |
| 1051 | + bad = [ |
| 1052 | + make_interpreter(2**32, [make_thread(1, [make_frame("a.py", 1, "f")])]) |
| 1053 | + ] |
| 1054 | + |
| 1055 | + writer = _remote_debugging.BinaryWriter(filename, 1000, 0, compression=0) |
| 1056 | + writer.write_sample(good, 1000) |
| 1057 | + with self.assertRaises(OverflowError): |
| 1058 | + writer.write_sample(bad, 2000) |
| 1059 | + writer.write_sample(good, 3000) |
| 1060 | + writer.finalize() |
| 1061 | + self.assertEqual(writer.total_samples, 2) |
| 1062 | + |
| 1063 | + reader_collector = RawCollector() |
| 1064 | + with BinaryReader(filename) as reader: |
| 1065 | + self.assertEqual(reader.replay_samples(reader_collector), 2) |
| 1066 | + |
997 | 1067 |
|
998 | 1068 | class TestBinaryFormatValidation(BinaryFormatTestBase): |
999 | 1069 | """Tests for malformed binary files.""" |
|
0 commit comments