48static std::string
vecToString( std::span<const std::string> aVec )
50 std::ostringstream ss;
53 for(
const std::string& s : aVec )
98 if( aJson.is_object() )
100 if( aJson.contains(
"exact" ) )
102 auto [v, units] =
parseScalar( aJson.at(
"exact" ), aKind );
109 if( aJson.contains(
"min" ) )
111 auto [v, units] =
parseScalar( aJson.at(
"min" ), aKind );
116 if( aJson.contains(
"max" ) )
118 auto [v, units] =
parseScalar( aJson.at(
"max" ), aKind );
127 throw std::runtime_error(
"Scalar constraint object must have"
128 " 'min', 'max', or 'exact': "
183 return std::to_string( aValue );
188 std::ostringstream ss;
189 ss << userStr.ToStdString() <<
" (" << aValue <<
" nm)";
232 if( aJson.is_number_integer() )
234 int raw = aJson.get<
int>();
239 return { raw, std::nullopt };
242 if( aJson.is_number_float() )
245 throw std::runtime_error(
"Float value not valid for a count constraint" );
250 if( aJson.is_string() )
254 throw std::runtime_error(
"String value not valid for a count constraint: " + aJson.dump() );
257 const std::string& dimStr = aJson.get<std::string>();
264 return {
KiROUND( dimIu ), detectedUnits };
267 throw std::runtime_error(
"Invalid scalar constraint value: " + aJson.dump() );
293 if( aConstraint.
GetMin() )
295 const int minVal = *aConstraint.
GetMin();
296 BOOST_TEST( aActual >= minVal,
"expected at least " << aConstraint.
Format( minVal ) <<
", got "
297 << aConstraint.
Format( aActual ) );
300 if( aConstraint.
GetMax() )
302 const int maxVal = *aConstraint.
GetMax();
303 BOOST_TEST( aActual <= maxVal,
"expected at most " << aConstraint.
Format( maxVal ) <<
", got "
304 << aConstraint.
Format( aActual ) );
321 static bool matchPredicate(
const std::string& aStr,
const std::string& aPattern )
323 return wxString( aStr ).Matches( aPattern );
340 static bool nameMatches(
const std::string& aName,
const std::string& aPattern )
342 return wxString( aName ).Matches( aPattern );
347 std::vector<const NETINFO_ITEM*> matches;
354 matches.push_back( net );
363 if(
nameMatches( net->GetNetname().ToStdString(), pattern ) )
365 matches.push_back( net );
376 wxASSERT(
m_Count.has_value() );
389 BOOST_FAIL(
"Net expectation must have at least a count or a name pattern" );
415 const auto& netMatchesPattern = [&](
const NETINFO_ITEM* n )
417 return nameMatches( n->GetNetname().ToStdString(), pattern );
420 bool found = std::any_of( matches.begin(), matches.end(), netMatchesPattern );
422 BOOST_TEST( found,
"Expected net matching '" << pattern <<
"'" );
429 std::string desc =
"Net";
441 desc +=
" count: " +
m_Count->Describe();
467 wxCHECK(
m_Count.has_value(), );
469 int actualCount = netSettings->GetNetclasses().size();
477 static bool nameMatches(
const wxString& aName,
const std::string& aPattern )
479 return aName.Matches( aPattern );
484 std::vector<const NETCLASS*> matches;
490 for(
const auto& [
name, nc] : netclasses )
492 matches.push_back( nc.get() );
497 for(
const auto& [
name, netclass] : netclasses )
503 matches.push_back( netclass.get() );
537 const auto& netclassMatchesPattern = [&](
const NETCLASS* nc )
542 bool found = std::any_of( matches.begin(), matches.end(), netclassMatchesPattern );
544 BOOST_TEST( found,
"Expected netclass matching '" << pattern <<
"'" );
554 BOOST_CHECK( nc->HasTrackWidth() );
563 BOOST_CHECK( nc->HasClearance() );
572 BOOST_CHECK( nc->HasDiffPairGap() );
581 BOOST_CHECK( nc->HasDiffPairWidth() );
592 int matchingNetCount = 0;
611 if( net->GetNetCode() <= 0 )
619 if( nc->
GetName() == aNetClassName )
628 std::string desc =
"Netclasses";
634 desc +=
" count: " +
m_Count->Describe();
664 std::vector<std::string> actualNames;
667 for(
const auto& layer : cuLayers )
669 actualNames.push_back( aBrd.
GetLayerName( layer ).ToStdString() );
674 for(
size_t i = 0; i <
m_CuNames.size(); ++i )
686 return std::string(
"Layers: " ) + (
m_CuCount.has_value() ?
m_CuCount->Describe() :
"N/A" );
716 BOOST_TEST_FAIL(
"Expression error: " << aMessage.ToStdString() <<
" at offset " << aOffset );
761 std::vector<const BOARD_ITEM*> items;
768 BOOST_TEST_MESSAGE(
"Collected " << items.size() <<
" items to evaluate expression against in "
769 << collectTimer.
msecs() <<
" ms" );
772 size_t matchCount = 0;
776 matchCount = items.size();
784 if( matchItem( *item, ucode ) )
795 BOOST_CHECK( matchItem( *parentItem, parentUcode ) );
802 <<
" items in " << matchTimer.
msecs() <<
" ms" );
814 BOOST_TEST( matchCount > 0,
"Expected expression to match at least one item, but it matched none" );
820 std::ostringstream ss;
830 std::vector<const BOARD_ITEM*> items;
833 items.push_back( track );
837 items.push_back( fp );
839 for(
const PAD*
pad : fp->Pads() )
840 items.push_back(
pad );
842 for(
const PCB_FIELD* field : fp->GetFields() )
843 items.push_back( field );
845 for(
const BOARD_ITEM* gi : fp->GraphicalItems() )
846 items.push_back( gi );
848 for(
const ZONE* zone : fp->Zones() )
849 items.push_back( zone );
853 items.push_back( item );
856 items.push_back( zone );
863 std::vector<const BOARD_ITEM*> items;
872 items.push_back( track );
881 items.push_back( track );
888 items.push_back( fp );
896 items.push_back( item );
903 items.push_back( item );
909 items.push_back( zone );
916 for(
const PAD*
pad : fp->Pads() )
917 items.push_back(
pad );
925 for(
const BOARD_ITEM* gi : fp->GraphicalItems() )
926 items.push_back( gi );
934 for(
const PCB_FIELD* field : fp->GetFields() )
935 items.push_back( field );
943 for(
const ZONE* zone : fp->Zones() )
944 items.push_back( zone );
962 std::vector<std::string>
result;
964 if( aJson.is_string() )
966 result.push_back( aJson );
968 else if( aJson.is_array() )
970 for(
const auto& entry : aJson )
972 if( !entry.is_string() )
974 throw std::runtime_error(
"Expected a string or an array of strings" );
977 result.push_back( entry );
982 throw std::runtime_error(
"Expected a string or an array of strings" );
991 auto netExpectation = std::make_unique<NET_EXPECTATION>();
993 if( aExpectationEntry.contains(
"count" ) )
995 netExpectation->m_Count =
999 if( aExpectationEntry.contains(
"name" ) )
1001 const auto& expectedNetName = aExpectationEntry.at(
"name" );
1002 netExpectation->m_NamePatterns =
getStringArray( expectedNetName );
1005 return netExpectation;
1011 auto netClassExpectation = std::make_unique<NETCLASS_EXPECTATION>();
1013 if( aExpectationEntry.contains(
"count" ) )
1015 netClassExpectation->m_Count =
1019 if( aExpectationEntry.contains(
"name" ) )
1021 const auto& expectedNetClassName = aExpectationEntry.at(
"name" );
1022 netClassExpectation->m_NetClassNames =
getStringArray( expectedNetClassName );
1025 if( aExpectationEntry.contains(
"trackWidth" ) )
1027 netClassExpectation->m_TrackWidth =
1031 if( aExpectationEntry.contains(
"clearance" ) )
1033 netClassExpectation->m_Clearance =
1037 if( aExpectationEntry.contains(
"dpGap" ) )
1039 netClassExpectation->m_DpGap =
1043 if( aExpectationEntry.contains(
"dpWidth" ) )
1045 netClassExpectation->m_DpWidth =
1049 if( aExpectationEntry.contains(
"netCount" ) )
1051 netClassExpectation->m_MatchingNetCount =
1055 return netClassExpectation;
1061 auto layerExpectation = std::make_unique<LAYER_EXPECTATION>();
1063 if( aExpectationEntry.contains(
"cuNames" ) )
1065 const auto& cuNamesEntry = aExpectationEntry.at(
"cuNames" );
1066 std::vector<std::string> cuNames =
getStringArray( cuNamesEntry );
1067 layerExpectation->m_CuNames = std::move( cuNames );
1070 if( aExpectationEntry.contains(
"cuCount" ) )
1072 layerExpectation->m_CuCount =
1075 else if( layerExpectation->m_CuNames.size() > 0 )
1078 layerExpectation->m_CuCount =
1082 return layerExpectation;
1088 auto evalExpectation = std::make_unique<ITEM_EVAL_EXPECTATION>();
1090 if( aExpectationEntry.contains(
"count" ) )
1092 evalExpectation->m_ExpectedMatches =
1097 if( aExpectationEntry.contains(
"expr" ) )
1099 evalExpectation->m_Expression = aExpectationEntry.at(
"expr" ).get<std::string>();
1102 if( aExpectationEntry.contains(
"itemType" ) )
1104 if( !aExpectationEntry.at(
"itemType" ).is_string() )
1106 throw std::runtime_error(
"Eval expectation 'itemType' field must be a string" );
1109 const std::string itemTypeStr = aExpectationEntry.at(
"itemType" ).get<std::string>();
1112 const static std::unordered_map<std::string, ITYPE> itemTypeMap = {
1113 {
"track", ITYPE::BOARD_TRACK },
1114 {
"via", ITYPE::BOARD_VIA },
1115 {
"footprint", ITYPE::BOARD_FOOTPRINT },
1116 {
"board_graphic", ITYPE::BOARD_GRAPHIC },
1117 {
"board_zone", ITYPE::BOARD_ZONE },
1118 {
"board_group", ITYPE::BOARD_GROUP },
1119 {
"pad", ITYPE::FP_PAD },
1120 {
"field", ITYPE::FP_FIELD },
1121 {
"fp_graphic", ITYPE::FP_GRAPHIC },
1122 {
"fp_zone", ITYPE::FP_ZONE },
1123 {
"any", ITYPE::ANY },
1126 const auto it = itemTypeMap.find( itemTypeStr );
1127 if( it == itemTypeMap.end() )
1129 throw std::runtime_error(
"Unknown eval expectation item type: " + itemTypeStr );
1132 evalExpectation->m_ItemType = it->second;
1135 if( aExpectationEntry.contains(
"parentExpr" ) )
1137 evalExpectation->m_ParentExpr = aExpectationEntry.at(
"parentExpr" ).get<std::string>();
1140 return evalExpectation;
1145 const nlohmann::json& aBrdExpectation )
1147 using ExpectationFactoryFunc = std::unique_ptr<BOARD_EXPECTATION> ( * )(
const nlohmann::json& );
1150 static const std::unordered_map<std::string, ExpectationFactoryFunc> factoryMap = {
1158 std::unique_ptr<BOARD_EXPECTATION_TEST>
test = std::make_unique<BOARD_EXPECTATION_TEST>( aBrdName );
1160 if( !aBrdExpectation.is_object() )
1162 throw std::runtime_error(
"Expectation entry for board " + aBrdName +
" is not a valid JSON object" );
1165 if( !aBrdExpectation.contains(
"type" ) || !aBrdExpectation.at(
"type" ).is_string() )
1167 throw std::runtime_error(
"Expectation entry for board " + aBrdName
1168 +
" must have a string field named 'type'" );
1171 const std::string expectationType = aBrdExpectation.at(
"type" ).get<std::string>();
1173 auto it = factoryMap.find( expectationType );
1174 if( it == factoryMap.end() )
1176 throw std::runtime_error(
"Unsupported expectation type '" + expectationType +
"' for board " + aBrdName );
1179 if( std::unique_ptr<BOARD_EXPECTATION> expectation = it->second( aBrdExpectation ) )
1182 if( aBrdExpectation.contains(
"comment" ) && aBrdExpectation.at(
"comment" ).is_string() )
1184 expectation->SetComment( aBrdExpectation.at(
"comment" ).get<std::string>() );
1187 if( aBrdExpectation.contains(
"skip" ) && aBrdExpectation.at(
"skip" ).is_boolean()
1188 && aBrdExpectation.at(
"skip" ).get<
bool>() )
1190 test->m_skip =
true;
1193 test->m_expectation = std::move( expectation );
1197 throw std::runtime_error(
"Failed to create expectation for board " + aBrdName );
1204std::vector<BOARD_EXPECTATION_TEST::DESCRIPTOR>
1207 std::vector<DESCRIPTOR> tests;
1209 if( !aJsonArray.is_array() )
1211 throw std::runtime_error(
"Board expectations JSON must be an array of expectations" );
1214 unsigned int index = 0;
1215 for(
const auto& expectationEntry : aJsonArray )
1217 if( !expectationEntry.is_object() )
1219 throw std::runtime_error(
"Expectation entry at index " + std::to_string(
index )
1220 +
" is not a valid JSON object" );
1224 std::vector<std::string> tags;
1226 if( expectationEntry.contains(
"testName" ) )
1228 if( !expectationEntry.at(
"testName" ).is_string() )
1230 throw std::runtime_error(
"Expectation entry 'testName' field at index " + std::to_string(
index )
1231 +
" must be a string, " +
" but is was "
1232 + expectationEntry.at(
"testName" ).type_name() );
1235 name = expectationEntry.at(
"testName" ).get<std::string>();
1243 if( expectationEntry.contains(
"tags" ) )
1245 if( !expectationEntry.at(
"tags" ).is_array() )
1247 throw std::runtime_error(
"Expectation entry 'tags' field at index " + std::to_string(
index )
1248 +
" must be an array of strings" );
1251 for(
const auto& tagEntry : expectationEntry.at(
"tags" ) )
1253 if( !tagEntry.is_string() )
1255 throw std::runtime_error(
"Expectation entry 'tags' field at index " + std::to_string(
index )
1256 +
" must be an array of strings" );
1259 tags.push_back( tagEntry.get<std::string>() );
1263 tests.emplace_back(
name, tags, expectationEntry );
1276 const std::string& expectationComment =
m_expectation->GetComment();
1277 if( !expectationComment.empty() )
1301 std::unique_ptr<BOARD_EXPECTATION_TEST> boardExpectationTest;
1306 if( boardExpectationTest )
1308 boardExpectationTest->RunTest( aBoard );
constexpr EDA_IU_SCALE pcbIUScale
static std::unique_ptr< BOARD_EXPECTATION > createNetExpectation(const nlohmann::json &aExpectationEntry)
static std::unique_ptr< BOARD_EXPECTATION > createNetClassExpectation(const nlohmann::json &aExpectationEntry)
static std::unique_ptr< BOARD_EXPECTATION > createItemExprExpectation(const nlohmann::json &aExpectationEntry)
static std::string vecToString(std::span< const std::string > aVec)
Format a vector of strings for display, e.g.
static std::unique_ptr< BOARD_EXPECTATION > createLayerExpectation(const nlohmann::json &aExpectationEntry)
static std::vector< std::string > getStringArray(const nlohmann::json &aJson)
static void CheckConstraint(const SCALAR_CONSTRAINT &aConstraint, int aActual)
Assert that aActual satisfies aConstraint using Boost.Test macros.
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
std::shared_ptr< NET_SETTINGS > m_NetSettings
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
BOARD_ITEM_CONTAINER * GetParent() const
Information pertinent to a Pcbnew printed circuit board.
const NETINFO_LIST & GetNetInfo() const
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
const ZONES & Zones() const
const GROUPS & Groups() const
The groups must maintain the following invariants.
int GetCopperLayerCount() const
const FOOTPRINTS & Footprints() const
const TRACKS & Tracks() const
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
unsigned GetNetCount() const
const DRAWINGS & Drawings() const
std::vector< const BOARD_ITEM * > collectAllBoardItems(const BOARD &aBrd) const
std::optional< wxString > m_Expression
void RunTest(const BOARD &aBrd) const override
static void reportError(const wxString &aMessage, int aOffset)
std::optional< wxString > m_ParentExpr
std::optional< ITEM_TYPE > m_ItemType
std::string GetName() const override
std::vector< const BOARD_ITEM * > collectItemsOfType(const BOARD &aBrd, ITEM_TYPE aType) const
std::optional< SCALAR_CONSTRAINT > m_ExpectedMatches
static std::unique_ptr< BOARD_EXPECTATION_TEST > CreateFromJson(const std::string &aBrdName, const nlohmann::json &aBrdExpectations)
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...
void RunTest(const BOARD &aBrd) const
Runs the test against the given board.
std::unique_ptr< BOARD_EXPECTATION > m_expectation
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.
A single expectation about a board, which can be run as a test against a parsed BOARD.
std::optional< SCALAR_CONSTRAINT > m_CuCount
std::string GetName() const override
void RunTest(const BOARD &aBrd) const override
std::vector< std::string > m_CuNames
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
void SetErrorCallback(std::function< void(const wxString &aMessage, int aOffset)> aCallback)
LSET is a set of PCB_LAYER_IDs.
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
void RunTest(const BOARD &aBrd) const override
static bool nameMatches(const wxString &aName, const std::string &aPattern)
std::optional< SCALAR_CONSTRAINT > m_Count
std::optional< SCALAR_CONSTRAINT > m_MatchingNetCount
Expectation for number of nets matching the netclass patterns in aggregate.
std::string GetName() const override
static int CountMatchingNets(const BOARD &aBrd, const wxString &aNetClassName)
std::optional< SCALAR_CONSTRAINT > m_DpGap
std::vector< std::string > m_NetClassNames
std::optional< SCALAR_CONSTRAINT > m_DpWidth
void doSimpleCountTest(const BOARD &aBrd) const
std::optional< SCALAR_CONSTRAINT > m_Clearance
std::optional< SCALAR_CONSTRAINT > m_TrackWidth
Expectation for track width constraint for all matching netclasses.
std::vector< const NETCLASS * > findMatchingNetclasses(const BOARD &aBrd) const
A collection of nets and the parameters used to route or test these nets.
const wxString GetName() const
Gets the name of this (maybe aggregate) netclass in a format for internal usage or for export to exte...
Handle the data for a net.
void RunTest(const BOARD &aBrd) const override
void doSimpleCountTest(const BOARD &aBrd) const
std::vector< std::string > m_NamePatterns
std::string GetName() const override
std::vector< const NETINFO_ITEM * > findMatchingNets(const BOARD &aBrd) const
static bool nameMatches(const std::string &aName, const std::string &aPattern)
std::optional< SCALAR_CONSTRAINT > m_Count
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetNetclasses() const
Gets all netclasses.
void SetItems(BOARD_ITEM *a, BOARD_ITEM *b=nullptr)
A small class to help profiling.
double msecs(bool aSinceLast=false)
A constraint on a scalar value (counts, dimensions, etc.), supporting exact, min, and max bounds.
bool Match(int aValue) const
Test if a value satisfies this constraint.
std::optional< int > m_max
static SCALAR_CONSTRAINT FromJson(const nlohmann::json &aJson, ROLE aKind)
Parse a scalar constraint from JSON.
std::optional< int > m_min
std::string Describe() const
std::optional< EDA_UNITS > m_displayUnits
Original units for display.
static SCALAR_CONSTRAINT Exact(int aValue, ROLE aKind=ROLE::COUNT)
static PARSED_SCALAR parseScalar(const nlohmann::json &aJson, ROLE aKind)
Parse a single bound value from JSON.
std::optional< int > GetMax() const
@ COUNT
A simple count of items (e.g. number of footprints, nets, etc.)
@ DIMENSION
A linear dimension (e.g. track width, clearance)
std::optional< int > GetMin() const
std::string Format(int aValue) const
Format a value for human-readable display.
static bool matchPredicate(const std::string &aStr, const std::string &aPattern)
void Test(const std::string &aStr) const
STRING_PATTERN_MATCHER(const std::string &aPattern)
Handle a list of polygons defining a copper zone.
PCB_LAYER_ID
A quick note on layer IDs:
KICOMMON_API wxString StringFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Return the string from aValue according to aUnits (inch, mm ...) for display.
KICOMMON_API double DoubleValueFromString(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Convert aTextValue to a double.
KICOMMON_API bool FetchUnitsFromString(const wxString &aTextValue, EDA_UNITS &aUnits)
Write any unit info found in the string to aUnits.
Class to handle a set of BOARD_ITEMs.
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.
const nlohmann::json & m_TestJson
Handy ref to the JSON entry for this expectations test, which saves looking it up again.
std::optional< EDA_UNITS > units
The units detected from the input, if any.
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)
VECTOR3I expected(15, 30, 45)
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
BOOST_CHECK_PREDICATE(ArePolylineEndPointsNearCircle,(chain)(c.m_geom.m_center_point)(radius)(accuracy+epsilon))
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)