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 );
299void RunBoardLoad(
const std::string& aBrdName,
const nlohmann::json& aBoardTestJson )
301 BOARD* board =
nullptr;
305 if( aBoardTestJson.contains(
"boardFile" ) )
307 const std::string boardFilePath =
310 if( !std::filesystem::exists( boardFilePath ) )
312 BOOST_FAIL(
"Board file does not exist: " + boardFilePath );
319 const bool expectLoadFailure = aBoardTestJson.value(
"expectLoadFailure",
false );
321 if( expectLoadFailure )
323 BOOST_CHECK( board ==
nullptr );
329 BOOST_CHECK( board !=
nullptr );
338 std::ostringstream ss;
345 ss <<
"\n " << msg.text;
361 if( !aBoardTestJson.contains(
"boardFile" ) )
363 BOOST_FAIL(
"Board test JSON does not contain a 'boardFile' entry" );
367 const std::string boardFilePath =
374 BOOST_FAIL(
"Failed to load cached board for expectations test: " + boardFilePath );
380 std::unique_ptr<BOARD_EXPECTATION_TEST> boardExpectationTest =
nullptr;
384 bool hasContent = board->GetNetCount() > 0 || board->Footprints().size() > 0;
385 BOOST_CHECK( hasContent );
389 if( aBoardTestJson.contains(
"boardExpectations" ) )
394 if( boardExpectationTest )
396 boardExpectationTest->RunTest( *board );
445 const nlohmann::json&
GetBoardJson(
const std::string& aBoardName )
const
447 return m_Json[
"boards"][aBoardName];
464 if( !aJson.contains(
"boards" ) || !aJson[
"boards"].is_object() )
466 throw std::runtime_error(
"Test register JSON file does not contain a valid 'boards' object." );
470 std::vector<ALLEGRO_BOARD_TEST_REF> boardTests;
473 for(
auto& [boardName, boardEntry] : aJson[
"boards"].items() )
476 .m_BrdName = boardName,
477 .m_BrdTestJson = boardEntry,
478 .m_HasHeaderTest = boardEntry.contains(
"header" ),
480 .m_HasBoardFile = boardEntry.contains(
"boardFile" ),
481 .m_HasExpectations = boardEntry.contains(
"boardExpectations" ),
484 if( boardEntry.contains(
"blocks" ) && boardEntry[
"blocks"].is_array() )
486 for(
const auto& blockTestEntry : boardEntry[
"blocks"] )
488 if( !blockTestEntry.contains(
"type" ) || !blockTestEntry[
"type"].is_string() )
490 throw std::runtime_error(
"Block test entry is missing a valid 'type' field" );
493 if( !blockTestEntry.contains(
"offset" ) || !blockTestEntry[
"offset"].is_string() )
495 throw std::runtime_error(
"Block test entry is missing a valid 'offset' field" );
498 const std::string blockTypeStr = blockTestEntry[
"type"];
499 const uint8_t blockType = std::stoul( blockTypeStr,
nullptr, 0 );
501 const std::string block_offset = blockTestEntry[
"offset"];
502 const size_t blockOffset = std::stoul( block_offset,
nullptr, 0 );
512 boardTests.push_back( std::move( boardTestRef ) );
526 const std::filesystem::path testRegisterJsonPath(
AllegroBoardDataDir(
"" ) +
"board_data_registry.json" );
527 std::ifstream jsonFileStream( testRegisterJsonPath );
529 reg.
m_Json = nlohmann::json::parse( jsonFileStream,
nullptr,
false,
true );
544 std::vector<std::string> labels;
546 if( boardTestJson.contains(
"testLabels" ) && boardTestJson[
"testLabels"].is_array() )
548 for(
const auto& label : boardTestJson[
"testLabels"] )
550 if( label.is_string() )
552 labels.push_back( label.get<std::string>() );
572 using BTS = boost::unit_test::test_suite;
573 std::vector<BTS*> suites;
574 BTS* blockSuite = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBlocks" ) );
581 catch(
const std::exception& ex )
583 std::string msg =
"Failed to load Allegro block test definitions: " + std::string( ex.what() );
586 const auto failingTestFunction = [=]()
592 boost::unit_test::make_test_case(
594 "FailureToLoadTestDefinitions",
605 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
606 blockSuite->add( boardSuite );
613 const auto testRunFunction = [&]()
620 boost::unit_test::make_test_case(
631 const auto testRunFunction = [&]()
638 wxString::Format(
"Block_0x%02x_offset_0x%010zx", blockTest.m_BlockType, blockTest.m_BlockOffset );
641 boost::unit_test::make_test_case(
643 testName.ToStdString(),
653 BTS* boardSuites = suites.emplace_back( BOOST_TEST_SUITE(
"AllegroBoards" ) );
663 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.
m_BrdName );
664 boardSuites->add( boardSuite );
669 boost::unit_test::test_case* loadingTestCase =
nullptr;
673 const auto testLoadFunction = [&]()
679 loadingTestCase = boost::unit_test::make_test_case(
686 for(
const std::string& label : testLabels )
688 loadingTestCase->add_label( label );
691 boardSuite->add( loadingTestCase );
696 const auto testRunFunction = [&]()
701 boost::unit_test::test_case* expectationTestCase = boost::unit_test::make_test_case(
711 expectationTestCase->depends_on( loadingTestCase );
712 boardSuite->add( expectationTestCase );
730 for( boost::unit_test::test_suite* suite : suites )
732 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 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")