KiCad PCB EDA Suite
Loading...
Searching...
No Matches
board_expectations.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 3
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/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 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
27
28#include <optional>
29
30#include <board.h>
31
32using namespace KI_TEST;
33
34
40{
41public:
42 static INT_MATCHER FromJson( const nlohmann::json& aJson )
43 {
44 INT_MATCHER matcher;
45
46 if( aJson.is_number() )
47 {
48 int v = aJson.get<int>();
49 matcher.m_min = v;
50 matcher.m_max = v;
51 }
52 else if( aJson.is_object() )
53 {
54 if( aJson.contains( "exact" ) )
55 {
56 int v = aJson["exact"];
57 matcher.m_min = v;
58 matcher.m_max = v;
59 }
60 else
61 {
62 if( aJson.contains( "min" ) )
63 matcher.m_min = aJson["min"];
64
65 if( aJson.contains( "max" ) )
66 matcher.m_max = aJson["max"];
67 }
68 }
69 else
70 {
71 throw std::runtime_error( "Invalid count expectation: " + aJson.dump() );
72 }
73
74 return matcher;
75 }
76
77 static INT_MATCHER Exact( int aValue )
78 {
79 INT_MATCHER matcher;
80 matcher.m_min = aValue;
81 matcher.m_max = aValue;
82 return matcher;
83 }
84
85 void Test( int aActual ) const
86 {
87 if( m_min )
88 BOOST_TEST( aActual >= *m_min );
89
90 if( m_max )
91 BOOST_TEST( aActual <= *m_max );
92 }
93
94 std::string Describe() const
95 {
96 if( m_min && m_max && *m_min == *m_max )
97 return "exactly " + std::to_string( *m_min );
98
99 std::string desc;
100
101 if( m_min )
102 desc += "at least " + std::to_string( *m_min );
103
104 if( m_max )
105 {
106 if( !desc.empty() )
107 desc += ", ";
108
109 desc += "at most " + std::to_string( *m_max );
110 }
111
112 return desc;
113 }
114
115private:
116 std::optional<int> m_min;
117 std::optional<int> m_max;
118};
119
120
125{
126public:
127 explicit STRING_PATTERN_MATCHER( const std::string& aPattern ) :
128 m_pattern( aPattern )
129 {
130 }
131
132 static bool matchPredicate( const std::string& aStr, const std::string& aPattern )
133 {
134 return wxString( aStr ).Matches( aPattern );
135 }
136
137 void Test( const std::string& aStr ) const { BOOST_CHECK_PREDICATE( matchPredicate, (aStr) ( m_pattern ) ); }
138
139private:
140 std::string m_pattern;
141};
142
143
145{
146public:
147 std::optional<INT_MATCHER> m_Count;
148
149private:
150 void RunTest( const BOARD& aBrd ) const override
151 {
152 int actualCount = aBrd.Footprints().size();
153
154 // TODO: filter footprints by layer, if layer filter is specified in the future
155
156 if( m_Count.has_value() )
157 {
158 BOOST_TEST_CONTEXT( "Footprint count: " + m_Count->Describe() )
159 {
160 m_Count->Test( actualCount );
161 }
162 }
163 }
164
165 std::string GetName() const override
166 {
167 return std::string( "Footprint: " ) + ( m_Count.has_value() ? m_Count->Describe() : "N/A" );
168 }
169};
170
171
173{
174public:
175 std::optional<INT_MATCHER> m_Count;
176 std::vector<std::string> m_NamePatterns;
177
178private:
179 static bool nameMatches( const std::string& aName, const std::string& aPattern )
180 {
181 return wxString( aName ).Matches( aPattern );
182 }
183
184 std::vector<const NETINFO_ITEM*> findMatchingNets( const BOARD& aBrd ) const
185 {
186 std::vector<const NETINFO_ITEM*> matches;
187
188 if( m_NamePatterns.empty() )
189 {
190 // No patterns = all nets
191 for( const NETINFO_ITEM* net : aBrd.GetNetInfo() )
192 {
193 matches.push_back( net );
194 }
195 return matches;
196 }
197
198 for( const NETINFO_ITEM* net : aBrd.GetNetInfo() )
199 {
200 for( const std::string& pattern : m_NamePatterns )
201 {
202 if( nameMatches( net->GetNetname().ToStdString(), pattern ) )
203 {
204 matches.push_back( net );
205 break;
206 }
207 }
208 }
209
210 return matches;
211 }
212
213 void doSimpleCountTest( const BOARD& aBrd ) const
214 {
215 wxASSERT( m_Count.has_value() );
216 int actualCount = aBrd.GetNetCount();
217
218 BOOST_TEST_CONTEXT( "Net count: " + m_Count->Describe() )
219 {
220 m_Count->Test( actualCount );
221 }
222 }
223
224 void RunTest( const BOARD& aBrd ) const override
225 {
226 // Optimisation - if we ONLY have a count, we have a simple test that doesn't require iterating
227 // all the nets
228 if( m_Count.has_value() && m_NamePatterns.empty() )
229 {
230 doSimpleCountTest( aBrd );
231 return;
232 }
233
234 std::vector<const NETINFO_ITEM*> matches = findMatchingNets( aBrd );
235
236 const NETINFO_LIST& nets = aBrd.GetNetInfo();
237
238 if( m_Count )
239 {
240 // We need to check the count of matching nets
241 BOOST_TEST_CONTEXT( "Net count: " + m_Count->Describe() )
242 {
243 m_Count->Test( static_cast<int>( matches.size() ) );
244 }
245 }
246 else
247 {
248 // No count: every pattern must match at least one net
249 for( const std::string& pattern : m_NamePatterns )
250 {
251 const auto& netMatchesPattern = [&]( const NETINFO_ITEM* n )
252 {
253 return nameMatches( n->GetNetname().ToStdString(), pattern );
254 };
255
256 bool found = std::any_of( matches.begin(), matches.end(), netMatchesPattern );
257
258 BOOST_TEST_CONTEXT( "Expected net matching '" << pattern << "'" )
259 {
260 BOOST_TEST( found );
261 }
262 }
263 }
264 }
265
266 std::string GetName() const override
267 {
268 std::string desc = "Net";
269
270 if( m_NamePatterns.size() == 1 )
271 {
272 desc += " '" + m_NamePatterns[0] + "'";
273 }
274 else if( !m_NamePatterns.empty() )
275 {
276 std::string joined;
277 for( size_t i = 0; i < m_NamePatterns.size(); ++i )
278 {
279 if( i > 0 )
280 joined += "', '";
281 joined += m_NamePatterns[i];
282 }
283 desc += " ['" + joined + "']";
284 }
285
286 if( m_Count )
287 desc += " count: " + m_Count->Describe();
288 else
289 desc += " exists";
290
291 return desc;
292 }
293};
294
295
297{
298public:
299 std::optional<INT_MATCHER> m_CuCount;
300 std::vector<std::string> m_CuNames;
301
302private:
303 void RunTest( const BOARD& aBrd ) const override
304 {
305 int actualCount = aBrd.GetCopperLayerCount();
306
307 if( m_CuCount.has_value() )
308 {
309 BOOST_TEST_CONTEXT( "Layer count: " + m_CuCount->Describe() )
310 {
311 m_CuCount->Test( actualCount );
312 }
313 }
314
315 if( !m_CuNames.empty() )
316 {
317 std::vector<std::string> actualNames;
318 const LSET cuLayers = aBrd.GetLayerSet() & LSET::AllCuMask();
319
320 for( const auto& layer : cuLayers )
321 {
322 actualNames.push_back( aBrd.GetLayerName( layer ).ToStdString() );
323 }
324
325 BOOST_REQUIRE( actualNames.size() == m_CuNames.size() );
326
327 for( size_t i = 0; i < m_CuNames.size(); ++i )
328 {
329 BOOST_TEST_CONTEXT( "Expecting Cu layer name: '" << m_CuNames[i] << "'" )
330 {
331 BOOST_TEST( actualNames[i] == m_CuNames[i] );
332 }
333 }
334 }
335 }
336
337 std::string GetName() const override
338 {
339 return std::string( "Layers: " ) + ( m_CuCount.has_value() ? m_CuCount->Describe() : "N/A" );
340 }
341};
342
343
344static std::unique_ptr<BOARD_EXPECTATION> createFootprintExpectation( const nlohmann::json& aExpectationEntry )
345{
346 auto footprintExpectation = std::make_unique<FOOTPRINT_EXPECTATION>();
347
348 if( aExpectationEntry.contains( "count" ) )
349 {
350 const auto& countEntry = aExpectationEntry["count"];
351 const INT_MATCHER countMatcher = INT_MATCHER::FromJson( countEntry );
352 footprintExpectation->m_Count = countMatcher;
353 }
354
355 return footprintExpectation;
356}
357
358
359static std::vector<std::string> getStringArray( const nlohmann::json& aJson )
360{
361 std::vector<std::string> result;
362
363 if( aJson.is_string() )
364 {
365 result.push_back( aJson );
366 }
367 else if( aJson.is_array() )
368 {
369 for( const auto& entry : aJson )
370 {
371 if( !entry.is_string() )
372 {
373 throw std::runtime_error( "Expected a string or an array of strings" );
374 }
375
376 result.push_back( entry );
377 }
378 }
379 else
380 {
381 throw std::runtime_error( "Expected a string or an array of strings" );
382 }
383
384 return result;
385}
386
387
388static std::unique_ptr<BOARD_EXPECTATION> createNetExpectation( const nlohmann::json& aExpectationEntry )
389{
390 auto netExpectation = std::make_unique<NET_EXPECTATION>();
391
392 if( aExpectationEntry.contains( "count" ) )
393 {
394 const auto& countEntry = aExpectationEntry["count"];
395 netExpectation->m_Count = INT_MATCHER::FromJson( countEntry );
396 }
397
398 if( aExpectationEntry.contains( "name" ) )
399 {
400 const auto& expectedNetName = aExpectationEntry["name"];
401 netExpectation->m_NamePatterns = getStringArray( expectedNetName );
402 }
403
404 return netExpectation;
405}
406
407
408static std::unique_ptr<BOARD_EXPECTATION> createLayerExpectation( const nlohmann::json& aExpectationEntry )
409{
410 auto layerExpectation = std::make_unique<LAYER_EXPECTATION>();
411
412 if( aExpectationEntry.contains( "cuNames" ) )
413 {
414 const auto& cuNamesEntry = aExpectationEntry["cuNames"];
415 std::vector<std::string> cuNames = getStringArray( cuNamesEntry );
416 layerExpectation->m_CuNames = std::move( cuNames );
417 }
418
419 if( aExpectationEntry.contains( "count" ) )
420 {
421 const auto& countEntry = aExpectationEntry["cuCount"];
422 layerExpectation->m_CuCount = INT_MATCHER::FromJson( countEntry );
423 }
424 else if( layerExpectation->m_CuNames.size() > 0 )
425 {
426 // If specific layer names are specified, we expect that many layers
427 layerExpectation->m_CuCount = INT_MATCHER::Exact( static_cast<int>( layerExpectation->m_CuNames.size() ) );
428 }
429
430 return layerExpectation;
431}
432
433
434std::unique_ptr<BOARD_EXPECTATION_TEST> BOARD_EXPECTATION_TEST::CreateFromJson( const std::string& aBrdName,
435 const nlohmann::json& aBrdExpectations )
436{
437 std::unique_ptr<BOARD_EXPECTATION_TEST> test = std::make_unique<BOARD_EXPECTATION_TEST>( aBrdName );
438
439 if( !aBrdExpectations.is_array() )
440 {
441 throw std::runtime_error( "Board expectations for board " + aBrdName + " are not a valid JSON object" );
442 }
443
444 for( const auto& expectationEntry : aBrdExpectations )
445 {
446 if( !expectationEntry.is_object() )
447 {
448 throw std::runtime_error( "Expectation entry for board " + aBrdName + " is not a valid JSON object" );
449 }
450
451 if( !expectationEntry.contains( "type" ) || !expectationEntry["type"].is_string() )
452 {
453 throw std::runtime_error( "Expectation entry for board " + aBrdName
454 + " must have a string field named 'type'" );
455 }
456
457 const std::string expectationType = expectationEntry["type"];
458
459 std::unique_ptr<BOARD_EXPECTATION> expectation;
460
461 if( expectationType == "footprint" )
462 {
463 expectation = createFootprintExpectation( expectationEntry );
464 }
465 else if( expectationType == "net" )
466 {
467 expectation = createNetExpectation( expectationEntry );
468 }
469 else if( expectationType == "layers" )
470 {
471 expectation = createLayerExpectation( expectationEntry );
472 }
473 else
474 {
475 throw std::runtime_error( "Unsupported expectation type '" + expectationType + "' for board " + aBrdName );
476 }
477
478 if( expectation )
479 test->m_expectations.push_back( std::move( expectation ) );
480 }
481
482 return test;
483}
484
485
486void BOARD_EXPECTATION_TEST::RunTest( const BOARD& aBrd ) const
487{
488 for( const auto& expectation : m_expectations )
489 {
490 BOOST_TEST_CONTEXT( wxString::Format( "Checking expectation of type %s", expectation->GetName() ) )
491 {
492 expectation->RunTest( aBrd );
493 }
494 }
495}
static std::unique_ptr< BOARD_EXPECTATION > createFootprintExpectation(const nlohmann::json &aExpectationEntry)
static std::unique_ptr< BOARD_EXPECTATION > createNetExpectation(const nlohmann::json &aExpectationEntry)
static std::unique_ptr< BOARD_EXPECTATION > createLayerExpectation(const nlohmann::json &aExpectationEntry)
static std::vector< std::string > getStringArray(const nlohmann::json &aJson)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
const NETINFO_LIST & GetNetInfo() const
Definition board.h:996
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition board.h:680
int GetCopperLayerCount() const
Definition board.cpp:919
const FOOTPRINTS & Footprints() const
Definition board.h:363
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:728
unsigned GetNetCount() const
Definition board.h:1027
std::optional< INT_MATCHER > m_Count
void RunTest(const BOARD &aBrd) const override
std::string GetName() const override
Simple binary expectation that checks if an integer value meets the expectation (exact,...
static INT_MATCHER Exact(int aValue)
std::string Describe() const
std::optional< int > m_min
std::optional< int > m_max
static INT_MATCHER FromJson(const nlohmann::json &aJson)
void Test(int aActual) const
std::vector< std::unique_ptr< BOARD_EXPECTATION > > m_expectations
static std::unique_ptr< BOARD_EXPECTATION_TEST > CreateFromJson(const std::string &aBrdName, const nlohmann::json &aBrdExpectations)
void RunTest(const BOARD &aBrd) const
Runs the test against the given board.
A single expectation about a board, which can be run as a test against a parsed BOARD.
std::string GetName() const override
void RunTest(const BOARD &aBrd) const override
std::vector< std::string > m_CuNames
std::optional< INT_MATCHER > m_CuCount
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
Handle the data for a net.
Definition netinfo.h:54
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:212
std::optional< INT_MATCHER > m_Count
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)
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)
BOOST_TEST(contains==c.ExpectedContains)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
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.