mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-02 14:33:43 +00:00
Add selectable shared ESP-NOW channels, raw/wrapped bridge compatibility, and bounded fragmentation for maximum-size packets.\n\nGive ESP32 Full profiles WiFi coexistence, stabilize WebConfig and USB terminal lifecycles, and keep disabled bridges stopped during observer updates.\n\nUpdate generated UI, firmware selection, documentation, and regression coverage.
374 lines
18 KiB
C++
374 lines
18 KiB
C++
// Host contract tests for the pure WebConfig config-batch / reboot / stop state
|
|
// machine. This spec mirrors src/helpers/esp32/WebConfigServer.cpp; it is not
|
|
// yet wired into the bridge (see WebConfigBatch.h scope note).
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <stdint.h>
|
|
#include <limits>
|
|
|
|
#include "helpers/WebConfigBatch.h"
|
|
|
|
namespace Batch = WebConfigBatch;
|
|
using State = WebConfigBatch::State;
|
|
|
|
// --------------------------------------------------------------------------
|
|
// POST accept classification
|
|
// --------------------------------------------------------------------------
|
|
TEST(WebConfigBatch, IdleAcceptsANewBatchWithChangesOrRebootOnly) {
|
|
// A normal save (changes present) is accepted.
|
|
EXPECT_EQ(Batch::PostOutcome::Accept,
|
|
Batch::classifyPost(State::Idle, /*reqid_matches=*/false, /*count=*/3, false));
|
|
// A reboot-only request (no changes) is still accepted.
|
|
EXPECT_EQ(Batch::PostOutcome::Accept,
|
|
Batch::classifyPost(State::Idle, false, 0, /*reboot_after=*/true));
|
|
}
|
|
|
|
TEST(WebConfigBatch, IdleWithNothingToDoIsNoChanges) {
|
|
EXPECT_EQ(Batch::PostOutcome::NoChanges,
|
|
Batch::classifyPost(State::Idle, false, 0, false));
|
|
}
|
|
|
|
TEST(WebConfigBatch, SameReqidReplaysWithoutReapplyingWhilePendingOrDone) {
|
|
// The idempotent-replay path fires for BOTH in-flight and finished batches
|
|
// when the reqid matches; commands are never re-applied.
|
|
EXPECT_EQ(Batch::PostOutcome::Replay,
|
|
Batch::classifyPost(State::Pending, /*reqid_matches=*/true, 3, false));
|
|
EXPECT_EQ(Batch::PostOutcome::Replay,
|
|
Batch::classifyPost(State::Done, /*reqid_matches=*/true, 3, false));
|
|
// Replay wins even over a would-be no-changes request.
|
|
EXPECT_EQ(Batch::PostOutcome::Replay,
|
|
Batch::classifyPost(State::Pending, true, 0, false));
|
|
// The replay body reports the batch's own state.
|
|
EXPECT_STREQ("pending", Batch::replayStateName(State::Pending));
|
|
EXPECT_STREQ("done", Batch::replayStateName(State::Done));
|
|
}
|
|
|
|
TEST(WebConfigBatch, DifferentReqidIsBusyOnlyWhilePending) {
|
|
// A second client (different reqid) while a batch is still draining => busy.
|
|
EXPECT_EQ(Batch::PostOutcome::Busy,
|
|
Batch::classifyPost(State::Pending, /*reqid_matches=*/false, 2, false));
|
|
// But once the previous batch is DONE, a different reqid is NOT busy: the new
|
|
// batch overwrites the finished slot (asymmetry with the Pending case).
|
|
EXPECT_EQ(Batch::PostOutcome::Accept,
|
|
Batch::classifyPost(State::Done, false, 2, false));
|
|
EXPECT_EQ(Batch::PostOutcome::NoChanges,
|
|
Batch::classifyPost(State::Done, false, 0, false));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Drain pacing / all_ok / finish
|
|
// --------------------------------------------------------------------------
|
|
TEST(WebConfigBatch, DrainNeverWaitsBeforeTheFirstOrAfterTheLastCommand) {
|
|
// batch_next == 0: the first command runs immediately (fires onConfigBatchStart).
|
|
EXPECT_FALSE(Batch::drainMustWait(0, 5, 1000, 1000));
|
|
// batch_next >= batch_count: nothing left to pace.
|
|
EXPECT_FALSE(Batch::drainMustWait(5, 5, 1000, 1000));
|
|
// A single-command (or reboot-only) batch never paces.
|
|
EXPECT_FALSE(Batch::drainMustWait(0, 1, 1000, 1000));
|
|
EXPECT_FALSE(Batch::drainMustWait(0, 0, 1000, 1000));
|
|
}
|
|
|
|
TEST(WebConfigBatch, DrainPacesTwentyFiveMillisBetweenCommandsWithInclusiveRelease) {
|
|
const uint32_t last = 1000;
|
|
// 24 ms after the previous command: still waiting.
|
|
EXPECT_TRUE(Batch::drainMustWait(2, 5, last + 24, last));
|
|
// Exactly 25 ms: the gate releases (production uses `< 25`).
|
|
EXPECT_FALSE(Batch::drainMustWait(2, 5, last + 25, last));
|
|
EXPECT_FALSE(Batch::drainMustWait(2, 5, last + 26, last));
|
|
}
|
|
|
|
TEST(WebConfigBatch, DrainPacingSurvivesMillisRollover) {
|
|
const uint32_t last = std::numeric_limits<uint32_t>::max() - 10;
|
|
EXPECT_TRUE(Batch::drainMustWait(2, 5, last + 24, last)); // 24 ms elapsed, wrapped
|
|
EXPECT_FALSE(Batch::drainMustWait(2, 5, last + 25, last)); // 25 ms elapsed, wrapped
|
|
}
|
|
|
|
TEST(WebConfigBatch, AllOkIsAStickyAndAcrossCommandReplies) {
|
|
bool all_ok = true;
|
|
all_ok = Batch::nextAllOk(all_ok, true);
|
|
EXPECT_TRUE(all_ok);
|
|
all_ok = Batch::nextAllOk(all_ok, false); // one command failed
|
|
EXPECT_FALSE(all_ok);
|
|
all_ok = Batch::nextAllOk(all_ok, true); // stays false forever after
|
|
EXPECT_FALSE(all_ok);
|
|
}
|
|
|
|
TEST(WebConfigBatch, DrainFinishesWhenTheIndexReachesTheCount) {
|
|
EXPECT_FALSE(Batch::drainFinished(4, 5));
|
|
EXPECT_TRUE(Batch::drainFinished(5, 5));
|
|
EXPECT_TRUE(Batch::drainFinished(0, 0)); // reboot-only / empty batch
|
|
}
|
|
|
|
TEST(WebConfigBatch, FinishArmsThirtySecondFallbackOnlyForAFullyOkRebootBatch) {
|
|
const uint32_t now = 100000;
|
|
EXPECT_EQ(now + Batch::kRebootFallbackMs,
|
|
Batch::finishRebootAt(/*reboot=*/true, /*all_ok=*/true, now));
|
|
// A partially-failed batch never reboots.
|
|
EXPECT_EQ(0u, Batch::finishRebootAt(true, false, now));
|
|
// No reboot requested.
|
|
EXPECT_EQ(0u, Batch::finishRebootAt(false, true, now));
|
|
}
|
|
|
|
TEST(WebConfigBatch, SetupWiFiHandoffRequiresASuccessfulCredentialChangingReboot) {
|
|
EXPECT_TRUE(Batch::shouldStartSetupWiFiHandoff(
|
|
/*setup=*/true, /*reboot=*/true, /*all_ok=*/true,
|
|
/*wifi_changed=*/true, /*has_ssid=*/true,
|
|
/*espnow_channel_changed=*/false));
|
|
EXPECT_FALSE(Batch::shouldStartSetupWiFiHandoff(
|
|
false, true, true, true, true, false));
|
|
EXPECT_FALSE(Batch::shouldStartSetupWiFiHandoff(
|
|
true, false, true, true, true, false));
|
|
EXPECT_FALSE(Batch::shouldStartSetupWiFiHandoff(
|
|
true, true, false, true, true, false));
|
|
EXPECT_FALSE(Batch::shouldStartSetupWiFiHandoff(
|
|
true, true, true, false, true, false));
|
|
EXPECT_FALSE(Batch::shouldStartSetupWiFiHandoff(
|
|
true, true, true, true, false, false));
|
|
}
|
|
|
|
TEST(WebConfigBatch, EspNowChannelChangeSkipsLiveSetupWiFiHandoff) {
|
|
EXPECT_FALSE(Batch::shouldStartSetupWiFiHandoff(
|
|
/*setup=*/true, /*reboot=*/true, /*all_ok=*/true,
|
|
/*wifi_changed=*/true, /*has_ssid=*/true,
|
|
/*espnow_channel_changed=*/true));
|
|
}
|
|
|
|
TEST(WebConfigBatch, SetupWiFiHandoffUsesBoundedConnectAndBriefFlushDelays) {
|
|
const uint32_t now = 500000;
|
|
EXPECT_EQ(now + Batch::kSetupWiFiConnectTimeoutMs,
|
|
Batch::setupWiFiConnectDeadline(now));
|
|
EXPECT_EQ(now + Batch::kSetupHandoffRebootConfirmMs,
|
|
Batch::confirmSetupHandoffRebootAt(now));
|
|
EXPECT_EQ(1000U, Batch::kSetupHandoffRebootConfirmMs);
|
|
}
|
|
|
|
TEST(WebConfigBatch, FullUnconfiguredSetupWindowIsAnAbsoluteThirtyMinutes) {
|
|
const uint32_t started = 1000;
|
|
EXPECT_EQ(30UL * 60UL * 1000UL, Batch::kFullSetupApWindowMs);
|
|
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
|
|
true, false, started + Batch::kFullSetupApWindowMs - 1, started,
|
|
Batch::kFullSetupApWindowMs));
|
|
EXPECT_TRUE(Batch::unconfiguredSetupWindowExpired(
|
|
true, false, started + Batch::kFullSetupApWindowMs, started,
|
|
Batch::kFullSetupApWindowMs));
|
|
|
|
// Saving any SSID converts the node to deployed behavior, which continues
|
|
// reconnecting and is not subject to the provisioning cutoff.
|
|
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
|
|
true, true, started + Batch::kFullSetupApWindowMs, started,
|
|
Batch::kFullSetupApWindowMs));
|
|
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
|
|
false, false, started + Batch::kFullSetupApWindowMs, started,
|
|
Batch::kFullSetupApWindowMs));
|
|
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
|
|
true, false, started + Batch::kFullSetupApWindowMs, started, 0));
|
|
}
|
|
|
|
TEST(WebConfigBatch, FullUnconfiguredSetupWindowSurvivesMillisRollover) {
|
|
const uint32_t started = std::numeric_limits<uint32_t>::max() - 1000;
|
|
EXPECT_FALSE(Batch::unconfiguredSetupWindowExpired(
|
|
true, false, started + Batch::kFullSetupApWindowMs - 1, started,
|
|
Batch::kFullSetupApWindowMs));
|
|
EXPECT_TRUE(Batch::unconfiguredSetupWindowExpired(
|
|
true, false, started + Batch::kFullSetupApWindowMs, started,
|
|
Batch::kFullSetupApWindowMs));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Result read
|
|
// --------------------------------------------------------------------------
|
|
TEST(WebConfigBatch, ResultReadClassifiesIdlePendingDoneAndUnknownReqid) {
|
|
// Idle: any valid reqid gets "idle" (no reqid check while idle).
|
|
EXPECT_EQ(Batch::ResultOutcome::Idle, Batch::classifyResult(State::Idle, false));
|
|
EXPECT_EQ(Batch::ResultOutcome::Idle, Batch::classifyResult(State::Idle, true));
|
|
// Matching reqid reflects the batch state.
|
|
EXPECT_EQ(Batch::ResultOutcome::Pending, Batch::classifyResult(State::Pending, true));
|
|
EXPECT_EQ(Batch::ResultOutcome::Done, Batch::classifyResult(State::Done, true));
|
|
// A live/finished batch with a mismatched reqid is unknown (404).
|
|
EXPECT_EQ(Batch::ResultOutcome::Unknown, Batch::classifyResult(State::Pending, false));
|
|
EXPECT_EQ(Batch::ResultOutcome::Unknown, Batch::classifyResult(State::Done, false));
|
|
}
|
|
|
|
TEST(WebConfigBatch, DoneBodyAdvertisesRebootOnlyWhenFullyOk) {
|
|
EXPECT_TRUE(Batch::doneReportsReboot(true, true));
|
|
EXPECT_FALSE(Batch::doneReportsReboot(true, false));
|
|
EXPECT_FALSE(Batch::doneReportsReboot(false, true));
|
|
}
|
|
|
|
TEST(WebConfigBatch, FirstDoneReadArmsTheThreeSecondRebootExactlyOnce) {
|
|
const uint32_t now = 500000;
|
|
// First read of a fully-OK reboot batch arms.
|
|
EXPECT_TRUE(Batch::shouldArmConfirmReboot(State::Done, true, true, /*already_armed=*/false));
|
|
EXPECT_EQ(now + Batch::kRebootConfirmMs, Batch::confirmRebootAt(now));
|
|
// Already armed => never re-arms (polling can't push the deadline out).
|
|
EXPECT_FALSE(Batch::shouldArmConfirmReboot(State::Done, true, true, /*already_armed=*/true));
|
|
// Not applicable while pending, without reboot, or after a partial failure.
|
|
EXPECT_FALSE(Batch::shouldArmConfirmReboot(State::Pending, true, true, false));
|
|
EXPECT_FALSE(Batch::shouldArmConfirmReboot(State::Done, false, true, false));
|
|
EXPECT_FALSE(Batch::shouldArmConfirmReboot(State::Done, true, false, false));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Reboot fire / pending
|
|
// --------------------------------------------------------------------------
|
|
TEST(WebConfigBatch, RebootFiresAtOrAfterTheDeadlineAndNeverWhenUnscheduled) {
|
|
EXPECT_FALSE(Batch::rebootDue(0, 1234567)); // 0 == unscheduled
|
|
const uint32_t at = 100000;
|
|
EXPECT_FALSE(Batch::rebootDue(at, at - 1));
|
|
EXPECT_TRUE(Batch::rebootDue(at, at)); // inclusive boundary
|
|
EXPECT_TRUE(Batch::rebootDue(at, at + 1));
|
|
}
|
|
|
|
TEST(WebConfigBatch, RebootDueSurvivesMillisRollover) {
|
|
const uint32_t at = std::numeric_limits<uint32_t>::max() - 5; // near the top
|
|
EXPECT_FALSE(Batch::rebootDue(at, at - 1));
|
|
EXPECT_TRUE(Batch::rebootDue(at, at));
|
|
EXPECT_TRUE(Batch::rebootDue(at, at + 10)); // now has wrapped past zero
|
|
}
|
|
|
|
TEST(WebConfigBatch, OnlyAConfigSaveRebootInDoneStateReportsPending) {
|
|
EXPECT_TRUE(Batch::isConfigRebootPending(/*reboot_at=*/123, /*batch_reboot=*/true, State::Done));
|
|
// Manual /api/reboot: reboot_at set but batch_reboot false => not "pending".
|
|
EXPECT_FALSE(Batch::isConfigRebootPending(123, false, State::Done));
|
|
// Not yet done, or nothing scheduled.
|
|
EXPECT_FALSE(Batch::isConfigRebootPending(123, true, State::Pending));
|
|
EXPECT_FALSE(Batch::isConfigRebootPending(0, true, State::Done));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Stop gating
|
|
// --------------------------------------------------------------------------
|
|
TEST(WebConfigBatch, StopFinalizesOnlyWhenNoHandlersAreInFlight) {
|
|
EXPECT_EQ(Batch::StopAction::Finalize,
|
|
Batch::stopStep(/*refs=*/0, /*warned=*/false, /*warn_at=*/50000, /*now=*/60000));
|
|
}
|
|
|
|
TEST(WebConfigBatch, StopWarnsOnceAfterTheDeadlineThenKeepsWaiting) {
|
|
const uint32_t warn_at = 50000;
|
|
// Before the warn deadline with handlers in flight: just wait.
|
|
EXPECT_EQ(Batch::StopAction::Wait, Batch::stopStep(2, false, warn_at, warn_at - 1));
|
|
// At the deadline, not yet warned: warn once.
|
|
EXPECT_EQ(Batch::StopAction::Warn, Batch::stopStep(2, false, warn_at, warn_at));
|
|
// Already warned: keep waiting (never warns again, never forces teardown).
|
|
EXPECT_EQ(Batch::StopAction::Wait, Batch::stopStep(2, true, warn_at, warn_at + 100000));
|
|
// An unscheduled warn timer never warns.
|
|
EXPECT_EQ(Batch::StopAction::Wait, Batch::stopStep(2, false, 0, 999999));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// CLI sequences (/api/cli), which share the deferred-command slot
|
|
// --------------------------------------------------------------------------
|
|
TEST(WebConfigBatch, CliReadPagesResultsAndNeverOverrunsWhatHasDrained) {
|
|
const int page = Batch::kCliResultPage;
|
|
// Nothing drained past the cursor yet.
|
|
EXPECT_EQ(0, Batch::cliPageCount(/*from=*/0, /*produced=*/0, page));
|
|
EXPECT_EQ(0, Batch::cliPageCount(/*from=*/3, /*produced=*/3, page));
|
|
// Partial progress: hand back exactly what exists.
|
|
EXPECT_EQ(3, Batch::cliPageCount(0, 3, page));
|
|
EXPECT_EQ(2, Batch::cliPageCount(5, 7, page));
|
|
// More available than fits in one read: cap at the page size.
|
|
EXPECT_EQ(page, Batch::cliPageCount(0, page + 5, page));
|
|
// A cursor beyond what has drained (stale or crafted) yields nothing rather
|
|
// than a negative count that would index backwards through the batch.
|
|
EXPECT_EQ(0, Batch::cliPageCount(/*from=*/9, /*produced=*/4, page));
|
|
}
|
|
|
|
TEST(WebConfigBatch, CliReadIsDoneOnlyOnceEveryResultHasBeenHandedOver) {
|
|
// Still executing: never final, however much has been read.
|
|
EXPECT_FALSE(Batch::cliReadIsFinal(State::Pending, /*from=*/0, /*page=*/8, /*total=*/8));
|
|
// Execution finished but the client has only seen the first page. Reporting
|
|
// "done" here would make a client that stops polling lose the rest.
|
|
EXPECT_FALSE(Batch::cliReadIsFinal(State::Done, /*from=*/0, /*page=*/8, /*total=*/20));
|
|
EXPECT_FALSE(Batch::cliReadIsFinal(State::Done, /*from=*/8, /*page=*/8, /*total=*/20));
|
|
// The read that hands over the last result is the final one.
|
|
EXPECT_TRUE(Batch::cliReadIsFinal(State::Done, /*from=*/16, /*page=*/4, /*total=*/20));
|
|
// Re-reading past the end stays final (polls after the last page).
|
|
EXPECT_TRUE(Batch::cliReadIsFinal(State::Done, /*from=*/20, /*page=*/0, /*total=*/20));
|
|
}
|
|
|
|
// Every string below is a literal lifted from CommonCLI.cpp /
|
|
// CommonCLI_Observer.cpp. Testing only for an "Err" prefix passed five of these
|
|
// off as success, which both coloured them green and let a queued reboot go
|
|
// ahead after them.
|
|
TEST(WebConfigBatch, CliFailureRepliesAreRecognisedInEveryShapeCommonCLIEmits) {
|
|
const char* failures[] = {
|
|
"Err - bad params", // MyMesh setperm
|
|
"ERR: bad pubkey", // neighbor.remove
|
|
"Error: IATA code must be exactly 3 letters",// observer setters
|
|
"(ERR: clock cannot go backwards)", // clock sync, parenthesised
|
|
"Unknown command", // top-level fallthrough
|
|
"unknown config: mqtt.nope", // set fallthrough
|
|
"??: mqtt.nope", // get fallthrough
|
|
"Can't find GPS", // gps
|
|
"File system erase: Err", // failure reported at the end
|
|
};
|
|
for (const char* f : failures) {
|
|
EXPECT_TRUE(Batch::cliReplyIsFailure(f)) << f;
|
|
}
|
|
|
|
const char* successes[] = {
|
|
"OK",
|
|
"OK - slot 1 preset: meshrank",
|
|
"> 22", // getter value
|
|
"> msgs: on, 1: analyzer-us (ok)", // getter, contains "ok"
|
|
"File system erase: OK", // same shape, succeeded
|
|
"Free: 142832, Min: 126808", // memory
|
|
"v1.16.0 (Build: 6 Jun 2026)", // ver
|
|
};
|
|
for (const char* s : successes) {
|
|
EXPECT_FALSE(Batch::cliReplyIsFailure(s)) << s;
|
|
}
|
|
// An empty reply is normalised to "OK" before it ever reaches the client.
|
|
EXPECT_FALSE(Batch::cliReplyIsFailure(""));
|
|
EXPECT_FALSE(Batch::cliReplyIsFailure(NULL));
|
|
}
|
|
|
|
TEST(WebConfigBatch, OnlyWritesGateTheDeferredReboot) {
|
|
// Writes gate it: these are what can leave a config not worth rebooting into.
|
|
EXPECT_TRUE(Batch::cliReplyGatesReboot("set tx 22"));
|
|
EXPECT_TRUE(Batch::cliReplyGatesReboot("set mqtt1.preset meshrank"));
|
|
EXPECT_TRUE(Batch::cliReplyGatesReboot("password hunter2"));
|
|
// Diagnostics do not. `memory` answering "Free: ..." must not be read as a
|
|
// failure and strand the operator's reboot, and a getter's "> value" must not
|
|
// be read as a success either -- neither is asked.
|
|
EXPECT_FALSE(Batch::cliReplyGatesReboot("memory"));
|
|
EXPECT_FALSE(Batch::cliReplyGatesReboot("get tx"));
|
|
EXPECT_FALSE(Batch::cliReplyGatesReboot("reboot"));
|
|
EXPECT_FALSE(Batch::cliReplyGatesReboot("advert"));
|
|
EXPECT_FALSE(Batch::cliReplyGatesReboot(NULL));
|
|
// "settle" must not be mistaken for a `set`; the space is part of the token.
|
|
EXPECT_FALSE(Batch::cliReplyGatesReboot("settle"));
|
|
|
|
// A write counts only on the "OK" prefix every setter keeps.
|
|
EXPECT_TRUE(Batch::cliWriteSucceeded("OK"));
|
|
EXPECT_TRUE(Batch::cliWriteSucceeded("OK - reboot to apply"));
|
|
EXPECT_FALSE(Batch::cliWriteSucceeded("unknown config: nope"));
|
|
EXPECT_FALSE(Batch::cliWriteSucceeded("Error: expected a number"));
|
|
EXPECT_FALSE(Batch::cliWriteSucceeded(""));
|
|
}
|
|
|
|
TEST(WebConfigBatch, CliRebootIsWithheldWhenAnyCommandInTheSequenceFailed) {
|
|
EXPECT_TRUE(Batch::cliRebootAllowed(/*has_reboot=*/true, /*all_ok=*/true));
|
|
// Same rule a config save follows: do not reboot into a half-applied config
|
|
// over a link the operator may not get back.
|
|
EXPECT_FALSE(Batch::cliRebootAllowed(true, false));
|
|
// No `reboot` in the sequence: nothing to allow either way.
|
|
EXPECT_FALSE(Batch::cliRebootAllowed(false, true));
|
|
EXPECT_FALSE(Batch::cliRebootAllowed(false, false));
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Wrap-around guard shared with the production _reboot_at assignments
|
|
// --------------------------------------------------------------------------
|
|
TEST(WebConfigBatch, ScheduleAtNeverReturnsTheUnscheduledSentinel) {
|
|
EXPECT_EQ(1000u + 3000u, Batch::scheduleAt(1000, 3000));
|
|
// A deadline that lands exactly on 0 is bumped to 1 so it still means "set".
|
|
const uint32_t just_below_wrap = std::numeric_limits<uint32_t>::max(); // +1 wraps to 0
|
|
EXPECT_EQ(1u, Batch::scheduleAt(just_below_wrap, 1));
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|