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",
145 if( wxFileExists(
path ) )
146 wxRemoveFile(
path );
152 wxString
path = wxFileName::CreateTempFileName( wxT(
"kicad_ipc2581_test" ) );
154 if( !aSuffix.IsEmpty() )
157 path += wxT(
".xml" );
165 wxString filename = ( aVersion ==
'C' ) ? wxT(
"IPC-2581C.xsd" ) : wxT(
"IPC-2581B1.xsd" );
169 std::unique_ptr<BOARD>
LoadBoard(
const std::string& aRelativePath )
172 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
174 m_kicadPlugin.LoadBoard( fullPath, board.get(),
nullptr,
nullptr );
183 std::map<std::string, UTF8> props;
184 props[
"units"] =
"mm";
185 props[
"version"] = std::string( 1, aVersion );
186 props[
"sigfig"] =
"3";
192 catch(
const std::exception& e )
194 aErrorMsg = wxString::Format(
"Export failed: %s", e.what() );
198 if( !wxFileExists( tempPath ) )
200 aErrorMsg =
"Export file was not created";
208 if( wxFileExists( xsdPath ) )
210 aErrorMsg = ValidateXmlWithXsd( tempPath, xsdPath );
211 return aErrorMsg.IsEmpty();
240 std::unique_ptr<BOARD> board =
LoadBoard(
"issue3812.kicad_pcb" );
245 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
249 wxString tempPath = CreateTempFile();
251 std::map<std::string, UTF8> props;
252 props[
"units"] =
"mm";
253 props[
"version"] =
"C";
254 props[
"sigfig"] =
"3";
256 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
262 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"<SurfaceFinish" ) ),
263 "SurfaceFinish element should be present" );
264 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"<Finish" ) ),
265 "Finish element should be present" );
266 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"type=\"ENIG-N\"" ) ),
267 "Finish type should be ENIG-N" );
270 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"COATING_TOP" ) ),
271 "COATING_TOP layer should be present" );
272 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"COATING_BOTTOM" ) ),
273 "COATING_BOTTOM layer should be present" );
274 BOOST_CHECK_MESSAGE( FileContainsPattern( tempPath, wxT(
"layerFunction=\"COATINGCOND\"" ) ),
275 "Coating layers should have layerFunction=COATINGCOND" );
288 std::unique_ptr<BOARD> board =
LoadBoard(
"vme-wren.kicad_pcb" );
293 const BOARD_STACKUP& stackup = board->GetDesignSettings().GetStackupDescriptor();
297 wxString tempPath = CreateTempFile();
299 std::map<std::string, UTF8> props;
300 props[
"units"] =
"mm";
301 props[
"version"] =
"C";
302 props[
"sigfig"] =
"3";
304 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
309 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"<SurfaceFinish" ) ),
310 "SurfaceFinish element should not be present for 'None' finish" );
313 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"COATING_TOP" ) ),
314 "COATING_TOP layer should not be present" );
315 BOOST_CHECK_MESSAGE( !FileContainsPattern( tempPath, wxT(
"COATING_BOTTOM" ) ),
316 "COATING_BOTTOM layer should not be present" );
331 if( !m_xmllintAvailable )
333 BOOST_WARN_MESSAGE(
false,
"xmllint not available, skipping schema validation tests" );
337 wxString xsdPath = GetXsdPath(
'B' );
339 if( !wxFileExists( xsdPath ) )
341 BOOST_WARN_MESSAGE(
false,
"IPC-2581B1.xsd not found, skipping schema validation" );
345 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
349 std::unique_ptr<BOARD> board =
LoadBoard( boardFile );
353 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
358 bool valid = ExportAndValidate( board.get(),
'B', errorMsg );
360 BOOST_CHECK_MESSAGE( valid,
"IPC-2581B validation failed for " + boardFile +
": " + errorMsg );
374 if( !m_xmllintAvailable )
376 BOOST_WARN_MESSAGE(
false,
"xmllint not available, skipping schema validation tests" );
380 wxString xsdPath = GetXsdPath(
'C' );
382 if( !wxFileExists( xsdPath ) )
384 BOOST_WARN_MESSAGE(
false,
"IPC-2581C.xsd not found, skipping schema validation" );
388 for(
const std::string& boardFile : VALIDATION_TEST_BOARDS )
392 std::unique_ptr<BOARD> board =
LoadBoard( boardFile );
396 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
401 bool valid = ExportAndValidate( board.get(),
'C', errorMsg );
403 BOOST_CHECK_MESSAGE( valid,
"IPC-2581C validation failed for " + boardFile +
": " + errorMsg );
418 static const std::vector<std::string> complexBoards = {
419 "intersectingzones.kicad_pcb",
420 "custom_pads.kicad_pcb",
423 for(
const std::string& boardFile : complexBoards )
427 std::unique_ptr<BOARD> board =
LoadBoard( boardFile );
431 BOOST_WARN_MESSAGE(
false,
"Could not load board: " + boardFile );
436 for(
char version : {
'B',
'C' } )
441 bool valid = ExportAndValidate( board.get(), version, errorMsg );
443 BOOST_CHECK_MESSAGE( valid,
444 wxString::Format(
"Export/validation failed for %s version %c: %s",
445 boardFile, version, errorMsg ) );
463 std::unique_ptr<BOARD> board =
LoadBoard(
"issue16658/issue16658.kicad_pcb" );
468 bool hasSmtPad =
false;
470 for(
FOOTPRINT* fp : board->Footprints() )
472 for(
PAD*
pad : fp->Pads() )
493 BOOST_REQUIRE_MESSAGE( hasSmtPad,
"Test board should have SMD pads" );
496 wxString tempPath = CreateTempFile();
498 std::map<std::string, UTF8> props;
499 props[
"units"] =
"mm";
500 props[
"version"] =
"C";
501 props[
"sigfig"] =
"3";
503 m_ipc2581Plugin.SaveBoard( tempPath, board.get(), &props );
509 bool hasFMaskLayer = FileContainsPattern( tempPath, wxT(
"layerRef=\"F.Mask\"" ) )
510 || FileContainsPattern( tempPath, wxT(
"layerRef=\"TSM\"" ) );
512 BOOST_CHECK_MESSAGE( hasFMaskLayer,
513 "IPC-2581 export should contain F.Mask layer features for SMD pads" );
516 bool hasLayerFeature = FileContainsPattern( tempPath, wxT(
"<LayerFeature" ) );
517 BOOST_CHECK_MESSAGE( hasLayerFeature,
"IPC-2581 export should contain LayerFeature elements" );
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")