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 <utility>
25#include <vector>
26
27#include <wx/ffile.h>
28
29#include <mock_pgm_base.h>
30#include <richio.h>
32#include <qa_utils/file_utils.h>
35#include <pegtl/contrib/analyze.hpp>
36
37#include <env_vars.h>
38#include <pgm_base.h>
46
47
48BOOST_AUTO_TEST_SUITE( LibraryTables )
49
50
52{
53 BOOST_REQUIRE( tao::pegtl::analyze< LIBRARY_TABLE_GRAMMAR::LIB_TABLE_FILE >( 1 ) == 0 );
54}
55
56
58{
60 tl::expected<LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR> result = parser.ParseBuffer( "" );
61 BOOST_REQUIRE( !result.has_value() );
62}
63
64
65BOOST_AUTO_TEST_CASE( ParseFromFile )
66{
67 std::vector<std::string> cases = {
68 "sym-lib-table",
69 "fp-lib-table"
70 };
71
72 std::filesystem::path p( KI_TEST::GetTestDataRootDir() );
73 p.append( "libraries/" );
74
76
77 for( const std::string& path : cases )
78 {
79 p.remove_filename();
80 p.append( path );
81
82 auto result = parser.Parse( p );
83
84 BOOST_REQUIRE( result.has_value() );
85 }
86}
87
88
89BOOST_AUTO_TEST_CASE( ParseAndConstruct )
90{
91 struct TESTCASE
92 {
93 wxString filename;
94 wxString expected_error;
95 size_t expected_rows;
96 bool check_formatted = true;
97 };
98
99 std::vector<TESTCASE> cases = {
100 { .filename = "sym-lib-table", .expected_rows = 224 },
101 { .filename = "fp-lib-table", .expected_rows = 146 },
102 { .filename = "nested-symbols", .expected_rows = 6 },
103 { .filename = "nested-disabled", .expected_rows = 4 },
104 { .filename = "nested-hidden", .expected_rows = 4 },
105 { .filename = "cycle", .expected_rows = 2 },
106 { .filename = "sym-hand-edited", .expected_rows = 2, .check_formatted = false },
107 { .filename = "corrupted", .expected_error = "Syntax error at line 6, column 9" },
108 { .filename = "truncated", .expected_error = "Syntax error at line 11, column 1" }
109 };
110
111 wxFileName fn( KI_TEST::GetTestDataRootDir(), wxEmptyString );
112 fn.AppendDir( "libraries" );
113
114 for( const auto& [filename, expected_error, expected_rows, check_formatted] : cases )
115 {
116 BOOST_TEST_CONTEXT( filename )
117 {
118 fn.SetName( filename );
120
121 BOOST_REQUIRE( table.IsOk() == ( expected_error.IsEmpty() ) );
122
123 BOOST_REQUIRE_MESSAGE( table.Rows().size() == expected_rows,
124 wxString::Format( "Expected %zu rows but got %zu",
125 expected_rows, table.Rows().size() ) );
126
127 BOOST_REQUIRE_MESSAGE( table.ErrorDescription() == expected_error,
128 wxString::Format( "Expected error '%s' but got '%s'",
129 expected_error, table.ErrorDescription() ) );
130
131 // Non-parsed tables can't be formatted
132 if( !table.IsOk() || !check_formatted )
133 continue;
134
135 std::ifstream inFp;
136 inFp.open( fn.GetFullPath().fn_str() );
137 BOOST_REQUIRE( inFp.is_open() );
138
139 std::stringstream inBuf;
140 inBuf << inFp.rdbuf();
141 std::string inData = inBuf.str();
142
143 STRING_FORMATTER formatter;
144 table.Format( &formatter );
146 KICAD_FORMAT::FORMAT_MODE::LIBRARY_TABLE );
147
148 if( formatter.GetString().compare( inData ) != 0 )
149 {
150 BOOST_TEST_MESSAGE( "--- original ---" );
151 BOOST_TEST_MESSAGE( inData );
152 BOOST_TEST_MESSAGE( "--- formatted ---" );
153 BOOST_TEST_MESSAGE( formatter.GetString() );
154 }
155
156 BOOST_REQUIRE( formatter.GetString().compare( inData ) == 0 );
157 }
158 }
159}
160
162{
163 LIBRARY_MANAGER manager;
164 manager.LoadGlobalTables();
165
166 BOOST_REQUIRE( manager.Rows( LIBRARY_TABLE_TYPE::SYMBOL ).size() == 3 );
167 BOOST_REQUIRE( manager.Rows( LIBRARY_TABLE_TYPE::FOOTPRINT ).size() == 146 );
168}
169
170
171// Regression coverage for the LoadGlobalTables guard: when a global library
172// table file does not exist on disk, loadTables() never inserts an entry for
173// that type into m_tables, so Table( aType, GLOBAL ) must return std::nullopt
174// instead of an entry holding a null pointer. The cleanupRemovedPCMLibraries
175// lambda inside LoadGlobalTables relies on this contract via .value_or(
176// nullptr ) and must not dereference the result. Exercising the full PCM
177// cleanup path requires extensive environment setup (3RD_PARTY env var plus
178// PCM auto-remove enabled in user settings), so we test the underlying
179// contract directly here.
180BOOST_AUTO_TEST_CASE( ManagerTableReturnsNulloptForUnloadedType )
181{
182 LIBRARY_MANAGER manager;
183
185 BOOST_REQUIRE( !result.has_value() );
186
187 LIBRARY_TABLE* table = result.value_or( nullptr );
188 BOOST_REQUIRE( table == nullptr );
189}
190
191
192BOOST_AUTO_TEST_CASE( NestedTablesDisabledHidden )
193{
194 // Test that disabled and hidden nested library table rows are parsed correctly
195 // This is a regression test for https://gitlab.com/kicad/code/kicad/-/issues/22784
196 // Note that full end-to-end testing requires the library manager to process the tables,
197 // but the parse test verifies the flag is correctly read from disk.
198
199 wxFileName fn( KI_TEST::GetTestDataRootDir(), wxEmptyString );
200 fn.AppendDir( "libraries" );
201
202 // Test with the disabled nested table
203 fn.SetName( "nested-disabled" );
204 LIBRARY_TABLE disabledTable( fn, LIBRARY_TABLE_SCOPE::GLOBAL );
205 BOOST_REQUIRE( disabledTable.IsOk() );
206 BOOST_REQUIRE_MESSAGE( disabledTable.Rows().size() == 4,
207 wxString::Format( "Expected 4 rows but got %zu",
208 disabledTable.Rows().size() ) );
209
210 // Verify the disabled flag is parsed correctly on the nested table row
211 bool foundDisabledRow = false;
212
213 for( const LIBRARY_TABLE_ROW& row : disabledTable.Rows() )
214 {
215 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
216 {
217 BOOST_REQUIRE_MESSAGE( row.Disabled(),
218 "Nested table row should have disabled flag set" );
219 foundDisabledRow = true;
220 }
221 }
222
223 BOOST_REQUIRE_MESSAGE( foundDisabledRow,
224 "Disabled nested table row not found in parsed table" );
225
226 // Test hidden nested table has same behavior
227 fn.SetName( "nested-hidden" );
229 BOOST_REQUIRE( hiddenTable.IsOk() );
230 BOOST_REQUIRE_MESSAGE( hiddenTable.Rows().size() == 4,
231 wxString::Format( "Expected 4 rows but got %zu",
232 hiddenTable.Rows().size() ) );
233
234 bool foundHiddenRow = false;
235
236 for( const LIBRARY_TABLE_ROW& row : hiddenTable.Rows() )
237 {
238 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
239 {
240 BOOST_REQUIRE_MESSAGE( row.Hidden(),
241 "Nested table row should have hidden flag set" );
242 foundHiddenRow = true;
243 }
244 }
245
246 BOOST_REQUIRE_MESSAGE( foundHiddenRow,
247 "Hidden nested table row not found in parsed table" );
248}
249
250
260BOOST_AUTO_TEST_CASE( InsertRowPreservesExistingRowPointers )
261{
262 LIBRARY_TABLE table( true, wxEmptyString, LIBRARY_TABLE_SCOPE::PROJECT );
264
265 // Seed with a few rows and snapshot pointers plus the expected URIs.
266 std::vector<const LIBRARY_TABLE_ROW*> seededPointers;
267 std::vector<wxString> seededUris;
268
269 for( int i = 0; i < 4; ++i )
270 {
271 LIBRARY_TABLE_ROW& row = table.InsertRow();
272 row.SetNickname( wxString::Format( wxS( "seed_%d" ), i ) );
273 row.SetURI( wxString::Format( wxS( "${KIPRJMOD}/libs/seed_%d.kicad_sym" ), i ) );
274 row.SetType( wxS( "KiCad" ) );
275
276 seededPointers.push_back( &row );
277 seededUris.push_back( row.URI() );
278 }
279
280 // Insert additional rows to force container growth that would reallocate
281 // a std::vector, and verify the seeded pointers continue to resolve to the
282 // same logical rows (same nickname and URI).
283 for( int i = 0; i < 64; ++i )
284 {
285 LIBRARY_TABLE_ROW& row = table.InsertRow();
286 row.SetNickname( wxString::Format( wxS( "extra_%d" ), i ) );
287 row.SetURI( wxString::Format( wxS( "${KIPRJMOD}/libs/extra_%d.kicad_sym" ), i ) );
288 row.SetType( wxS( "KiCad" ) );
289
290 for( size_t j = 0; j < seededPointers.size(); ++j )
291 {
292 BOOST_REQUIRE_MESSAGE(
293 seededPointers[j]->URI() == seededUris[j],
294 wxString::Format(
295 wxS( "Seed row %zu pointer was invalidated after inserting %d rows: "
296 "expected URI '%s', got '%s'" ),
297 j, i + 1, seededUris[j], seededPointers[j]->URI() ) );
298 }
299 }
300}
301
302
316BOOST_AUTO_TEST_CASE( IsPcmManagedRow_URITemplateMatching )
317{
318 struct CASE
319 {
320 wxString uri;
321 bool expectedPcmManaged;
322 wxString description;
323 };
324
325 std::vector<CASE> cases = {
326 { wxS( "${KICAD10_3RD_PARTY}/symbols/foo/foo.kicad_sym" ), true,
327 wxS( "Versioned 3RD_PARTY template should be recognised as PCM-managed" ) },
328 { wxS( "${KICAD9_3RD_PARTY}/symbols/legacy/legacy.kicad_sym" ), true,
329 wxS( "Legacy versioned 3RD_PARTY template should still match the wildcard" ) },
330 { wxS( "${KICAD10_3RD_PARTY}/footprints/bar/bar.pretty" ), true,
331 wxS( "Footprint library using 3RD_PARTY template should match" ) },
332 { wxS( "${KICAD10_3RD_PARTY}/design_blocks/baz/baz.kicad_blocks" ), true,
333 wxS( "Design block library using 3RD_PARTY template should match" ) },
334 { wxS( "${KICAD_USER_LIB}/symbols/test.kicad_sym" ), false,
335 wxS( "Row using a different env var must not be flagged as PCM-managed" ) },
336 { wxS( "${KIPRJMOD}/libs/local.kicad_sym" ), false,
337 wxS( "Project-relative row must not be flagged as PCM-managed" ) },
338 { wxS( "/abs/path/to/lib.kicad_sym" ), false,
339 wxS( "Absolute path row must not be flagged as PCM-managed" ) },
340 { wxS( "${}" ), false,
341 wxS( "Malformed empty var name must not match" ) },
342 { wxS( "${KICAD10_3RD_PARTY_EXTRA}/foo" ), false,
343 wxS( "Similar-but-different var name must not match" ) },
344 // Issue #23476: a user who repurposes KICADn_3RD_PARTY to point at their own
345 // library collection adds libraries directly under that root (no PCM category
346 // folder). Such rows must not be treated as PCM-managed or auto-remove deletes
347 // them.
348 { wxS( "${KICAD10_3RD_PARTY}/mylib.kicad_sym" ), false,
349 wxS( "User library directly under repurposed 3RD_PARTY root must not match" ) },
350 { wxS( "${KICAD10_3RD_PARTY}/MyLibs/mylib.kicad_sym" ), false,
351 wxS( "User library under a non-PCM subfolder of 3RD_PARTY must not match" ) },
352 { wxS( "${KICAD10_3RD_PARTY}/eagle/imported.pretty" ), false,
353 wxS( "User footprint library under a non-PCM subfolder must not match" ) },
354 { wxS( "${KICAD10_3RD_PARTY}/symbols" ), false,
355 wxS( "3RD_PARTY/symbols with no nested package must not match" ) },
356 { wxS( "${KICAD10_3RD_PARTY}/symbols/mylib.kicad_sym" ), false,
357 wxS( "User symbol library directly in symbols folder (no package level) must not match" ) },
358 { wxS( "${KICAD10_3RD_PARTY}symbols/foo/foo.kicad_sym" ), false,
359 wxS( "Missing separator after env var must not match" ) },
360 { wxS( "${KICAD10_3RD_PARTY}/design_blocks/baz/baz.kicad_dbl" ), false,
361 wxS( "Wrong design-block library extension must not match" ) },
362 { wxS( "${KICAD10_3RD_PARTY}/symbols/foo/foo.txt" ), false,
363 wxS( "Non-library file in PCM symbols tree must not match" ) },
364 { wxS( "${KICAD10_3RD_PARTY}\\symbols\\foo\\foo.kicad_sym" ), false,
365 wxS( "Backslash-separated URI is never emitted by PCM and must not match" ) },
366 { wxS( "${KICAD10_3RD_PARTY}/symbols//foo.kicad_sym" ), false,
367 wxS( "Empty package-id component must not match" ) },
368 { wxS( "${KICAD10_3RD_PARTY}/symbols/foo/.kicad_sym" ), false,
369 wxS( "Extension-only leaf with empty library stem must not match" ) },
370 };
371
372 for( const CASE& c : cases )
373 {
375 row.SetURI( c.uri );
376
378
379 BOOST_CHECK_MESSAGE(
380 actual == c.expectedPcmManaged,
381 wxString::Format( wxS( "%s: URI='%s' expected=%d actual=%d" ),
382 c.description, c.uri, c.expectedPcmManaged ? 1 : 0,
383 actual ? 1 : 0 ) );
384 }
385}
386
387
388BOOST_AUTO_TEST_CASE( ReadOnlyTable )
389{
390 // Create a temporary copy of a library table and make it read-only
391 wxFileName fn( KI_TEST::GetTestDataRootDir(), wxEmptyString );
392 fn.AppendDir( "libraries" );
393 fn.SetName( "sym-lib-table" );
394
395 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_test_ro_table" );
396 wxFileName tmpFn = tempDir.CreateChildFileStr( "sym-lib-table" );
397
398 wxCopyFile( fn.GetFullPath(), tmpFn.GetFullPath() );
399
400 // Verify a writable table is not read-only
401 {
402 LIBRARY_TABLE writableTable( tmpFn, LIBRARY_TABLE_SCOPE::GLOBAL );
403 BOOST_REQUIRE( writableTable.IsOk() );
404 BOOST_REQUIRE( !writableTable.IsReadOnly() );
405 }
406
407 // Make the file read-only
408 tmpFn.SetPermissions( wxS_IRUSR | wxS_IRGRP | wxS_IROTH );
409
410 // CI containers commonly run as root, where access(W_OK) succeeds regardless of the
411 // permission bits, so a read-only file still reports as writable. IsReadOnly() uses the
412 // same IsFileWritable() check, so when the file remains writable here the read-only
413 // assertions cannot hold and are not meaningful.
414 if( wxFileName( tmpFn.GetFullPath() ).IsFileWritable() )
415 {
416 BOOST_TEST_MESSAGE( "Skipping read-only table checks; file remains writable despite "
417 "read-only permissions (running as root?)" );
418 }
419 else
420 {
422 BOOST_REQUIRE( roTable.IsOk() );
423 BOOST_REQUIRE( roTable.IsReadOnly() );
424
425 // Save should return an error for read-only tables
426 LIBRARY_RESULT<void> result = roTable.Save();
427 BOOST_REQUIRE( !result.has_value() );
428 }
429
430 // Clean up
431 tmpFn.SetPermissions( wxS_IRUSR | wxS_IWUSR );
432 wxRemoveFile( tmpFn.GetFullPath() );
433}
434
435
446BOOST_AUTO_TEST_CASE( RowsAddedToANewProjectTableAreEnumerated )
447{
448 std::error_code ec;
449 std::filesystem::path dir =
450 std::filesystem::temp_directory_path( ec ) / std::filesystem::path( "kicad_qa_new_project_table" );
451
452 std::filesystem::remove_all( dir, ec );
453 std::filesystem::create_directories( dir, ec );
454
455 std::filesystem::path proPath = dir / "new_project.kicad_pro";
456
457 {
458 std::ofstream proFile( proPath );
459 proFile << R"({ "meta": { "filename": "new_project.kicad_pro", "version": 3 } })";
460 }
461
463 BOOST_REQUIRE( settings.LoadProject( wxString( proPath.string() ) ) );
464
465 wxFileName tableFile( wxString( ( dir / "sym-lib-table" ).string() ) );
466 BOOST_REQUIRE( !tableFile.FileExists() );
467
468 LIBRARY_MANAGER manager;
469
470 std::optional<LIBRARY_TABLE*> optTable = manager.Table( LIBRARY_TABLE_TYPE::SYMBOL, LIBRARY_TABLE_SCOPE::PROJECT );
471 BOOST_REQUIRE( optTable.has_value() );
472
473 LIBRARY_TABLE* table = optTable.value();
474 LIBRARY_TABLE_ROW& row = table->InsertRow();
475
476 row.SetNickname( wxS( "NewLib" ) );
477 row.SetURI( wxS( "${KIPRJMOD}/NewLib.kicad_sym" ) );
478 row.SetType( wxS( "KiCad" ) );
479
480 auto projectRowCount = [&]() -> size_t
481 {
483 };
484
485 BOOST_CHECK_MESSAGE( projectRowCount() == 1,
486 "A library added to a project with no library table must be visible to "
487 "Rows(), which is what fills the library tree" );
488
489 BOOST_REQUIRE( table->Save().has_value() );
490 BOOST_CHECK_EQUAL( projectRowCount(), 1 );
491
492 settings.UnloadProject( &settings.Prj(), false );
493 std::filesystem::remove_all( dir, ec );
494}
495
496
497BOOST_AUTO_TEST_CASE( LibOverrideSettings )
498{
499 // Test that LIB_OVERRIDE serialization in KICAD_SETTINGS works via the
500 // LIBRARY_MANAGER override API.
501 LIBRARY_MANAGER manager;
502
503 wxString tablePath = wxT( "/some/read-only/path/sym-lib-table" );
504 wxString nickname1 = wxT( "LibA" );
505 wxString nickname2 = wxT( "LibB" );
506
507 // Set an override
508 manager.SetLibOverride( tablePath, nickname1, true, false );
509 manager.SetLibOverride( tablePath, nickname2, false, true );
510
511 // Verify overrides via settings
513 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
514
515 BOOST_REQUIRE( settings != nullptr );
516 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 1 );
517 BOOST_REQUIRE( settings->m_LibOverrides[tablePath].count( nickname1 ) == 1 );
518 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname1].disabled == true );
519 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname1].hidden == false );
520 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname2].disabled == false );
521 BOOST_REQUIRE( settings->m_LibOverrides[tablePath][nickname2].hidden == true );
522
523 // Clear override (both disabled and hidden are false)
524 manager.ClearLibOverride( tablePath, nickname1 );
525 BOOST_REQUIRE( settings->m_LibOverrides[tablePath].count( nickname1 ) == 0 );
526
527 // Clear last entry should remove the table key too
528 manager.ClearLibOverride( tablePath, nickname2 );
529 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 0 );
530
531 // SetLibOverride with both false should also clear
532 manager.SetLibOverride( tablePath, nickname1, true, false );
533 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 1 );
534 manager.SetLibOverride( tablePath, nickname1, false, false );
535 BOOST_REQUIRE( settings->m_LibOverrides.count( tablePath ) == 0 );
536}
537
538
545BOOST_AUTO_TEST_CASE( CreateGlobalTableEmptyWhenNoStockTable )
546{
548
549 if( stockPath.IsFileReadable() )
550 {
551 BOOST_TEST_MESSAGE( "Skipping: stock design-block-lib-table exists; test only applies when "
552 "no stock table is installed" );
553 return;
554 }
555
556 const wxString tablePath =
558
559 // RAII guard: restore the global table file to its original state after the test.
560 struct FILE_RESTORE
561 {
562 wxString path;
563 bool existed = false;
564 wxString contents;
565
566 explicit FILE_RESTORE( const wxString& aPath ) : path( aPath )
567 {
568 existed = wxFileName::FileExists( path );
569
570 if( existed )
571 {
572 wxFFile in( path, wxT( "rb" ) );
573 in.ReadAll( &contents );
574 }
575 }
576
577 ~FILE_RESTORE()
578 {
579 if( existed )
580 {
581 wxFFile out( path, wxT( "wb" ) );
582 out.Write( contents );
583 }
584 else if( wxFileName::FileExists( path ) )
585 {
586 wxRemoveFile( path );
587 }
588 }
589 } restore( tablePath );
590
592
593 LIBRARY_TABLE reloaded( wxFileName( tablePath ), LIBRARY_TABLE_SCOPE::GLOBAL );
594
595 BOOST_REQUIRE( reloaded.IsOk() );
596 BOOST_CHECK_MESSAGE( reloaded.Rows().empty(),
597 "CreateGlobalTable must not write a dangling row when the stock table is absent" );
598}
599
600
607BOOST_AUTO_TEST_CASE( StockTableReferenceURIHonorsExternalDefinition )
608{
609 const wxString templateVar = ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) );
611
612 BOOST_REQUIRE( common != nullptr );
613
614 ENV_VAR_MAP& vars = common->m_Env.vars;
615
616 // Preserve and restore the original entry so neighbouring tests are unaffected.
617 const bool hadEntry = vars.count( templateVar ) > 0;
618 const ENV_VAR_ITEM savedEntry = hadEntry ? vars[templateVar] : ENV_VAR_ITEM();
619
622 {
623 ENV_VAR_ITEM& entry = vars[templateVar];
624
625 entry.SetDefinedExternally( false );
628
629 entry.SetDefinedExternally( true );
632 }
633
634 if( hadEntry )
635 vars[templateVar] = savedEntry;
636 else
637 vars.erase( templateVar );
638}
639
640
642static LIBRARY_TABLE makeImportedSymbolTable( const std::vector<std::pair<wxString, wxString>>& aUserRows )
643{
644 LIBRARY_TABLE table( true, wxEmptyString, LIBRARY_TABLE_SCOPE::GLOBAL );
646
647 for( const auto& [nickname, uri] : aUserRows )
648 {
649 LIBRARY_TABLE_ROW& row = table.InsertRow();
650 row.SetNickname( nickname );
651 row.SetURI( uri );
652 row.SetType( wxS( "KiCad" ) );
653 }
654
655 return table;
656}
657
658
659static size_t countChainedKiCadRows( const LIBRARY_TABLE& aTable )
660{
661 return std::ranges::count_if( aTable.Rows(),
662 []( const LIBRARY_TABLE_ROW& aRow )
663 {
664 return aRow.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME
665 && aRow.Nickname() == wxS( "KiCad" );
666 } );
667}
668
669
675BOOST_AUTO_TEST_CASE( MigrateBuiltInLibraries_NoStockRefsAddsNothing )
676{
677 const wxString stockPath = wxS( "${KICAD10_SYMBOL_DIR}/sym-lib-table" );
678
680 { wxS( "MyParts" ), wxS( "${KIPRJMOD}/../libs/MyParts.kicad_sym" ) },
681 { wxS( "MyPassives" ), wxS( "/home/user/kicad/MyPassives.kicad_sym" ) },
682 } );
683
684 const size_t rowsBefore = table.Rows().size();
685
687 table, LIBRARY_TABLE_TYPE::SYMBOL, stockPath, true );
688
689 BOOST_CHECK_MESSAGE( !modified, "Table with no stock references should not be modified" );
690 BOOST_CHECK_EQUAL( table.Rows().size(), rowsBefore );
692}
693
694
696BOOST_AUTO_TEST_CASE( MigrateBuiltInLibraries_DirectStockRowsBecomeChained )
697{
698 const wxString stockPath = wxS( "${KICAD10_SYMBOL_DIR}/sym-lib-table" );
699
701 { wxS( "Device" ), wxS( "${KICAD9_SYMBOL_DIR}/Device.kicad_sym" ) },
702 { wxS( "MyParts" ), wxS( "${KIPRJMOD}/../libs/MyParts.kicad_sym" ) },
703 } );
704
706 table, LIBRARY_TABLE_TYPE::SYMBOL, stockPath, true );
707
708 BOOST_CHECK( modified );
710
711 // The user's own row must survive and the direct stock row must be gone.
712 BOOST_CHECK( table.Row( wxS( "MyParts" ) ).has_value() );
713 BOOST_CHECK( !table.Row( wxS( "Device" ) ).has_value() );
714}
715
716
718BOOST_AUTO_TEST_CASE( MigrateBuiltInLibraries_ChainedRowMigratedInPlace )
719{
720 const wxString stockPath = wxS( "${KICAD10_SYMBOL_DIR}/sym-lib-table" );
721
722 LIBRARY_TABLE table( true, wxEmptyString, LIBRARY_TABLE_SCOPE::GLOBAL );
724
725 LIBRARY_TABLE_ROW& chained = table.InsertRow();
727 chained.SetNickname( wxS( "KiCad" ) );
728 chained.SetURI( wxS( "${KICAD9_SYMBOL_DIR}/sym-lib-table" ) );
729
730 LIBRARY_TABLE_ROW& mine = table.InsertRow();
731 mine.SetNickname( wxS( "MyParts" ) );
732 mine.SetURI( wxS( "${KIPRJMOD}/../libs/MyParts.kicad_sym" ) );
733 mine.SetType( wxS( "KiCad" ) );
734
736 table, LIBRARY_TABLE_TYPE::SYMBOL, stockPath, true );
737
738 BOOST_CHECK( modified );
740
741 auto migrated = table.Row( wxS( "KiCad" ) );
742 BOOST_REQUIRE( migrated.has_value() );
743 BOOST_CHECK_EQUAL( ( *migrated )->URI(), stockPath );
744}
745
746
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.
wxString CreateChildFileStr(const wxString &aName) const
Create and return the path to a direct child file as a wxString.
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)
static const wxString TABLE_TYPE_NAME
void SetURI(const wxString &aUri)
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 COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:546
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
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.
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
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")