28#include <boost/test/data/test_case.hpp>
98 const nlohmann::json&
GetBoardJson(
const std::string& aBoardName )
const
100 return m_Json[
"boards"][aBoardName];
111 std::string
path, protocol;
112 const std::string sep =
"://";
114 size_t sepPos = aDataSource.find( sep );
115 if( sepPos != std::string::npos )
117 protocol = aDataSource.substr( 0, sepPos );
118 path = aDataSource.substr( sepPos + sep.size() );
123 throw std::runtime_error(
"Unsupported data source URI (missing protocol): " + aDataSource );
126 if( protocol ==
"file" )
132 throw std::runtime_error(
"Unsupported data source protocol: " + protocol );
139 static const std::map<std::string, ALLEGRO::FMT_VER> fmtVerStrMap{
152 auto it = fmtVerStrMap.find( aFmtVerStr );
153 if( it != fmtVerStrMap.end() )
163 const nlohmann::json& headerTestEntry )
165 std::unique_ptr<HEADER_TEST_INFO> headerTest;
167 if( !headerTestEntry.is_object() )
169 throw std::runtime_error(
"Header test entry is not a valid JSON object" );
173 const std::string headerDataUri = std::string(
"file://" ) + boardDir +
"header.bin";
175 return std::make_unique<HEADER_TEST_INFO>( headerDataUri, headerTestEntry.value(
"skip",
false ) );
182 const nlohmann::json& blockTestJson = aBlockTestDescriptor.
m_BlockTestJson;
183 const bool skipBlock = blockTestJson.value(
"skip",
false );
185 const uint8_t& blockType = aBlockTestDescriptor.
m_BlockType;
186 const size_t& blockOffset = aBlockTestDescriptor.
m_BlockOffset;
189 wxString blockDataLoc = wxString::Format(
"0x%02x_0x%08zx.bin", blockType, blockOffset );
191 const std::string blockDataUri = std::string(
"file://" ) + boardDir + blockDataLoc.ToStdString();
193 const bool extraBlockTest = blockTestJson.value(
"extraBlockTest",
false );
224 void RunHeaderTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
232 std::unique_ptr<HEADER_TEST_INFO> headerTest =
nullptr;
234 if( aBoardTestJson.contains(
"header" ) && aBoardTestJson[
"header"].is_object() )
241 if( headerTest->m_Skip )
248 std::vector<uint8_t> headerData =
loadDataByUri( headerTest->m_HeaderDataSource );
249 FILE_STREAM fileStream( headerData.data(), headerData.size() );
273 void RunBlockTest(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson,
279 BOOST_TEST_CONTEXT( wxString::Format(
"Testing block type %#02x at offset %#010zx from board %s",
290 FILE_STREAM fileStream( data.data(), data.size() );
294 bool endOfObjectsMarker =
false;
295 std::unique_ptr<BLOCK_BASE> block = parser.
ParseBlock( endOfObjectsMarker );
317 std::ostringstream ss;
318 ss <<
"Expected no errors, but found " << aReporter.
GetErrorCount() <<
" errors:";
324 ss <<
"\n " << msg.text;
333void RunBoardLoad(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
335 BOARD* board =
nullptr;
339 if( aBoardTestJson.contains(
"boardFile" ) )
341 const std::string boardFilePath =
344 if( !std::filesystem::exists( boardFilePath ) )
346 BOOST_FAIL(
"Board file does not exist: " + boardFilePath );
353 const bool expectLoadFailure = aBoardTestJson.value(
"expectLoadFailure",
false );
355 if( expectLoadFailure )
357 BOOST_CHECK( board ==
nullptr );
363 BOOST_CHECK_MESSAGE( board !=
nullptr,
"Board load was expected to succeed, but it failed." );
369 if(
reporter.GetWarningCount() > 0 )
371 std::ostringstream ss;
372 ss << aBrdName <<
": " <<
reporter.GetWarningCount() <<
" warnings";
374 for(
const auto& msg :
reporter.GetMessages() )
378 ss <<
"\n " << msg.text;
392static uint8_t
getByteFromHexStr(
const nlohmann::json& aJsonEntry,
const std::string& aFieldName )
394 if( !aJsonEntry.contains( aFieldName ) )
396 throw std::runtime_error(
"Block test entry is missing the '" + aFieldName +
"' field" );
399 if( aJsonEntry[aFieldName].is_number_unsigned() )
401 const unsigned long value = aJsonEntry[aFieldName].get<
unsigned long>();
404 throw std::runtime_error(
"Value is too large to fit in a byte: " + std::to_string( value ) );
406 return static_cast<uint8_t
>( value );
408 else if( aJsonEntry[aFieldName].is_string() )
410 const std::string aHexStr = aJsonEntry[aFieldName];
412 const unsigned long value = std::stoul( aHexStr,
nullptr, 0 );
415 throw std::runtime_error(
"Value is too large to fit in a byte: " + aHexStr );
417 return static_cast<uint8_t
>( value );
420 throw std::runtime_error(
"Block test entry has an invalid '" + aFieldName
421 +
"' field (must be a byte value as a number or hex string)" );
429 if( !aBlockTestJson.contains(
"offset" ) || !aBlockTestJson[
"offset"].is_string() )
431 throw std::runtime_error(
"Block test entry is missing a valid 'offset' field" );
434 const std::string block_offset = aBlockTestJson[
"offset"];
435 const size_t blockOffset = std::stoul( block_offset,
nullptr, 0 );
454 if( !aJson.contains(
"boards" ) || !aJson[
"boards"].is_object() )
456 throw std::runtime_error(
"Test register JSON file does not contain a valid 'boards' object." );
459 std::vector<ALLEGRO_BOARD_TEST_DESCRIPTOR> boardTests;
461 for(
auto& [boardName, boardEntry] : aJson[
"boards"].items() )
464 .m_BrdName = boardName,
465 .m_HasHeaderTest = boardEntry.contains(
"header" ),
467 .m_HasBoardFile = boardEntry.contains(
"boardFile" ),
468 .m_ExpectationTests = {},
471 if( boardEntry.contains(
"blocks" ) && boardEntry[
"blocks"].is_array() )
473 for(
const auto& blockTestEntry : boardEntry[
"blocks"] )
479 if( boardEntry.contains(
"boardExpectations" ) )
481 const auto& expectationsEntry = boardEntry[
"boardExpectations"];
483 boardTestRef.m_ExpectationTests =
487 boardTests.push_back( std::move( boardTestRef ) );
502 const std::filesystem::path testRegisterJsonPath(
AllegroBoardDataDir(
"" ) +
"board_data_registry.json" );
503 std::ifstream jsonFileStream( testRegisterJsonPath );
505 reg.
m_Json = nlohmann::json::parse( jsonFileStream,
nullptr,
true,
true );
520 std::vector<std::string> labels;
522 if( boardTestJson.contains(
"testLabels" ) && boardTestJson[
"testLabels"].is_array() )
524 for(
const auto& label : boardTestJson[
"testLabels"] )
526 if( label.is_string() )
528 labels.push_back( label.get<std::string>() );
548 using BTS = boost::unit_test::test_suite;
549 std::vector<BTS*> suites;
550 BTS* blockSuite = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBlocks" ) );
557 catch(
const std::exception& ex )
559 std::string msg =
"Failed to load Allegro block test definitions: " + std::string( ex.what() );
562 const auto failingTestFunction = [=]()
568 boost::unit_test::make_test_case(
570 "FailureToLoadTestDefinitions",
581 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
582 blockSuite->add( boardSuite );
589 const auto testRunFunction = [&]()
596 boost::unit_test::make_test_case(
609 const auto testRunFunction = [&boardTest, &boardTestJson, &blockTest]()
616 wxString::Format(
"Block_0x%02x_offset_0x%010zx", blockTest.m_BlockType, blockTest.m_BlockOffset );
619 boost::unit_test::make_test_case(
621 testName.ToStdString(),
631 BTS* boardSuites = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBoards" ) );
641 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
642 boardSuites->add( boardSuite );
647 boost::unit_test::test_case* loadingTestCase =
nullptr;
651 const auto testLoadFunction = [&boardTest, &boardTestData]()
657 loadingTestCase = boost::unit_test::make_test_case(
664 for(
const std::string& label : testLabels )
666 loadingTestCase->add_label( label );
669 boardSuite->add( loadingTestCase );
674 BTS* expectationsSuite = BOOST_TEST_SUITE(
"Expectations" );
675 boardSuite->add( expectationsSuite );
679 const auto testRunFunction = [&boardTest, &boardTestData, &expectationTestRef]()
681 if( !boardTestData.contains(
"boardFile" ) )
684 "Board test JSON does not contain a 'boardFile' entry - cannot run expectations test" );
689 + boardTestData[
"boardFile"].get<std::string>();
695 BOOST_FAIL(
"Failed to load cached board for expectations test: " + boardFilePath );
702 boost::unit_test::test_case* expectationTestCase = boost::unit_test::make_test_case(
710 for(
const std::string& label : expectationTestRef.
m_Tags )
712 expectationTestCase->add_label( label );
715 expectationsSuite->add( expectationTestCase );
721 expectationsSuite->depends_on( loadingTestCase );
739 for( boost::unit_test::test_suite* suite : suites )
741 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
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_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
IbisParser parser & reporter
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")