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, you may find one here:
18 * https://www.gnu.org/licenses/gpl-3.0.en.html
19 */
20
21#include <boost/test/unit_test.hpp>
22
25
26#include <connection_graph.h>
27#include <sch_netchain.h>
28#include <schematic.h>
30#include <locale_io.h>
31
32#include <wx/debug.h>
33
34#include <functional>
35#include <stdexcept>
36
37
38// Test backdoor declared in connection_graph.h.
40 std::unique_ptr<SCH_NETCHAIN> aChain );
41
42
43// Silent no-op assert handler. wxFAIL_MSG routes through wxTheAssertHandler;
44// returning without side effects swallows the assertion so execution continues.
45static void swallowWxAssert( const wxString&, int, const wxString&, const wxString&,
46 const wxString& )
47{
48}
49
50
51// RAII swap of the wx assert handler. The eeschema test_module installs
52// KI_TEST::wxAssertThrower, which converts every wxFAIL_MSG into a thrown
53// WX_ASSERT_ERROR. RebuildNetChains()'s catch block opens with wxFAIL_MSG;
54// under the throwing handler that fires before the rollback statements run,
55// hiding the very behaviour this test is trying to validate. Install a
56// guaranteed-silent handler for the duration of the throw test, then put the
57// throwing handler back.
58//
59// Do not use wxSetDefaultAssertHandler() here. The default handler is not
60// silent across environments: headless CI builds trap/abort on the wxFAIL_MSG,
61// which Boost's signal monitor reports as a failed test, while interactive
62// builds merely log and continue.
64{
66 {
67 wxSetAssertHandler( &swallowWxAssert );
68 }
69
71 {
72 wxSetAssertHandler( &KI_TEST::wxAssertThrower );
73 }
74};
75
76
78{
80
82 {
83 // Defensive cleanup so a failing test never leaks a hook into a sibling test.
85 }
86
88 std::unique_ptr<SCHEMATIC> m_schematic;
89};
90
91
92// Regression for H-12. RebuildNetChains() snapshots m_committedNetChains.size() and
93// m_netChainsBuilt at entry and rolls them back if any of the restore passes throws.
94// A regression that moved the snapshot inside the try, dropped the resize, or forgot to
95// restore the built flag would leak partial chains and report stale readiness. Inject a
96// throw via the test hook and verify the catch handler truncates the vector and restores
97// the flag.
98BOOST_FIXTURE_TEST_CASE( NetChain_RebuildRollback_TruncatesAndRestoresOnThrow,
100{
102 KI_TEST::LoadSchematic( m_settingsManager, wxString( "net_chains_four_nets" ),
103 m_schematic );
104
105 CONNECTION_GRAPH* graph = m_schematic->ConnectionGraph();
106 BOOST_REQUIRE( graph );
107
108 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
109
110 // Initial pass leaves m_netChainsBuilt = true and the snapshot baseline at zero
111 // committed chains (the fixture has none persisted).
112 graph->Recalculate( sheets, /*aUnconditional=*/true );
113 BOOST_REQUIRE( graph->NetChainsBuilt() );
114
115 const std::size_t baselineCount = graph->GetCommittedNetChains().size();
116
117 // Note: Recalculate(true) -> Reset() flips m_netChainsBuilt to false BEFORE
118 // RebuildNetChains() runs, so the snapshot captured at RebuildNetChains entry is
119 // always false on the unconditional path. After the catch block restores the
120 // snapshot value, NetChainsBuilt() must therefore read false.
121 const bool expectedBuiltAfterRollback = false;
122
123 // Hook fires inside the try block, after the restore loops have run. It mimics a
124 // partial-rebuild failure: push a fresh chain onto m_committedNetChains so the
125 // container has grown beyond the snapshot, then throw. The catch block must
126 // truncate back to baselineCount and restore m_netChainsBuilt to baselineBuilt.
127 bool hookFired = false;
129 [&]( CONNECTION_GRAPH& aGraph )
130 {
131 hookFired = true;
132
133 auto stray = std::make_unique<SCH_NETCHAIN>();
134 stray->SetName( wxT( "ROLLBACK_PARTIAL" ) );
135 stray->AddNet( wxT( "/STRAY_NET" ) );
136 boost_test_inject_committed_net_chain( aGraph, std::move( stray ) );
137
138 BOOST_REQUIRE_GT( aGraph.GetCommittedNetChains().size(), 0u );
139
140 throw std::runtime_error( "rollback test injected throw" );
141 };
142
143 {
144 ASSERT_HANDLER_SCOPE silenceWxFail;
145 graph->Recalculate( sheets, /*aUnconditional=*/true );
146 }
147
149
150 BOOST_CHECK_MESSAGE( hookFired,
151 "Rollback hook never fired; snapshot/restore code path was not exercised" );
152
153 BOOST_CHECK_MESSAGE( graph->GetCommittedNetChains().size() == baselineCount,
154 "Catch block did not resize m_committedNetChains back to the snapshot; "
155 "partial chain leaked into the committed list" );
156
157 BOOST_CHECK_MESSAGE( graph->NetChainsBuilt() == expectedBuiltAfterRollback,
158 "Catch block did not restore m_netChainsBuilt; readiness flag is stale" );
159
160 // The stray name we injected must not survive the rollback. GetNetChainByName scans
161 // the live committed list; if rollback worked the lookup misses.
162 BOOST_CHECK_MESSAGE( graph->GetNetChainByName( wxT( "ROLLBACK_PARTIAL" ) ) == nullptr,
163 "Partially-built chain 'ROLLBACK_PARTIAL' survived the rollback" );
164}
165
166
167// Companion check. When RebuildNetChains() throws WITHOUT any growth in
168// m_committedNetChains, the catch block must still restore the built-flag snapshot
169// and clear m_potentialNetChains, leaving the graph in a clean pre-rebuild state.
170// Catches a regression that flipped m_netChainsBuilt to true before the hook call.
171BOOST_FIXTURE_TEST_CASE( NetChain_RebuildRollback_NoGrowthStillRestoresFlagAndPotentials,
173{
175 KI_TEST::LoadSchematic( m_settingsManager, wxString( "net_chains_four_nets" ),
176 m_schematic );
177
178 CONNECTION_GRAPH* graph = m_schematic->ConnectionGraph();
179 BOOST_REQUIRE( graph );
180
181 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
182
183 // LoadSchematic already ran a Recalculate, so the graph has potentials and built==true.
184 // We re-run with throw to confirm rollback returns the built flag to false (the
185 // snapshot value captured AFTER Reset() runs at the top of Recalculate(true)).
186 BOOST_REQUIRE( graph->NetChainsBuilt() );
187 const std::size_t baselineCount = graph->GetCommittedNetChains().size();
188
189 bool hookFired = false;
191 [&]( CONNECTION_GRAPH& )
192 {
193 hookFired = true;
194 throw std::runtime_error( "no-growth rollback test throw" );
195 };
196
197 {
198 ASSERT_HANDLER_SCOPE silenceWxFail;
199 graph->Recalculate( sheets, /*aUnconditional=*/true );
200 }
201
203
204 BOOST_CHECK( hookFired );
205
206 // Reset() inside Recalculate(true) clears m_netChainsBuilt to false BEFORE
207 // RebuildNetChains() snapshots it; after rollback the flag must read false.
209 "Rollback left m_netChainsBuilt = true; the success-flag write "
210 "escaped the catch handler's snapshot restore" );
211
212 BOOST_CHECK_EQUAL( graph->GetCommittedNetChains().size(), baselineCount );
213
214 // The catch block also clears m_potentialNetChains so a partial pass cannot leak
215 // half-populated inferred groupings into the next consumer.
216 BOOST_CHECK( graph->GetPotentialNetChains().empty() );
217}
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:41
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:71
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")