KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_symbol_library_parse_error.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 along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
28
30
31#include <lib_symbol.h>
32#include <richio.h>
35#include <sch_io/sch_io_mgr.h>
36
37#include <wx/filename.h>
38#include <wx/file.h>
39#include <wx/stdpaths.h>
40
41#include <fstream>
42
43
45{
46public:
50
52 {
53 // Clean up temp files
54 for( const wxString& file : m_tempFiles )
55 {
56 if( wxFileExists( file ) )
57 wxRemoveFile( file );
58 }
59 }
60
62 {
63 wxString tempDir = wxStandardPaths::Get().GetTempDir();
64 wxString fileName = tempDir + wxFileName::GetPathSeparator()
65 + wxString::Format( "test_lib_%d.kicad_sym", rand() );
66 m_tempFiles.push_back( fileName );
67 return fileName;
68 }
69
74 void CreateValidLibrary( const wxString& aPath, int aSymbolCount )
75 {
76 std::ofstream file( aPath.ToStdString() );
77
78 file << "(kicad_symbol_lib (version 20231120) (generator \"kicad_symbol_editor\")\n";
79 file << " (generator_version \"8.0\")\n";
80
81 for( int i = 1; i <= aSymbolCount; i++ )
82 {
83 file << " (symbol \"Symbol_" << i << "\"\n";
84 file << " (exclude_from_sim no) (in_bom yes) (on_board yes)\n";
85 file << " (duplicate_pin_numbers_are_jumpers no)\n";
86 file << " (property \"Reference\" \"U\" (at 0 0 0)\n";
87 file << " (effects (font (size 1.27 1.27)))\n";
88 file << " )\n";
89 file << " (property \"Value\" \"Symbol_" << i << "\" (at 0 2.54 0)\n";
90 file << " (effects (font (size 1.27 1.27)))\n";
91 file << " )\n";
92 file << " (property \"Footprint\" \"\" (at 0 0 0)\n";
93 file << " (effects (font (size 1.27 1.27)) hide)\n";
94 file << " )\n";
95 file << " (property \"Datasheet\" \"\" (at 0 0 0)\n";
96 file << " (effects (font (size 1.27 1.27)) hide)\n";
97 file << " )\n";
98 file << " (property \"Description\" \"Test symbol " << i << "\" (at 0 0 0)\n";
99 file << " (effects (font (size 1.27 1.27)) hide)\n";
100 file << " )\n";
101 file << " (symbol \"Symbol_" << i << "_1_1\"\n";
102 file << " (rectangle (start -2.54 2.54) (end 2.54 -2.54)\n";
103 file << " (stroke (width 0.254) (type default))\n";
104 file << " (fill (type background))\n";
105 file << " )\n";
106 file << " )\n";
107 file << " )\n";
108 }
109
110 file << ")\n";
111 file.close();
112 }
113
119 void CorruptLibraryAfterSymbol( const wxString& aPath, int aSymbolNumber )
120 {
121 // Read the entire file
122 std::ifstream inFile( aPath.ToStdString() );
123 std::string content( ( std::istreambuf_iterator<char>( inFile ) ),
124 std::istreambuf_iterator<char>() );
125 inFile.close();
126
127 // Find the position after the specified symbol
128 std::string searchFor = "Symbol_" + std::to_string( aSymbolNumber ) + "_1_1";
129 size_t pos = content.find( searchFor );
130
131 if( pos != std::string::npos )
132 {
133 // Find the end of this symbol (look for the closing parenthesis pattern)
134 // Go past the symbol definition to corrupt the next one
135 pos = content.find( " (symbol \"Symbol_" + std::to_string( aSymbolNumber + 1 ), pos );
136
137 if( pos != std::string::npos )
138 {
139 // Insert garbage that will cause a parse error
140 content.insert( pos + 10, "CORRUPT_DATA_HERE{{{invalid}}}syntax###" );
141 }
142 }
143
144 // Write the corrupted content back
145 std::ofstream outFile( aPath.ToStdString() );
146 outFile << content;
147 outFile.close();
148 }
149
150 std::vector<wxString> m_tempFiles;
151};
152
153
154BOOST_FIXTURE_TEST_SUITE( SymbolLibraryParseError, SYMBOL_LIBRARY_PARSE_ERROR_FIXTURE )
155
156
157
161BOOST_AUTO_TEST_CASE( ValidLibraryLoadsCompletely )
162{
163 wxString libPath = GetTempLibraryPath();
164 const int symbolCount = 5;
165
166 // Create a valid library with 5 symbols
167 CreateValidLibrary( libPath, symbolCount );
168
169 // Load the library using the cache
170 SCH_IO_KICAD_SEXPR_LIB_CACHE cache( libPath );
171
172 BOOST_CHECK_NO_THROW( cache.Load() );
173
174 // Verify all symbols are present
175 const LIB_SYMBOL_MAP& symbols = cache.GetSymbolMap();
176 BOOST_CHECK_EQUAL( symbols.size(), symbolCount );
177
178 for( int i = 1; i <= symbolCount; i++ )
179 {
180 wxString symbolName = wxString::Format( "Symbol_%d", i );
181 BOOST_CHECK_MESSAGE( symbols.find( symbolName ) != symbols.end(),
182 "Symbol " << symbolName << " should be present" );
183 }
184}
185
186
191BOOST_AUTO_TEST_CASE( ParseErrorSkipsCorruptSymbol )
192{
193 wxString libPath = GetTempLibraryPath();
194 const int symbolCount = 5;
195 const int corruptSymbol = 3; // Symbol_3 will be corrupted
196
197 // Create a valid library with 5 symbols
198 CreateValidLibrary( libPath, symbolCount );
199
200 // Corrupt symbol 3 (so it will fail to parse, but symbols 4 and 5 should still load)
201 CorruptLibraryAfterSymbol( libPath, corruptSymbol - 1 );
202
203 // Try to load the library - it should throw an exception due to parse error
204 // but still load all valid symbols
205 SCH_IO_KICAD_SEXPR_LIB_CACHE cache( libPath );
206
207 // The Load() should throw an IO_ERROR to notify of the parse error
208 BOOST_CHECK_THROW( cache.Load(), IO_ERROR );
209
210 // Verify that all valid symbols are present (only the corrupt one is missing)
211 const LIB_SYMBOL_MAP& symbols = cache.GetSymbolMap();
212
213 // Symbols 1 and 2 should be present (before the corrupted symbol)
214 for( int i = 1; i < corruptSymbol; i++ )
215 {
216 wxString symbolName = wxString::Format( "Symbol_%d", i );
217 BOOST_CHECK_MESSAGE( symbols.find( symbolName ) != symbols.end(),
218 "Symbol " << symbolName << " should be present (before corrupt symbol)" );
219 }
220
221 // Symbol 3 should NOT be present (it's the corrupt one)
222 {
223 wxString symbolName = wxString::Format( "Symbol_%d", corruptSymbol );
224 BOOST_CHECK_MESSAGE( symbols.find( symbolName ) == symbols.end(),
225 "Symbol " << symbolName << " should NOT be present (corrupt symbol)" );
226 }
227
228 // Symbols 4 and 5 should be present (after the corrupted symbol - error recovery worked)
229 for( int i = corruptSymbol + 1; i <= symbolCount; i++ )
230 {
231 wxString symbolName = wxString::Format( "Symbol_%d", i );
232 BOOST_CHECK_MESSAGE( symbols.find( symbolName ) != symbols.end(),
233 "Symbol " << symbolName << " should be present (after corrupt symbol, recovered)" );
234 }
235
236 // Verify that 4 symbols are loaded (all except the corrupt one)
237 BOOST_CHECK_EQUAL( symbols.size(), symbolCount - 1 );
238}
239
240
246BOOST_AUTO_TEST_CASE( SaveAfterParseErrorIsPrevented )
247{
248 wxString libPath = GetTempLibraryPath();
249 wxString backupPath = libPath + ".backup";
250 m_tempFiles.push_back( backupPath );
251
252 const int symbolCount = 5;
253 const int corruptSymbol = 3; // Symbol_3 will be corrupted
254
255 // Create a valid library with 5 symbols
256 CreateValidLibrary( libPath, symbolCount );
257
258 // Make a backup copy of the original file
259 wxCopyFile( libPath, backupPath );
260
261 // Corrupt symbol 3
262 CorruptLibraryAfterSymbol( libPath, corruptSymbol - 1 );
263
264 // Try to load the library
265 SCH_IO_KICAD_SEXPR_LIB_CACHE cache( libPath );
266
267 try
268 {
269 cache.Load();
270 BOOST_FAIL( "Load should have thrown an exception due to parse error" );
271 }
272 catch( const IO_ERROR& )
273 {
274 // Expected - parse error occurred but library was still loaded
275 }
276
277 // Verify all valid symbols were loaded (only the corrupt one is missing)
278 const LIB_SYMBOL_MAP& symbols = cache.GetSymbolMap();
279 BOOST_CHECK_EQUAL( symbols.size(), symbolCount - 1 ); // 4 symbols loaded
280
281 // Verify the parse error flag is set
282 BOOST_CHECK_MESSAGE( cache.HasParseError(),
283 "HasParseError() should return true after a parse error" );
284
285 // Mark the cache as modified (simulating user making a change)
286 cache.SetModified( true );
287
288 // The key test: attempting to save should throw an exception because
289 // the library has parse errors. This prevents losing the corrupt symbol.
290 BOOST_CHECK_THROW( cache.Save(), IO_ERROR );
291
292 // Verify the original file was not modified (backup should match)
293 // by checking that it still has the corruption (can't be loaded cleanly)
294 SCH_IO_KICAD_SEXPR_LIB_CACHE reloadedCache( libPath );
295 BOOST_CHECK_THROW( reloadedCache.Load(), IO_ERROR );
296}
297
298
302BOOST_AUTO_TEST_CASE( CacheTracksParseErrorState )
303{
304 wxString libPath = GetTempLibraryPath();
305 const int symbolCount = 5;
306 const int corruptAfter = 2;
307
308 // Create and corrupt a library
309 CreateValidLibrary( libPath, symbolCount );
310 CorruptLibraryAfterSymbol( libPath, corruptAfter );
311
312 SCH_IO_KICAD_SEXPR_LIB_CACHE cache( libPath );
313
314 // Before loading, no parse error
315 BOOST_CHECK( !cache.HasParseError() );
316
317 try
318 {
319 cache.Load();
320 }
321 catch( const IO_ERROR& )
322 {
323 // Expected
324 }
325
326 // After failed load, parse error flag should be set
327 BOOST_CHECK_MESSAGE( cache.HasParseError(),
328 "HasParseError() should return true after parse error during load" );
329
330 // For a valid library, the state should be clean:
331 wxString validLibPath = GetTempLibraryPath();
332 CreateValidLibrary( validLibPath, symbolCount );
333
334 SCH_IO_KICAD_SEXPR_LIB_CACHE validCache( validLibPath );
335
336 // Before loading, no parse error
337 BOOST_CHECK( !validCache.HasParseError() );
338
339 BOOST_CHECK_NO_THROW( validCache.Load() );
340
341 // After successful load, still no parse error
342 BOOST_CHECK_MESSAGE( !validCache.HasParseError(),
343 "HasParseError() should return false after successful load" );
344
345 // Verify that a valid library can be saved
346 validCache.SetModified( true );
347 BOOST_CHECK_NO_THROW( validCache.Save() );
348}
349
350
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
A cache assistant for the KiCad s-expression symbol libraries.
void Save(const std::optional< bool > &aOpt=std::nullopt) override
Save the entire library to file m_libFileName;.
bool HasParseError() const
const LIB_SYMBOL_MAP & GetSymbolMap() const
void SetModified(bool aModified=true)
void CreateValidLibrary(const wxString &aPath, int aSymbolCount)
Create a valid symbol library file with the specified number of symbols.
void CorruptLibraryAfterSymbol(const wxString &aPath, int aSymbolNumber)
Corrupt the library file by introducing a parse error after the specified symbol.
std::map< wxString, LIB_SYMBOL *, LibSymbolMapSort > LIB_SYMBOL_MAP
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(ValidLibraryLoadsCompletely)
Test that a valid library can be created, loaded, and all symbols are present.
BOOST_CHECK_EQUAL(result, "25.4")