Skip to content

Commit 7da9327

Browse files
committed
add loop arg to player script
1 parent b6280b1 commit 7da9327

2 files changed

Lines changed: 97 additions & 35 deletions

File tree

can/player.py

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

88
import argparse
99
import errno
10+
import math
1011
import sys
1112
from datetime import datetime
12-
from typing import TYPE_CHECKING, cast
13+
from typing import TYPE_CHECKING, Union, cast
1314

1415
from can import LogReader, MessageSync
1516
from can.cli import (
@@ -26,19 +27,22 @@
2627
from can import Message
2728

2829

30+
def parse_loop(value: str) -> Union[int, float]:
31+
"""Parse the loop argument, allowing integer or 'i' for infinite."""
32+
if value == "i":
33+
return float("inf")
34+
try:
35+
return int(value)
36+
except ValueError as exc:
37+
err_msg = "Loop count must be an integer or 'i' for infinite."
38+
raise argparse.ArgumentTypeError(err_msg) from exc
39+
40+
2941
def main() -> None:
3042
parser = argparse.ArgumentParser(description="Replay CAN traffic.")
3143

3244
player_group = parser.add_argument_group("Player arguments")
3345

34-
player_group.add_argument(
35-
"-f",
36-
"--file_name",
37-
dest="log_file",
38-
help="Path and base log filename, for supported types see can.LogReader.",
39-
default=None,
40-
)
41-
4246
player_group.add_argument(
4347
"-v",
4448
action="count",
@@ -75,7 +79,14 @@ def main() -> None:
7579
default=60 * 60 * 24,
7680
help="<s> skip gaps greater than 's' seconds",
7781
)
78-
82+
player_group.add_argument(
83+
"-l",
84+
"--loop",
85+
type=parse_loop,
86+
metavar="NUM",
87+
default=1,
88+
help="Replay file NUM times. Use 'i' for infinite loop (default: 1)",
89+
)
7990
player_group.add_argument(
8091
"infile",
8192
metavar="input-file",
@@ -103,25 +114,34 @@ def main() -> None:
103114
error_frames = results.error_frames
104115

105116
with create_bus_from_namespace(results) as bus:
106-
with LogReader(results.infile, **additional_config) as reader:
107-
in_sync = MessageSync(
108-
cast("Iterable[Message]", reader),
109-
timestamps=results.timestamps,
110-
gap=results.gap,
111-
skip=results.skip,
112-
)
113-
114-
print(f"Can LogReader (Started on {datetime.now()})")
115-
116-
try:
117-
for message in in_sync:
118-
if message.is_error_frame and not error_frames:
119-
continue
120-
if verbosity >= 3:
121-
print(message)
122-
bus.send(message)
123-
except KeyboardInterrupt:
124-
pass
117+
loop_count: Union[int, float] = results.loop
118+
iteration = 0
119+
try:
120+
while iteration < loop_count:
121+
with LogReader(results.infile, **additional_config) as reader:
122+
in_sync = MessageSync(
123+
cast("Iterable[Message]", reader),
124+
timestamps=results.timestamps,
125+
gap=results.gap,
126+
skip=results.skip,
127+
)
128+
129+
progress = (
130+
" [infinite loop]"
131+
if math.isinf(loop_count)
132+
else f" [loop {iteration + 1}/{int(loop_count)}]"
133+
)
134+
print(f"Can LogReader (Started on {datetime.now()}){progress}")
135+
136+
for message in in_sync:
137+
if message.is_error_frame and not error_frames:
138+
continue
139+
if verbosity >= 3:
140+
print(message)
141+
bus.send(message)
142+
iteration += 1
143+
except KeyboardInterrupt:
144+
pass
125145

126146

127147
if __name__ == "__main__":

test/test_player.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from unittest import mock
1212
from unittest.mock import Mock
1313

14+
from parameterized import parameterized
15+
1416
import can
1517
import can.player
1618

@@ -38,7 +40,7 @@ def assertSuccessfulCleanup(self):
3840
self.mock_virtual_bus.__exit__.assert_called_once()
3941

4042
def test_play_virtual(self):
41-
sys.argv = self.baseargs + [self.logfile]
43+
sys.argv = [*self.baseargs, self.logfile]
4244
can.player.main()
4345
msg1 = can.Message(
4446
timestamp=2.501,
@@ -65,8 +67,8 @@ def test_play_virtual(self):
6567
self.assertSuccessfulCleanup()
6668

6769
def test_play_virtual_verbose(self):
68-
sys.argv = self.baseargs + ["-v", self.logfile]
69-
with unittest.mock.patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
70+
sys.argv = [*self.baseargs, "-v", self.logfile]
71+
with mock.patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
7072
can.player.main()
7173
self.assertIn("09 08 07 06 05 04 03 02", mock_stdout.getvalue())
7274
self.assertIn("05 0c 00 00 00 00 00 00", mock_stdout.getvalue())
@@ -76,7 +78,7 @@ def test_play_virtual_verbose(self):
7678
def test_play_virtual_exit(self):
7779
self.MockSleep.side_effect = [None, KeyboardInterrupt]
7880

79-
sys.argv = self.baseargs + [self.logfile]
81+
sys.argv = [*self.baseargs, self.logfile]
8082
can.player.main()
8183
assert self.mock_virtual_bus.send.call_count <= 2
8284
self.assertSuccessfulCleanup()
@@ -85,7 +87,7 @@ def test_play_skip_error_frame(self):
8587
logfile = os.path.join(
8688
os.path.dirname(__file__), "data", "logfile_errorframes.asc"
8789
)
88-
sys.argv = self.baseargs + ["-v", logfile]
90+
sys.argv = [*self.baseargs, "-v", logfile]
8991
can.player.main()
9092
self.assertEqual(self.mock_virtual_bus.send.call_count, 9)
9193
self.assertSuccessfulCleanup()
@@ -94,11 +96,51 @@ def test_play_error_frame(self):
9496
logfile = os.path.join(
9597
os.path.dirname(__file__), "data", "logfile_errorframes.asc"
9698
)
97-
sys.argv = self.baseargs + ["-v", "--error-frames", logfile]
99+
sys.argv = [*self.baseargs, "-v", "--error-frames", logfile]
98100
can.player.main()
99101
self.assertEqual(self.mock_virtual_bus.send.call_count, 12)
100102
self.assertSuccessfulCleanup()
101103

104+
@parameterized.expand([0, 1, 2, 3])
105+
def test_play_loop(self, loop_val):
106+
sys.argv = [*self.baseargs, "--loop", str(loop_val), self.logfile]
107+
can.player.main()
108+
msg1 = can.Message(
109+
timestamp=2.501,
110+
arbitration_id=0xC8,
111+
is_extended_id=False,
112+
is_fd=False,
113+
is_rx=False,
114+
channel=1,
115+
dlc=8,
116+
data=[0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2],
117+
)
118+
msg2 = can.Message(
119+
timestamp=17.876708,
120+
arbitration_id=0x6F9,
121+
is_extended_id=False,
122+
is_fd=False,
123+
is_rx=True,
124+
channel=0,
125+
dlc=8,
126+
data=[0x5, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
127+
)
128+
for i in range(loop_val):
129+
self.assertTrue(
130+
msg1.equals(self.mock_virtual_bus.send.mock_calls[2 * i + 0].args[0])
131+
)
132+
self.assertTrue(
133+
msg2.equals(self.mock_virtual_bus.send.mock_calls[2 * i + 1].args[0])
134+
)
135+
self.assertSuccessfulCleanup()
136+
137+
def test_play_loop_infinite(self):
138+
self.mock_virtual_bus.send.side_effect = [None] * 99 + [KeyboardInterrupt]
139+
sys.argv = [*self.baseargs, "-l", "i", self.logfile]
140+
can.player.main()
141+
self.assertEqual(self.mock_virtual_bus.send.call_count, 100)
142+
self.assertSuccessfulCleanup()
143+
102144

103145
class TestPlayerCompressedFile(TestPlayerScriptModule):
104146
"""

0 commit comments

Comments
 (0)