forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ b6534c0
This commit is contained in:
1
iqpilot/common/tests/.gitignore
vendored
Normal file
1
iqpilot/common/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
test_common
|
||||
0
iqpilot/common/tests/__init__.py
Normal file
0
iqpilot/common/tests/__init__.py
Normal file
25
iqpilot/common/tests/native_test.h
Normal file
25
iqpilot/common/tests/native_test.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
inline void native_test_check(bool condition, const char *expression, const char *file, int line) {
|
||||
if (!condition) {
|
||||
throw std::runtime_error(std::string(file) + ":" + std::to_string(line) + ": check failed: " + expression);
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK(condition) native_test_check(static_cast<bool>(condition), #condition, __FILE__, __LINE__)
|
||||
#define REQUIRE(...) CHECK((__VA_ARGS__))
|
||||
|
||||
template <typename Function>
|
||||
int run_native_test(Function &&function) {
|
||||
try {
|
||||
function();
|
||||
return 0;
|
||||
} catch (const std::exception &error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
19
iqpilot/common/tests/test_file_helpers.py
Normal file
19
iqpilot/common/tests/test_file_helpers.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
from iqpilot.common.utils import atomic_write
|
||||
|
||||
|
||||
class TestFileHelpers:
|
||||
def run_atomic_write_func(self, atomic_write_func):
|
||||
path = f"/tmp/tmp{uuid4()}"
|
||||
with atomic_write_func(path) as f:
|
||||
f.write("test")
|
||||
assert not os.path.exists(path)
|
||||
|
||||
with open(path) as f:
|
||||
assert f.read() == "test"
|
||||
os.remove(path)
|
||||
|
||||
def test_atomic_write(self):
|
||||
self.run_atomic_write_func(atomic_write)
|
||||
15
iqpilot/common/tests/test_markdown.py
Normal file
15
iqpilot/common/tests/test_markdown.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
|
||||
from iqpilot.common.basedir import BASEDIR
|
||||
from iqpilot.common.markdown import parse_markdown
|
||||
|
||||
|
||||
class TestMarkdown:
|
||||
def test_all_release_notes(self):
|
||||
with open(os.path.join(BASEDIR, "iqpilot", "docs", "CHANGELOG.md")) as f:
|
||||
release_notes = f.read().split("\n\n")
|
||||
assert len(release_notes) > 10
|
||||
|
||||
for rn in release_notes:
|
||||
md = parse_markdown(rn)
|
||||
assert len(md) > 0
|
||||
33
iqpilot/common/tests/test_params.cc
Normal file
33
iqpilot/common/tests/test_params.cc
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "catch2/catch.hpp"
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <unistd.h>
|
||||
#define private public
|
||||
#include "common/params.h"
|
||||
#include "common/util.h"
|
||||
|
||||
TEST_CASE("params_nonblocking_put") {
|
||||
char tmp_path[] = "/tmp/asyncWriter_XXXXXX";
|
||||
const std::string param_path = mkdtemp(tmp_path);
|
||||
auto param_names = {"CarParams", "IsMetric"};
|
||||
{
|
||||
Params params(param_path);
|
||||
const int lock_fd = open((param_path + "/.lock").c_str(), O_CREAT | O_RDWR, 0775);
|
||||
REQUIRE(lock_fd >= 0);
|
||||
REQUIRE(flock(lock_fd, LOCK_EX) == 0);
|
||||
for (const auto &name : param_names) {
|
||||
params.putNonBlocking(name, "1");
|
||||
}
|
||||
|
||||
const bool future_valid = params.future.valid();
|
||||
const auto future_status = future_valid ? params.future.wait_for(std::chrono::milliseconds(0)) : std::future_status::deferred;
|
||||
REQUIRE(flock(lock_fd, LOCK_UN) == 0);
|
||||
REQUIRE(close(lock_fd) == 0);
|
||||
REQUIRE(future_valid);
|
||||
REQUIRE(future_status == std::future_status::timeout);
|
||||
}
|
||||
Params p(param_path);
|
||||
for (const auto &name : param_names) {
|
||||
REQUIRE(p.get(name) == "1");
|
||||
}
|
||||
}
|
||||
145
iqpilot/common/tests/test_params.py
Normal file
145
iqpilot/common/tests/test_params.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import pytest
|
||||
import datetime
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from iqpilot.common.params import Params, ParamKeyFlag, UnknownKeyName
|
||||
|
||||
class TestParams:
|
||||
def setup_method(self):
|
||||
self.params = Params()
|
||||
|
||||
def test_params_put_and_get(self):
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("DongleId") == "cb38263377b873ee"
|
||||
|
||||
def test_params_non_ascii(self):
|
||||
st = b"\xe1\x90\xff"
|
||||
self.params.put("CarParams", st)
|
||||
assert self.params.get("CarParams") == st
|
||||
|
||||
def test_params_get_cleared_manager_start(self):
|
||||
self.params.put("CarParams", b"test")
|
||||
self.params.put("DongleId", "cb38263377b873ee")
|
||||
assert self.params.get("CarParams") == b"test"
|
||||
|
||||
undefined_param = self.params.get_param_path(uuid.uuid4().hex)
|
||||
with open(undefined_param, "w") as f:
|
||||
f.write("test")
|
||||
assert os.path.isfile(undefined_param)
|
||||
|
||||
self.params.clear_all(ParamKeyFlag.CLEAR_ON_MANAGER_START)
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("DongleId") is not None
|
||||
assert not os.path.isfile(undefined_param)
|
||||
|
||||
def test_params_two_things(self):
|
||||
self.params.put("DongleId", "bob")
|
||||
self.params.put("AthenadPid", 123)
|
||||
assert self.params.get("DongleId") == "bob"
|
||||
assert self.params.get("AthenadPid") == 123
|
||||
|
||||
def test_params_get_block(self):
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
self.params.put("CarParams", b"test")
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert self.params.get("CarParams") is None
|
||||
assert self.params.get("CarParams", block=True) == b"test"
|
||||
|
||||
def test_params_unknown_key_fails(self):
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get("swag")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.get_bool("swag")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.put("swag", "abc")
|
||||
|
||||
with pytest.raises(UnknownKeyName):
|
||||
self.params.put_bool("swag", True)
|
||||
|
||||
def test_remove_not_there(self):
|
||||
assert self.params.get("CarParams") is None
|
||||
self.params.remove("CarParams")
|
||||
assert self.params.get("CarParams") is None
|
||||
|
||||
def test_get_bool(self):
|
||||
self.params.remove("IsMetric")
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put_bool("IsMetric", True)
|
||||
assert self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put_bool("IsMetric", False)
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put("IsMetric", True)
|
||||
assert self.params.get_bool("IsMetric")
|
||||
|
||||
self.params.put("IsMetric", False)
|
||||
assert not self.params.get_bool("IsMetric")
|
||||
|
||||
def test_navigation_disabled_default(self):
|
||||
self.params.remove("NavigationEnabled")
|
||||
assert not self.params.get_bool("NavigationEnabled")
|
||||
|
||||
def test_put_non_blocking_with_get_block(self):
|
||||
q = Params()
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
Params().put_nonblocking("CarParams", b"test")
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert q.get("CarParams") is None
|
||||
assert q.get("CarParams", True) == b"test"
|
||||
|
||||
def test_put_bool_non_blocking_with_get_block(self):
|
||||
q = Params()
|
||||
def _delayed_writer():
|
||||
time.sleep(0.1)
|
||||
Params().put_bool_nonblocking("CarParams", True)
|
||||
threading.Thread(target=_delayed_writer).start()
|
||||
assert q.get("CarParams") is None
|
||||
assert q.get("CarParams", True) == b"1"
|
||||
|
||||
def test_params_all_keys(self):
|
||||
keys = Params().all_keys()
|
||||
|
||||
# sanity checks
|
||||
assert len(keys) > 20
|
||||
assert len(keys) == len(set(keys))
|
||||
assert b"CarParams" in keys
|
||||
|
||||
def test_params_default_value(self):
|
||||
self.params.remove("LanguageSetting")
|
||||
self.params.remove("LongitudinalPersonality")
|
||||
self.params.remove("LiveParameters")
|
||||
|
||||
assert self.params.get("LanguageSetting") is None
|
||||
assert self.params.get("LanguageSetting", return_default=False) is None
|
||||
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
|
||||
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
|
||||
assert self.params.get("LiveParameters") is None
|
||||
assert self.params.get("LiveParameters", return_default=True) is None
|
||||
|
||||
def test_params_get_type(self):
|
||||
# json
|
||||
self.params.put("ApiCache_FirehoseStats", {"a": 0})
|
||||
assert self.params.get("ApiCache_FirehoseStats") == {"a": 0}
|
||||
|
||||
# int
|
||||
self.params.put("BootCount", 1441)
|
||||
assert self.params.get("BootCount") == 1441
|
||||
|
||||
# bool
|
||||
self.params.put("AdbEnabled", True)
|
||||
assert self.params.get("AdbEnabled")
|
||||
assert isinstance(self.params.get("AdbEnabled"), bool)
|
||||
|
||||
# time
|
||||
now = datetime.datetime.now(datetime.UTC)
|
||||
self.params.put("InstallDate", now)
|
||||
assert self.params.get("InstallDate") == now
|
||||
64
iqpilot/common/tests/test_realtime.py
Normal file
64
iqpilot/common/tests/test_realtime.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
|
||||
from iqpilot.common.realtime import config_background_thread, Ratekeeper
|
||||
|
||||
|
||||
class MonotonicClock:
|
||||
def __init__(self) -> None:
|
||||
self.now = 0.
|
||||
|
||||
def advance(self, seconds: float) -> None:
|
||||
self.now += seconds
|
||||
|
||||
def __call__(self) -> float:
|
||||
return self.now
|
||||
|
||||
|
||||
def test_ratekeeper_reset_discards_accumulated_lag(monkeypatch):
|
||||
clock = MonotonicClock()
|
||||
monkeypatch.setattr("iqpilot.common.realtime.time.monotonic", clock)
|
||||
rk = Ratekeeper(100)
|
||||
|
||||
rk.monitor_time()
|
||||
clock.advance(0.075)
|
||||
rk.monitor_time()
|
||||
assert rk.remaining == pytest.approx(-0.055)
|
||||
assert rk.lag == pytest.approx(0.055)
|
||||
|
||||
rk.reset()
|
||||
assert rk.remaining == 0.
|
||||
assert rk.lag == 0.
|
||||
|
||||
rk.monitor_time()
|
||||
assert rk.remaining == pytest.approx(0.01)
|
||||
assert rk.lag == 0.
|
||||
|
||||
|
||||
def test_ratekeeper_reset_preserves_frame_count(monkeypatch):
|
||||
clock = MonotonicClock()
|
||||
monkeypatch.setattr("iqpilot.common.realtime.time.monotonic", clock)
|
||||
rk = Ratekeeper(100)
|
||||
|
||||
rk.monitor_time()
|
||||
clock.advance(0.01)
|
||||
rk.monitor_time()
|
||||
frame = rk.frame
|
||||
|
||||
rk.reset()
|
||||
assert rk.frame == frame
|
||||
|
||||
|
||||
def test_config_background_thread_restores_normal_scheduling(monkeypatch):
|
||||
calls = []
|
||||
monkeypatch.setattr("iqpilot.common.realtime.sys.platform", "linux")
|
||||
monkeypatch.setattr("iqpilot.common.realtime.PC", False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.cpu_count", lambda: 8)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.SCHED_OTHER", 0, raising=False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.sched_param", lambda priority: priority, raising=False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.sched_setscheduler", lambda pid, policy, param: calls.append((pid, policy, param)), raising=False)
|
||||
monkeypatch.setattr("iqpilot.common.realtime.os.sched_setaffinity", lambda pid, cores: calls.append((pid, set(cores))), raising=False)
|
||||
|
||||
config_background_thread()
|
||||
|
||||
assert calls == [(0, 0, 0), (0, set(range(8)))]
|
||||
2
iqpilot/common/tests/test_runner.cc
Normal file
2
iqpilot/common/tests/test_runner.cc
Normal file
@@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
29
iqpilot/common/tests/test_simple_kalman.py
Normal file
29
iqpilot/common/tests/test_simple_kalman.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from iqpilot.common.simple_kalman import KF1D
|
||||
|
||||
|
||||
class TestSimpleKalman:
|
||||
def setup_method(self):
|
||||
dt = 0.01
|
||||
x0_0 = 0.0
|
||||
x1_0 = 0.0
|
||||
A0_0 = 1.0
|
||||
A0_1 = dt
|
||||
A1_0 = 0.0
|
||||
A1_1 = 1.0
|
||||
C0_0 = 1.0
|
||||
C0_1 = 0.0
|
||||
K0_0 = 0.12287673
|
||||
K1_0 = 0.29666309
|
||||
|
||||
self.kf = KF1D(x0=[[x0_0], [x1_0]],
|
||||
A=[[A0_0, A0_1], [A1_0, A1_1]],
|
||||
C=[C0_0, C0_1],
|
||||
K=[[K0_0], [K1_0]])
|
||||
|
||||
def test_getter_setter(self):
|
||||
self.kf.set_x([[1.0], [1.0]])
|
||||
assert self.kf.x == [[1.0], [1.0]]
|
||||
|
||||
def test_update_returns_state(self):
|
||||
x = self.kf.update(100)
|
||||
assert x == [i[0] for i in self.kf.x]
|
||||
91
iqpilot/common/tests/test_steer_delay.py
Normal file
91
iqpilot/common/tests/test_steer_delay.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
import iqpilot.cereal.messaging as messaging
|
||||
from iqpilot.cereal import car
|
||||
from iqpilot.common.params import Params
|
||||
from iqpilot.common.steer_delay import (
|
||||
SteerDelayPublisher,
|
||||
cached_steer_delay,
|
||||
fixed_steer_delay,
|
||||
lateral_action_delay,
|
||||
resolve_steer_delay,
|
||||
)
|
||||
|
||||
ANGLE = car.CarParams.SteerControlType.angle
|
||||
TORQUE = car.CarParams.SteerControlType.torque
|
||||
|
||||
LIVE_DELAY = 0.4387
|
||||
RACK_DELAY = 0.10
|
||||
OFFSET = 0.05
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def params(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
|
||||
p = Params()
|
||||
p.put("IQSteerDelayCache", LIVE_DELAY)
|
||||
p.put("IQSoftwareSteerDelay", OFFSET)
|
||||
return p
|
||||
|
||||
|
||||
def _car_params(steer_control_type):
|
||||
cp = car.CarParams.new_message()
|
||||
cp.steerControlType = steer_control_type
|
||||
cp.steerActuatorDelay = RACK_DELAY
|
||||
return cp
|
||||
|
||||
|
||||
def _lateral_delay_msg(value):
|
||||
msg = messaging.new_message("lateralDelay")
|
||||
msg.lateralDelay.lateralDelay = value
|
||||
return msg.as_reader()
|
||||
|
||||
|
||||
def test_params_fixture_is_isolated_from_the_real_device(params, tmp_path):
|
||||
assert str(tmp_path) in params.get_param_path("")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("live_enabled", [True, False])
|
||||
def test_torque_cars_always_use_live_delay(params, live_enabled):
|
||||
params.put_bool("IQLiveSteerDelay", live_enabled)
|
||||
assert lateral_action_delay(params, _car_params(TORQUE), LIVE_DELAY) == pytest.approx(LIVE_DELAY)
|
||||
|
||||
|
||||
def test_angle_cars_ignore_live_delay_when_self_tuning_is_off(params):
|
||||
params.put_bool("IQLiveSteerDelay", False)
|
||||
delay = lateral_action_delay(params, _car_params(ANGLE), LIVE_DELAY)
|
||||
assert delay == pytest.approx(RACK_DELAY + OFFSET)
|
||||
assert delay != pytest.approx(LIVE_DELAY)
|
||||
|
||||
|
||||
def test_angle_cars_use_cached_delay_when_self_tuning_is_on(params):
|
||||
params.put_bool("IQLiveSteerDelay", True)
|
||||
assert lateral_action_delay(params, _car_params(ANGLE), LIVE_DELAY) == pytest.approx(LIVE_DELAY)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("offset", [0.05, 0.20, 0.50])
|
||||
def test_manual_offset_reaches_the_path_and_matches_what_the_ui_reports(params, offset):
|
||||
params.put_bool("IQLiveSteerDelay", False)
|
||||
params.put("IQSoftwareSteerDelay", offset)
|
||||
ui_total = RACK_DELAY + offset
|
||||
assert fixed_steer_delay(params, RACK_DELAY) == pytest.approx(ui_total)
|
||||
assert lateral_action_delay(params, _car_params(ANGLE), LIVE_DELAY) == pytest.approx(ui_total)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("live_enabled", [False, True])
|
||||
def test_publisher_writes_the_value_the_resolver_reads(params, live_enabled):
|
||||
params.put_bool("IQLiveSteerDelay", live_enabled)
|
||||
params.put("IQSteerDelayCache", -1.0)
|
||||
SteerDelayPublisher(_car_params(ANGLE)).update(_lateral_delay_msg(LIVE_DELAY))
|
||||
|
||||
expected = LIVE_DELAY if live_enabled else RACK_DELAY + OFFSET
|
||||
deadline = time.monotonic() + 5.0
|
||||
while cached_steer_delay() != pytest.approx(expected) and time.monotonic() < deadline:
|
||||
time.sleep(0.01)
|
||||
assert cached_steer_delay() == pytest.approx(expected)
|
||||
assert resolve_steer_delay(params, RACK_DELAY) == pytest.approx(expected)
|
||||
84
iqpilot/common/tests/test_swaglog.cc
Normal file
84
iqpilot/common/tests/test_swaglog.cc
Normal file
@@ -0,0 +1,84 @@
|
||||
#include <zmq.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
#include "common/swaglog.h"
|
||||
#include "common/util.h"
|
||||
#include "system/hardware/hw.h"
|
||||
#include "third_party/json11/json11.hpp"
|
||||
|
||||
#include "iqpilot/common/version.h"
|
||||
|
||||
std::string daemon_name = "testy";
|
||||
std::string dongle_id = "test_dongle_id";
|
||||
int LINE_NO = 0;
|
||||
|
||||
void log_thread(int thread_id, int msg_cnt) {
|
||||
for (int i = 0; i < msg_cnt; ++i) {
|
||||
LOGD("%d", thread_id);
|
||||
LINE_NO = __LINE__ - 1;
|
||||
usleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void recv_log(void *sock, int thread_cnt, int thread_msg_cnt) {
|
||||
std::vector<int> thread_msgs(thread_cnt);
|
||||
int timeout_ms = 10000;
|
||||
REQUIRE(zmq_setsockopt(sock, ZMQ_RCVTIMEO, &timeout_ms, sizeof(timeout_ms)) == 0);
|
||||
|
||||
for (int total_count = 0; total_count < thread_cnt * thread_msg_cnt; ++total_count) {
|
||||
char buf[4096] = {};
|
||||
REQUIRE(zmq_recv(sock, buf, sizeof(buf), 0) > 0);
|
||||
|
||||
REQUIRE(buf[0] == CLOUDLOG_DEBUG);
|
||||
std::string err;
|
||||
auto msg = json11::Json::parse(buf + 1, err);
|
||||
REQUIRE(!msg.is_null());
|
||||
|
||||
REQUIRE(msg["levelnum"].int_value() == CLOUDLOG_DEBUG);
|
||||
REQUIRE_THAT(msg["filename"].string_value(), Catch::Contains("test_swaglog.cc"));
|
||||
REQUIRE(msg["funcname"].string_value() == "log_thread");
|
||||
REQUIRE(msg["lineno"].int_value() == LINE_NO);
|
||||
|
||||
auto ctx = msg["ctx"];
|
||||
|
||||
REQUIRE(ctx["daemon"].string_value() == daemon_name);
|
||||
REQUIRE(ctx["dongle_id"].string_value() == dongle_id);
|
||||
REQUIRE(ctx["dirty"].bool_value() == true);
|
||||
|
||||
REQUIRE(ctx["version"].string_value() == COMMA_VERSION);
|
||||
|
||||
std::string device = Hardware::get_name();
|
||||
REQUIRE(ctx["device"].string_value() == device);
|
||||
|
||||
int thread_id = atoi(msg["msg"].string_value().c_str());
|
||||
REQUIRE((thread_id >= 0 && thread_id < thread_cnt));
|
||||
thread_msgs[thread_id]++;
|
||||
}
|
||||
for (int i = 0; i < thread_cnt; ++i) {
|
||||
INFO("thread :" << i);
|
||||
REQUIRE(thread_msgs[i] == thread_msg_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("swaglog") {
|
||||
setenv("MANAGER_DAEMON", daemon_name.c_str(), 1);
|
||||
setenv("DONGLE_ID", dongle_id.c_str(), 1);
|
||||
setenv("dirty", "1", 1);
|
||||
const int thread_cnt = 5;
|
||||
const int thread_msg_cnt = 100;
|
||||
void *zctx = zmq_ctx_new();
|
||||
void *sock = zmq_socket(zctx, ZMQ_PULL);
|
||||
REQUIRE(zmq_bind(sock, Path::swaglog_ipc().c_str()) == 0);
|
||||
|
||||
std::vector<std::thread> log_threads;
|
||||
for (int i = 0; i < thread_cnt; ++i) {
|
||||
log_threads.push_back(std::thread(log_thread, i, thread_msg_cnt));
|
||||
}
|
||||
for (auto &t : log_threads) t.join();
|
||||
|
||||
recv_log(sock, thread_cnt, thread_msg_cnt);
|
||||
zmq_close(sock);
|
||||
zmq_ctx_destroy(zctx);
|
||||
}
|
||||
151
iqpilot/common/tests/test_util.cc
Normal file
151
iqpilot/common/tests/test_util.cc
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
#include "common/util.h"
|
||||
|
||||
std::string random_bytes(int size) {
|
||||
std::random_device rd;
|
||||
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> rbe(rd());
|
||||
std::string bytes(size + 1, '\0');
|
||||
std::generate(bytes.begin(), bytes.end(), std::ref(rbe));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
TEST_CASE("util::read_file") {
|
||||
#ifdef __linux__
|
||||
SECTION("read /proc/version") {
|
||||
std::string ret = util::read_file("/proc/version");
|
||||
REQUIRE(ret.find("Linux version") != std::string::npos);
|
||||
}
|
||||
SECTION("read from sysfs") {
|
||||
std::string ret = util::read_file("/sys/power/wakeup_count");
|
||||
REQUIRE(!ret.empty());
|
||||
}
|
||||
#endif
|
||||
SECTION("read file") {
|
||||
char filename[] = "/tmp/test_read_XXXXXX";
|
||||
int fd = mkstemp(filename);
|
||||
|
||||
REQUIRE(util::read_file(filename).empty());
|
||||
|
||||
std::string content = random_bytes(64 * 1024);
|
||||
write(fd, content.c_str(), content.size());
|
||||
std::string ret = util::read_file(filename);
|
||||
bool equal = (ret == content);
|
||||
REQUIRE(equal);
|
||||
close(fd);
|
||||
}
|
||||
SECTION("read directory") {
|
||||
REQUIRE(util::read_file(".").empty());
|
||||
}
|
||||
SECTION("read non-existent file") {
|
||||
std::string ret = util::read_file("does_not_exist");
|
||||
REQUIRE(ret.empty());
|
||||
}
|
||||
SECTION("read non-permission") {
|
||||
REQUIRE(util::read_file("/proc/kmsg").empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("util::file_exists") {
|
||||
char filename[] = "/tmp/test_file_exists_XXXXXX";
|
||||
int fd = mkstemp(filename);
|
||||
REQUIRE(fd != -1);
|
||||
close(fd);
|
||||
|
||||
SECTION("existent file") {
|
||||
REQUIRE(util::file_exists(filename));
|
||||
REQUIRE(util::file_exists("/tmp"));
|
||||
}
|
||||
SECTION("nonexistent file") {
|
||||
std::string fn = filename;
|
||||
REQUIRE(!util::file_exists(fn + "/nonexistent"));
|
||||
}
|
||||
SECTION("file has no access permissions") {
|
||||
std::string fn = filename;
|
||||
chmod(fn.c_str(), 0000);
|
||||
std::ifstream f(fn);
|
||||
REQUIRE(f.good() == false);
|
||||
REQUIRE(util::file_exists(fn));
|
||||
chmod(fn.c_str(), 0600);
|
||||
}
|
||||
::remove(filename);
|
||||
}
|
||||
|
||||
TEST_CASE("util::read_files_in_dir") {
|
||||
char tmp_path[] = "/tmp/test_XXXXXX";
|
||||
const std::string test_path = mkdtemp(tmp_path);
|
||||
const std::string files[] = {".test1", "'test2'", "test3"};
|
||||
for (auto fn : files) {
|
||||
std::ofstream{test_path + "/" + fn} << fn;
|
||||
}
|
||||
mkdir((test_path + "/dir").c_str(), 0777);
|
||||
|
||||
std::map<std::string, std::string> result = util::read_files_in_dir(test_path);
|
||||
REQUIRE(result.find("dir") == result.end());
|
||||
REQUIRE(result.size() == std::size(files));
|
||||
for (auto& [k, v] : result) {
|
||||
REQUIRE(k == v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("util::safe_fwrite") {
|
||||
char filename[] = "/tmp/XXXXXX";
|
||||
int fd = mkstemp(filename);
|
||||
close(fd);
|
||||
std::string dat = random_bytes(1024 * 1024);
|
||||
|
||||
FILE *f = util::safe_fopen(filename, "wb");
|
||||
REQUIRE(f != nullptr);
|
||||
size_t size = util::safe_fwrite(dat.data(), 1, dat.size(), f);
|
||||
REQUIRE(size == dat.size());
|
||||
int ret = util::safe_fflush(f);
|
||||
REQUIRE(ret == 0);
|
||||
ret = fclose(f);
|
||||
REQUIRE(ret == 0);
|
||||
bool equal = (dat == util::read_file(filename));
|
||||
REQUIRE(equal);
|
||||
}
|
||||
|
||||
TEST_CASE("util::create_directories") {
|
||||
system("rm -rf /tmp/test_create_directories");
|
||||
std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f";
|
||||
|
||||
auto check_dir_permissions = [](const std::string &dir, mode_t mode) -> bool {
|
||||
struct stat st = {};
|
||||
return stat(dir.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode;
|
||||
};
|
||||
|
||||
SECTION("create_directories") {
|
||||
REQUIRE(util::create_directories(dir, 0755));
|
||||
REQUIRE(check_dir_permissions(dir, 0755));
|
||||
}
|
||||
SECTION("dir already exists") {
|
||||
REQUIRE(util::create_directories(dir, 0755));
|
||||
REQUIRE(util::create_directories(dir, 0755));
|
||||
}
|
||||
SECTION("a file exists with the same name") {
|
||||
REQUIRE(util::create_directories(dir, 0755));
|
||||
int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT);
|
||||
REQUIRE(f != -1);
|
||||
close(f);
|
||||
REQUIRE(util::create_directories(dir + "/file", 0755) == false);
|
||||
REQUIRE(util::create_directories(dir + "/file/1/2/3", 0755) == false);
|
||||
}
|
||||
SECTION("end with slashes") {
|
||||
REQUIRE(util::create_directories(dir + "/", 0755));
|
||||
}
|
||||
SECTION("empty") {
|
||||
REQUIRE(util::create_directories("", 0755) == false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user