409 lines
19 KiB
C++
409 lines
19 KiB
C++
#include <IdentityStore.h>
|
|
#include "FileRead.h"
|
|
#include "FilePresence.h"
|
|
#include "PersistentStoreFormat.h"
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
|
|
#include "AtomicFileWriter.h"
|
|
#else
|
|
#include "ContactFileTransaction.h"
|
|
#endif
|
|
#include <helpers/TelemetryHistory.h>
|
|
#include <helpers/ExternalVoltageHistory.h>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
#define MAX_PATH_SIZE 64
|
|
#define OUT_PATH_UNKNOWN 0xff
|
|
#define OUT_PATH_FORCE_FLOOD 0xfe
|
|
#define MESH_DEBUG_PRINTLN(...) do {} while (0)
|
|
static uint32_t test_millis = 0;
|
|
static uint32_t millis() { return test_millis; }
|
|
|
|
#if defined(ESP32_PLATFORM)
|
|
static MemoryFS* metadata_fs = nullptr;
|
|
// Exercise production FilePresence's stat/error handling without a real mount.
|
|
extern "C" int stat(const char* path, struct stat*) noexcept {
|
|
if (!metadata_fs || metadata_fs->fail_metadata) { errno = EIO; return -1; }
|
|
assert(!strncmp(path, "/spiffs", 7));
|
|
if (metadata_fs->exists(path + 7)) return 0;
|
|
errno = ENOENT; return -1;
|
|
}
|
|
#endif
|
|
|
|
namespace mesh {
|
|
struct Packet {
|
|
uint8_t payload[184] = {};
|
|
size_t payload_len = 0;
|
|
uint8_t path_len = 0, path[MAX_PATH_SIZE] = {};
|
|
static bool isValidPathLen(uint8_t);
|
|
static size_t writePath(uint8_t*, const uint8_t*, uint8_t);
|
|
static uint8_t copyPath(uint8_t*, const uint8_t*, uint8_t);
|
|
};
|
|
struct Utils { static void toHex(char*, const uint8_t*, size_t); };
|
|
struct DataRouteState;
|
|
}
|
|
class RegionMap;
|
|
class CommonCLI {
|
|
public:
|
|
mesh::DataRouteState* _data_route = nullptr;
|
|
bool adoptLegacyDataTxPath(const uint8_t*, uint8_t);
|
|
bool getDataTxPath(const uint8_t*&, uint8_t&) const;
|
|
};
|
|
struct ClientInfo { bool admin = true; bool isAdmin() const { return admin; } };
|
|
class MyMesh {
|
|
public:
|
|
FILESYSTEM* _fs;
|
|
CommonCLI _cli;
|
|
struct Identity { uint8_t pub_key[32] = {}; } self_id;
|
|
mesh::TelemetryHistory telemetry_history;
|
|
mesh::ExternalVoltageHistory external_voltage_history;
|
|
bool telemetry_history_tx_prefs_healthy = false;
|
|
bool telemetry_history_tx_enabled = false;
|
|
uint8_t telemetry_history_tx_interval_days = 2;
|
|
uint8_t telemetry_history_tx_pending = 0;
|
|
bool telemetry_history_tx_manual = false;
|
|
uint8_t telemetry_history_tx_external_channel = 0;
|
|
uint8_t telemetry_history_tx_external_chunk = 0;
|
|
uint64_t telemetry_history_next_tx_uptime = 0;
|
|
uint64_t telemetry_history_tx_resume_uptime = 0;
|
|
uint64_t uptime_millis = 0;
|
|
uint32_t last_millis = 0;
|
|
std::vector<mesh::Packet> packets;
|
|
explicit MyMesh(FILESYSTEM& fs) : _fs(&fs) {}
|
|
void loadTelemetryHistoryTxPrefs();
|
|
bool saveTelemetryHistoryTxPrefs();
|
|
void serviceTelemetryHistoryTx();
|
|
bool sendTelemetryHistorySnapshot(mesh::TelemetryHistory::Series);
|
|
bool sendExternalVoltageHistorySnapshot(uint8_t, uint8_t);
|
|
void formatTelemetryHistoryTxStatus(char*, size_t) const;
|
|
void handle(char*, char*, ClientInfo*);
|
|
// Radio queue boundary. Payload formatting and producer scheduling are real.
|
|
mesh::Packet* createRawData(const uint8_t* data, size_t size) {
|
|
assert(size <= 184);
|
|
auto* packet = new mesh::Packet;
|
|
memcpy(packet->payload, data, size); packet->payload_len = size;
|
|
return packet;
|
|
}
|
|
bool sendDirect(mesh::Packet* packet, const uint8_t* path, uint8_t length) {
|
|
packet->path_len = mesh::Packet::copyPath(packet->path, path, length);
|
|
packets.push_back(*packet); delete packet; return true;
|
|
}
|
|
std::array<uint64_t, 8> state() const {
|
|
return {{telemetry_history_tx_enabled, telemetry_history_tx_interval_days,
|
|
telemetry_history_tx_pending, telemetry_history_tx_manual,
|
|
telemetry_history_tx_external_channel, telemetry_history_tx_external_chunk,
|
|
telemetry_history_next_tx_uptime, telemetry_history_tx_resume_uptime}};
|
|
}
|
|
std::string command(const char* text, ClientInfo* sender = nullptr) {
|
|
std::vector<char> input(text, text + strlen(text) + 1);
|
|
char reply[160] = {};
|
|
handle(input.data(), reply, sender); return reply;
|
|
}
|
|
};
|
|
#include "production.inc"
|
|
|
|
static const char target[] = "/telemetry_tx";
|
|
static const char temp[] = "/telemetry_tx.tmp";
|
|
static const char backup[] = "/telemetry_tx.bak";
|
|
static std::vector<uint8_t> current(bool enabled = true, uint8_t days = 7) {
|
|
return {'T', 'H', 'T', '3', uint8_t(enabled), days};
|
|
}
|
|
static std::vector<uint8_t> legacy(bool enabled = true, uint8_t length = 1,
|
|
uint8_t days = 7) {
|
|
std::vector<uint8_t> image(7 + MAX_PATH_SIZE, 0);
|
|
memcpy(image.data(), "THT2", 4);
|
|
image[4] = enabled; image[5] = length; image[6] = days;
|
|
for (size_t i = 0; i < MAX_PATH_SIZE; ++i) image[7 + i] = uint8_t(i + 0x12);
|
|
return image;
|
|
}
|
|
struct Fixture {
|
|
MemoryFS fs;
|
|
mesh::DataRouteState route;
|
|
MyMesh node;
|
|
Fixture() : node(fs) {
|
|
#if defined(ESP32_PLATFORM)
|
|
metadata_fs = &fs;
|
|
#endif
|
|
route.fs = &fs; node._cli._data_route = &route;
|
|
}
|
|
void boot() {
|
|
route = mesh::DataRouteState(); route.fs = &fs;
|
|
route.healthy = mesh::dataRouteLoad(route);
|
|
node.loadTelemetryHistoryTxPrefs();
|
|
}
|
|
void seed(const std::vector<uint8_t>& image = current()) {
|
|
fs.files[target] = image; boot();
|
|
}
|
|
void history() {
|
|
for (uint32_t i = 0; i < mesh::TelemetryHistory::BINARY_MAX_SAMPLES; ++i) {
|
|
node.telemetry_history.record(1000000 + i * 1800, 22, true, 3750, 0, 0, false);
|
|
}
|
|
}
|
|
void expectFault() {
|
|
assert(!node.telemetry_history_tx_prefs_healthy);
|
|
const auto before = node.state();
|
|
const auto writes = fs.writes;
|
|
node.serviceTelemetryHistoryTx();
|
|
assert(node.packets.empty() && node.state() == before);
|
|
assert(node.command("send telemetry.tx now") == "Err - telemetry.tx prefs unavailable");
|
|
assert(node.state() == before && fs.writes == writes);
|
|
char reply[160]; node.formatTelemetryHistoryTxStatus(reply, sizeof(reply));
|
|
assert(strstr(reply, " fault=prefs"));
|
|
ClientInfo unauthorized; unauthorized.admin = false;
|
|
assert(node.command("send telemetry.tx now", &unauthorized) == "Err - not permitted");
|
|
assert(!node.saveTelemetryHistoryTxPrefs() && fs.writes == writes);
|
|
}
|
|
};
|
|
|
|
static void checkFreshAndValid() {
|
|
Fixture f; f.boot();
|
|
assert(f.node.telemetry_history_tx_prefs_healthy);
|
|
assert(!f.node.telemetry_history_tx_enabled && f.node.telemetry_history_tx_interval_days == 2);
|
|
assert(f.fs.files.empty());
|
|
for (const bool enabled : {false, true}) {
|
|
for (const uint8_t days : {uint8_t(1), uint8_t(2), uint8_t(30)}) {
|
|
f.seed(current(enabled, days));
|
|
assert(f.node.telemetry_history_tx_prefs_healthy);
|
|
assert(f.node.telemetry_history_tx_enabled == enabled);
|
|
assert(f.node.telemetry_history_tx_interval_days == days);
|
|
assert(f.node.saveTelemetryHistoryTxPrefs() && f.fs.files.at(target) == current(enabled, days));
|
|
assert(!f.fs.exists(temp) && !f.fs.exists(backup));
|
|
char reply[160]; f.node.formatTelemetryHistoryTxStatus(reply, sizeof(reply));
|
|
assert(!strstr(reply, "fault="));
|
|
}
|
|
}
|
|
f.node._fs = nullptr; f.node.loadTelemetryHistoryTxPrefs(); f.expectFault();
|
|
}
|
|
|
|
static void checkLoadFailures() {
|
|
for (size_t length = 0; length <= 80; ++length) {
|
|
Fixture f; f.fs.files[target] = std::vector<uint8_t>(length, 0); f.boot();
|
|
f.expectFault(); assert(f.fs.files.at(target) == std::vector<uint8_t>(length, 0));
|
|
}
|
|
for (const auto& image : {current(), legacy()}) {
|
|
for (size_t limit = 0; limit < image.size(); ++limit) {
|
|
Fixture f; f.fs.files[target] = image; f.fs.read_limit[target] = limit;
|
|
f.boot(); f.expectFault(); assert(f.fs.files.at(target) == image);
|
|
f.fs.clearFaults(); f.boot(); assert(f.node.telemetry_history_tx_prefs_healthy);
|
|
}
|
|
Fixture f; f.fs.files[target] = image; f.fs.fail_read_open.insert(target);
|
|
f.boot(); f.expectFault(); assert(f.fs.files.at(target) == image);
|
|
}
|
|
for (unsigned value = 0; value <= 255; ++value) {
|
|
for (const bool old : {false, true}) {
|
|
for (const unsigned offset : {4U, old ? 6U : 5U}) {
|
|
Fixture f; auto image = old ? legacy() : current(); image[offset] = uint8_t(value);
|
|
f.fs.files[target] = image; f.boot();
|
|
const bool valid = offset == 4 ? value <= 1 : value >= 1 && value <= 30;
|
|
assert(f.node.telemetry_history_tx_prefs_healthy == valid);
|
|
if (!valid) f.expectFault();
|
|
}
|
|
}
|
|
}
|
|
#if !defined(RP2040_PLATFORM)
|
|
Fixture metadata;
|
|
metadata.fs.files[target] = current(); metadata.fs.fail_metadata = true;
|
|
metadata.boot(); metadata.expectFault();
|
|
assert(metadata.fs.files.at(target) == current());
|
|
#endif
|
|
}
|
|
|
|
static void checkLegacyMigration() {
|
|
for (const uint8_t length : {uint8_t(0), uint8_t(1), uint8_t(0x42), uint8_t(0x82)}) {
|
|
Fixture f; const auto image = legacy(true, length); f.seed(image);
|
|
assert(f.node.telemetry_history_tx_prefs_healthy && f.node.telemetry_history_tx_enabled);
|
|
assert(f.route.persisted && f.route.path_len == length);
|
|
const size_t bytes = (length & 63) * ((length >> 6) + 1);
|
|
assert(!memcmp(f.route.path, image.data() + 7, bytes));
|
|
assert(f.fs.files.at(target) == image); // loading never rewrites the legacy file
|
|
f.boot(); assert(f.node.telemetry_history_tx_prefs_healthy && f.route.path_len == length);
|
|
assert(f.node.saveTelemetryHistoryTxPrefs());
|
|
assert(f.fs.files.at(target) == current());
|
|
}
|
|
for (const uint8_t length : {uint8_t(0), uint8_t(0x42), uint8_t(OUT_PATH_UNKNOWN)}) {
|
|
Fixture f; f.route.path_len = length;
|
|
memset(f.route.path, 0xab, sizeof(f.route.path));
|
|
assert(mesh::dataRouteSave(f.route));
|
|
const auto saved_route = f.fs.files.at("/data_tx");
|
|
f.fs.files[target] = legacy(); const auto writes = f.fs.writes;
|
|
f.boot(); assert(f.node.telemetry_history_tx_prefs_healthy && f.route.path_len == length);
|
|
assert(f.fs.files.at("/data_tx") == saved_route && f.fs.writes == writes);
|
|
}
|
|
Fixture disabled; disabled.seed(legacy(false, OUT_PATH_UNKNOWN));
|
|
assert(disabled.node.telemetry_history_tx_prefs_healthy && !disabled.route.persisted);
|
|
for (unsigned length = 0; length <= 255; ++length) {
|
|
Fixture f; f.seed(legacy(true, uint8_t(length)));
|
|
const bool valid = length != OUT_PATH_UNKNOWN && length != OUT_PATH_FORCE_FLOOD
|
|
&& mesh::Packet::isValidPathLen(uint8_t(length));
|
|
assert(f.node.telemetry_history_tx_prefs_healthy == valid);
|
|
if (!valid) f.expectFault();
|
|
}
|
|
// The actual adoption helper must fail before applying enabled legacy state.
|
|
for (const int failure : {0, 1, 2}) {
|
|
Fixture f; f.fs.files[target] = legacy();
|
|
if (failure == 0) f.fs.fail_write_open.insert("/data_tx.tmp");
|
|
if (failure == 1) f.fs.write_limit["/data_tx.tmp"] = 107;
|
|
if (failure == 2) f.fs.fail_rename.insert({"/data_tx.tmp", "/data_tx"});
|
|
f.boot(); f.history(); f.expectFault();
|
|
assert(!f.node.telemetry_history_tx_enabled && !f.route.persisted);
|
|
assert(f.fs.files.at(target) == legacy() && !f.fs.exists("/data_tx"));
|
|
f.fs.clearFaults(); f.boot();
|
|
assert(f.node.telemetry_history_tx_prefs_healthy && f.route.persisted && f.route.path_len == 1);
|
|
assert(f.fs.files.at(target) == legacy());
|
|
}
|
|
Fixture bad_route; bad_route.fs.files["/data_tx"] = {'b', 'a', 'd'};
|
|
bad_route.seed(legacy()); bad_route.expectFault();
|
|
assert(bad_route.fs.files.at(target) == legacy());
|
|
}
|
|
|
|
static void verifyRollback(Fixture& f, const std::vector<uint8_t>& prior,
|
|
const std::array<uint64_t, 8>& state) {
|
|
assert(f.node.state() == state && f.node.telemetry_history_tx_prefs_healthy);
|
|
assert(f.fs.files.at(target) == prior);
|
|
assert(!f.fs.exists(temp));
|
|
f.fs.clearFaults(); f.boot();
|
|
assert(f.node.telemetry_history_tx_prefs_healthy && f.node.telemetry_history_tx_enabled);
|
|
assert(f.node.telemetry_history_tx_interval_days == 7);
|
|
assert(f.node.command("set telemetry.tx schedule off") == "OK - telemetry.tx schedule=off");
|
|
assert(f.fs.files.at(target) == current(false));
|
|
f.boot(); assert(f.node.telemetry_history_tx_prefs_healthy && !f.node.telemetry_history_tx_enabled);
|
|
}
|
|
|
|
static void checkWriteAndValidationFailures() {
|
|
for (const auto& prior : {current(), legacy()}) {
|
|
for (size_t limit = 0; limit < 6; ++limit) {
|
|
Fixture f; f.seed(prior); const auto before = f.node.state();
|
|
f.fs.write_limit[temp] = limit;
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
verifyRollback(f, prior, before);
|
|
}
|
|
for (const int failure : {0, 1}) {
|
|
Fixture f; f.seed(prior); const auto before = f.node.state();
|
|
if (failure == 0) f.fs.fail_write_open.insert(temp);
|
|
else f.fs.fail_read_open.insert(temp);
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
verifyRollback(f, prior, before);
|
|
}
|
|
for (size_t limit = 0; limit < 6; ++limit) {
|
|
Fixture f; f.seed(prior); const auto before = f.node.state();
|
|
f.fs.read_limit[temp] = limit;
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
verifyRollback(f, prior, before);
|
|
}
|
|
Fixture f; f.seed(prior); const auto before = f.node.state();
|
|
f.fs.fail_rename.insert({temp, target});
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
verifyRollback(f, prior, before);
|
|
}
|
|
for (const int failure : {0, 1}) {
|
|
Fixture f; f.seed();
|
|
if (failure == 0) f.fs.fail_read_open.insert(target);
|
|
else f.fs.read_limit[target] = 5;
|
|
const auto writes = f.fs.writes;
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
f.expectFault(); assert(f.fs.writes == writes && f.fs.files.at(target) == current());
|
|
}
|
|
// A failed fresh-install transaction is retryable only when still absent.
|
|
Fixture fresh; fresh.boot(); fresh.fs.write_limit[temp] = 5;
|
|
assert(fresh.node.command("set telemetry.tx schedule 5d") == "Err - unable to save telemetry.tx schedule");
|
|
assert(fresh.node.telemetry_history_tx_prefs_healthy && !fresh.fs.exists(target));
|
|
fresh.fs.clearFaults();
|
|
assert(fresh.node.command("set telemetry.tx schedule 5d") == "OK - telemetry.tx schedule=5d");
|
|
assert(fresh.fs.files.at(target) == current(true, 5));
|
|
}
|
|
|
|
static void checkRecoveryAndRenameFailures() {
|
|
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
|
|
Fixture recovered; recovered.fs.files[backup] = current();
|
|
recovered.boot(); assert(recovered.node.telemetry_history_tx_prefs_healthy);
|
|
assert(recovered.fs.files.at(target) == current() && !recovered.fs.exists(backup));
|
|
Fixture failed; failed.fs.files[backup] = current();
|
|
failed.fs.fail_rename.insert({backup, target});
|
|
failed.boot(); failed.expectFault();
|
|
assert(!failed.fs.exists(target) && failed.fs.files.at(backup) == current());
|
|
failed.fs.clearFaults(); failed.boot(); assert(failed.node.telemetry_history_tx_prefs_healthy);
|
|
Fixture corrupt; corrupt.fs.files[target] = {'b', 'a', 'd'};
|
|
corrupt.fs.files[backup] = current(); corrupt.boot(); corrupt.expectFault();
|
|
assert(corrupt.fs.files.at(target) == std::vector<uint8_t>({'b', 'a', 'd'}));
|
|
assert(corrupt.fs.files.at(backup) == current());
|
|
for (const int failure : {0, 1, 2}) {
|
|
Fixture f; f.seed(); const auto before = f.node.state();
|
|
if (failure == 0) { f.fs.files[temp] = {1}; f.fs.fail_remove.insert(temp); }
|
|
if (failure == 1) { f.fs.files[backup] = {1}; f.fs.fail_remove.insert(backup); }
|
|
if (failure == 2) f.fs.fail_rename.insert({target, backup});
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
assert(f.node.telemetry_history_tx_prefs_healthy && f.node.state() == before);
|
|
assert(f.fs.files.at(target) == current());
|
|
f.fs.clearFaults();
|
|
assert(f.node.command("set telemetry.tx schedule off") == "OK - telemetry.tx schedule=off");
|
|
}
|
|
Fixture gap; gap.seed(); gap.history(); const auto before = gap.node.state();
|
|
gap.fs.fail_rename.insert({temp, target}); gap.fs.fail_rename.insert({backup, target});
|
|
assert(gap.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
assert(gap.node.state() == before && gap.fs.files.at(backup) == current());
|
|
assert(!gap.fs.exists(target)); gap.expectFault();
|
|
gap.boot(); gap.expectFault(); // failed recovery remains fail-closed on reboot
|
|
gap.fs.clearFaults(); gap.boot();
|
|
assert(gap.node.telemetry_history_tx_prefs_healthy && gap.node.telemetry_history_tx_enabled);
|
|
assert(gap.fs.files.at(target) == current());
|
|
#endif
|
|
// Stale uncommitted candidates are never mistaken for a fresh durable image.
|
|
Fixture stale; stale.fs.files[temp] = current(); stale.boot();
|
|
assert(stale.node.telemetry_history_tx_prefs_healthy && !stale.node.telemetry_history_tx_enabled);
|
|
assert(stale.node.command("set telemetry.tx schedule 5d") == "OK - telemetry.tx schedule=5d");
|
|
assert(stale.fs.files.at(target) == current(true, 5) && !stale.fs.exists(temp));
|
|
}
|
|
|
|
static void checkAmbiguousPublicationAndServiceGate() {
|
|
for (const int failure : {0, 1}) {
|
|
Fixture f; f.seed(); f.history();
|
|
f.node.telemetry_history_tx_pending = TELEMETRY_HISTORY_TX_TEMPERATURE | TELEMETRY_HISTORY_TX_VOLTAGE;
|
|
f.node.telemetry_history_tx_manual = true;
|
|
f.node.telemetry_history_tx_resume_uptime = 10000000;
|
|
const auto before = f.node.state();
|
|
if (failure == 0) f.fs.fail_final_read = true;
|
|
else f.fs.corrupt_publish = true;
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
assert(f.node.state() == before); f.expectFault();
|
|
const auto published = f.fs.files.at(target);
|
|
f.boot(); f.expectFault();
|
|
assert(f.fs.files.at(target) == published);
|
|
f.fs.clearFaults(); f.boot();
|
|
if (failure == 0) {
|
|
assert(f.node.telemetry_history_tx_prefs_healthy && !f.node.telemetry_history_tx_enabled);
|
|
assert(f.fs.files.at(target) == current(false));
|
|
} else f.expectFault();
|
|
}
|
|
for (int limit = 0; limit < 6; ++limit) {
|
|
Fixture f; f.seed(); f.history();
|
|
f.fs.final_read_limit = limit;
|
|
assert(f.node.command("set telemetry.tx schedule off") == "Err - unable to save telemetry.tx schedule");
|
|
assert(f.fs.files.at(target) == current(false));
|
|
f.expectFault(); f.boot(); f.expectFault();
|
|
assert(f.fs.files.at(target) == current(false));
|
|
f.fs.clearFaults(); f.boot();
|
|
assert(f.node.telemetry_history_tx_prefs_healthy && !f.node.telemetry_history_tx_enabled);
|
|
}
|
|
Fixture healthy; healthy.seed(); healthy.history();
|
|
healthy.node.serviceTelemetryHistoryTx();
|
|
assert(healthy.node.packets.size() == 1);
|
|
assert(healthy.node.packets[0].payload_len == 184);
|
|
assert(!memcmp(healthy.node.packets[0].payload, "TTB1", 4));
|
|
test_millis += 2000; healthy.node.serviceTelemetryHistoryTx(); test_millis = 0;
|
|
assert(healthy.node.packets.size() == 2 && !memcmp(healthy.node.packets[1].payload, "TVB1", 4));
|
|
assert(healthy.node.command("send telemetry.tx now").find("OK - telemetry.tx queued") == 0);
|
|
}
|
|
|
|
int main() {
|
|
checkFreshAndValid();
|
|
checkLoadFailures();
|
|
checkLegacyMigration();
|
|
checkWriteAndValidationFailures();
|
|
checkRecoveryAndRenameFailures();
|
|
checkAmbiguousPublicationAndServiceGate();
|
|
puts("telemetry persistence checks passed");
|
|
}
|