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_SIZEpull/22995/head^2
parent
75dd0d6296
commit
c77354009c
6 changed files with 144 additions and 5 deletions
@ -1,2 +1,3 @@ |
||||
boardd |
||||
boardd_api_impl.cpp |
||||
tests/test_boardd_usbprotocol |
||||
|
@ -1,6 +1,9 @@ |
||||
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']) |
||||
|
||||
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