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* defaultSheet = m_schematic->GetTopLevelSheet( 0 );
134 SCH_SHEET* loadedSheet = nullptr;
135 BOOST_CHECK_NO_THROW( loadedSheet = io.LoadSchematicFile( fileName, m_schematic.get() ) );
136 BOOST_CHECK( loadedSheet != nullptr );
137
138 if( loadedSheet )
139 {
140 // Set it as root (which should create virtual root and make it a top-level sheet)
141 m_schematic->AddTopLevelSheet( loadedSheet );
142 m_schematic->RemoveTopLevelSheet( defaultSheet );
143 delete defaultSheet;
144
145 // Verify we can access the schematic
146 BOOST_CHECK( m_schematic->IsValid() );
147
148 // Verify current sheet is NOT the virtual root
149 SCH_SHEET_PATH& currentSheet2 = m_schematic->CurrentSheet();
150 BOOST_CHECK( !currentSheet2.empty() );
151 if( !currentSheet2.empty() )
152 {
153 BOOST_CHECK( currentSheet2.Last() != &m_schematic->Root() );
154 BOOST_CHECK( currentSheet2.Last()->m_Uuid != niluuid );
155 }
156
157 // Try to save again (this is where the bug occurs)
158 wxString fileName2 = GetTempFileName( "test_schematic_resave" );
159 fileName2 += ".kicad_sch";
160 m_tempFiles.push_back( fileName2 );
161
162 std::vector<SCH_SHEET*> topSheets2 = m_schematic->GetTopLevelSheets();
163 BOOST_REQUIRE( !topSheets2.empty() );
164
165 // This should not assert - this is the key test for the bug fix
166 // Symbol instances should be preserved, not dropped
167 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( fileName2, topSheets2[0], m_schematic.get() ) );
168
169 // Verify the second file was created
170 BOOST_CHECK( wxFileExists( fileName2 ) );
171 }
172}
173
174
178BOOST_AUTO_TEST_CASE( TestSaveLoadHierarchicalSchematic )
179{
180 // Create a schematic with hierarchy
181 m_schematic->CreateDefaultScreens();
182
183 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
184 BOOST_REQUIRE( !topSheets.empty() );
185
186 SCH_SCREEN* screen = topSheets[0]->GetScreen();
187 BOOST_REQUIRE( screen != nullptr );
188
189 // Create a sub-sheet
190 SCH_SHEET* subSheet = new SCH_SHEET( m_schematic.get() );
191 subSheet->SetName( "SubSheet" );
192 SCH_SCREEN* subScreen = new SCH_SCREEN( m_schematic.get() );
193 subSheet->SetScreen( subScreen );
194 subSheet->SetFileName( "subsheet.kicad_sch" );
195 subScreen->SetFileName( "subsheet.kicad_sch" );
196 screen->Append( subSheet );
197
198 // Save the main sheet
199 wxString mainFileName = GetTempFileName( "test_main" );
200 mainFileName += ".kicad_sch";
201 m_tempFiles.push_back( mainFileName );
202
203 // Save the sub-sheet
204 wxString subFileName = GetTempFileName( "test_sub" );
205 subFileName += ".kicad_sch";
206 m_tempFiles.push_back( subFileName );
207
209 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( mainFileName, topSheets[0], m_schematic.get() ) );
210 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( subFileName, subSheet, m_schematic.get() ) );
211
212 // Load it back
213 m_schematic->Reset();
214 SCH_SHEET* defaultSheet = m_schematic->GetTopLevelSheet( 0 );
215 SCH_SHEET* loadedSheet = nullptr;
216 BOOST_CHECK_NO_THROW( loadedSheet = io.LoadSchematicFile( mainFileName, m_schematic.get() ) );
217 BOOST_CHECK( loadedSheet != nullptr );
218
219 if( loadedSheet )
220 {
221 m_schematic->AddTopLevelSheet( loadedSheet );
222 m_schematic->RemoveTopLevelSheet( defaultSheet );
223 delete defaultSheet;
224
225 // Build hierarchy
226 m_schematic->RefreshHierarchy();
227
228 // Verify hierarchy has 2 sheets (main + sub)
229 SCH_SHEET_LIST hierarchy = m_schematic->Hierarchy();
230 BOOST_CHECK_EQUAL( hierarchy.size(), 2 );
231
232 // Try to save again
233 wxString mainFileName2 = GetTempFileName( "test_main_resave" );
234 mainFileName2 += ".kicad_sch";
235 m_tempFiles.push_back( mainFileName2 );
236
237 std::vector<SCH_SHEET*> topSheets2 = m_schematic->GetTopLevelSheets();
238 BOOST_REQUIRE( !topSheets2.empty() );
239
240 // This should not assert - this is the key test for the bug fix
241 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( mainFileName2, topSheets2[0], m_schematic.get() ) );
242
243 // Verify the file was created
244 BOOST_CHECK( wxFileExists( mainFileName2 ) );
245 }
246}
247
248
254BOOST_AUTO_TEST_CASE( TestLoadLegacyHierarchicalSchematic )
255{
256 // Load the legacy hierarchical schematic
257 wxFileName fn( KI_TEST::GetEeschemaTestDataDir() );
258 fn.AppendDir( "legacy_hierarchy" );
259 fn.SetName( "legacy_hierarchy" );
261 wxString mainFile = fn.GetFullPath();
262
263 BOOST_TEST_MESSAGE( "Loading schematic: " + mainFile.ToStdString() );
264 BOOST_REQUIRE( wxFileExists( mainFile ) );
265
267 SCH_SHEET* loadedSheet = nullptr;
268
269 // Load the schematic
270 BOOST_CHECK_NO_THROW( loadedSheet = io.LoadSchematicFile( mainFile, m_schematic.get() ) );
271 BOOST_REQUIRE( loadedSheet != nullptr );
272
273 BOOST_TEST_MESSAGE( "Loaded sheet UUID: " + loadedSheet->m_Uuid.AsString().ToStdString() );
274 BOOST_TEST_MESSAGE( "Loaded sheet name: " + loadedSheet->GetName().ToStdString() );
275
276 m_schematic->Reset();
277 SCH_SHEET* defaultSheet = m_schematic->GetTopLevelSheet( 0 );
278 m_schematic->AddTopLevelSheet( loadedSheet );
279 m_schematic->RemoveTopLevelSheet( defaultSheet );
280 delete defaultSheet;
281
282 // Build the hierarchy
283 m_schematic->RefreshHierarchy();
284 SCH_SHEET_LIST hierarchy = m_schematic->Hierarchy();
285
286 BOOST_TEST_MESSAGE( "Hierarchy size: " + std::to_string( hierarchy.size() ) );
287
288 // This schematic has 1 root + 2 sub-sheets = 3 total sheets
289 BOOST_REQUIRE_EQUAL( hierarchy.size(), 3 );
290
291 // Check each sheet path
292 for( size_t i = 0; i < hierarchy.size(); i++ )
293 {
294 const SCH_SHEET_PATH& path = hierarchy[i];
295 BOOST_TEST_MESSAGE( "\nSheet path [" + std::to_string(i) + "]: " + path.PathHumanReadable( false ).ToStdString() );
296 BOOST_TEST_MESSAGE( " Path KIID: " + path.Path().AsString().ToStdString() );
297 BOOST_TEST_MESSAGE( std::string(" Last screen: ") + (path.LastScreen() ? "YES" : "NO") );
298 BOOST_TEST_MESSAGE( " Path size: " + std::to_string( path.size() ) );
299
300 // Verify path has a screen
301 BOOST_CHECK( path.LastScreen() != nullptr );
302
303 if( path.LastScreen() )
304 {
305 // Count symbols on this sheet
306 int symbolCount = 0;
307 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
308 {
309 symbolCount++;
310 }
311 BOOST_TEST_MESSAGE( " Symbol count: " + std::to_string( symbolCount ) );
312 }
313 }
314
315 // Check for missing instances (this should create them if needed)
316 BOOST_TEST_MESSAGE( "\nChecking for missing symbol instances..." );
317 hierarchy.CheckForMissingSymbolInstances( m_project->GetProjectName() );
318
319 // Now verify that symbols have proper instance data
320 std::map<wxString, int> referenceCount;
321 int totalInstances = 0;
322 int emptyPaths = 0;
323
324 for( const SCH_SHEET_PATH& path : hierarchy )
325 {
326 if( !path.LastScreen() )
327 continue;
328
329 BOOST_TEST_MESSAGE( "\nVerifying instances for path: " + path.PathHumanReadable( false ).ToStdString() );
330
331 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
332 {
333 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
334 SCH_SYMBOL_INSTANCE symbolInstance;
335
336 // Check if symbol has instance for this path
337 bool hasInstance = symbol->GetInstance( symbolInstance, path.Path() );
338
339 if( hasInstance )
340 {
341 totalInstances++;
342
343 // Check that path is not empty
344 if( symbolInstance.m_Path.empty() )
345 {
346 emptyPaths++;
347 BOOST_TEST_MESSAGE( " ERROR: Symbol " + symbol->m_Uuid.AsString().ToStdString() +
348 " has EMPTY instance path!" );
349 }
350 else
351 {
352 referenceCount[symbolInstance.m_Reference]++;
353 BOOST_TEST_MESSAGE( " Symbol " + symbol->m_Uuid.AsString().ToStdString() +
354 " -> " + symbolInstance.m_Reference.ToStdString() +
355 " (path: " + symbolInstance.m_Path.AsString().ToStdString() + ")" );
356 }
357 }
358 else
359 {
360 BOOST_TEST_MESSAGE( " WARNING: Symbol " + symbol->m_Uuid.AsString().ToStdString() +
361 " has NO instance for path " + path.Path().AsString().ToStdString() );
362 }
363 }
364 }
365
366 BOOST_TEST_MESSAGE( "\nSummary:" );
367 BOOST_TEST_MESSAGE( " Total instances: " + std::to_string( totalInstances ) );
368 BOOST_TEST_MESSAGE( " Empty paths: " + std::to_string( emptyPaths ) );
369 BOOST_TEST_MESSAGE( " Unique references: " + std::to_string( referenceCount.size() ) );
370
371 // CRITICAL: No symbol instance should have an empty path
372 BOOST_CHECK_EQUAL( emptyPaths, 0 );
373
374 // The schematic should have instances for all symbols
375 BOOST_CHECK( totalInstances > 0 );
376
377 // Print reference counts for debugging
378 BOOST_TEST_MESSAGE( "\nReference designator counts:" );
379 for( const auto& pair : referenceCount )
380 {
381 BOOST_TEST_MESSAGE( " " + pair.first.ToStdString() + ": " + std::to_string( pair.second ) );
382 }
383
384 // Try to save the schematic - this should NOT crash or assert
385 BOOST_TEST_MESSAGE( "\nAttempting to save schematic..." );
386 wxString saveFile = GetTempFileName( "legacy_hierarchy_resave" );
387 saveFile += "." + FILEEXT::KiCadSchematicFileExtension;
388 m_tempFiles.push_back( saveFile );
389
390 std::vector<SCH_SHEET*> topSheets = m_schematic->GetTopLevelSheets();
391 BOOST_REQUIRE( !topSheets.empty() );
392
393 // This is the critical test - saving should not assert on empty paths
394 BOOST_CHECK_NO_THROW( io.SaveSchematicFile( saveFile, topSheets[0], m_schematic.get() ) );
395 BOOST_CHECK( wxFileExists( saveFile ) );
396
397 BOOST_TEST_MESSAGE( "Save successful!" );
398}
399
400
const KIID m_Uuid
Definition eda_item.h:522
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:48
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:382
wxString GetName() const
Definition sch_sheet.h:142
void SetName(const wxString &aName)
Definition sch_sheet.h:143
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:76
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()
std::string path
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