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