KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_incremental_netlister.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
23
24#include <connection_graph.h>
25#include <schematic.h>
26#include <sch_sheet.h>
27#include <sch_screen.h>
28#include <sch_symbol.h>
29#include <sch_pin.h>
30#include <lib_symbol.h>
32#include <locale_io.h>
33
35{
38
40 std::unique_ptr<SCHEMATIC> m_schematic;
41};
42
43BOOST_FIXTURE_TEST_CASE( DestroyingAnotherSchematicPreservesPinCleanup, CONNECTIVITY_TEST_FIXTURE )
44{
45 KI_TEST::LoadSchematic( m_settingsManager, "issue7203", m_schematic );
46 auto* graph = m_schematic->ConnectionGraph();
47 graph->Recalculate( m_schematic->Hierarchy(), true );
48 SCH_SCREEN* screen = m_schematic->Hierarchy().front().LastScreen();
49 auto symbols = screen->Items().OfType( SCH_SYMBOL_T );
50 BOOST_REQUIRE( symbols.begin() != symbols.end() );
51 auto* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
52 std::vector<SCH_PIN*> pins = symbol->GetPins();
53 BOOST_REQUIRE( !pins.empty() );
54 BOOST_REQUIRE( graph->GetSubgraphForItem( pins.front() ) );
55
56 {
57 SCHEMATIC other( nullptr );
58 }
59
60 screen->Remove( symbol );
61 delete symbol;
62
63 for( SCH_PIN* pin : pins )
64 BOOST_CHECK( !graph->GetSubgraphForItem( pin ) );
65}
66
67
68BOOST_FIXTURE_TEST_CASE( PinCleanupHandlesMultipleGraphsAndExpiredOwners, CONNECTIVITY_TEST_FIXTURE )
69{
70 KI_TEST::LoadSchematic( m_settingsManager, "issue7203", m_schematic );
71 auto* mainGraph = m_schematic->ConnectionGraph();
72 const auto paths = m_schematic->Hierarchy();
73 mainGraph->Recalculate( paths, true );
74 auto expired = std::make_unique<CONNECTION_GRAPH>( m_schematic.get() );
75 expired->Recalculate( paths, true );
76 CONNECTION_GRAPH otherGraph( m_schematic.get() );
77 otherGraph.Recalculate( paths, true );
78 SCH_SCREEN* screen = paths.front().LastScreen();
79 auto symbols = screen->Items().OfType( SCH_SYMBOL_T );
80 BOOST_REQUIRE( symbols.begin() != symbols.end() );
81 auto* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
82 const auto pins = symbol->GetPins();
83 BOOST_REQUIRE( !pins.empty() );
84
85 for( SCH_PIN* pin : pins )
86 {
87 BOOST_REQUIRE( mainGraph->GetSubgraphForItem( pin ) );
88 BOOST_REQUIRE( otherGraph.GetSubgraphForItem( pin ) );
89 }
90
91 // ASAN detects stale owner access during symbol destruction below.
92 expired.reset();
93 screen->Remove( symbol );
94 delete symbol;
95
96 for( SCH_PIN* pin : pins )
97 {
98 BOOST_CHECK( !mainGraph->GetSubgraphForItem( pin ) );
99 BOOST_CHECK( !otherGraph.GetSubgraphForItem( pin ) );
100 }
101}
102
103
105{
107
108 // Check for Errors when using global labels
109 std::vector<wxString> tests = {// "incremental_test",
110 // "issue10430",
111 // "issue10926_1",
112 // "issue11926",
113 // "issue12505",
114 // "issue12814",
115 // "issue13112",
116 // "issue13162",
117 // "issue13212",
118 // "issue13431",
119 // "issue13591",
120 // "issue16223",
121 // "issue6588",
122 "issue7203"};//,
123 // "issue9367"};
124
125 for( const wxString& test : tests )
126 {
127 KI_TEST::LoadSchematic( m_settingsManager, test, m_schematic );
128
129 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
130
131 for( const SCH_SHEET_PATH& path : sheets )
132 {
133 for( size_t ii = 0; ii < path.size(); ++ii )
134 {
135 const SCH_SHEET* sheet = path.GetSheet( ii );
136 const SCH_SCREEN* screen = sheet->GetScreen();
137 std::vector<SCH_ITEM*> items;
138
139 for( SCH_ITEM* item : screen->Items() )
140 {
141 if( !item->IsConnectable() )
142 {
143 continue;
144 }
145
146 if( item->Type() == SCH_SYMBOL_T )
147 {
148 for( SCH_PIN* pin : static_cast<SCH_SYMBOL*>( item )->GetPins() )
149 {
150 items.push_back( pin );
151 }
152 }
153 else
154 {
155 items.push_back( item );
156 }
157 }
158
159 for( SCH_ITEM* item : items )
160 {
161 const std::vector<SCH_ITEM*>& conn_items = item->ConnectedItems( path );
162 SCH_CONNECTION* conn = item->Connection();
163 wxString netname = conn ? conn->GetNetName() : wxString( "NoNet" );
164 int subgraph = conn ? conn->SubgraphCode() : -1;
165
166 BOOST_TEST_MESSAGE( test.ToStdString()
167 << ": Item " << item->GetFriendlyName().ToStdString()
168 << " in net " << netname.ToStdString() << " subgraph " << subgraph
169 << " has " << conn_items.size() << " connections" );
170
171 if( !conn )
172 continue;
173
174 if( !item->IsConnectable() )
175 continue;
176
177 std::vector<SCH_ITEM*> prev_items = item->ConnectedItems( path );
178 std::sort( prev_items.begin(), prev_items.end() );
179 alg::remove_duplicates( prev_items );
180
181 std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> all_items =
182 m_schematic->ConnectionGraph()->ExtractAffectedItems( { item } );
183 all_items.insert( { path, item } );
184 BOOST_TEST_MESSAGE( test.ToStdString()
185 << ": Item " << item->GetFriendlyName().ToStdString()
186 << " in net " << netname.ToStdString()
187 << " has " << all_items.size() << " affected items" );
188
189 CONNECTION_GRAPH new_graph( m_schematic.get() );
190
191 new_graph.SetLastCodes( m_schematic->ConnectionGraph() );
192
193 for( auto&[ apath, aitem ] : all_items )
194 {
195 wxCHECK2( aitem, continue );
196 aitem->SetConnectivityDirty();
197 }
198
199 new_graph.Recalculate( sheets, false );
200 m_schematic->ConnectionGraph()->Merge( new_graph );
201
202 std::vector<SCH_ITEM*> curr_items = item->ConnectedItems( path );
203 std::sort( curr_items.begin(), curr_items.end() );
204 alg::remove_duplicates( curr_items );
205
206 BOOST_CHECK_MESSAGE( prev_items == curr_items,
207 test.ToStdString()
208 << ": Item " << item->GetFriendlyName().ToStdString()
209 << " in net " << netname.ToStdString()
210 << " changed from " << prev_items.size() << " to " << curr_items.size()
211 << " Location:" << item->GetPosition().x << "," << item->GetPosition().y );
212 }
213
214 }
215 }
216 }
217}
218
219
220// Reproducer for Sentry KICAD-4SJ / KICAD-10HY. A pin on a shared (multi-instantiated) sheet is
221// registered in one subgraph per sheet path, but the connection graph's item-to-subgraph map only
222// remembers one of them. When SCH_SYMBOL::UpdatePins() frees the pin after a library update that
223// dropped it, ~SCH_ITEM only cleans the mapped subgraph and the other instance keeps a dangling
224// driver, which a later recalculation hands to CONNECTION_SUBGRAPH::ResolveDrivers().
225BOOST_FIXTURE_TEST_CASE( SharedSheetUpdatePinsNoDanglingDriver, CONNECTIVITY_TEST_FIXTURE )
226{
228
229 KI_TEST::LoadSchematic( m_settingsManager, wxS( "netlists/complex_hierarchy_shared/complex_hierarchy" ),
230 m_schematic );
231
232 CONNECTION_GRAPH* graph = m_schematic->ConnectionGraph();
233 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
234
235 std::map<SCH_SCREEN*, int> instanceCount;
236
237 for( const SCH_SHEET_PATH& path : sheets )
238 instanceCount[path.LastScreen()]++;
239
240 // Count how many retained subgraphs still reference an item. Pointer identity only; the
241 // pointer may be freed by the time this runs.
242 auto countRefs =
243 [&]( SCH_ITEM* aItem ) -> int
244 {
245 int refs = 0;
246
247 for( const auto& [key, subgraphs] : graph->GetNetMap() )
248 {
249 for( CONNECTION_SUBGRAPH* sg : subgraphs )
250 {
251 if( sg->GetItems().count( aItem ) )
252 refs++;
253 }
254 }
255
256 return refs;
257 };
258
259 // Find a pin on a shared screen that the initial full rebuild registered once per sheet path
260 SCH_SYMBOL* symbol = nullptr;
261 SCH_PIN* victim = nullptr;
262
263 for( auto& [screen, count] : instanceCount )
264 {
265 if( count < 2 )
266 continue;
267
268 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
269 {
270 SCH_SYMBOL* candidate = static_cast<SCH_SYMBOL*>( item );
271
272 if( !candidate->GetLibSymbolRef() || candidate->GetPins().size() < 2 )
273 continue;
274
275 for( SCH_PIN* pin : candidate->GetPins() )
276 {
277 if( countRefs( pin ) >= 2 )
278 {
279 symbol = candidate;
280 victim = pin;
281 break;
282 }
283 }
284
285 if( symbol )
286 break;
287 }
288
289 if( symbol )
290 break;
291 }
292
293 BOOST_REQUIRE_MESSAGE( symbol && victim, "No shared-sheet pin registered on multiple paths" );
294
295 wxString pinNumber = victim->GetNumber();
296 std::vector<SCH_PIN*> doomedPins = symbol->GetPinsByNumber( pinNumber );
297
298 BOOST_REQUIRE( !doomedPins.empty() );
299
300 std::vector<SCH_ITEM*> danglingCandidates( doomedPins.begin(), doomedPins.end() );
301
302 // Update the symbol from a library version that no longer has this pin. This mirrors
303 // Update Symbol from Library; SetLibSymbol() -> UpdatePins() frees the surplus SCH_PINs.
304 std::unique_ptr<LIB_SYMBOL> updated = symbol->GetLibSymbolRef()->Flatten();
305
306 for( SCH_PIN* libPin : updated->GetPinsByNumber( pinNumber ) )
307 updated->RemoveDrawItem( libPin );
308
309 symbol->SetLibSymbol( updated.release() );
310
311 BOOST_REQUIRE( symbol->GetPinsByNumber( pinNumber ).empty() );
312
313 // The freed pins must be gone from every retained subgraph, or the next incremental
314 // recalculation will dereference them from a thread pool worker
315 for( SCH_ITEM* freedPin : danglingCandidates )
316 {
317 BOOST_CHECK_MESSAGE( countRefs( freedPin ) == 0,
318 "Freed pin " << pinNumber.ToStdString()
319 << " still referenced by a retained subgraph" );
320 }
321}
322
323
324// Reproducer for issue 16836. A global label joining a local net to an existing multi-sheet net
325// used to rebuild only its own sheet, and the merge then dropped the rest of the net
326BOOST_FIXTURE_TEST_CASE( IncrementalMergeKeepsOtherSheetsOnNet, CONNECTIVITY_TEST_FIXTURE )
327{
329
330 KI_TEST::LoadSchematic( m_settingsManager, wxT( "issue16836/issue16836" ), m_schematic );
331
332 CONNECTION_GRAPH* graph = m_schematic->ConnectionGraph();
333 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
334
335 const wxString globalNet = wxT( "+12V" );
336 const wxString localNet = wxT( "local_label_on_page7" );
337
338 auto sheetsOnNet =
339 [&]( const wxString& aNetName )
340 {
341 std::set<wxString> paths;
342
343 for( const CONNECTION_SUBGRAPH* sg : graph->GetAllSubgraphs( aNetName ) )
344 paths.insert( sg->GetSheet().PathAsString() );
345
346 return paths;
347 };
348
349 const std::set<wxString> before = sheetsOnNet( globalNet );
350
351 BOOST_REQUIRE_MESSAGE( before.size() > 1,
352 "Fixture net " << globalNet.ToStdString() << " must span several sheets" );
353
354 // The reporter left a free-standing local label on the page they edited, so dropping a global
355 // label onto it reproduces their step without guessing at geometry
356 SCH_LABEL* localLabel = nullptr;
357 SCH_SHEET_PATH localPath;
358
359 for( const SCH_SHEET_PATH& path : sheets )
360 {
361 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_LABEL_T ) )
362 {
363 if( static_cast<SCH_LABEL*>( item )->GetText() == localNet )
364 {
365 localLabel = static_cast<SCH_LABEL*>( item );
366 localPath = path;
367 }
368 }
369 }
370
371 BOOST_REQUIRE_MESSAGE( localLabel, "Fixture must carry the local label from the issue report" );
372
373 SCH_GLOBALLABEL* newLabel = new SCH_GLOBALLABEL( localLabel->GetPosition(), globalNet );
374 localPath.LastScreen()->Append( newLabel );
375
376 // Mirror the incremental branch of SCHEMATIC::RecalculateConnections()
377 std::set<SCH_ITEM*> changed = { newLabel, localLabel };
378
379 std::set<std::pair<SCH_SHEET_PATH, SCH_ITEM*>> affected = graph->ExtractAffectedItems( changed );
380
381 affected.insert( { localPath, newLabel } );
382 affected.insert( { localPath, localLabel } );
383
384 for( const auto& [path, item] : affected )
385 item->SetConnectivityDirty();
386
387 CONNECTION_GRAPH new_graph( m_schematic.get() );
388 new_graph.SetLastCodes( graph );
389 new_graph.Recalculate( sheets, false );
390 graph->Merge( new_graph );
391
392 const std::set<wxString> after = sheetsOnNet( globalNet );
393
394 for( const wxString& path : before )
395 {
396 BOOST_CHECK_MESSAGE( after.count( path ),
397 "Sheet " << path.ToStdString() << " dropped from net "
398 << globalNet.ToStdString() << " (" << before.size() << " -> "
399 << after.size() << " sheets)" );
400 }
401
402 // Rebuilding only part of the net would also leave the survivors on their old net code, which
403 // splits the net for every consumer of GetNetMap()
404 std::set<int> codes;
405
406 for( const auto& [key, subgraphs] : graph->GetNetMap() )
407 {
408 if( key.Name == globalNet )
409 codes.insert( key.Netcode );
410 }
411
412 BOOST_CHECK_MESSAGE( codes.size() == 1, "Net " << globalNet.ToStdString() << " split across "
413 << codes.size() << " net codes" );
414}
Calculate the connectivity of a schematic and generate netlists.
const NET_MAP & GetNetMap() const
const std::vector< CONNECTION_SUBGRAPH * > & GetAllSubgraphs(const wxString &aNetName) const
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.
std::set< std::pair< SCH_SHEET_PATH, SCH_ITEM * > > ExtractAffectedItems(const std::set< SCH_ITEM * > &aItems)
For a set of items, this will remove the connected items and their associated data including subgraph...
void SetLastCodes(const CONNECTION_GRAPH *aOther)
void Merge(CONNECTION_GRAPH &aGraph)
Combine the input graph contents into the current graph.
CONNECTION_SUBGRAPH * GetSubgraphForItem(SCH_ITEM *aItem) const
A subgraph is a set of items that are electrically connected on a single sheet.
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
std::unique_ptr< LIB_SYMBOL > Flatten() const
Return a flattened symbol inheritance to the caller.
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
Holds all the data relating to one schematic.
Definition schematic.h:148
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
wxString GetNetName() const
int SubgraphCode() const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
const wxString & GetNumber() const
Definition sch_pin.h:142
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
bool Remove(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Remove aItem from the schematic associated with this screen.
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_SCREEN * LastScreen()
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
Schematic symbol object.
Definition sch_symbol.h:75
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
std::vector< SCH_PIN * > GetPinsByNumber(const wxString &aNumber) const
Find all symbol pins with the given number.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:183
void SetLibSymbol(LIB_SYMBOL *aLibSymbol)
Set this schematic symbol library symbol reference to aLibSymbol.
VECTOR2I GetPosition() const override
Definition sch_text.h:143
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
void remove_duplicates(_Container &__c)
Deletes all duplicate values from __c.
Definition kicad_algo.h:157
std::vector< FAB_LAYER_COLOR > dummy
std::unique_ptr< SCHEMATIC > m_schematic
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_FIXTURE_TEST_CASE(DestroyingAnotherSchematicPreservesPinCleanup, CONNECTIVITY_TEST_FIXTURE)
std::string path
KIBIS_PIN * pin
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_LABEL_T
Definition typeinfo.h:163