378 lines
21 KiB
Python
378 lines
21 KiB
Python
#!/usr/bin/env python3
|
|
"""Execute owner-bound room mail LoRa/CLI handlers against their real store."""
|
|
|
|
from pathlib import Path
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_room_board_store import FILESYSTEM_HARNESS, LFS_MOCK, STAT_MOCK
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
TESTS = r'''
|
|
#define FILE_O_WRITE 1
|
|
#include <helpers/RoomMailProtocol.h>
|
|
using Result = mesh::RoomMailResult;
|
|
static unsigned scenarios = 0;
|
|
static uint8_t a[32], b[32], c[32];
|
|
static std::string key(const uint8_t* bytes) {
|
|
char out[65]; mesh::room_mail_protocol_detail::hex(bytes, out); return out;
|
|
}
|
|
static std::map<std::string, std::vector<uint8_t>> snapshot(const MemoryFS& fs) {
|
|
std::map<std::string, std::vector<uint8_t>> out;
|
|
for (const auto& entry : fs.files) out[entry.first] = *entry.second;
|
|
return out;
|
|
}
|
|
static std::vector<uint8_t> request(MemoryFS& fs, const uint8_t* owner,
|
|
const std::vector<uint8_t>& input, size_t capacity = 160,
|
|
bool can_send = true, uint64_t nonce = 1) {
|
|
metadata_filesystem = &fs;
|
|
std::vector<uint8_t> output(capacity + 2, 0x7f);
|
|
const size_t used = mesh::handleRoomMailRequest(&fs, owner, can_send, nonce,
|
|
input.data(), input.size(), output.data() + 1, capacity, 123456);
|
|
assert(used <= capacity && output.front() == 0x7f && output.back() == 0x7f);
|
|
++scenarios; return std::vector<uint8_t>(output.begin() + 1, output.begin() + 1 + used);
|
|
}
|
|
static std::string command(MemoryFS& fs, const uint8_t* owner,
|
|
const std::string& text, size_t capacity = 160, bool can_send = true,
|
|
uint64_t nonce = 1, bool handled = true) {
|
|
metadata_filesystem = &fs;
|
|
std::vector<char> output(capacity + 2, 0x7f);
|
|
assert(mesh::handleRoomMailCommand(&fs, owner, can_send, nonce, text.c_str(),
|
|
output.data() + 1, capacity, 123456) == handled);
|
|
assert(output.front() == 0x7f && output.back() == 0x7f); ++scenarios;
|
|
if (!capacity || !handled) return "";
|
|
assert(memchr(output.data() + 1, 0, capacity)); return output.data() + 1;
|
|
}
|
|
static mesh::RoomMailStatus status(MemoryFS& fs, const uint8_t* owner) {
|
|
metadata_filesystem = &fs; mesh::RoomMailStatus out;
|
|
assert(mesh::getRoomMailStatus(&fs, owner, out) == Result::Success); return out;
|
|
}
|
|
static std::vector<uint8_t> edit(uint8_t op, uint32_t revision, uint8_t value) {
|
|
std::vector<uint8_t> out(7); out[0] = 11; out[1] = op;
|
|
mesh::room_mail_protocol_detail::put32(out.data() + 2, revision); out[6] = value; return out;
|
|
}
|
|
static std::vector<uint8_t> editKey(uint8_t op, uint32_t revision, const uint8_t* bytes) {
|
|
std::vector<uint8_t> out(38); out[0] = 11; out[1] = op;
|
|
mesh::room_mail_protocol_detail::put32(out.data() + 2, revision);
|
|
memcpy(out.data() + 6, bytes, 32); return out;
|
|
}
|
|
static std::vector<uint8_t> indexRequest(uint32_t revision = 0, uint8_t cursor = 0) {
|
|
std::vector<uint8_t> out(7); out[0] = 11; out[1] = 1;
|
|
mesh::room_mail_protocol_detail::put32(out.data() + 2, revision); out[6] = cursor; return out;
|
|
}
|
|
static std::vector<uint8_t> settings(uint32_t revision = 0, uint8_t cursor = 0) {
|
|
auto out = indexRequest(revision, cursor); out[1] = 0; return out;
|
|
}
|
|
static std::vector<uint8_t> read(uint32_t id, uint16_t offset = 0) {
|
|
std::vector<uint8_t> out(8); out[0] = 11; out[1] = 2;
|
|
mesh::room_mail_protocol_detail::put32(out.data() + 2, id);
|
|
mesh::room_mail_protocol_detail::put16(out.data() + 6, offset); return out;
|
|
}
|
|
static std::vector<uint8_t> send(const uint8_t* recipient, const std::string& body) {
|
|
std::vector<uint8_t> out(36 + body.size()); out[0] = 11; out[1] = 3;
|
|
memcpy(out.data() + 2, recipient, 32);
|
|
mesh::room_mail_protocol_detail::put16(out.data() + 34, uint16_t(body.size()));
|
|
memcpy(out.data() + 36, body.data(), body.size()); return out;
|
|
}
|
|
static std::vector<uint8_t> remove(uint8_t op, uint32_t id) {
|
|
std::vector<uint8_t> out(6); out[0] = 11; out[1] = op;
|
|
mesh::room_mail_protocol_detail::put32(out.data() + 2, id); return out;
|
|
}
|
|
static std::vector<uint8_t> padded(std::vector<uint8_t> payload) {
|
|
payload.resize(((payload.size() + 4 + 15) / 16) * 16 - 4, 0); return payload;
|
|
}
|
|
static uint32_t id(const std::vector<uint8_t>& response) {
|
|
assert(response.size() == 7 && (response[2] == 0 || response[2] == 8));
|
|
return mesh::room_mail_protocol_detail::get32(response.data() + 3);
|
|
}
|
|
int main() {
|
|
memset(a, 0xa1, 32); memset(b, 0xb2, 32); memset(c, 0xc3, 32);
|
|
MemoryFS fs;
|
|
auto response = request(fs, a, settings());
|
|
assert(response.size() == 13 && response[2] == 0 && response[7] == 0
|
|
&& response[8] == 0 && response[9] == 0 && response[11] == 255 && response[12] == 0);
|
|
assert(fs.files.empty());
|
|
assert(request(fs, a, indexRequest())[9] == 255 && fs.files.empty());
|
|
assert(command(fs, a, "mail settings").find("mode=closed delivery=chat queued=0") != std::string::npos);
|
|
assert(command(fs, a, "mail mode public", 160, false) == "OK");
|
|
assert(command(fs, a, "mail delivery mailbox", 160, false) == "OK");
|
|
auto own = status(fs, a); assert(own.settings.mailbox_only);
|
|
response = request(fs, a, editKey(6, own.revision, b)); assert(response[2] == 0);
|
|
own = status(fs, a); assert(own.settings.allowed_count == 1);
|
|
const uint32_t before_policy = own.revision;
|
|
response = request(fs, a, edit(8, own.revision, 2), 7, false); assert(response[2] == 0);
|
|
response = request(fs, a, edit(9, before_policy, 0)); assert(response[2] == 5);
|
|
own = status(fs, a); assert(own.settings.mode == mesh::RoomMailMode::Private && own.settings.mailbox_only);
|
|
response = request(fs, a, settings(), 45);
|
|
assert(response.size() == 45 && response[9] == 1 && response[11] == 255
|
|
&& memcmp(response.data() + 13, b, 32) == 0);
|
|
assert(request(fs, b, settings())[9] == 0); // Another key cannot enumerate the allow list.
|
|
assert(request(fs, c, send(a, "not authorized"))[2] == 6);
|
|
assert(request(fs, b, send(a, "writer permission required"), 160, false)[2] == 6);
|
|
assert(command(fs, b, "mail send " + key(a) + " blocked by role", 160, false) == "Error permission denied");
|
|
assert(status(fs, a).count == 0);
|
|
|
|
const std::string full_body(512, 'p'); const uint64_t nonce = UINT64_C(0x1234567800000009);
|
|
response = request(fs, b, send(a, full_body), 160, true, nonce); const uint32_t first = id(response);
|
|
own = status(fs, a); assert(own.count == 1);
|
|
response = request(fs, b, send(a, full_body), 160, true, nonce);
|
|
assert(response[2] == 8 && id(response) == first && status(fs, a).count == 1);
|
|
assert(request(fs, b, send(a, "changed"), 160, true, nonce)[2] == 9);
|
|
assert(request(fs, b, send(a, "different nonce high bits"), 160, true, nonce + (UINT64_C(1) << 32))[2] == 0);
|
|
assert(status(fs, a).count == 2);
|
|
|
|
const auto preserved = snapshot(fs);
|
|
for (const uint8_t* stranger : {b, c}) {
|
|
for (const auto& payload : {read(first), remove(4, first), remove(5, first)}) {
|
|
response = request(fs, stranger, payload);
|
|
assert(response[2] == 4 || response[2] == 6);
|
|
assert(response.size() <= 7);
|
|
}
|
|
const std::string output = command(fs, stranger, "mail read " + std::to_string(first));
|
|
assert(output == "Error not found" || output == "Error permission denied");
|
|
}
|
|
assert(snapshot(fs) == preserved && status(fs, a).count == 2);
|
|
own = status(fs, a);
|
|
response = request(fs, a, indexRequest());
|
|
assert(response[2] == 0 && response[7] == 2 && response[9] == 255 && response[10] == 2);
|
|
assert(response.size() == 87 && memcmp(response.data() + 15, b, 32) == 0);
|
|
response = request(fs, a, indexRequest(own.revision, 1), 49);
|
|
assert(response.size() == 49 && response[9] == 255 && response[10] == 1);
|
|
assert(request(fs, a, indexRequest(own.revision - 1))[2] == 5);
|
|
assert(request(fs, a, indexRequest(0, 1))[2] == 5);
|
|
assert(request(fs, a, indexRequest(own.revision, 3))[2] == 1);
|
|
|
|
// Check every route reply capacity, including capacities too short for a
|
|
// complete private header or entry. Response canaries prohibit overflows.
|
|
for (size_t capacity = 0; capacity <= 200; ++capacity) {
|
|
response = request(fs, a, indexRequest(), capacity);
|
|
if (capacity < 3) assert(response.empty());
|
|
else if (capacity < 49) assert(response.size() == 3 && response[2] == 1);
|
|
else {
|
|
assert(response[2] == 0 && response.size() == 11 + size_t(response[10]) * 38);
|
|
assert(response[10] >= 1 && response[10] <= 2);
|
|
}
|
|
response = request(fs, a, read(first), capacity);
|
|
if (capacity < 3) assert(response.empty());
|
|
else if (capacity <= 44) assert(response.size() == 3 && response[2] == 1);
|
|
else {
|
|
assert(response[2] == 0 && response.size() == 44 + size_t(response[11]));
|
|
assert(response[11] == std::min(size_t(128), capacity - 44));
|
|
assert(memcmp(response.data() + 12, b, 32) == 0);
|
|
}
|
|
response = request(fs, a, settings(), capacity);
|
|
if (capacity < 3) assert(response.empty());
|
|
else if (capacity < 45) assert(response.size() == 3 && response[2] == 1);
|
|
else assert(response.size() == 45 && response[2] == 0);
|
|
}
|
|
std::string assembled;
|
|
for (uint16_t offset = 0; offset < 512;) {
|
|
response = request(fs, a, read(first, offset), 90);
|
|
assert(response[2] == 0 && response[11] && response[11] <= 46);
|
|
assert(mesh::room_mail_protocol_detail::get16(response.data() + 7) == offset);
|
|
assert(mesh::room_mail_protocol_detail::get16(response.data() + 9) == 512);
|
|
assembled.append(reinterpret_cast<const char*>(response.data() + 44), response[11]); offset += response[11];
|
|
}
|
|
assert(assembled == full_body);
|
|
response = request(fs, a, read(first, 512), 44); assert(response.size() == 44 && response[11] == 0);
|
|
assert(request(fs, a, read(first, 513))[2] == 1);
|
|
assembled.clear();
|
|
for (size_t offset = 0; offset < 512;) {
|
|
const std::string output = command(fs, a, "mail read " + std::to_string(first) + " " + std::to_string(offset), 157);
|
|
const size_t data = output.find("text="); assert(data != std::string::npos);
|
|
const std::string bytes = output.substr(data + 5); assert(!bytes.empty());
|
|
assembled += bytes; offset += bytes.size();
|
|
}
|
|
assert(assembled == full_body);
|
|
assert(command(fs, a, "mail read " + std::to_string(first) + " 512").find("next=512 total=512 text=") != std::string::npos);
|
|
assert(command(fs, a, "mail inbox") == command(fs, a, "mail list"));
|
|
assert(command(fs, a, "mail list", 80).find("..") != std::string::npos);
|
|
{
|
|
MemoryFS utf_fs;
|
|
assert(command(utf_fs, a, "mail mode public") == "OK");
|
|
std::string unicode;
|
|
for (size_t i = 0; i < 128; ++i) unicode += "\xf0\x9f\x93\xa1";
|
|
const uint32_t unicode_id = id(request(utf_fs, b, send(a, unicode), 160, true, 300));
|
|
std::string text;
|
|
for (size_t offset = 0; offset < unicode.size();) {
|
|
const std::string output = command(utf_fs, a, "mail read " + std::to_string(unicode_id) + " " + std::to_string(offset), 83);
|
|
const size_t marker = output.find("text="); assert(marker != std::string::npos);
|
|
const std::string part = output.substr(marker + 5); assert(!part.empty() && part.size() % 4 == 0);
|
|
assert(mesh::room_mail_detail::validBody(part.data(), part.size()));
|
|
unsigned long got_id, got_offset, next; unsigned total;
|
|
assert(sscanf(output.c_str(), "id=%lu off=%lu next=%lu total=%u text=", &got_id, &got_offset, &next, &total) == 4);
|
|
assert(got_id == unicode_id && got_offset == offset && next == offset + part.size() && total == unicode.size());
|
|
text += part; offset = next;
|
|
}
|
|
assert(text == unicode);
|
|
for (unsigned offset = 1; offset <= 3; ++offset)
|
|
assert(command(utf_fs, a, "mail read " + std::to_string(unicode_id) + " " + std::to_string(offset)) == "Error offset inside UTF-8 character");
|
|
assert(request(utf_fs, a, read(unicode_id, 1))[2] == 0); // Binary reads retain arbitrary byte offsets.
|
|
}
|
|
for (size_t capacity = 0; capacity <= 160; ++capacity) {
|
|
command(fs, a, "mail settings", capacity);
|
|
command(fs, a, "mail list", capacity);
|
|
command(fs, a, "mail read " + std::to_string(first), capacity);
|
|
}
|
|
|
|
// No mutation is admitted if its complete confirmation cannot be returned.
|
|
for (size_t capacity = 0; capacity < 7; ++capacity) {
|
|
const auto before = snapshot(fs);
|
|
for (const auto& payload : {send(a, "capacity"), remove(4, first), remove(5, first),
|
|
edit(8, own.revision, 1), edit(9, own.revision, 0), editKey(6, own.revision, c), editKey(7, own.revision, b)})
|
|
request(fs, a, payload, capacity, true, 500);
|
|
assert(snapshot(fs) == before);
|
|
}
|
|
for (size_t capacity = 0; capacity < 32; ++capacity) {
|
|
const auto before = snapshot(fs);
|
|
command(fs, a, "mail mode public", capacity);
|
|
command(fs, a, "mail ack " + std::to_string(first), capacity);
|
|
command(fs, b, "mail send " + key(a) + " short confirmation", capacity, true, 501);
|
|
assert(snapshot(fs) == before);
|
|
}
|
|
response = request(fs, a, remove(4, first)); assert(response[2] == 0);
|
|
assert(status(fs, a).count == 1);
|
|
response = request(fs, b, send(a, full_body), 160, true, nonce);
|
|
assert(response[2] == 8 && id(response) == first && status(fs, a).count == 1);
|
|
assert(request(fs, a, remove(4, first))[2] == 0);
|
|
assert(request(fs, a, remove(5, first))[2] == 0);
|
|
assert(request(fs, a, read(first))[2] == 4);
|
|
{
|
|
MemoryFS air;
|
|
assert(request(air, a, padded(settings()))[2] == 0 && air.files.empty());
|
|
assert(request(air, a, padded(edit(8, 0, 2)))[2] == 0);
|
|
assert(request(air, a, padded(editKey(6, status(air, a).revision, b)))[2] == 0);
|
|
assert(request(air, a, padded(edit(9, status(air, a).revision, 1)))[2] == 0);
|
|
const std::string dog = "dog lost: please call home";
|
|
response = request(air, b, padded(send(a, dog)), 160, true, 700);
|
|
const uint32_t dog_id = id(response);
|
|
assert(request(air, a, padded(indexRequest()))[7] == 1);
|
|
response = request(air, a, padded(read(dog_id)));
|
|
assert(response[11] == dog.size() && std::string(reinterpret_cast<const char*>(response.data() + 44), response[11]) == dog);
|
|
assert(status(air, a).count == 1); // Reading on a later wake leaves queued mail intact.
|
|
assert(request(air, a, padded(remove(4, dog_id)))[2] == 0 && status(air, a).count == 0);
|
|
assert(request(air, a, padded(remove(5, dog_id)))[2] == 0);
|
|
assert(request(air, a, padded(editKey(7, status(air, a).revision, b)))[2] == 0);
|
|
const auto before = snapshot(air);
|
|
for (auto input : {settings(), indexRequest(), read(dog_id), send(a, "changed padding"),
|
|
remove(4, dog_id), remove(5, dog_id), edit(8, status(air, a).revision, 1),
|
|
edit(9, status(air, a).revision, 0), editKey(6, status(air, a).revision, b), editKey(7, status(air, a).revision, b)}) {
|
|
input.push_back(1); assert(request(air, a, input, 160, true, 701)[2] == 1);
|
|
}
|
|
assert(snapshot(air) == before);
|
|
}
|
|
|
|
assert(command(fs, a, "mail deny " + key(b)) == "OK");
|
|
assert(request(fs, b, send(a, "denied"), 160, true, 999)[2] == 6);
|
|
assert(command(fs, a, "mail mode public") == "OK");
|
|
response = request(fs, c, send(a, "public mode"), 160, true, 1000); const uint32_t public_id = id(response);
|
|
assert(command(fs, a, "mail delete " + std::to_string(public_id)).find("OK id=") == 0);
|
|
assert(command(fs, a, "mail delivery chat") == "OK" && !status(fs, a).settings.mailbox_only);
|
|
for (uint8_t value = 1; value <= 8; ++value) {
|
|
uint8_t allowed[32]; memset(allowed, value, 32);
|
|
assert(command(fs, a, "mail allow " + key(allowed)) == "OK");
|
|
}
|
|
assert(command(fs, a, "mail allow " + key(b)) == "Error mailbox full");
|
|
assert(command(fs, a, "mail settings").find("next=1") != std::string::npos);
|
|
assert(command(fs, a, "mail settings 7").find("next=8") != std::string::npos);
|
|
assert(request(fs, a, settings(status(fs, a).revision, 7), 45)[11] == 255);
|
|
assert(command(fs, a, "mail check").find("allowed=8") != std::string::npos);
|
|
assert(request(fs, c, send(a, std::string("bad\0body", 8)), 160, true, 1010)[2] == 1);
|
|
assert(request(fs, c, send(a, std::string("\xed\xa0\x80", 3)), 160, true, 1011)[2] == 1);
|
|
assert(request(fs, c, send(a, "cafe\xc3\xa9"), 160, true, 1012)[2] == 0);
|
|
assert(request(fs, c, send(a, "third queued"), 160, true, 1013)[2] == 0);
|
|
assert(request(fs, c, send(a, "fourth queued"), 160, true, 1014)[2] == 0);
|
|
assert(status(fs, a).count == 4);
|
|
assert(request(fs, c, send(a, "over quota"), 160, true, 1015)[2] == 7);
|
|
response = request(fs, a, indexRequest());
|
|
assert(response[7] == 4 && response[9] == 2 && response[10] == 2);
|
|
response = request(fs, a, indexRequest(status(fs, a).revision, 2));
|
|
assert(response[7] == 4 && response[9] == 255 && response[10] == 2);
|
|
|
|
for (const std::string& text : std::vector<std::string>{"mail", "mail bogus", "mail mode", "mail mode administrator",
|
|
"mail mode public trailing", "mail delivery radio", "mail allow " + key(b).substr(0, 63),
|
|
"mail deny " + std::string(64, '0'), "mail allow " + std::string(64, 'z'),
|
|
"mail allow " + key(b) + " trailing", "mail send " + key(a),
|
|
"mail send " + key(a) + " " + std::string(513, 'x'), "mail read 0", "mail read -1",
|
|
"mail read 4294967296", "mail read 1 513", "mail read 1 0 trailing", "mail ack 0",
|
|
"mail delete 1 trailing", "mail settings 9", "mail list 17"}) {
|
|
const auto before = snapshot(fs);
|
|
assert(command(fs, a, text).find("Error ") == 0); assert(snapshot(fs) == before);
|
|
}
|
|
command(fs, a, "mailbox settings", 160, true, 1, false);
|
|
command(fs, a, "unrelated", 160, true, 1, false);
|
|
const auto before_invalid = snapshot(fs);
|
|
for (const std::vector<uint8_t>& payload : std::vector<std::vector<uint8_t>>{
|
|
{}, {11}, {11, 255}, {11, 0}, {11, 0, 9}, {11, 0, 0, 1}, {11, 1}, {11, 2},
|
|
{11, 3}, {11, 4}, {11, 5}, edit(8, status(fs, a).revision, 3), edit(9, status(fs, a).revision, 2),
|
|
std::vector<uint8_t>(38, 0)}) {
|
|
response = request(fs, a, payload); assert(response.empty() || response[2] == 1);
|
|
}
|
|
assert(snapshot(fs) == before_invalid);
|
|
uint8_t zero[32] = {};
|
|
assert(request(fs, zero, settings())[2] == 1);
|
|
assert(command(fs, zero, "mail settings") == "Error invalid");
|
|
printf("PASS: %u owner-bound room mail LoRa/CLI protocol, capacity and durable retry scenarios\n", scenarios);
|
|
}
|
|
'''
|
|
|
|
|
|
class RoomMailProtocolTests(unittest.TestCase):
|
|
def test_actual_protocol_and_store_all_backend_modes(self):
|
|
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
|
|
if compiler is None:
|
|
self.skipTest("a host C++ compiler is required")
|
|
candidates = [Path(os.environ["MESHCORE_CRYPTO_DIR"])] if os.environ.get("MESHCORE_CRYPTO_DIR") else []
|
|
candidates += sorted((ROOT / ".pio/libdeps").glob("*/Crypto"))
|
|
crypto = next((path for path in candidates if (path / "SHA256.cpp").is_file()), None)
|
|
if crypto is None:
|
|
raise RuntimeError("Install rweather/Crypto or set MESHCORE_CRYPTO_DIR (no hash mock fallback)")
|
|
filesystem = FILESYSTEM_HARNESS.split("#define FILE_O_WRITE 1")[0]
|
|
filesystem = filesystem.replace(" File() = default;", """#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
File() = delete;
|
|
#else
|
|
File() = default;
|
|
#endif
|
|
explicit File(MemoryFS* owner): fs(owner) {}""")
|
|
filesystem = filesystem.replace("return {};", "return File(this);")
|
|
filesystem = filesystem.replace(
|
|
" void _lockFS() {}\n void _unlockFS() {}\n MemoryFS* _getFS() { return this; }\n", "", 1
|
|
)
|
|
filesystem = filesystem.replace(" void point(const std::string& event) {", """ int lock_depth = 0;
|
|
void _lockFS() { assert(lock_depth++ == 0); }
|
|
void _unlockFS() { assert(--lock_depth == 0); }
|
|
MemoryFS* _getFS() { return this; }
|
|
void point(const std::string& event) {""")
|
|
sanitizers = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie"]
|
|
if sys.platform.startswith("linux") else [])
|
|
with tempfile.TemporaryDirectory(prefix="meshcore-room-mail-protocol-") as temporary:
|
|
work = Path(temporary)
|
|
(work / "sys").mkdir()
|
|
(work / "sys/stat.h").write_text(STAT_MOCK)
|
|
source = work / "protocol.cpp"
|
|
for platform in (None, "NRF52_PLATFORM", "STM32_PLATFORM", "RP2040_PLATFORM", "ESP32_PLATFORM"):
|
|
with self.subTest(platform=platform):
|
|
source.write_text(filesystem + (LFS_MOCK if platform in ("NRF52_PLATFORM", "STM32_PLATFORM") else "") + TESTS)
|
|
binary = work / (platform or "generic")
|
|
defines = ["-D" + platform + "=1"] if platform else []
|
|
built = subprocess.run(
|
|
[compiler, "-std=c++11", "-Wall", "-Wextra", "-Werror", "-DHOST_BUILD", *sanitizers,
|
|
*defines, "-I" + str(work), "-I" + str(ROOT / "src"), "-I" + str(crypto), str(source),
|
|
str(crypto / "SHA256.cpp"), str(crypto / "Hash.cpp"), str(crypto / "Crypto.cpp"), "-o", str(binary)],
|
|
capture_output=True, text=True, timeout=60,
|
|
)
|
|
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
|
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
|
self.assertIn("owner-bound room mail LoRa/CLI protocol", checked.stdout)
|
|
print(checked.stdout.strip())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|