#include #include #include #include "catch2/catch.hpp" #include "selfdrive/ui/replay/replay.h" #include "selfdrive/ui/replay/util.h" const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; std::string sha_256(const QString &dat) { return QString(QCryptographicHash::hash(dat.toUtf8(), QCryptographicHash::Sha256).toHex()).toStdString(); } TEST_CASE("httpMultiPartDownload") { char filename[] = "/tmp/XXXXXX"; close(mkstemp(filename)); const char *stream_url = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; SECTION("5 connections") { REQUIRE(httpMultiPartDownload(stream_url, filename, 5)); } SECTION("1 connection") { REQUIRE(httpMultiPartDownload(stream_url, filename, 1)); } std::string content = util::read_file(filename); REQUIRE(content.size() == 9112651); std::string checksum = sha_256(QString::fromStdString(content)); REQUIRE(checksum == "e44edfbb545abdddfd17020ced2b18b6ec36506152267f32b6a8e3341f8126d6"); } int random_int(int min, int max) { std::random_device dev; std::mt19937 rng(dev()); std::uniform_int_distribution dist(min, max); return dist(rng); } TEST_CASE("Segment") { Route demo_route(DEMO_ROUTE); REQUIRE(demo_route.load()); REQUIRE(demo_route.segments().size() == 11); QEventLoop loop; Segment segment(0, demo_route.at(0), false, false); QObject::connect(&segment, &Segment::loadFinished, [&]() { REQUIRE(segment.isLoaded() == true); REQUIRE(segment.log != nullptr); REQUIRE(segment.frames[RoadCam] != nullptr); REQUIRE(segment.frames[DriverCam] == nullptr); REQUIRE(segment.frames[WideRoadCam] == nullptr); // LogReader & FrameReader REQUIRE(segment.log->events.size() > 0); REQUIRE(std::is_sorted(segment.log->events.begin(), segment.log->events.end(), Event::lessThan())); auto &fr = segment.frames[RoadCam]; REQUIRE(fr->getFrameCount() == 1200); std::unique_ptr rgb_buf = std::make_unique(fr->getRGBSize()); std::unique_ptr yuv_buf = std::make_unique(fr->getYUVSize()); // sequence get 50 frames for (int i = 0; i < 50; ++i) { REQUIRE(fr->get(i, rgb_buf.get(), yuv_buf.get())); } loop.quit(); }); loop.exec(); } // helper class for unit tests class TestReplay : public Replay { public: TestReplay(const QString &route) : Replay(route, {}, {}) {} void test_seek(); void testSeekTo(int seek_to); }; void TestReplay::testSeekTo(int seek_to) { seekTo(seek_to, false); while (true) { std::unique_lock lk(stream_lock_); stream_cv_.wait(lk, [=]() { return events_updated_ == true; }); events_updated_ = false; if (cur_mono_time_ != route_start_ts_ + seek_to * 1e9) { // wake up by the previous merging, skip it. continue; } Event cur_event(cereal::Event::Which::INIT_DATA, cur_mono_time_); auto eit = std::upper_bound(events_->begin(), events_->end(), &cur_event, Event::lessThan()); if (eit == events_->end()) { qDebug() << "waiting for events..."; continue; } REQUIRE(std::is_sorted(events_->begin(), events_->end(), Event::lessThan())); const int seek_to_segment = seek_to / 60; const int event_seconds = ((*eit)->mono_time - route_start_ts_) / 1e9; current_segment_ = event_seconds / 60; INFO("seek to [" << seek_to << "s segment " << seek_to_segment << "], events [" << event_seconds << "s segment" << current_segment_ << "]"); REQUIRE(event_seconds >= seek_to); if (event_seconds > seek_to) { auto it = segments_.lower_bound(seek_to_segment); REQUIRE(it->first == current_segment_); } break; } } void TestReplay::test_seek() { // create a dummy stream thread stream_thread_ = new QThread(this); QEventLoop loop; std::thread thread = std::thread([&]() { for (int i = 0; i < 100; ++i) { testSeekTo(random_int(0, 5 * 60)); } loop.quit(); }); loop.exec(); thread.join(); } TEST_CASE("Replay") { TestReplay replay(DEMO_ROUTE); REQUIRE(replay.load()); replay.test_seek(); }