48#include <wx/filename.h>
49#include <wx/process.h>
50#include <wx/txtstrm.h>
62bool IsXmllintAvailable()
66 int result = wxExecute(
"xmllint --version", output, errors, wxEXEC_SYNC );
75wxString ValidateXmlWithXsd(
const wxString& aXmlPath,
const wxString& aXsdPath )
77 wxString cmd = wxString::Format(
"xmllint --noout --schema \"%s\" \"%s\"",
82 int result = wxExecute( cmd, output, errors, wxEXEC_SYNC );
88 for(
const wxString& line : errors )
89 errorMsg += line +
"\n";
101bool FileContainsPattern(
const wxString& aFilePath,
const wxString& aPattern )
103 std::ifstream file( aFilePath.ToStdString() );
105 if( !file.is_open() )
108 std::stringstream buffer;
109 buffer << file.rdbuf();
110 std::string content = buffer.str();
112 return content.find( aPattern.ToStdString() ) != std::string::npos;
120static const std::vector<std::string> VALIDATION_TEST_BOARDS = {
121 "custom_pads.kicad_pcb",
122 "notched_zones.kicad_pcb",
124 "tracks_arcs_vias.kicad_pcb",
125 "issue7241.kicad_pcb",
126 "issue10906.kicad_pcb",
127 "issue22798.kicad_pcb",
128 "padstacks_complex.kicad_pcb",
129 "issue12609.kicad_pcb",
147 if( wxFileExists(
path ) )
148 wxRemoveFile(
path );
154 wxString
path = wxFileName::CreateTempFileName( wxT(
"kicad_ipc2581_test" ) );
156 if( !aSuffix.IsEmpty() )
159 path += wxT(
".xml" );
167 wxString filename = ( aVersion ==
'C' ) ? wxT(
"IPC-2581C.xsd" ) : wxT(
"IPC-2581B1.xsd" );
171 std::unique_ptr<BOARD>
LoadBoard(
const std::string& aRelativePath )
174 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
176 m_kicadPlugin.LoadBoard( fullPath, board.get(),
nullptr,
nullptr );
185 std::map<std::string, UTF8> props;
186 props[
"units"] =
"mm";
187 props[
"version"] = std::string( 1, aVersion );
188 props[
"sigfig"] =
"3";
194 catch(
const std::exception& e )
196 aErrorMsg = wxString::Format(
"Export failed: %s", e.what() );
200 if( !wxFileExists( tempPath ) )
202 aErrorMsg =
"Export file was not created";
210 if( wxFileExists( xsdPath ) )
212 aErrorMsg = ValidateXmlWithXsd( tempPath, xsdPath );
213 return aErrorMsg.IsEmpty();
242 std::unique_ptr<BOARD> board =
LoadBoard(
"issue3812.kicad_pcb" );
247 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
251 wxString tempPath = CreateTempFile();
253 std::map<std::string, UTF8> props;
254 props[
"units"] =
"mm";
255 props[
"version"] =
"C";
256 props[
"sigfig"] =
"3";
258 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
264 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"<SurfaceFinish" ) ),
265 "SurfaceFinish element should be present" );
266 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"<Finish" ) ),
267 "Finish element should be present" );
268 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"type=\"ENIG-N\"" ) ),
269 "Finish type should be ENIG-N" );
272 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"COATING_TOP" ) ),
273 "COATING_TOP layer should be present" );
274 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"COATING_BOTTOM" ) ),
275 "COATING_BOTTOM layer should be present" );
276 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"layerFunction=\"COATINGCOND\"" ) ),
277 "Coating layers should have layerFunction=COATINGCOND" );
290 std::unique_ptr<BOARD> board =
LoadBoard(
"vme-wren.kicad_pcb" );
295 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
299 wxString tempPath = CreateTempFile();
301 std::map<std::string, UTF8> props;
302 props[
"units"] =
"mm";
303 props[
"version"] =
"C";
304 props[
"sigfig"] =
"3";
306 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
311 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"<SurfaceFinish" ) ),
312 "SurfaceFinish element should not be present for 'None' finish" );
315 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"COATING_TOP" ) ),
316 "COATING_TOP layer should not be present" );
317 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"COATING_BOTTOM" ) ),
318 "COATING_BOTTOM layer should not be present" );
333 if( !m_xmllintAvailable )
335 BOOST_WARN_MESSAGE(
false,
"xmllint not available, skipping schema validation tests" );
339 wxString xsdPath = GetXsdPath(
'B' );
341 if( !wxFileExists( xsdPath ) )
343 BOOST_WARN_MESSAGE(
false,
"IPC-2581B1.xsd not found, skipping schema validation" );
347 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
351 std::unique_ptr<BOARD> board =
LoadBoard( boardFile );
355 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
360 bool valid = ExportAndValidate( board.get(),
'B', errorMsg );
362 BOOST_CHECK_MESSAGE( valid,
"IPC-2581B validation failed for " + boardFile +
": " + errorMsg );
376 if( !m_xmllintAvailable )
378 BOOST_WARN_MESSAGE(
false,
"xmllint not available, skipping schema validation tests" );
382 wxString xsdPath = GetXsdPath(
'C' );
384 if( !wxFileExists( xsdPath ) )
386 BOOST_WARN_MESSAGE(
false,
"IPC-2581C.xsd not found, skipping schema validation" );
390 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
394 std::unique_ptr<BOARD> board =
LoadBoard( boardFile );
398 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
403 bool valid = ExportAndValidate( board.get(),
'C', errorMsg );
405 BOOST_CHECK_MESSAGE( valid,
"IPC-2581C validation failed for " + boardFile +
": " + errorMsg );
420 static const std::vector<std::string> complexBoards = {
421 "intersectingzones.kicad_pcb",
422 "custom_pads.kicad_pcb",
425 for(
const std::string& boardFile : complexBoards )
429 std::unique_ptr<BOARD> board =
LoadBoard( boardFile );
433 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
438 for(
char version : {
'B',
'C' } )
443 bool valid = ExportAndValidate( board.get(), version, errorMsg );
445 BOOST_CHECK_MESSAGE( valid,
446 wxString::Format(
"Export/validation failed for %s version %c: %s",
447 boardFile, version, errorMsg ) );
465 std::unique_ptr<BOARD> board =
LoadBoard(
"issue16658/issue16658.kicad_pcb" );
470 bool hasSmtPad =
false;
472 for(
FOOTPRINT* fp : board->Footprints() )
474 for(
PAD*
pad : fp->Pads() )
495 BOOST_REQUIRE_MESSAGE( hasSmtPad,
"Test board should have SMD pads" );
498 wxString tempPath = CreateTempFile();
500 std::map<std::string, UTF8> props;
501 props[
"units"] =
"mm";
502 props[
"version"] =
"C";
503 props[
"sigfig"] =
"3";
505 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
511 bool hasFMaskLayer = FileContainsPattern( tempPath, wxT(
"layerRef=\"F.Mask\"" ) )
512 || FileContainsPattern( tempPath, wxT(
"layerRef=\"TSM\"" ) );
514 BOOST_CHECK_MESSAGE( hasFMaskLayer,
515 "IPC-2581 export should contain F.Mask layer features for SMD pads" );
518 bool hasLayerFeature = FileContainsPattern( tempPath, wxT(
"<LayerFeature" ) );
519 BOOST_CHECK_MESSAGE( hasLayerFeature,
"IPC-2581 export should contain LayerFeature elements" );
532 std::unique_ptr<BOARD> board =
LoadBoard(
"padstacks_complex.kicad_pcb" );
535 for(
char version : {
'B',
'C' } )
539 wxString tempPath = CreateTempFile();
541 std::map<std::string, UTF8> props;
542 props[
"units"] =
"mm";
543 props[
"version"] = std::string( 1, version );
544 props[
"sigfig"] =
"3";
546 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
549 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"refDes=\"\"" ) ),
550 "Empty refDes attribute found" );
551 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"<RefDes name=\"\"" ) ),
552 "Empty RefDes/@name attribute found" );
553 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"componentRef=\"\"" ) ),
554 "Empty PinRef/@componentRef attribute found" );
572 std::unique_ptr<BOARD> board =
LoadBoard(
"issue12609.kicad_pcb" );
575 wxString tempPath = CreateTempFile();
577 std::map<std::string, UTF8> props;
578 props[
"units"] =
"mm";
579 props[
"version"] =
"C";
580 props[
"sigfig"] =
"3";
582 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
588 std::ifstream xmlFile( tempPath.ToStdString() );
591 std::string xmlContent( ( std::istreambuf_iterator<char>( xmlFile ) ),
592 std::istreambuf_iterator<char>() );
595 size_t c5Pos = xmlContent.find(
"refDes=\"C5\"" );
597 if( c5Pos == std::string::npos )
598 c5Pos = xmlContent.find(
"refDes=\"NOREF_" );
600 BOOST_REQUIRE_MESSAGE( c5Pos != std::string::npos,
601 "C5 component should exist in export" );
604 std::string c5Region = xmlContent.substr( c5Pos, 200 );
605 BOOST_CHECK_MESSAGE( c5Region.find(
"rotation=\"90.0\"" ) != std::string::npos
606 || c5Region.find(
"rotation=\"90.00\"" ) != std::string::npos,
607 "C5 component rotation should be 90, not inverted. Region: "
617 std::unique_ptr<BOARD> board =
LoadBoard(
"issue12609.kicad_pcb" );
620 wxString tempPath = CreateTempFile();
622 std::map<std::string, UTF8> props;
623 props[
"units"] =
"mm";
624 props[
"version"] =
"C";
625 props[
"sigfig"] =
"3";
627 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
630 bool hasBom = FileContainsPattern( tempPath, wxT(
"<Bom " ) );
631 bool hasBomRef = FileContainsPattern( tempPath, wxT(
"<BomRef " ) );
635 BOOST_CHECK_MESSAGE( hasBomRef,
636 "Content should have BomRef when Bom section is present" );
General utilities for PCB file IO for QA programs.
Manage layers needed to make a physical board.
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
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)
BOARD * LoadBoard(const wxString &aFileName, bool aSetActive)
Loads a board from file This function identifies the file type by extension and determines the correc...
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_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")