From 5ba787024f53a00d76c7b253d1f3fee87763c679 Mon Sep 17 00:00:00 2001 From: Uku Loskit Date: Wed, 10 Jul 2024 00:56:39 +0300 Subject: [PATCH] Convert tests to pytests (#1060) * Convert tests to pytests * cleanup * args * clean * linter --------- Co-authored-by: Maxime Desroches --- .github/workflows/tests.yml | 2 +- can/tests/test_checksums.py | 13 +--- can/tests/test_dbc_exceptions.py | 20 +++--- can/tests/test_dbc_parser.py | 15 ++--- can/tests/test_define.py | 37 +++++------ can/tests/test_packer_parser.py | 94 +++++++++++++--------------- can/tests/test_parser_performance.py | 15 ++--- requirements.txt | 2 + 8 files changed, 79 insertions(+), 119 deletions(-) mode change 100755 => 100644 can/tests/test_checksums.py mode change 100755 => 100644 can/tests/test_dbc_exceptions.py mode change 100755 => 100644 can/tests/test_dbc_parser.py mode change 100755 => 100644 can/tests/test_define.py mode change 100755 => 100644 can/tests/test_packer_parser.py mode change 100755 => 100644 can/tests/test_parser_performance.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aaebd78243..7db8acdb30 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: - name: Build opendbc run: ${{ env.RUN }} "cd ../ && scons -j$(nproc) --minimal" - name: Unit tests - run: ${{ env.RUN }} "pytest" + run: ${{ env.RUN }} "pytest -n logical" static-analysis: name: static analysis diff --git a/can/tests/test_checksums.py b/can/tests/test_checksums.py old mode 100755 new mode 100644 index 93ddbe0074..25eadd64cd --- a/can/tests/test_checksums.py +++ b/can/tests/test_checksums.py @@ -1,12 +1,9 @@ -#!/usr/bin/env python3 -import unittest - from opendbc.can.parser import CANParser from opendbc.can.packer import CANPacker from opendbc.can.tests.test_packer_parser import can_list_to_can_capnp -class TestCanChecksums(unittest.TestCase): +class TestCanChecksums: def test_honda_checksum(self): """Test checksums for Honda standard and extended CAN ids""" @@ -34,9 +31,5 @@ def test_honda_checksum(self): can_strings = [can_list_to_can_capnp(msgs), ] parser.update_strings(can_strings) - self.assertEqual(parser.vl['LKAS_HUD']['CHECKSUM'], std) - self.assertEqual(parser.vl['LKAS_HUD_A']['CHECKSUM'], ext) - - -if __name__ == "__main__": - unittest.main() + assert parser.vl['LKAS_HUD']['CHECKSUM'] == std + assert parser.vl['LKAS_HUD_A']['CHECKSUM'] == ext diff --git a/can/tests/test_dbc_exceptions.py b/can/tests/test_dbc_exceptions.py old mode 100755 new mode 100644 index 1f13f53927..b765d27fb2 --- a/can/tests/test_dbc_exceptions.py +++ b/can/tests/test_dbc_exceptions.py @@ -1,28 +1,26 @@ -#!/usr/bin/env python3 - -import unittest +import pytest from opendbc.can.parser import CANParser, CANDefine from opendbc.can.packer import CANPacker from opendbc.can.tests import TEST_DBC -class TestCanParserPackerExceptions(unittest.TestCase): +class TestCanParserPackerExceptions: def test_civic_exceptions(self): dbc_file = "honda_civic_touring_2016_can_generated" dbc_invalid = dbc_file + "abcdef" msgs = [("STEERING_CONTROL", 50)] - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): CANParser(dbc_invalid, msgs, 0) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): CANPacker(dbc_invalid) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): CANDefine(dbc_invalid) - with self.assertRaises(KeyError): + with pytest.raises(KeyError): CANDefine(TEST_DBC) parser = CANParser(dbc_file, msgs, 0) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): parser.update_strings([b'']) # Everything is supposed to work below @@ -30,7 +28,3 @@ def test_civic_exceptions(self): CANParser(dbc_file, [], 0) CANPacker(dbc_file) CANDefine(dbc_file) - - -if __name__ == "__main__": - unittest.main() diff --git a/can/tests/test_dbc_parser.py b/can/tests/test_dbc_parser.py old mode 100755 new mode 100644 index 5da41d49b7..4e9bb34f20 --- a/can/tests/test_dbc_parser.py +++ b/can/tests/test_dbc_parser.py @@ -1,16 +1,13 @@ -#!/usr/bin/env python3 -import unittest - from opendbc.can.parser import CANParser from opendbc.can.tests import ALL_DBCS -class TestDBCParser(unittest.TestCase): +class TestDBCParser: def test_enough_dbcs(self): # sanity check that we're running on the real DBCs - self.assertGreater(len(ALL_DBCS), 20) + assert len(ALL_DBCS) > 20 - def test_parse_all_dbcs(self): + def test_parse_all_dbcs(self, subtests): """ Dynamic DBC parser checks: - Checksum and counter length, start bit, endianness @@ -20,9 +17,5 @@ def test_parse_all_dbcs(self): """ for dbc in ALL_DBCS: - with self.subTest(dbc=dbc): + with subtests.test(dbc=dbc): CANParser(dbc, [], 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/can/tests/test_define.py b/can/tests/test_define.py old mode 100755 new mode 100644 index 6387ef9cb0..ec29f4a59b --- a/can/tests/test_define.py +++ b/can/tests/test_define.py @@ -1,35 +1,26 @@ -#!/usr/bin/env python3 -import unittest - from opendbc.can.can_define import CANDefine from opendbc.can.tests import ALL_DBCS -class TestCADNDefine(unittest.TestCase): +class TestCADNDefine: def test_civic(self): dbc_file = "honda_civic_touring_2016_can_generated" defs = CANDefine(dbc_file) - self.assertDictEqual(defs.dv[399], defs.dv['STEER_STATUS']) - self.assertDictEqual(defs.dv[399], - {'STEER_STATUS': - {7: 'PERMANENT_FAULT', - 6: 'TMP_FAULT', - 5: 'FAULT_1', - 4: 'NO_TORQUE_ALERT_2', - 3: 'LOW_SPEED_LOCKOUT', - 2: 'NO_TORQUE_ALERT_1', - 0: 'NORMAL'} - } - ) - - def test_all_dbcs(self): + assert defs.dv[399] == defs.dv['STEER_STATUS'] + assert defs.dv[399] == {'STEER_STATUS': + {7: 'PERMANENT_FAULT', + 6: 'TMP_FAULT', + 5: 'FAULT_1', + 4: 'NO_TORQUE_ALERT_2', + 3: 'LOW_SPEED_LOCKOUT', + 2: 'NO_TORQUE_ALERT_1', + 0: 'NORMAL'} + } + + def test_all_dbcs(self, subtests): # Asserts no exceptions on all DBCs for dbc in ALL_DBCS: - with self.subTest(dbc=dbc): + with subtests.test(dbc=dbc): CANDefine(dbc) - - -if __name__ == "__main__": - unittest.main() diff --git a/can/tests/test_packer_parser.py b/can/tests/test_packer_parser.py old mode 100755 new mode 100644 index c313805c47..9023e53dc2 --- a/can/tests/test_packer_parser.py +++ b/can/tests/test_packer_parser.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python3 -import unittest +import pytest import random import cereal.messaging as messaging @@ -31,7 +30,7 @@ def can_list_to_can_capnp(can_msgs, msgtype='can', logMonoTime=None): return dat.to_bytes() -class TestCanParserPacker(unittest.TestCase): +class TestCanParserPacker: def test_packer(self): packer = CANPacker(TEST_DBC) @@ -39,9 +38,9 @@ def test_packer(self): for i in range(256): values = {"COUNTER": i} addr, _, dat, bus = packer.make_can_msg("CAN_FD_MESSAGE", b, values) - self.assertEqual(addr, 245) - self.assertEqual(bus, b) - self.assertEqual(dat[0], i) + assert addr == 245 + assert bus == b + assert dat[0] == i def test_packer_counter(self): msgs = [("CAN_FD_MESSAGE", 0), ] @@ -53,7 +52,7 @@ def test_packer_counter(self): msg = packer.make_can_msg("CAN_FD_MESSAGE", 0, {}) dat = can_list_to_can_capnp([msg, ]) parser.update_strings([dat]) - self.assertEqual(parser.vl["CAN_FD_MESSAGE"]["COUNTER"], i % 256) + assert parser.vl["CAN_FD_MESSAGE"]["COUNTER"] == (i % 256) # setting COUNTER should override for _ in range(100): @@ -64,7 +63,7 @@ def test_packer_counter(self): }) dat = can_list_to_can_capnp([msg, ]) parser.update_strings([dat]) - self.assertEqual(parser.vl["CAN_FD_MESSAGE"]["COUNTER"], cnt) + assert parser.vl["CAN_FD_MESSAGE"]["COUNTER"] == cnt # then, should resume counting from the override value cnt = parser.vl["CAN_FD_MESSAGE"]["COUNTER"] @@ -72,7 +71,7 @@ def test_packer_counter(self): msg = packer.make_can_msg("CAN_FD_MESSAGE", 0, {}) dat = can_list_to_can_capnp([msg, ]) parser.update_strings([dat]) - self.assertEqual(parser.vl["CAN_FD_MESSAGE"]["COUNTER"], (cnt + i) % 256) + assert parser.vl["CAN_FD_MESSAGE"]["COUNTER"] == ((cnt + i) % 256) def test_parser_can_valid(self): msgs = [("CAN_FD_MESSAGE", 10), ] @@ -80,13 +79,13 @@ def test_parser_can_valid(self): parser = CANParser(TEST_DBC, msgs, 0) # shouldn't be valid initially - self.assertFalse(parser.can_valid) + assert not parser.can_valid # not valid until the message is seen for _ in range(100): dat = can_list_to_can_capnp([]) parser.update_strings([dat]) - self.assertFalse(parser.can_valid) + assert not parser.can_valid # valid once seen for i in range(1, 100): @@ -94,7 +93,7 @@ def test_parser_can_valid(self): msg = packer.make_can_msg("CAN_FD_MESSAGE", 0, {}) dat = can_list_to_can_capnp([msg, ], logMonoTime=t) parser.update_strings([dat]) - self.assertTrue(parser.can_valid) + assert parser.can_valid def test_parser_counter_can_valid(self): """ @@ -113,13 +112,13 @@ def test_parser_counter_can_valid(self): # bad static counter, invalid once it's seen MAX_BAD_COUNTER messages for idx in range(0x1000): parser.update_strings([bts]) - self.assertEqual((idx + 1) < MAX_BAD_COUNTER, parser.can_valid) + assert ((idx + 1) < MAX_BAD_COUNTER) == parser.can_valid # one to recover msg = packer.make_can_msg("STEERING_CONTROL", 0, {"COUNTER": 1}) bts = can_list_to_can_capnp([msg]) parser.update_strings([bts]) - self.assertTrue(parser.can_valid) + assert parser.can_valid def test_parser_no_partial_update(self): """ @@ -144,18 +143,18 @@ def rx_steering_msg(values, bad_checksum=False): parser.update_strings([bts]) rx_steering_msg({"STEER_TORQUE": 100}, bad_checksum=False) - self.assertEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE"], 100) - self.assertEqual(parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"], [100]) + assert parser.vl["STEERING_CONTROL"]["STEER_TORQUE"] == 100 + assert parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"] == [100] for _ in range(5): rx_steering_msg({"STEER_TORQUE": 200}, bad_checksum=True) - self.assertEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE"], 100) - self.assertEqual(parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"], []) + assert parser.vl["STEERING_CONTROL"]["STEER_TORQUE"] == 100 + assert parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"] == [] # Even if CANParser doesn't update instantaneous vl, make sure it didn't add invalid values to vl_all rx_steering_msg({"STEER_TORQUE": 300}, bad_checksum=False) - self.assertEqual(parser.vl["STEERING_CONTROL"]["STEER_TORQUE"], 300) - self.assertEqual(parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"], [300]) + assert parser.vl["STEERING_CONTROL"]["STEER_TORQUE"] == 300 + assert parser.vl_all["STEERING_CONTROL"]["STEER_TORQUE"] == [300] def test_packer_parser(self): msgs = [ @@ -189,11 +188,11 @@ def test_packer_parser(self): for k, v in values.items(): for key, val in v.items(): - self.assertAlmostEqual(parser.vl[k][key], val) + assert parser.vl[k][key] == pytest.approx(val) # also check address for sig in ("STEER_TORQUE", "STEER_TORQUE_REQUEST", "COUNTER", "CHECKSUM"): - self.assertEqual(parser.vl["STEERING_CONTROL"][sig], parser.vl[228][sig]) + assert parser.vl["STEERING_CONTROL"][sig] == parser.vl[228][sig] def test_scale_offset(self): """Test that both scale and offset are correctly preserved""" @@ -209,7 +208,7 @@ def test_scale_offset(self): parser.update_strings([bts]) - self.assertAlmostEqual(parser.vl["VSA_STATUS"]["USER_BRAKE"], brake) + assert parser.vl["VSA_STATUS"]["USER_BRAKE"] == pytest.approx(brake) def test_subaru(self): # Subaru is little endian @@ -234,10 +233,10 @@ def test_subaru(self): bts = can_list_to_can_capnp([msgs]) parser.update_strings([bts]) - self.assertAlmostEqual(parser.vl["ES_LKAS"]["LKAS_Output"], steer) - self.assertAlmostEqual(parser.vl["ES_LKAS"]["LKAS_Request"], active) - self.assertAlmostEqual(parser.vl["ES_LKAS"]["SET_1"], 1) - self.assertAlmostEqual(parser.vl["ES_LKAS"]["COUNTER"], idx % 16) + assert parser.vl["ES_LKAS"]["LKAS_Output"] == pytest.approx(steer) + assert parser.vl["ES_LKAS"]["LKAS_Request"] == pytest.approx(active) + assert parser.vl["ES_LKAS"]["SET_1"] == pytest.approx(1) + assert parser.vl["ES_LKAS"]["COUNTER"] == pytest.approx(idx % 16) idx += 1 def test_bus_timeout(self): @@ -267,16 +266,16 @@ def send_msg(blank=False): # all good, no timeout for _ in range(1000): send_msg() - self.assertFalse(parser.bus_timeout, str(_)) + assert not parser.bus_timeout, str(_) # timeout after 10 blank msgs for n in range(200): send_msg(blank=True) - self.assertEqual(n >= 10, parser.bus_timeout) + assert (n >= 10) == parser.bus_timeout # no timeout immediately after seen again send_msg() - self.assertFalse(parser.bus_timeout) + assert not parser.bus_timeout def test_updated(self): """Test updated value dict""" @@ -286,7 +285,7 @@ def test_updated(self): packer = CANPacker(dbc_file) # Make sure nothing is updated - self.assertEqual(len(parser.vl_all["VSA_STATUS"]["USER_BRAKE"]), 0) + assert len(parser.vl_all["VSA_STATUS"]["USER_BRAKE"]) == 0 idx = 0 for _ in range(10): @@ -304,9 +303,9 @@ def test_updated(self): parser.update_strings(can_strings) vl_all = parser.vl_all["VSA_STATUS"]["USER_BRAKE"] - self.assertEqual(vl_all, user_brake_vals) + assert vl_all == user_brake_vals if len(user_brake_vals): - self.assertEqual(vl_all[-1], parser.vl["VSA_STATUS"]["USER_BRAKE"]) + assert vl_all[-1] == parser.vl["VSA_STATUS"]["USER_BRAKE"] def test_timestamp_nanos(self): """Test message timestamp dict""" @@ -323,7 +322,7 @@ def test_timestamp_nanos(self): # Check the default timestamp is zero for msg in ("VSA_STATUS", "POWERTRAIN_DATA"): ts_nanos = parser.ts_nanos[msg].values() - self.assertEqual(set(ts_nanos), {0}) + assert set(ts_nanos) == {0} # Check: # - timestamp is only updated for correct messages @@ -339,9 +338,9 @@ def test_timestamp_nanos(self): parser.update_strings(can_strings) ts_nanos = parser.ts_nanos["VSA_STATUS"].values() - self.assertEqual(set(ts_nanos), {log_mono_time}) + assert set(ts_nanos) == {log_mono_time} ts_nanos = parser.ts_nanos["POWERTRAIN_DATA"].values() - self.assertEqual(set(ts_nanos), {0}) + assert set(ts_nanos) == {0} def test_nonexistent_messages(self): # Ensure we don't allow messages not in the DBC @@ -349,13 +348,13 @@ def test_nonexistent_messages(self): for msg in existing_messages: CANParser(TEST_DBC, [(msg, 0)]) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): new_msg = msg + "1" if isinstance(msg, str) else msg + 1 CANParser(TEST_DBC, [(new_msg, 0)]) def test_track_all_signals(self): parser = CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 0)]) - self.assertEqual(parser.vl["ACC_CONTROL"], { + assert parser.vl["ACC_CONTROL"] == { "ACCEL_CMD": 0, "ALLOW_LONG_PRESS": 0, "ACC_MALFUNCTION": 0, @@ -371,15 +370,15 @@ def test_track_all_signals(self): "ITS_CONNECT_LEAD": 0, "ACCEL_CMD_ALT": 0, "CHECKSUM": 0, - }) + } def test_disallow_duplicate_messages(self): CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 5)]) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 5), ("ACC_CONTROL", 10)]) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 10), ("ACC_CONTROL", 10)]) def test_allow_undefined_msgs(self): @@ -387,13 +386,6 @@ def test_allow_undefined_msgs(self): # discovery tests in openpilot first packer = CANPacker("toyota_nodsu_pt_generated") - self.assertEqual(packer.make_can_msg("ACC_CONTROL", 0, {"UNKNOWN_SIGNAL": 0}), - [835, 0, b'\x00\x00\x00\x00\x00\x00\x00N', 0]) - self.assertEqual(packer.make_can_msg("UNKNOWN_MESSAGE", 0, {"UNKNOWN_SIGNAL": 0}), - [0, 0, b'', 0]) - self.assertEqual(packer.make_can_msg(0, 0, {"UNKNOWN_SIGNAL": 0}), - [0, 0, b'', 0]) - - -if __name__ == "__main__": - unittest.main() + assert packer.make_can_msg("ACC_CONTROL", 0, {"UNKNOWN_SIGNAL": 0}) == [835, 0, b'\x00\x00\x00\x00\x00\x00\x00N', 0] + assert packer.make_can_msg("UNKNOWN_MESSAGE", 0, {"UNKNOWN_SIGNAL": 0}) == [0, 0, b'', 0] + assert packer.make_can_msg(0, 0, {"UNKNOWN_SIGNAL": 0}) == [0, 0, b'', 0] diff --git a/can/tests/test_parser_performance.py b/can/tests/test_parser_performance.py old mode 100755 new mode 100644 index 2010fa4bf7..fe0392b825 --- a/can/tests/test_parser_performance.py +++ b/can/tests/test_parser_performance.py @@ -1,14 +1,13 @@ -#!/usr/bin/env python3 +import pytest import time -import unittest from opendbc.can.parser import CANParser from opendbc.can.packer import CANPacker from opendbc.can.tests.test_packer_parser import can_list_to_can_capnp -@unittest.skip("TODO: varies too much between machines") -class TestParser(unittest.TestCase): +@pytest.mark.skip("TODO: varies too much between machines") +class TestParser: def _benchmark(self, checks, thresholds, n): parser = CANParser('toyota_new_mc_pt_generated', checks, 0) packer = CANPacker('toyota_new_mc_pt_generated') @@ -43,13 +42,9 @@ def _benchmark(self, checks, thresholds, n): print('%s: [%d] %.1fms to parse %s, avg: %dns' % (self._testMethodName, n, et/1e6, len(can_msgs), avg_nanos)) minn, maxx = thresholds - self.assertLess(avg_nanos, maxx) - self.assertGreater(avg_nanos, minn, "Performance seems to have improved, update test thresholds.") + assert avg_nanos < maxx + assert avg_nanos > minn, "Performance seems to have improved, update test thresholds." def test_performance_all_signals(self): self._benchmark([('ACC_CONTROL', 10)], (10000, 19000), 1) self._benchmark([('ACC_CONTROL', 10)], (1300, 5000), 10) - - -if __name__ == "__main__": - unittest.main() diff --git a/requirements.txt b/requirements.txt index 10f8414dda..3eab8fb0c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,5 @@ pycapnp pyyaml scons pytest +pytest-xdist +pytest-subtests