Files
MeshCore/test/fixtures/common_prefs_apply_reply.cpp
T

321 lines
11 KiB
C++

// Insert only actual production parsing and the four bounded CLI branches.
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <helpers/CLICommandUtils.h>
#define BRIDGE_MAX_BAUD 115200
struct NodePrefs {
uint8_t bridge_enabled = 0;
uint8_t rs232_bridge_enabled = 0;
uint32_t bridge_baud = 9600;
uint8_t bridge_uart = 1;
uint8_t espnow_bridge_enabled = 1;
bool operator==(const NodePrefs& other) const {
return bridge_enabled == other.bridge_enabled
&& rs232_bridge_enabled == other.rs232_bridge_enabled
&& bridge_baud == other.bridge_baud && bridge_uart == other.bridge_uart
&& espnow_bridge_enabled == other.espnow_bridge_enabled;
}
};
struct Event {
std::string kind;
bool enabled;
NodePrefs prefs;
};
struct Sensors {
bool gps = false;
bool conflict = false;
bool yield = true;
bool gpsUsesSerialUart(uint8_t) const { return gps; }
bool gpsSerialTransportMayConflict(uint8_t) const { return conflict; }
bool gpsSerialTransportCanYield(uint8_t) const { return yield; }
};
struct Callbacks {
NodePrefs* prefs;
std::vector<Event> events;
std::vector<bool> responses;
size_t response_index = 0;
bool running = false;
bool save_result = true;
unsigned save_calls = 0;
unsigned running_queries = 0;
bool result(const char* kind, bool enabled) {
events.push_back({kind, enabled, *prefs});
assert(response_index < responses.size());
const bool accepted = responses[response_index++];
// A failed apply can have torn down its previous runtime owner. This
// forces rollback rather than accidentally hiding that branch in tests.
running = accepted && enabled;
return accepted;
}
bool setRs232BridgeState(bool enabled) { return result("state", enabled); }
bool restartRs232Bridge() { return result("restart", true); }
bool isRs232BridgeRunning() { ++running_queries; return running; }
bool save() {
events.push_back({"save", false, *prefs});
++save_calls;
return save_result;
}
};
struct CommonCLI {
NodePrefs* _prefs;
Callbacks* _callbacks;
Sensors* _sensors;
bool trySavePrefs() { return _callbacks->save(); }
void rs232(const char*, char*);
void bridge(const char*, char*);
void baud(const char*, char*);
void uart(const char*, char*);
};
@PARSE_ON_OFF@
@PRODUCTION_BRANCHES@
enum class Branch { Rs232, Bridge, Baud, Uart };
static bool isEnable(Branch branch) {
return branch == Branch::Rs232 || branch == Branch::Bridge;
}
static uint8_t& intent(NodePrefs& prefs) {
#ifdef WITH_MQTT_BRIDGE
return prefs.rs232_bridge_enabled;
#else
return prefs.bridge_enabled;
#endif
}
static NodePrefs initial(bool enabled) {
NodePrefs prefs;
intent(prefs) = enabled;
#ifdef WITH_MQTT_BRIDGE
prefs.bridge_enabled = !enabled; // Independent MQTT intent must survive.
#else
prefs.rs232_bridge_enabled = enabled;
#endif
return prefs;
}
static void run(CommonCLI& cli, Branch branch, bool enabled, char* reply,
bool unchanged = false) {
switch (branch) {
case Branch::Rs232:
cli.rs232(enabled ? "rs232.enabled on" : "rs232.enabled off", reply);
break;
case Branch::Bridge:
#ifndef WITH_MQTT_BRIDGE
cli.bridge(enabled ? "bridge.enabled on" : "bridge.enabled off", reply);
#else
assert(false && "standalone bridge branch is not an MQTT command");
#endif
break;
case Branch::Baud:
cli.baud(unchanged ? "bridge.baud 9600" : "bridge.baud 19200", reply);
break;
case Branch::Uart:
cli.uart(unchanged ? "bridge.uart 1" : "bridge.uart 2", reply);
break;
}
}
static NodePrefs candidateFor(NodePrefs before, Branch branch, bool enabled,
bool unchanged = false) {
if (isEnable(branch)) intent(before) = enabled;
else if (branch == Branch::Baud) before.bridge_baud = unchanged ? 9600 : 19200;
else before.bridge_uart = unchanged ? 1 : 2;
return before;
}
static const char* failedReply(Branch branch, bool applied, bool restored) {
if (applied) {
return restored ? "Error: save unconfirmed; reboot may differ"
: "Error: save unconfirmed; runtime restore failed; reboot may differ";
}
switch (branch) {
case Branch::Rs232:
return restored ? "Error: RS232 change failed; setting unchanged"
: "Error: RS232 change failed; previous runtime could not be restored";
case Branch::Bridge:
return restored ? "Error: bridge state change failed; setting unchanged"
: "Error: bridge state change failed; previous runtime state could not be restored";
case Branch::Baud:
return restored ? "Error: bridge failed to restart; baud unchanged"
: "Error: bridge failed to restart; previous baud could not be restored";
case Branch::Uart:
return restored ? "Error: bridge failed to restart; UART unchanged"
: "Error: bridge failed to restart; previous UART could not be restored";
}
assert(false);
return "";
}
static unsigned cases = 0;
static void matrixCase(Branch branch, bool previous, bool applied, bool saved,
bool restored) {
NodePrefs prefs = initial(previous);
const NodePrefs before = prefs;
const bool requested = !previous;
const NodePrefs candidate = candidateFor(before, branch, requested);
Callbacks callbacks{&prefs, {}, {applied, restored}};
callbacks.running = previous;
callbacks.save_result = saved;
Sensors sensors;
CommonCLI cli{&prefs, &callbacks, &sensors};
char reply[160] = {};
run(cli, branch, requested, reply);
assert(callbacks.save_calls == (applied ? 1u : 0u));
assert(callbacks.events[0].kind == (isEnable(branch) ? "state" : "restart"));
assert(callbacks.events[0].prefs == candidate);
if (isEnable(branch)) assert(callbacks.events[0].enabled == requested);
if (applied) {
assert(callbacks.events[1].kind == "save");
assert(callbacks.events[1].prefs == candidate);
}
if (applied && saved) {
assert(!strcmp(reply, "OK"));
assert(prefs == candidate);
assert(callbacks.events.size() == 2);
assert(callbacks.response_index == 1);
} else {
assert(!strcmp(reply, failedReply(branch, applied, restored)));
assert(prefs == before);
assert(callbacks.events.size() == (applied ? 3u : 2u));
const Event& rollback = callbacks.events.back();
assert(rollback.kind == (isEnable(branch) ? "state" : "restart"));
assert(rollback.prefs == before);
assert(rollback.enabled == (isEnable(branch) ? previous : true));
assert(callbacks.response_index == 2);
if (applied) {
assert(strstr(reply, "unconfirmed"));
assert(strstr(reply, "reboot may differ"));
assert(!strstr(reply, "unchanged"));
} else {
assert(!strstr(reply, "unconfirmed"));
}
}
++cases;
}
static void disabledSettingCase(Branch branch, bool saved, bool restored) {
NodePrefs prefs = initial(false);
const NodePrefs before = prefs;
const NodePrefs candidate = candidateFor(before, branch, false);
Callbacks callbacks{&prefs, {}, {restored}};
callbacks.save_result = saved;
Sensors sensors;
CommonCLI cli{&prefs, &callbacks, &sensors};
char reply[160] = {};
run(cli, branch, false, reply);
// A disabled bridge needs no runtime restart to apply UART/baud changes.
assert(callbacks.save_calls == 1);
assert(callbacks.events[0].kind == "save");
assert(callbacks.events[0].prefs == candidate);
if (saved) {
assert(!strcmp(reply, "OK"));
assert(prefs == candidate);
assert(callbacks.events.size() == 1);
assert(callbacks.response_index == 0);
} else {
assert(!strcmp(reply, failedReply(branch, true, restored)));
assert(prefs == before);
assert(callbacks.events.size() == 2);
assert(callbacks.events[1].kind == "state");
assert(!callbacks.events[1].enabled);
assert(callbacks.events[1].prefs == before);
assert(callbacks.response_index == 1);
}
++cases;
}
static void healthyCase(Branch branch, bool previous, bool gps, bool unchanged) {
NodePrefs prefs = initial(previous);
const NodePrefs before = prefs;
const bool requested = unchanged ? previous : !previous;
const NodePrefs candidate = candidateFor(before, branch, requested, unchanged);
const bool applies = isEnable(branch) || previous;
Callbacks callbacks{&prefs, {}, {true}};
callbacks.running = previous;
Sensors sensors;
sensors.gps = gps;
CommonCLI cli{&prefs, &callbacks, &sensors};
char reply[160] = {};
run(cli, branch, requested, reply, unchanged);
const bool paused = gps && ((isEnable(branch) && requested)
|| (branch == Branch::Uart && previous));
assert(!strcmp(reply, paused ? "OK - UART GPS paused" : "OK"));
assert(prefs == candidate);
assert(callbacks.save_calls == 1);
assert(callbacks.events.size() == (applies ? 2u : 1u));
assert(callbacks.response_index == (applies ? 1u : 0u));
for (const Event& event : callbacks.events) assert(event.prefs == candidate);
++cases;
}
#ifndef WITH_MQTT_BRIDGE
static void alreadyRestoredCase() {
NodePrefs prefs = initial(true);
const NodePrefs before = prefs;
Callbacks callbacks{&prefs, {}, {true}};
callbacks.running = true;
callbacks.save_result = false;
Sensors sensors;
CommonCLI cli{&prefs, &callbacks, &sensors};
char reply[160] = {};
// A successfully applied same-value ON leaves the old runtime running.
// A refused disable also leaves it stopped in this adapter and must call
// the restoration callback instead, as covered by matrixCase above.
run(cli, Branch::Bridge, true, reply, true);
assert(!strcmp(reply, "Error: save unconfirmed; reboot may differ"));
assert(prefs == before);
assert(callbacks.events.size() == 2);
assert(callbacks.response_index == 1);
assert(callbacks.running_queries == 1);
++cases;
}
#endif
int main() {
const Branch branches[] = {Branch::Rs232,
#ifndef WITH_MQTT_BRIDGE
Branch::Bridge,
#endif
Branch::Baud, Branch::Uart};
for (Branch branch : branches) {
for (bool applied : {false, true}) {
for (bool saved : {false, true}) {
for (bool restored : {false, true}) {
matrixCase(branch, true, applied, saved, restored);
if (isEnable(branch)) matrixCase(branch, false, applied, saved, restored);
}
}
}
for (bool previous : {false, true}) {
for (bool gps : {false, true}) {
for (bool unchanged : {false, true}) {
healthyCase(branch, previous, gps, unchanged);
}
}
}
}
for (Branch branch : {Branch::Baud, Branch::Uart}) {
for (bool saved : {false, true}) {
for (bool restored : {false, true}) disabledSettingCase(branch, saved, restored);
}
}
#ifndef WITH_MQTT_BRIDGE
alreadyRestoredCase();
#endif
printf("%u actual CommonCLI apply/save/restore cases passed\n", cases);
}