From 419decf91eb756ba9eb3d212f0ffdd0f5afc3217 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Mon, 27 Apr 2026 11:00:34 -0700 Subject: [PATCH] [nexus] migrate `test_reset` to Nexus (#12971) Migrate legacy Python test `test_reset.py` to Nexus C++ test `test_reset.cpp`. The test verifies that OpenThread correctly recovers network state, specifically frame counters and datasets, after sequential resets of nodes in a multi-hop topology (Leader <-> Router <-> ED). The test sequence: - Establish multi-hop topology: Leader <-> Router <-> ED. - Send 1010 pings from ED to Leader to advance the frame counter beyond the default storage threshold (1000). - Reset Leader, Router, and ED sequentially. - Verify end-to-end connectivity after resets, confirming that frame counters were correctly recovered from non-volatile storage. Legacy `tests/scripts/thread-cert/test_reset.py` is removed as its functionality is now fully covered by the Nexus test. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/test_reset.cpp | 154 ++++++++++++++++++++++++ tests/scripts/thread-cert/test_reset.py | 94 --------------- 3 files changed, 155 insertions(+), 94 deletions(-) create mode 100644 tests/nexus/test_reset.cpp delete mode 100755 tests/scripts/thread-cert/test_reset.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 986efbfb9..2d6083067 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -407,6 +407,7 @@ ot_nexus_test(mle_msg_key_seq_jump "core;nexus") ot_nexus_test(nat64_translator "core;nexus") ot_nexus_test(netdata_publisher "core;nexus") ot_nexus_test(reed_address_solicit_rejected "core;nexus") +ot_nexus_test(reset "core;nexus") ot_nexus_test(router_downgrade_on_sec_policy_change "core;nexus") ot_nexus_test(router_reattach "core;nexus") ot_nexus_test(router_reboot_multiple_link_request "core;nexus") diff --git a/tests/nexus/test_reset.cpp b/tests/nexus/test_reset.cpp new file mode 100644 index 000000000..b4ffd7fa1 --- /dev/null +++ b/tests/nexus/test_reset.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2026, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +/** + * Time to advance for a node to form a network and become leader, in milliseconds. + */ +static constexpr uint32_t kFormNetworkTime = 13 * 1000; + +/** + * Time to advance for a node to join as a child and upgrade to a router, in milliseconds. + */ +static constexpr uint32_t kAttachToRouterTime = 200 * 1000; + +/** + * Time to advance for a node to join as a child. + */ +static constexpr uint32_t kAttachAsChildTime = 5 * 1000; + +/** + * The number of pings to send to advance the frame counter beyond the storage threshold. + */ +static constexpr uint16_t kNumPings = 1010; + +void TestReset(void) +{ + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router = nexus.CreateNode(); + Node &ed = nexus.CreateNode(); + + leader.SetName("Leader"); + router.SetName("Router"); + ed.SetName("ED"); + + nexus.AdvanceTime(0); + + // Set up allowlists to enforce Leader <-> Router <-> ED topology + AllowLinkBetween(leader, router); + AllowLinkBetween(router, ed); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelInfo)); + + Log("---------------------------------------------------------------------------------------"); + Log("Forming network"); + + leader.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader.Get().IsLeader()); + + router.Join(leader); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(router.Get().IsRouter()); + + ed.Join(router, Node::kAsMed); + nexus.AdvanceTime(kAttachAsChildTime); + VerifyOrQuit(ed.Get().IsChild()); + + nexus.AdvanceTime(10 * 1000); // Extra time to stabilize + + const Ip6::Address &leaderRloc = leader.Get().GetMeshLocalRloc(); + + Log("---------------------------------------------------------------------------------------"); + Log("Sending %u pings from ED to Leader", kNumPings); + + for (uint16_t i = 0; i < kNumPings; i++) + { + // Use a 500ms timeout for each ping to ensure it has enough time in a multi-hop setup + nexus.SendAndVerifyEchoRequest(ed, leaderRloc, 0, Ip6::kDefaultHopLimit, 500); + } + + Log("---------------------------------------------------------------------------------------"); + Log("Resetting nodes sequentially"); + + Log("Resetting Leader"); + leader.Reset(); + AllowLinkBetween(leader, router); + leader.Get().Up(); + SuccessOrQuit(leader.Get().Start()); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader.Get().IsLeader()); + + Log("Resetting Router"); + router.Reset(); + AllowLinkBetween(router, leader); + AllowLinkBetween(router, ed); + router.Get().Up(); + SuccessOrQuit(router.Get().Start()); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(router.Get().IsRouter()); + + Log("Resetting ED"); + ed.Reset(); + AllowLinkBetween(ed, router); + ed.Get().Up(); + SuccessOrQuit(ed.Get().Start()); + nexus.AdvanceTime(kAttachAsChildTime); + VerifyOrQuit(ed.Get().IsChild()); + + nexus.AdvanceTime(10 * 1000); // Extra time to stabilize + + Log("---------------------------------------------------------------------------------------"); + Log("Verifying connectivity after resets"); + + // The success of this ping after resets is critical. + // Since we sent 1010 pings before resets, the frame counters on all nodes + // have advanced significantly. If a node fails to recover its frame counter + // from settings after reset, it would start at 0, and its packets would be + // rejected by its neighbors. + nexus.SendAndVerifyEchoRequest(ed, leaderRloc); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestReset(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_reset.py b/tests/scripts/thread-cert/test_reset.py deleted file mode 100755 index b23e58d67..000000000 --- a/tests/scripts/thread-cert/test_reset.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2020, The OpenThread Authors. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# - -import config -import unittest -import thread_cert - -LEADER = 1 -ROUTER = 2 -ED = 3 -SED = 4 - - -class TestReset(thread_cert.TestCase): - TOPOLOGY = { - LEADER: { - 'mode': 'rdn', - 'allowlist': [ROUTER] - }, - ROUTER: { - 'mode': 'rdn', - 'allowlist': [LEADER, ED] - }, - ED: { - 'is_mtd': True, - 'mode': 'rn', - 'allowlist': [ROUTER] - }, - } - - def test(self): - self.nodes[LEADER].start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(self.nodes[LEADER].get_state(), 'leader') - - self.nodes[ROUTER].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(self.nodes[ROUTER].get_state(), 'router') - - self.nodes[ED].start() - self.simulator.go(7) - self.assertEqual(self.nodes[ED].get_state(), 'child') - - for i in range(0, 1010): - self.assertTrue(self.nodes[ED].ping(self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.RLOC))) - self.simulator.go(1) - - self.nodes[LEADER].reset() - self.nodes[LEADER].start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(self.nodes[LEADER].get_state(), 'leader') - - self.nodes[ROUTER].reset() - self.nodes[ROUTER].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(self.nodes[ROUTER].get_state(), 'router') - - self.nodes[ED].reset() - self.nodes[LEADER].add_allowlist(self.nodes[ROUTER].get_addr64()) - self.nodes[LEADER].enable_allowlist() - self.nodes[ED].start() - self.simulator.go(7) - self.assertEqual(self.nodes[ED].get_state(), 'child') - self.assertTrue(self.nodes[ED].ping(self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.RLOC))) - - -if __name__ == '__main__': - unittest.main()