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 <sch_symbol.h>
27#include <sch_netchain.h>
28#include <sch_screen.h>
29#include <sch_sheet.h>
30#include <schematic.h>
32#include <locale_io.h>
34
35#include <wx/ffile.h>
36#include <wx/filename.h>
37#include <wx/stdpaths.h>
38#include <wx/xml/xml.h>
39
40
42{
45 {
46 m_workDir.AssignDir( wxStandardPaths::Get().GetTempDir() );
47 m_workDir.AppendDir(
48 wxString::Format( wxT( "kicad_qa_netchain_synth_%lu" ),
49 static_cast<unsigned long>( wxGetProcessId() ) ) );
50
51 wxFileName::Mkdir( m_workDir.GetFullPath(), 0755, wxPATH_MKDIR_FULL );
52
53 wxString projectPath = m_workDir.GetFullPath() + wxT( "synth_filter.kicad_pro" );
54 m_tempFiles.push_back( projectPath );
55
56 m_settingsManager.LoadProject( projectPath.ToStdString() );
58 }
59
61 {
62 m_schematic.reset();
63
64 // Release the project lock while its file still exists
65 m_settingsManager.UnloadProject( m_project, false );
66
67 for( const wxString& file : m_tempFiles )
68 {
69 if( wxFileExists( file ) )
70 wxRemoveFile( file );
71 }
72
73 if( m_workDir.DirExists() )
74 wxFileName::Rmdir( m_workDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
75 }
76
77 wxString PathInWorkDir( const wxString& aLeaf )
78 {
79 wxString full = m_workDir.GetFullPath() + aLeaf;
80 m_tempFiles.push_back( full );
81 return full;
82 }
83
85 PROJECT* m_project = nullptr;
86 std::unique_ptr<SCHEMATIC> m_schematic;
87 wxFileName m_workDir;
88 std::vector<wxString> m_tempFiles;
89};
90
91
92static wxXmlNode* find_child( wxXmlNode* parent, const wxString& name )
93{
94 for( wxXmlNode* child = parent->GetChildren(); child; child = child->GetNext() )
95 {
96 if( child->GetName() == name )
97 return child;
98 }
99
100 return nullptr;
101}
102
103
104// Keep native terminals and real nets while exercising transient member filtering.
105BOOST_FIXTURE_TEST_CASE( NetChainSyntheticNamesAreFilteredFromOutputs,
107{
109
110 const wxString chainName = wxT( "TEST_SYNTH_FILTER_CHAIN" );
111 KI_TEST::LoadSchematic( m_settingsManager, "net_chains_four_nets_labeled", m_schematic );
112 m_project = &m_schematic->Project();
113 auto& manager = m_schematic->NetChains();
114 BOOST_REQUIRE( !manager.GetPotentialNetChains().empty() );
115 BOOST_REQUIRE( !manager.GetPotentialNetChains().front()->GetSymbols().empty() );
116 SCH_SYMBOL* driver = *manager.GetPotentialNetChains().front()->GetSymbols().begin();
117
118 for( const SCH_SHEET_PATH& path : m_schematic->Hierarchy() )
119 {
120 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
121 item->SetExcludedFromBoard( item != driver, &path );
122 }
123
124 m_schematic->RebuildConnectivity();
125 BOOST_REQUIRE_EQUAL( manager.GetPotentialNetChains().size(), 1u );
126 auto* potential = manager.GetPotentialNetChains().front().get();
127 potential->AddNet( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) + wxString( "filter-test" ) );
128 std::vector<wxString> realNets;
129 std::vector<wxString> syntheticNets;
130
131 for( const wxString& net : potential->GetNets() )
132 {
133 if( net.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) )
134 syntheticNets.push_back( net );
135 else
136 realNets.push_back( net );
137 }
138
139 BOOST_REQUIRE_GE( realNets.size(), 2u );
140 BOOST_REQUIRE( !syntheticNets.empty() );
141 const wxString realNetA = realNets[0];
142 const wxString realNetB = realNets[1];
143 const wxString synthName = syntheticNets.front();
144 SCH_NETCHAIN* committed = manager.CreateNetChainFromPotential( potential, chainName );
145 BOOST_REQUIRE( committed );
146 BOOST_REQUIRE_EQUAL( committed->GetNets().count( synthName ), 1u );
147 SCH_SHEET* topSheet = m_schematic->GetTopLevelSheet();
148 SCH_SCREEN* topScreen = topSheet->GetScreen();
149 wxString rootFileName = PathInWorkDir( wxT( "synth_filter.kicad_sch" ) );
150 topSheet->SetFileName( wxT( "synth_filter.kicad_sch" ) );
151 topScreen->SetFileName( rootFileName );
152
153 // 1. XML netlist exporter (KiCad-internal flag emits <net_chains>).
154 wxFileName xmlFile( rootFileName );
155 xmlFile.SetName( xmlFile.GetName() + wxT( "_netlist" ) );
156 xmlFile.SetExt( wxT( "xml" ) );
157 m_tempFiles.push_back( xmlFile.GetFullPath() );
158
159 {
160 struct FILTER_EXPORTER : NETLIST_EXPORTER_XML
161 {
163 wxString transient;
164
165 bool writeNetlist( const wxString& aPath, unsigned aOptions, REPORTER& aReporter ) override
166 {
167 const auto& chains = m_schematic->NetChains().GetCommittedNetChains();
168
169 if( chains.size() != 1u )
170 return false;
171
172 // Export preparation rebuilds the chain before this serializer runs.
173 chains.front()->AddNet( transient );
174 return NETLIST_EXPORTER_XML::writeNetlist( aPath, aOptions, aReporter );
175 }
176 };
177
179 std::unique_ptr<FILTER_EXPORTER> exporter =
180 std::make_unique<FILTER_EXPORTER>( m_schematic.get() );
181 exporter->transient = synthName;
182
183 BOOST_REQUIRE( exporter->WriteNetlist( xmlFile.GetFullPath(), GNL_OPT_KICAD,
184 reporter ) );
185 BOOST_REQUIRE( reporter.GetMessages().IsEmpty() );
186 }
187
188 BOOST_REQUIRE( wxFileExists( xmlFile.GetFullPath() ) );
189
190 // Raw text scan catches the synthetic prefix anywhere in the document.
191 {
192 wxFFile rawXml( xmlFile.GetFullPath(), "rb" );
193 BOOST_REQUIRE( rawXml.IsOpened() );
194
195 wxString xmlText;
196 rawXml.ReadAll( &xmlText );
197 rawXml.Close();
198
199 BOOST_CHECK_MESSAGE(
200 xmlText.Find( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) ) == wxNOT_FOUND,
201 "XML netlist must not contain synthetic __SG_* net names" );
202 }
203
204 // Structural check: real nets remain in the chain's <members>; synthetic name does not.
205 {
206 wxXmlDocument xdoc;
207 BOOST_REQUIRE( xdoc.Load( xmlFile.GetFullPath() ) );
208 BOOST_REQUIRE( xdoc.GetRoot() );
209
210 wxXmlNode* netChains = find_child( xdoc.GetRoot(), wxT( "net_chains" ) );
211 BOOST_REQUIRE( netChains );
212
213 wxXmlNode* targetChain = nullptr;
214
215 for( wxXmlNode* xchain = netChains->GetChildren(); xchain; xchain = xchain->GetNext() )
216 {
217 if( xchain->GetName() != wxT( "net_chain" ) )
218 continue;
219
220 if( xchain->GetAttribute( wxT( "name" ), wxEmptyString ) == chainName )
221 {
222 targetChain = xchain;
223 break;
224 }
225 }
226
227 BOOST_REQUIRE_MESSAGE( targetChain, "Committed chain missing from XML output" );
228
229 wxXmlNode* members = find_child( targetChain, wxT( "members" ) );
230 BOOST_REQUIRE( members );
231
232 std::set<wxString> emittedNets;
233
234 for( wxXmlNode* xmem = members->GetChildren(); xmem; xmem = xmem->GetNext() )
235 {
236 if( xmem->GetName() != wxT( "member" ) )
237 continue;
238
239 emittedNets.insert( xmem->GetAttribute( wxT( "net" ), wxEmptyString ) );
240 }
241
242 BOOST_CHECK( emittedNets.count( realNetA ) == 1u );
243 BOOST_CHECK( emittedNets.count( realNetB ) == 1u );
244 BOOST_CHECK_MESSAGE( emittedNets.count( synthName ) == 0u,
245 "Synthetic net leaked into XML <member> list" );
246 }
247
248 // 2. sexpr writer must already filter synthetic names; reloading the file must
249 // yield a chain that resolves with the real members intact.
250 {
251 SCH_IO_KICAD_SEXPR saver;
252 BOOST_REQUIRE_NO_THROW( saver.SaveSchematicFile( rootFileName, topSheet,
253 m_schematic.get() ) );
254 BOOST_REQUIRE( wxFileExists( rootFileName ) );
255
256 wxFFile rawSexpr( rootFileName, "rb" );
257 BOOST_REQUIRE( rawSexpr.IsOpened() );
258
259 wxString sexprText;
260 rawSexpr.ReadAll( &sexprText );
261 rawSexpr.Close();
262
263 BOOST_CHECK_MESSAGE(
264 sexprText.Find( wxString( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ) ) == wxNOT_FOUND,
265 "kicad_sch must not contain synthetic __SG_* net names" );
266
267 // Guard against the early-skip path in sch_io_kicad_sexpr.cpp: if terminal refs were
268 // missing the writer would emit no net_chain section and the synthetic-prefix check
269 // above would pass vacuously.
270 BOOST_CHECK_MESSAGE( sexprText.Find( wxT( "(net_chain" ) ) != wxNOT_FOUND,
271 "kicad_sch must contain the committed net_chain section" );
272 BOOST_CHECK_MESSAGE( sexprText.Find( chainName ) != wxNOT_FOUND,
273 "kicad_sch must reference the committed chain by name" );
274 BOOST_CHECK_MESSAGE( sexprText.Find( realNetA ) != wxNOT_FOUND,
275 "kicad_sch must retain real net A in the chain's nets list" );
276 BOOST_CHECK_MESSAGE( sexprText.Find( realNetB ) != wxNOT_FOUND,
277 "kicad_sch must retain real net B in the chain's nets list" );
278 }
279
280 // Check the persisted member list independently of connectivity reconstruction.
281 {
282 SCH_IO_KICAD_SEXPR loader;
283 SCHEMATIC reloaded( nullptr );
284
285 reloaded.SetProject( m_project );
286
287 SCH_SHEET* loadedRoot = nullptr;
288 BOOST_REQUIRE_NO_THROW(
289 loadedRoot = loader.LoadSchematicFile( rootFileName, &reloaded ) );
290 BOOST_REQUIRE( loadedRoot );
291
292 const auto& overrides = reloaded.ConnectionGraph()->GetNetChainMemberNetOverrides();
293 auto it = overrides.find( chainName );
294 BOOST_REQUIRE_MESSAGE( it != overrides.end(),
295 "Reloaded schematic missing chain member-net override" );
296
297 const std::set<wxString>& reloadedNets = it->second;
298 BOOST_CHECK( !reloadedNets.empty() );
299 BOOST_CHECK( reloadedNets.count( realNetA ) == 1u );
300 BOOST_CHECK( reloadedNets.count( realNetB ) == 1u );
301
302 for( const wxString& n : reloadedNets )
303 {
304 BOOST_CHECK_MESSAGE(
305 !n.StartsWith( SCH_NETCHAIN::SYNTHETIC_NET_PREFIX ),
306 "Reloaded chain leaked a synthetic __SG_* member" );
307 }
308 }
309}
const char * name
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)
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:148
void SetProject(PROJECT *aPrj)
CONNECTION_GRAPH * ConnectionGraph() const
Definition schematic.h:317
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:165
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:390
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
Schematic symbol object.
Definition sch_symbol.h:75
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)
@ SCH_SYMBOL_T
Definition typeinfo.h:168