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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21
22#include <connection_graph.h>
23#include <advanced_config.h>
24#include <scoped_set_reset.h>
25#include <sch_symbol.h>
29#include <sch_netchain.h>
30#include <sch_screen.h>
31#include <sch_sheet.h>
32#include <schematic.h>
34#include <locale_io.h>
36
37#include <wx/ffile.h>
38#include <wx/filename.h>
39#include <wx/stdpaths.h>
40#include <wx/xml/xml.h>
41
42
44{
47 {
48 m_workDir.AssignDir( wxStandardPaths::Get().GetTempDir() );
49 m_workDir.AppendDir(
50 wxString::Format( wxT( "kicad_qa_netchain_synth_%lu" ),
51 static_cast<unsigned long>( wxGetProcessId() ) ) );
52
53 wxFileName::Mkdir( m_workDir.GetFullPath(), 0755, wxPATH_MKDIR_FULL );
54
55 wxString projectPath = m_workDir.GetFullPath() + wxT( "synth_filter.kicad_pro" );
56 m_tempFiles.push_back( projectPath );
57
58 m_settingsManager.LoadProject( projectPath.ToStdString() );
60 }
61
63 {
64 m_schematic.reset();
65
66 // Release the project lock while its file still exists
67 m_settingsManager.UnloadProject( m_project, false );
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
106// Keep native terminals and real nets while exercising transient member filtering.
107BOOST_FIXTURE_TEST_CASE( NetChainSyntheticNamesAreFilteredFromOutputs,
109{
111
112 const wxString chainName = wxT( "TEST_SYNTH_FILTER_CHAIN" );
113 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
114 SCOPED_SET_RESET restore( enabled, enabled );
115
116 for( bool useEngine : { false, true } )
117 {
118 BOOST_TEST_CONTEXT( "engine=" << useEngine )
119 {
120 enabled = useEngine;
121 KI_TEST::LoadSchematic( m_settingsManager, "net_chains_four_nets_labeled", m_schematic );
122 m_project = &m_schematic->Project();
123 auto& manager = m_schematic->NetChains();
124 BOOST_REQUIRE( !manager.GetPotentialNetChains().empty() );
125 BOOST_REQUIRE( !manager.GetPotentialNetChains().front()->GetSymbols().empty() );
126 SCH_SYMBOL* driver = *manager.GetPotentialNetChains().front()->GetSymbols().begin();
127
128 for( const SCH_SHEET_PATH& path : m_schematic->Hierarchy() )
129 {
130 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
131 item->SetExcludedFromBoard( item != driver, &path );
132 }
133
134 m_schematic->RebuildConnectivity();
135 BOOST_REQUIRE_EQUAL( manager.GetPotentialNetChains().size(), 1u );
136 auto* potential = manager.GetPotentialNetChains().front().get();
137 potential->AddNet( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) + wxString( "filter-test" ) );
138 std::vector<wxString> realNets;
139 std::vector<wxString> syntheticNets;
140
141 for( const wxString& net : potential->GetNets() )
142 {
143 if( net.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) )
144 syntheticNets.push_back( net );
145 else
146 realNets.push_back( net );
147 }
148
149 BOOST_REQUIRE_GE( realNets.size(), 2u );
150 BOOST_REQUIRE( !syntheticNets.empty() );
151 const wxString realNetA = realNets[0];
152 const wxString realNetB = realNets[1];
153 const wxString synthName = syntheticNets.front();
154 SCH_NETCHAIN* committed = manager.CreateNetChainFromPotential( potential, chainName );
155 BOOST_REQUIRE( committed );
156 BOOST_REQUIRE_EQUAL( committed->GetNets().count( synthName ), 1u );
157 SCH_SHEET* topSheet = m_schematic->GetTopLevelSheet();
158 SCH_SCREEN* topScreen = topSheet->GetScreen();
159 wxString rootFileName = PathInWorkDir( wxT( "synth_filter.kicad_sch" ) );
160 topSheet->SetFileName( wxT( "synth_filter.kicad_sch" ) );
161 topScreen->SetFileName( rootFileName );
162
163 // 1. XML netlist exporter (KiCad-internal flag emits <net_chains>).
164 wxFileName xmlFile( rootFileName );
165 xmlFile.SetName( xmlFile.GetName() + wxT( "_netlist" ) );
166 xmlFile.SetExt( wxT( "xml" ) );
167 m_tempFiles.push_back( xmlFile.GetFullPath() );
168
169 {
170 struct FILTER_EXPORTER : NETLIST_EXPORTER_XML
171 {
173 wxString transient;
174
175 bool writeNetlist( const wxString& aPath, unsigned aOptions, REPORTER& aReporter ) override
176 {
177 const auto& chains = m_schematic->NetChains().GetCommittedNetChains();
178
179 if( chains.size() != 1u )
180 return false;
181
182 // Export preparation rebuilds the chain before this serializer runs.
183 chains.front()->AddNet( transient );
184 return NETLIST_EXPORTER_XML::writeNetlist( aPath, aOptions, aReporter );
185 }
186 };
187
189 std::unique_ptr<FILTER_EXPORTER> exporter = std::make_unique<FILTER_EXPORTER>( m_schematic.get(),
190 nullptr );
191 exporter->transient = synthName;
192
193 BOOST_REQUIRE( exporter->WriteNetlist( xmlFile.GetFullPath(), GNL_OPT_KICAD, reporter ) );
194 BOOST_REQUIRE( reporter.GetMessages().IsEmpty() );
195 }
196
197 BOOST_REQUIRE( wxFileExists( xmlFile.GetFullPath() ) );
198
199 // Raw text scan catches the synthetic prefix anywhere in the document.
200 {
201 wxFFile rawXml( xmlFile.GetFullPath(), "rb" );
202 BOOST_REQUIRE( rawXml.IsOpened() );
203
204 wxString xmlText;
205 rawXml.ReadAll( &xmlText );
206 rawXml.Close();
207
208 BOOST_CHECK_MESSAGE( xmlText.Find( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) ) == wxNOT_FOUND,
209 "XML netlist must not contain synthetic __SG_* net names" );
210 }
211
212 // Structural check: real nets remain in the chain's <members>; synthetic name does not.
213 {
214 wxXmlDocument xdoc;
215 BOOST_REQUIRE( xdoc.Load( xmlFile.GetFullPath() ) );
216 BOOST_REQUIRE( xdoc.GetRoot() );
217
218 wxXmlNode* netChains = find_child( xdoc.GetRoot(), wxT( "net_chains" ) );
219 BOOST_REQUIRE( netChains );
220
221 wxXmlNode* targetChain = nullptr;
222
223 for( wxXmlNode* xchain = netChains->GetChildren(); xchain; xchain = xchain->GetNext() )
224 {
225 if( xchain->GetName() != wxT( "net_chain" ) )
226 continue;
227
228 if( xchain->GetAttribute( wxT( "name" ), wxEmptyString ) == chainName )
229 {
230 targetChain = xchain;
231 break;
232 }
233 }
234
235 BOOST_REQUIRE_MESSAGE( targetChain, "Committed chain missing from XML output" );
236
237 wxXmlNode* members = find_child( targetChain, wxT( "members" ) );
238 BOOST_REQUIRE( members );
239
240 std::set<wxString> emittedNets;
241
242 for( wxXmlNode* xmem = members->GetChildren(); xmem; xmem = xmem->GetNext() )
243 {
244 if( xmem->GetName() != wxT( "member" ) )
245 continue;
246
247 emittedNets.insert( xmem->GetAttribute( wxT( "net" ), wxEmptyString ) );
248 }
249
250 BOOST_CHECK( emittedNets.count( realNetA ) == 1u );
251 BOOST_CHECK( emittedNets.count( realNetB ) == 1u );
252 BOOST_CHECK_MESSAGE( emittedNets.count( synthName ) == 0u,
253 "Synthetic net leaked into XML <member> list" );
254 }
255
256 // 2. sexpr writer must already filter synthetic names; reloading the file must
257 // yield a chain that resolves with the real members intact.
258 {
259 SCH_IO_KICAD_SEXPR saver;
260 BOOST_REQUIRE_NO_THROW( saver.SaveSchematicFile( rootFileName, topSheet, m_schematic.get() ) );
261 BOOST_REQUIRE( wxFileExists( rootFileName ) );
262
263 wxFFile rawSexpr( rootFileName, "rb" );
264 BOOST_REQUIRE( rawSexpr.IsOpened() );
265
266 wxString sexprText;
267 rawSexpr.ReadAll( &sexprText );
268 rawSexpr.Close();
269
270 BOOST_CHECK_MESSAGE( sexprText.Find( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) ) == wxNOT_FOUND,
271 "kicad_sch must not contain synthetic __SG_* net names" );
272
273 // Guard against the early-skip path in sch_io_kicad_sexpr.cpp: if terminal refs were
274 // missing the writer would emit no net_chain section and the synthetic-prefix check
275 // above would pass vacuously.
276 BOOST_CHECK_MESSAGE( sexprText.Find( wxT( "(net_chain" ) ) != wxNOT_FOUND,
277 "kicad_sch must contain the committed net_chain section" );
278 BOOST_CHECK_MESSAGE( sexprText.Find( chainName ) != wxNOT_FOUND,
279 "kicad_sch must reference the committed chain by name" );
280 BOOST_CHECK_MESSAGE( sexprText.Find( realNetA ) != wxNOT_FOUND,
281 "kicad_sch must retain real net A in the chain's nets list" );
282 BOOST_CHECK_MESSAGE( sexprText.Find( realNetB ) != wxNOT_FOUND,
283 "kicad_sch must retain real net B in the chain's nets list" );
284 }
285
286 // Check the persisted member list independently of connectivity reconstruction.
287 {
288 SCH_IO_KICAD_SEXPR loader;
289 SCHEMATIC reloaded( nullptr );
290
291 reloaded.SetProject( m_project );
292
293 SCH_SHEET* loadedRoot = nullptr;
294 BOOST_REQUIRE_NO_THROW(
295 loadedRoot = loader.LoadSchematicFile( rootFileName, &reloaded ) );
296 BOOST_REQUIRE( loadedRoot );
297
298 const auto& overrides = reloaded.ConnectionGraph()->GetNetChainMemberNetOverrides();
299 auto it = overrides.find( chainName );
300 BOOST_REQUIRE_MESSAGE( it != overrides.end(),
301 "Reloaded schematic missing chain member-net override" );
302
303 const std::set<wxString>& reloadedNets = it->second;
304 BOOST_CHECK( !reloadedNets.empty() );
305 BOOST_CHECK( reloadedNets.count( realNetA ) == 1u );
306 BOOST_CHECK( reloadedNets.count( realNetB ) == 1u );
307
308 for( const wxString& n : reloadedNets )
309 {
310 BOOST_CHECK_MESSAGE( !n.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ),
311 "Reloaded chain leaked a synthetic __SG_* member" );
312 }
313 }
314 }
315 }
316}
const char * name
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
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:37
Generate a generic XML based netlist file.
NETLIST_EXPORTER_XML(SCHEMATIC *aSchematic, KIWAY *aKiway)
bool writeNetlist(const wxString &aOutFileName, unsigned aNetlistOptions, REPORTER &aReporter) override
Write generic netlist to aOutFileName.
Container for project specific data.
Definition project.h:63
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Holds all the data relating to one schematic.
Definition schematic.h:149
void SetProject(PROJECT *aPrj)
CONNECTION_GRAPH * ConnectionGraph() const
Definition schematic.h:319
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,...
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
A net chain is a collection of nets that are connected together through passive components.
const std::set< wxString > & GetNets() const
static constexpr wxStringCharType 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.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
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:392
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
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.
A wrapper for reporting to a wxString object.
Definition reporter.h:242
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
@ GNL_OPT_KICAD
std::vector< FAB_LAYER_COLOR > dummy
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
std::string path
IbisParser parser & reporter
BOOST_FIXTURE_TEST_CASE(NetChainSyntheticNamesAreFilteredFromOutputs, NETCHAIN_SYNTHETIC_FILTER_FIXTURE)
static wxXmlNode * find_child(wxXmlNode *parent, const wxString &name)
BOOST_TEST_CONTEXT("Test Clearance")
@ SCH_SYMBOL_T
Definition typeinfo.h:168