55#include < boost/test/unit_test.hpp>
66
77#include < mapport.h>
8+ #include < chrono>
9+ #include < thread>
10+ #include < test/util/setup_common.h>
11+ #include < netaddress.h>
12+ #include < netbase.h>
13+ #include < util/threadinterrupt.h>
14+ #include < mapport_hooks.h>
15+
16+ // Simple stub implementations matching the hook signatures (no network IO)
17+ static std::optional<CNetAddr> StubNoGateway (Network) { return std::nullopt ; }
18+ static std::vector<CNetAddr> StubNoLocalAddrs () { return {}; }
19+ static std::variant<MappingResult, MappingError> StubPCPNoResources (
20+ const PCPMappingNonce&, const CNetAddr&, const CNetAddr&, uint16_t , uint32_t , CThreadInterrupt&)
21+ {
22+ return MappingError{MappingError::NO_RESOURCES};
23+ }
24+ static std::variant<MappingResult, MappingError> StubPMPNoResources (
25+ const CNetAddr&, uint16_t , uint32_t , CThreadInterrupt&)
26+ {
27+ return MappingError{MappingError::NO_RESOURCES};
28+ }
29+
30+ struct MapportStubGuard {
31+ // Save originals
32+ decltype (mapport_hooks::QueryDefaultGatewayFn) qdg_orig = mapport_hooks::QueryDefaultGatewayFn;
33+ decltype (mapport_hooks::PCPRequestPortMapFn) pcp_orig = mapport_hooks::PCPRequestPortMapFn;
34+ decltype (mapport_hooks::NATPMPRequestPortMapFn) pmp_orig = mapport_hooks::NATPMPRequestPortMapFn;
35+ decltype (mapport_hooks::GetLocalAddressesFn) gla_orig = mapport_hooks::GetLocalAddressesFn;
36+ ~MapportStubGuard () {
37+ mapport_hooks::QueryDefaultGatewayFn = qdg_orig;
38+ mapport_hooks::PCPRequestPortMapFn = pcp_orig;
39+ mapport_hooks::NATPMPRequestPortMapFn = pmp_orig;
40+ mapport_hooks::GetLocalAddressesFn = gla_orig;
41+ }
42+ };
843
944// These tests intentionally avoid enabling any mapping protocol in order to
10- // exercise the control flow in mapport.cpp without starting background threads
11- // or performing any network operations.
45+ // exercise the control flow in mapport.cpp without performing any real
46+ // network operations. We rely on the fact that UPnP support is not compiled
47+ // in this build (USE_UPNP undefined). Enabling UPnP will still start the
48+ // mapport thread, but it won't attempt any UPnP work; we immediately
49+ // interrupt to keep the test fast and deterministic.
1250
13- BOOST_AUTO_TEST_SUITE (mapport_tests)
51+ BOOST_FIXTURE_TEST_SUITE (mapport_tests, BasicTestingSetup )
1452
1553BOOST_AUTO_TEST_CASE(start_stop_no_protocols)
1654{
@@ -26,4 +64,87 @@ BOOST_AUTO_TEST_CASE(start_stop_no_protocols)
2664 StopMapPort ();
2765}
2866
67+ BOOST_AUTO_TEST_CASE (start_thread_with_upnp_only_then_interrupt)
68+ {
69+ // Start the background thread by enabling only UPnP (no actual UPnP code
70+ // will run in this build). Immediately interrupt and stop it to exercise
71+ // ThreadMapPort(), StartThreadMapPort(), InterruptMapPort(), and StopMapPort().
72+ StartMapPort (true , false );
73+
74+ // Give the thread a tiny slice to start. Not strictly necessary, but helps
75+ // stabilize coverage across machines.
76+ std::this_thread::sleep_for (std::chrono::milliseconds (1 ));
77+
78+ InterruptMapPort ();
79+ StopMapPort ();
80+
81+ // Calling stop again should be a no-op.
82+ StopMapPort ();
83+ }
84+
85+ BOOST_AUTO_TEST_CASE (repeated_start_calls_are_idempotent)
86+ {
87+ // Start once with UPnP only, then start again. The second call should not
88+ // start a second thread. Interrupt/stop will terminate the single thread.
89+ StartMapPort (true , false );
90+ StartMapPort (true , false );
91+
92+ InterruptMapPort ();
93+ StopMapPort ();
94+ }
95+
96+ BOOST_AUTO_TEST_CASE (toggle_enable_disable_sequences)
97+ {
98+ // Sequence of toggles to exercise DispatchMapPort paths where
99+ // current==NONE and enabled toggles between NONE and non-NONE.
100+ StartMapPort (false , false ); // No thread should be started.
101+ StartMapPort (true , false ); // Start thread (UPnP-only path).
102+
103+ // Disable again while thread may be running. The thread will only exit
104+ // after interrupt/stop.
105+ StartMapPort (false , false );
106+ InterruptMapPort ();
107+ StopMapPort ();
108+
109+ // Final sanity: calls are safe repeatedly.
110+ InterruptMapPort ();
111+ StopMapPort ();
112+ }
113+
114+ BOOST_AUTO_TEST_CASE (start_with_pcp_only_then_interrupt)
115+ {
116+ // Avoid any real network. Force no default gateways and no local addresses.
117+ MapportStubGuard guard;
118+ mapport_hooks::QueryDefaultGatewayFn = &StubNoGateway;
119+ mapport_hooks::GetLocalAddressesFn = &StubNoLocalAddrs;
120+ mapport_hooks::PCPRequestPortMapFn = &StubPCPNoResources;
121+ mapport_hooks::NATPMPRequestPortMapFn = &StubPMPNoResources;
122+
123+ StartMapPort (false , true );
124+ std::this_thread::sleep_for (std::chrono::milliseconds (1 ));
125+ InterruptMapPort ();
126+ StopMapPort ();
127+ }
128+
129+ BOOST_AUTO_TEST_CASE (enabling_another_protocol_does_not_switch)
130+ {
131+ // Avoid network: no gateways so PCP does nothing.
132+ MapportStubGuard guard;
133+ mapport_hooks::QueryDefaultGatewayFn = [](Network){ return std::optional<CNetAddr>{}; };
134+ mapport_hooks::GetLocalAddressesFn = [](){ return std::vector<CNetAddr>{}; };
135+
136+ // Start with PCP only, then enable UPnP in addition. According to
137+ // DispatchMapPort(), enabling another protocol does not switch away from
138+ // the currently used one; the dispatch should early-return.
139+ StartMapPort (false , true ); // Start PCP path.
140+ std::this_thread::sleep_for (std::chrono::milliseconds (1 ));
141+
142+ // Enable UPnP while PCP is active.
143+ StartMapPort (true , true );
144+
145+ // Clean up.
146+ InterruptMapPort ();
147+ StopMapPort ();
148+ }
149+
29150BOOST_AUTO_TEST_SUITE_END ()
0 commit comments