KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_allegro_boards.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include "allegro_block_tests.h"
27#include "allegro_test_utils.h"
28
29#include <filesystem>
30#include <fstream>
31
32#include <boost/test/data/test_case.hpp>
33
34#include <json_common.h>
35
36#include <board.h>
37#include <reporter.h>
39
42
44
55
56using namespace ALLEGRO;
57using namespace KI_TEST;
58
59
60static std::vector<uint8_t> loadDataByUri( const std::string& aDataSource )
61{
62 // For now, we only support loading from files, but in the future we could also support
63 // loading from other sources (e.g. from compiled-in data, base64, etc.)
64
65 // Split off the protocol (e.g. "file://") if present, to get the actual path
66 std::string path, protocol;
67 const std::string sep = "://";
68
69 size_t sepPos = aDataSource.find( sep );
70 if( sepPos != std::string::npos )
71 {
72 protocol = aDataSource.substr( 0, sepPos );
73 path = aDataSource.substr( sepPos + sep.size() );
74 }
75 else
76 {
77 // No protocol
78 throw std::runtime_error( "Unsupported data source URI (missing protocol): " + aDataSource );
79 }
80
81 if( protocol == "file" )
82 {
83 // This means get it from a file in the QD data
84 return KI_TEST::LoadBinaryData( path, std::nullopt );
85 }
86
87 throw std::runtime_error( "Unsupported data source protocol: " + protocol );
88}
89
90
91ALLEGRO::FMT_VER getFormatVersionFromStr( const std::string& aFmtVerStr )
92{
93 // clang-format off
94 static const std::map<std::string, ALLEGRO::FMT_VER> fmtVerStrMap{
95 { "16.0", FMT_VER::V_160 },
96 { "16.2", FMT_VER::V_162 },
97 { "16.4", FMT_VER::V_164 },
98 { "16.5", FMT_VER::V_165 },
99 { "16.6", FMT_VER::V_166 },
100 { "17.2", FMT_VER::V_172 },
101 { "17.4", FMT_VER::V_174 },
102 { "17.5", FMT_VER::V_175 },
103 { "18.0", FMT_VER::V_180 },
104 };
105 // clang-format on
106
107 auto it = fmtVerStrMap.find( aFmtVerStr );
108 if( it != fmtVerStrMap.end() )
109 {
110 return it->second;
111 }
112
113 return FMT_VER::V_UNKNOWN;
114}
115
116
117static std::unique_ptr<HEADER_TEST_INFO> createHeaderTestEntry( const std::string& boardDir,
118 const nlohmann::json& headerTestEntry )
119{
120 std::unique_ptr<HEADER_TEST_INFO> headerTest = nullptr;
121
122 if( !headerTestEntry.is_object() )
123 {
124 throw std::runtime_error( "Header test entry is not a valid JSON object" );
125 }
126
127 // Default header location
128 const std::string headerDataUri = std::string( "file://" ) + boardDir + "header.bin";
129
130 return std::make_unique<HEADER_TEST_INFO>( headerDataUri, headerTestEntry.value( "skip", false ) );
131}
132
133
134static BLK_TEST_INFO createBlockTestEntry( const std::string& boardDir, const nlohmann::json& blockTestEntry )
135{
136 if( !blockTestEntry.contains( "type" ) || !blockTestEntry["type"].is_string() )
137 {
138 throw std::runtime_error( "Block test entry is missing a valid 'type' field" );
139 }
140
141 const std::string blockTypeStr = blockTestEntry["type"];
142 const uint8_t blockType = std::stoul( blockTypeStr, nullptr, 0 );
143
144 const std::string block_offset = blockTestEntry["offset"];
145 const size_t blockOffset = std::stoul( block_offset, nullptr, 0 );
146
147 const bool skipBlock = blockTestEntry.value( "skip", false );
148
149 // Default block data location is a file with a name based on the block type and offset
150 wxString blockDataLoc = wxString::Format( "0x%02x_0x%08zx.bin", blockType, blockOffset );
151
152 const std::string blockDataUri = std::string( "file://" ) + boardDir + blockDataLoc.ToStdString();
153
154 const auto blockTestCallback = [&]( const ALLEGRO::BLOCK_BASE& aBlock )
155 {
156 // For now, we don't have any specific expectations for the contents of the block, but we could add some if needed.
157 // For example, commonly used fields.
158 // Often just parsing successfully is enough.
159 };
160
161 return BLK_TEST_INFO{
162 blockType,
163 blockOffset,
164 skipBlock,
165 blockDataUri,
166 blockTestCallback,
167 };
168}
169
170
177
178
179static BOARD_TEST_INFO createBoardTestInfo( const std::string& aBrdName, const nlohmann::json& boardTestEntry )
180{
181 const std::string boardDir = AllegroBoardDataDir( aBrdName );
182
183 const ALLEGRO::FMT_VER formatVersion =
184 getFormatVersionFromStr( boardTestEntry.value( "formatVersion", "unknown" ) );
185
186 return BOARD_TEST_INFO{ aBrdName, boardDir, formatVersion };
187}
188
189
191{
192public:
193 void RunHeaderTest( const std::string& aBrdName, const nlohmann::json& aBoardTestJson )
194 {
195 BOOST_TEST_CONTEXT( wxString::Format( "Testing header from board %s", aBrdName ) )
196 {
197 BOARD_TEST_INFO brdDef = createBoardTestInfo( aBrdName, aBoardTestJson );
198
199 const std::string boardDir = AllegroBoardDataDir( aBrdName );
200
201 std::unique_ptr<HEADER_TEST_INFO> headerTest = nullptr;
202
203 if( aBoardTestJson.contains( "header" ) && aBoardTestJson["header"].is_object() )
204 {
205 headerTest = createHeaderTestEntry( boardDir, aBoardTestJson["header"] );
206 }
207
208 BOOST_REQUIRE( headerTest != nullptr );
209
210 if( headerTest->m_Skip )
211 {
212 BOOST_TEST_MESSAGE( "Skipping test for this header" );
213 return;
214 }
215
216 // Read in the header data
217 std::vector<uint8_t> headerData = loadDataByUri( headerTest->m_HeaderDataSource );
218 FILE_STREAM fileStream( headerData.data(), headerData.size() );
219 HEADER_PARSER parser( fileStream );
220
221 std::unique_ptr<FILE_HEADER> header = parser.ParseHeader();
222
223 BOOST_REQUIRE( header != nullptr );
224
225 // We don't expect any known header to be in an unknown format
227
228 // Make sure the format version matches what the board is expected to be
229 if( brdDef.m_FormatVersion != FMT_VER::V_UNKNOWN )
230 {
231 BOOST_TEST( parser.GetFormatVersion() == brdDef.m_FormatVersion );
232 }
233
234 // Other header tests - validate some commonly used fields?
235
236 // The parser should have consumed all the data in the header
237 BOOST_TEST( fileStream.Position() == headerData.size() );
238 }
239 }
240
241
242 void RunBlockTest( const std::string& aBrdName, const nlohmann::json& aBoardTestJson,
243 const nlohmann::json& aBlockTestJson )
244 {
245 const BOARD_TEST_INFO brdDef = createBoardTestInfo( aBrdName, aBoardTestJson );
246 const BLK_TEST_INFO blockTest = createBlockTestEntry( AllegroBoardDataDir( aBrdName ), aBlockTestJson );
247
248 BOOST_TEST_CONTEXT( wxString::Format( "Testing block type %#02x at offset %#010zx from board %s",
249 blockTest.m_BlockType, blockTest.m_BlockOffset, aBrdName ) )
250 {
251 if( blockTest.m_Skip )
252 {
253 BOOST_TEST_MESSAGE( "Skipping test for this block" );
254 return;
255 }
256
257 // Read in the block data, and create a FILE_STREAM for it
258 std::vector<uint8_t> data = loadDataByUri( blockTest.m_DataSource );
259 FILE_STREAM fileStream( data.data(), data.size() );
260
261 BLOCK_PARSER parser( fileStream, brdDef.m_FormatVersion );
262
263 bool endOfObjectsMarker = false;
264 std::unique_ptr<BLOCK_BASE> block = parser.ParseBlock( endOfObjectsMarker );
265
266 // Compare the result to the expected results
267 BOOST_REQUIRE( block != nullptr );
268
269 if( blockTest.m_ValidateFunc )
270 {
271 BOOST_TEST_CONTEXT( "Validating block contents" )
272 {
273 blockTest.m_ValidateFunc( *block );
274 }
275 }
276
277 // The parser should have consumed all the data in the block
278 BOOST_TEST( fileStream.Position() == data.size() );
279
280 // Look up and run additional ad-hoc block-level tests
281 KI_TEST::RunAdditionalBlockTest( aBrdName, blockTest.m_BlockOffset, *block );
282
283 // Now try to convert the block into a DB_OBJ
284 ALLEGRO::BRD_DB brd;
285 ALLEGRO::BRD_DB::OBJ_FACTORY objFactory( brd );
286 std::unique_ptr<ALLEGRO::DB_OBJ> dbObj = objFactory.CreateObject( *block );
287
288 // Not all blocks convert to DB_OBJ yet, but at least it mustn't crash.
289 // Eventually, this should always succeed.
290 if( dbObj )
291 {
292 KI_TEST::RunAdditionalObjectTest( aBrdName, blockTest.m_BlockOffset, *dbObj );
293 }
294 }
295 }
296};
297
298
299static void AssertNoErrors( const CAPTURING_REPORTER& aReporter )
300{
301 if( aReporter.GetErrorCount() > 0 )
302 {
303 std::ostringstream ss;
304 ss << "Expected no errors, but found " << aReporter.GetErrorCount() << " errors:";
305
306 for( const auto& msg : aReporter.GetMessages() )
307 {
308 if( msg.severity == RPT_SEVERITY_ERROR )
309 {
310 ss << "\n " << msg.text;
311 }
312 }
313 BOOST_TEST_MESSAGE( ss.str() );
314 }
315 BOOST_TEST( aReporter.GetErrorCount() == 0 );
316}
317
318
319void RunBoardLoad( const std::string& aBrdName, const nlohmann::json& aBoardTestJson )
320{
321 BOARD* board = nullptr;
322
323 BOOST_TEST_CONTEXT( wxString::Format( "Testing load from board %s", aBrdName ) )
324 {
325 if( aBoardTestJson.contains( "boardFile" ) )
326 {
327 const std::string boardFilePath =
328 KI_TEST::AllegroBoardDataDir( aBrdName ) + aBoardTestJson["boardFile"].get<std::string>();
329
330 if( !std::filesystem::exists( boardFilePath ) )
331 {
332 BOOST_FAIL( "Board file does not exist: " + boardFilePath );
333 return;
334 }
335
336 CAPTURING_REPORTER reporter;
337 board = KI_TEST::ALLEGRO_CACHED_LOADER::GetInstance().LoadAndCache( boardFilePath, &reporter );
338
339 const bool expectLoadFailure = aBoardTestJson.value( "expectLoadFailure", false );
340
341 if( expectLoadFailure )
342 {
343 BOOST_CHECK( board == nullptr );
344 BOOST_TEST_MESSAGE( "Board load was expected to fail, and it did." );
345 return;
346 }
347 else
348 {
349 BOOST_CHECK( board != nullptr );
350 BOOST_TEST_MESSAGE( "Board load was expected to succeed, and it did." );
351
352 // Can allow a certain number of warnings, but no errors
353 AssertNoErrors( reporter );
354
355 // Can check max warnings here if we want
356 if( reporter.GetWarningCount() > 0 )
357 {
358 std::ostringstream ss;
359 ss << aBrdName << ": " << reporter.GetWarningCount() << " warnings";
360
361 for( const auto& msg : reporter.GetMessages() )
362 {
363 if( msg.severity == RPT_SEVERITY_WARNING )
364 {
365 ss << "\n " << msg.text;
366 }
367 }
368
369 BOOST_TEST_MESSAGE( ss.str() );
370 }
371
372 KI_TEST::PrintBoardStats( board, aBrdName );
373 }
374 }
375 }
376}
377
378
379void RunBoardExpectations( const std::string& aBrdName, const nlohmann::json& aBoardTestJson )
380{
381 if( !aBoardTestJson.contains( "boardFile" ) )
382 {
383 BOOST_FAIL( "Board test JSON does not contain a 'boardFile' entry" );
384 return;
385 }
386
387 const std::string boardFilePath =
388 KI_TEST::AllegroBoardDataDir( aBrdName ) + aBoardTestJson["boardFile"].get<std::string>();
389
391
392 if( !board )
393 {
394 BOOST_FAIL( "Failed to load cached board for expectations test: " + boardFilePath );
395 return;
396 }
397
398 BOOST_TEST_CONTEXT( "Running board expectations" )
399 {
400 std::unique_ptr<BOARD_EXPECTATION_TEST> boardExpectationTest = nullptr;
401
402 // There are default expectations that apply unless otherwise specified
403 {
404 bool hasContent = board->GetNetCount() > 0 || board->Footprints().size() > 0;
405 BOOST_CHECK( hasContent ); // The board should have some content (e.g. nets or footprints)
406 }
407
408 // Load board expectations from the JSON file and create expectation objects
409 if( aBoardTestJson.contains( "boardExpectations" ) )
410 {
411 boardExpectationTest = BOARD_EXPECTATION_TEST::CreateFromJson( aBrdName, aBoardTestJson["boardExpectations"] );
412 }
413
414 if( boardExpectationTest )
415 {
416 boardExpectationTest->RunTest( *board );
417 }
418 }
419}
420
421
427{
428 uint8_t m_BlockType;
431 const nlohmann::json& m_BlockTestJson;
432};
433
439{
440 // The board name in the JSON registry
441 std::string m_BrdName;
442
443 // Handy ref to the JSON entry for this board test
444 const nlohmann::json& m_BrdTestJson;
445
447 std::vector<ALLEGRO_BLOCK_TEST> m_BlockTests;
450};
451
452
461{
462 nlohmann::json m_Json;
463 std::vector<ALLEGRO_BOARD_TEST_REF> m_BoardTests;
464
465 const nlohmann::json& GetBoardJson( const std::string& aBoardName ) const
466 {
467 return m_Json["boards"][aBoardName];
468 }
469};
470
471
479static std::vector<ALLEGRO_BOARD_TEST_REF> getBoardTestDefinitions( const nlohmann::json& aJson )
480{
481 // For now, we just return a hardcoded list of the boards we have test data for, but in the future
482 // we could make this dynamic by e.g. scanning the data dir or having a registry file.
483
484 if( !aJson.contains( "boards" ) || !aJson["boards"].is_object() )
485 {
486 throw std::runtime_error( "Test register JSON file does not contain a valid 'boards' object." );
487 }
488
489 // Iterate the "boards" dict
490 std::vector<ALLEGRO_BOARD_TEST_REF> boardTests;
491
492 // Build only the very basic test definitions here, just enough to construct the test cases metadata
493 for( auto& [boardName, boardEntry] : aJson["boards"].items() )
494 {
495 ALLEGRO_BOARD_TEST_REF boardTestRef{
496 .m_BrdName = boardName,
497 .m_BrdTestJson = boardEntry,
498 .m_HasHeaderTest = boardEntry.contains( "header" ),
499 .m_BlockTests = {},
500 .m_HasBoardFile = boardEntry.contains( "boardFile" ),
501 .m_HasExpectations = boardEntry.contains( "boardExpectations" ),
502 };
503
504 if( boardEntry.contains( "blocks" ) && boardEntry["blocks"].is_array() )
505 {
506 for( const auto& blockTestEntry : boardEntry["blocks"] )
507 {
508 if( !blockTestEntry.contains( "type" ) || !blockTestEntry["type"].is_string() )
509 {
510 throw std::runtime_error( "Block test entry is missing a valid 'type' field" );
511 }
512
513 if( !blockTestEntry.contains( "offset" ) || !blockTestEntry["offset"].is_string() )
514 {
515 throw std::runtime_error( "Block test entry is missing a valid 'offset' field" );
516 }
517
518 const std::string blockTypeStr = blockTestEntry["type"];
519 const uint8_t blockType = std::stoul( blockTypeStr, nullptr, 0 );
520
521 const std::string block_offset = blockTestEntry["offset"];
522 const size_t blockOffset = std::stoul( block_offset, nullptr, 0 );
523
524 boardTestRef.m_BlockTests.push_back( ALLEGRO_BLOCK_TEST{
525 blockType,
526 blockOffset,
527 blockTestEntry,
528 } );
529 }
530 }
531
532 boardTests.push_back( std::move( boardTestRef ) );
533 }
534
535 return boardTests;
536}
537
538
540{
541 static ALLEGRO_BLOCK_TEST_REGISTRY registry = []()
542 {
544
545 // There is currently one registry file, but we could have more
546 const std::filesystem::path testRegisterJsonPath( AllegroBoardDataDir( "" ) + "board_data_registry.json" );
547 std::ifstream jsonFileStream( testRegisterJsonPath );
548
549 reg.m_Json = nlohmann::json::parse( jsonFileStream, nullptr, false, true );
551
552 return reg;
553 }();
554
555 return registry;
556}
557
558
562static std::vector<std::string> getBoardTestLabels( const nlohmann::json& boardTestJson )
563{
564 std::vector<std::string> labels;
565
566 if( boardTestJson.contains( "testLabels" ) && boardTestJson["testLabels"].is_array() )
567 {
568 for( const auto& label : boardTestJson["testLabels"] )
569 {
570 if( label.is_string() )
571 {
572 labels.push_back( label.get<std::string>() );
573 }
574 }
575 }
576
577 return labels;
578}
579
580
590static std::vector<boost::unit_test::test_suite*> buildAllegroBoardSuites()
591{
592 using BTS = boost::unit_test::test_suite;
593 std::vector<BTS*> suites;
594 BTS* blockSuite = suites.emplace_back( BOOST_TEST_SUITE( "AllegroBlocks" ) );
595 const ALLEGRO_BLOCK_TEST_REGISTRY* registry = nullptr;
596
597 try
598 {
599 registry = &buildTestRegistry();
600 }
601 catch( const std::exception& ex )
602 {
603 std::string msg = "Failed to load Allegro block test definitions: " + std::string( ex.what() );
604
605 // Register one failing test to report the error, so that we don't just silently skip all the tests
606 const auto failingTestFunction = [=]()
607 {
608 BOOST_FAIL( msg );
609 };
610
611 blockSuite->add(
612 boost::unit_test::make_test_case(
613 failingTestFunction,
614 "FailureToLoadTestDefinitions",
615 __FILE__,
616 __LINE__
617 )
618 );
619
620 return suites;
621 }
622
623 for( const ALLEGRO_BOARD_TEST_REF& boardTest : registry->m_BoardTests )
624 {
625 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.m_BrdName );
626 blockSuite->add( boardSuite );
627
628 const nlohmann::json& boardTestJson = registry->GetBoardJson( boardTest.m_BrdName );
629
630 if( boardTest.m_HasHeaderTest )
631 {
632 // Note: captures ref-to-static
633 const auto testRunFunction = [&]()
634 {
636 fixture.RunHeaderTest( boardTest.m_BrdName, boardTestJson );
637 };
638
639 boardSuite->add(
640 boost::unit_test::make_test_case(
641 testRunFunction,
642 "Header",
643 __FILE__,
644 __LINE__
645 )
646 );
647 }
648
649 for( const auto& blockTest : boardTest.m_BlockTests )
650 {
651 const auto testRunFunction = [&]()
652 {
654 fixture.RunBlockTest( boardTest.m_BrdName, boardTestJson, blockTest.m_BlockTestJson );
655 };
656
657 wxString testName =
658 wxString::Format( "Block_0x%02x_offset_0x%010zx", blockTest.m_BlockType, blockTest.m_BlockOffset );
659
660 boardSuite->add(
661 boost::unit_test::make_test_case(
662 testRunFunction,
663 testName.ToStdString(),
664 __FILE__,
665 __LINE__
666 )
667 );
668 }
669 }
670
671 // Separate test suite for board expectations, which will run after the header and block parsing tests,
672 // and will have access to the fully parsed board (and obviously be much slower)
673 BTS* boardSuites = suites.emplace_back( BOOST_TEST_SUITE( "AllegroBoards" ) );
674
675 for( const ALLEGRO_BOARD_TEST_REF& boardTest : registry->m_BoardTests )
676 {
677 if( !boardTest.m_HasBoardFile )
678 {
679 continue;
680 }
681
682 // Board-level suite
683 BTS* boardSuite = BOOST_TEST_SUITE( boardTest.m_BrdName );
684 boardSuites->add( boardSuite );
685
686 const nlohmann::json& boardTestData = registry->GetBoardJson( boardTest.m_BrdName );
687
688 const std::vector<std::string> testLabels = getBoardTestLabels( boardTestData );
689 boost::unit_test::test_case* loadingTestCase = nullptr;
690
691 if( boardTest.m_HasBoardFile )
692 {
693 const auto testLoadFunction = [&]()
694 {
695 RunBoardLoad( boardTest.m_BrdName, boardTestData );
696 };
697
698 // The first test unit loads (and caches) the board.
699 loadingTestCase = boost::unit_test::make_test_case(
700 testLoadFunction,
701 "Import",
702 __FILE__,
703 __LINE__
704 );
705
706 for( const std::string& label : testLabels )
707 {
708 loadingTestCase->add_label( label );
709 }
710
711 boardSuite->add( loadingTestCase );
712 }
713
714 if( boardTest.m_HasExpectations )
715 {
716 const auto testRunFunction = [&]()
717 {
718 RunBoardExpectations( boardTest.m_BrdName, boardTestData );
719 };
720
721 boost::unit_test::test_case* expectationTestCase = boost::unit_test::make_test_case(
722 testRunFunction,
723 "Expectations",
724 __FILE__,
725 __LINE__
726 );
727
728 // We can only run the expectations test after the board has been loaded
729 // and that test passes.
730 BOOST_REQUIRE( loadingTestCase != nullptr );
731 expectationTestCase->depends_on( loadingTestCase );
732 boardSuite->add( expectationTestCase );
733 }
734 }
735
736 return suites;
737}
738
739
745{
747 {
748 std::vector<boost::unit_test::test_suite*> suites = buildAllegroBoardSuites();
749
750 for( boost::unit_test::test_suite* suite : suites )
751 {
752 boost::unit_test::framework::master_test_suite().add( suite );
753 }
754 }
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...
Definition allegro_db.h:960
std::unique_ptr< DB_OBJ > CreateObject(const BLOCK_BASE &aBlock) const
An Allegro database that represents a .brd file (amd presumably .dra)
Definition allegro_db.h:948
Stream that reads primitive types from a memory buffer containing Allegro .brd (or ....
size_t Position() const
Parses a .brd header, taking care of any version-specific differences.
std::unique_ptr< FILE_HEADER > ParseHeader()
FMT_VER GetFormatVersion() const
Get the parsed format version.
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.
Definition board.h:322
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.
const std::vector< MESSAGE > & GetMessages() 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.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
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())
std::string path
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")