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 );
198 const bool extraDbObjTest = blockTestJson.value(
"extraDbObjTest",
false );
230 void RunHeaderTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
238 std::unique_ptr<HEADER_TEST_INFO> headerTest =
nullptr;
240 if( aBoardTestJson.contains(
"header" ) && aBoardTestJson[
"header"].is_object() )
247 if( headerTest->m_Skip )
254 std::vector<uint8_t> headerData =
loadDataByUri( headerTest->m_HeaderDataSource );
255 FILE_STREAM fileStream( headerData.data(), headerData.size() );
279 void RunBlockTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson,
285 BOOST_TEST_CONTEXT( wxString::Format(
"Testing block type %#02x at offset %#010zx from board %s",
296 FILE_STREAM fileStream( data.data(), data.size() );
300 bool endOfObjectsMarker =
false;
301 std::unique_ptr<BLOCK_BASE> block = parser.
ParseBlock( endOfObjectsMarker );
320 std::unique_ptr<ALLEGRO::DB_OBJ> dbObj = objFactory.
CreateObject( *block );
336 std::ostringstream ss;
337 ss <<
"Expected no errors, but found " << aReporter.
GetErrorCount() <<
" errors:";
343 ss <<
"\n " << msg.text;
352void RunBoardLoad(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
354 BOARD* board =
nullptr;
358 if( aBoardTestJson.contains(
"boardFile" ) )
360 const std::string boardFilePath =
363 if( !std::filesystem::exists( boardFilePath ) )
365 BOOST_FAIL(
"Board file does not exist: " + boardFilePath );
372 const bool expectLoadFailure = aBoardTestJson.value(
"expectLoadFailure",
false );
374 if( expectLoadFailure )
376 BOOST_CHECK( board ==
nullptr );
382 BOOST_CHECK_MESSAGE( board !=
nullptr,
"Board load was expected to succeed, but it failed." );
390 std::ostringstream ss;
397 ss <<
"\n " << msg.text;
411static uint8_t
getByteFromHexStr(
const nlohmann::json& aJsonEntry,
const std::string& aFieldName )
413 if( !aJsonEntry.contains( aFieldName ) )
415 throw std::runtime_error(
"Block test entry is missing the '" + aFieldName +
"' field" );
418 if( aJsonEntry[aFieldName].is_number_unsigned() )
420 const unsigned long value = aJsonEntry[aFieldName].get<
unsigned long>();
423 throw std::runtime_error(
"Value is too large to fit in a byte: " + std::to_string( value ) );
425 return static_cast<uint8_t
>( value );
427 else if( aJsonEntry[aFieldName].is_string() )
429 const std::string aHexStr = aJsonEntry[aFieldName];
431 const unsigned long value = std::stoul( aHexStr,
nullptr, 0 );
434 throw std::runtime_error(
"Value is too large to fit in a byte: " + aHexStr );
436 return static_cast<uint8_t
>( value );
439 throw std::runtime_error(
"Block test entry has an invalid '" + aFieldName
440 +
"' field (must be a byte value as a number or hex string)" );
448 if( !aBlockTestJson.contains(
"offset" ) || !aBlockTestJson[
"offset"].is_string() )
450 throw std::runtime_error(
"Block test entry is missing a valid 'offset' field" );
453 const std::string block_offset = aBlockTestJson[
"offset"];
454 const size_t blockOffset = std::stoul( block_offset,
nullptr, 0 );
473 if( !aJson.contains(
"boards" ) || !aJson[
"boards"].is_object() )
475 throw std::runtime_error(
"Test register JSON file does not contain a valid 'boards' object." );
478 std::vector<ALLEGRO_BOARD_TEST_DESCRIPTOR> boardTests;
480 for(
auto& [boardName, boardEntry] : aJson[
"boards"].items() )
483 .m_BrdName = boardName,
484 .m_HasHeaderTest = boardEntry.contains(
"header" ),
486 .m_HasBoardFile = boardEntry.contains(
"boardFile" ),
487 .m_ExpectationTests = {},
490 if( boardEntry.contains(
"blocks" ) && boardEntry[
"blocks"].is_array() )
492 for(
const auto& blockTestEntry : boardEntry[
"blocks"] )
498 if( boardEntry.contains(
"boardExpectations" ) )
500 const auto& expectationsEntry = boardEntry[
"boardExpectations"];
502 boardTestRef.m_ExpectationTests =
506 boardTests.push_back( std::move( boardTestRef ) );
521 const std::filesystem::path testRegisterJsonPath(
AllegroBoardDataDir(
"" ) +
"board_data_registry.json" );
522 std::ifstream jsonFileStream( testRegisterJsonPath );
524 reg.
m_Json = nlohmann::json::parse( jsonFileStream,
nullptr,
true,
true );
539 std::vector<std::string> labels;
541 if( boardTestJson.contains(
"testLabels" ) && boardTestJson[
"testLabels"].is_array() )
543 for(
const auto& label : boardTestJson[
"testLabels"] )
545 if( label.is_string() )
547 labels.push_back( label.get<std::string>() );
567 using BTS = boost::unit_test::test_suite;
568 std::vector<BTS*> suites;
569 BTS* blockSuite = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBlocks" ) );
576 catch(
const std::exception& ex )
578 std::string msg =
"Failed to load Allegro block test definitions: " + std::string( ex.what() );
581 const auto failingTestFunction = [=]()
587 boost::unit_test::make_test_case(
589 "FailureToLoadTestDefinitions",
600 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
601 blockSuite->add( boardSuite );
608 const auto testRunFunction = [&]()
615 boost::unit_test::make_test_case(
628 const auto testRunFunction = [&boardTest, &boardTestJson, &blockTest]()
635 wxString::Format(
"Block_0x%02x_offset_0x%010zx", blockTest.m_BlockType, blockTest.m_BlockOffset );
638 boost::unit_test::make_test_case(
640 testName.ToStdString(),
650 BTS* boardSuites = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBoards" ) );
660 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
661 boardSuites->add( boardSuite );
666 boost::unit_test::test_case* loadingTestCase =
nullptr;
670 const auto testLoadFunction = [&boardTest, &boardTestData]()
676 loadingTestCase = boost::unit_test::make_test_case(
683 for(
const std::string& label : testLabels )
685 loadingTestCase->add_label( label );
688 boardSuite->add( loadingTestCase );
693 BTS* expectationsSuite = BOOST_TEST_SUITE(
"Expectations" );
694 boardSuite->add( expectationsSuite );
698 const auto testRunFunction = [&boardTest, &boardTestData, &expectationTestRef]()
700 if( !boardTestData.contains(
"boardFile" ) )
703 "Board test JSON does not contain a 'boardFile' entry - cannot run expectations test" );
708 + boardTestData[
"boardFile"].get<std::string>();
714 BOOST_FAIL(
"Failed to load cached board for expectations test: " + boardFilePath );
721 boost::unit_test::test_case* expectationTestCase = boost::unit_test::make_test_case(
729 for(
const std::string& label : expectationTestRef.
m_Tags )
731 expectationTestCase->add_label( label );
734 expectationsSuite->add( expectationTestCase );
740 expectationsSuite->depends_on( loadingTestCase );
758 for( boost::unit_test::test_suite* suite : suites )
760 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.
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 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 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.
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.
bool m_ExtraDbObjTest
Do we have an additional DB_OBJ-level test to run for this block?
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")