KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_net_chain_synthetic_filter.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
23#include <connection_graph.h>
26#include <sch_netchain.h>
27#include <sch_screen.h>
28#include <sch_sheet.h>
29#include <schematic.h>
31#include <locale_io.h>
33
34#include <wx/ffile.h>
35#include <wx/filename.h>
36#include <wx/stdpaths.h>
37#include <wx/xml/xml.h>
38
39
40// Test backdoor declared in connection_graph.h. Pushes a fully-formed committed chain
41// into the graph so SaveSchematicFile / NETLIST_EXPORTER_XML have something to serialize.
43 std::unique_ptr<SCH_NETCHAIN> aChain );
44
45
47{
50 {
51 m_workDir.AssignDir( wxStandardPaths::Get().GetTempDir() );
52 m_workDir.AppendDir(
53 wxString::Format( wxT( "kicad_qa_netchain_synth_%lu" ),
54 static_cast<unsigned long>( wxGetProcessId() ) ) );
55
56 wxFileName::Mkdir( m_workDir.GetFullPath(), 0755, wxPATH_MKDIR_FULL );
57
58 wxString projectPath = m_workDir.GetFullPath() + wxT( "synth_filter.kicad_pro" );
59 m_tempFiles.push_back( projectPath );
60
61 m_settingsManager.LoadProject( projectPath.ToStdString() );
63 }
64
66 {
67 m_schematic.reset();
68
69 for( const wxString& file : m_tempFiles )
70 {
71 if( wxFileExists( file ) )
72 wxRemoveFile( file );
73 }
74
75 if( m_workDir.DirExists() )
76 wxFileName::Rmdir( m_workDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
77 }
78
79 wxString PathInWorkDir( const wxString& aLeaf )
80 {
81 wxString full = m_workDir.GetFullPath() + aLeaf;
82 m_tempFiles.push_back( full );
83 return full;
84 }
85
87 PROJECT* m_project = nullptr;
88 std::unique_ptr<SCHEMATIC> m_schematic;
89 wxFileName m_workDir;
90 std::vector<wxString> m_tempFiles;
91};
92
93
94static wxXmlNode* find_child( wxXmlNode* parent, const wxString& name )
95{
96 for( wxXmlNode* child = parent->GetChildren(); child; child = child->GetNext() )
97 {
98 if( child->GetName() == name )
99 return child;
100 }
101
102 return nullptr;
103}
104
105
119BOOST_FIXTURE_TEST_CASE( NetChainSyntheticNamesAreFilteredFromOutputs,
121{
123
124 const wxString chainName = wxT( "TEST_SYNTH_FILTER_CHAIN" );
125 const wxString realNetA = wxT( "/SIG_A" );
126 const wxString realNetB = wxT( "/SIG_B" );
127 const wxString synthName = wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX )
128 + wxT( "0xdeadbeef" );
129
130 m_schematic = std::make_unique<SCHEMATIC>( nullptr );
131 m_schematic->SetProject( m_project );
132 m_schematic->CreateDefaultScreens();
133
134 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
135 BOOST_REQUIRE( !topSheets.empty() );
136
137 SCH_SHEET* topSheet = topSheets[0];
138 SCH_SCREEN* topScreen = topSheet->GetScreen();
139 BOOST_REQUIRE( topScreen );
140
141 wxString rootFileName = PathInWorkDir( wxT( "synth_filter.kicad_sch" ) );
142 topSheet->SetFileName( wxT( "synth_filter.kicad_sch" ) );
143 topScreen->SetFileName( rootFileName );
144
145 m_schematic->RefreshHierarchy();
146
147 auto chain = std::make_unique<SCH_NETCHAIN>();
148 chain->SetName( chainName );
149 chain->AddNet( realNetA );
150 chain->AddNet( realNetB );
151 chain->AddNet( synthName );
152
153 // Terminal refs are required by the sexpr writer; without them the chain is skipped
154 // before the synthetic-net filter loop runs and this test would not exercise the filter.
155 chain->SetTerminalRefs( wxT( "U1" ), wxT( "1" ), wxT( "U2" ), wxT( "2" ) );
156
157 boost_test_inject_committed_net_chain( *m_schematic->ConnectionGraph(), std::move( chain ) );
158
159 BOOST_REQUIRE_EQUAL( m_schematic->ConnectionGraph()->GetCommittedNetChains().size(), 1u );
160
161 // 1. XML netlist exporter (KiCad-internal flag emits <net_chains>).
162 wxFileName xmlFile( rootFileName );
163 xmlFile.SetName( xmlFile.GetName() + wxT( "_netlist" ) );
164 xmlFile.SetExt( wxT( "xml" ) );
165 m_tempFiles.push_back( xmlFile.GetFullPath() );
166
167 {
168 WX_STRING_REPORTER reporter;
169 std::unique_ptr<NETLIST_EXPORTER_XML> exporter =
170 std::make_unique<NETLIST_EXPORTER_XML>( m_schematic.get() );
171
172 BOOST_REQUIRE( exporter->WriteNetlist( xmlFile.GetFullPath(), GNL_OPT_KICAD,
173 reporter ) );
174 BOOST_REQUIRE( reporter.GetMessages().IsEmpty() );
175 }
176
177 BOOST_REQUIRE( wxFileExists( xmlFile.GetFullPath() ) );
178
179 // Raw text scan catches the synthetic prefix anywhere in the document.
180 {
181 wxFFile rawXml( xmlFile.GetFullPath(), "rb" );
182 BOOST_REQUIRE( rawXml.IsOpened() );
183
184 wxString xmlText;
185 rawXml.ReadAll( &xmlText );
186 rawXml.Close();
187
189 xmlText.Find( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) ) == wxNOT_FOUND,
190 "XML netlist must not contain synthetic __SG_* net names" );
191 }
192
193 // Structural check: real nets remain in the chain's <members>; synthetic name does not.
194 {
195 wxXmlDocument xdoc;
196 BOOST_REQUIRE( xdoc.Load( xmlFile.GetFullPath() ) );
197 BOOST_REQUIRE( xdoc.GetRoot() );
198
199 wxXmlNode* netChains = find_child( xdoc.GetRoot(), wxT( "net_chains" ) );
200 BOOST_REQUIRE( netChains );
201
202 wxXmlNode* targetChain = nullptr;
203
204 for( wxXmlNode* xchain = netChains->GetChildren(); xchain; xchain = xchain->GetNext() )
205 {
206 if( xchain->GetName() != wxT( "net_chain" ) )
207 continue;
208
209 if( xchain->GetAttribute( wxT( "name" ), wxEmptyString ) == chainName )
210 {
211 targetChain = xchain;
212 break;
213 }
214 }
215
216 BOOST_REQUIRE_MESSAGE( targetChain, "Committed chain missing from XML output" );
217
218 wxXmlNode* members = find_child( targetChain, wxT( "members" ) );
219 BOOST_REQUIRE( members );
220
221 std::set<wxString> emittedNets;
222
223 for( wxXmlNode* xmem = members->GetChildren(); xmem; xmem = xmem->GetNext() )
224 {
225 if( xmem->GetName() != wxT( "member" ) )
226 continue;
227
228 emittedNets.insert( xmem->GetAttribute( wxT( "net" ), wxEmptyString ) );
229 }
230
231 BOOST_CHECK( emittedNets.count( realNetA ) == 1u );
232 BOOST_CHECK( emittedNets.count( realNetB ) == 1u );
233 BOOST_CHECK_MESSAGE( emittedNets.count( synthName ) == 0u,
234 "Synthetic net leaked into XML <member> list" );
235 }
236
237 // 2. sexpr writer must already filter synthetic names; reloading the file must
238 // yield a chain that resolves with the real members intact.
239 {
240 SCH_IO_KICAD_SEXPR saver;
241 BOOST_REQUIRE_NO_THROW( saver.SaveSchematicFile( rootFileName, topSheet,
242 m_schematic.get() ) );
243 BOOST_REQUIRE( wxFileExists( rootFileName ) );
244
245 wxFFile rawSexpr( rootFileName, "rb" );
246 BOOST_REQUIRE( rawSexpr.IsOpened() );
247
248 wxString sexprText;
249 rawSexpr.ReadAll( &sexprText );
250 rawSexpr.Close();
251
253 sexprText.Find( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) ) == wxNOT_FOUND,
254 "kicad_sch must not contain synthetic __SG_* net names" );
255
256 // Guard against the early-skip path in sch_io_kicad_sexpr.cpp: if terminal refs were
257 // missing the writer would emit no net_chain section and the synthetic-prefix check
258 // above would pass vacuously.
259 BOOST_CHECK_MESSAGE( sexprText.Find( wxT( "(net_chain" ) ) != wxNOT_FOUND,
260 "kicad_sch must contain the committed net_chain section" );
261 BOOST_CHECK_MESSAGE( sexprText.Find( chainName ) != wxNOT_FOUND,
262 "kicad_sch must reference the committed chain by name" );
263 BOOST_CHECK_MESSAGE( sexprText.Find( realNetA ) != wxNOT_FOUND,
264 "kicad_sch must retain real net A in the chain's nets list" );
265 BOOST_CHECK_MESSAGE( sexprText.Find( realNetB ) != wxNOT_FOUND,
266 "kicad_sch must retain real net B in the chain's nets list" );
267 }
268
269 // 3. Reload the saved file. RebuildNetChains needs real schematic items to repopulate
270 // GetCommittedNetChains(), so instead we verify the parser-side member-net overrides
271 // that the IO layer hands to the connection graph during load.
272 {
273 SCH_IO_KICAD_SEXPR loader;
274 SCHEMATIC reloaded( nullptr );
275
276 reloaded.SetProject( m_project );
277
278 SCH_SHEET* loadedRoot = nullptr;
279 BOOST_REQUIRE_NO_THROW(
280 loadedRoot = loader.LoadSchematicFile( rootFileName, &reloaded ) );
281 BOOST_REQUIRE( loadedRoot );
282
283 const auto& overrides = reloaded.ConnectionGraph()->GetNetChainMemberNetOverrides();
284 auto it = overrides.find( chainName );
285 BOOST_REQUIRE_MESSAGE( it != overrides.end(),
286 "Reloaded schematic missing chain member-net override" );
287
288 const std::set<wxString>& reloadedNets = it->second;
289 BOOST_CHECK( !reloadedNets.empty() );
290 BOOST_CHECK( reloadedNets.count( realNetA ) == 1u );
291 BOOST_CHECK( reloadedNets.count( realNetB ) == 1u );
292
293 for( const wxString& n : reloadedNets )
294 {
296 !n.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ),
297 "Reloaded chain leaked a synthetic __SG_* member" );
298 }
299 }
300}
const char * name
Calculate the connectivity of a schematic and generates netlists.
const std::map< wxString, std::set< wxString > > & GetNetChainMemberNetOverrides() const
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:41
Container for project specific data.
Definition project.h:66
Holds all the data relating to one schematic.
Definition schematic.h:89
void SetProject(PROJECT *aPrj)
CONNECTION_GRAPH * ConnectionGraph() const
Definition schematic.h:200
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void SaveSchematicFile(const wxString &aFileName, SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aSchematic to a storage file in a format that this SCH_IO implementation knows about,...
SCH_SHEET * LoadSchematicFile(const wxString &aFileName, SCHEMATIC *aSchematic, SCH_SHEET *aAppendToMe=nullptr, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load information from some input file format that this SCH_IO implementation knows about,...
static constexpr char SYNTHETIC_NET_PREFIX[]
Prefix used when synthesising net names for unnamed subgraphs.
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:380
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:143
A wrapper for reporting to a wxString object.
Definition reporter.h:193
const wxString & GetMessages() const
Definition reporter.cpp:105
@ GNL_OPT_KICAD
std::vector< FAB_LAYER_COLOR > dummy
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(NetChainSyntheticNamesAreFilteredFromOutputs, NETCHAIN_SYNTHETIC_FILTER_FIXTURE)
Regression: synthetic per-run subgraph names (__SG_*) embed subgraph codes that are not stable across...
static wxXmlNode * find_child(wxXmlNode *parent, const wxString &name)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
const SHAPE_LINE_CHAIN chain