32#include <boost/test/data/test_case.hpp>
60static std::vector<uint8_t>
loadDataByUri(
const std::string& aDataSource )
66 std::string
path, protocol;
67 const std::string sep =
"://";
69 size_t sepPos = aDataSource.find( sep );
70 if( sepPos != std::string::npos )
72 protocol = aDataSource.substr( 0, sepPos );
73 path = aDataSource.substr( sepPos + sep.size() );
78 throw std::runtime_error(
"Unsupported data source URI (missing protocol): " + aDataSource );
81 if( protocol ==
"file" )
87 throw std::runtime_error(
"Unsupported data source protocol: " + protocol );
94 static const std::map<std::string, ALLEGRO::FMT_VER> fmtVerStrMap{
107 auto it = fmtVerStrMap.find( aFmtVerStr );
108 if( it != fmtVerStrMap.end() )
118 const nlohmann::json& headerTestEntry )
120 std::unique_ptr<HEADER_TEST_INFO> headerTest =
nullptr;
122 if( !headerTestEntry.is_object() )
124 throw std::runtime_error(
"Header test entry is not a valid JSON object" );
128 const std::string headerDataUri = std::string(
"file://" ) + boardDir +
"header.bin";
130 return std::make_unique<HEADER_TEST_INFO>( headerDataUri, headerTestEntry.value(
"skip",
false ) );
136 if( !blockTestEntry.contains(
"type" ) || !blockTestEntry[
"type"].is_string() )
138 throw std::runtime_error(
"Block test entry is missing a valid 'type' field" );
141 const std::string blockTypeStr = blockTestEntry[
"type"];
142 const uint8_t blockType = std::stoul( blockTypeStr,
nullptr, 0 );
144 const std::string block_offset = blockTestEntry[
"offset"];
145 const size_t blockOffset = std::stoul( block_offset,
nullptr, 0 );
147 const bool skipBlock = blockTestEntry.value(
"skip",
false );
150 wxString blockDataLoc = wxString::Format(
"0x%02x_0x%08zx.bin", blockType, blockOffset );
152 const std::string blockDataUri = std::string(
"file://" ) + boardDir + blockDataLoc.ToStdString();
193 void RunHeaderTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
201 std::unique_ptr<HEADER_TEST_INFO> headerTest =
nullptr;
203 if( aBoardTestJson.contains(
"header" ) && aBoardTestJson[
"header"].is_object() )
210 if( headerTest->m_Skip )
217 std::vector<uint8_t> headerData =
loadDataByUri( headerTest->m_HeaderDataSource );
218 FILE_STREAM fileStream( headerData.data(), headerData.size() );
221 std::unique_ptr<FILE_HEADER> header = parser.
ParseHeader();
242 void RunBlockTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson,
243 const nlohmann::json& aBlockTestJson )
248 BOOST_TEST_CONTEXT( wxString::Format(
"Testing block type %#02x at offset %#010zx from board %s",
259 FILE_STREAM fileStream( data.data(), data.size() );
263 bool endOfObjectsMarker =
false;
264 std::unique_ptr<BLOCK_BASE> block = parser.
ParseBlock( endOfObjectsMarker );
286 std::unique_ptr<ALLEGRO::DB_OBJ> dbObj = objFactory.
CreateObject( *block );
303 std::ostringstream ss;
304 ss <<
"Expected no errors, but found " << aReporter.
GetErrorCount() <<
" errors:";
310 ss <<
"\n " << msg.text;
319void RunBoardLoad(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
321 BOARD* board =
nullptr;
325 if( aBoardTestJson.contains(
"boardFile" ) )
327 const std::string boardFilePath =
330 if( !std::filesystem::exists( boardFilePath ) )
332 BOOST_FAIL(
"Board file does not exist: " + boardFilePath );
339 const bool expectLoadFailure = aBoardTestJson.value(
"expectLoadFailure",
false );
341 if( expectLoadFailure )
343 BOOST_CHECK( board ==
nullptr );
349 BOOST_CHECK( board !=
nullptr );
358 std::ostringstream ss;
365 ss <<
"\n " << msg.text;
381 if( !aBoardTestJson.contains(
"boardFile" ) )
383 BOOST_FAIL(
"Board test JSON does not contain a 'boardFile' entry" );
387 const std::string boardFilePath =
394 BOOST_FAIL(
"Failed to load cached board for expectations test: " + boardFilePath );
400 std::unique_ptr<BOARD_EXPECTATION_TEST> boardExpectationTest =
nullptr;
404 bool hasContent = board->GetNetCount() > 0 || board->Footprints().size() > 0;
405 BOOST_CHECK( hasContent );
409 if( aBoardTestJson.contains(
"boardExpectations" ) )
414 if( boardExpectationTest )
416 boardExpectationTest->RunTest( *board );
465 const nlohmann::json&
GetBoardJson(
const std::string& aBoardName )
const
467 return m_Json[
"boards"][aBoardName];
484 if( !aJson.contains(
"boards" ) || !aJson[
"boards"].is_object() )
486 throw std::runtime_error(
"Test register JSON file does not contain a valid 'boards' object." );
490 std::vector<ALLEGRO_BOARD_TEST_REF> boardTests;
493 for(
auto& [boardName, boardEntry] : aJson[
"boards"].items() )
496 .m_BrdName = boardName,
497 .m_BrdTestJson = boardEntry,
498 .m_HasHeaderTest = boardEntry.contains(
"header" ),
500 .m_HasBoardFile = boardEntry.contains(
"boardFile" ),
501 .m_HasExpectations = boardEntry.contains(
"boardExpectations" ),
504 if( boardEntry.contains(
"blocks" ) && boardEntry[
"blocks"].is_array() )
506 for(
const auto& blockTestEntry : boardEntry[
"blocks"] )
508 if( !blockTestEntry.contains(
"type" ) || !blockTestEntry[
"type"].is_string() )
510 throw std::runtime_error(
"Block test entry is missing a valid 'type' field" );
513 if( !blockTestEntry.contains(
"offset" ) || !blockTestEntry[
"offset"].is_string() )
515 throw std::runtime_error(
"Block test entry is missing a valid 'offset' field" );
518 const std::string blockTypeStr = blockTestEntry[
"type"];
519 const uint8_t blockType = std::stoul( blockTypeStr,
nullptr, 0 );
521 const std::string block_offset = blockTestEntry[
"offset"];
522 const size_t blockOffset = std::stoul( block_offset,
nullptr, 0 );
532 boardTests.push_back( std::move( boardTestRef ) );
546 const std::filesystem::path testRegisterJsonPath(
AllegroBoardDataDir(
"" ) +
"board_data_registry.json" );
547 std::ifstream jsonFileStream( testRegisterJsonPath );
549 reg.
m_Json = nlohmann::json::parse( jsonFileStream,
nullptr,
false,
true );
564 std::vector<std::string> labels;
566 if( boardTestJson.contains(
"testLabels" ) && boardTestJson[
"testLabels"].is_array() )
568 for(
const auto& label : boardTestJson[
"testLabels"] )
570 if( label.is_string() )
572 labels.push_back( label.get<std::string>() );
592 using BTS = boost::unit_test::test_suite;
593 std::vector<BTS*> suites;
594 BTS* blockSuite = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBlocks" ) );
601 catch(
const std::exception& ex )
603 std::string msg =
"Failed to load Allegro block test definitions: " + std::string( ex.what() );
606 const auto failingTestFunction = [=]()
612 boost::unit_test::make_test_case(
614 "FailureToLoadTestDefinitions",
625 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
626 blockSuite->add( boardSuite );
633 const auto testRunFunction = [&]()
640 boost::unit_test::make_test_case(
651 const auto testRunFunction = [&]()
658 wxString::Format(
"Block_0x%02x_offset_0x%010zx", blockTest.m_BlockType, blockTest.m_BlockOffset );
661 boost::unit_test::make_test_case(
663 testName.ToStdString(),
673 BTS* boardSuites = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBoards" ) );
683 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
684 boardSuites->add( boardSuite );
689 boost::unit_test::test_case* loadingTestCase =
nullptr;
693 const auto testLoadFunction = [&]()
699 loadingTestCase = boost::unit_test::make_test_case(
706 for(
const std::string& label : testLabels )
708 loadingTestCase->add_label( label );
711 boardSuite->add( loadingTestCase );
716 const auto testRunFunction = [&]()
721 boost::unit_test::test_case* expectationTestCase = boost::unit_test::make_test_case(
731 expectationTestCase->depends_on( loadingTestCase );
732 boardSuite->add( expectationTestCase );
750 for( boost::unit_test::test_suite* suite : suites )
752 boost::unit_test::framework::master_test_suite().add( suite );
General utilities for PCB file IO for QA programs.
The base class for all blocks in the main body of an Allegro file.
The block parser is responsible for parsing individual blocks of data from the file stream.
std::unique_ptr< BLOCK_BASE > ParseBlock(bool &aEndOfObjectsMarker)
Parse one block from the stream, returning a BLOCK_BASE representing the raw data of the block.
Converts blocks of "raw" binary-ish data into a DB_OBJ of the appropriate type to be stored in the DB...
std::unique_ptr< DB_OBJ > CreateObject(const BLOCK_BASE &aBlock) const
An Allegro database that represents a .brd file (amd presumably .dra)
Stream that reads primitive types from a memory buffer containing Allegro .brd (or ....
void RunHeaderTest(const std::string &aBrdName, const nlohmann::json &aBoardTestJson)
void RunBlockTest(const std::string &aBrdName, const nlohmann::json &aBoardTestJson, const nlohmann::json &aBlockTestJson)
Information pertinent to a Pcbnew printed circuit board.
static ALLEGRO_CACHED_LOADER & GetInstance()
Get the singleton instance of the Allegro board cache loader.
static std::unique_ptr< BOARD_EXPECTATION_TEST > CreateFromJson(const std::string &aBrdName, const nlohmann::json &aBrdExpectations)
BOARD * LoadAndCache(const std::string &aFilePath, REPORTER *aReporter)
Load (or reload) board for the given file path and send the load messages to the given reporter.
BOARD * GetCachedBoard(const std::string &aFilePath)
Get a cached board for the given file path, or load it if not already cached, without forcing a reloa...
Custom REPORTER that captures all messages for later analysis in the unit test framework.
int GetErrorCount() const
const std::vector< MESSAGE > & GetMessages() const
int GetWarningCount() const
FMT_VER
The format of an Allego file.
void RunAdditionalObjectTest(const std::string &aBoardName, size_t aBlockOffset, const ALLEGRO::DB_OBJ &aDbObj)
Look up and run any additional ad-hoc tests for a DB_OBJ (parsed and converted block)
std::string AllegroBoardDataDir(const std::string &aBoardName)
void RunAdditionalBlockTest(const std::string &aBoardName, size_t aBlockOffset, const ALLEGRO::BLOCK_BASE &aBlock)
Look up and run any additional ad-hoc tests for a block.
std::vector< uint8_t > LoadBinaryData(const std::string &aFilePath, std::optional< size_t > aLoadBytes=std::nullopt)
Load the contents of a file into a vector of bytes.
void PrintBoardStats(const BOARD *aBoard, const std::string &aBoardName)
Print detailed board statistics for debugging using test-framework logging.
The registry of known Allegro board and block tests, populated at static init time by reading the JSO...
std::vector< ALLEGRO_BOARD_TEST_REF > m_BoardTests
const nlohmann::json & GetBoardJson(const std::string &aBoardName) const
Just enough information about the board and block tests to be able to name and register the test case...
const nlohmann::json & m_BlockTestJson
Handy ref to the JSON entry for this block test.
Just enough information about the board test to be able to name and register any tests for this board...
std::vector< ALLEGRO_BLOCK_TEST > m_BlockTests
const nlohmann::json & m_BrdTestJson
ALLEGRO::FMT_VER m_FormatVersion
A single block of test data, along with the expected result of parsing it.
std::function< void(const ALLEGRO::BLOCK_BASE &)> m_ValidateFunc
An optional function to validate the contents of the parsed block if parsing is expected to succeed.
std::string m_DataSource
The raw bytes of the block, as copied from the file.
size_t m_BlockOffset
The offset within the board file where this block is located (used for error messages)
uint8_t m_BlockType
The type of the block, as in the first byte.
bool m_Skip
Whether to skip this test while parsers don't support a certain format.
static struct RegisterBlockSuites s_registerHeaderBlockSuites
static void AssertNoErrors(const CAPTURING_REPORTER &aReporter)
static BLK_TEST_INFO createBlockTestEntry(const std::string &boardDir, const nlohmann::json &blockTestEntry)
static std::unique_ptr< HEADER_TEST_INFO > createHeaderTestEntry(const std::string &boardDir, const nlohmann::json &headerTestEntry)
static BOARD_TEST_INFO createBoardTestInfo(const std::string &aBrdName, const nlohmann::json &boardTestEntry)
static std::vector< uint8_t > loadDataByUri(const std::string &aDataSource)
static std::vector< ALLEGRO_BOARD_TEST_REF > getBoardTestDefinitions(const nlohmann::json &aJson)
Construct a list of test definitions for the boards we have test data for, by reading the registry JS...
static std::vector< boost::unit_test::test_suite * > buildAllegroBoardSuites()
This function initializes the test suites for Allegro block and board parsing.
void RunBoardLoad(const std::string &aBrdName, const nlohmann::json &aBoardTestJson)
static std::vector< std::string > getBoardTestLabels(const nlohmann::json &boardTestJson)
Get the labels associated with a board test, which can be used to e.g.
void RunBoardExpectations(const std::string &aBrdName, const nlohmann::json &aBoardTestJson)
static const ALLEGRO_BLOCK_TEST_REGISTRY & buildTestRegistry()
ALLEGRO::FMT_VER getFormatVersionFromStr(const std::string &aFmtVerStr)
BOOST_TEST(contains==c.ExpectedContains)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")