28#include <wx/filename.h>
55wxString stageProject(
const wxString& aStem )
57 wxString sep = wxFileName::GetPathSeparator();
58 wxString dir = wxFileName::GetTempDir() + sep + aStem + wxT(
"-symreconcile-qa" );
60 if( wxDirExists( dir ) )
61 wxFileName::Rmdir( dir, wxPATH_RMDIR_RECURSIVE );
63 wxFileName::Mkdir( dir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
65 wxString projectPath = dir + sep + aStem + wxT(
".kicad_pro" );
69 Pgm().GetSettingsManager().
Prj().GetProjectDirectory() );
75wxString easyEdaProV3Archive()
78 +
"pcbnew/plugins/easyedapro/ProProject_LS2K0300Core_2025-11-14.epro2" );
83bool hasSymbolLib(
const wxString& aPath )
93 return dir.GetFirst( &
name, spec, wxDIR_FILES );
101 std::unique_ptr<SCHEMATIC> m_schematic;
102 std::vector<std::unique_ptr<LIB_SYMBOL>> m_definitions;
103 wxString m_sourceDir;
107IMPORTED_SAMPLE importSample(
PROJECT& aProject )
109 IMPORTED_SAMPLE sample;
111 wxString sep = wxFileName::GetPathSeparator();
112 sample.m_sourceDir = aProject.
GetProjectPath() + wxT(
"import-src" );
113 BOOST_REQUIRE( wxFileName::Mkdir( sample.m_sourceDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
115 wxFileName archiveFile( sample.m_sourceDir + sep, wxS(
"sample" ), wxS(
"epro2" ) );
116 BOOST_REQUIRE( wxCopyFile( easyEdaProV3Archive(), archiveFile.GetFullPath() ) );
118 sample.m_schematic = std::make_unique<SCHEMATIC>(
nullptr );
121 sample.m_plugin.reset( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EASYEDAPRO_V3 ) );
125 BOOST_REQUIRE_NO_THROW( rootSheet = sample.m_plugin->LoadSchematicFile(
126 archiveFile.GetFullPath(), sample.m_schematic.get() ) );
131 for(
LIB_SYMBOL* symbol : sample.m_plugin->GetImportedCachedLibrarySymbols() )
132 sample.m_definitions.emplace_back( symbol );
134 BOOST_REQUIRE_GT( sample.m_definitions.size(), 0 );
140std::vector<SCH_SYMBOL*> placedSymbols(
SCHEMATIC& aSchematic )
142 std::vector<SCH_SYMBOL*> symbols;
146 if(
SCH_SCREEN* screen = sheetPath.LastScreen() )
149 symbols.push_back(
static_cast<SCH_SYMBOL*
>( item ) );
159 const wxString& aFileStem,
const LIB_SYMBOL& aSymbol )
166 pi->CreateLibrary( libFn.GetFullPath() );
168 auto copy = std::make_unique<LIB_SYMBOL>( aSymbol );
170 pi->SaveSymbol( libFn.GetFullPath(),
copy.release() );
177 row.
SetURI( wxS(
"${KIPRJMOD}/" ) + libFn.GetFullName() );
187struct COLLISION_CANDIDATE
189 SCH_SYMBOL* m_symbol =
nullptr;
190 LIB_SYMBOL* m_definition =
nullptr;
196COLLISION_CANDIDATE findCandidate( IMPORTED_SAMPLE& aSample )
198 COLLISION_CANDIDATE candidate;
200 for(
SCH_SYMBOL* symbol : placedSymbols( *aSample.m_schematic ) )
202 wxString nick = symbol->GetLibId().GetUniStringLibNickname();
203 wxString
name = symbol->GetLibId().GetUniStringLibItemName();
205 if( nick.IsEmpty() ||
name.IsEmpty() )
208 for(
const std::unique_ptr<LIB_SYMBOL>& def : aSample.m_definitions )
210 if( def->GetLibId().GetUniStringLibItemName() !=
name || def->GetPins().empty() )
213 candidate.m_symbol = symbol;
214 candidate.m_definition = def.get();
215 candidate.m_nickname = nick;
216 candidate.m_name =
name;
234 wxString projectPath = stageProject( wxS(
"easyedapro_v3" ) );
237 IMPORTED_SAMPLE sample = importSample(
project );
238 SCHEMATIC& schematic = *sample.m_schematic;
241 BOOST_CHECK_MESSAGE( !hasSymbolLib( sample.m_sourceDir ),
242 "LoadSchematicFile wrote a library into the source directory" );
247 const wxString cacheNick = wxS(
"ls2k0300-import-syms" );
251 reconciler.
Reconcile( &schematic, std::move( sample.m_definitions ), cacheNick, {} );
255 BOOST_CHECK_GT(
result.m_savedToCache, 0 );
257 wxFileName cacheFile(
project.GetProjectPath(), cacheNick,
259 BOOST_CHECK_MESSAGE( wxFileExists( cacheFile.GetFullPath() ),
260 "Generated .kicad_sym was not published into the project" );
264 BOOST_CHECK( projectTable->
HasRow( cacheNick ) );
286 BOOST_CHECK_MESSAGE( !nick.IsEmpty(),
287 wxString::Format(
"Symbol '%s' left with empty nickname",
name ) );
288 BOOST_CHECK_MESSAGE( adapter->
LoadSymbol( nick,
name ) !=
nullptr,
289 wxString::Format(
"LIB_ID '%s:%s' does not resolve after "
290 "reconciliation", nick,
name ) );
295 BOOST_CHECK_GT( resolved, 0 );
298 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
305 wxString projectPath = stageProject( wxS(
"easyedapro_v2" ) );
308 wxString sep = wxFileName::GetPathSeparator();
309 wxString srcDir = projectPath + wxT(
"import-src" );
310 BOOST_REQUIRE( wxFileName::Mkdir( srcDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
312 wxString source = wxString::FromUTF8(
314 +
"pcbnew/plugins/easyedapro/ProProject_Yuzuki Chameleon_2023-09-02.epro" );
317 wxFileName archiveFile( srcDir + sep, wxS(
"sample" ), wxS(
"epro" ) );
318 BOOST_REQUIRE( wxCopyFile( source, archiveFile.GetFullPath() ) );
327 BOOST_REQUIRE_NO_THROW(
328 rootSheet = plugin->LoadSchematicFile( archiveFile.GetFullPath(), &schematic ) );
331 BOOST_CHECK_MESSAGE( !hasSymbolLib( srcDir ),
332 "LoadSchematicFile wrote a library into the source directory" );
335 std::vector<LIB_SYMBOL*> raw = plugin->GetImportedCachedLibrarySymbols();
336 BOOST_CHECK_GT( raw.size(), 0 );
341 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
349 wxString projectPath = stageProject( wxS(
"symreconcile_collide" ) );
352 IMPORTED_SAMPLE sample = importSample(
project );
353 COLLISION_CANDIDATE candidate = findCandidate( sample );
362 publishLibrary(
project, *adapter, candidate.m_nickname, wxS(
"impostor" ), impostor );
364 const wxString cacheNick = wxS(
"collide-import-syms" );
368 sample.m_schematic.get(), std::move( sample.m_definitions ), cacheNick, {} );
377 BOOST_CHECK_GT( linked->
GetPins().size(), 0 );
379 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
387 wxString projectPath = stageProject( wxS(
"symreconcile_equivalent" ) );
390 IMPORTED_SAMPLE sample = importSample(
project );
391 COLLISION_CANDIDATE candidate = findCandidate( sample );
397 publishLibrary(
project, *adapter, candidate.m_nickname, wxS(
"genuine" ),
398 *candidate.m_definition );
400 const wxString cacheNick = wxS(
"equivalent-import-syms" );
401 wxString sourceNick = candidate.m_nickname;
405 sample.m_schematic.get(), std::move( sample.m_definitions ), cacheNick, {} );
408 BOOST_CHECK_GT(
result.m_linkedToSource, 0 );
410 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
418 wxString projectPath = stageProject( wxS(
"symreconcile_namecollide" ) );
421 IMPORTED_SAMPLE sample = importSample(
project );
427 for(
SCH_SYMBOL* symbol : placedSymbols( *sample.m_schematic ) )
429 const std::unique_ptr<LIB_SYMBOL>& part = symbol->GetLibSymbolRef();
431 if( !part || part->GetPins().empty() )
435 firstPlaced = symbol;
437 secondPlaced = symbol;
446 const wxString sharedName = wxS(
"SHARED_SYM" );
450 std::vector<std::unique_ptr<LIB_SYMBOL>> defs;
453 auto relabel = [&](
SCH_SYMBOL* aSymbol,
const wxString& aNickname )
455 auto def = std::make_unique<LIB_SYMBOL>( *aSymbol->
GetLibSymbolRef() );
456 def->SetName( sharedName );
457 def->SetLibId(
LIB_ID( aNickname, sharedName ) );
458 defs.push_back( std::move( def ) );
463 relabel( firstPlaced, wxS(
"libAlpha" ) );
464 relabel( secondPlaced, wxS(
"libBeta" ) );
469 const wxString cacheNick = wxS(
"namecollide-import-syms" );
474 reconciler.
Reconcile( sample.m_schematic.get(), std::move( defs ), cacheNick, {} );
484 "Symbols from two source libraries share one cache item name" );
500 BOOST_CHECK_MESSAGE(
reporter.GetMessages().Contains(
501 wxString::Format( wxS(
"renamed to '%s'" ), renamed ) ),
502 "Cache rename was not reported" );
504 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
512 wxString projectPath = stageProject( wxS(
"symreconcile_row" ) );
515 IMPORTED_SAMPLE sample = importSample(
project );
520 const wxString cacheNick = wxS(
"row-import-syms" );
524 publishLibrary(
project, *adapter, cacheNick, wxS(
"user-owned" ), owned );
532 const wxString uriBefore = row->
URI();
537 sample.m_schematic.get(), std::move( sample.m_definitions ), cacheNick, {} );
539 BOOST_CHECK(
result.m_cacheNickname.IsEmpty() );
542 wxFileName cacheFile(
project.GetProjectPath(), cacheNick,
544 BOOST_CHECK_MESSAGE( !wxFileExists( cacheFile.GetFullPath() ),
545 "Published a cache over a nickname the user already owns" );
547 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
554 wxString projectPath = stageProject( wxS(
"symreconcile_rotable" ) );
564 wxFileName tableFn(
table->Path() );
565 tableFn.SetPermissions( wxS_IRUSR | wxS_IRGRP | wxS_IROTH );
568 if( tableFn.IsFileWritable() )
570 BOOST_TEST_MESSAGE(
"Skipping read-only table check; file remains writable (running as "
572 tableFn.SetPermissions( wxS_IRUSR | wxS_IWUSR );
573 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
577 IMPORTED_SAMPLE sample = importSample(
project );
579 const wxString cacheNick = wxS(
"rotable-import-syms" );
583 sample.m_schematic.get(), std::move( sample.m_definitions ), cacheNick, {} );
585 BOOST_CHECK(
result.m_cacheNickname.IsEmpty() );
587 BOOST_CHECK_GT(
result.m_unresolved, 0 );
589 tableFn.SetPermissions( wxS_IRUSR | wxS_IWUSR );
590 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
597 wxString projectPath = stageProject( wxS(
"symreconcile_clobber" ) );
600 IMPORTED_SAMPLE sample = importSample(
project );
605 const wxString cacheNick = wxS(
"victim-import-syms" );
607 wxFileName victim(
project.GetProjectPath(), cacheNick,
610 const wxString sentinel = wxS(
"(kicad_symbol_lib (version 20231120) (generator \"qa\"))" );
611 BOOST_REQUIRE( wxFile( victim.GetFullPath(), wxFile::write ).Write( sentinel ) );
616 sample.m_schematic.get(), std::move( sample.m_definitions ), cacheNick, {} );
619 BOOST_CHECK(
result.m_cacheNickname.IsEmpty() );
620 BOOST_CHECK_GT(
result.m_unresolved, 0 );
623 wxFile readBack( victim.GetFullPath(), wxFile::read );
627 wxFileName::Rmdir( projectPath, wxPATH_RMDIR_RECURSIVE );
EE_TYPE OfType(KICAD_T aType) const
std::optional< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
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
void SetNickname(const wxString &aNickname)
void SetType(const wxString &aType)
void SetURI(const wxString &aUri)
void SetScope(LIBRARY_TABLE_SCOPE aScope)
const wxString & URI() const
bool HasRow(const wxString &aNickname) const
A logical library item identifier and consists of various portions much like a URI.
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
const wxString GetUniStringLibNickname() const
Define a library symbol object.
std::vector< SCH_PIN * > GetPins() const override
virtual SETTINGS_MANAGER & GetSettingsManager() const
virtual LIBRARY_MANAGER & GetLibraryManager() const
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
Container for project specific data.
virtual const wxString GetProjectPath() const
Return the full path of the project.
Holds all the data relating to one schematic.
SCH_SHEET_LIST Hierarchy() const
Return the full schematic flattened hierarchical sheet list.
void SetProject(PROJECT *aPrj)
Base class for any item which can be embedded within the SCHEMATIC container class,...
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
void SetLibId(const LIB_ID &aName)
const LIB_ID & GetLibId() const override
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
Frame-independent, non-interactive service that reconciles the symbol-library references of a freshly...
SYMBOL_IMPORT_RECONCILE_RESULT Reconcile(SCHEMATIC *aSchematic, std::vector< std::unique_ptr< LIB_SYMBOL > > aDefinitions, const wxString &aCacheNickname, const std::vector< wxString > &aSourceLibNicknames)
Reconcile aSchematic against the importer definitions and the provenance source libraries.
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.
A wrapper for reporting to a wxString object.
static const std::string KiCadSymbolLibFileExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
std::string GetTestDataRootDir()
PGM_BASE & Pgm()
The global program "get" accessor.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
Outcome of a post-import symbol-library reconciliation pass.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_AUTO_TEST_CASE(EasyEdaProV3SchematicResolvesToGeneratedCache)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
Definition of file extensions used in Kicad.