openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
5.9 KiB

#include <QCryptographicHash>
#include <QDebug>
#include <QEventLoop>
#include <QString>
#include <future>
#include "catch2/catch.hpp"
#include "selfdrive/common/util.h"
#include "selfdrive/ui/replay/framereader.h"
#include "selfdrive/ui/replay/replay.h"
#include "selfdrive/ui/replay/route.h"
#include "selfdrive/ui/replay/util.h"
const char *stream_url = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/fcamera.hevc";
// TEST_CASE("FrameReader") {
// SECTION("process&get") {
// FrameReader fr;
// REQUIRE(fr.load(stream_url) == true);
// REQUIRE(fr.valid() == true);
// REQUIRE(fr.getFrameCount() == 1200);
// // random get 50 frames
// // srand(time(NULL));
// // for (int i = 0; i < 50; ++i) {
// // int idx = rand() % (fr.getFrameCount() - 1);
// // REQUIRE(fr.get(idx) != nullptr);
// // }
// // sequence get 50 frames {
// for (int i = 0; i < 50; ++i) {
// REQUIRE(fr.get(i) != nullptr);
// }
// }
// }
std::string sha_256(const QString &dat) {
return QString(QCryptographicHash::hash(dat.toUtf8(), QCryptographicHash::Sha256).toHex()).toStdString();
}
TEST_CASE("httpMultiPartDownload") {
char filename[] = "/tmp/XXXXXX";
int fd = mkstemp(filename);
REQUIRE(fd != -1);
close(fd);
SECTION("http 200") {
REQUIRE(httpMultiPartDownload(stream_url, filename, 5));
std::string content = util::read_file(filename);
REQUIRE(content.size() == 37495242);
std::string checksum = sha_256(QString::fromStdString(content));
REQUIRE(checksum == "d8ff81560ce7ed6f16d5fb5a6d6dd13aba06c8080c62cfe768327914318744c4");
}
SECTION("http 404") {
REQUIRE(httpMultiPartDownload(util::string_format("%s_abc", stream_url), filename, 5) == false);
}
}
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);
}
bool is_events_ordered(const std::vector<Event *> &events) {
REQUIRE(events.size() > 0);
uint64_t prev_mono_time = 0;
cereal::Event::Which prev_which = cereal::Event::INIT_DATA;
for (auto event : events) {
if (event->mono_time < prev_mono_time || (event->mono_time == prev_mono_time && event->which < prev_which)) {
return false;
}
prev_mono_time = event->mono_time;
prev_which = event->which;
}
return true;
}
const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36";
TEST_CASE("Segment") {
Route demo_route(DEMO_ROUTE);
REQUIRE(demo_route.load());
REQUIRE(demo_route.size() == 11);
QEventLoop loop;
Segment segment(0, demo_route.at(0), false, false);
REQUIRE(segment.isValid() == true);
REQUIRE(segment.isLoaded() == false);
QObject::connect(&segment, &Segment::loadFinished, [&]() {
REQUIRE(segment.isLoaded() == true);
REQUIRE(segment.log != nullptr);
REQUIRE(segment.log->events.size() > 0);
REQUIRE(is_events_ordered(segment.log->events));
REQUIRE(segment.frames[RoadCam] != nullptr);
REQUIRE(segment.frames[RoadCam]->getFrameCount() > 0);
REQUIRE(segment.frames[DriverCam] == nullptr);
REQUIRE(segment.frames[WideRoadCam] == nullptr);
loop.quit();
});
loop.exec();
}
// helper class for unit tests
class TestReplay : public Replay {
public:
TestReplay(const QString &route) : Replay(route, {}, {}) {}
void test_seek();
protected:
void testSeekTo(int seek_to, const std::set<int> &invalid_segments = {});
};
void TestReplay::testSeekTo(int seek_to, const std::set<int> &invalid_segments) {
seekTo(seek_to);
// wait for seek finish
while (true) {
std::unique_lock lk(stream_lock_);
stream_cv_.wait(lk, [=]() { return events_updated_ == true; });
events_updated_ = false;
// verify result
REQUIRE(uint64_t(route_start_ts_ + seek_to * 1e9) == cur_mono_time_);
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;
}
INFO("seek to [" << seek_to << "s segment " << seek_to / 60 << "]");
REQUIRE(!events_->empty());
REQUIRE(is_events_ordered(*events_));
REQUIRE(eit != events_->end());
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("event [" << event_seconds << "s segment " << current_segment_ << "]");
REQUIRE(event_seconds >= seek_to);
if (invalid_segments.find(seek_to_segment) == invalid_segments.end()) {
REQUIRE(event_seconds == seek_to); // at the same time
} else {
if (current_segment_ == seek_to_segment) {
// seek cross-boundary. e.g. seek_to 60s(segment 1), but segment 0 end at 60.021 and segemnt 1 is invalid.
REQUIRE(event_seconds == seek_to);
} else {
REQUIRE(current_segment_ > seek_to_segment);
REQUIRE(invalid_segments.find(current_segment_) == invalid_segments.end());
}
}
break;
}
}
void TestReplay::test_seek() {
QEventLoop loop;
std::thread thread = std::thread([&]() {
const int loop_count = 100;
// random seek in one segment
for (int i = 0; i < loop_count; ++i) {
testSeekTo(random_int(0, 60));
}
// random seek in 3 segments
for (int i = 0; i < loop_count; ++i) {
testSeekTo(random_int(0, 60 * 3));
}
// random seek in invalid segments
std::set<int> invalid_segments{5, 6, 7, 9};
for (int i : invalid_segments) {
route_->segments_[i].rlog = route_->segments_[i].qlog = "";
route_->segments_[i].road_cam = route_->segments_[i].qcamera = "";
}
for (int i = 0; i < loop_count; ++i) {
testSeekTo(random_int(4 * 60, 60 * 10), invalid_segments);
}
loop.quit();
});
loop.exec();
thread.join();
}
TEST_CASE("Replay") {
TestReplay replay(DEMO_ROUTE);
REQUIRE(replay.load());
replay.test_seek();
}