50#include <wx/filename.h>
51#include <wx/process.h>
52#include <wx/txtstrm.h>
64bool IsXmllintAvailable()
68 int result = wxExecute(
"xmllint --version",
output, errors, wxEXEC_SYNC );
77wxString ValidateXmlWithXsd(
const wxString& aXmlPath,
const wxString& aXsdPath )
79 wxString cmd = wxString::Format(
"xmllint --noout --schema \"%s\" \"%s\"",
84 int result = wxExecute( cmd,
output, errors, wxEXEC_SYNC );
90 for(
const wxString& line : errors )
91 errorMsg += line +
"\n";
103bool FileContainsPattern(
const wxString& aFilePath,
const wxString& aPattern )
105 std::ifstream file( aFilePath.ToStdString() );
107 if( !file.is_open() )
110 std::stringstream buffer;
111 buffer << file.rdbuf();
112 std::string content = buffer.str();
114 return content.find( aPattern.ToStdString() ) != std::string::npos;
122static const std::vector<std::string> VALIDATION_TEST_BOARDS = {
123 "custom_pads.kicad_pcb",
124 "notched_zones.kicad_pcb",
126 "tracks_arcs_vias.kicad_pcb",
127 "issue7241.kicad_pcb",
128 "issue10906.kicad_pcb",
129 "issue22798.kicad_pcb",
130 "padstacks_complex.kicad_pcb",
131 "issue12609.kicad_pcb",
132 "issue22794.kicad_pcb",
150 if( wxFileExists(
path ) )
151 wxRemoveFile(
path );
157 wxString
path = wxFileName::CreateTempFileName( wxT(
"kicad_ipc2581_test" ) );
159 if( !aSuffix.IsEmpty() )
162 path += wxT(
".xml" );
170 wxString filename = ( aVersion ==
'C' ) ? wxT(
"IPC-2581C.xsd" ) : wxT(
"IPC-2581B1.xsd" );
174 std::unique_ptr<BOARD>
LoadBoard(
const std::string& aRelativePath )
177 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
179 m_kicadPlugin.LoadBoard( fullPath, board.get(),
nullptr,
nullptr );
188 std::map<std::string, UTF8> props;
189 props[
"units"] =
"mm";
190 props[
"version"] = std::string( 1, aVersion );
191 props[
"sigfig"] =
"3";
197 catch(
const std::exception& e )
199 aErrorMsg = wxString::Format(
"Export failed: %s", e.what() );
203 if( !wxFileExists( tempPath ) )
205 aErrorMsg =
"Export file was not created";
213 if( wxFileExists( xsdPath ) )
215 aErrorMsg = ValidateXmlWithXsd( tempPath, xsdPath );
216 return aErrorMsg.IsEmpty();
245 std::unique_ptr<BOARD> board = LoadBoard(
"issue3812.kicad_pcb" );
250 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
254 wxString tempPath = CreateTempFile();
256 std::map<std::string, UTF8> props;
257 props[
"units"] =
"mm";
258 props[
"version"] =
"C";
259 props[
"sigfig"] =
"3";
261 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
268 "SurfaceFinish element should be present" );
270 "SurfaceFinish type should be ENIG-N" );
274 "COATING_TOP layer should be present" );
276 "COATING_BOTTOM layer should be present" );
277 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"layerFunction=\"COATINGCOND\"" ) ),
278 "Coating layers should have layerFunction=COATINGCOND" );
291 std::unique_ptr<BOARD> board = LoadBoard(
"vme-wren.kicad_pcb" );
296 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
300 wxString tempPath = CreateTempFile();
302 std::map<std::string, UTF8> props;
303 props[
"units"] =
"mm";
304 props[
"version"] =
"C";
305 props[
"sigfig"] =
"3";
307 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
313 "SurfaceFinish element should not be present for 'None' finish" );
317 "COATING_TOP layer should not be present" );
319 "COATING_BOTTOM layer should not be present" );
334 if( !m_xmllintAvailable )
336 BOOST_WARN_MESSAGE(
false,
"xmllint not available, skipping schema validation tests" );
340 wxString xsdPath = GetXsdPath(
'B' );
342 if( !wxFileExists( xsdPath ) )
344 BOOST_WARN_MESSAGE(
false,
"IPC-2581B1.xsd not found, skipping schema validation" );
348 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
352 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
356 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
361 bool valid = ExportAndValidate( board.get(),
'B', errorMsg );
363 BOOST_CHECK_MESSAGE( valid,
"IPC-2581B validation failed for " + boardFile +
": " + errorMsg );
377 if( !m_xmllintAvailable )
379 BOOST_WARN_MESSAGE(
false,
"xmllint not available, skipping schema validation tests" );
383 wxString xsdPath = GetXsdPath(
'C' );
385 if( !wxFileExists( xsdPath ) )
387 BOOST_WARN_MESSAGE(
false,
"IPC-2581C.xsd not found, skipping schema validation" );
391 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
395 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
399 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
404 bool valid = ExportAndValidate( board.get(),
'C', errorMsg );
406 BOOST_CHECK_MESSAGE( valid,
"IPC-2581C validation failed for " + boardFile +
": " + errorMsg );
421 static const std::vector<std::string> complexBoards = {
422 "intersectingzones.kicad_pcb",
423 "custom_pads.kicad_pcb",
426 for(
const std::string& boardFile : complexBoards )
430 std::unique_ptr<BOARD> board = LoadBoard( boardFile );
434 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
439 for(
char version : {
'B',
'C' } )
444 bool valid = ExportAndValidate( board.get(), version, errorMsg );
447 wxString::Format(
"Export/validation failed for %s version %c: %s",
448 boardFile, version, errorMsg ) );
466 std::unique_ptr<BOARD> board = LoadBoard(
"issue16658/issue16658.kicad_pcb" );
471 bool hasSmtPad =
false;
473 for(
FOOTPRINT* fp : board->Footprints() )
475 for(
PAD*
pad : fp->Pads() )
496 BOOST_REQUIRE_MESSAGE( hasSmtPad,
"Test board should have SMD pads" );
499 wxString tempPath = CreateTempFile();
501 std::map<std::string, UTF8> props;
502 props[
"units"] =
"mm";
503 props[
"version"] =
"C";
504 props[
"sigfig"] =
"3";
506 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
512 bool hasFMaskLayer = FileContainsPattern( tempPath, wxT(
"layerRef=\"F.Mask\"" ) )
513 || FileContainsPattern( tempPath, wxT(
"layerRef=\"TSM\"" ) );
516 "IPC-2581 export should contain F.Mask layer features for SMD pads" );
519 bool hasLayerFeature = FileContainsPattern( tempPath, wxT(
"<LayerFeature" ) );
520 BOOST_CHECK_MESSAGE( hasLayerFeature,
"IPC-2581 export should contain LayerFeature elements" );
533 std::unique_ptr<BOARD> board = LoadBoard(
"padstacks_complex.kicad_pcb" );
536 for(
char version : {
'B',
'C' } )
540 wxString tempPath = CreateTempFile();
542 std::map<std::string, UTF8> props;
543 props[
"units"] =
"mm";
544 props[
"version"] = std::string( 1, version );
545 props[
"sigfig"] =
"3";
547 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
551 "Empty refDes attribute found" );
553 "Empty RefDes/@name attribute found" );
555 "Empty PinRef/@componentRef attribute found" );
573 std::unique_ptr<BOARD> board = LoadBoard(
"issue12609.kicad_pcb" );
576 wxString tempPath = CreateTempFile();
578 std::map<std::string, UTF8> props;
579 props[
"units"] =
"mm";
580 props[
"version"] =
"C";
581 props[
"sigfig"] =
"3";
583 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
589 std::ifstream xmlFile( tempPath.ToStdString() );
592 std::string xmlContent( ( std::istreambuf_iterator<char>( xmlFile ) ),
593 std::istreambuf_iterator<char>() );
596 size_t c5Pos = xmlContent.find(
"refDes=\"C5\"" );
598 if( c5Pos == std::string::npos )
599 c5Pos = xmlContent.find(
"refDes=\"NOREF_" );
601 BOOST_REQUIRE_MESSAGE( c5Pos != std::string::npos,
602 "C5 component should exist in export" );
605 std::string c5Region = xmlContent.substr( c5Pos, 200 );
607 || c5Region.find(
"rotation=\"90.00\"" ) != std::string::npos,
608 "C5 component rotation should be 90, not inverted. Region: "
618 std::unique_ptr<BOARD> board = LoadBoard(
"issue12609.kicad_pcb" );
621 wxString tempPath = CreateTempFile();
623 std::map<std::string, UTF8> props;
624 props[
"units"] =
"mm";
625 props[
"version"] =
"C";
626 props[
"sigfig"] =
"3";
628 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
631 bool hasBom = FileContainsPattern( tempPath, wxT(
"<Bom " ) );
632 bool hasBomRef = FileContainsPattern( tempPath, wxT(
"<BomRef " ) );
637 "Content should have BomRef when Bom section is present" );
657 std::unique_ptr<BOARD> board = LoadBoard(
"test_copper_graphics.kicad_pcb" );
660 for(
char version : {
'B',
'C' } )
665 bool valid = ExportAndValidate( board.get(), version, errorMsg );
668 wxString::Format(
"Knockout text export should be schema-valid "
669 "(version %c): %s", version, errorMsg ) );
707 via->SetSecondaryDrillStartLayer(
F_Cu );
708 via->SetSecondaryDrillEndLayer(
In3_Cu );
712 wxString tempPath = CreateTempFile();
713 std::map<std::string, UTF8> props;
714 props[
"units"] =
"mm";
715 props[
"version"] =
"C";
716 props[
"sigfig"] =
"4";
718 BOOST_REQUIRE_NO_THROW( m_ipc2581Plugin.SaveBoard( tempPath, &board, &props ) );
722 FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"START_LAYER\"" ) ),
723 "Backdrill spec should declare a START_LAYER child" );
725 FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"MUST_NOT_CUT_LAYER\"" ) ),
726 "Backdrill spec should declare a MUST_NOT_CUT_LAYER child" );
728 FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"MAX_STUB_LENGTH\"" ) ),
729 "Backdrill spec should declare a MAX_STUB_LENGTH child" );
734 "Backdrill must convey layers through Property layerOrGroupRef" );
736 "Backdrill should not use schema-invalid startLayerRef attribute" );
738 "Backdrill should not use schema-invalid mustNotCutLayerRef attribute" );
740 "Backdrill should not use schema-invalid maxStubLength attribute" );
742 "Backdrill should not use the non-standard postMachining attribute" );
747 FileContainsPattern( tempPath, wxT(
"layerOrGroupRef=\"In4.Cu\"" ) ),
748 "Front backdrill must-not-cut layer should be In4.Cu" );
753 "Exporter should not emit a primary backdrill spec slot" );
758 FileContainsPattern( tempPath, wxT(
"<Backdrill type=\"OTHER\"" ) ),
759 "Post-machining hint should produce a Backdrill type=OTHER child" );
761 FileContainsPattern( tempPath, wxT(
"comment=\"post-machining=COUNTERSINK\"" ) ),
762 "OTHER Backdrill should carry the post-machining comment" );
constexpr EDA_IU_SCALE pcbIUScale
General utilities for PCB file IO for QA programs.
Container for design settings for a BOARD object.
BOARD_STACKUP & GetStackupDescriptor()
Manage layers needed to make a physical board.
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetCopperLayerCount(int aCount)
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
@ SMD
Smd pad, appears on the solder paste layer (default)
wxString GetXsdPath(char aVersion)
PCB_IO_IPC2581 m_ipc2581Plugin
bool ExportAndValidate(BOARD *aBoard, char aVersion, wxString &aErrorMsg)
std::vector< wxString > m_tempFiles
PCB_IO_KICAD_SEXPR m_kicadPlugin
~IPC2581_EXPORT_FIXTURE()
wxString CreateTempFile(const wxString &aSuffix=wxT(""))
std::unique_ptr< BOARD > LoadBoard(const std::string &aRelativePath)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(SurfaceFinishExport)
Test that surface finish is exported correctly (Issue #22690)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I