KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_footprint_import_reconciler.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 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <memory>
21#include <set>
22#include <vector>
23
24#include <qa_utils/file_utils.h>
27
28#include <wx/dir.h>
29#include <wx/filefn.h>
30#include <wx/filename.h>
31#include <wx/stdpaths.h>
32
33#include <board.h>
34#include <footprint.h>
35#include <lib_id.h>
36#include <pad.h>
37#include <pgm_base.h>
38#include <project.h>
39#include <project_pcb.h>
40#include <reporter.h>
44#include <kiface_base.h>
49#include <pcbnew_settings.h>
52#include <pcb_io/pcb_io_mgr.h>
56#include <tool/tool_manager.h>
57
59
60
61namespace
62{
64wxString stageProject( const std::filesystem::path& aProjectRoot, const wxString& aStem )
65{
66 wxString sep = wxFileName::GetPathSeparator();
67 wxString projectPath = wxString::FromUTF8( aProjectRoot.string() ) + sep + aStem + wxT( ".kicad_pro" );
68
69 Pgm().GetSettingsManager().LoadProject( projectPath );
71 Pgm().GetSettingsManager().Prj().GetProjectDirectory() );
72
74}
75
76
78struct IMPORTED_BOARD
79{
80 std::unique_ptr<PCB_IO_EASYEDAPRO_V3> m_plugin;
81 std::unique_ptr<BOARD> m_board;
82 std::vector<std::unique_ptr<FOOTPRINT>> m_definitions;
83 std::unique_ptr<KI_TEST::SCOPED_TEMP_DIR> m_sourceDir;
84};
85
86
87IMPORTED_BOARD importSampleBoard( PROJECT& aProject, const std::string& aTag )
88{
89 IMPORTED_BOARD sample;
90
91 const wxString archiveName = wxS( "ProProject_LS2K0300Core_2025-11-14.epro2" );
92
93 wxFileName srcFn( wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ) );
94 srcFn.AppendDir( wxS( "plugins" ) );
95 srcFn.AppendDir( wxS( "easyedapro" ) );
96 srcFn.SetFullName( archiveName );
97 BOOST_REQUIRE_MESSAGE( srcFn.FileExists(), "Missing EasyEDA Pro v3 board fixture" );
98
99 sample.m_sourceDir = std::make_unique<KI_TEST::SCOPED_TEMP_DIR>( wxString::FromUTF8( aTag ) );
100
101 wxFileName importFn( sample.m_sourceDir->PathStr(), archiveName );
102 BOOST_REQUIRE( wxCopyFile( srcFn.GetFullPath(), importFn.GetFullPath() ) );
103
104 std::map<std::string, UTF8> properties;
105 properties["pcb_id"] = "eb9fbfba682940f7a002816e66fbb3d7";
106
107 sample.m_plugin = std::make_unique<PCB_IO_EASYEDAPRO_V3>();
108 sample.m_board = std::make_unique<BOARD>();
109 sample.m_board->SetProject( &aProject );
110 sample.m_plugin->LoadAndAppendBoard( importFn.GetFullPath(), *sample.m_board, &properties, &aProject );
111
112 BOOST_REQUIRE_GT( sample.m_board->Footprints().size(), 0 );
113
114 for( FOOTPRINT* fp : sample.m_plugin->GetImportedCachedLibraryFootprints() )
115 sample.m_definitions.emplace_back( fp );
116
117 BOOST_REQUIRE_GT( sample.m_definitions.size(), 0 );
118
119 return sample;
120}
121
122
124void publishLibrary( PROJECT& aProject, FOOTPRINT_LIBRARY_ADAPTER& aAdapter,
125 const wxString& aNickname, const wxString& aDirStem,
126 const FOOTPRINT& aFootprint )
127{
128 wxFileName libDir( aProject.GetProjectPath(), aDirStem,
130 wxString libPath = libDir.GetFullPath();
131
133 BOOST_REQUIRE( pi );
134
135 pi->CreateLibrary( libPath );
136
137 std::unique_ptr<FOOTPRINT> copy( static_cast<FOOTPRINT*>( aFootprint.Clone() ) );
138 copy->SetFPID( LIB_ID( aNickname, aFootprint.GetFPID().GetUniStringLibItemName() ) );
139 copy->SetReference( wxS( "REF**" ) );
140 pi->FootprintSave( libPath, copy.get() );
141
142 LIBRARY_TABLE* table = aAdapter.ProjectTable().value_or( nullptr );
144
145 LIBRARY_TABLE_ROW& row = table->InsertRow();
146 row.SetNickname( aNickname );
147 row.SetURI( wxS( "${KIPRJMOD}/" ) + libDir.GetFullName() );
148 row.SetType( wxS( "KiCad" ) );
150
151 BOOST_REQUIRE( table->Save().has_value() );
152 aAdapter.LoadOne( aNickname );
153}
154
155
157struct COLLISION_CANDIDATE
158{
159 FOOTPRINT* m_footprint = nullptr;
160 wxString m_nickname;
161 wxString m_name;
162};
163
164
165COLLISION_CANDIDATE findCandidate( IMPORTED_BOARD& aSample )
166{
167 COLLISION_CANDIDATE candidate;
168
169 for( FOOTPRINT* fp : aSample.m_board->Footprints() )
170 {
171 wxString nick = fp->GetFPID().GetUniStringLibNickname();
172 wxString name = fp->GetFPID().GetUniStringLibItemName();
173
174 if( nick.IsEmpty() || name.IsEmpty() )
175 continue;
176
177 for( const std::unique_ptr<FOOTPRINT>& def : aSample.m_definitions )
178 {
179 if( def->GetFPID().GetUniStringLibItemName() != name || def->Pads().empty() )
180 continue;
181
182 candidate.m_footprint = fp;
183 candidate.m_nickname = nick;
184 candidate.m_name = name;
185
186 return candidate;
187 }
188 }
189
190 return candidate;
191}
192} // namespace
193
194
195BOOST_AUTO_TEST_SUITE( FootprintImportReconciler )
196
197
198// Eagle board reconciles to a generated cache: .pretty published, row added, every FPID resolves
199// fails on revert, since Eagle FPIDs keep empty nicknames
200BOOST_AUTO_TEST_CASE( EagleBoardResolvesToGeneratedCache )
201{
202 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "eagle_fpreconcile" ) );
203 wxString projectPath = stageProject( projectDir.Path(), wxS( "eagle_fpreconcile" ) );
205
206 wxFileName brdFn( KI_TEST::GetEeschemaTestDataDir() );
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" );
211
212 PCB_IO_EAGLE plugin;
213 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
214 board->SetProject( &project );
215 plugin.LoadAndAppendBoard( brdFn.GetFullPath(), *board, nullptr, &project );
216
217 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
218
219 std::vector<FOOTPRINT*> raw = plugin.GetImportedCachedLibraryFootprints();
220 std::vector<std::unique_ptr<FOOTPRINT>> defs;
221
222 for( FOOTPRINT* fp : raw )
223 defs.emplace_back( fp );
224
226 BOOST_REQUIRE( adapter );
227
228 const wxString cacheNick = wxS( "eagle_test-import-fps" );
229 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
230
232 reconciler.Reconcile( board.get(), std::move( defs ), cacheNick, {} );
233
234 // cache library published
235 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
236 BOOST_CHECK_GT( result.m_savedToCache, 0 );
237
238 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
240 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
241 "Generated .pretty was not published" );
242
243 // project fp-lib table gained the row
244 LIBRARY_TABLE* projectTable = adapter->ProjectTable().value_or( nullptr );
245 BOOST_REQUIRE( projectTable );
246 BOOST_CHECK( projectTable->HasRow( cacheNick ) );
247
248 // every board FPID resolves via the adapter
249 int resolved = 0;
250
251 for( FOOTPRINT* fp : board->Footprints() )
252 {
253 wxString name = fp->GetFPID().GetUniStringLibItemName();
254
255 if( name.IsEmpty() )
256 continue;
257
258 wxString nick = fp->GetFPID().GetUniStringLibNickname();
259
260 BOOST_CHECK_MESSAGE( !nick.IsEmpty(),
261 wxString::Format( "Board footprint '%s' left with empty nickname",
262 name ) );
263 BOOST_CHECK_MESSAGE( adapter->FootprintExists( nick, name ),
264 wxString::Format( "FPID '%s:%s' does not resolve after reconciliation",
265 nick, name ) );
266 resolved++;
267 }
268
269 BOOST_CHECK_GT( resolved, 0 );
270 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
271}
272
273
274// Altium footprints carry a source-.PcbLib nick not registered here, so all fall back to the cache
275// and resolve through the adapter
276BOOST_AUTO_TEST_CASE( AltiumBoardResolvesToGeneratedCache )
277{
278 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "altium_fpreconcile" ) );
279 stageProject( projectDir.Path(), wxS( "altium_fpreconcile" ) );
281
282 std::string dataPath =
283 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
284
286 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
287 board->SetProject( &project );
288 plugin.LoadAndAppendBoard( dataPath, *board, nullptr, &project );
289
290 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
291
292 std::vector<FOOTPRINT*> raw = plugin.GetImportedCachedLibraryFootprints();
293 std::vector<std::unique_ptr<FOOTPRINT>> defs;
294
295 for( FOOTPRINT* fp : raw )
296 defs.emplace_back( fp );
297
299 BOOST_REQUIRE( adapter );
300
301 const wxString cacheNick = wxS( "hifive-import-fps" );
302 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
303
305 reconciler.Reconcile( board.get(), std::move( defs ), cacheNick, {} );
306
307 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
308 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
309
310 int resolved = 0;
311
312 for( FOOTPRINT* fp : board->Footprints() )
313 {
314 wxString name = fp->GetFPID().GetUniStringLibItemName();
315
316 if( name.IsEmpty() )
317 continue;
318
319 wxString nick = fp->GetFPID().GetUniStringLibNickname();
320
321 BOOST_CHECK_MESSAGE( adapter->FootprintExists( nick, name ),
322 wxString::Format( "FPID '%s:%s' does not resolve after reconciliation",
323 nick, name ) );
324 resolved++;
325 }
326
327 BOOST_CHECK_GT( resolved, 0 );
328}
329
330
331// A cached footprint must equal every placed instance, or updating from the library edits the board
332// fails on revert, since distinct placed versions then share one cache item
333BOOST_AUTO_TEST_CASE( CachedFootprintsMatchPlacedFootprints )
334{
335 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "altium_fpparity" ) );
336 stageProject( projectDir.Path(), wxS( "altium_fpparity" ) );
338
340 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
341 board->SetProject( &project );
343 + "plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc",
344 *board, nullptr, &project );
345
346 std::vector<std::unique_ptr<FOOTPRINT>> defs;
347
349 defs.emplace_back( fp );
350
352 BOOST_REQUIRE( adapter );
353
354 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
355 reconciler.Reconcile( board.get(), std::move( defs ), wxS( "parity-fps" ), {} );
356
357 int checked = 0;
358 int differing = 0;
359
360 for( FOOTPRINT* fp : board->Footprints() )
361 {
362 std::unique_ptr<FOOTPRINT> cached( adapter->LoadFootprint( fp->GetFPID().GetUniStringLibNickname(),
363 fp->GetFPID().GetUniStringLibItemName(), true ) );
364
365 if( !cached )
366 continue;
367
368 checked++;
369
370 if( fp->FootprintNeedsUpdate( cached.get(), BOARD_ITEM::COMPARE_FLAGS::DRC ) )
371 differing++;
372 }
373
374 // an import with no cache entries would pass unchecked
375 BOOST_CHECK_GT( checked, 0 );
376
377 BOOST_CHECK_EQUAL( differing, 0 );
378}
379
380
381// The same holds when the importer hands over no definitions and the placed footprints are all there is
382// fails on revert, since the first placement is then cached for every same-named one
383BOOST_AUTO_TEST_CASE( CachedFootprintsMatchPlacedFootprintsWithoutDefinitions )
384{
385 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "allegro_fpparity" ) );
386 stageProject( projectDir.Path(), wxS( "allegro_fpparity" ) );
388
389 PCB_IO_ALLEGRO plugin;
390 std::unique_ptr<BOARD> board = plugin.LoadBoard( KI_TEST::GetPcbnewTestDataDir()
391 + "plugins/allegro/boards/ProiectBoard/ProiectBoard.brd",
392 nullptr, &project );
393 BOOST_REQUIRE( board );
394
396 BOOST_REQUIRE( adapter );
397
398 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
399 reconciler.Reconcile( board.get(), {}, wxS( "parity-fps" ), {} );
400
401 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
402
403 int differing = 0;
404
405 for( FOOTPRINT* fp : board->Footprints() )
406 {
407 std::unique_ptr<FOOTPRINT> cached( adapter->LoadFootprint( fp->GetFPID().GetUniStringLibNickname(),
408 fp->GetFPID().GetUniStringLibItemName(), true ) );
409
410 // a footprint with no cache entry would pass unchecked
411 BOOST_REQUIRE( cached );
412
413 if( fp->FootprintNeedsUpdate( cached.get(), BOARD_ITEM::COMPARE_FLAGS::DRC ) )
414 differing++;
415 }
416
417 BOOST_CHECK_EQUAL( differing, 0 );
418}
419
420
421// A source library the project names may lack a footprint the board uses, which must not stop the
422// reconciliation; fails on revert, since the missing footprint is then an error that aborts it
423BOOST_AUTO_TEST_CASE( SourceLibraryMissingAFootprintStillReconciles )
424{
425 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "altium_fpreconcile_missing" ) );
426 stageProject( projectDir.Path(), wxS( "altium_fpreconcile_missing" ) );
428
429 std::string dataPath =
430 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
431
433 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
434 board->SetProject( &project );
435 plugin.LoadAndAppendBoard( dataPath, *board, nullptr, &project );
436
437 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
438
439 std::vector<std::unique_ptr<FOOTPRINT>> defs;
440
442 defs.emplace_back( fp );
443
445 BOOST_REQUIRE( adapter );
446
447 // a real Altium library that holds none of the board's footprints
448 std::optional<LIBRARY_TABLE*> table =
451
452 LIBRARY_TABLE_ROW& row = ( *table )->InsertRow();
453 row.SetNickname( wxS( "other_source" ) );
454 row.SetURI( wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/pcblib/Tracks.v6.PcbLib" ) );
456 BOOST_REQUIRE( ( *table )->Save() );
458
459 const wxString cacheNick = wxS( "hifive-missing-import-fps" );
460 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
461
463
464 BOOST_CHECK_NO_THROW( result = reconciler.Reconcile( board.get(), std::move( defs ), cacheNick,
465 { wxS( "other_source" ) } ) );
466
467 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
468 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
469}
470
471
472// A back side footprint is cached un-flipped and must still read as the same footprint
473// fails on revert, since a half turned rectangular pad then reads as a changed pad
474BOOST_AUTO_TEST_CASE( BackSideFootprintsMatchTheirCachedCopy )
475{
476 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "backside_fpparity" ) );
477 stageProject( projectDir.Path(), wxS( "backside_fpparity" ) );
479
480 PCBNEW_SETTINGS settings;
482 Kiface().InitSettings( &settings );
483
484 struct RESTORE
485 {
486 ~RESTORE() { Kiface().InitSettings( nullptr ); }
487 } restore;
488
490 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
491 board->SetProject( &project );
493 + "plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc",
494 *board, nullptr, &project );
495
496 std::vector<std::unique_ptr<FOOTPRINT>> defs;
497
499 defs.emplace_back( fp );
500
501 int flipped = 0;
502
503 for( FOOTPRINT* fp : board->Footprints() )
504 {
505 if( fp->GetLayer() == F_Cu && fp->Pads().size() > 2 )
506 {
507 fp->Flip( fp->GetPosition(), FLIP_DIRECTION::LEFT_RIGHT );
508 flipped++;
509 }
510 }
511
512 BOOST_REQUIRE_GT( flipped, 0 );
513
515 BOOST_REQUIRE( adapter );
516
517 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
518 reconciler.Reconcile( board.get(), std::move( defs ), wxS( "parity-fps" ), {} );
519
520 // the DRC reads the board back from disk, so compare what was saved, not what was imported
521 PCB_IO_KICAD_SEXPR kicad;
522 wxString boardPath = project.GetProjectPath() + wxS( "backside.kicad_pcb" );
523 kicad.SaveBoard( boardPath, *board );
524 std::unique_ptr<BOARD> reloaded( kicad.LoadBoard( boardPath, nullptr, &project ) );
525 BOOST_REQUIRE( reloaded );
526
527 int checked = 0;
528 int differing = 0;
529
530 for( FOOTPRINT* fp : reloaded->Footprints() )
531 {
532 std::unique_ptr<FOOTPRINT> cached( adapter->LoadFootprint( fp->GetFPID().GetUniStringLibNickname(),
533 fp->GetFPID().GetUniStringLibItemName(), true ) );
534
535 if( !cached )
536 continue;
537
538 checked++;
539
540 if( fp->FootprintNeedsUpdate( cached.get(), BOARD_ITEM::COMPARE_FLAGS::DRC ) )
541 differing++;
542 }
543
544 // an import with no cache entries would pass unchecked
545 BOOST_CHECK_GT( checked, 0 );
546
547 BOOST_CHECK_EQUAL( differing, 0 );
548}
549
550
551// A back side footprint with an asymmetric custom pad is cached un-flipped and must read as unchanged
552// fails on revert, since a left-right flip leaves the cached copy half turned and its pad angle off by 180
553BOOST_AUTO_TEST_CASE( BackSideCustomPadMatchesCachedCopy )
554{
555 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "backside_custompad" ) );
556 stageProject( projectDir.Path(), wxS( "backside_custompad" ) );
558
559 PCBNEW_SETTINGS settings;
561 Kiface().InitSettings( &settings );
562
563 struct RESTORE
564 {
565 ~RESTORE() { Kiface().InitSettings( nullptr ); }
566 } restore;
567
568 BOARD board;
569 board.SetProject( &project );
570
571 FOOTPRINT* footprint = new FOOTPRINT( &board );
572 footprint->SetFPID( LIB_ID( wxS( "" ), wxS( "AsymmetricPad" ) ) );
573 board.Add( footprint );
574
575 PAD* pad = new PAD( footprint );
576 pad->SetNumber( wxS( "1" ) );
577 pad->SetAttribute( PAD_ATTRIB::SMD );
578 pad->SetLayerSet( PAD::SMDMask() );
579 pad->SetShape( F_Cu, PAD_SHAPE::CUSTOM );
580 pad->SetSize( F_Cu, VECTOR2I( 300000, 300000 ) );
581 pad->SetAnchorPadShape( F_Cu, PAD_SHAPE::CIRCLE );
582 pad->AddPrimitivePoly( F_Cu, std::vector<VECTOR2I>{ { 0, 0 }, { 900000, 0 }, { 900000, 300000 }, { 0, 300000 } },
583 0, true );
584 footprint->Add( pad );
585
586 footprint->Flip( footprint->GetPosition(), FLIP_DIRECTION::LEFT_RIGHT );
587 footprint->Rotate( footprint->GetPosition(), ANGLE_90 );
588
590 BOOST_REQUIRE( adapter );
591
592 publishLibrary( project, *adapter, wxS( "backside-custom" ), wxS( "backside-custom" ), *footprint );
593 footprint->SetFPID( LIB_ID( wxS( "backside-custom" ), wxS( "AsymmetricPad" ) ) );
594
595 std::unique_ptr<FOOTPRINT> cached( adapter->LoadFootprint( wxS( "backside-custom" ), wxS( "AsymmetricPad" ), true ) );
596 BOOST_REQUIRE( cached );
597
598 BOOST_CHECK( !footprint->FootprintNeedsUpdate( cached.get(), BOARD_ITEM::COMPARE_FLAGS::DRC ) );
599}
600
601
602// A cached custom padstack lists only the layers it defines, as the cache knows no layer count
603// fails on revert, since the cached pad then carries an entry for every possible copper layer
604BOOST_AUTO_TEST_CASE( CachedCustomPadstackKeepsDefinedLayers )
605{
606 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "cached_custompadstack" ) );
607 stageProject( projectDir.Path(), wxS( "cached_custompadstack" ) );
609
610 BOARD board;
611 board.SetProject( &project );
612
613 FOOTPRINT* footprint = new FOOTPRINT( &board );
614 footprint->SetFPID( LIB_ID( wxS( "" ), wxS( "CustomStack" ) ) );
615 board.Add( footprint );
616
617 PAD* pad = new PAD( footprint );
618 pad->SetNumber( wxS( "1" ) );
619 pad->SetAttribute( PAD_ATTRIB::NPTH );
620 pad->SetLayerSet( PAD::UnplatedHoleMask() );
621 pad->SetSize( F_Cu, VECTOR2I( 1000000, 1000000 ) );
622 pad->SetDrillSize( VECTOR2I( 1000000, 1000000 ) );
623 pad->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
624 footprint->Add( pad );
625
627 BOOST_REQUIRE( adapter );
628
629 publishLibrary( project, *adapter, wxS( "custom-stack" ), wxS( "custom-stack" ), *footprint );
630
631 std::unique_ptr<FOOTPRINT> cached( adapter->LoadFootprint( wxS( "custom-stack" ), wxS( "CustomStack" ), true ) );
632 BOOST_REQUIRE( cached );
633 BOOST_REQUIRE_EQUAL( cached->Pads().size(), 1 );
634
635 BOOST_CHECK_LE( cached->Pads().front()->Padstack().UniqueLayers().size(), 2 );
636}
637
638
639// EasyEDA Pro v3 hands definitions over the standard hook, leaving the source directory untouched
640// fails on revert, since LoadBoard then publishes its own .pretty beside the archive
641BOOST_AUTO_TEST_CASE( EasyEdaProV3BoardResolvesToGeneratedCache )
642{
643 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "easyedapro_v3_fpreconcile" ) );
644 stageProject( projectDir.Path(), wxS( "easyedapro_v3_fpreconcile" ) );
646
647 IMPORTED_BOARD sample = importSampleBoard( project, "easyedapro_v3_fpreconcile_src" );
648 BOARD* board = sample.m_board.get();
649
650 // the importer must not have published anything of its own beside the archive
651 wxFileName srcDir( sample.m_sourceDir->PathStr(), wxEmptyString );
652 wxFileName strayLib( srcDir.GetPath(),
653 EASYEDAPRO::ShortenLibName( wxS( "ProProject_LS2K0300Core_2025-11-14" ) ),
655 BOOST_CHECK_MESSAGE( !wxDir::Exists( strayLib.GetFullPath() ),
656 "LoadBoard wrote a library into the source directory" );
657
659 BOOST_REQUIRE( adapter );
660
661 const wxString cacheNick = wxS( "ls2k0300-import-fps" );
662 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
663
665 reconciler.Reconcile( board, std::move( sample.m_definitions ), cacheNick, {} );
666
667 // the cache lands in the project directory, not next to the source archive
668 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
669 BOOST_CHECK_GT( result.m_savedToCache, 0 );
670
671 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
673 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
674 "Generated .pretty was not published into the project" );
675
676 LIBRARY_TABLE* projectTable = adapter->ProjectTable().value_or( nullptr );
677 BOOST_REQUIRE( projectTable );
678 BOOST_CHECK( projectTable->HasRow( cacheNick ) );
679
680 int resolved = 0;
681
682 for( FOOTPRINT* fp : board->Footprints() )
683 {
684 wxString name = fp->GetFPID().GetUniStringLibItemName();
685
686 if( name.IsEmpty() )
687 continue;
688
689 wxString nick = fp->GetFPID().GetUniStringLibNickname();
690
691 BOOST_CHECK_MESSAGE( adapter->FootprintExists( nick, name ),
692 wxString::Format( "FPID '%s:%s' does not resolve after reconciliation",
693 nick, name ) );
694 resolved++;
695 }
696
697 BOOST_CHECK_GT( resolved, 0 );
698 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
699}
700
701
702// An unrelated library that happens to carry the nickname the importer emitted must not swallow
703// the imported definition; without the provenance check the footprint relinks to the wrong part
704BOOST_AUTO_TEST_CASE( CollidingNicknameDoesNotStealTheLink )
705{
706 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "fpreconcile_collide" ) );
707 stageProject( projectDir.Path(), wxS( "fpreconcile_collide" ) );
709
710 IMPORTED_BOARD sample = importSampleBoard( project, "fpreconcile_collide_src" );
711 COLLISION_CANDIDATE candidate = findCandidate( sample );
712 BOOST_REQUIRE( candidate.m_footprint );
713
715 BOOST_REQUIRE( adapter );
716
717 // a padless namesake registered under the importer's nickname: same name, different part
718 FOOTPRINT impostor( nullptr );
719 impostor.SetFPID( LIB_ID( candidate.m_nickname, candidate.m_name ) );
720 BOOST_REQUIRE( impostor.Pads().empty() );
721 publishLibrary( project, *adapter, candidate.m_nickname, wxS( "impostor" ), impostor );
722
723 const wxString cacheNick = wxS( "collide-import-fps" );
724 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
725
727 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
728
729 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
730
731 // the imported definition is kept in the cache rather than dropped for the namesake
732 BOOST_CHECK_EQUAL( candidate.m_footprint->GetFPID().GetUniStringLibNickname(), cacheNick );
733
734 std::unique_ptr<FOOTPRINT> linked( adapter->LoadFootprint( cacheNick, candidate.m_name,
735 true ) );
736 BOOST_REQUIRE( linked );
737 BOOST_CHECK_GT( linked->Pads().size(), 0 );
738}
739
740
741// Two source libraries supplying different footprints under one bare name must both survive the
742// cache; keying the cache by the bare name alone dropped the second and relinked its instance
743BOOST_AUTO_TEST_CASE( SameNameFromDifferentLibrariesKeepsBothDefinitions )
744{
745 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "fpreconcile_namecollide" ) );
746 stageProject( projectDir.Path(), wxS( "fpreconcile_namecollide" ) );
748
749 std::string dataPath =
750 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
751
753 std::unique_ptr<BOARD> source = std::make_unique<BOARD>();
754 source->SetProject( &project );
755 plugin.LoadAndAppendBoard( dataPath, *source, nullptr, &project );
756
757 // two real imported footprints that a pad count tells apart
758 FOOTPRINT* firstSource = nullptr;
759 FOOTPRINT* secondSource = nullptr;
760
761 for( FOOTPRINT* fp : source->Footprints() )
762 {
763 if( fp->Pads().empty() )
764 continue;
765
766 if( !firstSource )
767 firstSource = fp;
768 else if( fp->Pads().size() != firstSource->Pads().size() )
769 secondSource = fp;
770
771 if( secondSource )
772 break;
773 }
774
775 BOOST_REQUIRE( firstSource );
776 BOOST_REQUIRE( secondSource );
777
778 const wxString sharedName = wxS( "SHARED_FP" );
779 const size_t firstPads = firstSource->Pads().size();
780 const size_t secondPads = secondSource->Pads().size();
781
782 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
783 board->SetProject( &project );
784
785 std::vector<std::unique_ptr<FOOTPRINT>> defs;
786
787 // the same bare name under two source libraries, both placed and defined
788 auto place = [&]( const FOOTPRINT* aSource, const wxString& aNickname )
789 {
790 FOOTPRINT* placed = static_cast<FOOTPRINT*>( aSource->Clone() );
791 placed->SetFPID( LIB_ID( aNickname, sharedName ) );
792 board->Add( placed, ADD_MODE::APPEND );
793
794 std::unique_ptr<FOOTPRINT> def( static_cast<FOOTPRINT*>( aSource->Clone() ) );
795 def->SetFPID( LIB_ID( aNickname, sharedName ) );
796 defs.push_back( std::move( def ) );
797
798 return placed;
799 };
800
801 FOOTPRINT* firstPlaced = place( firstSource, wxS( "libAlpha" ) );
802 FOOTPRINT* secondPlaced = place( secondSource, wxS( "libBeta" ) );
803
805 BOOST_REQUIRE( adapter );
806
807 const wxString cacheNick = wxS( "namecollide-import-fps" );
809 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath(), reporter );
810
812 reconciler.Reconcile( board.get(), std::move( defs ), cacheNick, {} );
813
814 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
815 BOOST_CHECK_EQUAL( result.m_savedToCache, 2 );
816
817 LIB_ID firstId = firstPlaced->GetFPID();
818 LIB_ID secondId = secondPlaced->GetFPID();
819
820 BOOST_CHECK_EQUAL( firstId.GetUniStringLibNickname(), cacheNick );
821 BOOST_CHECK_EQUAL( secondId.GetUniStringLibNickname(), cacheNick );
822 BOOST_CHECK_MESSAGE( firstId.GetUniStringLibItemName() != secondId.GetUniStringLibItemName(),
823 "Footprints from two source libraries share one cache item name" );
824
825 // each instance still resolves to the footprint it was imported as
826 std::unique_ptr<FOOTPRINT> firstLinked(
827 adapter->LoadFootprint( cacheNick, firstId.GetUniStringLibItemName(), true ) );
828 std::unique_ptr<FOOTPRINT> secondLinked(
829 adapter->LoadFootprint( cacheNick, secondId.GetUniStringLibItemName(), true ) );
830
831 BOOST_REQUIRE( firstLinked );
832 BOOST_REQUIRE( secondLinked );
833 BOOST_CHECK_EQUAL( firstLinked->Pads().size(), firstPads );
834 BOOST_CHECK_EQUAL( secondLinked->Pads().size(), secondPads );
835
836 // the user is told which footprint the cache renamed
837 const wxString renamed = firstId.GetUniStringLibItemName() == sharedName
838 ? secondId.GetUniStringLibItemName()
839 : firstId.GetUniStringLibItemName();
840
841 BOOST_CHECK_MESSAGE( reporter.GetMessages().Contains(
842 wxString::Format( wxS( "renamed to '%s'" ), renamed ) ),
843 "Cache rename was not reported" );
844}
845
846
847// A project row already owning the cache nickname is a user library even when no .pretty sits at
848// the generated path, so publishing must not rewrite its URI
849BOOST_AUTO_TEST_CASE( ExistingUserRowIsNotRepurposed )
850{
851 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "fpreconcile_row" ) );
852 stageProject( projectDir.Path(), wxS( "fpreconcile_row" ) );
854
855 IMPORTED_BOARD sample = importSampleBoard( project, "fpreconcile_row_src" );
856
858 BOOST_REQUIRE( adapter );
859
860 const wxString cacheNick = wxS( "row-import-fps" );
861
862 // the user's row owns the nickname but points at a library of its own
863 FOOTPRINT owned( nullptr );
864 owned.SetFPID( LIB_ID( cacheNick, wxS( "UserPart" ) ) );
865 publishLibrary( project, *adapter, cacheNick, wxS( "user-owned" ), owned );
866
867 LIBRARY_TABLE* table = adapter->ProjectTable().value_or( nullptr );
869
870 LIBRARY_TABLE_ROW* row = table->Row( cacheNick ).value_or( nullptr );
871 BOOST_REQUIRE( row );
872
873 const wxString uriBefore = row->URI();
874
875 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
876
878 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
879
880 BOOST_CHECK( result.m_cacheNickname.IsEmpty() );
881 BOOST_CHECK_EQUAL( row->URI(), uriBefore );
882
883 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
885 BOOST_CHECK_MESSAGE( !wxDir::Exists( prettyDir.GetFullPath() ),
886 "Published a cache over a nickname the user already owns" );
887}
888
889
890// regression gate for a netlist of reconciled FPIDs applying via BOARD_NETLIST_UPDATER with 0 errors
891// and no not-found, over the same adapter path Update PCB from Schematic uses
892BOOST_AUTO_TEST_CASE( ReconciledFootprintsResolveViaNetlistUpdater )
893{
894 KI_TEST::SCOPED_TEMP_DIR projectDir( wxS( "eagle_netlist_roundtrip" ) );
895 stageProject( projectDir.Path(), wxS( "eagle_netlist_roundtrip" ) );
897
898 wxFileName brdFn( KI_TEST::GetEeschemaTestDataDir() );
899 brdFn.AppendDir( wxS( "io" ) );
900 brdFn.AppendDir( wxS( "eagle" ) );
901 brdFn.SetFullName( wxS( "eagle-import-testfile.brd" ) );
902 BOOST_REQUIRE( brdFn.FileExists() );
903
904 PCB_IO_EAGLE plugin;
905 std::unique_ptr<BOARD> imported = std::make_unique<BOARD>();
906 imported->SetProject( &project );
907 plugin.LoadAndAppendBoard( brdFn.GetFullPath(), *imported, nullptr, &project );
908
909 std::vector<FOOTPRINT*> raw = plugin.GetImportedCachedLibraryFootprints();
910 std::vector<std::unique_ptr<FOOTPRINT>> defs;
911
912 for( FOOTPRINT* fp : raw )
913 defs.emplace_back( fp );
914
916 BOOST_REQUIRE( adapter );
917
918 const wxString cacheNick = wxS( "eagle_test-import-fps" );
919 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
920 reconciler.Reconcile( imported.get(), std::move( defs ), cacheNick, {} );
921
922 // netlist of distinct reconciled FPIDs, one fresh component each
924 std::set<wxString> seen;
925 int expectedComponents = 0;
926
927 for( FOOTPRINT* fp : imported->Footprints() )
928 {
929 LIB_ID fpid = fp->GetFPID();
930
931 if( fpid.GetUniStringLibItemName().IsEmpty() || !seen.insert( fpid.GetUniStringLibId() ).second )
932 continue;
933
934 wxString ref = wxString::Format( wxS( "U%d" ), ++expectedComponents );
935 netlist.AddComponent(
936 new COMPONENT( fpid, ref, ref, KIID_PATH(), std::vector<KIID>{ KIID() } ) );
937 }
938
939 BOOST_REQUIRE_GT( expectedComponents, 0 );
940
941 // updater must load each footprint from the reconciled lib onto a fresh board
942 std::unique_ptr<BOARD> target = std::make_unique<BOARD>();
943 target->SetProject( &project );
944
945 TOOL_MANAGER toolMgr;
946 toolMgr.SetEnvironment( target.get(), nullptr, nullptr, nullptr, nullptr );
947 toolMgr.RegisterTool( new KI_TEST::DUMMY_TOOL() );
948
950 BOARD_NETLIST_UPDATER updater( &toolMgr, target.get() );
951 updater.SetReporter( &reporter );
952 updater.SetIsDryRun( false );
953 updater.SetWillDrag( false );
954 updater.SetReplaceFootprints( false );
955 updater.SetDeleteUnusedFootprints( false );
956
957 BOOST_REQUIRE( updater.UpdateNetlist( netlist ) );
958
959 BOOST_CHECK_EQUAL( updater.GetErrorCount(), 0 );
960 BOOST_CHECK_MESSAGE( !reporter.GetMessages().Lower().Contains( wxS( "not found" ) ),
961 "Netlist updater reported a footprint not found after reconciliation" );
962 BOOST_CHECK_EQUAL( static_cast<int>( target->Footprints().size() ), expectedComponents );
963}
964
965
const char * name
Update the BOARD with a new netlist.
void SetReporter(REPORTER *aReporter)
bool UpdateNetlist(NETLIST &aNetlist)
Update the board's components according to the new netlist.
void SetWillDrag(bool aWillDrag)
void SetIsDryRun(bool aEnabled)
Enable dry run mode (just report, no changes to PCB).
void SetDeleteUnusedFootprints(bool aEnabled)
void SetReplaceFootprints(bool aEnabled)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
const FOOTPRINTS & Footprints() const
Definition board.h:465
void SetProject(PROJECT *aProject, bool aReferenceOnly=false)
Link a board to a given project.
Definition board.cpp:379
Frame-independent, non-interactive service that reconciles the footprint-library references of a fres...
FOOTPRINT_IMPORT_RECONCILE_RESULT Reconcile(BOARD *aBoard, std::vector< std::unique_ptr< FOOTPRINT > > aDefinitions, const wxString &aCacheNickname, const std::vector< wxString > &aSourceLibNicknames)
Reconcile aBoard 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...
FOOTPRINT * LoadFootprint(const wxString &aNickname, const wxString &aName, bool aKeepUUID)
Load a FOOTPRINT having aName from the library given by aNickname.
std::optional< LIB_STATUS > LoadOne(LIB_DATA *aLib) override
Loads or reloads the given library, if it exists.
bool FootprintExists(const wxString &aNickname, const wxString &aName)
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:418
bool FootprintNeedsUpdate(const FOOTPRINT *aLibFP, int aCompareFlags=0, REPORTER *aReporter=nullptr)
Return true if a board footprint differs from the library version.
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
std::deque< PAD * > & Pads()
Definition footprint.h:355
const LIB_ID & GetFPID() const
Definition footprint.h:417
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
VECTOR2I GetPosition() const override
Definition footprint.h:379
void InitSettings(APP_SETTINGS_BASE *aSettings)
Definition kiface_base.h:94
Definition kiid.h:46
wxString PathStr() const
Get the path to the temporary directory as a wxString.
Definition file_utils.h:64
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
Definition file_utils.h:61
std::optional< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
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 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.
Definition lib_id.h:45
wxString GetUniStringLibId() const
Definition lib_id.h:144
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
const wxString GetUniStringLibNickname() const
Definition lib_id.h:84
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:172
Definition pad.h:61
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:636
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:622
FLIP_DIRECTION m_FlipDirection
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 PCB_IO API or a portion o...
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
A PCB_IO derivation for saving and loading Pcbnew s-expression formatted files.
void SaveBoard(const wxString &aFileName, BOARD &aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:54
@ ALTIUM_DESIGNER
Definition pcb_io_mgr.h:59
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a PCB_IO which the caller can use to import, export, save, or load design documents.
static const wxString ShowType(PCB_FILE_T aFileType)
Return a brief name for a plugin given aFileType enum.
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.
Definition pcb_io.cpp:85
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:125
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:276
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.
Master controller class:
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
A wrapper for reporting to a wxString object.
Definition reporter.h:242
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
Definition cvpcb.cpp:165
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
static const std::string KiCadFootprintLibPathExtension
std::string source
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
PROJECT & Prj()
Definition kicad.cpp:737
@ F_Cu
Definition layer_ids.h:60
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
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.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
Outcome of a post-import footprint-library reconciliation pass.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(EagleBoardResolvesToGeneratedCache)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string netlist
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708