52static std::string
vecToString( std::span<const std::string> aVec )
54 std::ostringstream ss;
57 for(
const std::string& s : aVec )
102 if( aJson.is_object() )
104 if( aJson.contains(
"exact" ) )
106 auto [v, units] =
parseScalar( aJson.at(
"exact" ), aKind );
113 if( aJson.contains(
"min" ) )
115 auto [v, units] =
parseScalar( aJson.at(
"min" ), aKind );
120 if( aJson.contains(
"max" ) )
122 auto [v, units] =
parseScalar( aJson.at(
"max" ), aKind );
131 throw std::runtime_error(
"Scalar constraint object must have"
132 " 'min', 'max', or 'exact': "
187 return std::to_string( aValue );
192 std::ostringstream ss;
193 ss << userStr.ToStdString() <<
" (" << aValue <<
" nm)";
236 if( aJson.is_number_integer() )
238 int raw = aJson.get<
int>();
243 return { raw, std::nullopt };
246 if( aJson.is_number_float() )
249 throw std::runtime_error(
"Float value not valid for a count constraint" );
254 if( aJson.is_string() )
258 throw std::runtime_error(
"String value not valid for a count constraint: " + aJson.dump() );
261 const std::string& dimStr = aJson.get<std::string>();
268 return {
KiROUND( dimIu ), detectedUnits };
271 throw std::runtime_error(
"Invalid scalar constraint value: " + aJson.dump() );
297 if( aConstraint.
GetMin() )
299 const int minVal = *aConstraint.
GetMin();
300 BOOST_TEST( aActual >= minVal,
"expected at least " << aConstraint.
Format( minVal ) <<
", got "
301 << aConstraint.
Format( aActual ) );
304 if( aConstraint.
GetMax() )
306 const int maxVal = *aConstraint.
GetMax();
307 BOOST_TEST( aActual <= maxVal,
"expected at most " << aConstraint.
Format( maxVal ) <<
", got "
308 << aConstraint.
Format( aActual ) );
325 static bool matchPredicate(
const std::string& aStr,
const std::string& aPattern )
327 return wxString( aStr ).Matches( aPattern );
344 static bool nameMatches(
const std::string& aName,
const std::string& aPattern )
346 return wxString( aName ).Matches( aPattern );
351 std::vector<const NETINFO_ITEM*> matches;
358 matches.push_back( net );
367 if(
nameMatches( net->GetNetname().ToStdString(), pattern ) )
369 matches.push_back( net );
380 wxASSERT(
m_Count.has_value() );
393 BOOST_FAIL(
"Net expectation must have at least a count or a name pattern" );
419 const auto& netMatchesPattern = [&](
const NETINFO_ITEM* n )
421 return nameMatches( n->GetNetname().ToStdString(), pattern );
424 bool found = std::any_of( matches.begin(), matches.end(), netMatchesPattern );
426 BOOST_TEST( found,
"Expected net matching '" << pattern <<
"'" );
433 std::string desc =
"Net";
445 desc +=
" count: " +
m_Count->Describe();
471 wxCHECK(
m_Count.has_value(), );
473 int actualCount = netSettings->GetNetclasses().size();
481 static bool nameMatches(
const wxString& aName,
const std::string& aPattern )
483 return aName.Matches( aPattern );
488 std::vector<const NETCLASS*> matches;
494 for(
const auto& [
name, nc] : netclasses )
496 matches.push_back( nc.get() );
501 for(
const auto& [
name, netclass] : netclasses )
507 matches.push_back( netclass.get() );
541 const auto& netclassMatchesPattern = [&](
const NETCLASS* nc )
546 bool found = std::any_of( matches.begin(), matches.end(), netclassMatchesPattern );
548 BOOST_TEST( found,
"Expected netclass matching '" << pattern <<
"'" );
558 BOOST_CHECK( nc->HasTrackWidth() );
567 BOOST_CHECK( nc->HasClearance() );
576 BOOST_CHECK( nc->HasDiffPairGap() );
585 BOOST_CHECK( nc->HasDiffPairWidth() );
596 int matchingNetCount = 0;
615 if( net->GetNetCode() <= 0 )
623 if( nc->
GetName() == aNetClassName )
632 std::string desc =
"Netclasses";
638 desc +=
" count: " +
m_Count->Describe();
668 std::vector<std::string> actualNames;
671 for(
const auto& layer : cuLayers )
673 actualNames.push_back( aBrd.
GetLayerName( layer ).ToStdString() );
678 for(
size_t i = 0; i <
m_CuNames.size(); ++i )
690 return std::string(
"Layers: " ) + (
m_CuCount.has_value() ?
m_CuCount->Describe() :
"N/A" );
720 BOOST_TEST_FAIL(
"Expression error: " << aMessage.ToStdString() <<
" at offset " << aOffset );
765 std::vector<const BOARD_ITEM*> items;
772 BOOST_TEST_MESSAGE(
"Collected " << items.size() <<
" items to evaluate expression against in "
773 << collectTimer.
msecs() <<
" ms" );
776 size_t matchCount = 0;
780 matchCount = items.size();
788 if( matchItem( *item, ucode ) )
799 BOOST_CHECK( matchItem( *parentItem, parentUcode ) );
806 <<
" items in " << matchTimer.
msecs() <<
" ms" );
818 BOOST_TEST( matchCount > 0,
"Expected expression to match at least one item, but it matched none" );
824 std::ostringstream ss;
834 std::vector<const BOARD_ITEM*> items;
837 items.push_back( track );
841 items.push_back( fp );
843 for(
const PAD*
pad : fp->Pads() )
844 items.push_back(
pad );
846 for(
const PCB_FIELD* field : fp->GetFields() )
847 items.push_back( field );
849 for(
const BOARD_ITEM* gi : fp->GraphicalItems() )
850 items.push_back( gi );
852 for(
const ZONE* zone : fp->Zones() )
853 items.push_back( zone );
857 items.push_back( item );
860 items.push_back( zone );
867 std::vector<const BOARD_ITEM*> items;
876 items.push_back( track );
885 items.push_back( track );
892 items.push_back( fp );
900 items.push_back( item );
907 items.push_back( item );
913 items.push_back( zone );
920 for(
const PAD*
pad : fp->Pads() )
921 items.push_back(
pad );
929 for(
const BOARD_ITEM* gi : fp->GraphicalItems() )
930 items.push_back( gi );
938 for(
const PCB_FIELD* field : fp->GetFields() )
939 items.push_back( field );
947 for(
const ZONE* zone : fp->Zones() )
948 items.push_back( zone );
966 std::vector<std::string>
result;
968 if( aJson.is_string() )
970 result.push_back( aJson );
972 else if( aJson.is_array() )
974 for(
const auto& entry : aJson )
976 if( !entry.is_string() )
978 throw std::runtime_error(
"Expected a string or an array of strings" );
981 result.push_back( entry );
986 throw std::runtime_error(
"Expected a string or an array of strings" );
995 auto netExpectation = std::make_unique<NET_EXPECTATION>();
997 if( aExpectationEntry.contains(
"count" ) )
999 netExpectation->m_Count =
1003 if( aExpectationEntry.contains(
"name" ) )
1005 const auto& expectedNetName = aExpectationEntry.at(
"name" );
1006 netExpectation->m_NamePatterns =
getStringArray( expectedNetName );
1009 return netExpectation;
1015 auto netClassExpectation = std::make_unique<NETCLASS_EXPECTATION>();
1017 if( aExpectationEntry.contains(
"count" ) )
1019 netClassExpectation->m_Count =
1023 if( aExpectationEntry.contains(
"name" ) )
1025 const auto& expectedNetClassName = aExpectationEntry.at(
"name" );
1026 netClassExpectation->m_NetClassNames =
getStringArray( expectedNetClassName );
1029 if( aExpectationEntry.contains(
"trackWidth" ) )
1031 netClassExpectation->m_TrackWidth =
1035 if( aExpectationEntry.contains(
"clearance" ) )
1037 netClassExpectation->m_Clearance =
1041 if( aExpectationEntry.contains(
"dpGap" ) )
1043 netClassExpectation->m_DpGap =
1047 if( aExpectationEntry.contains(
"dpWidth" ) )
1049 netClassExpectation->m_DpWidth =
1053 if( aExpectationEntry.contains(
"netCount" ) )
1055 netClassExpectation->m_MatchingNetCount =
1059 return netClassExpectation;
1065 auto layerExpectation = std::make_unique<LAYER_EXPECTATION>();
1067 if( aExpectationEntry.contains(
"cuNames" ) )
1069 const auto& cuNamesEntry = aExpectationEntry.at(
"cuNames" );
1070 std::vector<std::string> cuNames =
getStringArray( cuNamesEntry );
1071 layerExpectation->m_CuNames = std::move( cuNames );
1074 if( aExpectationEntry.contains(
"cuCount" ) )
1076 layerExpectation->m_CuCount =
1079 else if( layerExpectation->m_CuNames.size() > 0 )
1082 layerExpectation->m_CuCount =
1086 return layerExpectation;
1092 auto evalExpectation = std::make_unique<ITEM_EVAL_EXPECTATION>();
1094 if( aExpectationEntry.contains(
"count" ) )
1096 evalExpectation->m_ExpectedMatches =
1101 if( aExpectationEntry.contains(
"expr" ) )
1103 evalExpectation->m_Expression = aExpectationEntry.at(
"expr" ).get<std::string>();
1106 if( aExpectationEntry.contains(
"itemType" ) )
1108 if( !aExpectationEntry.at(
"itemType" ).is_string() )
1110 throw std::runtime_error(
"Eval expectation 'itemType' field must be a string" );
1113 const std::string itemTypeStr = aExpectationEntry.at(
"itemType" ).get<std::string>();
1116 const static std::unordered_map<std::string, ITYPE> itemTypeMap = {
1117 {
"track", ITYPE::BOARD_TRACK },
1118 {
"via", ITYPE::BOARD_VIA },
1119 {
"footprint", ITYPE::BOARD_FOOTPRINT },
1120 {
"board_graphic", ITYPE::BOARD_GRAPHIC },
1121 {
"board_zone", ITYPE::BOARD_ZONE },
1122 {
"board_group", ITYPE::BOARD_GROUP },
1123 {
"pad", ITYPE::FP_PAD },
1124 {
"field", ITYPE::FP_FIELD },
1125 {
"fp_graphic", ITYPE::FP_GRAPHIC },
1126 {
"fp_zone", ITYPE::FP_ZONE },
1127 {
"any", ITYPE::ANY },
1130 const auto it = itemTypeMap.find( itemTypeStr );
1131 if( it == itemTypeMap.end() )
1133 throw std::runtime_error(
"Unknown eval expectation item type: " + itemTypeStr );
1136 evalExpectation->m_ItemType = it->second;
1139 if( aExpectationEntry.contains(
"parentExpr" ) )
1141 evalExpectation->m_ParentExpr = aExpectationEntry.at(
"parentExpr" ).get<std::string>();
1144 return evalExpectation;
1149 const nlohmann::json& aBrdExpectation )
1151 using ExpectationFactoryFunc = std::unique_ptr<BOARD_EXPECTATION> ( * )(
const nlohmann::json& );
1154 static const std::unordered_map<std::string, ExpectationFactoryFunc> factoryMap = {
1162 std::unique_ptr<BOARD_EXPECTATION_TEST>
test = std::make_unique<BOARD_EXPECTATION_TEST>( aBrdName );
1164 if( !aBrdExpectation.is_object() )
1166 throw std::runtime_error(
"Expectation entry for board " + aBrdName +
" is not a valid JSON object" );
1169 if( !aBrdExpectation.contains(
"type" ) || !aBrdExpectation.at(
"type" ).is_string() )
1171 throw std::runtime_error(
"Expectation entry for board " + aBrdName
1172 +
" must have a string field named 'type'" );
1175 const std::string expectationType = aBrdExpectation.at(
"type" ).get<std::string>();
1177 auto it = factoryMap.find( expectationType );
1178 if( it == factoryMap.end() )
1180 throw std::runtime_error(
"Unsupported expectation type '" + expectationType +
"' for board " + aBrdName );
1183 if( std::unique_ptr<BOARD_EXPECTATION> expectation = it->second( aBrdExpectation ) )
1186 if( aBrdExpectation.contains(
"comment" ) && aBrdExpectation.at(
"comment" ).is_string() )
1188 expectation->SetComment( aBrdExpectation.at(
"comment" ).get<std::string>() );
1191 if( aBrdExpectation.contains(
"skip" ) && aBrdExpectation.at(
"skip" ).is_boolean()
1192 && aBrdExpectation.at(
"skip" ).get<
bool>() )
1194 test->m_skip =
true;
1197 test->m_expectation = std::move( expectation );
1201 throw std::runtime_error(
"Failed to create expectation for board " + aBrdName );
1208std::vector<BOARD_EXPECTATION_TEST::DESCRIPTOR>
1211 std::vector<DESCRIPTOR> tests;
1213 if( !aJsonArray.is_array() )
1215 throw std::runtime_error(
"Board expectations JSON must be an array of expectations" );
1218 unsigned int index = 0;
1219 for(
const auto& expectationEntry : aJsonArray )
1221 if( !expectationEntry.is_object() )
1223 throw std::runtime_error(
"Expectation entry at index " + std::to_string(
index )
1224 +
" is not a valid JSON object" );
1228 std::vector<std::string> tags;
1230 if( expectationEntry.contains(
"testName" ) )
1232 if( !expectationEntry.at(
"testName" ).is_string() )
1234 throw std::runtime_error(
"Expectation entry 'testName' field at index " + std::to_string(
index )
1235 +
" must be a string, " +
" but is was "
1236 + expectationEntry.at(
"testName" ).type_name() );
1239 name = expectationEntry.at(
"testName" ).get<std::string>();
1247 if( expectationEntry.contains(
"tags" ) )
1249 if( !expectationEntry.at(
"tags" ).is_array() )
1251 throw std::runtime_error(
"Expectation entry 'tags' field at index " + std::to_string(
index )
1252 +
" must be an array of strings" );
1255 for(
const auto& tagEntry : expectationEntry.at(
"tags" ) )
1257 if( !tagEntry.is_string() )
1259 throw std::runtime_error(
"Expectation entry 'tags' field at index " + std::to_string(
index )
1260 +
" must be an array of strings" );
1263 tags.push_back( tagEntry.get<std::string>() );
1267 tests.emplace_back(
name, tags, expectationEntry );
1280 const std::string& expectationComment =
m_expectation->GetComment();
1281 if( !expectationComment.empty() )
1305 std::unique_ptr<BOARD_EXPECTATION_TEST> boardExpectationTest;
1310 if( boardExpectationTest )
1312 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_TEST(contains==c.ExpectedContains)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
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)