108 lines
4.9 KiB
Python
108 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Exercise production role setters against failed saves and active flash uploads."""
|
|
from pathlib import Path
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class ManagementPasswordCommandsTest(unittest.TestCase):
|
|
def test_infrastructure_and_companion_publish_only_confirmed_saves(self):
|
|
cli = (ROOT / "src/helpers/CommonCLI.cpp").read_text()
|
|
start = cli.index(' } else if (memcmp(command, "password ", 9) == 0) {')
|
|
end = cli.index(' } else if (memcmp(command, "clear stats",', start)
|
|
infrastructure = cli[start:end].replace('} else if (', 'if (', 1) + '\n }\n'
|
|
companion = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text()
|
|
start = companion.index('bool MyMesh::handleLocalControlCommand(')
|
|
end = companion.index(' if (mesh::powerdiag::handleCommand(', start)
|
|
companion = companion[start:end] + ' return false;\n}\n'
|
|
source = r'''
|
|
#include <helpers/esp32/ManagementPassword.h>
|
|
#include <helpers/CLICommandUtils.h>
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <initializer_list>
|
|
using namespace mesh::esp32::managementPassword;
|
|
bool save_ok = true; int saves = 0;
|
|
bool durableSave() {
|
|
++saves; Snapshot current; assert(snapshot(current));
|
|
UploadGuard blocked(current.generation); assert(!blocked.acquired());
|
|
return save_ok;
|
|
}
|
|
namespace mesh { namespace esp32 { namespace companionAdminPassword {
|
|
bool save(const char*) { return durableSave(); }
|
|
} } }
|
|
struct StrHelper {
|
|
static void strncpy(char* out, const char* in, size_t size) { ::strncpy(out, in, size); out[size - 1] = 0; }
|
|
};
|
|
struct NodePrefs { char password[16] = "password"; };
|
|
class CommonCLI {
|
|
public:
|
|
NodePrefs prefs; NodePrefs* _prefs = &prefs;
|
|
bool trySavePrefs() { return durableSave(); }
|
|
void command(char* command, char* reply) {
|
|
@INFRASTRUCTURE@
|
|
}
|
|
};
|
|
class MyMesh {
|
|
public:
|
|
char _management_password[16] = "password";
|
|
bool _management_password_ready = true;
|
|
bool handleLocalControlCommand(const char*, char*, size_t);
|
|
void command(char* command, char* reply) { handleLocalControlCommand(command, reply, 160); }
|
|
};
|
|
@COMPANION@
|
|
void unchanged(uint32_t generation) {
|
|
Snapshot current; assert(snapshot(current));
|
|
assert(current.generation == generation && !strcmp(current.password, "password"));
|
|
}
|
|
template <typename Role> void test(Role& role) {
|
|
char command[80], reply[160]; Snapshot initial;
|
|
assert(publish("password") && snapshot(initial));
|
|
for (const char* value : {"", "1234567890123456", "bad\nvalue", "bad\rvalue"}) {
|
|
snprintf(command, sizeof(command), "password %s", value);
|
|
reply[0] = 0; const int before = saves; role.command(command, reply);
|
|
assert(!strncmp(reply, "Error:", 6) && saves == before); unchanged(initial.generation);
|
|
}
|
|
save_ok = false; strcpy(command, "password replacement"); role.command(command, reply);
|
|
assert(strstr(reply, "unconfirmed")); unchanged(initial.generation);
|
|
save_ok = true;
|
|
{
|
|
UploadGuard upload(initial.generation); assert(upload.acquired());
|
|
const int before = saves; role.command(command, reply);
|
|
assert(strstr(reply, "busy") && saves == before); unchanged(initial.generation);
|
|
}
|
|
role.command(command, reply); assert(!strcmp(reply, "password now: replacement"));
|
|
Snapshot changed; assert(snapshot(changed));
|
|
assert(!strcmp(changed.password, "replacement") && changed.generation == initial.generation + 1);
|
|
role.command(command, reply); assert(snapshot(changed) && changed.generation == initial.generation + 1);
|
|
strcpy(command, "password leading space"); role.command(command, reply);
|
|
assert(snapshot(changed) && !strcmp(changed.password, " leading space"));
|
|
}
|
|
int main() {
|
|
CommonCLI infrastructure; test(infrastructure);
|
|
assert(!strcmp(infrastructure.prefs.password, " leading space"));
|
|
MyMesh companion; test(companion);
|
|
assert(companion._management_password_ready && !strcmp(companion._management_password, " leading space"));
|
|
}
|
|
'''.replace("@INFRASTRUCTURE@", infrastructure).replace("@COMPANION@", companion)
|
|
with tempfile.TemporaryDirectory(prefix="management-command-") as temporary:
|
|
work = Path(temporary)
|
|
(work / "test.cpp").write_text(source)
|
|
result = subprocess.run([os.environ.get("CXX", "g++"), "-std=c++11", "-Wall", "-Wextra", "-Werror",
|
|
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie",
|
|
"-DMESH_MANAGEMENT_PASSWORD_HOST_TEST=1", "-DESP_PLATFORM=1", "-DESP32_PLATFORM=1", '-DWIFI_SSID=""',
|
|
"-I", str(ROOT / "src"), str(work / "test.cpp"), "-o", str(work / "test")],
|
|
capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
result = subprocess.run([str(work / "test")], capture_output=True, text=True, timeout=15)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|