panda: add unit tests for usb protocol (pack/unpack) (#22955)
* prepare for unit tests
* add to selfdrive_tests.yaml
* test header
* test chunk count
* rename test function
* continue
* don't check chunks count
* test recv_can
* continue
* small cleanup
* merge master
* cleanup
* rename functions
* test different packet size
* fix operator precedence problem
* refactor unpack_can_buffer
* cleanup test
* cleanup unpack_can_buffer
* add test for multiple pandas
* rename to test_panda
* restore test_boardd
* rename to test_boardd_usbprotocol
* fix typo
* bus_offset = [0,4]
* change src
* use USBPACKET_MAX_SIZE
old-commit-hash: c77354009c
commatwo_master
parent
987f87cc0e
commit
a919e5721e
6 changed files with 144 additions and 5 deletions
@ -1,2 +1,3 @@ |
|||||||
boardd |
boardd |
||||||
boardd_api_impl.cpp |
boardd_api_impl.cpp |
||||||
|
tests/test_boardd_usbprotocol |
||||||
|
@ -1,6 +1,9 @@ |
|||||||
Import('env', 'envCython', 'common', 'cereal', 'messaging') |
Import('env', 'envCython', 'common', 'cereal', 'messaging') |
||||||
|
|
||||||
env.Program('boardd', ['boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj']) |
libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] |
||||||
|
env.Program('boardd', ['boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=libs) |
||||||
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) |
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) |
||||||
|
|
||||||
envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) |
envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) |
||||||
|
if GetOption('test'): |
||||||
|
env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc'], LIBS=libs) |
||||||
|
@ -0,0 +1,115 @@ |
|||||||
|
#define CATCH_CONFIG_MAIN |
||||||
|
#define CATCH_CONFIG_ENABLE_BENCHMARKING |
||||||
|
#include <random> |
||||||
|
|
||||||
|
#include "catch2/catch.hpp" |
||||||
|
#include "cereal/messaging/messaging.h" |
||||||
|
#include "selfdrive/boardd/panda.h" |
||||||
|
|
||||||
|
const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; |
||||||
|
|
||||||
|
int random_int(int min, int max) { |
||||||
|
std::random_device dev; |
||||||
|
std::mt19937 rng(dev()); |
||||||
|
std::uniform_int_distribution<std::mt19937::result_type> dist(min, max); |
||||||
|
return dist(rng); |
||||||
|
} |
||||||
|
|
||||||
|
struct PandaTest : public Panda { |
||||||
|
PandaTest(uint32_t bus_offset, int can_list_size); |
||||||
|
void test_can_send(); |
||||||
|
void test_can_recv(); |
||||||
|
|
||||||
|
std::map<int, std::string> test_data; |
||||||
|
int can_list_size = 0; |
||||||
|
int total_pakets_size = 0; |
||||||
|
MessageBuilder msg; |
||||||
|
capnp::List<cereal::CanData>::Reader can_data_list; |
||||||
|
}; |
||||||
|
|
||||||
|
PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size) : can_list_size(can_list_size), Panda(bus_offset_) { |
||||||
|
// prepare test data
|
||||||
|
for (int i = 0; i < std::size(dlc_to_len); ++i) { |
||||||
|
std::random_device rd; |
||||||
|
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> rbe(rd()); |
||||||
|
|
||||||
|
int data_len = dlc_to_len[i]; |
||||||
|
std::string bytes(data_len, '\0'); |
||||||
|
std::generate(bytes.begin(), bytes.end(), std::ref(rbe)); |
||||||
|
test_data[data_len] = bytes; |
||||||
|
} |
||||||
|
|
||||||
|
// generate can messages for this panda
|
||||||
|
auto can_list = msg.initEvent().initSendcan(can_list_size); |
||||||
|
for (uint8_t i = 0; i < can_list_size; ++i) { |
||||||
|
auto can = can_list[i]; |
||||||
|
uint32_t id = random_int(0, std::size(dlc_to_len) - 1); |
||||||
|
const std::string &dat = test_data[dlc_to_len[id]]; |
||||||
|
can.setAddress(i); |
||||||
|
can.setSrc(random_int(0, 3) + bus_offset); |
||||||
|
can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); |
||||||
|
total_pakets_size += CANPACKET_HEAD_SIZE + dat.size(); |
||||||
|
} |
||||||
|
|
||||||
|
can_data_list = can_list.asReader(); |
||||||
|
INFO("test " << can_list_size << " packets, total size " << total_pakets_size); |
||||||
|
} |
||||||
|
|
||||||
|
void PandaTest::test_can_send() { |
||||||
|
std::vector<uint8_t> unpacked_data; |
||||||
|
this->pack_can_buffer(can_data_list, [&](uint8_t *chunk, size_t size) { |
||||||
|
int size_left = size; |
||||||
|
for (int i = 0, counter = 0; i < size; i += USBPACKET_MAX_SIZE, counter++) { |
||||||
|
REQUIRE(chunk[i] == counter); |
||||||
|
|
||||||
|
const int len = std::min(USBPACKET_MAX_SIZE, (uint32_t)size_left); |
||||||
|
unpacked_data.insert(unpacked_data.end(), &chunk[i + 1], &chunk[i + len]); |
||||||
|
size_left -= len; |
||||||
|
} |
||||||
|
}); |
||||||
|
REQUIRE(unpacked_data.size() == total_pakets_size); |
||||||
|
|
||||||
|
int cnt = 0; |
||||||
|
INFO("test can message integrity"); |
||||||
|
for (int pos = 0, pckt_len = 0; pos < unpacked_data.size(); pos += pckt_len) { |
||||||
|
can_header header; |
||||||
|
memcpy(&header, &unpacked_data[pos], CANPACKET_HEAD_SIZE); |
||||||
|
const uint8_t data_len = dlc_to_len[header.data_len_code]; |
||||||
|
pckt_len = CANPACKET_HEAD_SIZE + data_len; |
||||||
|
|
||||||
|
REQUIRE(header.addr == cnt); |
||||||
|
REQUIRE(test_data.find(data_len) != test_data.end()); |
||||||
|
const std::string &dat = test_data[data_len]; |
||||||
|
REQUIRE(memcmp(dat.data(), &unpacked_data[pos + 5], dat.size()) == 0); |
||||||
|
++cnt; |
||||||
|
} |
||||||
|
REQUIRE(cnt == can_list_size); |
||||||
|
} |
||||||
|
|
||||||
|
void PandaTest::test_can_recv() { |
||||||
|
std::vector<can_frame> frames; |
||||||
|
this->pack_can_buffer(can_data_list, [&](uint8_t *data, size_t size) { |
||||||
|
this->unpack_can_buffer(data, size, frames); |
||||||
|
}); |
||||||
|
|
||||||
|
REQUIRE(frames.size() == can_list_size); |
||||||
|
for (int i = 0; i < frames.size(); ++i) { |
||||||
|
REQUIRE(frames[i].address == i); |
||||||
|
REQUIRE(test_data.find(frames[i].dat.size()) != test_data.end()); |
||||||
|
const std::string &dat = test_data[frames[i].dat.size()]; |
||||||
|
REQUIRE(memcmp(dat.data(), frames[i].dat.data(), dat.size()) == 0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("send/recv can packets") { |
||||||
|
auto bus_offset = GENERATE(0, 4); |
||||||
|
auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); |
||||||
|
PandaTest test(bus_offset, can_list_size); |
||||||
|
|
||||||
|
SECTION("can_send") { |
||||||
|
test.test_can_send(); |
||||||
|
} |
||||||
|
SECTION("can_receive") { |
||||||
|
test.test_can_recv(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue