KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_save_load_schematic.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
28
29#include <boost/test/unit_test.hpp>
30#include <eeschema_test_utils.h>
31
33#include <sch_screen.h>
34#include <sch_sheet.h>
35#include <sch_symbol.h>
36#include <schematic.h>
37#include <kiid.h>
38#include <sch_file_versions.h>
40
41#include <wx/filename.h>
42#include <wx/stdpaths.h>
43
44
46{
49 {
50 // Create a temporary project file so we have a valid project name
51 wxString tempDir = wxStandardPaths::Get().GetTempDir();
52 wxString projectPath = tempDir + wxFileName::GetPathSeparator() + wxT("test_project.kicad_pro");
53 m_tempFiles.push_back( projectPath );
54
55 m_settingsManager.LoadProject( projectPath.ToStdString() );
56 m_schematic = std::make_unique<SCHEMATIC>( nullptr );
58 m_schematic->SetProject( m_project );
59 }
60
62 {
63 // Clean up temp files
64 for( const wxString& file : m_tempFiles )
65 {
66 if( wxFileExists( file ) )
67 wxRemoveFile( file );
68 }
69
70 m_schematic.reset();
71 }
72
73 wxString GetTempFileName( const wxString& aPrefix )
74 {
75 wxString tempDir = wxStandardPaths::Get().GetTempDir();
76 wxString fileName = wxFileName::CreateTempFileName( tempDir + wxFileName::GetPathSeparator() + aPrefix );
77 m_tempFiles.push_back( fileName );
78 return fileName;
79 }
80
82 std::unique_ptr<SCHEMATIC> m_schematic;
84 std::vector<wxString> m_tempFiles;
85};
86
87
88BOOST_FIXTURE_TEST_SUITE( SaveLoadSchematic, SAVE_LOAD_FIXTURE )
89
90
91
95BOOST_AUTO_TEST_CASE( TestSaveLoadSimpleSchematic )
96{
97 // Create a simple schematic
98 m_schematic->CreateDefaultScreens();
99
100 // Get the first (and only) top-level sheet
101 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
102 BOOST_REQUIRE( !topSheets.empty() );
103
104 SCH_SCREEN* screen = topSheets[0]->GetScreen();
105 BOOST_REQUIRE( screen != nullptr );
106
107 // Set some basic properties
108 screen->SetFileName( "test.kicad_sch" );
109 topSheets[0]->SetName( "Main Sheet" );
110
111 // Verify that currentSheet is set to a top-level sheet, not virtual root
112 SCH_SHEET_PATH& currentSheet = m_schematic->CurrentSheet();
113 BOOST_CHECK( !currentSheet.empty() );
114 if( !currentSheet.empty() )
115 {
116 BOOST_CHECK( currentSheet.Last() != &m_schematic->Root() );
117 BOOST_CHECK( currentSheet.Last()->m_Uuid != niluuid );
118 }
119
120 // Save the schematic
121 wxString fileName = GetTempFileName( "test_schematic" );
122 fileName += ".kicad_sch";
123 m_tempFiles.push_back( fileName );
124
126 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( fileName, topSheets[0], m_schematic.get() ) );
127
128 // Verify file was created
129 BOOST_CHECK( wxFileExists( fileName ) );
130
131 // Now try to load it back
132 m_schematic->Reset();
133 SCH_SHEET* loadedSheet = nullptr;
134 BOOST_CHECK_NO_THROW( loadedSheet = io.LoadSchematicFile( fileName, m_schematic.get() ) );
135 BOOST_CHECK( loadedSheet != nullptr );
136
137 if( loadedSheet )
138 {
139 // Set it as root (which should create virtual root and make it a top-level sheet)
140 m_schematic->SetRoot( loadedSheet );
141
142 // Verify we can access the schematic
143 BOOST_CHECK( m_schematic->IsValid() );
144
145 // Verify current sheet is NOT the virtual root
146 SCH_SHEET_PATH& currentSheet2 = m_schematic->CurrentSheet();
147 BOOST_CHECK( !currentSheet2.empty() );
148 if( !currentSheet2.empty() )
149 {
150 BOOST_CHECK( currentSheet2.Last() != &m_schematic->Root() );
151 BOOST_CHECK( currentSheet2.Last()->m_Uuid != niluuid );
152 }
153
154 // Try to save again (this is where the bug occurs)
155 wxString fileName2 = GetTempFileName( "test_schematic_resave" );
156 fileName2 += ".kicad_sch";
157 m_tempFiles.push_back( fileName2 );
158
159 std::vector<SCH_SHEET*> topSheets2 = m_schematic->GetTopLevelSheets();
160 BOOST_REQUIRE( !topSheets2.empty() );
161
162 // This should not assert - this is the key test for the bug fix
163 // Symbol instances should be preserved, not dropped
164 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( fileName2, topSheets2[0], m_schematic.get() ) );
165
166 // Verify the second file was created
167 BOOST_CHECK( wxFileExists( fileName2 ) );
168 }
169}
170
171
175BOOST_AUTO_TEST_CASE( TestSaveLoadHierarchicalSchematic )
176{
177 // Create a schematic with hierarchy
178 m_schematic->CreateDefaultScreens();
179
180 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
181 BOOST_REQUIRE( !topSheets.empty() );
182
183 SCH_SCREEN* screen = topSheets[0]->GetScreen();
184 BOOST_REQUIRE( screen != nullptr );
185
186 // Create a sub-sheet
187 SCH_SHEET* subSheet = new SCH_SHEET( m_schematic.get() );
188 subSheet->SetName( "SubSheet" );
189 SCH_SCREEN* subScreen = new SCH_SCREEN( m_schematic.get() );
190 subSheet->SetScreen( subScreen );
191 subSheet->SetFileName( "subsheet.kicad_sch" );
192 subScreen->SetFileName( "subsheet.kicad_sch" );
193 screen->Append( subSheet );
194
195 // Save the main sheet
196 wxString mainFileName = GetTempFileName( "test_main" );
197 mainFileName += ".kicad_sch";
198 m_tempFiles.push_back( mainFileName );
199
200 // Save the sub-sheet
201 wxString subFileName = GetTempFileName( "test_sub" );
202 subFileName += ".kicad_sch";
203 m_tempFiles.push_back( subFileName );
204
206 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( mainFileName, topSheets[0], m_schematic.get() ) );
207 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( subFileName, subSheet, m_schematic.get() ) );
208
209 // Load it back
210 m_schematic->Reset();
211 SCH_SHEET* loadedSheet = nullptr;
212 BOOST_CHECK_NO_THROW( loadedSheet = io.LoadSchematicFile( mainFileName, m_schematic.get() ) );
213 BOOST_CHECK( loadedSheet != nullptr );
214
215 if( loadedSheet )
216 {
217 m_schematic->SetRoot( loadedSheet );
218
219 // Build hierarchy
220 m_schematic->RefreshHierarchy();
221
222 // Verify hierarchy has 2 sheets (main + sub)
223 SCH_SHEET_LIST hierarchy = m_schematic->Hierarchy();
224 BOOST_CHECK_EQUAL( hierarchy.size(), 2 );
225
226 // Try to save again
227 wxString mainFileName2 = GetTempFileName( "test_main_resave" );
228 mainFileName2 += ".kicad_sch";
229 m_tempFiles.push_back( mainFileName2 );
230
231 std::vector<SCH_SHEET*> topSheets2 = m_schematic->GetTopLevelSheets();
232 BOOST_REQUIRE( !topSheets2.empty() );
233
234 // This should not assert - this is the key test for the bug fix
235 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( mainFileName2, topSheets2[0], m_schematic.get() ) );
236
237 // Verify the file was created
238 BOOST_CHECK( wxFileExists( mainFileName2 ) );
239 }
240}
241
242
248BOOST_AUTO_TEST_CASE( TestLoadLegacyHierarchicalSchematic )
249{
250 // Load the legacy hierarchical schematic
251 wxFileName fn( KI_TEST::GetEeschemaTestDataDir() );
252 fn.AppendDir( "legacy_hierarchy" );
253 fn.SetName( "legacy_hierarchy" );
255 wxString mainFile = fn.GetFullPath();
256
257 BOOST_TEST_MESSAGE( "Loading schematic: " + mainFile.ToStdString() );
258 BOOST_REQUIRE( wxFileExists( mainFile ) );
259
261 SCH_SHEET* loadedSheet = nullptr;
262
263 // Load the schematic
264 BOOST_CHECK_NO_THROW( loadedSheet = io.LoadSchematicFile( mainFile, m_schematic.get() ) );
265 BOOST_REQUIRE( loadedSheet != nullptr );
266
267 BOOST_TEST_MESSAGE( "Loaded sheet UUID: " + loadedSheet->m_Uuid.AsString().ToStdString() );
268 BOOST_TEST_MESSAGE( "Loaded sheet name: " + loadedSheet->GetName().ToStdString() );
269
270 m_schematic->SetRoot( loadedSheet );
271
272 // Build the hierarchy
273 m_schematic->RefreshHierarchy();
274 SCH_SHEET_LIST hierarchy = m_schematic->Hierarchy();
275
276 BOOST_TEST_MESSAGE( "Hierarchy size: " + std::to_string( hierarchy.size() ) );
277
278 // This schematic has 1 root + 2 sub-sheets = 3 total sheets
279 BOOST_REQUIRE_EQUAL( hierarchy.size(), 3 );
280
281 // Check each sheet path
282 for( size_t i = 0; i < hierarchy.size(); i++ )
283 {
284 const SCH_SHEET_PATH& path = hierarchy[i];
285 BOOST_TEST_MESSAGE( "\nSheet path [" + std::to_string(i) + "]: " + path.PathHumanReadable( false ).ToStdString() );
286 BOOST_TEST_MESSAGE( " Path KIID: " + path.Path().AsString().ToStdString() );
287 BOOST_TEST_MESSAGE( std::string(" Last screen: ") + (path.LastScreen() ? "YES" : "NO") );
288 BOOST_TEST_MESSAGE( " Path size: " + std::to_string( path.size() ) );
289
290 // Verify path has a screen
291 BOOST_CHECK( path.LastScreen() != nullptr );
292
293 if( path.LastScreen() )
294 {
295 // Count symbols on this sheet
296 int symbolCount = 0;
297 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
298 {
299 symbolCount++;
300 }
301 BOOST_TEST_MESSAGE( " Symbol count: " + std::to_string( symbolCount ) );
302 }
303 }
304
305 // Check for missing instances (this should create them if needed)
306 BOOST_TEST_MESSAGE( "\nChecking for missing symbol instances..." );
307 hierarchy.CheckForMissingSymbolInstances( m_project->GetProjectName() );
308
309 // Now verify that symbols have proper instance data
310 std::map<wxString, int> referenceCount;
311 int totalInstances = 0;
312 int emptyPaths = 0;
313
314 for( const SCH_SHEET_PATH& path : hierarchy )
315 {
316 if( !path.LastScreen() )
317 continue;
318
319 BOOST_TEST_MESSAGE( "\nVerifying instances for path: " + path.PathHumanReadable( false ).ToStdString() );
320
321 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
322 {
323 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
324 SCH_SYMBOL_INSTANCE symbolInstance;
325
326 // Check if symbol has instance for this path
327 bool hasInstance = symbol->GetInstance( symbolInstance, path.Path() );
328
329 if( hasInstance )
330 {
331 totalInstances++;
332
333 // Check that path is not empty
334 if( symbolInstance.m_Path.empty() )
335 {
336 emptyPaths++;
337 BOOST_TEST_MESSAGE( " ERROR: Symbol " + symbol->m_Uuid.AsString().ToStdString() +
338 " has EMPTY instance path!" );
339 }
340 else
341 {
342 referenceCount[symbolInstance.m_Reference]++;
343 BOOST_TEST_MESSAGE( " Symbol " + symbol->m_Uuid.AsString().ToStdString() +
344 " -> " + symbolInstance.m_Reference.ToStdString() +
345 " (path: " + symbolInstance.m_Path.AsString().ToStdString() + ")" );
346 }
347 }
348 else
349 {
350 BOOST_TEST_MESSAGE( " WARNING: Symbol " + symbol->m_Uuid.AsString().ToStdString() +
351 " has NO instance for path " + path.Path().AsString().ToStdString() );
352 }
353 }
354 }
355
356 BOOST_TEST_MESSAGE( "\nSummary:" );
357 BOOST_TEST_MESSAGE( " Total instances: " + std::to_string( totalInstances ) );
358 BOOST_TEST_MESSAGE( " Empty paths: " + std::to_string( emptyPaths ) );
359 BOOST_TEST_MESSAGE( " Unique references: " + std::to_string( referenceCount.size() ) );
360
361 // CRITICAL: No symbol instance should have an empty path
362 BOOST_CHECK_EQUAL( emptyPaths, 0 );
363
364 // The schematic should have instances for all symbols
365 BOOST_CHECK( totalInstances > 0 );
366
367 // Print reference counts for debugging
368 BOOST_TEST_MESSAGE( "\nReference designator counts:" );
369 for( const auto& pair : referenceCount )
370 {
371 BOOST_TEST_MESSAGE( " " + pair.first.ToStdString() + ": " + std::to_string( pair.second ) );
372 }
373
374 // Try to save the schematic - this should NOT crash or assert
375 BOOST_TEST_MESSAGE( "\nAttempting to save schematic..." );
376 wxString saveFile = GetTempFileName( "legacy_hierarchy_resave" );
377 saveFile += "." + FILEEXT::KiCadSchematicFileExtension;
378 m_tempFiles.push_back( saveFile );
379
380 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
381 BOOST_REQUIRE( !topSheets.empty() );
382
383 // This is the critical test - saving should not assert on empty paths
384 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( saveFile, topSheets[0], m_schematic.get() ) );
385 BOOST_CHECK( wxFileExists( saveFile ) );
386
387 BOOST_TEST_MESSAGE( "Save successful!" );
388}
389
390
const KIID m_Uuid
Definition eda_item.h:516
wxString AsString() const
Definition kiid.cpp:356
wxString AsString() const
Definition kiid.cpp:246
Container for project specific data.
Definition project.h:65
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:167
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void CheckForMissingSymbolInstances(const wxString &aProjectName)
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
bool empty() const
Forwarded method from std::vector.
SCH_SHEET * Last() const
Return a pointer to the last SCH_SHEET of the list.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:47
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:338
wxString GetName() const
Definition sch_sheet.h:113
void SetName(const wxString &aName)
Definition sch_sheet.h:114
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:75
bool GetInstance(SCH_SYMBOL_INSTANCE &aInstance, const KIID_PATH &aSheetPath, bool aTestFromEnd=false) const
static const std::string KiCadSchematicFileExtension
KIID niluuid(0)
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
std::vector< wxString > m_tempFiles
wxString GetTempFileName(const wxString &aPrefix)
SETTINGS_MANAGER m_settingsManager
std::unique_ptr< SCHEMATIC > m_schematic
A simple container for schematic symbol instance information.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(TestSaveLoadSimpleSchematic)
Test that we can save and reload a schematic with the virtual root pattern and that symbol instances ...
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_SYMBOL_T
Definition typeinfo.h:176