KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_symbol_embedded_files.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
24
26
27// Code under test
28#include <lib_symbol.h>
30#include <sch_io/sch_io_mgr.h>
31#include <embedded_files.h>
32#include <mmh3_hash.h>
33#include <schematic.h>
34#include <sch_screen.h>
35#include <sch_sheet.h>
36#include <sch_sheet_path.h>
37#include <sch_symbol.h>
38#include <project.h>
39#include <lib_id.h>
41
42#include <wx/filename.h>
43#include <wx/dir.h>
44
46{
47public:
49 {
50 m_tempDir = wxFileName::CreateTempFileName( wxS( "kicad_test_" ) );
51 wxRemoveFile( m_tempDir );
52 wxFileName::Mkdir( m_tempDir );
53
54 m_libPath = wxFileName( m_tempDir, wxS( "test_lib.kicad_sym" ) ).GetFullPath();
55 }
56
58 {
59 // Clean up temporary directory
60 if( wxFileName::DirExists( m_tempDir ) )
61 {
62 wxFileName::Rmdir( m_tempDir, wxPATH_RMDIR_RECURSIVE );
63 }
64 }
65
66 wxString GetTempDir() const { return m_tempDir; }
67 wxString GetLibPath() const { return m_libPath; }
68
73 const std::string& aContent )
74 {
76 file->name = aName;
77 file->decompressedData.assign( aContent.begin(), aContent.end() );
78
80 hash.add( file->decompressedData );
81 file->data_hash = hash.digest().ToString();
82
85
86 return file;
87 }
88
89private:
90 wxString m_tempDir;
91 wxString m_libPath;
92};
93
94
95BOOST_FIXTURE_TEST_SUITE( SymbolEmbeddedFiles, SYMBOL_EMBEDDED_FILES_TEST_FIXTURE )
96
97
98
110BOOST_AUTO_TEST_CASE( DerivedSymbolEmbeddedFiles )
111{
112 // Step 1: Create a parent symbol with an embedded file and save
113 std::unique_ptr<LIB_SYMBOL> parentSymbol = std::make_unique<LIB_SYMBOL>( wxS( "ParentSymbol" ) );
114 parentSymbol->GetValueField().SetText( wxS( "ParentSymbol" ) );
115 parentSymbol->GetReferenceField().SetText( wxS( "U" ) );
116
117 // Add an embedded file to the parent
119 CreateTestEmbeddedFile( wxS( "parent_datasheet.pdf" ), "Parent datasheet content" );
120 parentSymbol->AddFile( parentFile );
121
122 BOOST_CHECK( parentSymbol->HasFile( wxS( "parent_datasheet.pdf" ) ) );
123 BOOST_CHECK_EQUAL( parentSymbol->EmbeddedFileMap().size(), 1 );
124
125 // Save the parent symbol to the library
126 {
127 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
128 plugin->CreateLibrary( GetLibPath() );
129 plugin->SaveSymbol( GetLibPath(), new LIB_SYMBOL( *parentSymbol ) );
130 plugin->SaveLibrary( GetLibPath() );
131 }
132
133 // Step 2: Close and reload the symbol to verify that the embedded file still exists.
134 {
135 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
136 LIB_SYMBOL* loadedParent = plugin->LoadSymbol( GetLibPath(), wxS( "ParentSymbol" ) );
137 BOOST_REQUIRE( loadedParent );
138 BOOST_CHECK( loadedParent->HasFile( wxS( "parent_datasheet.pdf" ) ) );
139 }
140
141 // Step 3: Create a derived symbol based on the first symbol and save
142 {
143 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
144 LIB_SYMBOL* loadedParent = plugin->LoadSymbol( GetLibPath(), wxS( "ParentSymbol" ) );
145 BOOST_REQUIRE( loadedParent );
146
147 std::unique_ptr<LIB_SYMBOL> derivedSymbol = std::make_unique<LIB_SYMBOL>( wxS( "DerivedSymbol" ) );
148 derivedSymbol->GetValueField().SetText( wxS( "DerivedSymbol" ) );
149 derivedSymbol->SetParent( loadedParent );
150
151 plugin->SaveSymbol( GetLibPath(), new LIB_SYMBOL( *derivedSymbol ) );
152 plugin->SaveLibrary( GetLibPath() );
153 }
154
155 // Step 4: Close and re-open the derived symbol. Ensure that the embedded file does not exist in the derived symbol.
156 {
157 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
158 LIB_SYMBOL* loadedDerived = plugin->LoadSymbol( GetLibPath(), wxS( "DerivedSymbol" ) );
159 BOOST_REQUIRE( loadedDerived );
160
161 // The derived symbol itself should not have the file in its map
162 BOOST_CHECK( !loadedDerived->HasFile( wxS( "parent_datasheet.pdf" ) ) );
163 BOOST_CHECK_EQUAL( loadedDerived->EmbeddedFileMap().size(), 0 );
164 }
165
166 // Step 5: Embed a new file in the derived symbol
167 {
168 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
169 // We need to load parent to set it again, because LoadSymbol doesn't automatically link parents from disk without a library table
170 LIB_SYMBOL* loadedParent = plugin->LoadSymbol( GetLibPath(), wxS( "ParentSymbol" ) );
171 LIB_SYMBOL* loadedDerived = plugin->LoadSymbol( GetLibPath(), wxS( "DerivedSymbol" ) );
172 BOOST_REQUIRE( loadedDerived );
173 loadedDerived->SetParent( loadedParent );
174
175 EMBEDDED_FILES::EMBEDDED_FILE* derivedFile =
176 CreateTestEmbeddedFile( wxS( "derived_datasheet.pdf" ), "Derived datasheet content" );
177 loadedDerived->AddFile( derivedFile );
178
179 BOOST_CHECK( loadedDerived->HasFile( wxS( "derived_datasheet.pdf" ) ) );
180
181 plugin->SaveSymbol( GetLibPath(), new LIB_SYMBOL( *loadedDerived ) );
182 plugin->SaveLibrary( GetLibPath() );
183 }
184
185 // Step 6: Close and reopen the derived symbol. Ensure that the new embedded file exists
186 {
187 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
188 LIB_SYMBOL* loadedDerived = plugin->LoadSymbol( GetLibPath(), wxS( "DerivedSymbol" ) );
189 BOOST_REQUIRE( loadedDerived );
190 BOOST_CHECK( loadedDerived->HasFile( wxS( "derived_datasheet.pdf" ) ) );
191 }
192
193 // Step 7: Add only the derived symbol to a new schematic sheet. Ensure that both embedded files are present in the schematic.
194 {
195 SCHEMATIC schematic( nullptr );
196
197 schematic.Reset();
198 SCH_SHEET* defaultSheet = schematic.GetTopLevelSheet( 0 );
199
200 SCH_SHEET* rootSheet = new SCH_SHEET( &schematic );
201 SCH_SCREEN* screen = new SCH_SCREEN( &schematic );
202 rootSheet->SetScreen( screen );
203
204 schematic.AddTopLevelSheet( rootSheet );
205 schematic.RemoveTopLevelSheet( defaultSheet );
206 delete defaultSheet;
207
208 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
209 LIB_SYMBOL* loadedParent = plugin->LoadSymbol( GetLibPath(), wxS( "ParentSymbol" ) );
210 LIB_SYMBOL* loadedDerived = plugin->LoadSymbol( GetLibPath(), wxS( "DerivedSymbol" ) );
211
212 loadedDerived->SetParent( loadedParent );
213
214 SCH_SHEET_PATH rootPath;
215 rootPath.push_back( &schematic.Root() );
216 rootPath.push_back( rootSheet );
217
218 SCH_SYMBOL* schSymbol = new SCH_SYMBOL( *loadedDerived, LIB_ID( "test_lib", "DerivedSymbol" ),
219 &rootPath, 0 );
220 screen->Append( schSymbol );
221
222 // Verify derived file
223 BOOST_CHECK( schSymbol->GetLibSymbolRef()->HasFile( wxS( "derived_datasheet.pdf" ) ) );
224
225 // Verify parent file (should be flattened into the symbol)
226 BOOST_CHECK( schSymbol->GetLibSymbolRef()->HasFile( wxS( "parent_datasheet.pdf" ) ) );
227 }
228}
229
230
234BOOST_AUTO_TEST_CASE( CopySymbolPreservesEmbeddedFiles )
235{
236 // Create a symbol with an embedded file
237 std::unique_ptr<LIB_SYMBOL> original = std::make_unique<LIB_SYMBOL>( wxS( "Original" ) );
238 original->GetValueField().SetText( wxS( "Original" ) );
239
241 CreateTestEmbeddedFile( wxS( "test.pdf" ), "Test content" );
242 original->AddFile( file );
243
244 BOOST_CHECK_EQUAL( original->EmbeddedFileMap().size(), 1 );
245
246 // Copy the symbol using copy constructor
247 std::unique_ptr<LIB_SYMBOL> copy = std::make_unique<LIB_SYMBOL>( *original );
248
249 // Verify the copy has the embedded file
250 BOOST_CHECK_EQUAL( copy->EmbeddedFileMap().size(), 1 );
251 BOOST_CHECK( copy->HasFile( wxS( "test.pdf" ) ) );
252
253 EMBEDDED_FILES::EMBEDDED_FILE* copiedFile = copy->GetEmbeddedFile( wxS( "test.pdf" ) );
254 BOOST_REQUIRE( copiedFile );
255 BOOST_CHECK( copiedFile->Validate() );
256}
257
258
262BOOST_AUTO_TEST_CASE( AssignmentPreservesEmbeddedFiles )
263{
264 // Create a symbol with an embedded file
265 std::unique_ptr<LIB_SYMBOL> source = std::make_unique<LIB_SYMBOL>( wxS( "Source" ) );
266 source->GetValueField().SetText( wxS( "Source" ) );
267
269 CreateTestEmbeddedFile( wxS( "source.pdf" ), "Source content" );
270 source->AddFile( file );
271
272 // Create a destination symbol
273 std::unique_ptr<LIB_SYMBOL> dest = std::make_unique<LIB_SYMBOL>( wxS( "Dest" ) );
274 dest->GetValueField().SetText( wxS( "Dest" ) );
275
276 // Assign source to destination
277 *dest = *source;
278
279 // Verify destination has the embedded file
280 BOOST_CHECK_EQUAL( dest->EmbeddedFileMap().size(), 1 );
281 BOOST_CHECK( dest->HasFile( wxS( "source.pdf" ) ) );
282
283 EMBEDDED_FILES::EMBEDDED_FILE* destFile = dest->GetEmbeddedFile( wxS( "source.pdf" ) );
284 BOOST_REQUIRE( destFile );
285 BOOST_CHECK( destFile->Validate() );
286}
287
288
296BOOST_AUTO_TEST_CASE( AddEmbeddedFileToPlacedSymbolSurvivesSave )
297{
298 SETTINGS_MANAGER settingsManager;
299 wxString projectPath = wxFileName( GetTempDir(), wxS( "test_project.kicad_pro" ) ).GetFullPath();
300 settingsManager.LoadProject( projectPath.ToStdString() );
301
302 auto schematic = std::make_unique<SCHEMATIC>( nullptr );
303 schematic->SetProject( &settingsManager.Prj() );
304 schematic->Reset();
305
306 SCH_SHEET* defaultSheet = schematic->GetTopLevelSheet( 0 );
307 SCH_SHEET* rootSheet = new SCH_SHEET( schematic.get() );
308 SCH_SCREEN* screen = new SCH_SCREEN( schematic.get() );
309 rootSheet->SetScreen( screen );
310
311 schematic->AddTopLevelSheet( rootSheet );
312 schematic->RemoveTopLevelSheet( defaultSheet );
313 delete defaultSheet;
314
315 // Build a library symbol carrying one embedded file and place it on the sheet.
316 std::unique_ptr<LIB_SYMBOL> libSymbol = std::make_unique<LIB_SYMBOL>( wxS( "EmbeddedSymbol" ) );
317 libSymbol->GetValueField().SetText( wxS( "EmbeddedSymbol" ) );
318 libSymbol->GetReferenceField().SetText( wxS( "U" ) );
319 libSymbol->AddFile( CreateTestEmbeddedFile( wxS( "datasheet.pdf" ), "Original datasheet" ) );
320
321 SCH_SHEET_PATH rootPath;
322 rootPath.push_back( &schematic->Root() );
323 rootPath.push_back( rootSheet );
324
325 // Two instances share one cached library symbol. This is the case that exposes the bug:
326 // removing one symbol keeps the cache entry alive, so Append must reconcile the cache
327 // through its "unchanged" branch (which is what LIB_SYMBOL::Compare reports for an
328 // embedded-file-only edit) rather than rebuilding it from scratch.
329 SCH_SYMBOL* schSymbol =
330 new SCH_SYMBOL( *libSymbol, LIB_ID( "test_lib", "EmbeddedSymbol" ), &rootPath, 0 );
331 schSymbol->SetRef( &rootPath, wxS( "U1" ) );
332 screen->Append( schSymbol );
333
334 SCH_SYMBOL* otherSymbol =
335 new SCH_SYMBOL( *libSymbol, LIB_ID( "test_lib", "EmbeddedSymbol" ), &rootPath, 0 );
336 otherSymbol->SetRef( &rootPath, wxS( "U2" ) );
337 otherSymbol->Move( VECTOR2I( 2540000, 0 ) );
338 screen->Append( otherSymbol );
339
340 // Adding a second file through the properties dialog only touches the placed instance's
341 // library symbol, mirroring what PANEL_EMBEDDED_FILES does.
342 schSymbol->GetEmbeddedFiles()->AddFile( CreateTestEmbeddedFile( wxS( "extra.pdf" ), "Newly added" ) );
343
344 BOOST_CHECK( schSymbol->GetEmbeddedFiles()->HasFile( wxS( "datasheet.pdf" ) ) );
345 BOOST_CHECK( schSymbol->GetEmbeddedFiles()->HasFile( wxS( "extra.pdf" ) ) );
346
347 // Re-add the symbol the same way the properties dialog does on OK; Append must push the
348 // instance's embedded files into the cache that is serialized.
349 BOOST_REQUIRE( screen->Remove( schSymbol ) );
350 screen->Append( schSymbol );
351
352 wxString fileName = wxFileName( GetTempDir(), wxS( "embedded_roundtrip.kicad_sch" ) ).GetFullPath();
353
355 io.SaveSchematicFile( fileName, rootSheet, schematic.get() );
356 BOOST_REQUIRE( wxFileExists( fileName ) );
357
358 schematic->Reset();
359 SCH_SHEET* newDefaultSheet = schematic->GetTopLevelSheet( 0 );
360 SCH_SHEET* loadedSheet = io.LoadSchematicFile( fileName, schematic.get() );
361 BOOST_REQUIRE( loadedSheet );
362
363 schematic->AddTopLevelSheet( loadedSheet );
364 schematic->RemoveTopLevelSheet( newDefaultSheet );
365 delete newDefaultSheet;
366
367 SCH_SCREEN* loadedScreen = loadedSheet->GetScreen();
368 BOOST_REQUIRE( loadedScreen );
369
370 bool foundSymbol = false;
371
372 for( const auto& [name, cachedSymbol] : loadedScreen->GetLibSymbols() )
373 {
374 if( cachedSymbol->GetName() == wxS( "EmbeddedSymbol" ) )
375 {
376 foundSymbol = true;
377 BOOST_CHECK( cachedSymbol->HasFile( wxS( "datasheet.pdf" ) ) );
378 BOOST_CHECK( cachedSymbol->HasFile( wxS( "extra.pdf" ) ) );
379 }
380 }
381
382 BOOST_CHECK( foundSymbol );
383}
384
385
const char * name
bool HasFile(const wxString &name) const
EMBEDDED_FILE * AddFile(const wxFileName &aName, bool aOverwrite)
Load a file from disk and adds it to the collection.
static uint32_t Seed()
const std::map< wxString, EMBEDDED_FILE * > & EmbeddedFileMap() const
static RETURN_CODE CompressAndEncode(EMBEDDED_FILE &aFile)
Take data from the #decompressedData buffer and compresses it using ZSTD into the #compressedEncodedD...
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
Define a library symbol object.
Definition lib_symbol.h:79
void SetParent(LIB_SYMBOL *aParent=nullptr)
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:117
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
Holds all the data relating to one schematic.
Definition schematic.h:90
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,...
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
const std::map< wxString, LIB_SYMBOL * > & GetLibSymbols() const
Fetch a list of unique LIB_SYMBOL object pointers required to properly render each SCH_SYMBOL in this...
Definition sch_screen.h:494
bool Remove(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Remove aItem from the schematic associated with this screen.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:44
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:69
EMBEDDED_FILES * GetEmbeddedFiles() override
SCH_SYMBOLs don't currently support embedded files, but their LIB_SYMBOL counterparts do.
void Move(const VECTOR2I &aMoveVector) override
Move the item by aMoveVector to a new position.
Definition sch_symbol.h:781
void SetRef(const SCH_SHEET_PATH *aSheet, const wxString &aReference)
Set the reference for the given sheet path for this symbol.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:177
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
EMBEDDED_FILES::EMBEDDED_FILE * CreateTestEmbeddedFile(const wxString &aName, const std::string &aContent)
Create a test embedded file with the given name and content.
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
std::vector< char > decompressedData
std::string ToString() const
Definition hash_128.h:43
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(DerivedSymbolEmbeddedFiles)
Test embedded files in parent and derived symbols: 1) Create a symbol and add an embedded file and sa...
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683