Files
HaloKeymind/test/test_network_policy/test_network_policy.cpp
T
agessaman 7aed59075b fix(network): bound link-transition teardown and gate Ethernet restarts
- Link down/switch edges now softDisconnect() every started slot instead
  of disconnect(). The full stop could wait forever for a DISCONNECTED
  event on the Core 0 MQTT task, and it destroyed and recreated every
  slot's esp-mqtt task on each Wi-Fi drop, where the bridge previously
  stopped none (measured: 62 s deauth, five slots, zero stops).
  softDisconnect() is bounded and keeps the task; reconnectSlotClient()
  then calls reconnect() on the new route. Rename the transition action
  to disconnect_started_slots to match.
- Ethernet no-IP recovery and init retries rebuild the CH390 netif, so
  they now run inside the route-switch mutation gate. An OTA/WebConfig
  lock taken after maintain() samples the lock count can no longer have
  the interface torn down underneath it.
2026-09-19 10:23:12 -07:00

203 lines
9.0 KiB
C++

#include <gtest/gtest.h>
#include "helpers/NetworkPolicy.h"
TEST(NetworkPolicy, MqttDownDisconnectsSlotsWithoutRequestingImmediateRetry) {
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::Down);
EXPECT_TRUE(actions.disconnect_started_slots);
EXPECT_FALSE(actions.retry_disconnected_slots_now);
EXPECT_FALSE(actions.reset_reconnect_backoff);
}
TEST(NetworkPolicy, MqttUpRetriesWithoutClearingBrokerCircuitBreaker) {
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::Up);
EXPECT_FALSE(actions.disconnect_started_slots);
EXPECT_TRUE(actions.retry_disconnected_slots_now);
EXPECT_FALSE(actions.reset_reconnect_backoff);
}
TEST(NetworkPolicy, NoTransitionHasNoMqttSideEffects) {
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::None);
EXPECT_FALSE(actions.disconnect_started_slots);
EXPECT_FALSE(actions.retry_disconnected_slots_now);
EXPECT_FALSE(actions.reset_reconnect_backoff);
}
TEST(NetworkPolicy, LinkSwitchReconnectsMqttSlotsImmediately) {
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::Switched);
EXPECT_TRUE(actions.disconnect_started_slots);
EXPECT_TRUE(actions.retry_disconnected_slots_now);
EXPECT_TRUE(actions.reset_reconnect_backoff);
}
TEST(NetworkPolicy, MediumNamesAreStableForTransitionLogs) {
EXPECT_STREQ("none", NetworkPolicy::mediumName(NetworkMedium::None));
EXPECT_STREQ("ethernet", NetworkPolicy::mediumName(NetworkMedium::Ethernet));
EXPECT_STREQ("wifi", NetworkPolicy::mediumName(NetworkMedium::WiFi));
}
TEST(NetworkPolicy, BootPrefersEthernetAndOtherwiseUsesConfiguredWifi) {
EXPECT_EQ(NetworkMedium::Ethernet, NetworkPolicy::bootSelection(true, true));
EXPECT_EQ(NetworkMedium::WiFi, NetworkPolicy::bootSelection(false, true));
EXPECT_EQ(NetworkMedium::None, NetworkPolicy::bootSelection(false, false));
}
TEST(NetworkPolicy, EthernetBootProbeUsesFullDeadlineForLinkOrUnknownState) {
EXPECT_TRUE(NetworkPolicy::ethernetBootProbePending(
true, false, false, false, 1500, 8000));
EXPECT_TRUE(NetworkPolicy::ethernetBootProbePending(
true, false, true, true, 7999, 8000));
EXPECT_FALSE(NetworkPolicy::ethernetBootProbePending(
true, false, true, true, 8000, 8000));
EXPECT_FALSE(NetworkPolicy::ethernetBootProbePending(
true, true, true, true, 100, 8000));
EXPECT_FALSE(NetworkPolicy::ethernetBootProbePending(
false, false, false, false, 100, 8000));
}
TEST(NetworkPolicy, EthernetBootProbeStopsEarlyOnKnownCableDown) {
EXPECT_TRUE(NetworkPolicy::ethernetBootProbePending(
true, false, true, false,
NetworkPolicy::kEthernetNoLinkBootGraceMs - 1, 8000));
EXPECT_FALSE(NetworkPolicy::ethernetBootProbePending(
true, false, true, false,
NetworkPolicy::kEthernetNoLinkBootGraceMs, 8000));
}
TEST(NetworkPolicy, EthernetInitRetryUsesBoundedExponentialBackoff) {
EXPECT_EQ(0u, NetworkPolicy::ethernetInitRetryDelayMs(0));
EXPECT_EQ(5000u, NetworkPolicy::ethernetInitRetryDelayMs(1));
EXPECT_EQ(10000u, NetworkPolicy::ethernetInitRetryDelayMs(2));
EXPECT_EQ(300000u, NetworkPolicy::ethernetInitRetryDelayMs(8));
EXPECT_EQ(300000u, NetworkPolicy::ethernetInitRetryDelayMs(255));
EXPECT_FALSE(NetworkPolicy::ethernetInitRetryDue(1, 4999, 0));
EXPECT_TRUE(NetworkPolicy::ethernetInitRetryDue(1, 5000, 0));
EXPECT_TRUE(NetworkPolicy::ethernetInitRetryDue(1, 10, 10u - 5000u));
}
TEST(NetworkPolicy, EthernetNoIpRecoveryRequiresSustainedCarrier) {
const uint32_t timeout = NetworkPolicy::kEthernetNoIpRecoveryMs;
EXPECT_FALSE(NetworkPolicy::ethernetNoIpRecoveryDue(
true, true, false, timeout, 0));
EXPECT_FALSE(NetworkPolicy::ethernetNoIpRecoveryDue(
true, true, false, timeout, 1));
EXPECT_TRUE(NetworkPolicy::ethernetNoIpRecoveryDue(
true, true, false, timeout + 1, 1));
EXPECT_FALSE(NetworkPolicy::ethernetNoIpRecoveryDue(
true, false, false, timeout + 1, 1));
EXPECT_FALSE(NetworkPolicy::ethernetNoIpRecoveryDue(
true, true, true, timeout + 1, 1));
EXPECT_TRUE(NetworkPolicy::ethernetNoIpRecoveryDue(
true, true, false, 100, 100u - timeout));
}
TEST(NetworkPolicy, EthernetFailureWaitsForGraceAndConnectedWifi) {
NetworkPolicy::AutomaticSelectionInput input = {
NetworkMedium::Ethernet, false, true, true, false, 0,
NetworkPolicy::kEthernetDownGraceMs - 1};
EXPECT_EQ(NetworkMedium::Ethernet, NetworkPolicy::automaticSelection(input));
input.selected_down_ms = NetworkPolicy::kEthernetDownGraceMs;
EXPECT_EQ(NetworkMedium::WiFi, NetworkPolicy::automaticSelection(input));
}
TEST(NetworkPolicy, FailbackRequiresStableEthernet) {
NetworkPolicy::AutomaticSelectionInput input = {
NetworkMedium::WiFi, true, true, true, false,
NetworkPolicy::kEthernetFailbackStableMs - 1, 0};
EXPECT_EQ(NetworkMedium::WiFi, NetworkPolicy::automaticSelection(input));
input.ethernet_stable_ms = NetworkPolicy::kEthernetFailbackStableMs;
EXPECT_EQ(NetworkMedium::Ethernet, NetworkPolicy::automaticSelection(input));
}
TEST(NetworkPolicy, DeadWifiFailsBackToReadyEthernetImmediately) {
const NetworkPolicy::AutomaticSelectionInput input = {
NetworkMedium::WiFi, true, false, true, false, 0, 0};
EXPECT_EQ(NetworkMedium::Ethernet, NetworkPolicy::automaticSelection(input));
}
TEST(NetworkPolicy, SwitchingLockPinsTheCurrentMedium) {
const NetworkPolicy::AutomaticSelectionInput input = {
NetworkMedium::WiFi, true, true, true, true,
NetworkPolicy::kEthernetFailbackStableMs, 0};
EXPECT_EQ(NetworkMedium::WiFi, NetworkPolicy::automaticSelection(input));
}
TEST(NetworkPolicy, DiagnosticsIdentifyEthernetFallbackCause) {
using Reason = NetworkDiagnosticReason;
EXPECT_EQ(Reason::EthernetInitFailed,
NetworkPolicy::automaticDiagnosticReason(
false, false, false, false, NetworkMedium::WiFi, false, 0));
EXPECT_EQ(Reason::EthernetLinkUnknown,
NetworkPolicy::automaticDiagnosticReason(
true, false, false, false, NetworkMedium::WiFi, false, 0));
EXPECT_EQ(Reason::EthernetLinkDown,
NetworkPolicy::automaticDiagnosticReason(
true, true, false, false, NetworkMedium::WiFi, false, 0));
EXPECT_EQ(Reason::EthernetAwaitingIp,
NetworkPolicy::automaticDiagnosticReason(
true, true, true, false, NetworkMedium::WiFi, false, 0));
}
TEST(NetworkPolicy, DiagnosticsExplainWhyReadyEthernetHasNotBeenSelected) {
using Reason = NetworkDiagnosticReason;
EXPECT_EQ(Reason::SwitchingLocked,
NetworkPolicy::automaticDiagnosticReason(
true, true, true, true, NetworkMedium::WiFi, true,
NetworkPolicy::kEthernetFailbackStableMs));
EXPECT_EQ(Reason::EthernetStabilizing,
NetworkPolicy::automaticDiagnosticReason(
true, true, true, true, NetworkMedium::WiFi, false,
NetworkPolicy::kEthernetFailbackStableMs - 1));
EXPECT_EQ(Reason::EthernetReady,
NetworkPolicy::automaticDiagnosticReason(
true, true, true, true, NetworkMedium::WiFi, false,
NetworkPolicy::kEthernetFailbackStableMs));
EXPECT_EQ(Reason::EthernetActive,
NetworkPolicy::automaticDiagnosticReason(
true, true, true, true, NetworkMedium::Ethernet, false, 0));
}
TEST(NetworkPolicy, NtpIsScheduledOncePerConnectivityEdge) {
EXPECT_TRUE(NetworkPolicy::ntpPendingAfterConnectivitySample(
false, false, false, true));
EXPECT_FALSE(NetworkPolicy::ntpPendingAfterConnectivitySample(
false, false, true, true));
EXPECT_TRUE(NetworkPolicy::ntpPendingAfterConnectivitySample(
false, true, true, true));
EXPECT_FALSE(NetworkPolicy::ntpPendingAfterConnectivitySample(
true, false, false, true));
}
TEST(NetworkPolicy, FailedNtpSyncWaitsForThirtySecondRetryBoundary) {
EXPECT_FALSE(NetworkPolicy::ntpRetryDue(false, true, 29999, 0));
EXPECT_TRUE(NetworkPolicy::ntpRetryDue(false, true, 30000, 0));
EXPECT_FALSE(NetworkPolicy::ntpRetryDue(true, true, 60000, 0));
EXPECT_FALSE(NetworkPolicy::ntpRetryDue(false, false, 60000, 0));
EXPECT_TRUE(NetworkPolicy::ntpRetryDue(false, true, 10, 0xffff8000u));
}
TEST(NetworkPolicy, StartOtaUsesReachableSelectedNetworkByDefault) {
EXPECT_TRUE(NetworkPolicy::startOtaUsesSelectedNetwork(false, true));
EXPECT_FALSE(NetworkPolicy::startOtaUsesSelectedNetwork(false, false));
}
TEST(NetworkPolicy, StartOtaForceApOverridesAReachableSelectedNetwork) {
EXPECT_FALSE(NetworkPolicy::startOtaUsesSelectedNetwork(true, true));
EXPECT_FALSE(NetworkPolicy::startOtaUsesSelectedNetwork(true, false));
}
TEST(NetworkPolicy, ManualOtaTimeoutIsUploadSafeAndWrapSafe) {
const uint32_t timeout = NetworkPolicy::kManualOtaSessionTimeoutMs;
EXPECT_FALSE(NetworkPolicy::manualOtaTimeoutDue(timeout - 1, 0, false));
EXPECT_TRUE(NetworkPolicy::manualOtaTimeoutDue(timeout, 0, false));
EXPECT_FALSE(NetworkPolicy::manualOtaTimeoutDue(timeout, 0, true));
EXPECT_TRUE(NetworkPolicy::manualOtaTimeoutDue(
100, 100 - timeout, false));
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}