From 5340cfabc3e2506b0aeadd992a8f40ee51564115 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Thu, 23 Apr 2026 00:47:41 -0700 Subject: [PATCH] [nexus] migrate test_router_reattach.py to nexus (#12967) This commit migrates the router reattach test from the thread-cert Python framework to the Nexus C++ framework. The new Nexus test 'test_router_reattach.cpp' replicates the original test scenario: - A full 32-node router network is formed. - Router upgrade/downgrade thresholds are set to 32. - A router is reset and verified to re-attach and reclaim its router role. - The test ensures the router does not downgrade after the router selection jitter interval. The original Python script 'tests/scripts/thread-cert/ test_router_reattach.py' is removed as its functionality is now fully covered by Nexus. Nexus tests provide faster and more scalable network simulations within a single process, improving CI efficiency. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/test_router_reattach.cpp | 117 +++++++++ .../thread-cert/test_router_reattach.py | 226 ------------------ 3 files changed, 118 insertions(+), 226 deletions(-) create mode 100644 tests/nexus/test_router_reattach.cpp delete mode 100755 tests/scripts/thread-cert/test_router_reattach.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 4889eff61..dbd5f1c96 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -407,6 +407,7 @@ 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(router_downgrade_on_sec_policy_change "core;nexus") +ot_nexus_test(router_reattach "core;nexus") ot_nexus_test(srp_auto_start "core;nexus") ot_nexus_test(srp_client_change_lease "core;nexus") ot_nexus_test(srp_client_remove_host "core;nexus") diff --git a/tests/nexus/test_router_reattach.cpp b/tests/nexus/test_router_reattach.cpp new file mode 100644 index 000000000..44db1dfb2 --- /dev/null +++ b/tests/nexus/test_router_reattach.cpp @@ -0,0 +1,117 @@ +/* + * 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 = 30 * 1000; + +void TestRouterReattach(void) +{ + Core nexus; + const uint8_t kNumRouters = 32; + Node *nodes[kNumRouters]; + + Log("Creating %u nodes", kNumRouters); + for (uint8_t i = 0; i < kNumRouters; i++) + { + nodes[i] = &nexus.CreateNode(); + nodes[i]->SetName("Node", i + 1); + } + + nexus.AdvanceTime(0); + + // Step 1: Start Leader + Log("Step 1: Starting Leader"); + nodes[0]->Get().SetRouterUpgradeThreshold(32); + nodes[0]->Get().SetRouterDowngradeThreshold(32); + nodes[0]->Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(nodes[0]->Get().IsLeader()); + + // Step 2: Start other 31 routers + Log("Step 2: Starting other 31 routers"); + for (uint8_t i = 1; i < kNumRouters; i++) + { + nodes[i]->Get().SetRouterUpgradeThreshold(32); + nodes[i]->Get().SetRouterDowngradeThreshold(32); + nodes[i]->Get().SetRouterSelectionJitter(1); + nodes[i]->Join(*nodes[0]); + nexus.AdvanceTime(kAttachToRouterTime); + Log("Node %u role: %s", i + 1, nodes[i]->GetExtendedRoleString()); + VerifyOrQuit(nodes[i]->Get().IsRouter()); + } + + // Step 3: Reset Node 2 (index 1) + Log("Step 3: Resetting Node 2"); + nodes[1]->Reset(); + nodes[1]->Get().SetRouterUpgradeThreshold(32); + nodes[1]->Get().SetRouterDowngradeThreshold(32); + nodes[1]->Get().SetRouterSelectionJitter(3); + + // Re-enable and start + Log("Step 4: Restarting Node 2"); + nodes[1]->Get().Up(); + SuccessOrQuit(nodes[1]->Get().Start()); + VerifyOrQuit(nodes[1]->Get().GetRouterDowngradeThreshold() == 32); + + // Verify it restores as Router + Log("Step 5: Verifying Node 2 restores as Router"); + nexus.AdvanceTime(1000); + VerifyOrQuit(nodes[1]->Get().IsRouter()); + + // Verify it doesn't downgrade after Router Selection Jitter + Log("Step 6: Verifying Node 2 does not downgrade"); + nexus.AdvanceTime(5000); + VerifyOrQuit(nodes[1]->Get().IsRouter()); + + nexus.SaveTestInfo("test_router_reattach.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestRouterReattach(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_router_reattach.py b/tests/scripts/thread-cert/test_router_reattach.py deleted file mode 100755 index 2cb4b5b92..000000000 --- a/tests/scripts/thread-cert/test_router_reattach.py +++ /dev/null @@ -1,226 +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 unittest - -import config -import thread_cert - - -class test_router_reattach(thread_cert.TestCase): - TOPOLOGY = { - 1: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 2: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 3: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 4: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 5: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 6: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 7: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 8: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 9: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 10: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 11: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 12: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 13: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 14: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 15: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 16: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 17: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 18: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 19: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 20: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 21: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 22: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 23: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 24: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 25: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 26: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 27: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 28: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 29: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 30: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 31: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - 32: { - 'mode': 'rdn', - 'router_downgrade_threshold': 32, - 'router_upgrade_threshold': 32 - }, - } - - def test(self): - self.nodes[1].start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(self.nodes[1].get_state(), 'leader') - - for i in range(2, 33): - self.nodes[i].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(self.nodes[i].get_state(), 'router') - - self.nodes[2].reset() - self.nodes[2].set_router_selection_jitter(3) - self.nodes[2].set_router_upgrade_threshold(32) - self.nodes[2].set_router_downgrade_threshold(32) - - self.nodes[2].start() - self.assertEqual(self.nodes[2].get_router_downgrade_threshold(), 32) - # Verify that the node restored as Router. - self.simulator.go(1) - self.assertEqual(self.nodes[2].get_state(), 'router') - # Verify that the node does not downgrade after Router Selection Jitter. - self.simulator.go(5) - self.assertEqual(self.nodes[2].get_state(), 'router') - - -if __name__ == '__main__': - unittest.main()