Skip to content

Commit cba5baf

Browse files
committed
Renaming Filter Fixtures
1 parent 75c3c68 commit cba5baf

12 files changed

+69
-171
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Each replay test can be authored into its own file, like `my_replay_test.py`. We
5454

5555
Replay testing has three distinct phases, **all of which are required to run a replay test**:
5656

57-
### Fixtures `@fixtures`
57+
### Filter Fixtures `@fixtures`
5858

5959
For collecting and preparing your fixtures to be run against your launch specification. Duties include:
6060
- Provides a mechanism for specifying your input fixtures (e.g. `lidar_data.mcap`)
@@ -67,9 +67,9 @@ Here is how you use it:
6767

6868
```python
6969
@fixtures.parameterize([McapFixture(path="/tmp/mcap/my_data.mcap")])
70-
class Fixtures:
71-
input_topics = ["/vehicle/cmd_vel"]
72-
output_topics = ["/user/cmd_vel"]
70+
class FilterFixtures:
71+
required_input_topics = ["/vehicle/cmd_vel"]
72+
expected_output_topics = ["/user/cmd_vel"]
7373
```
7474

7575
### Run `@run`

replay_testing/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def run(parser, args):
9494

9595
runner = ReplayTestingRunner(test_module)
9696

97-
runner.fixtures()
97+
runner.filter_fixtures()
9898
runner.run()
9999
exit_code, junit_xml_path = runner.analyze()
100100

replay_testing/decorators/fixtures.py

+18-18
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#
1515

1616
from ..models import ReplayTestingPhase, McapFixture
17+
from ..logging_config import get_logger
18+
19+
_logger_ = get_logger()
1720

1821

1922
class fixtures:
@@ -23,28 +26,25 @@ def __init__(self, *args, **kwargs):
2326
# If args/kwargs are provided, treat them as parameters
2427
self.fixture_list = kwargs.get("fixture_list", None)
2528

26-
def __call__(self, cls):
27-
if not hasattr(cls, "input_topics"):
28-
raise TypeError(f"Class {cls.__name__} must define a 'input_topics' attribute.")
29-
30-
if not isinstance(cls.input_topics, list):
31-
raise TypeError(f"Class {cls.__name} 'input_topics' attribute must be a list.")
32-
33-
if not all(isinstance(topic, str) for topic in cls.input_topics):
34-
raise TypeError(
35-
f"Class {cls.__name} 'input_topics' attribute must be a list of strings."
29+
def validate_class_variable(self, cls, prop: str, deprecated_variable: str):
30+
if hasattr(cls, deprecated_variable):
31+
_logger_.warning(
32+
f"Class {cls.__name__} '{prop}' attribute is deprecated. See docs for updated usage."
3633
)
34+
return
3735

38-
if not hasattr(cls, "output_topics"):
39-
raise TypeError(f"Class {cls.__name__} must define a 'output_topics' attribute.")
36+
if not hasattr(cls, prop):
37+
raise TypeError(f"Class {cls.__name__} must define a '{prop}' attribute.")
4038

41-
if not isinstance(cls.output_topics, list):
42-
raise TypeError(f"Class {cls.__name} 'output_topics' attribute must be a list.")
39+
if not isinstance(getattr(cls, prop), list):
40+
raise TypeError(f"Class {cls.__name} '{prop}' attribute must be a list.")
4341

44-
if not all(isinstance(topic, str) for topic in cls.output_topics):
45-
raise TypeError(
46-
f"Class {cls.__name} 'input_topics' attribute must be a list of strings."
47-
)
42+
if not all(isinstance(topic, str) for topic in getattr(cls, prop)):
43+
raise TypeError(f"Class {cls.__name} '{prop}' attribute must be a list of strings.")
44+
45+
def __call__(self, cls):
46+
self.validate_class_variable(cls, "required_input_topics", "input_topics")
47+
self.validate_class_variable(cls, "expected_output_topics", "output_topics")
4848

4949
cls.fixture_list = self.fixture_list
5050
cls.__annotations__["replay_testing_phase"] = ReplayTestingPhase.FIXTURES

replay_testing/replay_runner.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def _create_run_launch_description(
128128
]
129129
)
130130

131-
def fixtures(self) -> str:
131+
def filter_fixtures(self) -> str:
132132
self._log_stage_start(ReplayTestingPhase.FIXTURES)
133133

134134
# todo: Add exception handling
@@ -153,13 +153,24 @@ def fixtures(self) -> str:
153153

154154
topic_types = reader.get_all_topics_and_types()
155155

156+
required_input_topics = (
157+
fixture.required_input_topics
158+
if hasattr(fixture, "required_input_topics")
159+
else fixture.input_topics
160+
)
161+
expected_output_topics = (
162+
fixture.expected_output_topics
163+
if hasattr(fixture, "expected_output_topics")
164+
else fixture.output_topics
165+
)
166+
156167
input_topics_present = []
157168
for topic_type in topic_types:
158-
if topic_type.name in fixture.input_topics:
169+
if topic_type.name in required_input_topics:
159170
input_topics_present.append(topic_type.name)
160171

161-
if set(input_topics_present) != set(fixture.input_topics):
162-
diff = difflib.ndiff(input_topics_present, fixture.input_topics)
172+
if set(input_topics_present) != set(required_input_topics):
173+
diff = difflib.ndiff(input_topics_present, required_input_topics)
163174
diff_str = "\n".join(diff)
164175
_logger_.error(f"Input topics do not match. Diff: {diff_str}")
165176
raise AssertionError("Input topics do not match. Check logs for more information")
@@ -174,7 +185,7 @@ def fixtures(self) -> str:
174185
filter_mcap(
175186
replay_fixture.input_fixture.path,
176187
replay_fixture.filtered_fixture.path,
177-
fixture.output_topics,
188+
expected_output_topics,
178189
)
179190

180191
self._replay_fixtures.append(replay_fixture)

test/replay_tests/basic_replay.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232

3333
@fixtures.parameterize([McapFixture(path=cmd_vel_only_fixture)])
3434
class Fixtures:
35-
input_topics = ["/vehicle/cmd_vel"]
36-
output_topics = ["/user/cmd_vel"]
35+
required_input_topics = ["/vehicle/cmd_vel"]
36+
expected_output_topics = ["/user/cmd_vel"]
3737

3838

3939
@run.default()

test/replay_tests/basic_replay_failure.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131

3232

3333
@fixtures.parameterize([McapFixture(path=cmd_vel_only_fixture)])
34-
class Fixtures:
35-
input_topics = ["/vehicle/cmd_vel"]
36-
output_topics = ["/user/cmd_vel"]
34+
class FilterFixtures:
35+
required_input_topics = ["/vehicle/cmd_vel"]
36+
expected_output_topics = ["/user/cmd_vel"]
3737

3838

3939
@run.default()

test/replay_tests/basic_replay_multiple_fixtures_and_params.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
]
4242
)
4343
class Fixtures:
44-
input_topics = ["/vehicle/cmd_vel"]
45-
output_topics = ["/user/cmd_vel"]
44+
required_input_topics = ["/vehicle/cmd_vel"]
45+
expected_output_topics = ["/user/cmd_vel"]
4646

4747

4848
@run.parameterize(

test/replay_tests/nexus_replay.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222

2323
@fixtures.parameterize([NexusFixture(path="generic/cmd_vel_only.mcap")])
2424
class Fixtures:
25-
input_topics = ["/vehicle/cmd_vel"]
26-
output_topics = ["/user/cmd_vel"]
25+
required_input_topics = ["/vehicle/cmd_vel"]
26+
expected_output_topics = ["/user/cmd_vel"]
2727

2828

2929
@run.default()

test/test_cli.py

-35
Original file line numberDiff line numberDiff line change
@@ -64,38 +64,3 @@ def test_cli_xml_output_success(capsys):
6464
assert "test_cmd_vel" in tescase.get("name")
6565
assert tescase.get("classname") == "AnalyzeBasicReplay"
6666
assert tescase.find("failure") is None
67-
68-
69-
@pytest.skip("File created causes colcon test-result to fail")
70-
def test_cli_xml_output_failure(capsys):
71-
xml_path = os.path.join("/tmp", "test.xml")
72-
replay_file_path = os.path.join(
73-
replay_testing_dir, "test", "replay_tests", "basic_replay_failure.py"
74-
)
75-
76-
# Mock sys.argv for the CLI arguments
77-
sys.argv = ["replay_test", replay_file_path, "--junit-xml", xml_path, "--verbose"]
78-
79-
with pytest.raises(SystemExit) as pytest_wrapped_e:
80-
main()
81-
82-
# Check that exit code is 0 (indicating success)
83-
assert pytest_wrapped_e.type is SystemExit
84-
assert pytest_wrapped_e.value.code == 1
85-
86-
junit_xml = ET.parse(xml_path)
87-
# Remove file so it doesn't conflict with colcon test-result since this is an expected failure
88-
os.remove(xml_path)
89-
90-
root = junit_xml.getroot()
91-
92-
# Assert top level name
93-
assert root.tag == "testsuites"
94-
95-
# Assert on test suites within <testsuites> in a loop
96-
for testsuite in root.iter("testsuite"):
97-
assert testsuite.get("tests") == "1"
98-
for tescase in testsuite.iter("testcase"):
99-
assert "test_expected_failure" in tescase.get("name")
100-
assert tescase.get("classname") == "AnalyzeBasicReplay"
101-
assert tescase.find("failure") is not None

test/test_copyright.py

-40
This file was deleted.

test/test_pep257.py

-38
This file was deleted.

0 commit comments

Comments
 (0)