KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_symbol_library.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 <fmt/format.h>
22
23#include <memory>
26
27#include <mock_pgm_base.h>
28#include <pgm_base.h>
29#include "eeschema_test_utils.h"
30
32
33// NOTE: this is for the new symbol library manager adapter and
34// library manager system. There is also test_symbol_library_manager.cpp
35// which is for the old code; that one can be removed once the old
36// library manager doesn't exist anymore
37
38
39namespace
40{
41constexpr const char* kDeviceLibNickname = "Device";
42}
43
44
46{
47protected:
52 {
54 std::make_unique<SYMBOL_LIBRARY_ADAPTER>( aManager ) );
55
56 std::optional<LIBRARY_MANAGER_ADAPTER*> adapterOpt =
58 BOOST_REQUIRE( adapterOpt.has_value() );
59 return static_cast<SYMBOL_LIBRARY_ADAPTER*>( *adapterOpt );
60 }
61
62 wxFileName GetTestProjectSchPath() const
63 {
64 wxFileName fn( KI_TEST::GetTestDataRootDir(), "test_project.kicad_sch" );
65 fn.AppendDir( "libraries" );
66 fn.AppendDir( "test_project" );
67 return fn;
68 }
69
73 std::unique_ptr<KI_TEST::SCOPED_PROCESS_ENV_VAR> EnsureGlobalSymbolDir()
74 {
76 path += wxT( "/libraries" );
77 return std::make_unique<KI_TEST::SCOPED_PROCESS_ENV_VAR>( wxT( "KICAD9_SYMBOL_DIR" ), path );
78 }
79};
80
81BOOST_FIXTURE_TEST_SUITE( SymbolLibrary, TEST_SYMBOL_LIBRARY_FIXTURE )
82
83BOOST_AUTO_TEST_CASE( ProjectLibraryTable )
84{
85 if( !wxGetEnv( wxT( "KICAD_CONFIG_HOME_IS_QA" ), nullptr ) )
86 {
87 BOOST_TEST_MESSAGE( "QA test is running using unknown config home; skipping" );
88 return;
89 }
90
91 LIBRARY_MANAGER manager;
92 manager.LoadGlobalTables();
93
94 wxFileName fn( KI_TEST::GetTestDataRootDir(), "test_project.kicad_sch" );
95 fn.AppendDir( "libraries" );
96 fn.AppendDir( "test_project" );
97
98 std::vector<LIBRARY_TABLE_ROW*> rows = manager.Rows( LIBRARY_TABLE_TYPE::SYMBOL );
99
100 BOOST_REQUIRE( rows.size() == 3 );
101 BOOST_REQUIRE( rows[0]->Nickname() == "Device" );
102
103 LoadSchematic( fn.GetFullPath() );
104 PROJECT& project = SettingsManager().Prj();
105 manager.LoadProjectTables( project.GetProjectDirectory() );
106
107 rows = manager.Rows( LIBRARY_TABLE_TYPE::SYMBOL );
108
109 BOOST_REQUIRE( rows.size() == 4 );
110 BOOST_REQUIRE( rows[0]->Nickname() == "Device" );
111 BOOST_REQUIRE( rows[0]->URI() == "${KIPRJMOD}/Device.kicad_sym" );
112
113 SYMBOL_LIBRARY_ADAPTER adapter( manager );
114 adapter.LoadOne( "Device" );
115
116 std::vector<LIB_SYMBOL*> symbols = adapter.GetSymbols( "Device" );
117
118 // Check that the project's copy of Device is the one being returned
119 BOOST_REQUIRE( symbols.size() == 3 );
120
121 symbols = adapter.GetSymbols( "power" );
122 BOOST_TEST_MESSAGE( symbols.size() );
123
124 BOOST_REQUIRE( adapter.LoadSymbol( "Device", "R" ) );
125}
126
127
128// If you want to do benchmarking, it can be useful to run this testcase standalone with
129// the KICAD_CONFIG_HOME and KICAD9_SYMBOL_DIR environment variables set to an actual KiCad
130// installation so that you can benchmark a typical load
132{
133 LIBRARY_MANAGER manager;
134 manager.LoadGlobalTables();
135
137 SYMBOL_LIBRARY_ADAPTER adapter( manager );
138
139 auto tstart = std::chrono::high_resolution_clock::now();
140 adapter.AsyncLoad();
141
142 constexpr static int interval = 250;
143 constexpr static int timeLimit = 20000;
144 int elapsed = 0;
145
146 while( true )
147 {
148 std::this_thread::sleep_for( std::chrono::milliseconds( interval ) );
149
150 if( std::optional<float> loadStatus = adapter.AsyncLoadProgress(); loadStatus.has_value() )
151 {
152 BOOST_TEST_MESSAGE( fmt::format( "Loading libraries: ({:.1f}%)", *loadStatus * 100 ) );
153
154 if( loadStatus >= 1 )
155 break;
156 }
157 else
158 {
159 // Just informational; this could be fine when running in a QA context
160 BOOST_TEST_MESSAGE( "Async load not in progress" );
161 break;
162 }
163
164 elapsed += interval;
165
166 if( elapsed > timeLimit )
167 {
168 BOOST_TEST_FAIL( "Exceeded timeout" );
169 break;
170 }
171 }
172
173 auto duration = std::chrono::high_resolution_clock::now() - tstart;
174
176 fmt::format( "took {}ms",
177 std::chrono::duration_cast<std::chrono::milliseconds>( duration ).count() ) );
178
179 adapter.BlockUntilLoaded();
180
181 std::vector<LIBRARY_TABLE_ROW*> rows = manager.Rows( LIBRARY_TABLE_TYPE::SYMBOL );
182
183 BOOST_REQUIRE_GE( rows.size(), 2 );
184
185 for( auto& [nickname, status] : adapter.GetLibraryStatuses() )
186 {
187 wxString msg = nickname;
188 BOOST_REQUIRE( status.load_status != LOAD_STATUS::LOADING );
189
190 switch( status.load_status )
191 {
192 default:
194 msg << ": loaded OK";
195 BOOST_REQUIRE( !status.error.has_value() );
196 break;
197
199 BOOST_REQUIRE( status.error.has_value() );
200 msg << ": error: " << status.error->message;
201 break;
202 }
203
204 BOOST_TEST_MESSAGE( msg );
205 }
206}
207
208
209// A LIBRARY_MANAGER_ADAPTER caches loaded global libraries in the process-wide static
210// GlobalLibraries with raw LIBRARY_TABLE_ROW pointers into its own manager's tables. When a
211// transient manager is destroyed those pointers dangle, and a later manager's fetchIfLoaded()
212// would return the stale entry. The adapter destructor must evict the entries it owns.
213BOOST_AUTO_TEST_CASE( GlobalCacheEvictedOnAdapterDestruction )
214{
215 wxString loadedNickname;
216
217 {
218 LIBRARY_MANAGER manager;
219 manager.LoadGlobalTables();
220
222 SYMBOL_LIBRARY_ADAPTER adapter( manager );
223
224 adapter.AsyncLoad();
225 adapter.BlockUntilLoaded();
226
227 for( auto& [nickname, status] : adapter.GetLibraryStatuses() )
228 {
229 if( status.load_status == LOAD_STATUS::LOADED )
230 {
231 loadedNickname = nickname;
232 break;
233 }
234 }
235
236 if( loadedNickname.IsEmpty() )
237 {
238 BOOST_TEST_MESSAGE( "No global symbol library loaded; skipping" );
239 return;
240 }
241
242 BOOST_REQUIRE( adapter.IsLibraryLoaded( loadedNickname ) );
243 }
244
245 // The owning manager and adapter are gone. A fresh adapter must not still see the library
246 // loaded in the global cache. IsLibraryLoaded() reads only status, so it is safe even when
247 // the entry's row dangles: pre-fix it returns true (stale entry), post-fix false (evicted).
248 LIBRARY_MANAGER manager2;
249 SYMBOL_LIBRARY_ADAPTER adapter2( manager2 );
250
251 BOOST_CHECK_MESSAGE( !adapter2.IsLibraryLoaded( loadedNickname ),
252 "global cache entry must be evicted when its owning adapter is destroyed" );
253}
254
255// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23756
256// Verifies that LoadProjectTables() clears the adapter's cached LIB_DATA entries
257// before destroying the backing LIBRARY_TABLE objects. Without this, LIB_DATA::row
258// raw pointers dangle across a reload and later calls such as GetLibraryDescription()
259// crash when dereferencing freed LIBRARY_TABLE_ROW memory. The user-visible trigger
260// was creating a new library through File -> Export -> Symbols, which re-enters the
261// SelectLibrary loop and iterates all loaded libraries to show their descriptions.
262BOOST_AUTO_TEST_CASE( LoadProjectTablesClearsAdapterCache )
263{
264 if( !wxGetEnv( wxT( "KICAD_CONFIG_HOME_IS_QA" ), nullptr ) )
265 {
266 BOOST_TEST_MESSAGE( "QA test is running using unknown config home; skipping" );
267 return;
268 }
269
270 LIBRARY_MANAGER manager;
271 manager.LoadGlobalTables();
272
273 LoadSchematic( GetTestProjectSchPath().GetFullPath() );
274 PROJECT& project = SettingsManager().Prj();
275 manager.LoadProjectTables( project.GetProjectDirectory() );
276
277 SYMBOL_LIBRARY_ADAPTER* adapter = RegisterSymbolAdapter( manager );
278
279 // Force a project-scope library into m_libraries so that LIB_DATA::row holds
280 // a raw pointer into the project LIBRARY_TABLE's deque. The project-scope Device
281 // row shadows any global Device entry thanks to LoadProjectTables search order.
282 adapter->LoadOne( kDeviceLibNickname );
283 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
284
285 // Reload the project tables. Before the fix, this destroyed the old project
286 // LIBRARY_TABLE (via loadTables()'s aTarget.erase(type)) while leaving the stale
287 // LIB_DATA entry with its dangling row pointer in the adapter's m_libraries cache.
288 manager.LoadProjectTables( project.GetProjectDirectory() );
289
290 // After the fix, the project cache must be cleared. IsLibraryLoaded() reads only
291 // status (no row deref) so it is safe to call even if the cache was not cleared,
292 // which makes it a reliable regression assertion: pre-fix it returns true
293 // (stale LOADED entry), post-fix it returns false (cache emptied).
294 BOOST_REQUIRE_MESSAGE( !adapter->IsLibraryLoaded( kDeviceLibNickname ),
295 "Project library cache must be cleared by LoadProjectTables "
296 "to prevent dangling LIB_DATA::row pointers" );
297
298 // Reloading the library should succeed and produce a readable description using
299 // the fresh row pointer from the new project table.
300 adapter->LoadOne( kDeviceLibNickname );
301 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
302 BOOST_REQUIRE( adapter->GetLibraryDescription( kDeviceLibNickname ).has_value() );
303}
304
305
306// Follow-up regression for https://gitlab.com/kicad/code/kicad/-/issues/23756
307// Verifies that LoadProjectTables() preserves project-over-global shadowing for
308// cached nicknames. SYMBOL_LIBRARY_ADAPTER::GlobalLibraries is a process-wide
309// static, so a global library loaded by an earlier code path can persist as
310// LOADED while a later project introduces a same-named project row that should
311// shadow it. Erasing the project cache on reload would expose the stale global
312// entry until the project library is explicitly reloaded; resetting in place
313// (LIB_DATA{}) keeps the project nickname as a sentinel so fetchIfLoaded() and
314// IsLibraryLoaded() correctly report it as not loaded.
315BOOST_AUTO_TEST_CASE( ProjectReloadPreservesShadowing )
316{
317 if( !wxGetEnv( wxT( "KICAD_CONFIG_HOME_IS_QA" ), nullptr ) )
318 {
319 BOOST_TEST_MESSAGE( "QA test is running using unknown config home; skipping" );
320 return;
321 }
322
323 std::unique_ptr<KI_TEST::SCOPED_PROCESS_ENV_VAR> scopedSymDirEnvVar = EnsureGlobalSymbolDir();
324
325 LIBRARY_MANAGER manager;
326
327 // Register the adapter BEFORE loading tables. LoadGlobalTables() routes
328 // GlobalTablesChanged() through registered adapters to clear the static
329 // GlobalLibraries cache. Without this, stale LIB_DATA entries left over
330 // from earlier test cases (AsyncLoad populates the process-wide
331 // GlobalLibraries with LIB_DATA::row pointers into a LIBRARY_MANAGER that
332 // is subsequently destroyed) would dangle into this test run.
333 SYMBOL_LIBRARY_ADAPTER* adapter = RegisterSymbolAdapter( manager );
334
335 manager.LoadGlobalTables();
336
337 // Force the global "Device" into the process-wide globalLibs cache before
338 // the project is loaded. With no project tables present, loadIfNeeded()
339 // falls through PROJECT scope and populates GLOBAL scope. After this call
340 // the static GlobalLibraries map holds Device with status LOADED.
341 adapter->LoadOne( kDeviceLibNickname );
342 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
343
344 // Now load the project. Its symbol library table contains a "Device" row
345 // that shadows the global Device (verified by ProjectLibraryTable above).
346 LoadSchematic( GetTestProjectSchPath().GetFullPath() );
347 PROJECT& project = SettingsManager().Prj();
348 manager.LoadProjectTables( project.GetProjectDirectory() );
349
350 // LoadOne now resolves through PROJECT scope first and populates m_libraries
351 // with the project Device row, masking the global Device entry.
352 adapter->LoadOne( kDeviceLibNickname );
353 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
354
355 // Reload the project tables to fire ProjectTablesChanged. The project cache
356 // entry must be invalidated in place rather than erased so it continues to
357 // mask the still-LOADED global Device entry until the project library is
358 // explicitly reloaded.
359 manager.LoadProjectTables( project.GetProjectDirectory() );
360
361 // Pre-fix (m_libraries.clear()): the project Device entry is gone, so
362 // IsLibraryLoaded() falls through to globalLibs and returns true.
363 // Post-fix (in-place reset): the project Device entry remains but is no
364 // longer LOADED, so IsLibraryLoaded() returns false immediately.
365 BOOST_REQUIRE_MESSAGE( !adapter->IsLibraryLoaded( kDeviceLibNickname ),
366 "Project library reload must preserve project-over-global "
367 "shadowing for cached nicknames" );
368
369 // Reloading repopulates the entry from the rebuilt project table.
370 adapter->LoadOne( kDeviceLibNickname );
371 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
372 BOOST_REQUIRE( adapter->GetLibraryDescription( kDeviceLibNickname ).has_value() );
373}
374
375
376// Follow-up regression for https://gitlab.com/kicad/code/kicad/-/issues/23756
377// Complements ProjectReloadPreservesShadowing: if a project library is removed
378// (so the rebuilt project table no longer contains its nickname), the cached
379// sentinel installed by ProjectTablesChanged() must not permanently mask a
380// same-named global library. ProjectTablesReloaded() is responsible for erasing
381// sentinels that no longer have a backing project row.
382BOOST_AUTO_TEST_CASE( ProjectReloadReleasesRemovedShadow )
383{
384 if( !wxGetEnv( wxT( "KICAD_CONFIG_HOME_IS_QA" ), nullptr ) )
385 {
386 BOOST_TEST_MESSAGE( "QA test is running using unknown config home; skipping" );
387 return;
388 }
389
390 std::unique_ptr<KI_TEST::SCOPED_PROCESS_ENV_VAR> scopedSymDirEnvVar = EnsureGlobalSymbolDir();
391
392 LIBRARY_MANAGER manager;
393
394 // Register adapter first so LoadGlobalTables() clears any stale static
395 // GlobalLibraries entries left over from earlier test cases.
396 SYMBOL_LIBRARY_ADAPTER* adapter = RegisterSymbolAdapter( manager );
397
398 manager.LoadGlobalTables();
399
400 // Warm the global cache with Device.
401 adapter->LoadOne( kDeviceLibNickname );
402 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
403
404 // Load the project and materialise the project-scope Device entry in
405 // m_libraries (shadows the global Device).
406 LoadSchematic( GetTestProjectSchPath().GetFullPath() );
407 PROJECT& project = SettingsManager().Prj();
408 manager.LoadProjectTables( project.GetProjectDirectory() );
409
410 adapter->LoadOne( kDeviceLibNickname );
411 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
412
413 // Now simulate the project losing its Device row. Pointing LoadProjectTables
414 // at a non-readable path tears down every project-scope LIBRARY_TABLE so the
415 // rebuilt state has no project Device row. ProjectTablesChanged() installs
416 // the sentinel in m_libraries, and ProjectTablesReloaded() must erase it
417 // because GetRow(PROJECT, "Device") no longer resolves.
418 manager.LoadProjectTables( wxString( wxS( "/nonexistent/path/for/test" ) ) );
419
420 // Pre-fix (sentinel left in place): the stale INVALID entry in m_libraries
421 // short-circuits IsLibraryLoaded() before it consults globalLibs(), so it
422 // returns false even though the global Device is still LOADED.
423 // Post-fix (ProjectTablesReloaded prunes the sentinel): IsLibraryLoaded()
424 // falls through to globalLibs() and finds the still-LOADED global Device.
425 BOOST_REQUIRE_MESSAGE( adapter->IsLibraryLoaded( kDeviceLibNickname ),
426 "Removing a project library must expose the same-named global "
427 "library rather than leave a permanent sentinel mask" );
428}
429
430
431// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23480
432// Verifies that reloading project tables during an async library load does not
433// crash due to dangling LIBRARY_TABLE_ROW pointers held by background workers.
434BOOST_AUTO_TEST_CASE( ProjectChangeDuringAsyncLoad )
435{
436 if( !wxGetEnv( wxT( "KICAD_CONFIG_HOME_IS_QA" ), nullptr ) )
437 {
438 BOOST_TEST_MESSAGE( "QA test is running using unknown config home; skipping" );
439 return;
440 }
441
442 LIBRARY_MANAGER manager;
443 manager.LoadGlobalTables();
444
445 LoadSchematic( GetTestProjectSchPath().GetFullPath() );
446 PROJECT& project = SettingsManager().Prj();
447 manager.LoadProjectTables( project.GetProjectDirectory() );
448
449 SYMBOL_LIBRARY_ADAPTER* adapter = RegisterSymbolAdapter( manager );
450
451 adapter->AsyncLoad();
452
453 // Immediately trigger a project table reload while async workers are running.
454 // Before the fix, this destroyed table rows that workers were still using.
455 manager.ProjectChanged();
456
457 adapter->BlockUntilLoaded();
458
459 BOOST_TEST_MESSAGE( "ProjectChanged during async load completed without crash" );
460}
461
462
463// A library whose file is missing must report the failure every time it is loaded. The
464// plugin keeps the cache it built for the library even when the load throws, and that
465// cache still answers to the missing path and still looks up to date, so a second load
466// reports an empty library instead of the error and the table row loses its warning icon.
467BOOST_AUTO_TEST_CASE( MissingLibraryReportsErrorOnEveryLoad )
468{
469 if( !wxGetEnv( wxT( "KICAD_CONFIG_HOME_IS_QA" ), nullptr ) )
470 {
471 BOOST_TEST_MESSAGE( "QA test is running using unknown config home; skipping" );
472 return;
473 }
474
475 LIBRARY_MANAGER manager;
476 manager.LoadGlobalTables();
477
478 LoadSchematic( GetTestProjectSchPath().GetFullPath() );
479 PROJECT& project = SettingsManager().Prj();
480 manager.LoadProjectTables( project.GetProjectDirectory() );
481
482 SYMBOL_LIBRARY_ADAPTER* adapter = RegisterSymbolAdapter( manager );
483
484 std::optional<LIBRARY_TABLE*> optTable = manager.Table( LIBRARY_TABLE_TYPE::SYMBOL, LIBRARY_TABLE_SCOPE::PROJECT );
485 BOOST_REQUIRE( optTable.has_value() );
486
487 LIBRARY_TABLE_ROW& row = ( *optTable )->InsertRow();
488 row.SetNickname( wxS( "Missing" ) );
489 row.SetURI( wxS( "${KIPRJMOD}/does_not_exist.kicad_sym" ) );
490 row.SetType( wxS( "KiCad" ) );
491
492 std::optional<LIB_STATUS> first = adapter->LoadOne( wxS( "Missing" ) );
493 BOOST_REQUIRE( first.has_value() );
494 BOOST_REQUIRE( first->load_status == LOAD_STATUS::LOAD_ERROR );
495
496 std::optional<LIB_STATUS> second = adapter->LoadOne( wxS( "Missing" ) );
497 BOOST_REQUIRE( second.has_value() );
498 BOOST_CHECK_MESSAGE( second->load_status == LOAD_STATUS::LOAD_ERROR,
499 "A library whose file is missing must not come back as loaded" );
500
501 adapter->CheckTableRow( row );
502 BOOST_CHECK_MESSAGE( !row.IsOk(), "A row whose library failed to load must be flagged in the table" );
503}
504
505
506// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24855
507// A library row marked (disabled) must never be contacted by the background loader.
508// The user complaint was a disabled HTTP library still blocking startup while its
509// unreachable backend was probed; a disabled file-based row exercises the same
510// AsyncLoad skip path deterministically without a network dependency.
511BOOST_AUTO_TEST_CASE( AsyncLoadSkipsDisabledRows )
512{
513 wxFileName deviceDir( KI_TEST::GetTestDataRootDir(), wxEmptyString );
514 deviceDir.AppendDir( "libraries" );
515 deviceDir.AppendDir( "test_project" );
516
517 // Inject the resolved test-data path to find the library
518 KI_TEST::SCOPED_PROCESS_ENV_VAR env( wxT( "QA_ISSUE24855_DIR" ), deviceDir.GetPath() );
519
520 wxFileName fixtureDir( KI_TEST::GetTestDataRootDir(), wxEmptyString );
521 fixtureDir.AppendDir( "libraries" );
522 fixtureDir.AppendDir( "issue24855" );
523
524 LIBRARY_MANAGER manager;
526
527 SYMBOL_LIBRARY_ADAPTER* adapter = RegisterSymbolAdapter( manager );
528 manager.LoadProjectTables( fixtureDir.GetPath() );
529
530 std::vector<LIBRARY_TABLE_ROW*> rows = manager.Rows( LIBRARY_TABLE_TYPE::SYMBOL,
532 BOOST_REQUIRE_EQUAL( rows.size(), 2 );
533
534 adapter->AsyncLoad();
535 adapter->BlockUntilLoaded();
536
537 BOOST_CHECK_MESSAGE( adapter->IsLibraryLoaded( "DeviceEnabled" ),
538 "Enabled library must be loaded by AsyncLoad" );
539
540 BOOST_CHECK_MESSAGE( !adapter->IsLibraryLoaded( "DeviceDisabled" ),
541 "Disabled library must not be contacted by AsyncLoad" );
542
543 // The dialog validates rows through CheckTableRow. A disabled row with an unreachable URI
544 // must be left untouched rather than probed, so its stale error is cleared without a load.
545 LIBRARY_TABLE_ROW validationRow = *rows[1];
546 validationRow.SetURI( fixtureDir.GetPathWithSep() + wxT( "missing.kicad_sym" ) );
547 validationRow.SetOk( false );
548 validationRow.SetErrorDescription( wxT( "stale error" ) );
549
550 adapter->CheckTableRow( validationRow );
551
552 BOOST_CHECK_MESSAGE( validationRow.IsOk() && validationRow.ErrorDescription().IsEmpty(),
553 "CheckTableRow must not probe a disabled row's backend" );
554}
555
556
560BOOST_AUTO_TEST_CASE( UnloadedProjectRowShadowsLoadedGlobal )
561{
562 if( !wxGetEnv( wxT( "KICAD_CONFIG_HOME_IS_QA" ), nullptr ) )
563 {
564 BOOST_TEST_MESSAGE( "QA test is running using unknown config home; skipping" );
565 return;
566 }
567
568 std::unique_ptr<KI_TEST::SCOPED_PROCESS_ENV_VAR> scopedSymDirEnvVar = EnsureGlobalSymbolDir();
569
570 LIBRARY_MANAGER manager;
571 SYMBOL_LIBRARY_ADAPTER* adapter = RegisterSymbolAdapter( manager );
572
573 manager.LoadGlobalTables();
574
575 adapter->LoadOne( kDeviceLibNickname );
576 BOOST_REQUIRE( adapter->IsLibraryLoaded( kDeviceLibNickname ) );
577
578 // The project sym-lib-table defines its own Device row that shadows the global one
579 LoadSchematic( GetTestProjectSchPath().GetFullPath() );
580 PROJECT& project = SettingsManager().Prj();
581 manager.LoadProjectTables( project.GetProjectDirectory() );
582
583 BOOST_REQUIRE( adapter->GetRow( kDeviceLibNickname, LIBRARY_TABLE_SCOPE::PROJECT ).has_value() );
584
585 // The project row exists but was never loaded: the global entry must not leak
586 // through as the answer for the shared nickname.
587 BOOST_CHECK_MESSAGE( !adapter->HasLibrary( kDeviceLibNickname ),
588 "an unloaded project library must shadow a loaded global library "
589 "of the same nickname" );
590 BOOST_CHECK_MESSAGE( adapter->GetLibraryNames().empty(),
591 "GetLibraryNames must not list the global library for a nickname "
592 "shadowed by an unloaded project row" );
593 BOOST_CHECK_MESSAGE( !adapter->GetLibraryDescription( kDeviceLibNickname ).has_value(),
594 "GetLibraryDescription must not return the global library's "
595 "description for an unloaded project nickname" );
596}
597
598
A generic fixture for loading schematics and associated settings for qa tests.
RAII helper that sets a process-level environment variable for the lifetime of the object.
std::optional< float > AsyncLoadProgress() const
Returns the fraction of the libraries queued by AsyncLoad() that have been processed,...
void CheckTableRow(LIBRARY_TABLE_ROW &aRow)
bool IsLibraryLoaded(const wxString &aNickname)
std::optional< wxString > GetLibraryDescription(const wxString &aNickname) const
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library tables.
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::vector< wxString > GetLibraryNames() const
Returns a list of library nicknames that are available (skips any that failed to load)
void AsyncLoad()
Loads all available libraries for this adapter type in the background.
std::vector< std::pair< wxString, LIB_STATUS > > GetLibraryStatuses() const
Returns a list of all library nicknames and their status (even if they failed to load)
std::optional< LIBRARY_MANAGER_ADAPTER * > Adapter(LIBRARY_TABLE_TYPE aType) const
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...
void RegisterAdapter(LIBRARY_TABLE_TYPE aType, std::unique_ptr< LIBRARY_MANAGER_ADAPTER > &&aAdapter)
void LoadProjectTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the project library tables in the given list, or all tables if no list is given
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
void ProjectChanged()
Notify all adapters that the project has changed.
void SetNickname(const wxString &aNickname)
void SetOk(bool aOk=true)
void SetType(const wxString &aType)
void SetErrorDescription(const wxString &aDescription)
const wxString & ErrorDescription() const
void SetURI(const wxString &aUri)
bool IsOk() const
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
Container for project specific data.
Definition project.h:63
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
std::optional< LIB_STATUS > LoadOne(LIB_DATA *aLib) override
Loads or reloads the given library, if it exists.
LIB_SYMBOL * LoadSymbol(const wxString &aNickname, const wxString &aName)
Load a LIB_SYMBOL having aName from the library given by aNickname.
std::vector< LIB_SYMBOL * > GetSymbols(const wxString &aNickname, SYMBOL_TYPE aType=SYMBOL_TYPE::ALL_SYMBOLS)
wxFileName GetTestProjectSchPath() const
std::unique_ptr< KI_TEST::SCOPED_PROCESS_ENV_VAR > EnsureGlobalSymbolDir()
Ensure KICAD9_SYMBOL_DIR points at the QA data libraries so the global sym-lib-table's ${KICAD9_SYMBO...
SYMBOL_LIBRARY_ADAPTER * RegisterSymbolAdapter(LIBRARY_MANAGER &aManager)
Register a fresh SYMBOL_LIBRARY_ADAPTER on the manager and return it.
std::string GetTestDataRootDir()
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
static void LoadSchematic(SCHEMATIC *aSchematic, SCH_SHEET *aRootSheet, const wxString &aFileName)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_AUTO_TEST_CASE(ProjectLibraryTable)