Files
MeshCore/test/test_companion_discovery_correlation.py
T

202 lines
8.7 KiB
Python

#!/usr/bin/env python3
"""Execute the production discovery handler against idle and active tags."""
from pathlib import Path
import os
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
SANITIZERS = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
HARNESS = r"""
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
#include <helpers/CompanionDelayedReplies.h>
#define MESH_DEBUG_PRINTLN(...) ((void)0)
#define PAYLOAD_TYPE_RESPONSE 1
#define MAX_PATH_SIZE 64
@CODES@
struct ContactInfo { struct { uint8_t pub_key[32] = {1, 2, 3, 4, 5, 6}; } id; };
struct Clock { uint32_t now = 100; uint32_t getMillis() const { return now; } };
struct SerialBoundary : BaseSerialInterface {
bool available = true;
unsigned writes = 0;
BaseSerialInterface* written_route = nullptr;
std::vector<uint8_t> written;
void enable() override {}
void disable() override {}
bool isEnabled() const override { return true; }
bool isConnected() const override { return available; }
bool isReadBusy() const override { return false; }
bool isWriteBusy() const override { return false; }
size_t checkRecvFrame(uint8_t*) override { return 0; }
size_t writeFrame(const uint8_t* frame, size_t len) override { return writeFrameToRoute(this,frame,len); }
bool isReplyRouteAvailable(BaseSerialInterface* route) const override {
return available && route != nullptr;
}
size_t writeFrameToRoute(BaseSerialInterface* route, const uint8_t* frame, size_t len) override {
++writes; written_route = route; written.assign(frame, frame + len); return len;
}
};
namespace mesh { struct Packet {
static bool isValidPathLen(uint8_t);
static size_t writePath(uint8_t*, const uint8_t*, uint8_t);
}; }
struct BaseChatMesh {
unsigned base_path_calls = 0;
bool onContactPathRecv(ContactInfo&, uint8_t*, uint8_t, uint8_t*, uint8_t,
uint8_t, uint8_t*, uint8_t) {
++base_path_calls;
return true;
}
};
struct MyMesh : BaseChatMesh {
mesh::CompanionDelayedReplies _delayed_replies;
SerialBoundary serial;
SerialBoundary* _serial = &serial;
Clock clock;
Clock* _ms = &clock;
uint8_t out_frame[MAX_FRAME_SIZE] = {};
bool millisHasNowPassed(unsigned long) const;
size_t writePendingSerialFrame(const uint8_t*, size_t, uint32_t);
void clearPendingReqs();
void servicePendingSerialReply();
void servicePendingSerialReply(uint32_t);
void arm(mesh::CompanionDelayedReplies::Kind kind,BaseSerialInterface* route,uint32_t tag,uint32_t deadline) {
ContactInfo contact;
assert(_delayed_replies.reserveRequest(kind,contact.id.pub_key,route,false,clock.now));
assert(kind==mesh::CompanionDelayedReplies::Login || _delayed_replies.allowRequestTag(tag));
_delayed_replies.armRequest(tag,1000,false,clock.now);
servicePendingSerialReply();
_delayed_replies.request.radio_deadline=deadline;
serial.writes=0;serial.written.clear();
}
bool onContactPathRecv(ContactInfo&, uint8_t*, uint8_t, uint8_t*, uint8_t,
uint8_t, uint8_t*, uint8_t);
};
@METHODS@
static bool response(MyMesh& mesh, uint32_t tag) {
ContactInfo contact;
uint8_t in_path[] = {0x21, 0x22}, out_path[] = {0x31}, extra[5] = {};
memcpy(extra, &tag, sizeof(tag));
return mesh.onContactPathRecv(contact, in_path, 2, out_path, 1,
PAYLOAD_TYPE_RESPONSE, extra, sizeof(extra));
}
int main() {
SerialBoundary original_route, newer_route;
unsigned checks = 0;
// Radio and transport boundaries are mocks; every correlation decision,
// output envelope, expiry, and retirement below executes production code.
for (unsigned kind = 0; kind != 4; ++kind) {
MyMesh mesh;
const mesh::CompanionDelayedReplies::Kind kinds[]={mesh::CompanionDelayedReplies::Login,
mesh::CompanionDelayedReplies::Status,mesh::CompanionDelayedReplies::Telemetry,
mesh::CompanionDelayedReplies::Binary};
mesh.arm(kinds[kind],&original_route,0x11223344,900);
memset(mesh.out_frame, 0x5a, sizeof(mesh.out_frame));
assert(response(mesh, 0));
assert(mesh._delayed_replies.request.tag == 0x11223344);
assert(mesh._delayed_replies.request.kind == kinds[kind]);
assert(mesh._delayed_replies.request.route == &original_route);
assert(mesh._delayed_replies.request.radio_deadline == 900);
assert(mesh.base_path_calls == 1 && mesh.serial.writes == 0);
for (uint8_t byte : mesh.out_frame) assert(byte == 0x5a);
++checks;
}
{
MyMesh mesh;
mesh.arm(mesh::CompanionDelayedReplies::Discovery,&original_route,17,900);
assert(!response(mesh, 17));
assert(mesh.base_path_calls == 0 && mesh.serial.writes == 1);
assert(mesh.serial.written_route == &original_route);
const std::vector<uint8_t> expected = {
PUSH_CODE_PATH_DISCOVERY_RESPONSE, 0, 1, 2, 3, 4, 5, 6,
1, 0x31, 2, 0x21, 0x22};
assert(mesh.serial.written == expected);
assert(!mesh._delayed_replies.hasRequest() && mesh._delayed_replies.request.route == nullptr);
++checks;
}
{
MyMesh mesh;
mesh.arm(mesh::CompanionDelayedReplies::Discovery,&original_route,17,900);
assert(response(mesh, 18));
assert(mesh._delayed_replies.request.tag == 17 && mesh.base_path_calls == 1);
assert(mesh._delayed_replies.request.route == &original_route && mesh.serial.writes == 0);
++checks;
}
for (uint32_t now : {100U, 101U}) {
MyMesh mesh;
mesh.arm(mesh::CompanionDelayedReplies::Discovery,&original_route,17,100);
mesh.clock.now = now;
mesh.servicePendingSerialReply();
assert(!mesh._delayed_replies.hasRequest() && mesh._delayed_replies.request.route == nullptr);
mesh.arm(mesh::CompanionDelayedReplies::Status,&newer_route,23,500);
assert(response(mesh, 0));
assert(mesh._delayed_replies.request.tag == 23 && mesh.base_path_calls == 1);
assert(mesh._delayed_replies.request.route == &newer_route);
assert(mesh._delayed_replies.request.radio_deadline == 500 && mesh.serial.writes == 0);
++checks;
}
assert(checks == 8);
std::printf("PASS: %u production discovery correlation checks\n", checks);
}
"""
class CompanionDiscoveryCorrelationTests(unittest.TestCase):
def test_actual_handler_preserves_unrelated_pending_requests(self):
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
if compiler is None:
self.skipTest("a host C++17 compiler is required")
source = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text()
packet = (ROOT / "src/Packet.cpp").read_text()
dispatcher = (ROOT / "src/Dispatcher.cpp").read_text()
methods = "\n".join(extract_braced(source, signature) for signature in (
"size_t MyMesh::writePendingSerialFrame(", "void MyMesh::clearPendingReqs(",
"void MyMesh::servicePendingSerialReply(", "bool MyMesh::onContactPathRecv(",
"void MyMesh::servicePendingSerialReply(uint32_t now)",
))
methods += "\n" + extract_braced(
dispatcher, "bool Dispatcher::millisHasNowPassed(").replace("Dispatcher::", "MyMesh::")
methods += "\nnamespace mesh {\n" + "\n".join(
extract_braced(packet, signature) for signature in (
"bool Packet::isValidPathLen(", "size_t Packet::writePath(",
)) + "\n}\n"
code = re.search(r"^#define PUSH_CODE_PATH_DISCOVERY_RESPONSE\s+\S+", source, re.MULTILINE)
self.assertIsNotNone(code)
generated = HARNESS.replace("@CODES@", code.group()).replace("@METHODS@", methods)
with tempfile.TemporaryDirectory(prefix="meshcore-discovery-correlation-") as directory:
work = Path(directory)
cpp = work / "discovery.cpp"
cpp.write_text(generated, encoding="ascii")
binary = work / "discovery.exe"
compiled = subprocess.run([
compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror", *SANITIZERS,
'-isystem',str(ROOT/'test/mocks'),f'-I{ROOT/"src"}',
'-DCOMPANION_FEATURE_TEXT_TERMINAL=0',str(cpp),
str(ROOT/'src/helpers/CompanionDelayedReplies.cpp'),"-o",str(binary),
], capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stdout + compiled.stderr)
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
self.assertIn("PASS: 8 production discovery correlation checks", checked.stdout)
if __name__ == "__main__":
unittest.main()