30#include <wx/filename.h>
31#include <wx/stdpaths.h>
58wxString stageProject(
const wxString& aStem )
60 wxString sep = wxFileName::GetPathSeparator();
61 wxString dir = wxStandardPaths::Get().GetTempDir() + sep + aStem + wxT(
"-fpreconcile-qa" );
63 if( wxDirExists( dir ) )
64 wxFileName::Rmdir( dir, wxPATH_RMDIR_RECURSIVE );
66 wxFileName::Mkdir( dir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
68 wxString projectPath = dir + sep + aStem + wxT(
".kicad_pro" );
72 Pgm().GetSettingsManager().
Prj().GetProjectDirectory() );
81 std::unique_ptr<PCB_IO_EASYEDAPRO_V3> m_plugin;
82 std::unique_ptr<BOARD> m_board;
83 std::vector<std::unique_ptr<FOOTPRINT>> m_definitions;
84 std::unique_ptr<KI_TEST::SCOPED_TEMP_DIR> m_sourceDir;
88IMPORTED_BOARD importSampleBoard(
PROJECT& aProject,
const std::string& aTag )
90 IMPORTED_BOARD sample;
92 const wxString archiveName = wxS(
"ProProject_LS2K0300Core_2025-11-14.epro2" );
95 srcFn.AppendDir( wxS(
"plugins" ) );
96 srcFn.AppendDir( wxS(
"easyedapro" ) );
97 srcFn.SetFullName( archiveName );
98 BOOST_REQUIRE_MESSAGE( srcFn.FileExists(),
"Missing EasyEDA Pro v3 board fixture" );
100 sample.m_sourceDir = std::make_unique<KI_TEST::SCOPED_TEMP_DIR>( wxString::FromUTF8( aTag ) );
102 wxFileName importFn( sample.m_sourceDir->
PathStr(), archiveName );
103 BOOST_REQUIRE( wxCopyFile( srcFn.GetFullPath(), importFn.GetFullPath() ) );
105 std::map<std::string, UTF8> properties;
106 properties[
"pcb_id"] =
"eb9fbfba682940f7a002816e66fbb3d7";
108 sample.m_plugin = std::make_unique<PCB_IO_EASYEDAPRO_V3>();
109 sample.m_board = std::make_unique<BOARD>();
111 sample.m_plugin->LoadAndAppendBoard( importFn.GetFullPath(), *sample.m_board, &properties, &aProject );
113 BOOST_REQUIRE_GT( sample.m_board->
Footprints().size(), 0 );
115 for(
FOOTPRINT* fp : sample.m_plugin->GetImportedCachedLibraryFootprints() )
116 sample.m_definitions.emplace_back( fp );
118 BOOST_REQUIRE_GT( sample.m_definitions.size(), 0 );
126 const wxString& aNickname,
const wxString& aDirStem,
131 wxString libPath = libDir.GetFullPath();
136 pi->CreateLibrary( libPath );
140 copy->SetReference( wxS(
"REF**" ) );
141 pi->FootprintSave( libPath,
copy.get() );
148 row.
SetURI( wxS(
"${KIPRJMOD}/" ) + libDir.GetFullName() );
158struct COLLISION_CANDIDATE
166COLLISION_CANDIDATE findCandidate( IMPORTED_BOARD& aSample )
168 COLLISION_CANDIDATE candidate;
172 wxString nick = fp->GetFPID().GetUniStringLibNickname();
173 wxString
name = fp->GetFPID().GetUniStringLibItemName();
175 if( nick.IsEmpty() ||
name.IsEmpty() )
178 for(
const std::unique_ptr<FOOTPRINT>& def : aSample.m_definitions )
180 if( def->GetFPID().GetUniStringLibItemName() !=
name || def->Pads().empty() )
183 candidate.m_footprint = fp;
184 candidate.m_nickname = nick;
185 candidate.m_name =
name;
203 wxString projectPath = stageProject( wxS(
"eagle_fpreconcile" ) );
207 brdFn.AppendDir( wxS(
"io" ) );
208 brdFn.AppendDir( wxS(
"eagle" ) );
209 brdFn.SetFullName( wxS(
"eagle-import-testfile.brd" ) );
210 BOOST_REQUIRE_MESSAGE( brdFn.FileExists(),
"Missing Eagle board fixture" );
213 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
217 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
220 std::vector<std::unique_ptr<FOOTPRINT>> defs;
223 defs.emplace_back( fp );
228 const wxString cacheNick = wxS(
"eagle_test-import-fps" );
232 reconciler.
Reconcile( board.get(), std::move( defs ), cacheNick, {} );
236 BOOST_CHECK_GT(
result.m_savedToCache, 0 );
238 wxFileName prettyDir(
project.GetProjectPath(), cacheNick,
240 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
241 "Generated .pretty was not published" );
246 BOOST_CHECK( projectTable->
HasRow( cacheNick ) );
251 for(
FOOTPRINT* fp : board->Footprints() )
253 wxString
name = fp->GetFPID().GetUniStringLibItemName();
258 wxString nick = fp->GetFPID().GetUniStringLibNickname();
260 BOOST_CHECK_MESSAGE( !nick.IsEmpty(),
261 wxString::Format(
"Board footprint '%s' left with empty nickname",
264 wxString::Format(
"FPID '%s:%s' does not resolve after reconciliation",
269 BOOST_CHECK_GT( resolved, 0 );
278 stageProject( wxS(
"altium_fpreconcile" ) );
281 std::string dataPath =
285 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
289 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
292 std::vector<std::unique_ptr<FOOTPRINT>> defs;
295 defs.emplace_back( fp );
300 const wxString cacheNick = wxS(
"hifive-import-fps" );
304 reconciler.
Reconcile( board.get(), std::move( defs ), cacheNick, {} );
311 for(
FOOTPRINT* fp : board->Footprints() )
313 wxString
name = fp->GetFPID().GetUniStringLibItemName();
318 wxString nick = fp->GetFPID().GetUniStringLibNickname();
321 wxString::Format(
"FPID '%s:%s' does not resolve after reconciliation",
326 BOOST_CHECK_GT( resolved, 0 );
334 stageProject( wxS(
"easyedapro_v3_fpreconcile" ) );
337 IMPORTED_BOARD sample = importSampleBoard(
project,
"easyedapro_v3_fpreconcile_src" );
338 BOARD* board = sample.m_board.get();
341 wxFileName srcDir( sample.m_sourceDir->
PathStr(), wxEmptyString );
342 wxFileName strayLib( srcDir.GetPath(),
345 BOOST_CHECK_MESSAGE( !wxDir::Exists( strayLib.GetFullPath() ),
346 "LoadBoard wrote a library into the source directory" );
351 const wxString cacheNick = wxS(
"ls2k0300-import-fps" );
355 reconciler.
Reconcile( board, std::move( sample.m_definitions ), cacheNick, {} );
359 BOOST_CHECK_GT(
result.m_savedToCache, 0 );
361 wxFileName prettyDir(
project.GetProjectPath(), cacheNick,
363 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
364 "Generated .pretty was not published into the project" );
368 BOOST_CHECK( projectTable->
HasRow( cacheNick ) );
374 wxString
name = fp->GetFPID().GetUniStringLibItemName();
379 wxString nick = fp->GetFPID().GetUniStringLibNickname();
382 wxString::Format(
"FPID '%s:%s' does not resolve after reconciliation",
387 BOOST_CHECK_GT( resolved, 0 );
396 stageProject( wxS(
"fpreconcile_collide" ) );
399 IMPORTED_BOARD sample = importSampleBoard(
project,
"fpreconcile_collide_src" );
400 COLLISION_CANDIDATE candidate = findCandidate( sample );
408 impostor.
SetFPID(
LIB_ID( candidate.m_nickname, candidate.m_name ) );
410 publishLibrary(
project, *adapter, candidate.m_nickname, wxS(
"impostor" ), impostor );
412 const wxString cacheNick = wxS(
"collide-import-fps" );
416 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
421 BOOST_CHECK_EQUAL( candidate.m_footprint->GetFPID().GetUniStringLibNickname(), cacheNick );
423 std::unique_ptr<FOOTPRINT> linked( adapter->
LoadFootprint( cacheNick, candidate.m_name,
426 BOOST_CHECK_GT( linked->Pads().size(), 0 );
434 stageProject( wxS(
"fpreconcile_namecollide" ) );
437 std::string dataPath =
441 std::unique_ptr<BOARD> source = std::make_unique<BOARD>();
442 source->SetProject( &
project );
449 for(
FOOTPRINT* fp : source->Footprints() )
451 if( fp->Pads().empty() )
456 else if( fp->Pads().size() != firstSource->
Pads().size() )
466 const wxString sharedName = wxS(
"SHARED_FP" );
467 const size_t firstPads = firstSource->
Pads().size();
468 const size_t secondPads = secondSource->
Pads().size();
470 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
473 std::vector<std::unique_ptr<FOOTPRINT>> defs;
476 auto place = [&](
const FOOTPRINT* aSource,
const wxString& aNickname )
482 std::unique_ptr<FOOTPRINT> def(
static_cast<FOOTPRINT*
>( aSource->
Clone() ) );
483 def->SetFPID(
LIB_ID( aNickname, sharedName ) );
484 defs.push_back( std::move( def ) );
489 FOOTPRINT* firstPlaced = place( firstSource, wxS(
"libAlpha" ) );
490 FOOTPRINT* secondPlaced = place( secondSource, wxS(
"libBeta" ) );
495 const wxString cacheNick = wxS(
"namecollide-import-fps" );
500 reconciler.
Reconcile( board.get(), std::move( defs ), cacheNick, {} );
511 "Footprints from two source libraries share one cache item name" );
514 std::unique_ptr<FOOTPRINT> firstLinked(
516 std::unique_ptr<FOOTPRINT> secondLinked(
529 BOOST_CHECK_MESSAGE(
reporter.GetMessages().Contains(
530 wxString::Format( wxS(
"renamed to '%s'" ), renamed ) ),
531 "Cache rename was not reported" );
539 stageProject( wxS(
"fpreconcile_row" ) );
542 IMPORTED_BOARD sample = importSampleBoard(
project,
"fpreconcile_row_src" );
547 const wxString cacheNick = wxS(
"row-import-fps" );
552 publishLibrary(
project, *adapter, cacheNick, wxS(
"user-owned" ), owned );
560 const wxString uriBefore = row->
URI();
565 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
567 BOOST_CHECK(
result.m_cacheNickname.IsEmpty() );
570 wxFileName prettyDir(
project.GetProjectPath(), cacheNick,
572 BOOST_CHECK_MESSAGE( !wxDir::Exists( prettyDir.GetFullPath() ),
573 "Published a cache over a nickname the user already owns" );
581 stageProject( wxS(
"eagle_netlist_roundtrip" ) );
585 brdFn.AppendDir( wxS(
"io" ) );
586 brdFn.AppendDir( wxS(
"eagle" ) );
587 brdFn.SetFullName( wxS(
"eagle-import-testfile.brd" ) );
591 std::unique_ptr<BOARD> imported = std::make_unique<BOARD>();
592 imported->SetProject( &
project );
596 std::vector<std::unique_ptr<FOOTPRINT>> defs;
599 defs.emplace_back( fp );
604 const wxString cacheNick = wxS(
"eagle_test-import-fps" );
606 reconciler.
Reconcile( imported.get(), std::move( defs ), cacheNick, {} );
610 std::set<wxString> seen;
611 int expectedComponents = 0;
613 for(
FOOTPRINT* fp : imported->Footprints() )
615 LIB_ID fpid = fp->GetFPID();
620 wxString ref = wxString::Format( wxS(
"U%d" ), ++expectedComponents );
625 BOOST_REQUIRE_GT( expectedComponents, 0 );
628 std::unique_ptr<BOARD> target = std::make_unique<BOARD>();
629 target->SetProject( &
project );
632 toolMgr.
SetEnvironment( target.get(),
nullptr,
nullptr,
nullptr,
nullptr );
644 BOOST_CHECK_MESSAGE( !
reporter.GetMessages().Lower().Contains( wxS(
"not found" ) ),
645 "Netlist updater reported a footprint not found after reconciliation" );
646 BOOST_CHECK_EQUAL(
static_cast<int>( target->Footprints().size() ), expectedComponents );
Update the BOARD with a new netlist.
void SetReporter(REPORTER *aReporter)
Enable dry run mode (just report, no changes to PCB).
bool UpdateNetlist(NETLIST &aNetlist)
Update the board's components according to the new netlist.
void SetDeleteUnusedFootprints(bool aEnabled)
int GetErrorCount() const
void SetReplaceFootprints(bool aEnabled)
Information pertinent to a Pcbnew printed circuit board.
const FOOTPRINTS & Footprints() const
void SetProject(PROJECT *aProject, bool aReferenceOnly=false)
Link a board to a given project.
wxString PathStr() const
Get the path to the temporary directory as a wxString.
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.
wxString GetUniStringLibId() const
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
const wxString GetUniStringLibNickname() const
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
Works with Eagle 6.x XML board files and footprints to implement the Pcbnew #PLUGIN API or a portion ...
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
@ KICAD_SEXP
S-expression Pcbnew file format.
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
void LoadAndAppendBoard(const wxString &aFileName, BOARD &aAppendToMe, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Same as LoadBoard(), but appends the loaded board to an existing board, which must already exist.
virtual SETTINGS_MANAGER & GetSettingsManager() const
virtual LIBRARY_MANAGER & GetLibraryManager() const
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
virtual const wxString GetProjectPath() const
Return the full path of the project.
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.
A wrapper for reporting to a wxString object.
static const std::string KiCadFootprintLibPathExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
wxString ShortenLibName(wxString aProjectName)
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
PGM_BASE & Pgm()
The global program "get" accessor.
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
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")