mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-16 15:50:11 +00:00
280 lines
11 KiB
C++
280 lines
11 KiB
C++
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
#include "Telemetry/LocationPersistence.h"
|
|
|
|
namespace {
|
|
|
|
int passed = 0;
|
|
int failures = 0;
|
|
#define CHECK(expr) do { if (expr) { ++passed; } else { ++failures; \
|
|
std::cerr << "FAIL line " << __LINE__ << ": " #expr << '\n'; } } while (false)
|
|
|
|
struct SlotData {
|
|
bool present = false;
|
|
std::size_t size = 0;
|
|
uint8_t bytes[Telemetry::MAX_LOCATION_STATE_RECORD_BYTES]{};
|
|
};
|
|
|
|
class FakeStorage : public Telemetry::LocationPersistenceStorage {
|
|
public:
|
|
bool mounted = true;
|
|
int operations = 0;
|
|
int fail_at = 0;
|
|
bool corrupt_write = false;
|
|
SlotData slots[3]{};
|
|
|
|
bool available() const override { return mounted; }
|
|
|
|
bool stat(Telemetry::LocationPersistenceSlot slot, bool& exists) override {
|
|
if (fails()) return false;
|
|
exists = at(slot).present;
|
|
return true;
|
|
}
|
|
|
|
bool read(Telemetry::LocationPersistenceSlot slot, uint8_t* output,
|
|
std::size_t capacity, std::size_t& size) override {
|
|
if (fails()) return false;
|
|
const SlotData& source = at(slot);
|
|
if (!source.present || source.size > capacity) return false;
|
|
std::memcpy(output, source.bytes, source.size);
|
|
size = source.size;
|
|
return true;
|
|
}
|
|
|
|
bool write(Telemetry::LocationPersistenceSlot slot, const uint8_t* data,
|
|
std::size_t size) override {
|
|
if (fails()) {
|
|
SlotData& target = at(slot);
|
|
target.present = true;
|
|
target.size = size / 2;
|
|
if (target.size > 0) std::memcpy(target.bytes, data, target.size);
|
|
return false;
|
|
}
|
|
SlotData& target = at(slot);
|
|
target.present = true;
|
|
target.size = size;
|
|
std::memcpy(target.bytes, data, size);
|
|
if (corrupt_write && size > 20) target.bytes[20] ^= 1U;
|
|
return true;
|
|
}
|
|
|
|
bool remove(Telemetry::LocationPersistenceSlot slot) override {
|
|
if (fails()) return false;
|
|
at(slot) = SlotData{};
|
|
return true;
|
|
}
|
|
|
|
bool rename(Telemetry::LocationPersistenceSlot from,
|
|
Telemetry::LocationPersistenceSlot to) override {
|
|
if (fails()) return false;
|
|
if (!at(from).present || at(to).present) return false;
|
|
at(to) = at(from);
|
|
at(from) = SlotData{};
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
bool fails() {
|
|
++operations;
|
|
return fail_at != 0 && operations == fail_at;
|
|
}
|
|
SlotData& at(Telemetry::LocationPersistenceSlot slot) {
|
|
return slots[static_cast<std::size_t>(slot)];
|
|
}
|
|
const SlotData& at(Telemetry::LocationPersistenceSlot slot) const {
|
|
return slots[static_cast<std::size_t>(slot)];
|
|
}
|
|
};
|
|
|
|
Telemetry::LocationStateSnapshot state(uint32_t marker) {
|
|
Telemetry::LocationStateSnapshot value{};
|
|
value.session_count = 1;
|
|
for (std::size_t i = 0; i < Telemetry::PEER_ID_SIZE; ++i) {
|
|
value.sessions[0].peer.bytes[i] = static_cast<uint8_t>(marker + i);
|
|
}
|
|
value.sessions[0].record.cadence_millis =
|
|
Telemetry::MIN_SHARE_CADENCE_MILLIS + marker;
|
|
value.sessions[0].record.approx_radius_meters = static_cast<int32_t>(marker);
|
|
value.location_count = 1;
|
|
value.locations[0].peer = value.sessions[0].peer;
|
|
value.locations[0].location.latitude_e6 = 1000000 + static_cast<int32_t>(marker);
|
|
value.locations[0].location.longitude_e6 = -2000000;
|
|
value.locations[0].source_timestamp_millis = marker;
|
|
value.locations[0].received_at_millis = marker + 1;
|
|
return value;
|
|
}
|
|
|
|
uint32_t marker(const Telemetry::LocationStateSnapshot& value) {
|
|
return value.sessions[0].record.cadence_millis -
|
|
Telemetry::MIN_SHARE_CADENCE_MILLIS;
|
|
}
|
|
|
|
void put(FakeStorage& storage, Telemetry::LocationPersistenceSlot slot,
|
|
const Telemetry::LocationStateSnapshot& value) {
|
|
SlotData& target = storage.slots[static_cast<std::size_t>(slot)];
|
|
target.present = true;
|
|
std::size_t written = 0;
|
|
CHECK(Telemetry::encodeLocationStateRecord(
|
|
value, target.bytes, sizeof(target.bytes), written) ==
|
|
Telemetry::LocationStateRecordResult::OK);
|
|
target.size = written;
|
|
}
|
|
|
|
void recoversByLiveTempBackupPriority() {
|
|
FakeStorage storage;
|
|
put(storage, Telemetry::LocationPersistenceSlot::LIVE, state(1));
|
|
put(storage, Telemetry::LocationPersistenceSlot::TEMP, state(2));
|
|
put(storage, Telemetry::LocationPersistenceSlot::BACKUP, state(3));
|
|
Telemetry::TransactionalLocationPersistence persistence(storage);
|
|
Telemetry::LocationStateSnapshot output{};
|
|
CHECK(persistence.load(output) == Telemetry::LocationPersistenceResult::LOADED_LIVE);
|
|
CHECK(marker(output) == 1);
|
|
|
|
storage.slots[0].bytes[20] ^= 1U;
|
|
CHECK(persistence.load(output) == Telemetry::LocationPersistenceResult::RECOVERED_TEMP);
|
|
CHECK(marker(output) == 2);
|
|
CHECK(storage.slots[0].present);
|
|
|
|
FakeStorage backup_only;
|
|
put(backup_only, Telemetry::LocationPersistenceSlot::LIVE, state(4));
|
|
backup_only.slots[0].bytes[20] ^= 1U;
|
|
put(backup_only, Telemetry::LocationPersistenceSlot::TEMP, state(5));
|
|
backup_only.slots[1].bytes[20] ^= 1U;
|
|
put(backup_only, Telemetry::LocationPersistenceSlot::BACKUP, state(6));
|
|
Telemetry::TransactionalLocationPersistence backup_persistence(backup_only);
|
|
CHECK(backup_persistence.load(output) ==
|
|
Telemetry::LocationPersistenceResult::RECOVERED_BACKUP);
|
|
CHECK(marker(output) == 6);
|
|
CHECK(backup_only.slots[2].present);
|
|
}
|
|
|
|
void failsClosedWhenUnavailableMissingOrCorrupt() {
|
|
Telemetry::LocationStateSnapshot output = state(9);
|
|
FakeStorage unavailable;
|
|
unavailable.mounted = false;
|
|
Telemetry::TransactionalLocationPersistence unavailable_persistence(unavailable);
|
|
CHECK(unavailable_persistence.load(output) ==
|
|
Telemetry::LocationPersistenceResult::UNAVAILABLE);
|
|
CHECK(marker(output) == 9);
|
|
|
|
FakeStorage empty;
|
|
Telemetry::TransactionalLocationPersistence empty_persistence(empty);
|
|
CHECK(empty_persistence.load(output) ==
|
|
Telemetry::LocationPersistenceResult::NOT_FOUND);
|
|
CHECK(marker(output) == 9);
|
|
|
|
FakeStorage corrupt;
|
|
put(corrupt, Telemetry::LocationPersistenceSlot::LIVE, state(1));
|
|
put(corrupt, Telemetry::LocationPersistenceSlot::TEMP, state(2));
|
|
put(corrupt, Telemetry::LocationPersistenceSlot::BACKUP, state(3));
|
|
for (auto& slot : corrupt.slots) slot.bytes[20] ^= 1U;
|
|
Telemetry::TransactionalLocationPersistence corrupt_persistence(corrupt);
|
|
CHECK(corrupt_persistence.load(output) ==
|
|
Telemetry::LocationPersistenceResult::INVALID_STATE);
|
|
CHECK(marker(output) == 9);
|
|
}
|
|
|
|
void everyInterruptedSaveRetainsAValidGeneration() {
|
|
FakeStorage baseline;
|
|
put(baseline, Telemetry::LocationPersistenceSlot::LIVE, state(10));
|
|
put(baseline, Telemetry::LocationPersistenceSlot::BACKUP, state(9));
|
|
int observed_failures = 0;
|
|
for (int fail_at = 1; fail_at <= 16; ++fail_at) {
|
|
FakeStorage interrupted = baseline;
|
|
interrupted.fail_at = fail_at;
|
|
Telemetry::TransactionalLocationPersistence persistence(interrupted);
|
|
const auto result = persistence.save(state(20));
|
|
if (result != Telemetry::LocationPersistenceResult::SAVED) {
|
|
++observed_failures;
|
|
}
|
|
interrupted.fail_at = 0;
|
|
interrupted.operations = 0;
|
|
Telemetry::LocationStateSnapshot output{};
|
|
const auto loaded = persistence.load(output);
|
|
CHECK(loaded == Telemetry::LocationPersistenceResult::LOADED_LIVE ||
|
|
loaded == Telemetry::LocationPersistenceResult::RECOVERED_TEMP ||
|
|
loaded == Telemetry::LocationPersistenceResult::RECOVERED_BACKUP);
|
|
CHECK(marker(output) == 10 || marker(output) == 20);
|
|
}
|
|
CHECK(observed_failures >= 8);
|
|
}
|
|
|
|
void validatesTempBeforeReplacingLive() {
|
|
FakeStorage storage;
|
|
put(storage, Telemetry::LocationPersistenceSlot::LIVE, state(30));
|
|
storage.corrupt_write = true;
|
|
Telemetry::TransactionalLocationPersistence persistence(storage);
|
|
CHECK(persistence.save(state(40)) ==
|
|
Telemetry::LocationPersistenceResult::INVALID_STATE);
|
|
storage.corrupt_write = false;
|
|
Telemetry::LocationStateSnapshot output{};
|
|
CHECK(persistence.load(output) == Telemetry::LocationPersistenceResult::LOADED_LIVE);
|
|
CHECK(marker(output) == 30);
|
|
}
|
|
|
|
void successfulPromotionIsCommittedWhenReadbackIoFails() {
|
|
FakeStorage storage;
|
|
put(storage, Telemetry::LocationPersistenceSlot::LIVE, state(10));
|
|
Telemetry::TransactionalLocationPersistence persistence(storage);
|
|
storage.fail_at = 11; // promoted LIVE stat/read-back cannot be observed
|
|
CHECK(persistence.save(state(20)) ==
|
|
Telemetry::LocationPersistenceResult::SAVED);
|
|
storage.fail_at = 0;
|
|
storage.operations = 0;
|
|
Telemetry::LocationStateSnapshot output{};
|
|
CHECK(persistence.load(output) ==
|
|
Telemetry::LocationPersistenceResult::LOADED_LIVE);
|
|
CHECK(marker(output) == 20);
|
|
}
|
|
|
|
void transientHigherPriorityIoNeverFallsBackOrRepairs() {
|
|
FakeStorage storage;
|
|
put(storage, Telemetry::LocationPersistenceSlot::LIVE, state(20));
|
|
put(storage, Telemetry::LocationPersistenceSlot::BACKUP, state(10));
|
|
Telemetry::TransactionalLocationPersistence persistence(storage);
|
|
Telemetry::LocationStateSnapshot output = state(99);
|
|
|
|
storage.fail_at = 2; // live stat succeeds, live read fails
|
|
CHECK(persistence.load(output) == Telemetry::LocationPersistenceResult::IO_ERROR);
|
|
CHECK(marker(output) == 99);
|
|
storage.fail_at = 0;
|
|
storage.operations = 0;
|
|
CHECK(persistence.load(output) == Telemetry::LocationPersistenceResult::LOADED_LIVE);
|
|
CHECK(marker(output) == 20);
|
|
|
|
FakeStorage temp_error;
|
|
put(temp_error, Telemetry::LocationPersistenceSlot::LIVE, state(30));
|
|
temp_error.slots[0].bytes[20] ^= 1U;
|
|
put(temp_error, Telemetry::LocationPersistenceSlot::TEMP, state(40));
|
|
put(temp_error, Telemetry::LocationPersistenceSlot::BACKUP, state(25));
|
|
Telemetry::TransactionalLocationPersistence temp_persistence(temp_error);
|
|
output = state(98);
|
|
temp_error.fail_at = 4; // invalid live, then temporary read fails
|
|
CHECK(temp_persistence.load(output) ==
|
|
Telemetry::LocationPersistenceResult::IO_ERROR);
|
|
CHECK(marker(output) == 98);
|
|
temp_error.fail_at = 0;
|
|
temp_error.operations = 0;
|
|
CHECK(temp_persistence.load(output) ==
|
|
Telemetry::LocationPersistenceResult::RECOVERED_TEMP);
|
|
CHECK(marker(output) == 40);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
recoversByLiveTempBackupPriority();
|
|
failsClosedWhenUnavailableMissingOrCorrupt();
|
|
everyInterruptedSaveRetainsAValidGeneration();
|
|
validatesTempBeforeReplacingLive();
|
|
successfulPromotionIsCommittedWhenReadbackIoFails();
|
|
transientHigherPriorityIoNeverFallsBackOrRepairs();
|
|
std::cout << "location persistence: " << passed << " passed, "
|
|
<< failures << " failed\n";
|
|
return failures == 0 ? 0 : 1;
|
|
}
|