"""Offline trial synchronization, routing, traffic bounds and cleanup tests.""" import importlib.util import io from pathlib import Path import signal import struct import subprocess import tempfile import unittest from unittest.mock import Mock, patch spec = importlib.util.spec_from_file_location("run_trial_linux", Path(__file__).with_name("run_trial_linux.py")) trial = importlib.util.module_from_spec(spec) spec.loader.exec_module(trial) SOURCE, CLIENT, MAC = "192.0.2.10", "192.0.2.11", "02:00:00:00:00:11" HEADER = struct.pack("= 1 / rate - 1e-9 for a, b in zip(sends, sends[1:]))) def test_scheduler_pause_skips_slots_without_catchup_bursts(self): evidence, sends, sleeps = self.pacing_run(1000, oversleep=.003) self.assertLess(len(sends), 300) self.assertGreater(evidence["missed_schedules"], 500) self.assertGreaterEqual(evidence["maximum_lateness_seconds"], .003 - 1e-9) self.assertTrue(all(b - a >= .001 - 1e-9 for a, b in zip(sends, sends[1:]))) self.assertLessEqual(evidence["sent_payload_bytes"], 1000 * 1200) self.assertLessEqual(len(sleeps), 1001) def test_payload_count_interface_and_partial_receipt(self): with tempfile.TemporaryDirectory() as directory: output = Path(directory) marker = output / "capture.active" marker.touch() clock = [0.0] def sleep(duration): clock[0] += duration udp = Mock() udp.sendmsg.side_effect = [1200, RuntimeError("synthetic send failure")] context = Mock() context.__enter__ = Mock(return_value=udp) context.__exit__ = Mock(return_value=False) proc = Mock() proc.poll.return_value = None evidence = {} with patch.object(trial.socket, "socket", return_value=context), \ patch.object(trial.socket, "if_nametoindex", return_value=7, create=True), \ patch.object(trial.time, "monotonic", side_effect=lambda: clock[0]), \ patch.object(trial.time, "sleep", side_effect=sleep): with self.assertRaisesRegex(RuntimeError, "synthetic send failure"): trial.send_traffic(proc, SOURCE, CLIENT, evidence, Mock(), "eth0", output) args = udp.sendmsg.call_args_list[0].args self.assertEqual(args[0], [bytes(1200)]) self.assertEqual(struct.unpack("=I4s4s", args[1][0][2])[0], 7) self.assertEqual(args[3], (CLIENT, 9)) self.assertEqual(evidence["sent_packets"], 1) self.assertIn("traffic_ended_unix_ns", evidence) self.assertIn("actual_packets_per_second", evidence) def test_no_traffic_after_capture_marker_removed(self): with tempfile.TemporaryDirectory() as directory: evidence = {} proc = Mock() proc.poll.return_value = None with patch.object(trial.socket, "socket") as socket_mock, \ patch.object(trial.socket, "if_nametoindex", return_value=7, create=True): with self.assertRaisesRegex(RuntimeError, "Capture ended"): trial.send_traffic(proc, SOURCE, CLIENT, evidence, Mock(), "eth0", Path(directory)) socket_mock.return_value.__enter__.return_value.sendmsg.assert_not_called() self.assertEqual(evidence["sent_packets"], 0) class ProfileTests(unittest.TestCase): def test_defaults_and_controlled_high_rate_profile(self): self.assertEqual(trial.validate_profile(250, 30, 45), 7500) self.assertEqual(trial.validate_profile(1000, 30, 45), 30000) self.assertEqual(trial.validate_profile(1000, 55, 60), 55000) def test_all_resource_and_duration_limits(self): for profile in ((0, 30, 45), (1001, 30, 45), (250, 30, 121), (250, 0, 45), (250, 41, 45), (1000, 61, 120), (1000, 56, 120), (1.5, 1, 6)): with self.subTest(profile=profile), self.assertRaises(ValueError): trial.validate_profile(*profile) def test_invalid_send_profile_cannot_open_socket(self): with patch.object(trial.socket, "socket") as opened: with self.assertRaises(ValueError): trial.send_traffic(Mock(), SOURCE, CLIENT, {}, Mock(), "eth0", Path("unused"), pps=1001) opened.assert_not_called() if __name__ == "__main__": unittest.main()