Files

253 lines
8.2 KiB
C++

#include <helpers/NonBlockingWriteStream.h>
#include <helpers/nrf52/EthernetCLI.h>
#include "NetworkLoggingMacro.h"
#include <helpers/ui/CHSC6XTouch.h>
#include <helpers/ethernet/ch390/CH390Config.h>
#include <iostream>
uint32_t mock_millis = 0;
RawSerial Serial;
MockFicr mock_ficr;
MockEthernet Ethernet;
TwoWire Wire;
MockCh390 CH390;
std::string ethernet_output;
std::string ethernet_input;
bool accept_client = false;
class MockUsb : public Stream {
public:
int capacity = 256;
bool refuse_write = false;
unsigned int tries = 0;
std::string output;
int available() override { return 0; }
int read() override { return -1; }
int peek() override { return -1; }
void flush() override { assert(false && "blocking flush"); }
int availableForWrite() override { return capacity; }
size_t write(uint8_t) override { assert(false && "blocking write"); return 0; }
size_t write(const uint8_t*, size_t) override { assert(false && "blocking write"); return 0; }
};
static MockUsb usb;
static bool logging_enabled = true;
static bool debug_enabled = true;
static size_t tryUsbWrite(void*, const uint8_t* data, size_t size) {
++usb.tries;
if (usb.refuse_write || usb.capacity <= 0) return 0;
const size_t written = size < static_cast<size_t>(usb.capacity)
? size : static_cast<size_t>(usb.capacity);
usb.output.append(reinterpret_cast<const char*>(data), written);
return written;
}
static mesh::SingleAttemptNonBlockingStream logging_port(usb, tryUsbWrite);
namespace mesh {
bool isUsbLoggingEnabled() { return logging_enabled; }
bool isUsbDebugEnabled() { return debug_enabled; }
Stream& usbLoggingPort() { return logging_port; }
}
static void reset() {
usb.output.clear();
usb.capacity = 256;
usb.refuse_write = false;
usb.tries = 0;
logging_enabled = true;
debug_enabled = true;
ethernet_running = false;
ethernet_client = EthernetClient();
ethernet_output.clear();
ethernet_input.clear();
accept_client = false;
Ethernet = MockEthernet();
mock_millis = 0;
}
static void acceptClientAndReply() {
accept_client = true;
ethernet_loop_maintain();
assert(ethernet_take_session_reset());
assert(!ethernet_take_session_reset());
ethernet_input = "eth.status\r";
char command[80] = {};
assert(ethernet_read_line(command, sizeof(command)));
assert(strcmp(command, "eth.status") == 0);
char reply[80] = {};
assert(ethernet_handle_command(command, reply));
ethernet_send_reply(reply);
assert(ethernet_output == "MeshCore CLI\r\n\r\n -> ETH: 192.168.1.42:23\r\n");
}
int main() {
(void)&ethernet_start_task;
// Runtime-off diagnostics must not attempt USB, while functional Ethernet
// commands and the client banner still work.
reset();
logging_enabled = false;
ethernet_task(nullptr);
acceptClientAndReply();
assert(usb.output.empty() && usb.tries == 0 && mock_millis == 0);
// The master switch can remain on for packets while verbose ETH output is
// disabled. Its functional banner and command reply still reach the client.
reset();
debug_enabled = false;
ethernet_task(nullptr);
acceptClientAndReply();
assert(logging_enabled && usb.output.empty() && usb.tries == 0);
reset();
ethernet_task(nullptr);
acceptClientAndReply();
assert(usb.output.find("ETH: MAC: 02:92:1F:12:34:56\n") != std::string::npos);
assert(usb.output.find("ETH: Listening on TCP port 23\n") != std::string::npos);
assert(usb.output.find("ETH: Client connected from 192.168.1.42\n") != std::string::npos);
assert(usb.tries == 6 && mock_millis == 0);
// An open-but-stalled host has no TX space. No raw write or wait is allowed.
reset();
usb.capacity = 0;
ethernet_task(nullptr);
acceptClientAndReply();
assert(usb.output.empty() && usb.tries == 0 && mock_millis == 0);
// The host can also vanish after the capacity preflight. The real shared
// single-attempt facade drops each diagnostic instead of retrying or waiting.
reset();
usb.refuse_write = true;
ethernet_task(nullptr);
acceptClientAndReply();
assert(usb.output.empty() && usb.tries == 6 && mock_millis == 0);
for (int failure : {0, 1, 2}) {
reset();
Ethernet.failures = 1;
if (failure == 0) Ethernet.hardware = EthernetNoHardware;
if (failure == 1) Ethernet.link = LinkOFF;
ethernet_task(nullptr);
const char* expected = failure == 0 ? "Hardware not found, giving up"
: failure == 1 ? "Cable not connected, will retry" : "DHCP failed, will retry";
assert(usb.output.find(expected) != std::string::npos);
assert(mock_millis == (failure == 0 ? 0u : 30000u));
}
reset();
int evaluated = 0;
logging_enabled = false;
NETWORK_DEBUG_PRINTLN("suppressed %d", ++evaluated);
assert(evaluated == 0 && usb.tries == 0);
logging_enabled = true;
debug_enabled = false;
NETWORK_DEBUG_PRINTLN("packet-only %d", ++evaluated);
assert(evaluated == 0 && usb.tries == 0);
debug_enabled = true;
usb.capacity = 0;
NETWORK_DEBUG_PRINTLN("stalled %d", ++evaluated);
assert(evaluated == 0 && usb.tries == 0);
usb.capacity = 256;
NETWORK_DEBUG_PRINTLN("visible %d", ++evaluated);
#if defined(MQTT_DEBUG) && MQTT_DEBUG
assert(evaluated == 1 && usb.output == "MQTT: visible 1\n" && usb.tries == 1);
usb.refuse_write = true;
NETWORK_DEBUG_PRINTLN("disconnected %d", ++evaluated);
assert(usb.tries == 2 && usb.output == "MQTT: visible 1\n");
#else
assert(evaluated == 0 && usb.output.empty() && usb.tries == 0);
#endif
assert(mock_millis == 0);
// Touch probe, late-presence and optional raw frames use the same runtime
// logging gate as every other diagnostic, including protected boot mode.
reset();
logging_enabled = false;
Wire = TwoWire();
CHSC6XTouch found;
assert(found.begin());
Wire.frame[0] = 0x1F;
assert(!found.checkTap(500));
assert(found.checkTap(600));
assert(usb.output.empty() && usb.tries == 0);
Wire = TwoWire();
Wire.present = false;
CHSC6XTouch late;
assert(!late.begin());
assert(Wire.probes == 1); // No diagnostic I2C scan when logging is off.
Wire.present = true;
Wire.frame[0] = 0x1F;
assert(!late.checkTap(500));
assert(late.checkTap(600));
Wire.reply_length = 2;
late.checkTap(700);
assert(usb.output.empty() && usb.tries == 0);
reset();
debug_enabled = false;
Wire = TwoWire();
CHSC6XTouch packet_only;
assert(packet_only.begin());
Wire.frame[0] = 0x1F;
assert(!packet_only.checkTap(500));
assert(packet_only.checkTap(600));
assert(logging_enabled && usb.output.empty() && usb.tries == 0);
Wire = TwoWire();
Wire.present = false;
CHSC6XTouch quiet_scan;
assert(!quiet_scan.begin() && Wire.probes == 1);
reset();
Wire = TwoWire();
CHSC6XTouch visible;
assert(visible.begin());
assert(usb.output == "Touch: CHSC6X found at 0x2E\n");
visible.checkTap(500);
#if defined(DISPLAY_TOUCH_DEBUG)
assert(usb.output.find("Touch: raw 00 01 02 03 04") != std::string::npos);
#endif
usb.output.clear();
usb.capacity = 0;
CHSC6XTouch stalled;
assert(stalled.begin());
stalled.checkTap(500);
assert(usb.output.empty());
reset();
Wire = TwoWire();
Wire.present = false;
Wire.all_addresses_present = false;
CHSC6XTouch absent;
assert(!absent.begin());
assert(usb.output.find("Touch: idle or absent at 0x2E; I2C bus currently answers:\n") == 0);
assert(Wire.probes == 113);
reset();
usb.capacity = 640;
Wire = TwoWire();
Wire.all_addresses_present = true;
Wire.fail_probe_count = 1; // Idle boot probe, then every scan address replies.
CHSC6XTouch full_scan;
assert(!full_scan.begin());
assert(usb.output.size() > 600 && usb.output.size() < 640 && usb.tries == 1);
assert(usb.output.substr(usb.output.size() - 6) == " 0x77\n");
reset();
CH390 = MockCh390();
logging_enabled = false;
assert(beginConfiguredCH390("invalid hostname"));
assert(CH390.begins == 1 && usb.output.empty() && usb.tries == 0);
logging_enabled = true;
debug_enabled = false;
assert(beginConfiguredCH390("invalid hostname"));
assert(usb.output.empty() && usb.tries == 0);
debug_enabled = true;
assert(beginConfiguredCH390("invalid hostname"));
assert(usb.output == "Network: invalid Ethernet hostname invalid hostname\n");
usb.output.clear();
usb.capacity = 0;
assert(beginConfiguredCH390("invalid hostname"));
assert(usb.output.empty());
std::cout << "PASS: gated USB diagnostics\n";
}