KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_issue24409_cli_hierarchical_instances.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 2
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// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24409
21
23
24#include <memory>
25#include <set>
26
27#include <advanced_config.h>
28#include <connection_graph.h>
29#include <eeschema_helpers.h>
30#include <erc/erc.h>
31#include <erc/erc_item.h>
32#include <erc/erc_report.h>
33#include <erc/erc_settings.h>
34#include <locale_io.h>
35#include <scoped_set_reset.h>
36#include <sch_marker.h>
37#include <sch_pin.h>
38#include <sch_reference_list.h>
39#include <sch_screen.h>
40#include <sch_sheet.h>
41#include <sch_sheet_path.h>
42#include <sch_symbol.h>
43#include <schematic.h>
44
45
46// The reproduction case has three sheet instances of one sub-sheet with two
47// resistors R1/R2 (no wires). Per-instance annotations make this R1+R2,
48// R3+R4 and R5+R6. RunERC dedups markers that share a driver pin so each
49// pin produces exactly one pin_not_connected marker, but the dedup must
50// pick the marker on the *first* sheet path (by page number) so that
51// kicad-cli produces the same report a user sees in the GUI. Before the
52// fix the unordered iteration of SCH_ITEM::m_connection_map caused
53// subgraphs to be created in hash-bucket order, so the marker landed on
54// whichever instance happened to surface first (R5/R6 on this layout).
55BOOST_AUTO_TEST_CASE( Issue24409CliHierarchicalInstances )
56{
58 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
59 SCOPED_SET_RESET restore( enabled, enabled );
60
61 wxString schPath = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() ) +
62 wxS( "issue24409/issue24409.kicad_sch" );
63
64 for( bool useEngine : { false, true } )
65 {
66 BOOST_TEST_CONTEXT( "engine=" << useEngine )
67 {
68 enabled = useEngine;
69
70 // Use the CLI/headless loader directly so the regression covers the path
71 // exercised by kicad-cli sch erc / sch export bom / sch export netlist.
72 std::unique_ptr<SCHEMATIC> sch( EESCHEMA_HELPERS::LoadSchematic( schPath, true, false ) );
73 BOOST_REQUIRE( sch != nullptr );
74
75 SCH_SHEET_LIST sheets = sch->Hierarchy();
76
77 // Sanity: three sub-sheet instances + the root sheet.
78 BOOST_REQUIRE_EQUAL( sheets.size(), 4u );
79
80 // The two resistors get annotated R1..R6 once instances are resolved.
82 sheets.GetSymbols( refs, SYMBOL_FILTER_NON_POWER, false );
83 BOOST_REQUIRE_EQUAL( refs.GetCount(), 6u );
84
85 std::set<wxString> seenRefs;
86
87 for( size_t i = 0; i < refs.GetCount(); ++i )
88 seenRefs.insert( refs[i].GetRef() );
89
90 BOOST_CHECK( seenRefs.count( wxS( "R1" ) ) );
91 BOOST_CHECK( seenRefs.count( wxS( "R2" ) ) );
92 BOOST_CHECK( seenRefs.count( wxS( "R3" ) ) );
93 BOOST_CHECK( seenRefs.count( wxS( "R4" ) ) );
94 BOOST_CHECK( seenRefs.count( wxS( "R5" ) ) );
95 BOOST_CHECK( seenRefs.count( wxS( "R6" ) ) );
96
97 // Now run ERC against the loaded schematic and verify that every pin on
98 // every sheet instance has been reported. Disable the library-mismatch
99 // and sim-model checks because the reproduction project ships without
100 // installed libraries.
101 ERC_SETTINGS& settings = sch->ErcSettings();
105
106 sch->ConnectionGraph()->RunERC();
107
108 // Walk every sheet's screen and count pin_not_connected markers.
109 int unconnectedMarkers = 0;
110 std::set<wxString> markedRefs;
111
112 std::set<const SCH_SCREEN*> visitedScreens;
113
114 for( const SCH_SHEET_PATH& sheet : sheets )
115 {
116 SCH_SCREEN* screen = sheet.LastScreen();
117
118 // Each underlying screen carries its own markers; visit each only once.
119 if( !visitedScreens.insert( screen ).second )
120 continue;
121
122 for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) )
123 {
124 SCH_MARKER* marker = static_cast<SCH_MARKER*>( item );
125 std::shared_ptr<ERC_ITEM> ercItem =
126 std::dynamic_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
127
128 if( !ercItem || ercItem->GetErrorCode() != ERCE_PIN_NOT_CONNECTED )
129 continue;
130
131 ++unconnectedMarkers;
132
133 const SCH_SHEET_PATH& markerSheet = ercItem->IsSheetSpecific()
134 ? ercItem->GetSpecificSheetPath()
135 : sheet;
136 EDA_ITEM* erred = sch->ResolveItem( ercItem->GetMainItemID(), nullptr, true );
137
138 if( SCH_PIN* pin = dynamic_cast<SCH_PIN*>( erred ) )
139 {
140 if( SCH_SYMBOL* parent = dynamic_cast<SCH_SYMBOL*>( pin->GetParentSymbol() ) )
141 markedRefs.insert( parent->GetRef( &markerSheet ) );
142 }
143 }
144 }
145
146 // 2 resistors per sub-sheet * 2 pins per resistor = 4 logical pins; the
147 // RunERC dedup collapses the three instances of each pin down to one
148 // marker apiece so we expect 4 markers in total.
149 BOOST_CHECK_MESSAGE( unconnectedMarkers == 4,
150 "Expected 4 deduplicated pin_not_connected markers, got "
151 << unconnectedMarkers );
152
153 // The dedup must keep the marker on the first sheet instance by page
154 // number (Untitled Sheet, page 2, annotated R1/R2) so that kicad-cli
155 // matches the GUI. Without the fix this is R5/R6 because the unordered
156 // iteration of m_connection_map surfaces the last sheet first.
157 for( const wxString ref : { wxS( "R1" ), wxS( "R2" ) } )
158 {
159 BOOST_CHECK_MESSAGE( markedRefs.count( ref ),
160 "Missing pin_not_connected marker for " << ref
161 << " on the first sheet instance" );
162 }
163
164 for( const wxString ref : { wxS( "R3" ), wxS( "R4" ), wxS( "R5" ), wxS( "R6" ) } )
165 {
166 BOOST_CHECK_MESSAGE( !markedRefs.count( ref ),
167 "Unexpected pin_not_connected marker for " << ref
168 << "; dedup should keep only the first instance" );
169 }
170
171 // Also exercise the text report writer end-to-end so the regression catches
172 // the case where the marker is on the right sheet but the printed reference
173 // is wrong (i.e. the symbol's REFERENCE field text was left on the last
174 // sheet's annotation by the helpers' UpdateAllScreenReferences loop).
175 std::shared_ptr<SHEETLIST_ERC_ITEMS_PROVIDER> markersProvider =
176 std::make_shared<SHEETLIST_ERC_ITEMS_PROVIDER>( sch.get() );
177 markersProvider->SetSeverities( RPT_SEVERITY_ERROR | RPT_SEVERITY_WARNING );
178
179 ERC_REPORT reportWriter( sch.get(), EDA_UNITS::MM, markersProvider );
180 wxString report = reportWriter.GetTextReport();
181
182 for( const wxString ref : { wxS( "R1" ), wxS( "R2" ) } )
183 {
184 BOOST_CHECK_MESSAGE( report.Contains( wxS( "Symbol " ) + ref + wxS( " " ) ),
185 "ERC text report missing pin_not_connected for "
186 << ref << "\n" << report );
187 }
188
189 for( const wxString ref : { wxS( "R3" ), wxS( "R4" ), wxS( "R5" ), wxS( "R6" ) } )
190 {
191 BOOST_CHECK_MESSAGE( !report.Contains( wxS( "Symbol " ) + ref + wxS( " " ) ),
192 "ERC text report unexpectedly mentions "
193 << ref << "\n" << report );
194 }
195 }
196 }
197}
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
static SCHEMATIC * LoadSchematic(const wxString &aFileName, bool aSetActive, bool aForceDefaultProject, PROJECT *aProject=nullptr, bool aCalculateConnectivity=true, REPORTER *aRootReporter=nullptr)
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
wxString GetTextReport()
Returns the ERC report in "text" (human readable) format in the C-locale.
Container for ERC settings.
std::map< int, SEVERITY > m_ERCSeverities
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
std::shared_ptr< RC_ITEM > GetRCItem() const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void GetSymbols(SCH_REFERENCE_LIST &aReferences, SYMBOL_FILTER aSymbolFilter, bool aForceIncludeOrphanSymbols=false) const
Add a SCH_REFERENCE object to aReferences for each symbol in the list of sheets.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Schematic symbol object.
Definition sch_symbol.h:73
RAII class that sets an value at construction and resets it to the original value at destruction.
@ ERCE_PIN_NOT_CONNECTED
Pin not connected and not no connect symbol.
@ ERCE_SIMULATION_MODEL
An error was found in the simulation model.
@ ERCE_LIB_SYMBOL_MISMATCH
Symbol doesn't match copy in library.
@ ERCE_LIB_SYMBOL_ISSUES
Symbol not found in active libraries.
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_IGNORE
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
@ SYMBOL_FILTER_NON_POWER
std::vector< FAB_LAYER_COLOR > dummy
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_CASE(Issue24409CliHierarchicalInstances)
KIBIS_PIN * pin
BOOST_TEST_CONTEXT("Test Clearance")
@ SCH_MARKER_T
Definition typeinfo.h:154