KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_library_tables.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 * @author Jon Evans <[email protected]>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <algorithm>
22#include <filesystem>
23#include <fstream>
24#include <optional>
25#include <utility>
26#include <vector>
27
28#include <wx/ffile.h>
29
30#include <mock_pgm_base.h>
31#include <richio.h>
34#include <qa_utils/file_utils.h>
37#include <pegtl/contrib/analyze.hpp>
38
39#include <env_vars.h>
40#include <pgm_base.h>
46#include <scoped_set_reset.h>
49
50
51BOOST_AUTO_TEST_SUITE( LibraryTables )
52
53
55{
56 BOOST_REQUIRE( tao::pegtl::analyze< LIBRARY_TABLE_GRAMMAR::LIB_TABLE_FILE >( 1 ) == 0 );
57}
58
59
61{
63 tl::expected<LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR> result = parser.ParseBuffer( "" );
64 BOOST_REQUIRE( !result.has_value() );
65}
66
67
68BOOST_AUTO_TEST_CASE( ParseFromFile )
69{
70 std::vector<std::string> cases = {
71 "sym-lib-table",
72 "fp-lib-table"
73 };
74
75 std::filesystem::path p( KI_TEST::GetTestDataRootDir() );
76 p.append( "libraries/" );
77
79
80 for( const std::string& path : cases )
81 {
82 p.remove_filename();
83 p.append( path );
84
85 auto result = parser.Parse( p );
86
87 BOOST_REQUIRE( result.has_value() );
88 }
89}
90
91
92BOOST_AUTO_TEST_CASE( ParseAndConstruct )
93{
94 struct TESTCASE
95 {
96 wxString filename;
97 wxString expected_error;
98 size_t expected_rows;
99 bool check_formatted = true;
100 };
101
102 std::vector<TESTCASE> cases = {
103 { .filename = "sym-lib-table", .expected_rows = 224 },
104 { .filename = "fp-lib-table", .expected_rows = 146 },
105 { .filename = "nested-symbols", .expected_rows = 6 },
106 { .filename = "nested-disabled", .expected_rows = 4 },
107 { .filename = "nested-hidden", .expected_rows = 4 },
108 { .filename = "cycle", .expected_rows = 2 },
109 { .filename = "sym-hand-edited", .expected_rows = 2, .check_formatted = false },
110 { .filename = "corrupted", .expected_error = "Syntax error at line 6, column 9" },
111 { .filename = "truncated", .expected_error = "Syntax error at line 11, column 1" }
112 };
113
114 wxFileName fn( KI_TEST::GetTestDataRootDir(), wxEmptyString );
115 fn.AppendDir( "libraries" );
116
117 for( const auto& [filename, expected_error, expected_rows, check_formatted] : cases )
118 {
119 BOOST_TEST_CONTEXT( filename )
120 {
121 fn.SetName( filename );
123
124 BOOST_REQUIRE( table.IsOk() == ( expected_error.IsEmpty() ) );
125
126 BOOST_REQUIRE_MESSAGE( table.Rows().size() == expected_rows,
127 wxString::Format( "Expected %zu rows but got %zu",
128 expected_rows, table.Rows().size() ) );
129
130 BOOST_REQUIRE_MESSAGE( table.ErrorDescription() == expected_error,
131 wxString::Format( "Expected error '%s' but got '%s'",
132 expected_error, table.ErrorDescription() ) );
133
134 // Non-parsed tables can't be formatted
135 if( !table.IsOk() || !check_formatted )
136 continue;
137
138 std::ifstream inFp;
139 inFp.open( fn.GetFullPath().fn_str() );
140 BOOST_REQUIRE( inFp.is_open() );
141
142 std::stringstream inBuf;
143 inBuf << inFp.rdbuf();
144 std::string inData = inBuf.str();
145
146 STRING_FORMATTER formatter;
147 table.Format( &formatter );
149 KICAD_FORMAT::FORMAT_MODE::LIBRARY_TABLE );
150
151 if( formatter.GetString().compare( inData ) != 0 )
152 {
153 BOOST_TEST_MESSAGE( "--- original ---" );
154 BOOST_TEST_MESSAGE( inData );
155 BOOST_TEST_MESSAGE( "--- formatted ---" );
156 BOOST_TEST_MESSAGE( formatter.GetString() );
157 }
158
159 BOOST_REQUIRE( formatter.GetString().compare( inData ) == 0 );
160 }
161 }
162}
163
165{
166 LIBRARY_MANAGER manager;
167 manager.LoadGlobalTables();
168
169 BOOST_REQUIRE( manager.Rows( LIBRARY_TABLE_TYPE::SYMBOL ).size() == 3 );
170 BOOST_REQUIRE( manager.Rows( LIBRARY_TABLE_TYPE::FOOTPRINT ).size() == 146 );
171}
172
173
174/*
175 * Regression test for PCM library auto-add scan.
176 *
177 * The PCM auto-add scan is triggered by the presence of PCM packages in the 3RD_PARTY directory.
178 * But when this happens, if there is no global design block table, this process shouldn't die.
179 *
180 * Sentry KICAD-17T5 was a startup crash when the design block table was missing.
181 */
182BOOST_AUTO_TEST_CASE( PcmScanWithoutDesignBlockTable )
183{
184 // Create a temporary directory with a PCM package containing a design block library
185 KI_TEST::SCOPED_TEMP_DIR tempDir( wxS( "pcm-scan" ) );
186
187 const bool dirsOk =
188 std::filesystem::create_directories( tempDir.Path() / "design_blocks" / "test" / "block.kicad_blocks" );
189 BOOST_REQUIRE( dirsOk );
190
191 // PCM auto-add must be on
192 bool& autoAddSetting = Pgm().GetSettingsManager().GetAppSettings<KICAD_SETTINGS>( "kicad" )->m_PcmLibAutoAdd;
193 SCOPED_SET_RESET<bool> autoAddSettingReset( autoAddSetting, true );
194
195 KI_TEST::SCOPED_PGM_ENV_VAR envVar( wxS( "KICAD10_3RD_PARTY" ), tempDir.PathStr() );
196
197 LIBRARY_MANAGER manager;
198
199 // Only the symbol table is loaded; the design block table is absent.
201
204}
205
206
207// Regression coverage for the LoadGlobalTables guard: when a global library
208// table file does not exist on disk, loadTables() never inserts an entry for
209// that type into m_tables, so Table( aType, GLOBAL ) must return std::nullopt
210// instead of an entry holding a null pointer. The cleanupRemovedPCMLibraries
211// lambda inside LoadGlobalTables relies on this contract via .value_or(
212// nullptr ) and must not dereference the result. Exercising the full PCM
213// cleanup path requires extensive environment setup (3RD_PARTY env var plus
214// PCM auto-remove enabled in user settings), so we test the underlying
215// contract directly here.
216BOOST_AUTO_TEST_CASE( ManagerTableReturnsNulloptForUnloadedType )
217{
218 LIBRARY_MANAGER manager;
219
221 BOOST_REQUIRE( !result.has_value() );
222
223 LIBRARY_TABLE* table = result.value_or( nullptr );
224 BOOST_REQUIRE( table == nullptr );
225}
226
227
228BOOST_AUTO_TEST_CASE( NestedTablesDisabledHidden )
229{
230 // Test that disabled and hidden nested library table rows are parsed correctly
231 // This is a regression test for https://gitlab.com/kicad/code/kicad/-/issues/22784
232 // Note that full end-to-end testing requires the library manager to process the tables,
233 // but the parse test verifies the flag is correctly read from disk.
234
235 wxFileName fn( KI_TEST::GetTestDataRootDir(), wxEmptyString );
236 fn.AppendDir( "libraries" );
237
238 // Test with the disabled nested table
239 fn.SetName( "nested-disabled" );
240 LIBRARY_TABLE disabledTable( fn, LIBRARY_TABLE_SCOPE::GLOBAL );
241 BOOST_REQUIRE( disabledTable.IsOk() );
242 BOOST_REQUIRE_MESSAGE( disabledTable.Rows().size() == 4,
243 wxString::Format( "Expected 4 rows but got %zu",
244 disabledTable.Rows().size() ) );
245
246 // Verify the disabled flag is parsed correctly on the nested table row
247 bool foundDisabledRow = false;
248
249 for( const LIBRARY_TABLE_ROW& row : disabledTable.Rows() )
250 {
251 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
252 {
253 BOOST_REQUIRE_MESSAGE( row.Disabled(),
254 "Nested table row should have disabled flag set" );
255 foundDisabledRow = true;
256 }
257 }
258
259 BOOST_REQUIRE_MESSAGE( foundDisabledRow,
260 "Disabled nested table row not found in parsed table" );
261
262 // Test hidden nested table has same behavior
263 fn.SetName( "nested-hidden" );
265 BOOST_REQUIRE( hiddenTable.IsOk() );
266 BOOST_REQUIRE_MESSAGE( hiddenTable.Rows().size() == 4,
267 wxString::Format( "Expected 4 rows but got %zu",
268 hiddenTable.Rows().size() ) );
269
270 bool foundHiddenRow = false;
271
272 for( const LIBRARY_TABLE_ROW& row : hiddenTable.Rows() )
273 {
274 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
275 {
276 BOOST_REQUIRE_MESSAGE( row.Hidden(),
277 "Nested table row should have hidden flag set" );
278 foundHiddenRow = true;
279 }
280 }
281
282 BOOST_REQUIRE_MESSAGE( foundHiddenRow,
283 "Hidden nested table row not found in parsed table" );
284}
285
286
296BOOST_AUTO_TEST_CASE( InsertRowPreservesExistingRowPointers )
297{
298 LIBRARY_TABLE table( true, wxEmptyString, LIBRARY_TABLE_SCOPE::PROJECT );
300
301 // Seed with a few rows and snapshot pointers plus the expected URIs.
302 std::vector<const LIBRARY_TABLE_ROW*> seededPointers;
303 std::vector<wxString> seededUris;
304
305 for( int i = 0; i < 4; ++i )
306 {
307 LIBRARY_TABLE_ROW& row = table.InsertRow();
308 row.SetNickname( wxString::Format( wxS( "seed_%d" ), i ) );
309 row.SetURI( wxString::Format( wxS( "${KIPRJMOD}/libs/seed_%d.kicad_sym" ), i ) );
310 row.SetType( wxS( "KiCad" ) );
311
312 seededPointers.push_back( &row );
313 seededUris.push_back( row.URI() );
314 }
315
316 // Insert additional rows to force container growth that would reallocate
317 // a std::vector, and verify the seeded pointers continue to resolve to the
318 // same logical rows (same nickname and URI).
319 for( int i = 0; i < 64; ++i )
320 {
321 LIBRARY_TABLE_ROW& row = table.InsertRow();
322 row.SetNickname( wxString::Format( wxS( "extra_%d" ), i ) );
323 row.SetURI( wxString::Format( wxS( "${KIPRJMOD}/libs/extra_%d.kicad_sym" ), i ) );
324 row.SetType( wxS( "KiCad" ) );
325
326 for( size_t j = 0; j < seededPointers.size(); ++j )
327 {
328 BOOST_REQUIRE_MESSAGE(
329 seededPointers[j]->URI() == seededUris[j],
330 wxString::Format(
331 wxS( "Seed row %zu pointer was invalidated after inserting %d rows: "
332 "expected URI '%s', got '%s'" ),
333 j, i + 1, seededUris[j], seededPointers[j]->URI() ) );
334 }
335 }
336}
337
338
352BOOST_AUTO_TEST_CASE( IsPcmManagedRow_URITemplateMatching )
353{
354 struct CASE
355 {
356 wxString uri;
357 bool expectedPcmManaged;
358 wxString description;
359 };
360
361 std::vector<CASE> cases = {
362 { wxS( "${KICAD10_3RD_PARTY}/symbols/foo/foo.kicad_sym" ), true,
363 wxS( "Versioned 3RD_PARTY template should be recognised as PCM-managed" ) },
364 { wxS( "${KICAD9_3RD_PARTY}/symbols/legacy/legacy.kicad_sym" ), true,
365 wxS( "Legacy versioned 3RD_PARTY template should still match the wildcard" ) },
366 { wxS( "${KICAD10_3RD_PARTY}/footprints/bar/bar.pretty" ), true,
367 wxS( "Footprint library using 3RD_PARTY template should match" ) },
368 { wxS( "${KICAD10_3RD_PARTY}/design_blocks/baz/baz.kicad_blocks" ), true,
369 wxS( "Design block library using 3RD_PARTY template should match" ) },
370 { wxS( "${KICAD_USER_LIB}/symbols/test.kicad_sym" ), false,
371 wxS( "Row using a different env var must not be flagged as PCM-managed" ) },
372 { wxS( "${KIPRJMOD}/libs/local.kicad_sym" ), false,
373 wxS( "Project-relative row must not be flagged as PCM-managed" ) },
374 { wxS( "/abs/path/to/lib.kicad_sym" ), false,
375 wxS( "Absolute path row must not be flagged as PCM-managed" ) },
376 { wxS( "${}" ), false,
377 wxS( "Malformed empty var name must not match" ) },
378 { wxS( "${KICAD10_3RD_PARTY_EXTRA}/foo" ), false,
379 wxS( "Similar-but-different var name must not match" ) },
380 // Issue #23476: a user who repurposes KICADn_3RD_PARTY to point at their own
381 // library collection adds libraries directly under that root (no PCM category
382 // folder). Such rows must not be treated as PCM-managed or auto-remove deletes
383 // them.
384 { wxS( "${KICAD10_3RD_PARTY}/mylib.kicad_sym" ), false,
385 wxS( "User library directly under repurposed 3RD_PARTY root must not match" ) },
386 { wxS( "${KICAD10_3RD_PARTY}/MyLibs/mylib.kicad_sym" ), false,
387 wxS( "User library under a non-PCM subfolder of 3RD_PARTY must not match" ) },
388 { wxS( "${KICAD10_3RD_PARTY}/eagle/imported.pretty" ), false,
389 wxS( "User footprint library under a non-PCM subfolder must not match" ) },
390 { wxS( "${KICAD10_3RD_PARTY}/symbols" ), false,
391 wxS( "3RD_PARTY/symbols with no nested package must not match" ) },
392 { wxS( "${KICAD10_3RD_PARTY}/symbols/mylib.kicad_sym" ), false,
393 wxS( "User symbol library directly in symbols folder (no package level) must not match" ) },
394 { wxS( "${KICAD10_3RD_PARTY}symbols/foo/foo.kicad_sym" ), false,
395 wxS( "Missing separator after env var must not match" ) },
396 { wxS( "${KICAD10_3RD_PARTY}/design_blocks/baz/baz.kicad_dbl" ), false,
397 wxS( "Wrong design-block library extension must not match" ) },
398 { wxS( "${KICAD10_3RD_PARTY}/symbols/foo/foo.txt" ), false,
399 wxS( "Non-library file in PCM symbols tree must not match" ) },
400 { wxS( "${KICAD10_3RD_PARTY}\\symbols\\foo\\foo.kicad_sym" ), false,
401 wxS( "Backslash-separated URI is never emitted by PCM and must not match" ) },
402 { wxS( "${KICAD10_3RD_PARTY}/symbols//foo.kicad_sym" ), false,
403 wxS( "Empty package-id component must not match" ) },
404 { wxS( "${KICAD10_3RD_PARTY}/symbols/foo/.kicad_sym" ), false,
405 wxS( "Extension-only leaf with empty library stem must not match" ) },
406 };
407
408 for( const CASE& c : cases )
409 {
411 row.SetURI( c.uri );
412
414
415 BOOST_CHECK_MESSAGE(
416 actual == c.expectedPcmManaged,
417 wxString::Format( wxS( "%s: URI='%s' expected=%d actual=%d" ),
418 c.description, c.uri, c.expectedPcmManaged ? 1 : 0,
419 actual ? 1 : 0 ) );
420 }
421}
422
423
424BOOST_AUTO_TEST_CASE( ReadOnlyTable )
425{
426 // Create a temporary copy of a library table and make it read-only
427 wxFileName fn( KI_TEST::GetTestDataRootDir(), wxEmptyString );
428 fn.AppendDir( "libraries" );
429 fn.SetName( "sym-lib-table" );
430
431 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_test_ro_table" );
432 wxFileName tmpFn = tempDir.CreateChildFileStr( "sym-lib-table" );
433
434 wxCopyFile( fn.GetFullPath(), tmpFn.GetFullPath() );
435
436 // Verify a writable table is not read-only
437 {
438 LIBRARY_TABLE writableTable( tmpFn, LIBRARY_TABLE_SCOPE::GLOBAL );
439 BOOST_REQUIRE( writableTable.IsOk() );
440 BOOST_REQUIRE( !writableTable.IsReadOnly() );
441 }
442
443 // Make the file read-only
444 tmpFn.SetPermissions( wxS_IRUSR | wxS_IRGRP | wxS_IROTH );
445
446 // CI containers commonly run as root, where access(W_OK) succeeds regardless of the
447 // permission bits, so a read-only file still reports as writable. IsReadOnly() uses the
448 // same IsFileWritable() check, so when the file remains writable here the read-only
449 // assertions cannot hold and are not meaningful.
450 if( wxFileName( tmpFn.GetFullPath() ).IsFileWritable() )
451 {
452 BOOST_TEST_MESSAGE( "Skipping read-only table checks; file remains writable despite "
453 "read-only permissions (running as root?)" );
454 }
455 else
456 {
458 BOOST_REQUIRE( roTable.IsOk() );
459 BOOST_REQUIRE( roTable.IsReadOnly() );
460
461 // Save should return an error for read-only tables
462 LIBRARY_RESULT<void> result = roTable.Save();
463 BOOST_REQUIRE( !result.has_value() );
464 }
465
466 // Clean up
467 tmpFn.SetPermissions( wxS_IRUSR | wxS_IWUSR );
468 wxRemoveFile( tmpFn.GetFullPath() );
469}
470
471
482BOOST_AUTO_TEST_CASE( RowsAddedToANewProjectTableAreEnumerated )
483{
484 std::error_code ec;
485 std::filesystem::path dir =
486 std::filesystem::temp_directory_path( ec ) / std::filesystem::path( "kicad_qa_new_project_table" );
487
488 std::filesystem::remove_all( dir, ec );
489 std::filesystem::create_directories( dir, ec );
490
491 std::filesystem::path proPath = dir / "new_project.kicad_pro";
492
493 {
494 std::ofstream proFile( proPath );
495 proFile << R"({ "meta": { "filename": "new_project.kicad_pro", "version": 3 } })";
496 }
497
499 BOOST_REQUIRE( settings.LoadProject( wxString( proPath.string() ) ) );
500
501 wxFileName tableFile( wxString( ( dir / "sym-lib-table" ).string() ) );
502 BOOST_REQUIRE( !tableFile.FileExists() );
503
504 LIBRARY_MANAGER manager;
505
506 std::optional<LIBRARY_TABLE*> optTable = manager.Table( LIBRARY_TABLE_TYPE::SYMBOL, LIBRARY_TABLE_SCOPE::PROJECT );
507 BOOST_REQUIRE( optTable.has_value() );
508
509 LIBRARY_TABLE* table = optTable.value();
510 LIBRARY_TABLE_ROW& row = table->InsertRow();
511
512 row.SetNickname( wxS( "NewLib" ) );
513 row.SetURI( wxS( "${KIPRJMOD}/NewLib.kicad_sym" ) );
514 row.SetType( wxS( "KiCad" ) );
515
516 auto projectRowCount = [&]() -> size_t
517 {
519 };
520
521 BOOST_CHECK_MESSAGE( projectRowCount() == 1,
522 "A library added to a project with no library table must be visible to "
523 "Rows(), which is what fills the library tree" );
524
525 BOOST_REQUIRE( table->Save().has_value() );
526 BOOST_CHECK_EQUAL( projectRowCount(), 1 );
527
528 settings.UnloadProject( &settings.Prj(), false );
529 std::filesystem::remove_all( dir, ec );
530}
531
532
533BOOST_AUTO_TEST_CASE( LibOverrideSettings )
534{
535 // Test that LIB_OVERRIDE serialization in KICAD_SETTINGS works via the
536 // LIBRARY_MANAGER override API.
537 LIBRARY_MANAGER manager;
538
539 wxString tablePath = wxT( "/some/read-only/path/sym-lib-table" );
540 wxString nickname1 = wxT( "LibA" );
541 wxString nickname2 = wxT( "LibB" );
542
543 // Set an override
544 manager.SetLibOverride( tablePath, nickname1, true, false );
545 manager.SetLibOverride( tablePath, nickname2, false, true );
546
547 // Verify overrides via settings
549 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
550
551 BOOST_REQUIRE( settings != nullptr );
552 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 1 );
553 BOOST_REQUIRE( settings->m_LibOverrides[tablePath].count( nickname1 ) == 1 );
554 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname1].disabled == true );
555 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname1].hidden == false );
556 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname2].disabled == false );
557 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname2].hidden == true );
558
559 // Clear override (both disabled and hidden are false)
560 manager.ClearLibOverride( tablePath, nickname1 );
561 BOOST_REQUIRE( settings->m_LibOverrides[tablePath].count( nickname1 ) == 0 );
562
563 // Clear last entry should remove the table key too
564 manager.ClearLibOverride( tablePath, nickname2 );
565 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 0 );
566
567 // SetLibOverride with both false should also clear
568 manager.SetLibOverride( tablePath, nickname1, true, false );
569 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 1 );
570 manager.SetLibOverride( tablePath, nickname1, false, false );
571 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 0 );
572}
573
574
581BOOST_AUTO_TEST_CASE( CreateGlobalTableEmptyWhenNoStockTable )
582{
584
585 if( stockPath.IsFileReadable() )
586 {
587 BOOST_TEST_MESSAGE( "Skipping: stock design-block-lib-table exists; test only applies when "
588 "no stock table is installed" );
589 return;
590 }
591
592 const wxString tablePath =
594
595 // RAII guard: restore the global table file to its original state after the test.
596 struct FILE_RESTORE
597 {
598 wxString path;
599 bool existed = false;
600 wxString contents;
601
602 explicit FILE_RESTORE( const wxString& aPath ) : path( aPath )
603 {
604 existed = wxFileName::FileExists( path );
605
606 if( existed )
607 {
608 wxFFile in( path, wxT( "rb" ) );
609 in.ReadAll( &contents );
610 }
611 }
612
613 ~FILE_RESTORE()
614 {
615 if( existed )
616 {
617 wxFFile out( path, wxT( "wb" ) );
618 out.Write( contents );
619 }
620 else if( wxFileName::FileExists( path ) )
621 {
622 wxRemoveFile( path );
623 }
624 }
625 } restore( tablePath );
626
628
629 LIBRARY_TABLE reloaded( wxFileName( tablePath ), LIBRARY_TABLE_SCOPE::GLOBAL );
630
631 BOOST_REQUIRE( reloaded.IsOk() );
632 BOOST_CHECK_MESSAGE( reloaded.Rows().empty(),
633 "CreateGlobalTable must not write a dangling row when the stock table is absent" );
634}
635
636
643BOOST_AUTO_TEST_CASE( StockTableReferenceURIHonorsExternalDefinition )
644{
645 const wxString templateVar = ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) );
646
647 // Preserve and restore the original entry so neighbouring tests are unaffected.
648 KI_TEST::SCOPED_PGM_ENV_VAR envVarGuard( templateVar, wxEmptyString );
649
652 {
653 ENV_VAR_ITEM& entry = envVarGuard.GetItem();
654
655 entry.SetDefinedExternally( false );
658
659 entry.SetDefinedExternally( true );
662 }
663}
664
665
667static LIBRARY_TABLE makeImportedSymbolTable( const std::vector<std::pair<wxString, wxString>>& aUserRows )
668{
669 LIBRARY_TABLE table( true, wxEmptyString, LIBRARY_TABLE_SCOPE::GLOBAL );
671
672 for( const auto& [nickname, uri] : aUserRows )
673 {
674 LIBRARY_TABLE_ROW& row = table.InsertRow();
675 row.SetNickname( nickname );
676 row.SetURI( uri );
677 row.SetType( wxS( "KiCad" ) );
678 }
679
680 return table;
681}
682
683
684static size_t countChainedKiCadRows( const LIBRARY_TABLE& aTable )
685{
686 return std::ranges::count_if( aTable.Rows(),
687 []( const LIBRARY_TABLE_ROW& aRow )
688 {
689 return aRow.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME
690 && aRow.Nickname() == wxS( "KiCad" );
691 } );
692}
693
694
700BOOST_AUTO_TEST_CASE( MigrateBuiltInLibraries_NoStockRefsAddsNothing )
701{
702 const wxString stockPath = wxS( "${KICAD10_SYMBOL_DIR}/sym-lib-table" );
703
705 { wxS( "MyParts" ), wxS( "${KIPRJMOD}/../libs/MyParts.kicad_sym" ) },
706 { wxS( "MyPassives" ), wxS( "/home/user/kicad/MyPassives.kicad_sym" ) },
707 } );
708
709 const size_t rowsBefore = table.Rows().size();
710
712 table, LIBRARY_TABLE_TYPE::SYMBOL, stockPath, true );
713
714 BOOST_CHECK_MESSAGE( !modified, "Table with no stock references should not be modified" );
715 BOOST_CHECK_EQUAL( table.Rows().size(), rowsBefore );
717}
718
719
721BOOST_AUTO_TEST_CASE( MigrateBuiltInLibraries_DirectStockRowsBecomeChained )
722{
723 const wxString stockPath = wxS( "${KICAD10_SYMBOL_DIR}/sym-lib-table" );
724
726 { wxS( "Device" ), wxS( "${KICAD9_SYMBOL_DIR}/Device.kicad_sym" ) },
727 { wxS( "MyParts" ), wxS( "${KIPRJMOD}/../libs/MyParts.kicad_sym" ) },
728 } );
729
731 table, LIBRARY_TABLE_TYPE::SYMBOL, stockPath, true );
732
733 BOOST_CHECK( modified );
735
736 // The user's own row must survive and the direct stock row must be gone.
737 BOOST_CHECK( table.Row( wxS( "MyParts" ) ).has_value() );
738 BOOST_CHECK( !table.Row( wxS( "Device" ) ).has_value() );
739}
740
741
743BOOST_AUTO_TEST_CASE( MigrateBuiltInLibraries_ChainedRowMigratedInPlace )
744{
745 const wxString stockPath = wxS( "${KICAD10_SYMBOL_DIR}/sym-lib-table" );
746
747 LIBRARY_TABLE table( true, wxEmptyString, LIBRARY_TABLE_SCOPE::GLOBAL );
749
750 LIBRARY_TABLE_ROW& chained = table.InsertRow();
752 chained.SetNickname( wxS( "KiCad" ) );
753 chained.SetURI( wxS( "${KICAD9_SYMBOL_DIR}/sym-lib-table" ) );
754
755 LIBRARY_TABLE_ROW& mine = table.InsertRow();
756 mine.SetNickname( wxS( "MyParts" ) );
757 mine.SetURI( wxS( "${KIPRJMOD}/../libs/MyParts.kicad_sym" ) );
758 mine.SetType( wxS( "KiCad" ) );
759
761 table, LIBRARY_TABLE_TYPE::SYMBOL, stockPath, true );
762
763 BOOST_CHECK( modified );
765
766 auto migrated = table.Row( wxS( "KiCad" ) );
767 BOOST_REQUIRE( migrated.has_value() );
768 BOOST_CHECK_EQUAL( ( *migrated )->URI(), stockPath );
769}
770
771
781BOOST_AUTO_TEST_CASE( QuotedTextRoundTripsEscapes )
782{
784
785 const std::string escaped =
786 "(sym_lib_table (lib (name \"x\") (descr \"say \\\"hi\\\"\\\\line1\\nline2\\r\")))";
787
788 tl::expected<LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR> result = parser.ParseBuffer( escaped );
789 BOOST_REQUIRE( result.has_value() );
790 BOOST_REQUIRE_EQUAL( result->rows.size(), 1 );
791 BOOST_CHECK_EQUAL( result->rows[0].description, "say \"hi\"\\line1\nline2\r" );
792
793 // Round-trip: format the parsed value back out and parse it again.
794 LIBRARY_TABLE table( true, wxEmptyString, LIBRARY_TABLE_SCOPE::GLOBAL );
796
797 LIBRARY_TABLE_ROW& row = table.InsertRow();
798 row.SetNickname( wxS( "x" ) );
799 row.SetType( wxS( "KiCad" ) );
800 row.SetURI( wxS( "${KIPRJMOD}/x.kicad_sym" ) );
801 row.SetDescription( wxS( "say \"hi\"\\line1\nline2\r" ) );
802
803 STRING_FORMATTER formatter;
804 table.Format( &formatter );
805
806 tl::expected<LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR> reparsed =
807 parser.ParseBuffer( formatter.GetString() );
808 BOOST_REQUIRE_MESSAGE( reparsed.has_value(),
809 "a formatted table containing escaped quotes/newlines must re-parse" );
810
811 if( reparsed.has_value() && !reparsed->rows.empty() )
812 BOOST_CHECK_EQUAL( reparsed->rows[0].description, row.Description() );
813}
814
815
KiCad uses environment variables internally for determining the base paths for libraries,...
void SetDefinedExternally(bool aIsDefinedExternally=true)
std::map< wxString, std::map< wxString, LIB_OVERRIDE > > m_LibOverrides
Overrides for libraries in read-only nested tables.
RAII helper that sets a KiCad program-level environment variable for the lifetime of the object.
ENV_VAR_ITEM & GetItem()
Get the wrapped environment variable item.
wxString PathStr() const
Get the path to the temporary directory as a wxString.
Definition file_utils.h:62
wxString CreateChildFileStr(const wxString &aName) const
Create and return the path to a direct child file as a wxString.
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
Definition file_utils.h:59
static wxString StockTableTokenizedURI(LIBRARY_TABLE_TYPE aType)
void ClearLibOverride(const wxString &aTablePath, const wxString &aNickname)
Removes any override for a library that no longer needs one.
std::optional< LIBRARY_TABLE * > Table(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
Retrieves a given table; creating a new empty project table if a valid project is loaded and the give...
static wxString DefaultGlobalTablePath(LIBRARY_TABLE_TYPE aType)
void SetLibOverride(const wxString &aTablePath, const wxString &aNickname, bool aDisabled, bool aHidden)
Set a user override for a library in a read-only nested table.
static bool CreateGlobalTable(LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries)
static bool IsPcmManagedRow(const LIBRARY_TABLE_ROW &aRow)
Return true if a library table row was added by the Plugin and Content Manager.
std::vector< LIBRARY_TABLE_ROW * > Rows(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH, bool aIncludeInvalid=false) const
Returns a flattened list of libraries of the given type.
void LoadGlobalTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the global library tables in the given list, or all tables if no list is given
static wxString StockTableReferenceURI(LIBRARY_TABLE_TYPE aType)
static wxString StockTablePath(LIBRARY_TABLE_TYPE aType)
tl::expected< LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR > ParseBuffer(const std::string &aBuffer)
tl::expected< LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR > Parse(const std::filesystem::path &aPath)
void SetNickname(const wxString &aNickname)
void SetType(const wxString &aType)
void SetDescription(const wxString &aDescription)
static const wxString TABLE_TYPE_NAME
void SetURI(const wxString &aUri)
const wxString & Description() const
const wxString & URI() const
LIBRARY_RESULT< void > Save()
bool IsReadOnly() const
Returns true if the underlying file exists but is not writable.
const std::deque< LIBRARY_TABLE_ROW > & Rows() const
bool IsOk() const
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
RAII class that sets an value at construction and resets it to the original value at destruction.
T * GetAppSettings(const char *aFilename)
Return a handle to the a given settings by type.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
static KICOMMON_API bool MigrateBuiltInLibraries(LIBRARY_TABLE &aTable, LIBRARY_TABLE_TYPE aType, const wxString &aStockPath, bool aStockPathValid)
Migrates built-in (stock) library references in an imported global library table to the current versi...
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:430
std::string & MutableString()
Definition richio.h:458
const std::string & GetString()
Definition richio.h:453
Functions related to environment variables, including help functions.
tl::expected< ResultType, LIBRARY_ERROR > LIBRARY_RESULT
LIBRARY_TABLE_TYPE
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:78
void Prettify(std::string &aSource, FORMAT_MODE aMode)
Pretty-prints s-expression text according to KiCad format rules.
std::string GetTestDataRootDir()
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
static size_t countChainedKiCadRows(const LIBRARY_TABLE &aTable)
BOOST_AUTO_TEST_CASE(Grammar)
static LIBRARY_TABLE makeImportedSymbolTable(const std::vector< std::pair< wxString, wxString > > &aUserRows)
Builds an in-memory symbol library table seeded with the given user rows.
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
int actual
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")