KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_libeval_compiler.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, see <https://www.gnu.org/licenses/>.
18 */
19
22#include <boost/test/data/test_case.hpp>
23
24#include <wx/wx.h>
25
26#include <layer_ids.h>
27#include <gal/color4d.h>
28#include <geometry/eda_angle.h>
30#include <drc/drc_rule.h>
31#include <drc/drc_rule_parser.h>
32#include <ki_exception.h>
33#include <reporter.h>
34#include <pcbnew/board.h>
36#include <pcbnew/pcb_shape.h>
37#include <pcbnew/pcb_table.h>
38#include <pcbnew/pcb_track.h>
39#include <pcbnew/footprint.h>
40#include <pcbnew/pcb_text.h>
41#include <pcbnew/zone.h>
43#include <properties/property.h>
45
46BOOST_AUTO_TEST_SUITE( Libeval_Compiler )
47
49{
50 wxString expression;
53
54 friend std::ostream& operator<<( std::ostream& os, const EXPR_TO_TEST& expr )
55 {
56 os << expr.expression;
57 return os;
58 }
59};
60
62
63const static std::vector<EXPR_TO_TEST> simpleExpressions = {
64 { "10mm + 20 mm", false, VAL( 30e6 ) },
65 { "3*(7+8)", false, VAL( 3 * ( 7 + 8 ) ) },
66 { "3*7+8", false, VAL( 3 * 7 + 8 ) },
67 { "(3*7)+8", false, VAL( 3 * 7 + 8 ) },
68 { "10mm + 20)", true, VAL( 0 ) },
69
70 { "1", false, VAL(1) },
71 { "1.5", false, VAL(1.5) },
72 { "1,5", false, VAL(1.5) },
73 { "1mm", false, VAL(1e6) },
74 // Any White-space is OK
75 { " 1 + 2 ", false, VAL(3) },
76 // Decimals are OK in expressions
77 { "1.5 + 0.2 + 0.1", false, VAL(1.8) },
78 // Negatives are OK
79 { "3 - 10", false, VAL(-7) },
80 // Lots of operands
81 { "1 + 2 + 10 + 1000.05", false, VAL(1013.05) },
82 // Operator precedence
83 { "1 + 2 - 4 * 20 / 2", false, VAL(-37) },
84 // Parens
85 { "(1)", false, VAL(1) },
86 // Parens affect precedence
87 { "-(1 + (2 - 4)) * 20.8 / 2", false, VAL(10.4) },
88 // Unary addition is a sign, not a leading operator
89 { "+2 - 1", false, VAL(1) },
90 // A short-circuited || must yield a normalized 1, not the raw (nonzero) left operand, so a
91 // boolean feeding a further operator behaves the same as the non-short-circuited path.
92 { "(2 || 0) == 1", false, VAL(1) },
93 { "(7 || 0) + 5", false, VAL(6) }
94};
95
96
97const static std::vector<EXPR_TO_TEST> introspectionExpressions = {
98 { "A.type == 'Pad' && B.type == 'Pad' && (A.existsOnLayer('F.Cu'))", false, VAL( 0.0 ) },
99 { "A.Width > B.Width", false, VAL( 0.0 ) },
100 { "A.Width + B.Width", false, VAL( pcbIUScale.MilsToIU(10) + pcbIUScale.MilsToIU(20) ) },
101 { "A.Netclass", false, VAL( "HV_LINE" ) },
102 { "A.Net_Class == 'HV_LINE'", false, VAL( 1.0 ) },
103 { "(A.Netclass == 'HV_LINE') && (B.netclass == 'otherClass') && (B.netclass != 'F.Cu')", false, VAL( 1.0 ) },
104 { "A.Netclass + 1.0", false, VAL( 1.0 ) },
105 { "A.hasNetclass('HV_LINE')", false, VAL( 1.0 ) },
106 { "A.hasNetclass('HV_*')", false, VAL( 1.0 ) },
107 { "A.existsOnLayer(B.Layer)", false, VAL( 1.0 ) },
108 { "A.type == 'Track' && B.type == 'Track' && A.layer == 'F.Cu'", false, VAL( 1.0 ) },
109 { "(A.type == 'Track') && (B.type == 'Track') && (A.layer == 'F.Cu')", false, VAL( 1.0 ) },
110 { "A.type == 'Via' && A.isMicroVia()", false, VAL(0.0) }
111};
112
113
114static bool testEvalExpr( const wxString& expr, const LIBEVAL::VALUE& expectedResult, bool expectError = false,
115 BOARD_ITEM* itemA = nullptr, BOARD_ITEM* itemB = nullptr )
116{
117 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
118 PCBEXPR_UCODE ucode;
121 bool ok = true;
122
123 context.SetItems( itemA, itemB );
124
125
126 BOOST_TEST_MESSAGE( "Expr: '" << expr.c_str() << "'" );
127
128 bool error = !compiler.Compile( expr, &ucode, &preflightContext );
129
130 BOOST_CHECK_EQUAL( error, expectError );
131
132 if( error != expectError )
133 {
134 BOOST_TEST_MESSAGE( "Result: FAIL: " << compiler.GetError().message.c_str() <<
135 " (code pos: " << compiler.GetError().srcPos << ")" );
136
137 return false;
138 }
139
140 if( error )
141 return true;
142
144
145 if( ok )
146 {
147 result = ucode.Run( &context );
148 ok = ( result->EqualTo( &context, &expectedResult ) );
149 }
150
151 if( expectedResult.GetType() == LIBEVAL::VT_NUMERIC )
152 {
153 BOOST_CHECK_EQUAL( result->AsDouble(), expectedResult.AsDouble() );
154 }
155 else
156 {
157 BOOST_CHECK_EQUAL( result->AsString(), expectedResult.AsString() );
158 }
159
160 return ok;
161}
162
163
164static void expectCompileError( const wxString& aExpr )
165{
166 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
167 PCBEXPR_UCODE ucode;
169
170 compiler.Compile( aExpr, &ucode, &preflight );
171
172 BOOST_CHECK_MESSAGE( compiler.IsErrorPending(), "Expected a compile error for: " << aExpr.mb_str() );
173}
174
175
176static void expectCompileSuccess( const wxString& aExpr )
177{
178 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
179 PCBEXPR_UCODE ucode;
181
182 compiler.Compile( aExpr, &ucode, &preflight );
183
184 BOOST_CHECK_MESSAGE( !compiler.IsErrorPending(), "Expected expression to compile: " << aExpr.mb_str() );
185}
186
187
188BOOST_DATA_TEST_CASE( SimpleExpressions, boost::unit_test::data::make( simpleExpressions ), expr )
189{
190 testEvalExpr( expr.expression, expr.expectedResult, expr.expectError );
191}
192
193
194BOOST_AUTO_TEST_CASE( IntrospectedProperties )
195{
197 propMgr.Rebuild();
198
199 BOARD brd;
200
201 const NETINFO_LIST& netInfo = brd.GetNetInfo();
202
203 std::shared_ptr<NETCLASS> netclass1( new NETCLASS( "HV_LINE" ) );
204 std::shared_ptr<NETCLASS> netclass2( new NETCLASS( "otherClass" ) );
205
206 NETINFO_ITEM* net1info = new NETINFO_ITEM( &brd, "net1", 1 );
207 NETINFO_ITEM* net2info = new NETINFO_ITEM( &brd, "net2", 2 );
208
209 net1info->SetNetClass( netclass1 );
210 net2info->SetNetClass( netclass2 );
211
212 PCB_TRACK trackA( &brd );
213 PCB_TRACK trackB( &brd );
214
215 trackA.SetNet( net1info );
216 trackB.SetNet( net2info );
217
218 trackB.SetLayer( F_Cu );
219
220 trackA.SetWidth( pcbIUScale.MilsToIU( 10 ) );
221 trackB.SetWidth( pcbIUScale.MilsToIU( 20 ) );
222
223 for( const auto& expr : introspectionExpressions )
224 {
225 testEvalExpr( expr.expression, expr.expectedResult, expr.expectError, &trackA, &trackB );
226 }
227}
228
229
230BOOST_AUTO_TEST_CASE( IntrospectedExtendedNumericProperties )
231{
233 propMgr.Rebuild();
234
235 BOARD brd;
236 ZONE zone( &brd );
237
238 zone.SetLayer( F_Cu );
239 zone.SetAssignedPriority( 7 );
240 zone.SetHatchOrientation( EDA_ANGLE( 45.0, DEGREES_T ) );
241 zone.SetMinIslandArea( 123456789LL );
242
243 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 1.0 ), false, &zone );
244 testEvalExpr( wxT( "A.Hatch_Orientation == 45deg" ), VAL( 1.0 ), false, &zone );
245 testEvalExpr( wxT( "A.Minimum_Island_Area == 123456789" ), VAL( 1.0 ), false, &zone );
246
247 PCB_SHAPE ellipse( &brd, SHAPE_T::ELLIPSE );
248 ellipse.SetEllipseRotation( EDA_ANGLE( 30.0, DEGREES_T ) );
249
250 testEvalExpr( wxT( "A.Ellipse_Rotation == 30deg" ), VAL( 1.0 ), false, &ellipse );
251
252 FOOTPRINT footprint( &brd );
253 footprint.SetLocalSolderPasteMarginRatio( 0.125 );
254
255 testEvalExpr( wxT( "A.Solderpaste_Margin_Ratio_Override == 0.125" ), VAL( 1.0 ), false, &footprint );
256
257 footprint.SetLocalSolderPasteMarginRatio( std::nullopt );
258
259 testEvalExpr( wxT( "A.Solderpaste_Margin_Ratio_Override == null" ), VAL( 1.0 ), false, &footprint );
260}
261
262
263BOOST_AUTO_TEST_CASE( RenamedProperties )
264{
266 propMgr.Rebuild();
267
268 BOARD brd;
269 PCB_TRACK track( &brd );
270
271 track.SetStart( VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 2.0 ) ) );
272 track.SetEnd( VECTOR2I( pcbIUScale.mmToIU( 3.0 ), pcbIUScale.mmToIU( 4.0 ) ) );
273
274 expectCompileSuccess( wxT( "A.Origin_X == 1mm" ) );
275 expectCompileSuccess( wxT( "A.Origin_Y == 2mm" ) );
276
277 testEvalExpr( wxT( "A.Origin_X == 1mm" ), VAL( 1.0 ), false, &track );
278 testEvalExpr( wxT( "A.Origin_Y == 2mm" ), VAL( 1.0 ), false, &track );
279 testEvalExpr( wxT( "A.Origin_X != A.End_X" ), VAL( 1.0 ), false, &track );
280 testEvalExpr( wxT( "A.origin_y != A.End_Y" ), VAL( 1.0 ), false, &track );
281
282 testEvalExpr( wxT( "A.Start_X == 1mm" ), VAL( 1.0 ), false, &track );
283 testEvalExpr( wxT( "A.Start_Y == 2mm" ), VAL( 1.0 ), false, &track );
284}
285
286
287BOOST_AUTO_TEST_CASE( IntrospectedColorProperties )
288{
290 propMgr.Rebuild();
291
292 BOARD brd;
293 PCB_TABLE table( &brd, 0 );
294
295 auto checkColor =
296 [&]( const COLOR4D& aColor, const wxString& aExpectedCss )
297 {
298 table.SetBorderColor( aColor );
299 BOOST_CHECK_EQUAL( aColor.ToCSSString(), aExpectedCss );
300
301 wxString expression = wxT( "A.Border_Color == '" ) + aExpectedCss + wxT( "'" );
302 testEvalExpr( expression, VAL( 1.0 ), false, &table );
303 };
304
305 checkColor( COLOR4D( 0.25, 0.5, 0.75, 0.5 ), wxT( "rgba(64, 128, 191, 0.502)" ) );
306#ifndef __WXMAC__ // WXMAC doesn't have colour names initialized when run headless
307 checkColor( COLOR4D( wxString( wxT( "red" ) ) ), wxT( "rgb(255, 0, 0)" ) );
308#endif
309}
310
311
312BOOST_AUTO_TEST_CASE( ExpressionPropertyTypesAreSupported )
313{
315 propMgr.Rebuild();
316
317 std::set<std::pair<TYPE_ID, PROPERTY_BASE*>> seen;
318 std::map<wxString, std::set<LIBEVAL::VAR_TYPE_T>> fieldTypes;
319
320 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
321 {
322 if( !propMgr.IsOfType( cls.type, TYPE_HASH( BOARD_ITEM ) ) )
323 continue;
324
325 for( PROPERTY_BASE* prop : cls.properties )
326 {
327 if( !seen.emplace( cls.type, prop ).second )
328 continue;
329
331
332 BOOST_CHECK_MESSAGE( kind != PCBEXPR_PROPERTY_KIND::UNSUPPORTED,
333 "Unsupported expression property: class=" << cls.name.mb_str()
334 << ", property=" << prop->Name().mb_str()
335 << ", type_hash=" << prop->TypeHash() );
336
338 fieldTypes[prop->Name()].insert( PCBEXPR_VAR_REF::ExpressionType( kind ) );
339 }
340 }
341
342 // PCB_TARGET::Shape is numeric while EDA_SHAPE::Shape is an enum, so a global
343 // expression reference cannot have one stable public type.
344 auto shapeTypes = fieldTypes.find( wxT( "Shape" ) );
345 BOOST_REQUIRE( shapeTypes != fieldTypes.end() );
346 BOOST_CHECK_EQUAL( shapeTypes->second.size(), 2u );
347 fieldTypes.erase( shapeTypes );
348
349 for( const auto& [field, types] : fieldTypes )
350 {
351 BOOST_CHECK_MESSAGE( types.size() == 1, "Incompatible expression types for property: " << field.mb_str() );
352 }
353
354 expectCompileError( wxT( "A.Shape" ) );
355}
356
357
358BOOST_AUTO_TEST_CASE( IntrospectedPropertyAvailability )
359{
361 propMgr.Rebuild();
362
363 BOARD brd;
364 ZONE zone( &brd );
365
366 zone.SetLayer( F_Cu );
367 zone.SetAssignedPriority( 7 );
368 zone.SetIsRuleArea( true );
369
370 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 0.0 ), false, &zone );
371
372 zone.SetIsRuleArea( false );
373
374 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 1.0 ), false, &zone );
375}
376
377
378BOOST_AUTO_TEST_CASE( InNetChainClassWildcard )
379{
381 propMgr.Rebuild();
382
383 BOARD brd;
384
385 std::shared_ptr<NET_SETTINGS> netSettings = brd.GetDesignSettings().m_NetSettings;
386 netSettings->SetNetChainClass( wxT( "ChainHS" ), wxT( "HighSpeed" ) );
387
388 NETINFO_ITEM* netUnclassified = new NETINFO_ITEM( &brd, "netA", 1 );
389 NETINFO_ITEM* netClassified = new NETINFO_ITEM( &brd, "netB", 2 );
390 NETINFO_ITEM* netNoChain = new NETINFO_ITEM( &brd, "netC", 3 );
391
392 netUnclassified->SetNetChain( wxT( "ChainOrphan" ) );
393 netClassified->SetNetChain( wxT( "ChainHS" ) );
394
395 PCB_TRACK trackUnclassified( &brd );
396 PCB_TRACK trackClassified( &brd );
397 PCB_TRACK trackNoChain( &brd );
398
399 trackUnclassified.SetNet( netUnclassified );
400 trackClassified.SetNet( netClassified );
401 trackNoChain.SetNet( netNoChain );
402
403 // A chain with no class assignment must not match any inNetChainClass() pattern,
404 // including the '*' wildcard.
405 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackUnclassified, &trackUnclassified );
406 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 0.0 ), false, &trackUnclassified, &trackUnclassified );
407
408 // Net with no chain at all must not match either.
409 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackNoChain, &trackNoChain );
410
411 // Properly classified chain must match both wildcard and exact patterns.
412 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
413 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
414 testEvalExpr( wxT( "A.inNetChainClass('High*')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
415 testEvalExpr( wxT( "A.inNetChainClass('LowSpeed')" ), VAL( 0.0 ), false, &trackClassified, &trackClassified );
416}
417
418BOOST_AUTO_TEST_CASE( StackedMicroviaExpression )
419{
421 propMgr.Rebuild();
422
423 BOARD brd;
424 brd.SetCopperLayerCount( 4 );
425
426 auto microvia = []( BOARD* aBoard, const VECTOR2I& aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom )
427 {
428 PCB_VIA* via = new PCB_VIA( aBoard );
429
430 via->SetViaType( VIATYPE::MICROVIA );
431 via->SetPosition( aPos );
432 via->SetLayerPair( aTop, aBottom );
433 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.25 ) );
434 via->SetDrill( pcbIUScale.mmToIU( 0.1 ) );
435 aBoard->Add( via );
436
437 return via;
438 };
439
440 VECTOR2I origin( 0, 0 );
441 VECTOR2I away( pcbIUScale.mmToIU( 10 ), 0 );
442
443 PCB_VIA* upperHop = microvia( &brd, origin, F_Cu, In1_Cu );
444 PCB_VIA* lowerHop = microvia( &brd, origin, In1_Cu, In2_Cu );
445 PCB_VIA* lone = microvia( &brd, away, F_Cu, In1_Cu );
446
447 PCB_VIA* through = new PCB_VIA( &brd );
448
449 through->SetViaType( VIATYPE::THROUGH );
450 through->SetPosition( origin );
451 through->SetLayerPair( F_Cu, B_Cu );
452 through->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.6 ) );
453 through->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
454 brd.Add( through );
455
456 // Both hops of a stack are in it, not just the one on top.
457 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, upperHop, upperHop );
458 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, lowerHop, lowerHop );
459
460 // A microvia landing on nothing is not a stack.
461 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 0.0 ), false, lone, lone );
462
463 // A through via sharing the position of a stack is not part of it.
464 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 0.0 ), false, through, through );
465
466 // The predicate composes with the rest of the language.
467 testEvalExpr( wxT( "A.isMicroVia() && !A.isStackedVia()" ), VAL( 1.0 ), false, lone, lone );
468
469 // The relation is cached for the whole board, so an edit has to drop it.
470 PCB_VIA* landing = microvia( &brd, away, In1_Cu, In2_Cu );
471 brd.IncrementTimeStamp();
472
473 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, lone, lone );
474 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, landing, landing );
475}
476
477BOOST_AUTO_TEST_CASE( ParentNavigation )
478{
480 propMgr.Rebuild();
481
482 BOARD brd;
483
484 FOOTPRINT fp( &brd );
485 fp.SetReference( wxT( "J1" ) );
486
487 // A text item living inside the footprint. Its direct parent is the footprint, so
488 // "A.Parent" navigates to J1.
489 PCB_TEXT* text = new PCB_TEXT( &fp );
490 text->SetText( wxT( "J1" ) );
491 fp.Add( text );
492
493 // The headline capability: getField() only returns a value when its receiver is a
494 // footprint, so this passes solely because navigation steps from the text to its parent.
495 testEvalExpr( wxT( "A.Parent.getField('Reference') == 'J1'" ), VAL( 1.0 ), false, text, text );
496
497 // The exact pattern from the issue (parent field compared to the text's own value).
498 testEvalExpr( wxT( "A.Parent.getField('Reference') == A.Text" ), VAL( 1.0 ), false, text, text );
499
500 // The parent resolves to a footprint object that can be type-queried.
501 testEvalExpr( wxT( "A.Parent.Type == 'Footprint'" ), VAL( 1.0 ), false, text, text );
502
503 // Regression: the terminal "Parent" string still yields the parent footprint reference,
504 // i.e. the object and the string refer to the same parent.
505 testEvalExpr( wxT( "A.Parent == 'J1'" ), VAL( 1.0 ), false, text, text );
506
507 // Without navigation getField() has a non-footprint receiver and returns "", so this must
508 // NOT match - it guards against the navigation silently applying to the base item.
509 testEvalExpr( wxT( "A.getField('Reference') == 'J1'" ), VAL( 0.0 ), false, text, text );
510
511 // Error cases. These are code-generation errors (not parse errors), which the shared
512 // testEvalExpr does not observe through Compile()'s return value, so check the pending-error
513 // status directly. None of these expressions contain a bare number, so the only error that
514 // can be raised is the unrecognized item/property we are testing for.
515 // An unknown property on the parent, an unknown navigation step, and navigation on the
516 // layer pseudo-item (which has no parent).
517 expectCompileError( wxT( "A.Parent.bogusProperty" ) );
518 expectCompileError( wxT( "A.bogus.Reference == 'J1'" ) );
519 expectCompileError( wxT( "L.Parent.Type == 'Footprint'" ) );
520}
521
522
523BOOST_AUTO_TEST_CASE( ReceiverValidation )
524{
525 expectCompileSuccess( wxT( "L == 'F.Cu'" ) );
526 expectCompileSuccess( wxT( "AB.isCoupledDiffPair()" ) );
527 expectCompileSuccess( wxT( "A.Parent.Reference == 'J1'" ) );
528 expectCompileSuccess( wxT( "A.Width == 1mm" ) );
529
530 expectCompileError( wxT( "L.Width == 1mm" ) );
531 expectCompileError( wxT( "AB.Width == A.Width" ) );
532 expectCompileError( wxT( "AB.Parent.Reference == 'J1'" ) );
533}
534
535
536BOOST_AUTO_TEST_CASE( LayerReceiverLayerField )
537{
538 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
539 PCBEXPR_UCODE ucode;
541
542 compiler.Compile( wxT( "L.Layer == '*.Paste'" ), &ucode, &preflight );
543 BOOST_REQUIRE( !compiler.IsErrorPending() );
544
545 BOARD brd;
546 PCB_TRACK track( &brd );
549
550 paste.SetItems( &track, &track );
551 copper.SetItems( &track, &track );
552
553 BOOST_CHECK_EQUAL( ucode.Run( &paste )->AsDouble(), 1.0 );
554 BOOST_CHECK_EQUAL( ucode.Run( &copper )->AsDouble(), 0.0 );
555}
556
557
558BOOST_AUTO_TEST_CASE( DynamicCourtyardArgument )
559{
561
562 BOARD board;
563 FOOTPRINT* target = new FOOTPRINT( &board );
564 target->SetReference( wxS( "U1" ) );
565 KI_TEST::DrawRect( *target, { 0, 0 }, { pcbIUScale.mmToIU( 4 ), pcbIUScale.mmToIU( 4 ) },
566 0, pcbIUScale.mmToIU( 0.05 ), F_CrtYd );
567 board.Add( target );
568
569 FOOTPRINT* other = new FOOTPRINT( &board );
570 other->SetReference( wxS( "U2" ) );
571 board.Add( other );
572
573 PCB_SHAPE graphic( other, SHAPE_T::SEGMENT );
574 graphic.SetLayer( F_Fab );
575 graphic.SetStart( { 0, 0 } );
576 graphic.SetEnd( { pcbIUScale.mmToIU( 1 ), 0 } );
577 graphic.SetWidth( pcbIUScale.mmToIU( 0.05 ) );
578
579 PCB_TEXT targetChild( target );
580 PCB_TEXT otherChild( other );
581
582 for( const wxString& expression : {
583 wxString( "A.intersectsFrontCourtyard(B.Parent)" ),
584 wxString( "A.intersectsCourtyard(B.Parent.Reference)" ),
585 wxString( "A.intersectsFrontCourtyard(B.Parent.getField('Reference'))" ) } )
586 {
587 BOOST_TEST_CONTEXT( expression )
588 {
589 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
590 PCBEXPR_UCODE ucode;
593
594 BOOST_REQUIRE( compiler.Compile( expression, &ucode, &preflight ) );
595 BOOST_REQUIRE_MESSAGE( !compiler.IsErrorPending(), compiler.GetError().message );
596 BOOST_CHECK( ucode.RequiresPairItems() );
597
598 context.SetItems( &graphic, &targetChild );
599 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 1.0 );
600
601 context.SetItems( &graphic, &otherChild );
602 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 0.0 );
603
604 context.SetItems( &graphic, &targetChild );
605 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 1.0 );
606 }
607 }
608}
609
610
611BOOST_AUTO_TEST_CASE( FunctionArgumentValidation )
612{
613 expectCompileError( wxS( "A.intersectsFrontCourtyard()" ) );
614 expectCompileError( wxS( "A.intersectsFrontCourtyard('')" ) );
615 expectCompileError( wxS( "A.memberOfFootprint('')" ) );
616 expectCompileError( wxS( "A.fromTo('U1-1')" ) );
617 expectCompileError( wxS( "A.intersectsFrontCourtyard(B.UnknownProperty)" ) );
618 expectCompileError( wxS( "A.intersectsFrontCourtyard(B.Parent.unknownFunction())" ) );
619}
620
621
622BOOST_AUTO_TEST_CASE( SingleItemRulePreflight )
623{
625
626 for( const wxString& constraint : {
627 wxString( "assertion \"A.Type == 'Graphic'\"" ),
628 wxString( "hole_size (min 0.2mm)" ), wxString( "text_height (min 1mm)" ),
629 wxString( "text_thickness (min 0.1mm)" ),
630 wxString( "track_segment_length (min 1mm)" ), wxString( "annular_width (min 0.1mm)" ),
631 wxString( "solder_mask_expansion (opt 0mm)" ),
632 wxString( "solder_paste_abs_margin (opt 0mm)" ),
633 wxString( "solder_paste_rel_margin (opt 0mm)" ), wxString( "disallow track" ),
634 wxString( "via_diameter (min 0.5mm)" ), wxString( "length (max 100mm)" ),
635 wxString( "net_chain_length (max 100mm)" ), wxString( "stub_length (max 1mm)" ),
636 wxString( "return_path (layer 'B.Cu')" ), wxString( "skew (max 1mm)" ),
637 wxString( "via_count (max 2)" ),
638 wxString( "via_dangling (max 0)" ), wxString( "bridged_mask (min 0)" ),
639 wxString( "microvia_stack_depth (max 2)" ), wxString( "microvia_aspect_ratio (max 1)" ) } )
640 {
641 for( bool conditionFirst : { true, false } )
642 {
643 BOOST_TEST_CONTEXT( constraint << ", condition first: " << conditionFirst )
644 {
645 wxString condition = wxS( "(condition \"B.Type == 'Graphic'\")" );
646 wxString body = wxString::Format( wxS( "(constraint %s)" ), constraint );
647 wxString source = wxS( "(version 1)\n(rule test\n" )
648 + ( conditionFirst ? condition + wxS( "\n" ) + body
649 : body + wxS( "\n" ) + condition ) + wxS( "\n)" );
650 std::vector<std::shared_ptr<DRC_RULE>> rules;
652 wxString controlSource = source;
653 controlSource.Replace( wxS( "B.Type" ), wxS( "A.Type" ) );
654 DRC_RULES_PARSER controlParser( controlSource, wxS( "single item control" ) );
655 controlParser.Parse( rules, &reporter );
656 BOOST_REQUIRE_MESSAGE( !reporter.GetMessages().Contains( wxS( "ERROR:" ) ), reporter.GetMessages() );
657 reporter.Clear();
658
659 DRC_RULES_PARSER parser( source, wxS( "single item preflight" ) );
660 parser.Parse( rules, &reporter );
661 BOOST_CHECK_MESSAGE( reporter.GetMessages().Contains( wxS( "Item 'B'" ) ),
662 reporter.GetMessages() );
663
664 DRC_RULES_PARSER throwingParser( source, wxS( "single item preflight" ) );
665 BOOST_CHECK_EXCEPTION( throwingParser.Parse( rules, nullptr ), PARSE_ERROR,
666 [conditionFirst]( const PARSE_ERROR& error )
667 {
668 return error.What().Contains( wxS( "Item 'B' is not available" ) )
669 && error.lineNumber == ( conditionFirst ? 3 : 4 );
670 } );
671 }
672 }
673 }
674}
675
676
677BOOST_AUTO_TEST_CASE( PairConditionsInSingleItemRules )
678{
680
681 for( const wxString& body : {
682 wxString( "(condition \"A.intersectsFrontCourtyard(B.Parent)\")"
683 "(constraint assertion \"A.Type == 'Graphic'\")" ),
684 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint hole_size (min 0.2mm))" ),
685 wxString( "(constraint assertion \"B.Type == 'Graphic'\")" ),
686 wxString( "(constraint assertion \"A.intersectsFrontCourtyard(B.Parent.getField('Reference'))\")" ),
687 wxString( "(condition \"B.Type == 'Graphic'\")(constraint clearance (min 0.2mm))"
688 "(constraint hole_size (min 0.2mm))" ),
689 wxString( "(condition \"AB.isCoupledDiffPair() && B.Type == 'Track'\")"
690 "(constraint length (max 100mm))" ) } )
691 {
692 BOOST_TEST_CONTEXT( body )
693 {
694 std::vector<std::shared_ptr<DRC_RULE>> rules;
695 DRC_RULES_PARSER parser( wxS( "(version 1)(rule test " ) + body + wxS( ")" ), wxS( "preflight" ) );
696 BOOST_CHECK_EXCEPTION( parser.Parse( rules, nullptr ), PARSE_ERROR,
697 []( const PARSE_ERROR& error )
698 {
699 return error.What().Contains( wxS( "Item 'B' is not available" ) );
700 } );
701 }
702 }
703
704 for( const wxString& body : {
705 wxString( "(condition \"A.Reference == 'B.Width'\")(constraint hole_size (min 0.2mm))" ),
706 wxString( "(constraint assertion \"A.Type == 'Graphic'\")" ),
707 wxString( "(condition \"A.intersectsFrontCourtyard(B.Parent)\")"
708 "(constraint physical_clearance (min 100mm))" ),
709 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint diff_pair_gap (min 0.2mm))" ),
710 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint track_width (min 0.2mm))" ),
711 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint diff_pair_uncoupled (max 1mm))" ),
712 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint length (max 100mm))" ),
713 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint net_chain_length (max 100mm))" ),
714 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint skew (max 1mm))" ),
715 wxString( "(condition \"B.Type == 'Track'\")(constraint track_width (min 0.2mm))" ),
716 wxString( "(condition \"B.Type == 'Track'\")(constraint track_angle (min 45))" ),
717 wxString( "(condition \"B.Type == 'Zone'\")(constraint thermal_relief_gap (min 0.2mm))" ) } )
718 {
719 BOOST_TEST_CONTEXT( body )
720 {
721 std::vector<std::shared_ptr<DRC_RULE>> rules;
723 DRC_RULES_PARSER parser( wxS( "(version 1)(rule test " ) + body + wxS( ")" ), wxS( "preflight" ) );
724 parser.Parse( rules, &reporter );
725 BOOST_CHECK_MESSAGE( !reporter.GetMessages().Contains( wxS( "ERROR:" ) ), reporter.GetMessages() );
726 }
727 }
728}
729
730
731BOOST_AUTO_TEST_CASE( PairItemDependency )
732{
733 auto requiresPairItems =
734 []( const wxString& aExpr )
735 {
736 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
737 PCBEXPR_UCODE ucode;
739
740 BOOST_REQUIRE( compiler.Compile( aExpr, &ucode, &preflight ) );
741 return ucode.RequiresPairItems();
742 };
743
744 BOOST_CHECK( !requiresPairItems( wxT( "A.Width == 1mm" ) ) );
745 BOOST_CHECK( requiresPairItems( wxT( "B.Width == 1mm" ) ) );
746 BOOST_CHECK( requiresPairItems( wxT( "AB.isCoupledDiffPair()" ) ) );
747 BOOST_CHECK( !requiresPairItems( wxT( "A.Reference == 'B.Width'" ) ) );
748}
749
750
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
Construction utilities for PCB tests.
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
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...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1207
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
void IncrementTimeStamp()
Definition board.cpp:446
void SetCopperLayerCount(int aCount)
Definition board.cpp:1137
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1299
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
void SetLocalSolderPasteMarginRatio(std::optional< double > aRatio)
Definition footprint.h:529
void SetReference(const wxString &aReference)
Definition footprint.h:907
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
wxString ToCSSString() const
Definition color4d.cpp:146
bool IsErrorPending() const
const ERROR_STATUS & GetError() const
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
VALUE * Run(CONTEXT *ctx)
virtual const wxString & AsString() const
virtual double AsDouble() const
VAR_TYPE_T GetType() const
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:43
Handle the data for a net.
Definition netinfo.h:50
void SetNetChain(const wxString &aNetChain)
Definition netinfo.h:123
void SetNetClass(const std::shared_ptr< NETCLASS > &aNetClass)
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:231
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
void SetItems(BOARD_ITEM *a, BOARD_ITEM *b=nullptr)
bool RequiresPairItems() const
static PCBEXPR_PROPERTY_KIND ClassifyProperty(const PROPERTY_BASE *aProperty)
static LIBEVAL::VAR_TYPE_T ExpressionType(PCBEXPR_PROPERTY_KIND aKind)
void SetWidth(int aWidth) override
void SetEnd(const VECTOR2I &aEnd) override
void SetEllipseRotation(const EDA_ANGLE &aA) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
void SetDrill(int aDrill)
Definition pcb_track.h:771
void SetPosition(const VECTOR2I &aPoint) override
Definition pcb_track.h:581
void SetLayerPair(PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer)
For a via m_layer contains the top layer, the other layer is in m_bottomLayer/.
void SetViaType(VIATYPE aViaType)
Definition pcb_track.h:411
void SetWidth(int aWidth) override
virtual size_t TypeHash() const =0
Return type-id of the property type.
const wxString & Name() const
Definition property.h:221
Provide class metadata.Helper macro to map type hashes to names.
CLASSES_INFO GetAllClasses()
static PROPERTY_MANAGER & Instance()
void Rebuild()
Rebuild the list of all registered properties.
bool IsOfType(TYPE_ID aDerived, TYPE_ID aBase) const
Return true if aDerived is inherited from aBase.
A wrapper for reporting to a wxString object.
Definition reporter.h:242
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetHatchOrientation(const EDA_ANGLE &aStep)
Definition zone.h:332
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:641
void SetIsRuleArea(bool aEnable)
Definition zone.h:808
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
void SetMinIslandArea(long long int aArea)
Definition zone.h:833
@ NULL_CONSTRAINT
Definition drc_rule.h:50
@ DEGREES_T
Definition eda_angle.h:31
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ F_Paste
Definition layer_ids.h:100
@ B_Cu
Definition layer_ids.h:61
@ In2_Cu
Definition layer_ids.h:63
@ F_Fab
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In1_Cu
Definition layer_ids.h:62
@ F_Cu
Definition layer_ids.h:60
void DrawRect(FOOTPRINT &aFootprint, const VECTOR2I &aPos, const VECTOR2I &aSize, int aRadius, int aWidth, PCB_LAYER_ID aLayer)
Draw a rectangle on a footprint.
PCBEXPR_PROPERTY_KIND
#define TYPE_HASH(x)
Definition property.h:74
friend std::ostream & operator<<(std::ostream &os, const EXPR_TO_TEST &expr)
LIBEVAL::VALUE expectedResult
A filename or source description, a problem input line, a line number, a byte offset,...
BOOST_DATA_TEST_CASE(ConvertToKicadUnit, boost::unit_test::data::make(altium_to_kicad_unit), input_value, expected_result)
Test conversation from Altium internal units into KiCad internal units.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
static void expectCompileError(const wxString &aExpr)
static void expectCompileSuccess(const wxString &aExpr)
static const std::vector< EXPR_TO_TEST > simpleExpressions
static bool testEvalExpr(const wxString &expr, const LIBEVAL::VALUE &expectedResult, bool expectError=false, BOARD_ITEM *itemA=nullptr, BOARD_ITEM *itemB=nullptr)
LIBEVAL::VALUE VAL
static const std::vector< EXPR_TO_TEST > introspectionExpressions
BOOST_AUTO_TEST_CASE(IntrospectedProperties)
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683