KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_net_chain_rollback.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21
24
25#include <connection_graph.h>
26#include <sch_netchain.h>
27#include <schematic.h>
29#include <locale_io.h>
30
31#include <wx/debug.h>
32
33#include <functional>
34#include <stdexcept>
35
36
37// Test backdoor declared in connection_graph.h.
39 std::unique_ptr<SCH_NETCHAIN> aChain );
40
41
42// Silent no-op assert handler. wxFAIL_MSG routes through wxTheAssertHandler;
43// returning without side effects swallows the assertion so execution continues.
44static void swallowWxAssert( const wxString&, int, const wxString&, const wxString&,
45 const wxString& )
46{
47}
48
49
50// RAII swap of the wx assert handler. The eeschema test_module installs
51// KI_TEST::wxAssertThrower, which converts every wxFAIL_MSG into a thrown
52// WX_ASSERT_ERROR. RebuildNetChains()'s catch block opens with wxFAIL_MSG;
53// under the throwing handler that fires before the rollback statements run,
54// hiding the very behaviour this test is trying to validate. Install a
55// guaranteed-silent handler for the duration of the throw test, then put the
56// throwing handler back.
57//
58// Do not use wxSetDefaultAssertHandler() here. The default handler is not
59// silent across environments: headless CI builds trap/abort on the wxFAIL_MSG,
60// which Boost's signal monitor reports as a failed test, while interactive
61// builds merely log and continue.
63{
65 {
66 wxSetAssertHandler( &swallowWxAssert );
67 }
68
70 {
71 wxSetAssertHandler( &KI_TEST::wxAssertThrower );
72 }
73};
74
75
77{
79
81 {
82 // Defensive cleanup so a failing test never leaks a hook into a sibling test.
84 }
85
87 std::unique_ptr<SCHEMATIC> m_schematic;
88};
89
90
91// Regression for H-12. RebuildNetChains() snapshots m_committedNetChains.size() and
92// m_netChainsBuilt at entry and rolls them back if any of the restore passes throws.
93// A regression that moved the snapshot inside the try, dropped the resize, or forgot to
94// restore the built flag would leak partial chains and report stale readiness. Inject a
95// throw via the test hook and verify the catch handler truncates the vector and restores
96// the flag.
97BOOST_FIXTURE_TEST_CASE( NetChain_RebuildRollback_TruncatesAndRestoresOnThrow,
99{
101 KI_TEST::LoadSchematic( m_settingsManager, wxString( "net_chains_four_nets" ),
102 m_schematic );
103
104 CONNECTION_GRAPH* graph = m_schematic->ConnectionGraph();
105 BOOST_REQUIRE( graph );
106
107 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
108
109 // Initial pass leaves m_netChainsBuilt = true and the snapshot baseline at zero
110 // committed chains (the fixture has none persisted).
111 graph->Recalculate( sheets, /*aUnconditional=*/true );
112 BOOST_REQUIRE( graph->NetChainsBuilt() );
113
114 const std::size_t baselineCount = graph->GetCommittedNetChains().size();
115
116 // Note: Recalculate(true) -> Reset() flips m_netChainsBuilt to false BEFORE
117 // RebuildNetChains() runs, so the snapshot captured at RebuildNetChains entry is
118 // always false on the unconditional path. After the catch block restores the
119 // snapshot value, NetChainsBuilt() must therefore read false.
120 const bool expectedBuiltAfterRollback = false;
121
122 // Hook fires inside the try block, after the restore loops have run. It mimics a
123 // partial-rebuild failure: push a fresh chain onto m_committedNetChains so the
124 // container has grown beyond the snapshot, then throw. The catch block must
125 // truncate back to baselineCount and restore m_netChainsBuilt to baselineBuilt.
126 bool hookFired = false;
128 [&]( CONNECTION_GRAPH& aGraph )
129 {
130 hookFired = true;
131
132 auto stray = std::make_unique<SCH_NETCHAIN>();
133 stray->SetName( wxT( "ROLLBACK_PARTIAL" ) );
134 stray->AddNet( wxT( "/STRAY_NET" ) );
135 boost_test_inject_committed_net_chain( aGraph, std::move( stray ) );
136
137 BOOST_REQUIRE_GT( aGraph.GetCommittedNetChains().size(), 0u );
138
139 throw std::runtime_error( "rollback test injected throw" );
140 };
141
142 {
143 ASSERT_HANDLER_SCOPE silenceWxFail;
144 graph->Recalculate( sheets, /*aUnconditional=*/true );
145 }
146
148
149 BOOST_CHECK_MESSAGE( hookFired,
150 "Rollback hook never fired; snapshot/restore code path was not exercised" );
151
152 BOOST_CHECK_MESSAGE( graph->GetCommittedNetChains().size() == baselineCount,
153 "Catch block did not resize m_committedNetChains back to the snapshot; "
154 "partial chain leaked into the committed list" );
155
156 BOOST_CHECK_MESSAGE( graph->NetChainsBuilt() == expectedBuiltAfterRollback,
157 "Catch block did not restore m_netChainsBuilt; readiness flag is stale" );
158
159 // The stray name we injected must not survive the rollback. GetNetChainByName scans
160 // the live committed list; if rollback worked the lookup misses.
161 BOOST_CHECK_MESSAGE( graph->GetNetChainByName( wxT( "ROLLBACK_PARTIAL" ) ) == nullptr,
162 "Partially-built chain 'ROLLBACK_PARTIAL' survived the rollback" );
163}
164
165
166// Companion check. When RebuildNetChains() throws WITHOUT any growth in
167// m_committedNetChains, the catch block must still restore the built-flag snapshot
168// and clear m_potentialNetChains, leaving the graph in a clean pre-rebuild state.
169// Catches a regression that flipped m_netChainsBuilt to true before the hook call.
170BOOST_FIXTURE_TEST_CASE( NetChain_RebuildRollback_NoGrowthStillRestoresFlagAndPotentials,
172{
174 KI_TEST::LoadSchematic( m_settingsManager, wxString( "net_chains_four_nets" ),
175 m_schematic );
176
177 CONNECTION_GRAPH* graph = m_schematic->ConnectionGraph();
178 BOOST_REQUIRE( graph );
179
180 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
181
182 // LoadSchematic already ran a Recalculate, so the graph has potentials and built==true.
183 // We re-run with throw to confirm rollback returns the built flag to false (the
184 // snapshot value captured AFTER Reset() runs at the top of Recalculate(true)).
185 BOOST_REQUIRE( graph->NetChainsBuilt() );
186 const std::size_t baselineCount = graph->GetCommittedNetChains().size();
187
188 bool hookFired = false;
190 [&]( CONNECTION_GRAPH& )
191 {
192 hookFired = true;
193 throw std::runtime_error( "no-growth rollback test throw" );
194 };
195
196 {
197 ASSERT_HANDLER_SCOPE silenceWxFail;
198 graph->Recalculate( sheets, /*aUnconditional=*/true );
199 }
200
202
203 BOOST_CHECK( hookFired );
204
205 // Reset() inside Recalculate(true) clears m_netChainsBuilt to false BEFORE
206 // RebuildNetChains() snapshots it; after rollback the flag must read false.
208 "Rollback left m_netChainsBuilt = true; the success-flag write "
209 "escaped the catch handler's snapshot restore" );
210
211 BOOST_CHECK_EQUAL( graph->GetCommittedNetChains().size(), baselineCount );
212
213 // The catch block also clears m_potentialNetChains so a partial pass cannot leak
214 // half-populated inferred groupings into the next consumer.
215 BOOST_CHECK( graph->GetPotentialNetChains().empty() );
216}
Calculate the connectivity of a schematic and generates netlists.
SCH_NETCHAIN * GetNetChainByName(const wxString &aName)
static std::function< void(CONNECTION_GRAPH &)> & RebuildNetChainsTestHook()
Test-only hook fired inside RebuildNetChains() after the restore passes have finished but before the ...
void Recalculate(const SCH_SHEET_LIST &aSheetList, bool aUnconditional=false, std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Update the connection graph for the given list of sheets.
const std::vector< std::unique_ptr< SCH_NETCHAIN > > & GetPotentialNetChains() const
Potential net chains are inferred groupings produced by RebuildNetChains() but not yet user-committed...
bool NetChainsBuilt() const
Returns true once RebuildNetChains() has completed at least once on this graph.
const std::vector< std::unique_ptr< SCH_NETCHAIN > > & GetCommittedNetChains() const
Return user-created (committed) net chains (legacy accessor retained under net-chain API).
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
void wxAssertThrower(const wxString &aFile, int aLine, const wxString &aFunc, const wxString &aCond, const wxString &aMsg)
Definition wx_assert.h:67
std::vector< FAB_LAYER_COLOR > dummy
std::unique_ptr< SCHEMATIC > m_schematic
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
void boost_test_inject_committed_net_chain(CONNECTION_GRAPH &aGraph, std::unique_ptr< SCH_NETCHAIN > aChain)
BOOST_FIXTURE_TEST_CASE(NetChain_RebuildRollback_TruncatesAndRestoresOnThrow, NETCHAIN_ROLLBACK_FIXTURE)
static void swallowWxAssert(const wxString &, int, const wxString &, const wxString &, const wxString &)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_CHECK_EQUAL(result, "25.4")