mirror of
https://github.com/infiniteCable2/opendbc.git
synced 2026-04-06 05:53:54 +08:00
Replace pytest with unittest + unittest-parallel (#3191)
This commit is contained in:
@@ -26,5 +26,5 @@ test:
|
||||
run: opendbc/safety/tests/misra/test_misra.sh
|
||||
|
||||
# *** tests ***
|
||||
pytest:
|
||||
run: pytest -n8
|
||||
unittest:
|
||||
run: unittest-parallel -j8 -s opendbc -p 'test_*.py' -t .
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import copy
|
||||
import unittest
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
|
||||
|
||||
class TestCanChecksums:
|
||||
class TestCanChecksums(unittest.TestCase):
|
||||
|
||||
def verify_checksum(self, subtests, dbc_file: str, msg_name: str, msg_addr: int, test_messages: list[bytes],
|
||||
def verify_checksum(self, dbc_file: str, msg_name: str, msg_addr: int, test_messages: list[bytes],
|
||||
checksum_field: str = 'CHECKSUM', counter_field = 'COUNTER'):
|
||||
"""
|
||||
Verify that opendbc calculates payload CRCs/checksums matching those received in known-good sample messages
|
||||
@@ -24,37 +25,37 @@ class TestCanChecksums:
|
||||
|
||||
parser.update([0, [modified_msg]])
|
||||
tested = parser.vl[msg_name]
|
||||
with subtests.test(counter=expected[counter_field]):
|
||||
with self.subTest(counter=expected[counter_field]):
|
||||
assert tested[checksum_field] == expected[checksum_field]
|
||||
|
||||
def verify_fca_giorgio_crc(self, subtests, msg_name: str, msg_addr: int, test_messages: list[bytes]):
|
||||
def verify_fca_giorgio_crc(self, msg_name: str, msg_addr: int, test_messages: list[bytes]):
|
||||
"""Test modified SAE J1850 CRCs, with special final XOR cases for EPS messages"""
|
||||
assert len(test_messages) == 3
|
||||
self.verify_checksum(subtests, "fca_giorgio", msg_name, msg_addr, test_messages)
|
||||
self.verify_checksum("fca_giorgio", msg_name, msg_addr, test_messages)
|
||||
|
||||
def test_fca_giorgio_eps_1(self, subtests):
|
||||
self.verify_fca_giorgio_crc(subtests, "EPS_1", 0xDE, [
|
||||
def test_fca_giorgio_eps_1(self):
|
||||
self.verify_fca_giorgio_crc("EPS_1", 0xDE, [
|
||||
b'\x17\x51\x97\xcc\x00\xdf',
|
||||
b'\x17\x51\x97\xc9\x01\xa3',
|
||||
b'\x17\x51\x97\xcc\x02\xe5',
|
||||
])
|
||||
|
||||
def test_fca_giorgio_eps_2(self, subtests):
|
||||
self.verify_fca_giorgio_crc(subtests, "EPS_2", 0x106, [
|
||||
def test_fca_giorgio_eps_2(self):
|
||||
self.verify_fca_giorgio_crc("EPS_2", 0x106, [
|
||||
b'\x7c\x43\x57\x60\x00\x00\xa1',
|
||||
b'\x7c\x63\x58\xe0\x00\x01\xd5',
|
||||
b'\x7c\x63\x58\xe0\x00\x02\xf2',
|
||||
])
|
||||
|
||||
def test_fca_giorgio_eps_3(self, subtests):
|
||||
self.verify_fca_giorgio_crc(subtests, "EPS_3", 0x122, [
|
||||
def test_fca_giorgio_eps_3(self):
|
||||
self.verify_fca_giorgio_crc("EPS_3", 0x122, [
|
||||
b'\x7b\x30\x00\xf8',
|
||||
b'\x7b\x10\x01\x90',
|
||||
b'\x7b\xf0\x02\x6e',
|
||||
])
|
||||
|
||||
def test_fca_giorgio_abs_2(self, subtests):
|
||||
self.verify_fca_giorgio_crc(subtests, "ABS_2", 0xFE, [
|
||||
def test_fca_giorgio_abs_2(self):
|
||||
self.verify_fca_giorgio_crc("ABS_2", 0xFE, [
|
||||
b'\x7e\x38\x00\x7d\x10\x31\x80\x32',
|
||||
b'\x7e\x38\x00\x7d\x10\x31\x81\x2f',
|
||||
b'\x7e\x38\x00\x7d\x20\x31\x82\x20',
|
||||
@@ -90,13 +91,13 @@ class TestCanChecksums:
|
||||
assert parser.vl['LKAS_HUD']['CHECKSUM'] == std
|
||||
assert parser.vl['LKAS_HUD_A']['CHECKSUM'] == ext
|
||||
|
||||
def verify_volkswagen_mqb_crc(self, subtests, msg_name: str, msg_addr: int, test_messages: list[bytes], counter_field: str = 'COUNTER'):
|
||||
def verify_volkswagen_mqb_crc(self, msg_name: str, msg_addr: int, test_messages: list[bytes], counter_field: str = 'COUNTER'):
|
||||
"""Test AUTOSAR E2E Profile 2 CRCs"""
|
||||
assert len(test_messages) == 16 # All counter values must be tested
|
||||
self.verify_checksum(subtests, "vw_mqb", msg_name, msg_addr, test_messages, counter_field=counter_field)
|
||||
self.verify_checksum("vw_mqb", msg_name, msg_addr, test_messages, counter_field=counter_field)
|
||||
|
||||
def test_volkswagen_mqb_crc_lwi_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "LWI_01", 0x86, [
|
||||
def test_volkswagen_mqb_crc_lwi_01(self):
|
||||
self.verify_volkswagen_mqb_crc("LWI_01", 0x86, [
|
||||
b'\x6b\x00\xbd\x00\x00\x00\x00\x00',
|
||||
b'\xee\x01\x0a\x00\x00\x00\x00\x00',
|
||||
b'\xd8\x02\xa9\x00\x00\x00\x00\x00',
|
||||
@@ -115,8 +116,8 @@ class TestCanChecksums:
|
||||
b'\x60\x0f\x62\xc0\x00\x00\x00\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_airbag_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "Airbag_01", 0x40, [
|
||||
def test_volkswagen_mqb_crc_airbag_01(self):
|
||||
self.verify_volkswagen_mqb_crc("Airbag_01", 0x40, [
|
||||
b'\xaf\x00\x00\x80\xc0\x00\x20\x3e',
|
||||
b'\x54\x01\x00\x80\xc0\x00\x20\x1a',
|
||||
b'\x54\x02\x00\x80\xc0\x00\x60\x00',
|
||||
@@ -135,8 +136,8 @@ class TestCanChecksums:
|
||||
b'\xe5\x0f\x00\x80\xc0\x00\x40\xf6',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_lh_eps_03(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "LH_EPS_03", 0x9F, [
|
||||
def test_volkswagen_mqb_crc_lh_eps_03(self):
|
||||
self.verify_volkswagen_mqb_crc("LH_EPS_03", 0x9F, [
|
||||
b'\x11\x30\x2e\x00\x05\x1c\x80\x30',
|
||||
b'\x5b\x31\x8e\x03\x05\x53\x00\x30',
|
||||
b'\xcb\x32\xd3\x06\x05\x73\x00\x30',
|
||||
@@ -155,8 +156,8 @@ class TestCanChecksums:
|
||||
b'\xe2\x3f\x05\x00\x05\x0a\x00\x30',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_getriebe_11(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "Getriebe_11", 0xAD, [
|
||||
def test_volkswagen_mqb_crc_getriebe_11(self):
|
||||
self.verify_volkswagen_mqb_crc("Getriebe_11", 0xAD, [
|
||||
b'\xf8\xe0\xbf\xff\x5f\x20\x20\x20',
|
||||
b'\xb0\xe1\xbf\xff\xc6\x98\x21\x80',
|
||||
b'\xd2\xe2\xbf\xff\x5f\x20\x20\x20',
|
||||
@@ -175,8 +176,8 @@ class TestCanChecksums:
|
||||
b'\x36\xef\xbf\xff\xaa\x20\x20\x10',
|
||||
], counter_field="COUNTER_DISABLED") # see opendbc#1235
|
||||
|
||||
def test_volkswagen_mqb_crc_esp_21(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ESP_21", 0xFD, [
|
||||
def test_volkswagen_mqb_crc_esp_21(self):
|
||||
self.verify_volkswagen_mqb_crc("ESP_21", 0xFD, [
|
||||
b'\x66\xd0\x1f\x80\x45\x05\x00\x00',
|
||||
b'\x87\xd1\x1f\x80\x52\x05\x00\x00',
|
||||
b'\xcd\xd2\x1f\x80\x50\x06\x00\x00',
|
||||
@@ -195,8 +196,8 @@ class TestCanChecksums:
|
||||
b'\xfb\xdf\x1f\x80\x46\x00\x00\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_esp_02(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ESP_02", 0x101, [
|
||||
def test_volkswagen_mqb_crc_esp_02(self):
|
||||
self.verify_volkswagen_mqb_crc("ESP_02", 0x101, [
|
||||
b'\xf2\x00\x7e\xff\xa1\x2a\x40\x00',
|
||||
b'\xd3\x01\x7d\x00\xa2\x0c\x02\x00',
|
||||
b'\x03\x02\x7a\x06\xa2\x49\x42\x00',
|
||||
@@ -215,8 +216,8 @@ class TestCanChecksums:
|
||||
b'\x49\x0f\x85\x12\xa2\xf6\x01\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_esp_05(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ESP_05", 0x106, [
|
||||
def test_volkswagen_mqb_crc_esp_05(self):
|
||||
self.verify_volkswagen_mqb_crc("ESP_05", 0x106, [
|
||||
b'\x90\x80\x64\x00\x00\x00\xe7\x10',
|
||||
b'\xf4\x81\x64\x00\x00\x00\xe7\x10',
|
||||
b'\x90\x82\x63\x00\x00\x00\xe8\x10',
|
||||
@@ -235,8 +236,8 @@ class TestCanChecksums:
|
||||
b'\x3f\x8f\x82\x04\x00\x00\xe6\x30',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_esp_10(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ESP_10", 0x116, [
|
||||
def test_volkswagen_mqb_crc_esp_10(self):
|
||||
self.verify_volkswagen_mqb_crc("ESP_10", 0x116, [
|
||||
b'\x2d\x00\xd5\x98\x9f\x26\x25\x0f',
|
||||
b'\x24\x01\x60\x63\x2c\x5e\x3b\x0f',
|
||||
b'\x08\x02\xb2\x2f\xee\x9a\x29\x0f',
|
||||
@@ -255,8 +256,8 @@ class TestCanChecksums:
|
||||
b'\x15\x0f\x51\x59\x56\x35\xb1\x0f',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_acc_10(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ACC_10", 0x117, [
|
||||
def test_volkswagen_mqb_crc_acc_10(self):
|
||||
self.verify_volkswagen_mqb_crc("ACC_10", 0x117, [
|
||||
b'\x9b\x00\x00\x40\x68\x00\x00\xff',
|
||||
b'\xff\x01\x00\x40\x68\x00\x00\xff',
|
||||
b'\x53\x02\x00\x40\x68\x00\x00\xff',
|
||||
@@ -275,8 +276,8 @@ class TestCanChecksums:
|
||||
b'\xd9\x0f\x00\x40\x68\x00\x00\xff',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_tsk_06(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "TSK_06", 0x120, [
|
||||
def test_volkswagen_mqb_crc_tsk_06(self):
|
||||
self.verify_volkswagen_mqb_crc("TSK_06", 0x120, [
|
||||
b'\xc1\x00\x00\x02\x00\x08\xff\x21',
|
||||
b'\x34\x01\x00\x02\x00\x08\xff\x21',
|
||||
b'\xcc\x02\x00\x02\x00\x08\xff\x21',
|
||||
@@ -295,8 +296,8 @@ class TestCanChecksums:
|
||||
b'\x0b\x0f\x00\x02\x00\x08\xff\x21',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_motor_20(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "Motor_20", 0x121, [
|
||||
def test_volkswagen_mqb_crc_motor_20(self):
|
||||
self.verify_volkswagen_mqb_crc("Motor_20", 0x121, [
|
||||
b'\xb9\x00\x00\xc0\x39\x46\x7e\xfe',
|
||||
b'\x85\x31\x20\x00\x1a\x46\x7e\xfe',
|
||||
b'\xc7\x12\x00\x40\x1a\x46\x7e\xfe',
|
||||
@@ -315,8 +316,8 @@ class TestCanChecksums:
|
||||
b'\xaf\x0f\x20\x80\x39\x4c\x7e\xfe',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_acc_06(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ACC_06", 0x122, [
|
||||
def test_volkswagen_mqb_crc_acc_06(self):
|
||||
self.verify_volkswagen_mqb_crc("ACC_06", 0x122, [
|
||||
b'\x14\x80\x00\xfe\x07\x00\x00\x18',
|
||||
b'\x9f\x81\x00\xfe\x07\x00\x00\x18',
|
||||
b'\x0a\x82\x00\xfe\x07\x00\x00\x28',
|
||||
@@ -335,8 +336,8 @@ class TestCanChecksums:
|
||||
b'\x6f\x8f\x00\xfe\x07\x00\x00\x28',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_hca_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "HCA_01", 0x126, [
|
||||
def test_volkswagen_mqb_crc_hca_01(self):
|
||||
self.verify_volkswagen_mqb_crc("HCA_01", 0x126, [
|
||||
b'\x00\x30\x0d\xc0\x05\xfe\x07\x00',
|
||||
b'\x3e\x31\x54\xc0\x05\xfe\x07\x00',
|
||||
b'\xa7\x32\xbb\x40\x05\xfe\x07\x00',
|
||||
@@ -355,8 +356,8 @@ class TestCanChecksums:
|
||||
b'\x9b\x3f\x20\x40\x05\xfe\x07\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_gra_acc_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "GRA_ACC_01", 0x12B, [
|
||||
def test_volkswagen_mqb_crc_gra_acc_01(self):
|
||||
self.verify_volkswagen_mqb_crc("GRA_ACC_01", 0x12B, [
|
||||
b'\x86\x40\x80\x2a\x00\x00\x00\x00',
|
||||
b'\xf4\x41\x80\x2a\x00\x00\x00\x00',
|
||||
b'\x50\x42\x80\x2a\x00\x00\x00\x00',
|
||||
@@ -375,8 +376,8 @@ class TestCanChecksums:
|
||||
b'\x0d\x4f\x80\x2a\x00\x00\x00\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_acc_07(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ACC_07", 0x12E, [
|
||||
def test_volkswagen_mqb_crc_acc_07(self):
|
||||
self.verify_volkswagen_mqb_crc("ACC_07", 0x12E, [
|
||||
b'\xac\xe0\x7f\x00\xfe\x00\xc0\xff',
|
||||
b'\xa2\xe1\x7f\x00\xfe\x00\xc0\xff',
|
||||
b'\x6b\xe2\x7f\x00\xfe\x00\xc0\xff',
|
||||
@@ -395,8 +396,8 @@ class TestCanChecksums:
|
||||
b'\x85\xef\x7f\x00\xfe\x00\xc0\xff',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_motor_ev_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "Motor_EV_01", 0x187, [
|
||||
def test_volkswagen_mqb_crc_motor_ev_01(self):
|
||||
self.verify_volkswagen_mqb_crc("Motor_EV_01", 0x187, [
|
||||
b'\x70\x80\x15\x00\x00\x00\x00\xF0',
|
||||
b'\x07\x81\x15\x00\x00\x00\x00\xF0',
|
||||
b'\x7A\x82\x15\x00\x00\x00\x00\xF0',
|
||||
@@ -415,8 +416,8 @@ class TestCanChecksums:
|
||||
b'\x00\x8F\x15\x00\x00\x00\x00\xF0',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_esp_33(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ESP_33", 0x1AB, [
|
||||
def test_volkswagen_mqb_crc_esp_33(self):
|
||||
self.verify_volkswagen_mqb_crc("ESP_33", 0x1AB, [
|
||||
b'\x64\x00\x80\x02\x00\x00\x00\x00',
|
||||
b'\x19\x01\x00\x00\x00\x00\x00\x00',
|
||||
b'\xfc\x02\x00\x10\x01\x00\x00\x00',
|
||||
@@ -435,8 +436,8 @@ class TestCanChecksums:
|
||||
b'\x68\x0f\x80\x02\x00\x00\x00\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_acc_02(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ACC_02", 0x30C, [
|
||||
def test_volkswagen_mqb_crc_acc_02(self):
|
||||
self.verify_volkswagen_mqb_crc("ACC_02", 0x30C, [
|
||||
b'\x82\xf0\x3f\x00\x40\x30\x00\x40',
|
||||
b'\xe6\xf1\x3f\x00\x40\x30\x00\x40',
|
||||
b'\x4a\xf2\x3f\x00\x40\x30\x00\x40',
|
||||
@@ -455,8 +456,8 @@ class TestCanChecksums:
|
||||
b'\xc0\xff\x3f\x00\x40\x30\x00\x40',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_swa_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "SWA_01", 0x30F, [
|
||||
def test_volkswagen_mqb_crc_swa_01(self):
|
||||
self.verify_volkswagen_mqb_crc("SWA_01", 0x30F, [
|
||||
b'\x10\x00\x10\x00\x00\x00\x00\x00',
|
||||
b'\x74\x01\x10\x00\x00\x00\x00\x00',
|
||||
b'\xD8\x02\x10\x00\x00\x00\x00\x00',
|
||||
@@ -475,8 +476,8 @@ class TestCanChecksums:
|
||||
b'\x52\x0F\x10\x00\x00\x00\x00\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_acc_04(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ACC_04", 0x324, [
|
||||
def test_volkswagen_mqb_crc_acc_04(self):
|
||||
self.verify_volkswagen_mqb_crc("ACC_04", 0x324, [
|
||||
b'\xba\x00\x00\x00\x00\x00\x00\x10',
|
||||
b'\xde\x01\x00\x00\x00\x00\x00\x10',
|
||||
b'\x72\x02\x00\x00\x00\x00\x00\x10',
|
||||
@@ -495,8 +496,8 @@ class TestCanChecksums:
|
||||
b'\xdd\x0f\x00\x00\x00\x00\x00\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_klemmen_status_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "Klemmen_Status_01", 0x3C0, [
|
||||
def test_volkswagen_mqb_crc_klemmen_status_01(self):
|
||||
self.verify_volkswagen_mqb_crc("Klemmen_Status_01", 0x3C0, [
|
||||
b'\x74\x00\x03\x00',
|
||||
b'\xc1\x01\x03\x00',
|
||||
b'\x31\x02\x03\x00',
|
||||
@@ -515,8 +516,8 @@ class TestCanChecksums:
|
||||
b'\x35\x0f\x03\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_licht_anf_01(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "Licht_Anf_01", 0x3D5, [
|
||||
def test_volkswagen_mqb_crc_licht_anf_01(self):
|
||||
self.verify_volkswagen_mqb_crc("Licht_Anf_01", 0x3D5, [
|
||||
b'\xc8\x00\x00\x04\x00\x00\x00\x00',
|
||||
b'\x9f\x01\x00\x04\x00\x00\x00\x00',
|
||||
b'\x5e\x02\x00\x04\x00\x00\x00\x00',
|
||||
@@ -535,8 +536,8 @@ class TestCanChecksums:
|
||||
b'\x98\x0f\x00\x04\x00\x00\x00\x00',
|
||||
])
|
||||
|
||||
def test_volkswagen_mqb_crc_esp_20(self, subtests):
|
||||
self.verify_volkswagen_mqb_crc(subtests, "ESP_20", 0x65D, [
|
||||
def test_volkswagen_mqb_crc_esp_20(self):
|
||||
self.verify_volkswagen_mqb_crc("ESP_20", 0x65D, [
|
||||
b'\x98\x30\x2b\x10\x00\x00\x22\x81',
|
||||
b'\xc8\x31\x2b\x10\x00\x00\x22\x81',
|
||||
b'\x9d\x32\x2b\x10\x00\x00\x22\x81',
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from opendbc.can import CANDefine, CANPacker, CANParser
|
||||
from opendbc.can.tests import TEST_DBC
|
||||
|
||||
|
||||
class TestCanParserPackerExceptions:
|
||||
class TestCanParserPackerExceptions(unittest.TestCase):
|
||||
def test_civic_exceptions(self):
|
||||
dbc_file = "honda_civic_touring_2016_can_generated"
|
||||
dbc_invalid = dbc_file + "abcdef"
|
||||
msgs = [("STEERING_CONTROL", 50)]
|
||||
with pytest.raises(FileNotFoundError):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
CANParser(dbc_invalid, msgs, 0)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
CANPacker(dbc_invalid)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
CANDefine(dbc_invalid)
|
||||
with pytest.raises(KeyError):
|
||||
with self.assertRaises(KeyError):
|
||||
CANDefine(TEST_DBC)
|
||||
|
||||
parser = CANParser(dbc_file, msgs, 0)
|
||||
with pytest.raises(IndexError):
|
||||
with self.assertRaises(IndexError):
|
||||
parser.update([b''])
|
||||
|
||||
# Everything is supposed to work below
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import unittest
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.can.tests import ALL_DBCS
|
||||
|
||||
|
||||
class TestDBCParser:
|
||||
class TestDBCParser(unittest.TestCase):
|
||||
def test_enough_dbcs(self):
|
||||
# sanity check that we're running on the real DBCs
|
||||
assert len(ALL_DBCS) > 20
|
||||
|
||||
def test_parse_all_dbcs(self, subtests):
|
||||
def test_parse_all_dbcs(self):
|
||||
"""
|
||||
Dynamic DBC parser checks:
|
||||
- Checksum and counter length, start bit, endianness
|
||||
@@ -17,5 +18,5 @@ class TestDBCParser:
|
||||
"""
|
||||
|
||||
for dbc in ALL_DBCS:
|
||||
with subtests.test(dbc=dbc):
|
||||
with self.subTest(dbc=dbc):
|
||||
CANParser(dbc, [], 0)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import random
|
||||
|
||||
from opendbc.can import CANPacker, CANParser
|
||||
@@ -7,7 +7,7 @@ from opendbc.can.tests import TEST_DBC
|
||||
MAX_BAD_COUNTER = 5
|
||||
|
||||
|
||||
class TestCanParserPacker:
|
||||
class TestCanParserPacker(unittest.TestCase):
|
||||
def test_packer(self):
|
||||
packer = CANPacker(TEST_DBC)
|
||||
|
||||
@@ -169,7 +169,7 @@ class TestCanParserPacker:
|
||||
|
||||
for k, v in values.items():
|
||||
for key, val in v.items():
|
||||
assert parser.vl[k][key] == pytest.approx(val)
|
||||
self.assertAlmostEqual(parser.vl[k][key], val)
|
||||
|
||||
# also check address
|
||||
for sig in ("STEER_TORQUE", "STEER_TORQUE_REQUEST", "COUNTER", "CHECKSUM"):
|
||||
@@ -187,7 +187,7 @@ class TestCanParserPacker:
|
||||
msgs = packer.make_can_msg("VSA_STATUS", 0, values)
|
||||
parser.update([0, [msgs]])
|
||||
|
||||
assert parser.vl["VSA_STATUS"]["USER_BRAKE"] == pytest.approx(brake)
|
||||
self.assertAlmostEqual(parser.vl["VSA_STATUS"]["USER_BRAKE"], brake)
|
||||
|
||||
def test_subaru(self):
|
||||
# Subaru is little endian
|
||||
@@ -211,10 +211,10 @@ class TestCanParserPacker:
|
||||
msgs = packer.make_can_msg("ES_LKAS", 0, values)
|
||||
parser.update([0, [msgs]])
|
||||
|
||||
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)
|
||||
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)
|
||||
idx += 1
|
||||
|
||||
def test_bus_timeout(self):
|
||||
@@ -325,7 +325,7 @@ class TestCanParserPacker:
|
||||
|
||||
for msg in existing_messages:
|
||||
CANParser(TEST_DBC, [(msg, 0)], 0)
|
||||
with pytest.raises(RuntimeError):
|
||||
with self.assertRaises(RuntimeError):
|
||||
new_msg = msg + "1" if isinstance(msg, str) else msg + 1
|
||||
CANParser(TEST_DBC, [(new_msg, 0)], 0)
|
||||
|
||||
@@ -352,10 +352,10 @@ class TestCanParserPacker:
|
||||
def test_disallow_duplicate_messages(self):
|
||||
CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 5)], 0)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
with self.assertRaises(RuntimeError):
|
||||
CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 5), ("ACC_CONTROL", 10)], 0)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
with self.assertRaises(RuntimeError):
|
||||
CANParser("toyota_nodsu_pt_generated", [("ACC_CONTROL", 10), ("ACC_CONTROL", 10)], 0)
|
||||
|
||||
def test_allow_undefined_msgs(self):
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import random
|
||||
from collections.abc import Iterable
|
||||
import unittest
|
||||
|
||||
from hypothesis import settings, given, strategies as st
|
||||
import pytest
|
||||
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.fw_versions import build_fw_dict
|
||||
from opendbc.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, get_platform_codes
|
||||
from opendbc.car.ford.fingerprints import FW_VERSIONS
|
||||
from opendbc.testing import parameterized
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
@@ -40,15 +40,15 @@ ECU_PART_NUMBER = {
|
||||
}
|
||||
|
||||
|
||||
class TestFordFW:
|
||||
class TestFordFW(unittest.TestCase):
|
||||
def test_fw_query_config(self):
|
||||
for (ecu, addr, subaddr) in FW_QUERY_CONFIG.extra_ecus:
|
||||
assert ecu in ECU_ADDRESSES, "Unknown ECU"
|
||||
assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch"
|
||||
assert subaddr is None, "Unexpected ECU subaddress"
|
||||
|
||||
@pytest.mark.parametrize("car_model,fw_versions", FW_VERSIONS.items())
|
||||
def test_fw_versions(self, car_model: str, fw_versions: dict[tuple[int, int, int | None], Iterable[bytes]]):
|
||||
@parameterized("car_model, fw_versions", FW_VERSIONS.items())
|
||||
def test_fw_versions(self, car_model, fw_versions):
|
||||
for (ecu, addr, subaddr), fws in fw_versions.items():
|
||||
assert ecu in ECU_PART_NUMBER, "Unexpected ECU"
|
||||
assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch"
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from opendbc.car.gm.fingerprints import FINGERPRINTS
|
||||
from opendbc.car.gm.values import CAMERA_ACC_CAR, GM_RX_OFFSET
|
||||
from opendbc.testing import parameterized
|
||||
|
||||
CAMERA_DIAGNOSTIC_ADDRESS = 0x24b
|
||||
|
||||
|
||||
class TestGMFingerprint:
|
||||
@pytest.mark.parametrize("car_model,fingerprints", FINGERPRINTS.items())
|
||||
class TestGMFingerprint(unittest.TestCase):
|
||||
@parameterized("car_model, fingerprints", FINGERPRINTS.items())
|
||||
def test_can_fingerprints(self, car_model, fingerprints):
|
||||
assert len(fingerprints) > 0
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from hypothesis import settings, given, strategies as st
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.structs import CarParams
|
||||
@@ -43,7 +43,7 @@ NO_DATES_PLATFORMS = {
|
||||
CANFD_EXPECTED_ECUS = {Ecu.fwdCamera, Ecu.fwdRadar}
|
||||
|
||||
|
||||
class TestHyundaiFingerprint:
|
||||
class TestHyundaiFingerprint(unittest.TestCase):
|
||||
def test_feature_detection(self):
|
||||
# LKA steering
|
||||
for lka_steering in (True, False):
|
||||
@@ -91,13 +91,13 @@ class TestHyundaiFingerprint:
|
||||
assert len(ecus_not_in_whitelist) == 0, \
|
||||
f"{car_model}: Car model has unexpected ECUs: {ecu_strings}"
|
||||
|
||||
def test_blacklisted_parts(self, subtests):
|
||||
def test_blacklisted_parts(self):
|
||||
# Asserts no ECUs known to be shared across platforms exist in the database.
|
||||
# Tucson having Santa Cruz camera and EPS for example
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
if car_model == CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN:
|
||||
pytest.skip("Skip checking Santa Cruz for its parts")
|
||||
raise unittest.SkipTest("Skip checking Santa Cruz for its parts")
|
||||
|
||||
for code, _ in get_platform_codes(ecus[(Ecu.fwdCamera, 0x7c4, None)]):
|
||||
if b"-" not in code:
|
||||
@@ -105,14 +105,14 @@ class TestHyundaiFingerprint:
|
||||
part = code.split(b"-")[1]
|
||||
assert not part.startswith(b'CW'), "Car has bad part number"
|
||||
|
||||
def test_correct_ecu_response_database(self, subtests):
|
||||
def test_correct_ecu_response_database(self):
|
||||
"""
|
||||
Assert standard responses for certain ECUs, since they can
|
||||
respond to multiple queries with different data
|
||||
"""
|
||||
expected_fw_prefix = HYUNDAI_VERSION_REQUEST_LONG[1:]
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
assert all(fw.startswith(expected_fw_prefix) for fw in fws), \
|
||||
f"FW from unexpected request in database: {(ecu, fws)}"
|
||||
@@ -125,10 +125,10 @@ class TestHyundaiFingerprint:
|
||||
fws = data.draw(fw_strategy)
|
||||
get_platform_codes(fws)
|
||||
|
||||
def test_expected_platform_codes(self, subtests):
|
||||
def test_expected_platform_codes(self):
|
||||
# Ensures we don't accidentally add multiple platform codes for a car unless it is intentional
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
@@ -144,14 +144,14 @@ class TestHyundaiFingerprint:
|
||||
|
||||
# Tests for platform codes, part numbers, and FW dates which Hyundai will use to fuzzy
|
||||
# fingerprint in the absence of full FW matches:
|
||||
def test_platform_code_ecus_available(self, subtests):
|
||||
def test_platform_code_ecus_available(self):
|
||||
# TODO: add queries for these non-CAN FD cars to get EPS
|
||||
no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, CAR.KIA_K7_2017,
|
||||
CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.HYUNDAI_ELANTRA}
|
||||
|
||||
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for platform_code_ecu in PLATFORM_CODE_ECUS:
|
||||
if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS:
|
||||
continue
|
||||
@@ -159,14 +159,14 @@ class TestHyundaiFingerprint:
|
||||
continue
|
||||
assert platform_code_ecu in [e[0] for e in ecus]
|
||||
|
||||
def test_fw_format(self, subtests):
|
||||
def test_fw_format(self):
|
||||
# Asserts:
|
||||
# - every supported ECU FW version returns one platform code
|
||||
# - every supported ECU FW version has a part number
|
||||
# - expected parsing of ECU FW dates
|
||||
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
@@ -183,7 +183,7 @@ class TestHyundaiFingerprint:
|
||||
assert all(date is not None for _, date in codes)
|
||||
|
||||
if car_model == CAR.HYUNDAI_GENESIS:
|
||||
pytest.skip("No part numbers for car model")
|
||||
raise unittest.SkipTest("No part numbers for car model")
|
||||
|
||||
# Hyundai places the ECU part number in their FW versions, assert all parsable
|
||||
# Some examples of valid formats: b"56310-L0010", b"56310L0010", b"56310/M6300"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import pytest
|
||||
import unittest
|
||||
from opendbc.car.can_definitions import CanData
|
||||
from opendbc.car.car_helpers import FRAME_FINGERPRINT, can_fingerprint
|
||||
from opendbc.car.fingerprints import _FINGERPRINTS as FINGERPRINTS
|
||||
from opendbc.testing import parameterized
|
||||
|
||||
|
||||
class TestCanFingerprint:
|
||||
@pytest.mark.parametrize("car_model, fingerprints", FINGERPRINTS.items())
|
||||
class TestCanFingerprint(unittest.TestCase):
|
||||
@parameterized("car_model, fingerprints", FINGERPRINTS.items())
|
||||
def test_can_fingerprint(self, car_model, fingerprints):
|
||||
"""Tests online fingerprinting function on offline fingerprints"""
|
||||
|
||||
@@ -21,7 +22,7 @@ class TestCanFingerprint:
|
||||
assert finger[1] == fingerprint
|
||||
assert finger[2] == {}
|
||||
|
||||
def test_timing(self, subtests):
|
||||
def test_timing(self):
|
||||
# just pick any CAN fingerprinting car
|
||||
car_model = "CHEVROLET_BOLT_EUV"
|
||||
fingerprint = FINGERPRINTS[car_model][0]
|
||||
@@ -42,7 +43,7 @@ class TestCanFingerprint:
|
||||
cases.append((FRAME_FINGERPRINT * 2, None, can))
|
||||
|
||||
for expected_frames, car_model, can in cases:
|
||||
with subtests.test(expected_frames=expected_frames, car_model=car_model):
|
||||
with self.subTest(expected_frames=expected_frames, car_model=car_model):
|
||||
frames = 0
|
||||
|
||||
def can_recv(**kwargs):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import math
|
||||
import unittest
|
||||
import hypothesis.strategies as st
|
||||
import pytest
|
||||
from functools import cache
|
||||
from hypothesis import Phase, given, settings
|
||||
from collections.abc import Callable
|
||||
@@ -61,14 +61,13 @@ def get_fuzzy_car_interface(car_name: str, draw: DrawType) -> CarInterfaceBase:
|
||||
return CarInterface(car_params)
|
||||
|
||||
|
||||
class TestCarInterfaces:
|
||||
def _make_car_test(car_name):
|
||||
# FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause
|
||||
# many generated examples to overrun when max_examples > ~20, don't use it
|
||||
@pytest.mark.parametrize("car_name", sorted(PLATFORMS))
|
||||
@settings(max_examples=MAX_EXAMPLES, deadline=None,
|
||||
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
||||
@given(data=st.data())
|
||||
def test_car_interfaces(self, car_name, data):
|
||||
def test(self, data):
|
||||
car_interface = get_fuzzy_car_interface(car_name, data.draw)
|
||||
car_params = car_interface.CP.as_reader()
|
||||
|
||||
@@ -130,6 +129,10 @@ class TestCarInterfaces:
|
||||
rr = radar_interface.update(cans)
|
||||
assert rr is None or len(rr.errors) > 0
|
||||
|
||||
return test
|
||||
|
||||
|
||||
class TestCarInterfaces(unittest.TestCase):
|
||||
def test_interface_attrs(self):
|
||||
"""Asserts basic behavior of interface attribute getter"""
|
||||
num_brands = len(get_interface_attr('CAR'))
|
||||
@@ -154,3 +157,7 @@ class TestCarInterfaces:
|
||||
ret = get_interface_attr('FINGERPRINTS', ignore_none=True)
|
||||
none_brands_in_ret = none_brands.intersection(ret)
|
||||
assert len(none_brands_in_ret) == 0, f'Brands with None values in ignore_none=True result: {none_brands_in_ret}'
|
||||
|
||||
|
||||
for car_name in sorted(PLATFORMS):
|
||||
setattr(TestCarInterfaces, f'test_car_interfaces_{car_name}', _make_car_test(car_name))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from collections import defaultdict
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.docs import get_all_car_docs
|
||||
@@ -8,33 +8,33 @@ from opendbc.car.honda.values import CAR as HONDA
|
||||
from opendbc.car.values import PLATFORMS
|
||||
|
||||
|
||||
class TestCarDocs:
|
||||
class TestCarDocs(unittest.TestCase):
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
def setUpClass(cls):
|
||||
cls.all_cars = get_all_car_docs()
|
||||
|
||||
def test_duplicate_years(self, subtests):
|
||||
def test_duplicate_years(self):
|
||||
make_model_years = defaultdict(list)
|
||||
for car in self.all_cars:
|
||||
with subtests.test(car_docs_name=car.name):
|
||||
with self.subTest(car_docs_name=car.name):
|
||||
if car.support_type != SupportType.UPSTREAM:
|
||||
pytest.skip()
|
||||
raise unittest.SkipTest
|
||||
|
||||
make_model = (car.make, car.model)
|
||||
for year in car.year_list:
|
||||
assert year not in make_model_years[make_model], f"{car.name}: Duplicate model year"
|
||||
make_model_years[make_model].append(year)
|
||||
|
||||
def test_missing_car_docs(self, subtests):
|
||||
def test_missing_car_docs(self):
|
||||
all_car_docs_platforms = [name for name, config in PLATFORMS.items()]
|
||||
for platform in sorted(interfaces.keys()):
|
||||
with subtests.test(platform=platform):
|
||||
with self.subTest(platform=platform):
|
||||
assert platform in all_car_docs_platforms, f"Platform: {platform} doesn't have a CarDocs entry"
|
||||
|
||||
def test_naming_conventions(self, subtests):
|
||||
def test_naming_conventions(self):
|
||||
# Asserts market-standard car naming conventions by brand
|
||||
for car in self.all_cars:
|
||||
with subtests.test(car=car.name):
|
||||
with self.subTest(car=car.name):
|
||||
tokens = car.model.lower().split(" ")
|
||||
if car.brand == "hyundai":
|
||||
assert "phev" not in tokens, "Use `Plug-in Hybrid`"
|
||||
@@ -47,29 +47,29 @@ class TestCarDocs:
|
||||
if "rav4" in tokens:
|
||||
assert "RAV4" in car.model, "Use correct capitalization"
|
||||
|
||||
def test_torque_star(self, subtests):
|
||||
def test_torque_star(self):
|
||||
# Asserts brand-specific assumptions around steering torque star
|
||||
for car in self.all_cars:
|
||||
with subtests.test(car=car.name):
|
||||
with self.subTest(car=car.name):
|
||||
# honda sanity check, it's the definition of a no torque star
|
||||
if car.car_fingerprint in (HONDA.HONDA_ACCORD, HONDA.HONDA_CIVIC, HONDA.HONDA_CRV, HONDA.HONDA_ODYSSEY, HONDA.HONDA_ODYSSEY_TWN, HONDA.HONDA_PILOT):
|
||||
assert car.row[Column.STEERING_TORQUE] == Star.EMPTY, f"{car.name} has full torque star"
|
||||
elif car.brand in ("toyota", "hyundai"):
|
||||
assert car.row[Column.STEERING_TORQUE] != Star.EMPTY, f"{car.name} has no torque star"
|
||||
|
||||
def test_year_format(self, subtests):
|
||||
def test_year_format(self):
|
||||
for car in self.all_cars:
|
||||
with subtests.test(car=car.name):
|
||||
with self.subTest(car=car.name):
|
||||
if car.name == "comma body":
|
||||
pytest.skip()
|
||||
raise unittest.SkipTest
|
||||
|
||||
assert car.years and car.year_list, f"Format years correctly: {car.name}"
|
||||
|
||||
def test_harnesses(self, subtests):
|
||||
def test_harnesses(self):
|
||||
for car in self.all_cars:
|
||||
with subtests.test(car=car.name):
|
||||
with self.subTest(car=car.name):
|
||||
if car.name == "comma body" or car.support_type != SupportType.UPSTREAM:
|
||||
pytest.skip()
|
||||
raise unittest.SkipTest
|
||||
|
||||
car_part_type = [p.part_type for p in car.car_parts.all_parts()]
|
||||
car_parts = list(car.car_parts.all_parts())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import random
|
||||
import time
|
||||
from collections import defaultdict
|
||||
@@ -10,6 +11,7 @@ from opendbc.car.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, \
|
||||
match_fw_to_car, get_brand_ecu_matches, get_fw_versions, get_present_ecus
|
||||
from opendbc.car.vin import get_vin
|
||||
from opendbc.testing import parameterized
|
||||
|
||||
CarFw = CarParams.CarFw
|
||||
Ecu = CarParams.Ecu
|
||||
@@ -17,14 +19,14 @@ Ecu = CarParams.Ecu
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
|
||||
class TestFwFingerprint:
|
||||
class TestFwFingerprint(unittest.TestCase):
|
||||
def assertFingerprints(self, candidates, expected):
|
||||
candidates = list(candidates)
|
||||
assert len(candidates) == 1, f"got more than one candidate: {candidates}"
|
||||
assert candidates[0] == expected
|
||||
|
||||
@pytest.mark.parametrize("brand, car_model, ecus, test_non_essential",
|
||||
[(b, c, e[c], n) for b, e in VERSIONS.items() for c in e for n in (True, False)])
|
||||
@parameterized("brand, car_model, ecus, test_non_essential",
|
||||
[(b, c, e[c], n) for b, e in VERSIONS.items() for c in e for n in (True, False)])
|
||||
def test_exact_match(self, brand, car_model, ecus, test_non_essential):
|
||||
config = FW_QUERY_CONFIGS[brand]
|
||||
CP = CarParams()
|
||||
@@ -48,12 +50,12 @@ class TestFwFingerprint:
|
||||
if len(matches) != 0:
|
||||
self.assertFingerprints(matches, car_model)
|
||||
|
||||
@pytest.mark.parametrize("brand, car_model, ecus", [(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
|
||||
@parameterized("brand, car_model, ecus", [(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
|
||||
def test_custom_fuzzy_match(self, brand, car_model, ecus):
|
||||
# Assert brand-specific fuzzy fingerprinting function doesn't disagree with standard fuzzy function
|
||||
config = FW_QUERY_CONFIGS[brand]
|
||||
if config.match_fw_to_car_fuzzy is None:
|
||||
pytest.skip("Brand does not implement custom fuzzy fingerprinting function")
|
||||
raise unittest.SkipTest("Brand does not implement custom fuzzy fingerprinting function")
|
||||
|
||||
CP = CarParams()
|
||||
for _ in range(5):
|
||||
@@ -70,12 +72,12 @@ class TestFwFingerprint:
|
||||
if len(matches) == 1 and len(brand_matches) == 1:
|
||||
assert matches == brand_matches
|
||||
|
||||
@pytest.mark.parametrize("brand, car_model, ecus", [(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
|
||||
@parameterized("brand, car_model, ecus", [(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
|
||||
def test_fuzzy_match_ecu_count(self, brand, car_model, ecus):
|
||||
# Asserts that fuzzy matching does not count matching FW, but ECU address keys
|
||||
valid_ecus = [e for e in ecus if e[0] not in FUZZY_EXCLUDE_ECUS]
|
||||
if not len(valid_ecus):
|
||||
pytest.skip("Car model has no compatible ECUs for fuzzy matching")
|
||||
raise unittest.SkipTest("Car model has no compatible ECUs for fuzzy matching")
|
||||
|
||||
fw = []
|
||||
for ecu in valid_ecus:
|
||||
@@ -95,11 +97,11 @@ class TestFwFingerprint:
|
||||
elif len(matches):
|
||||
self.assertFingerprints(matches, car_model)
|
||||
|
||||
def test_fw_version_lists(self, subtests):
|
||||
def test_fw_version_lists(self):
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, ecu_fw in ecus.items():
|
||||
with subtests.test(ecu):
|
||||
with self.subTest(ecu):
|
||||
duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1}
|
||||
assert not len(duplicates), f'{car_model}: Duplicate FW versions: Ecu.{ecu[0]}, {duplicates}'
|
||||
assert len(ecu_fw) > 0, f'{car_model}: No FW versions: Ecu.{ecu[0]}'
|
||||
@@ -114,18 +116,18 @@ class TestFwFingerprint:
|
||||
ecu_strings = ", ".join([f'Ecu.{ecu}' for ecu in ecus_for_addr])
|
||||
assert len(ecus_for_addr) <= 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})"
|
||||
|
||||
def test_data_collection_ecus(self, subtests):
|
||||
def test_data_collection_ecus(self):
|
||||
# Asserts no extra ECUs are in the fingerprinting database
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
for car_model, ecus in VERSIONS[brand].items():
|
||||
bad_ecus = set(ecus).intersection(config.extra_ecus)
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
assert not len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}'
|
||||
|
||||
def test_blacklisted_ecus(self, subtests):
|
||||
def test_blacklisted_ecus(self):
|
||||
blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
with self.subTest(car_model=car_model.value):
|
||||
CP = interfaces[car_model].get_non_essential_params(car_model)
|
||||
if CP.brand == 'subaru':
|
||||
for ecu in ecus.keys():
|
||||
@@ -137,16 +139,16 @@ class TestFwFingerprint:
|
||||
for ecu in ecus.keys():
|
||||
assert ecu[0] != Ecu.transmission, f"{car_model}: Blacklisted ecu: (Ecu.{ecu[0]}, {hex(ecu[1])})"
|
||||
|
||||
def test_missing_versions_and_configs(self, subtests):
|
||||
def test_missing_versions_and_configs(self):
|
||||
brand_versions = set(VERSIONS.keys())
|
||||
brand_configs = set(FW_QUERY_CONFIGS.keys())
|
||||
if len(brand_configs - brand_versions):
|
||||
with subtests.test():
|
||||
pytest.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}")
|
||||
with self.subTest():
|
||||
self.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}")
|
||||
|
||||
if len(brand_versions - brand_configs):
|
||||
with subtests.test():
|
||||
pytest.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}")
|
||||
with self.subTest():
|
||||
self.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}")
|
||||
|
||||
# Ensure each brand has at least 1 ECU to query, and extra ECU retrieval
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
@@ -155,9 +157,9 @@ class TestFwFingerprint:
|
||||
if len(VERSIONS[brand]) > 0:
|
||||
assert len(config.get_all_ecus(VERSIONS[brand])) > 0
|
||||
|
||||
def test_fw_request_ecu_whitelist(self, subtests):
|
||||
def test_fw_request_ecu_whitelist(self):
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with subtests.test(brand=brand):
|
||||
with self.subTest(brand=brand):
|
||||
whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus}
|
||||
brand_ecus = {fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw}
|
||||
brand_ecus |= {ecu[0] for ecu in config.extra_ecus}
|
||||
@@ -189,7 +191,7 @@ class TestFwFingerprint:
|
||||
assert not any(any(e) for b, e in brand_matches.items() if b != 'toyota')
|
||||
|
||||
|
||||
class TestFwFingerprintTiming:
|
||||
class TestFwFingerprintTiming(unittest.TestCase):
|
||||
N: int = 5
|
||||
TOL: float = 0.05
|
||||
|
||||
@@ -216,16 +218,16 @@ class TestFwFingerprintTiming:
|
||||
self.total_time += timeout
|
||||
return {}
|
||||
|
||||
def _benchmark_brand(self, brand, num_pandas, mocker):
|
||||
def _benchmark_brand(self, brand, num_pandas):
|
||||
self.total_time = 0
|
||||
mocker.patch("opendbc.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)
|
||||
for _ in range(self.N):
|
||||
# Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on
|
||||
self.current_obd_multiplexing = True
|
||||
with patch("opendbc.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data):
|
||||
for _ in range(self.N):
|
||||
# Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on
|
||||
self.current_obd_multiplexing = True
|
||||
|
||||
t = time.perf_counter()
|
||||
get_fw_versions(self.fake_can_recv, self.fake_can_send, self.fake_set_obd_multiplexing, brand, num_pandas=num_pandas)
|
||||
self.total_time += time.perf_counter() - t
|
||||
t = time.perf_counter()
|
||||
get_fw_versions(self.fake_can_recv, self.fake_can_send, self.fake_set_obd_multiplexing, brand, num_pandas=num_pandas)
|
||||
self.total_time += time.perf_counter() - t
|
||||
|
||||
return self.total_time / self.N
|
||||
|
||||
@@ -233,7 +235,7 @@ class TestFwFingerprintTiming:
|
||||
assert avg_time < ref_time + self.TOL
|
||||
assert avg_time > ref_time - self.TOL, "Performance seems to have improved, update test refs."
|
||||
|
||||
def test_startup_timing(self, subtests, mocker):
|
||||
def test_startup_timing(self):
|
||||
# Tests worse-case VIN query time and typical present ECU query time
|
||||
vin_ref_times = {'worst': 1.6, 'best': 0.8} # best assumes we go through all queries to get a match
|
||||
present_ecu_ref_time = 0.45
|
||||
@@ -243,23 +245,23 @@ class TestFwFingerprintTiming:
|
||||
return set()
|
||||
|
||||
self.total_time = 0.0
|
||||
mocker.patch("opendbc.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs)
|
||||
for _ in range(self.N):
|
||||
self.current_obd_multiplexing = True
|
||||
get_present_ecus(self.fake_can_recv, self.fake_can_send, self.fake_set_obd_multiplexing, num_pandas=2)
|
||||
with patch("opendbc.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs):
|
||||
for _ in range(self.N):
|
||||
self.current_obd_multiplexing = True
|
||||
get_present_ecus(self.fake_can_recv, self.fake_can_send, self.fake_set_obd_multiplexing, num_pandas=2)
|
||||
self._assert_timing(self.total_time / self.N, present_ecu_ref_time)
|
||||
print(f'get_present_ecus, query time={self.total_time / self.N} seconds')
|
||||
|
||||
for name, args in (('worst', {}), ('best', {'retry': 1})):
|
||||
with subtests.test(name=name):
|
||||
with self.subTest(name=name):
|
||||
self.total_time = 0.0
|
||||
mocker.patch("opendbc.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)
|
||||
for _ in range(self.N):
|
||||
get_vin(self.fake_can_recv, self.fake_can_send, (0, 1), **args)
|
||||
with patch("opendbc.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data):
|
||||
for _ in range(self.N):
|
||||
get_vin(self.fake_can_recv, self.fake_can_send, (0, 1), **args)
|
||||
self._assert_timing(self.total_time / self.N, vin_ref_times[name])
|
||||
print(f'get_vin {name} case, query time={self.total_time / self.N} seconds')
|
||||
|
||||
def test_fw_query_timing(self, subtests, mocker):
|
||||
def test_fw_query_timing(self):
|
||||
total_ref_time = {1: 7.4, 2: 8.0}
|
||||
brand_ref_times = {
|
||||
1: {
|
||||
@@ -287,8 +289,8 @@ class TestFwFingerprintTiming:
|
||||
total_times = {1: 0.0, 2: 0.0}
|
||||
for num_pandas in (1, 2):
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with subtests.test(brand=brand, num_pandas=num_pandas):
|
||||
avg_time = self._benchmark_brand(brand, num_pandas, mocker)
|
||||
with self.subTest(brand=brand, num_pandas=num_pandas):
|
||||
avg_time = self._benchmark_brand(brand, num_pandas)
|
||||
total_times[num_pandas] += avg_time
|
||||
avg_time = round(avg_time, 2)
|
||||
|
||||
@@ -301,12 +303,12 @@ class TestFwFingerprintTiming:
|
||||
print(f'{brand=}, {num_pandas=}, {len(config.requests)=}, avg FW query time={avg_time} seconds')
|
||||
|
||||
for num_pandas in (1, 2):
|
||||
with subtests.test(brand='all_brands', num_pandas=num_pandas):
|
||||
with self.subTest(brand='all_brands', num_pandas=num_pandas):
|
||||
total_time = round(total_times[num_pandas], 2)
|
||||
self._assert_timing(total_time, total_ref_time[num_pandas])
|
||||
print(f'all brands, total FW query time={total_time} seconds')
|
||||
|
||||
def test_get_fw_versions(self, subtests, mocker):
|
||||
def test_get_fw_versions(self):
|
||||
# some coverage on IsoTpParallelQuery and panda UDS library
|
||||
# TODO: replace this with full fingerprint simulation testing
|
||||
# https://github.com/commaai/panda/pull/1329
|
||||
@@ -321,8 +323,8 @@ class TestFwFingerprintTiming:
|
||||
t += 0.0001
|
||||
return t
|
||||
|
||||
mocker.patch("opendbc.car.carlog.carlog.exception", fake_carlog_exception)
|
||||
mocker.patch("time.monotonic", fake_monotonic)
|
||||
for brand in FW_QUERY_CONFIGS.keys():
|
||||
with subtests.test(brand=brand):
|
||||
get_fw_versions(self.fake_can_recv, self.fake_can_send, lambda obd: None, brand)
|
||||
with patch("opendbc.car.carlog.carlog.exception", fake_carlog_exception), \
|
||||
patch("time.monotonic", fake_monotonic):
|
||||
for brand in FW_QUERY_CONFIGS.keys():
|
||||
with self.subTest(brand=brand):
|
||||
get_fw_versions(self.fake_can_recv, self.fake_can_send, lambda obd: None, brand)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
import unittest
|
||||
import importlib
|
||||
from opendbc.testing import parameterized_class
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
@@ -21,23 +19,26 @@ JERK_MEAS_T = 0.5
|
||||
|
||||
|
||||
@parameterized_class('car_model', [(c,) for c in sorted(PLATFORMS)])
|
||||
class TestLateralLimits:
|
||||
class TestLateralLimits(unittest.TestCase):
|
||||
car_model: str
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
def setUpClass(cls):
|
||||
if 'car_model' not in cls.__dict__:
|
||||
raise unittest.SkipTest('Base class')
|
||||
|
||||
CarInterface = interfaces[cls.car_model]
|
||||
CP = CarInterface.get_non_essential_params(cls.car_model)
|
||||
|
||||
if cls.car_model == 'MOCK':
|
||||
pytest.skip('Mock car')
|
||||
raise unittest.SkipTest('Mock car')
|
||||
|
||||
# TODO: test all platforms
|
||||
if CP.steerControlType != 'torque':
|
||||
pytest.skip()
|
||||
raise unittest.SkipTest
|
||||
|
||||
if CP.notCar:
|
||||
pytest.skip()
|
||||
raise unittest.SkipTest
|
||||
|
||||
CarControllerParams = importlib.import_module(f'opendbc.car.{CP.brand}.values').CarControllerParams
|
||||
cls.control_params = CarControllerParams(CP)
|
||||
@@ -66,31 +67,3 @@ class TestLateralLimits:
|
||||
|
||||
def test_max_lateral_accel(self):
|
||||
assert self.torque_params["MAX_LAT_ACCEL_MEASURED"] <= ISO_LATERAL_ACCEL
|
||||
|
||||
|
||||
class LatAccelReport:
|
||||
car_model_jerks: defaultdict[str, dict[str, float]] = defaultdict(dict)
|
||||
|
||||
def pytest_sessionfinish(self):
|
||||
print(f"\n\n---- Lateral limit report ({len(PLATFORMS)} cars) ----\n")
|
||||
|
||||
max_car_model_len = max([len(car_model) for car_model in self.car_model_jerks])
|
||||
for car_model, _jerks in sorted(self.car_model_jerks.items(), key=lambda i: i[1]['up_jerk'], reverse=True):
|
||||
violation = _jerks["up_jerk"] > MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE or \
|
||||
_jerks["down_jerk"] > MAX_LAT_JERK_DOWN
|
||||
violation_str = " - VIOLATION" if violation else ""
|
||||
|
||||
print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} " +
|
||||
f"m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}")
|
||||
|
||||
@pytest.fixture(scope="class", autouse=True)
|
||||
def class_setup(self, request):
|
||||
yield
|
||||
cls = request.cls
|
||||
if hasattr(cls, "control_params"):
|
||||
up_jerk, down_jerk = TestLateralLimits.calculate_0_5s_jerk(cls.control_params, cls.torque_params)
|
||||
self.car_model_jerks[cls.car_model] = {"up_jerk": up_jerk, "down_jerk": down_jerk}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(pytest.main([__file__, '-n0', '--no-summary'], plugins=[LatAccelReport()])) # noqa: TID251
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import unittest
|
||||
from opendbc.car.values import PLATFORMS
|
||||
|
||||
|
||||
class TestPlatformConfigs:
|
||||
def test_configs(self, subtests):
|
||||
class TestPlatformConfigs(unittest.TestCase):
|
||||
def test_configs(self):
|
||||
|
||||
for name, platform in PLATFORMS.items():
|
||||
with subtests.test(platform=str(platform)):
|
||||
with self.subTest(platform=str(platform)):
|
||||
assert platform.config._frozen
|
||||
|
||||
if platform != "MOCK":
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from opendbc.car.values import PLATFORMS
|
||||
from opendbc.car.tests.routes import non_tested_cars, routes
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", PLATFORMS.keys())
|
||||
def test_test_route_present(platform):
|
||||
tested_platforms = [r.car_model for r in routes]
|
||||
assert platform in set(tested_platforms) | set(non_tested_cars), \
|
||||
f"Missing test route for {platform}. Add a route to opendbc/car/tests/routes.py"
|
||||
class TestRoutes(unittest.TestCase):
|
||||
def test_test_route_present(self):
|
||||
tested_platforms = [r.car_model for r in routes]
|
||||
for platform in PLATFORMS.keys():
|
||||
with self.subTest(platform=platform):
|
||||
assert platform in set(tested_platforms) | set(non_tested_cars), \
|
||||
f"Missing test route for {platform}. Add a route to opendbc/car/tests/routes.py"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
@@ -8,8 +8,8 @@ from opendbc.car.honda.values import CAR
|
||||
from opendbc.car.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices
|
||||
|
||||
|
||||
class TestVehicleModel:
|
||||
def setup_method(self):
|
||||
class TestVehicleModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
|
||||
self.VM = VehicleModel(CP)
|
||||
|
||||
@@ -21,7 +21,7 @@ class TestVehicleModel:
|
||||
yr = self.VM.yaw_rate(sa, u, roll)
|
||||
new_sa = self.VM.get_steer_from_yaw_rate(yr, u, roll)
|
||||
|
||||
assert sa == pytest.approx(new_sa)
|
||||
self.assertAlmostEqual(sa, new_sa, places=10)
|
||||
|
||||
def test_dyn_ss_sol_against_yaw_rate(self):
|
||||
"""Verify that the yaw_rate helper function matches the results
|
||||
@@ -36,7 +36,7 @@ class TestVehicleModel:
|
||||
|
||||
# Compute yaw rate using direct computations
|
||||
yr2 = self.VM.yaw_rate(sa, u, roll)
|
||||
assert float(yr1[0]) == pytest.approx(yr2)
|
||||
self.assertAlmostEqual(float(yr1[0]), yr2, places=10)
|
||||
|
||||
def test_syn_ss_sol_simulate(self):
|
||||
"""Verifies that dyn_ss_sol matches a simulation"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import random
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from opendbc.car import DT_CTRL
|
||||
from opendbc.car.structs import CarParams
|
||||
@@ -14,7 +15,7 @@ CHASSIS_CODE_PATTERN = re.compile('[A-Z0-9]{2}')
|
||||
SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P<gateway>[0-9][0-9A-Z]{2})(?P<unknown>[0-9][0-9A-Z][0-9])(?P<unknown2>[0-9A-Z]{2}[0-9])([A-Z0-9]| )')
|
||||
|
||||
|
||||
class TestVolkswagenHCAMitigation:
|
||||
class TestVolkswagenHCAMitigation(unittest.TestCase):
|
||||
STUCK_TORQUE_FRAMES = round(CCP.STEER_TIME_STUCK_TORQUE / (DT_CTRL * CCP.STEER_STEP))
|
||||
|
||||
def test_same_torque_mitigation(self):
|
||||
@@ -28,18 +29,18 @@ class TestVolkswagenHCAMitigation:
|
||||
expected_torque = actuator_value - (1, -1)[actuator_value < 0] if should_nudge else actuator_value
|
||||
assert hca_mitigation.update(actuator_value, actuator_value) == expected_torque, f"{frame=}"
|
||||
|
||||
class TestVolkswagenPlatformConfigs:
|
||||
def test_spare_part_fw_pattern(self, subtests):
|
||||
class TestVolkswagenPlatformConfigs(unittest.TestCase):
|
||||
def test_spare_part_fw_pattern(self):
|
||||
# Relied on for determining if a FW is likely VW
|
||||
for platform, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(platform=platform.value):
|
||||
with self.subTest(platform=platform.value):
|
||||
for fws in ecus.values():
|
||||
for fw in fws:
|
||||
assert SPARE_PART_FW_PATTERN.match(fw) is not None, f"Bad FW: {fw}"
|
||||
|
||||
def test_chassis_codes(self, subtests):
|
||||
def test_chassis_codes(self):
|
||||
for platform in CAR:
|
||||
with subtests.test(platform=platform.value):
|
||||
with self.subTest(platform=platform.value):
|
||||
assert len(platform.config.wmis) > 0, "WMIs not set"
|
||||
assert len(platform.config.chassis_codes) > 0, "Chassis codes not set"
|
||||
assert all(CHASSIS_CODE_PATTERN.match(cc) for cc in
|
||||
@@ -52,11 +53,11 @@ class TestVolkswagenPlatformConfigs:
|
||||
assert set() == platform.config.chassis_codes & comp.config.chassis_codes, \
|
||||
f"Shared chassis codes: {comp}"
|
||||
|
||||
def test_custom_fuzzy_fingerprinting(self, subtests):
|
||||
def test_custom_fuzzy_fingerprinting(self):
|
||||
all_radar_fw = list({fw for ecus in FW_VERSIONS.values() for fw in ecus[Ecu.fwdRadar, 0x757, None]})
|
||||
|
||||
for platform in CAR:
|
||||
with subtests.test(platform=platform.name):
|
||||
with self.subTest(platform=platform.name):
|
||||
for wmi in WMI:
|
||||
for chassis_code in platform.config.chassis_codes | {"00"}:
|
||||
vin = ["0"] * 17
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import glob
|
||||
import pytest
|
||||
import unittest
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -46,24 +46,26 @@ for p in patterns:
|
||||
mutations = [mutations[0]] + rng.sample(mutations[1:], min(2, len(mutations) - 1))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fn, rule, transform, should_fail", mutations)
|
||||
def test_misra_mutation(fn, rule, transform, should_fail):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
shutil.copytree(ROOT, tmp, dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns('.venv', '.git', '*.ctu-info', '.hypothesis'))
|
||||
class TestMisraMutation(unittest.TestCase):
|
||||
def test_misra_mutation(self):
|
||||
for fn, rule, transform, should_fail in mutations:
|
||||
with self.subTest(fn=fn, rule=rule, should_fail=should_fail):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
shutil.copytree(ROOT, tmp, dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns('.venv', '.git', '*.ctu-info', '.hypothesis'))
|
||||
|
||||
# apply patch
|
||||
if fn is not None:
|
||||
with open(os.path.join(tmp, fn), 'r+') as f:
|
||||
content = f.read()
|
||||
f.seek(0)
|
||||
f.write(transform(content))
|
||||
# apply patch
|
||||
if fn is not None:
|
||||
with open(os.path.join(tmp, fn), 'r+') as f:
|
||||
content = f.read()
|
||||
f.seek(0)
|
||||
f.write(transform(content))
|
||||
|
||||
# run test
|
||||
r = subprocess.run(f"OPENDBC_ROOT={tmp} opendbc/safety/tests/misra/test_misra.sh",
|
||||
stdout=subprocess.PIPE, cwd=ROOT, shell=True, encoding='utf8')
|
||||
print(r.stdout) # helpful for debugging failures
|
||||
failed = r.returncode != 0
|
||||
assert failed == should_fail
|
||||
if should_fail:
|
||||
assert rule in r.stdout, "MISRA test failed but not for the correct violation"
|
||||
# run test
|
||||
r = subprocess.run(f"OPENDBC_ROOT={tmp} opendbc/safety/tests/misra/test_misra.sh",
|
||||
stdout=subprocess.PIPE, cwd=ROOT, shell=True, encoding='utf8')
|
||||
print(r.stdout) # helpful for debugging failures
|
||||
failed = r.returncode != 0
|
||||
assert failed == should_fail
|
||||
if should_fail:
|
||||
assert rule in r.stdout, "MISRA test failed but not for the correct violation"
|
||||
|
||||
@@ -11,7 +11,7 @@ rm -f ./libsafety/*.gcda
|
||||
scons -j$(nproc) -D
|
||||
|
||||
# run safety tests and generate coverage data
|
||||
pytest -n8 --ignore-glob=misra/*
|
||||
python -m unittest discover -s . -p 'test_*.py' -t ../../../
|
||||
|
||||
# NOTE: we accept that these tools will have slight differences,
|
||||
# and in return, we get to use the stock toolchain instead of
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
import functools
|
||||
import sys
|
||||
|
||||
|
||||
def parameterized(argnames, argvalues):
|
||||
"""Method decorator that runs a test once per parameter set using subTest.
|
||||
|
||||
Usage:
|
||||
@parameterized("x, y", [(1, 2), (3, 4)])
|
||||
def test_add(self, x, y): ...
|
||||
|
||||
@parameterized("car_model, fingerprints", FINGERPRINTS.items())
|
||||
def test_fw(self, car_model, fingerprints): ...
|
||||
"""
|
||||
if isinstance(argnames, str):
|
||||
argnames = [a.strip() for a in argnames.split(',')]
|
||||
|
||||
def decorator(func):
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(self):
|
||||
for values in argvalues:
|
||||
if not isinstance(values, (tuple, list)):
|
||||
values = (values,)
|
||||
kwargs = dict(zip(argnames, values, strict=True))
|
||||
with self.subTest(**kwargs):
|
||||
func(self, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def parameterized_class(attrs, values=None):
|
||||
"""Class decorator that generates subclasses with different class attributes.
|
||||
|
||||
|
||||
@@ -24,14 +24,8 @@ testing = [
|
||||
"tree-sitter",
|
||||
"tree-sitter-c",
|
||||
"gcovr",
|
||||
# FIXME: pytest 9.0.0 doesn't support unittest.SkipTest
|
||||
"pytest==8.4.2",
|
||||
"pytest-mock",
|
||||
# https://github.com/pytest-dev/pytest-xdist/pull/1229
|
||||
"pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da",
|
||||
"pytest-subtests",
|
||||
"unittest-parallel",
|
||||
"hypothesis==6.47.*",
|
||||
"parameterized>=0.8,<0.9",
|
||||
"zstandard",
|
||||
|
||||
# static analysis
|
||||
@@ -53,13 +47,6 @@ examples = [
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-Werror --strict-config --strict-markers --durations=10 -n auto --ignore-glob=opendbc/safety/tests/misra/*.sh"
|
||||
python_files = "test_*.py"
|
||||
testpaths = [
|
||||
"opendbc"
|
||||
]
|
||||
|
||||
[tool.codespell]
|
||||
quiet-level = 3
|
||||
ignore-words-list = "alo,ba,bu,deque,hda,grey,arange"
|
||||
@@ -107,10 +94,7 @@ flake8-implicit-str-concat.allow-multiline=false
|
||||
"site_scons/*" = ["ALL"]
|
||||
|
||||
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
||||
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
|
||||
"numpy.mean".msg = "Sum and divide. np.mean is slow"
|
||||
# TODO: re-enable when all tests are converted to pytest
|
||||
#"unittest".msg = "Use pytest"
|
||||
|
||||
[tool.ty.rules]
|
||||
# Ignore rules that produce false positives due to:
|
||||
|
||||
158
uv.lock
generated
158
uv.lock
generated
@@ -141,6 +141,45 @@ wheels = [
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "requests" }]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.13.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cppcheck"
|
||||
version = "2.16.0"
|
||||
@@ -155,15 +194,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/66/65/08d3a5039b565231c501b31d1a973d4222e9803c03b2c31a9c08bdec3e30/cpplint-2.0.2-py3-none-any.whl", hash = "sha256:7ec188b5a08e604294ae7e7f88ec3ece2699de857f0533b305620c8cf237cad5", size = 81987, upload-time = "2025-04-08T01:22:24.101Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gcovr"
|
||||
version = "8.6"
|
||||
@@ -201,15 +231,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inputs"
|
||||
version = "0.5"
|
||||
@@ -383,15 +404,11 @@ testing = [
|
||||
{ name = "gcovr" },
|
||||
{ name = "hypothesis" },
|
||||
{ name = "lefthook" },
|
||||
{ name = "parameterized" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-mock" },
|
||||
{ name = "pytest-subtests" },
|
||||
{ name = "pytest-xdist" },
|
||||
{ name = "ruff" },
|
||||
{ name = "tree-sitter" },
|
||||
{ name = "tree-sitter-c" },
|
||||
{ name = "ty" },
|
||||
{ name = "unittest-parallel" },
|
||||
{ name = "zstandard" },
|
||||
]
|
||||
|
||||
@@ -408,50 +425,19 @@ requires-dist = [
|
||||
{ name = "jinja2", marker = "extra == 'docs'" },
|
||||
{ name = "lefthook", marker = "extra == 'testing'" },
|
||||
{ name = "numpy" },
|
||||
{ name = "parameterized", marker = "extra == 'testing'", specifier = ">=0.8,<0.9" },
|
||||
{ name = "pycapnp", specifier = "==2.1.0" },
|
||||
{ name = "pycryptodome" },
|
||||
{ name = "pytest", marker = "extra == 'testing'", specifier = "==8.4.2" },
|
||||
{ name = "pytest-mock", marker = "extra == 'testing'" },
|
||||
{ name = "pytest-subtests", marker = "extra == 'testing'" },
|
||||
{ name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" },
|
||||
{ name = "ruff", marker = "extra == 'testing'" },
|
||||
{ name = "scons" },
|
||||
{ name = "tqdm" },
|
||||
{ name = "tree-sitter", marker = "extra == 'testing'" },
|
||||
{ name = "tree-sitter-c", marker = "extra == 'testing'" },
|
||||
{ name = "ty", marker = "extra == 'testing'" },
|
||||
{ name = "unittest-parallel", marker = "extra == 'testing'" },
|
||||
{ name = "zstandard", marker = "extra == 'testing'" },
|
||||
]
|
||||
provides-extras = ["testing", "docs", "examples"]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parameterized"
|
||||
version = "0.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/23/2288f308d238b4f261c039cafcd650435d624de97c6ffc903f06ea8af50f/parameterized-0.8.1.tar.gz", hash = "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c", size = 23936, upload-time = "2021-01-09T20:35:18.235Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/13/fe468c8c7400a8eca204e6e160a29bf7dcd45a76e20f1c030f3eaa690d93/parameterized-0.8.1-py2.py3-none-any.whl", hash = "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9", size = 26354, upload-time = "2021-01-09T20:35:16.307Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycapnp"
|
||||
version = "2.1.0"
|
||||
@@ -521,56 +507,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-mock"
|
||||
version = "3.15.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-subtests"
|
||||
version = "0.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.7.1.dev24+g2b4372bd6"
|
||||
source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }
|
||||
dependencies = [
|
||||
{ name = "execnet" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
@@ -702,6 +638,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/4f/5dd60904c8105cda4d0be34d3a446c180933c76b84ae0742e58f02133713/ty-0.0.18-py3-none-win_arm64.whl", hash = "sha256:01770c3c82137c6b216aa3251478f0b197e181054ee92243772de553d3586398", size = 10095449, upload-time = "2026-02-20T21:51:34.914Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unittest-parallel"
|
||||
version = "1.7.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/26/b15a0337182988748210c2bcee60a780fe10057ceb23da2547ec29a1d443/unittest_parallel-1.7.6.tar.gz", hash = "sha256:b16bf52bec7b900b8fc7945de97c45f87d50025ac06c1a64e35e91c278756dfc", size = 9834, upload-time = "2025-12-01T19:17:36.599Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/9f/3a7d6077488e977a6da79bf51d182dd0ea441d0bb542f443d13d1806dc95/unittest_parallel-1.7.6-py3-none-any.whl", hash = "sha256:c55eff2d1f5806ec272a0f7c7ed5309197ae4550ee37cd28d3d0864a32981bfe", size = 9260, upload-time = "2025-12-01T19:17:34.849Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
|
||||
Reference in New Issue
Block a user