32#include <boost/test/data/test_case.hpp>
102 const nlohmann::json&
GetBoardJson(
const std::string& aBoardName )
const
104 return m_Json[
"boards"][aBoardName];
115 std::string
path, protocol;
116 const std::string sep =
"://";
118 size_t sepPos = aDataSource.find( sep );
119 if( sepPos != std::string::npos )
121 protocol = aDataSource.substr( 0, sepPos );
122 path = aDataSource.substr( sepPos + sep.size() );
127 throw std::runtime_error(
"Unsupported data source URI (missing protocol): " + aDataSource );
130 if( protocol ==
"file" )
136 throw std::runtime_error(
"Unsupported data source protocol: " + protocol );
143 static const std::map<std::string, ALLEGRO::FMT_VER> fmtVerStrMap{
156 auto it = fmtVerStrMap.find( aFmtVerStr );
157 if( it != fmtVerStrMap.end() )
167 const nlohmann::json& headerTestEntry )
169 std::unique_ptr<HEADER_TEST_INFO> headerTest;
171 if( !headerTestEntry.is_object() )
173 throw std::runtime_error(
"Header test entry is not a valid JSON object" );
177 const std::string headerDataUri = std::string(
"file://" ) + boardDir +
"header.bin";
179 return std::make_unique<HEADER_TEST_INFO>( headerDataUri, headerTestEntry.value(
"skip",
false ) );
186 const nlohmann::json& blockTestJson = aBlockTestDescriptor.
m_BlockTestJson;
187 const bool skipBlock = blockTestJson.value(
"skip",
false );
189 const uint8_t& blockType = aBlockTestDescriptor.
m_BlockType;
190 const size_t& blockOffset = aBlockTestDescriptor.
m_BlockOffset;
193 wxString blockDataLoc = wxString::Format(
"0x%02x_0x%08zx.bin", blockType, blockOffset );
195 const std::string blockDataUri = std::string(
"file://" ) + boardDir + blockDataLoc.ToStdString();
197 const bool extraBlockTest = blockTestJson.value(
"extraBlockTest",
false );
228 void RunHeaderTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
236 std::unique_ptr<HEADER_TEST_INFO> headerTest =
nullptr;
238 if( aBoardTestJson.contains(
"header" ) && aBoardTestJson[
"header"].is_object() )
245 if( headerTest->m_Skip )
252 std::vector<uint8_t> headerData =
loadDataByUri( headerTest->m_HeaderDataSource );
253 FILE_STREAM fileStream( headerData.data(), headerData.size() );
277 void RunBlockTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson,
283 BOOST_TEST_CONTEXT( wxString::Format(
"Testing block type %#02x at offset %#010zx from board %s",
294 FILE_STREAM fileStream( data.data(), data.size() );
298 bool endOfObjectsMarker =
false;
299 std::unique_ptr<BLOCK_BASE> block = parser.
ParseBlock( endOfObjectsMarker );
321 std::ostringstream ss;
322 ss <<
"Expected no errors, but found " << aReporter.
GetErrorCount() <<
" errors:";
328 ss <<
"\n " << msg.text;
337void RunBoardLoad(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
339 BOARD* board =
nullptr;
343 if( aBoardTestJson.contains(
"boardFile" ) )
345 const std::string boardFilePath =
348 if( !std::filesystem::exists( boardFilePath ) )
350 BOOST_FAIL(
"Board file does not exist: " + boardFilePath );
357 const bool expectLoadFailure = aBoardTestJson.value(
"expectLoadFailure",
false );
359 if( expectLoadFailure )
361 BOOST_CHECK( board ==
nullptr );
367 BOOST_CHECK_MESSAGE( board !=
nullptr,
"Board load was expected to succeed, but it failed." );
375 std::ostringstream ss;
382 ss <<
"\n " << msg.text;
396static uint8_t
getByteFromHexStr(
const nlohmann::json& aJsonEntry,
const std::string& aFieldName )
398 if( !aJsonEntry.contains( aFieldName ) )
400 throw std::runtime_error(
"Block test entry is missing the '" + aFieldName +
"' field" );
403 if( aJsonEntry[aFieldName].is_number_unsigned() )
405 const unsigned long value = aJsonEntry[aFieldName].get<
unsigned long>();
408 throw std::runtime_error(
"Value is too large to fit in a byte: " + std::to_string( value ) );
410 return static_cast<uint8_t
>( value );
412 else if( aJsonEntry[aFieldName].is_string() )
414 const std::string aHexStr = aJsonEntry[aFieldName];
416 const unsigned long value = std::stoul( aHexStr,
nullptr, 0 );
419 throw std::runtime_error(
"Value is too large to fit in a byte: " + aHexStr );
421 return static_cast<uint8_t
>( value );
424 throw std::runtime_error(
"Block test entry has an invalid '" + aFieldName
425 +
"' field (must be a byte value as a number or hex string)" );
433 if( !aBlockTestJson.contains(
"offset" ) || !aBlockTestJson[
"offset"].is_string() )
435 throw std::runtime_error(
"Block test entry is missing a valid 'offset' field" );
438 const std::string block_offset = aBlockTestJson[
"offset"];
439 const size_t blockOffset = std::stoul( block_offset,
nullptr, 0 );
458 if( !aJson.contains(
"boards" ) || !aJson[
"boards"].is_object() )
460 throw std::runtime_error(
"Test register JSON file does not contain a valid 'boards' object." );
463 std::vector<ALLEGRO_BOARD_TEST_DESCRIPTOR> boardTests;
465 for(
auto& [boardName, boardEntry] : aJson[
"boards"].items() )
468 .m_BrdName = boardName,
469 .m_HasHeaderTest = boardEntry.contains(
"header" ),
471 .m_HasBoardFile = boardEntry.contains(
"boardFile" ),
472 .m_ExpectationTests = {},
475 if( boardEntry.contains(
"blocks" ) && boardEntry[
"blocks"].is_array() )
477 for(
const auto& blockTestEntry : boardEntry[
"blocks"] )
483 if( boardEntry.contains(
"boardExpectations" ) )
485 const auto& expectationsEntry = boardEntry[
"boardExpectations"];
487 boardTestRef.m_ExpectationTests =
491 boardTests.push_back( std::move( boardTestRef ) );
506 const std::filesystem::path testRegisterJsonPath(
AllegroBoardDataDir(
"" ) +
"board_data_registry.json" );
507 std::ifstream jsonFileStream( testRegisterJsonPath );
509 reg.
m_Json = nlohmann::json::parse( jsonFileStream,
nullptr,
true,
true );
524 std::vector<std::string> labels;
526 if( boardTestJson.contains(
"testLabels" ) && boardTestJson[
"testLabels"].is_array() )
528 for(
const auto& label : boardTestJson[
"testLabels"] )
530 if( label.is_string() )
532 labels.push_back( label.get<std::string>() );
552 using BTS = boost::unit_test::test_suite;
553 std::vector<BTS*> suites;
554 BTS* blockSuite = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBlocks" ) );
561 catch(
const std::exception& ex )
563 std::string msg =
"Failed to load Allegro block test definitions: " + std::string( ex.what() );
566 const auto failingTestFunction = [=]()
572 boost::unit_test::make_test_case(
574 "FailureToLoadTestDefinitions",
585 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
586 blockSuite->add( boardSuite );
593 const auto testRunFunction = [&]()
600 boost::unit_test::make_test_case(
613 const auto testRunFunction = [&boardTest, &boardTestJson, &blockTest]()
620 wxString::Format(
"Block_0x%02x_offset_0x%010zx", blockTest.m_BlockType, blockTest.m_BlockOffset );
623 boost::unit_test::make_test_case(
625 testName.ToStdString(),
635 BTS* boardSuites = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBoards" ) );
645 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
646 boardSuites->add( boardSuite );
651 boost::unit_test::test_case* loadingTestCase =
nullptr;
655 const auto testLoadFunction = [&boardTest, &boardTestData]()
661 loadingTestCase = boost::unit_test::make_test_case(
668 for(
const std::string& label : testLabels )
670 loadingTestCase->add_label( label );
673 boardSuite->add( loadingTestCase );
678 BTS* expectationsSuite = BOOST_TEST_SUITE(
"Expectations" );
679 boardSuite->add( expectationsSuite );
683 const auto testRunFunction = [&boardTest, &boardTestData, &expectationTestRef]()
685 if( !boardTestData.contains(
"boardFile" ) )
688 "Board test JSON does not contain a 'boardFile' entry - cannot run expectations test" );
693 + boardTestData[
"boardFile"].get<std::string>();
699 BOOST_FAIL(
"Failed to load cached board for expectations test: " + boardFilePath );
706 boost::unit_test::test_case* expectationTestCase = boost::unit_test::make_test_case(
714 for(
const std::string& label : expectationTestRef.
m_Tags )
716 expectationTestCase->add_label( label );
719 expectationsSuite->add( expectationTestCase );
725 expectationsSuite->depends_on( loadingTestCase );
743 for( boost::unit_test::test_suite* suite : suites )
745 boost::unit_test::framework::master_test_suite().add( suite );
General utilities for PCB file IO for QA programs.
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.
Stream that reads primitive types from a memory buffer containing Allegro .brd (or ....
void RunBlockTest(const std::string &aBrdName, const nlohmann::json &aBoardTestJson, const ALLEGRO_BLOCK_TEST_DESCRIPTOR &aBlockTest)
void RunHeaderTest(const std::string &aBrdName, const nlohmann::json &aBoardTestJson)
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::vector< DESCRIPTOR > ExtractExpectationTestsFromJson(const nlohmann::json &aExpectationArray)
Extracts expectation tests from the given JSON array and returns a list of test references that can b...
static void RunFromRef(const std::string &aBrdName, const BOARD &aBoard, const BOARD_EXPECTATION_TEST::DESCRIPTOR &aExpectationTestRef)
Constructs a BOARD_EXPECTATION_TEST from the given JSON definition, and runs it on the given board.
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 Allegro file.
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.
Just enough information about a block-level test to be able to name and register it with the test run...
const nlohmann::json & m_BlockTestJson
Handy ref to the JSON entry for this block test.
The registry of known Allegro board and block tests, populated at static init time by reading the JSO...
const nlohmann::json & GetBoardJson(const std::string &aBoardName) const
std::vector< ALLEGRO_BOARD_TEST_DESCRIPTOR > m_BoardTests
Just enough information about the board test to be able to name and register any tests for this board...
std::vector< BOARD_EXPECTATION_TEST::DESCRIPTOR > m_ExpectationTests
List of expectation tests found in the JSON for this board.
std::vector< ALLEGRO_BLOCK_TEST_DESCRIPTOR > m_BlockTests
ALLEGRO::FMT_VER m_FormatVersion
A complete description of a block test.
uint8_t m_BlockType
The type of the block, as in the first byte.
std::string m_DataSource
The the source of the block data (probably a filename)
bool m_ExtraBlockTest
Do we have an additional block-level test to run for this block?
size_t m_BlockOffset
The offset within the board file where this block is located (used for error messages)
bool m_Skip
Whether to skip this test while parsers don't support a certain format.
Lightweight descriptor for a BOARD_EXPECTATION_TEST, which can be used to refer to the test unambiguo...
std::string m_TestName
If the test has a name, it's that, else an index - this is for naming the test for filtering.
std::vector< std::string > m_Tags
Tags associated with the test, which can be used for filtering.
static struct RegisterBlockSuites s_registerHeaderBlockSuites
static void AssertNoErrors(const CAPTURING_REPORTER &aReporter)
static std::unique_ptr< HEADER_TEST_INFO > createHeaderTestEntry(const std::string &boardDir, const nlohmann::json &headerTestEntry)
static uint8_t getByteFromHexStr(const nlohmann::json &aJsonEntry, const std::string &aFieldName)
static BOARD_TEST_INFO createBoardTestInfo(const std::string &aBrdName, const nlohmann::json &boardTestEntry)
static BLOCK_TEST_INFO createBlockTestEntry(const std::string &boardDir, const ALLEGRO_BLOCK_TEST_DESCRIPTOR &aBlockTestDescriptor)
static std::vector< uint8_t > loadDataByUri(const std::string &aDataSource)
static ALLEGRO_BLOCK_TEST_DESCRIPTOR createBlockTestDescriptor(const nlohmann::json &aBlockTestJson)
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.
static std::vector< ALLEGRO_BOARD_TEST_DESCRIPTOR > getBoardTestDefinitions(const nlohmann::json &aJson)
Construct a list of test descriptrs (lightweight objects) for the boards we have test data for,...
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())
std::vector< std::string > header
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
BOOST_TEST_CONTEXT("Test Clearance")