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>
46
47BOOST_AUTO_TEST_SUITE( Libeval_Compiler )
48
50{
51 wxString expression;
54
55 friend std::ostream& operator<<( std::ostream& os, const EXPR_TO_TEST& expr )
56 {
57 os << expr.expression;
58 return os;
59 }
60};
61
63
64const static std::vector<EXPR_TO_TEST> simpleExpressions = {
65 { "10mm + 20 mm", false, VAL( 30e6 ) },
66 { "3*(7+8)", false, VAL( 3 * ( 7 + 8 ) ) },
67 { "3*7+8", false, VAL( 3 * 7 + 8 ) },
68 { "(3*7)+8", false, VAL( 3 * 7 + 8 ) },
69 { "10mm + 20)", true, VAL( 0 ) },
70
71 { "1", false, VAL(1) },
72 { "1.5", false, VAL(1.5) },
73 { "1,5", false, VAL(1.5) },
74 { "1mm", false, VAL(1e6) },
75 // Any White-space is OK
76 { " 1 + 2 ", false, VAL(3) },
77 // Decimals are OK in expressions
78 { "1.5 + 0.2 + 0.1", false, VAL(1.8) },
79 // Negatives are OK
80 { "3 - 10", false, VAL(-7) },
81 // Lots of operands
82 { "1 + 2 + 10 + 1000.05", false, VAL(1013.05) },
83 // Operator precedence
84 { "1 + 2 - 4 * 20 / 2", false, VAL(-37) },
85 // Parens
86 { "(1)", false, VAL(1) },
87 // Parens affect precedence
88 { "-(1 + (2 - 4)) * 20.8 / 2", false, VAL(10.4) },
89 // Unary addition is a sign, not a leading operator
90 { "+2 - 1", false, VAL(1) },
91 // A short-circuited || must yield a normalized 1, not the raw (nonzero) left operand, so a
92 // boolean feeding a further operator behaves the same as the non-short-circuited path.
93 { "(2 || 0) == 1", false, VAL(1) },
94 { "(7 || 0) + 5", false, VAL(6) }
95};
96
97
98const static std::vector<EXPR_TO_TEST> introspectionExpressions = {
99 { "A.type == 'Pad' && B.type == 'Pad' && (A.existsOnLayer('F.Cu'))", false, VAL( 0.0 ) },
100 { "A.Width > B.Width", false, VAL( 0.0 ) },
101 { "A.Width + B.Width", false, VAL( pcbIUScale.MilsToIU(10) + pcbIUScale.MilsToIU(20) ) },
102 { "A.Netclass", false, VAL( "HV_LINE" ) },
103 { "A.Net_Class == 'HV_LINE'", false, VAL( 1.0 ) },
104 { "(A.Netclass == 'HV_LINE') && (B.netclass == 'otherClass') && (B.netclass != 'F.Cu')", false, VAL( 1.0 ) },
105 { "A.Netclass + 1.0", false, VAL( 1.0 ) },
106 { "A.hasNetclass('HV_LINE')", false, VAL( 1.0 ) },
107 { "A.hasNetclass('HV_*')", false, VAL( 1.0 ) },
108 { "A.existsOnLayer(B.Layer)", false, VAL( 1.0 ) },
109 { "A.type == 'Track' && B.type == 'Track' && A.layer == 'F.Cu'", false, VAL( 1.0 ) },
110 { "(A.type == 'Track') && (B.type == 'Track') && (A.layer == 'F.Cu')", false, VAL( 1.0 ) },
111 { "A.type == 'Via' && A.isMicroVia()", false, VAL(0.0) }
112};
113
114
115static bool testEvalExpr( const wxString& expr, const LIBEVAL::VALUE& expectedResult, bool expectError = false,
116 BOARD_ITEM* itemA = nullptr, BOARD_ITEM* itemB = nullptr )
117{
118 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
119 PCBEXPR_UCODE ucode;
122 bool ok = true;
123
124 context.SetItems( itemA, itemB );
125
126
127 BOOST_TEST_MESSAGE( "Expr: '" << expr.c_str() << "'" );
128
129 bool error = !compiler.Compile( expr, &ucode, &preflightContext );
130
131 BOOST_CHECK_EQUAL( error, expectError );
132
133 if( error != expectError )
134 {
135 BOOST_TEST_MESSAGE( "Result: FAIL: " << compiler.GetError().message.c_str() <<
136 " (code pos: " << compiler.GetError().srcPos << ")" );
137
138 return false;
139 }
140
141 if( error )
142 return true;
143
145
146 if( ok )
147 {
148 result = ucode.Run( &context );
149 ok = ( result->EqualTo( &context, &expectedResult ) );
150 }
151
152 if( expectedResult.GetType() == LIBEVAL::VT_NUMERIC )
153 {
154 BOOST_CHECK_EQUAL( result->AsDouble(), expectedResult.AsDouble() );
155 }
156 else
157 {
158 BOOST_CHECK_EQUAL( result->AsString(), expectedResult.AsString() );
159 }
160
161 return ok;
162}
163
164
165static void expectCompileError( const wxString& aExpr )
166{
167 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
168 PCBEXPR_UCODE ucode;
170
171 compiler.Compile( aExpr, &ucode, &preflight );
172
173 BOOST_CHECK_MESSAGE( compiler.IsErrorPending(), "Expected a compile error for: " << aExpr.mb_str() );
174}
175
176
177static void expectCompileSuccess( const wxString& aExpr )
178{
179 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
180 PCBEXPR_UCODE ucode;
182
183 compiler.Compile( aExpr, &ucode, &preflight );
184
185 BOOST_CHECK_MESSAGE( !compiler.IsErrorPending(), "Expected expression to compile: " << aExpr.mb_str() );
186}
187
188
189BOOST_DATA_TEST_CASE( SimpleExpressions, boost::unit_test::data::make( simpleExpressions ), expr )
190{
191 testEvalExpr( expr.expression, expr.expectedResult, expr.expectError );
192}
193
194
195BOOST_AUTO_TEST_CASE( IntrospectedProperties )
196{
198 propMgr.Rebuild();
199
200 BOARD brd;
201
202 const NETINFO_LIST& netInfo = brd.GetNetInfo();
203
204 std::shared_ptr<NETCLASS> netclass1( new NETCLASS( "HV_LINE" ) );
205 std::shared_ptr<NETCLASS> netclass2( new NETCLASS( "otherClass" ) );
206
207 NETINFO_ITEM* net1info = new NETINFO_ITEM( &brd, "net1", 1 );
208 NETINFO_ITEM* net2info = new NETINFO_ITEM( &brd, "net2", 2 );
209
210 net1info->SetNetClass( netclass1 );
211 net2info->SetNetClass( netclass2 );
212
213 PCB_TRACK trackA( &brd );
214 PCB_TRACK trackB( &brd );
215
216 trackA.SetNet( net1info );
217 trackB.SetNet( net2info );
218
219 trackB.SetLayer( F_Cu );
220
221 trackA.SetWidth( pcbIUScale.MilsToIU( 10 ) );
222 trackB.SetWidth( pcbIUScale.MilsToIU( 20 ) );
223
224 for( const auto& expr : introspectionExpressions )
225 {
226 testEvalExpr( expr.expression, expr.expectedResult, expr.expectError, &trackA, &trackB );
227 }
228}
229
230
231BOOST_AUTO_TEST_CASE( IntrospectedExtendedNumericProperties )
232{
234 propMgr.Rebuild();
235
236 BOARD brd;
237 ZONE zone( &brd );
238
239 zone.SetLayer( F_Cu );
240 zone.SetAssignedPriority( 7 );
241 zone.SetHatchOrientation( EDA_ANGLE( 45.0, DEGREES_T ) );
242 zone.SetMinIslandArea( 123456789LL );
243
244 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 1.0 ), false, &zone );
245 testEvalExpr( wxT( "A.Hatch_Orientation == 45deg" ), VAL( 1.0 ), false, &zone );
246 testEvalExpr( wxT( "A.Minimum_Island_Area == 123456789" ), VAL( 1.0 ), false, &zone );
247
248 PCB_SHAPE ellipse( &brd, SHAPE_T::ELLIPSE );
249 ellipse.SetEllipseRotation( EDA_ANGLE( 30.0, DEGREES_T ) );
250
251 testEvalExpr( wxT( "A.Ellipse_Rotation == 30deg" ), VAL( 1.0 ), false, &ellipse );
252
253 FOOTPRINT footprint( &brd );
254 footprint.SetLocalSolderPasteMarginRatio( 0.125 );
255
256 testEvalExpr( wxT( "A.Solderpaste_Margin_Ratio_Override == 0.125" ), VAL( 1.0 ), false, &footprint );
257
258 footprint.SetLocalSolderPasteMarginRatio( std::nullopt );
259
260 testEvalExpr( wxT( "A.Solderpaste_Margin_Ratio_Override == null" ), VAL( 1.0 ), false, &footprint );
261}
262
263
264BOOST_AUTO_TEST_CASE( RenamedProperties )
265{
267 propMgr.Rebuild();
268
269 BOARD brd;
270 PCB_TRACK track( &brd );
271
272 track.SetStart( VECTOR2I( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 2.0 ) ) );
273 track.SetEnd( VECTOR2I( pcbIUScale.mmToIU( 3.0 ), pcbIUScale.mmToIU( 4.0 ) ) );
274
275 expectCompileSuccess( wxT( "A.Origin_X == 1mm" ) );
276 expectCompileSuccess( wxT( "A.Origin_Y == 2mm" ) );
277
278 testEvalExpr( wxT( "A.Origin_X == 1mm" ), VAL( 1.0 ), false, &track );
279 testEvalExpr( wxT( "A.Origin_Y == 2mm" ), VAL( 1.0 ), false, &track );
280 testEvalExpr( wxT( "A.Origin_X != A.End_X" ), VAL( 1.0 ), false, &track );
281 testEvalExpr( wxT( "A.origin_y != A.End_Y" ), VAL( 1.0 ), false, &track );
282
283 testEvalExpr( wxT( "A.Start_X == 1mm" ), VAL( 1.0 ), false, &track );
284 testEvalExpr( wxT( "A.Start_Y == 2mm" ), VAL( 1.0 ), false, &track );
285}
286
287
288BOOST_AUTO_TEST_CASE( IntrospectedColorProperties )
289{
291 propMgr.Rebuild();
292
293 BOARD brd;
294 PCB_TABLE table( &brd, 0 );
295
296 auto checkColor =
297 [&]( const COLOR4D& aColor, const wxString& aExpectedCss )
298 {
299 table.SetBorderColor( aColor );
300 BOOST_CHECK_EQUAL( aColor.ToCSSString(), aExpectedCss );
301
302 wxString expression = wxT( "A.Border_Color == '" ) + aExpectedCss + wxT( "'" );
303 testEvalExpr( expression, VAL( 1.0 ), false, &table );
304 };
305
306 checkColor( COLOR4D( 0.25, 0.5, 0.75, 0.5 ), wxT( "rgba(64, 128, 191, 0.502)" ) );
307#ifndef __WXMAC__ // WXMAC doesn't have colour names initialized when run headless
308 checkColor( COLOR4D( wxString( wxT( "red" ) ) ), wxT( "rgb(255, 0, 0)" ) );
309#endif
310}
311
312
313BOOST_AUTO_TEST_CASE( ExpressionPropertyTypesAreSupported )
314{
316 propMgr.Rebuild();
317
318 std::set<std::pair<TYPE_ID, PROPERTY_BASE*>> seen;
319 std::map<wxString, std::set<LIBEVAL::VAR_TYPE_T>> fieldTypes;
320
321 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
322 {
323 if( !propMgr.IsOfType( cls.type, TYPE_HASH( BOARD_ITEM ) ) )
324 continue;
325
326 for( PROPERTY_BASE* prop : cls.properties )
327 {
328 if( !seen.emplace( cls.type, prop ).second )
329 continue;
330
332
333 BOOST_CHECK_MESSAGE( kind != PCBEXPR_PROPERTY_KIND::UNSUPPORTED,
334 "Unsupported expression property: class=" << cls.name.mb_str()
335 << ", property=" << prop->Name().mb_str()
336 << ", type_hash=" << prop->TypeHash() );
337
339 fieldTypes[prop->Name()].insert( PCBEXPR_VAR_REF::ExpressionType( kind ) );
340 }
341 }
342
343 // PCB_TARGET::Shape is numeric while EDA_SHAPE::Shape is an enum, so a global
344 // expression reference cannot have one stable public type.
345 auto shapeTypes = fieldTypes.find( wxT( "Shape" ) );
346 BOOST_REQUIRE( shapeTypes != fieldTypes.end() );
347 BOOST_CHECK_EQUAL( shapeTypes->second.size(), 2u );
348 fieldTypes.erase( shapeTypes );
349
350 for( const auto& [field, types] : fieldTypes )
351 {
352 BOOST_CHECK_MESSAGE( types.size() == 1, "Incompatible expression types for property: " << field.mb_str() );
353 }
354
355 expectCompileError( wxT( "A.Shape" ) );
356}
357
358
359BOOST_AUTO_TEST_CASE( IntrospectedPropertyAvailability )
360{
362 propMgr.Rebuild();
363
364 BOARD brd;
365 ZONE zone( &brd );
366
367 zone.SetLayer( F_Cu );
368 zone.SetAssignedPriority( 7 );
369 zone.SetIsRuleArea( true );
370
371 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 0.0 ), false, &zone );
372
373 zone.SetIsRuleArea( false );
374
375 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 1.0 ), false, &zone );
376}
377
378
379BOOST_AUTO_TEST_CASE( InNetChainClassWildcard )
380{
382 propMgr.Rebuild();
383
384 BOARD brd;
385
386 std::shared_ptr<NET_SETTINGS> netSettings = brd.GetDesignSettings().m_NetSettings;
387 netSettings->SetNetChainClass( wxT( "ChainHS" ), wxT( "HighSpeed" ) );
388
389 NETINFO_ITEM* netUnclassified = new NETINFO_ITEM( &brd, "netA", 1 );
390 NETINFO_ITEM* netClassified = new NETINFO_ITEM( &brd, "netB", 2 );
391 NETINFO_ITEM* netNoChain = new NETINFO_ITEM( &brd, "netC", 3 );
392
393 netUnclassified->SetNetChain( wxT( "ChainOrphan" ) );
394 netClassified->SetNetChain( wxT( "ChainHS" ) );
395
396 PCB_TRACK trackUnclassified( &brd );
397 PCB_TRACK trackClassified( &brd );
398 PCB_TRACK trackNoChain( &brd );
399
400 trackUnclassified.SetNet( netUnclassified );
401 trackClassified.SetNet( netClassified );
402 trackNoChain.SetNet( netNoChain );
403
404 // A chain with no class assignment must not match any inNetChainClass() pattern,
405 // including the '*' wildcard.
406 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackUnclassified, &trackUnclassified );
407 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 0.0 ), false, &trackUnclassified, &trackUnclassified );
408
409 // Net with no chain at all must not match either.
410 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackNoChain, &trackNoChain );
411
412 // Properly classified chain must match both wildcard and exact patterns.
413 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
414 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
415 testEvalExpr( wxT( "A.inNetChainClass('High*')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
416 testEvalExpr( wxT( "A.inNetChainClass('LowSpeed')" ), VAL( 0.0 ), false, &trackClassified, &trackClassified );
417}
418
419BOOST_AUTO_TEST_CASE( StackedMicroviaExpression )
420{
422 propMgr.Rebuild();
423
424 BOARD brd;
425 brd.SetCopperLayerCount( 4 );
426
427 auto microvia = []( BOARD* aBoard, const VECTOR2I& aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom )
428 {
429 PCB_VIA* via = new PCB_VIA( aBoard );
430
431 via->SetViaType( VIATYPE::MICROVIA );
432 via->SetPosition( aPos );
433 via->SetLayerPair( aTop, aBottom );
434 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.25 ) );
435 via->SetDrill( pcbIUScale.mmToIU( 0.1 ) );
436 aBoard->Add( via );
437
438 return via;
439 };
440
441 VECTOR2I origin( 0, 0 );
442 VECTOR2I away( pcbIUScale.mmToIU( 10 ), 0 );
443
444 PCB_VIA* upperHop = microvia( &brd, origin, F_Cu, In1_Cu );
445 PCB_VIA* lowerHop = microvia( &brd, origin, In1_Cu, In2_Cu );
446 PCB_VIA* lone = microvia( &brd, away, F_Cu, In1_Cu );
447
448 PCB_VIA* through = new PCB_VIA( &brd );
449
450 through->SetViaType( VIATYPE::THROUGH );
451 through->SetPosition( origin );
452 through->SetLayerPair( F_Cu, B_Cu );
453 through->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.6 ) );
454 through->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
455 brd.Add( through );
456
457 // Both hops of a stack are in it, not just the one on top.
458 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, upperHop, upperHop );
459 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, lowerHop, lowerHop );
460
461 // A microvia landing on nothing is not a stack.
462 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 0.0 ), false, lone, lone );
463
464 // A through via sharing the position of a stack is not part of it.
465 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 0.0 ), false, through, through );
466
467 // The predicate composes with the rest of the language.
468 testEvalExpr( wxT( "A.isMicroVia() && !A.isStackedVia()" ), VAL( 1.0 ), false, lone, lone );
469
470 // The relation is cached for the whole board, so an edit has to drop it.
471 PCB_VIA* landing = microvia( &brd, away, In1_Cu, In2_Cu );
472 brd.IncrementTimeStamp();
473
474 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, lone, lone );
475 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, landing, landing );
476}
477
478BOOST_AUTO_TEST_CASE( ParentNavigation )
479{
481 propMgr.Rebuild();
482
483 BOARD brd;
484
485 FOOTPRINT fp( &brd );
486 fp.SetReference( wxT( "J1" ) );
487
488 // A text item living inside the footprint. Its direct parent is the footprint, so
489 // "A.Parent" navigates to J1.
490 PCB_TEXT* text = new PCB_TEXT( &fp );
491 text->SetText( wxT( "J1" ) );
492 fp.Add( text );
493
494 // The headline capability: getField() only returns a value when its receiver is a
495 // footprint, so this passes solely because navigation steps from the text to its parent.
496 testEvalExpr( wxT( "A.Parent.getField('Reference') == 'J1'" ), VAL( 1.0 ), false, text, text );
497
498 // The exact pattern from the issue (parent field compared to the text's own value).
499 testEvalExpr( wxT( "A.Parent.getField('Reference') == A.Text" ), VAL( 1.0 ), false, text, text );
500
501 // The parent resolves to a footprint object that can be type-queried.
502 testEvalExpr( wxT( "A.Parent.Type == 'Footprint'" ), VAL( 1.0 ), false, text, text );
503
504 // Regression: the terminal "Parent" string still yields the parent footprint reference,
505 // i.e. the object and the string refer to the same parent.
506 testEvalExpr( wxT( "A.Parent == 'J1'" ), VAL( 1.0 ), false, text, text );
507
508 // Without navigation getField() has a non-footprint receiver and returns "", so this must
509 // NOT match - it guards against the navigation silently applying to the base item.
510 testEvalExpr( wxT( "A.getField('Reference') == 'J1'" ), VAL( 0.0 ), false, text, text );
511
512 // Error cases. These are code-generation errors (not parse errors), which the shared
513 // testEvalExpr does not observe through Compile()'s return value, so check the pending-error
514 // status directly. None of these expressions contain a bare number, so the only error that
515 // can be raised is the unrecognized item/property we are testing for.
516 // An unknown property on the parent, an unknown navigation step, and navigation on the
517 // layer pseudo-item (which has no parent).
518 expectCompileError( wxT( "A.Parent.bogusProperty" ) );
519 expectCompileError( wxT( "A.bogus.Reference == 'J1'" ) );
520 expectCompileError( wxT( "L.Parent.Type == 'Footprint'" ) );
521}
522
523
524// A property missing on a child item resolves against its parent footprint.
525BOOST_AUTO_TEST_CASE( PropertyParentFallback )
526{
528
529 BOARD brd;
530
531 FOOTPRINT fp( &brd );
532 fp.SetReference( wxT( "J1" ) );
533
534 PCB_TEXT* text = new PCB_TEXT( &fp );
535 fp.Add( text );
536
537 testEvalExpr( wxT( "A.Reference == 'J1'" ), VAL( 1.0 ), false, text, text );
538 testEvalExpr( wxT( "A.Reference == 'J2'" ), VAL( 0.0 ), false, text, text );
539}
540
541
542// A bool property must be readable in a rule.
543BOOST_AUTO_TEST_CASE( BoolPropertyEvaluation )
544{
546
547 BOARD brd;
548
549 FOOTPRINT fp( &brd );
550
551 testEvalExpr( wxT( "A.Do_not_Populate == 0" ), VAL( 1.0 ), false, &fp, &fp );
552
553 fp.SetDNP( true );
554
555 testEvalExpr( wxT( "A.Do_not_Populate == 1" ), VAL( 1.0 ), false, &fp, &fp );
556}
557
558
559// Queries read flags and fields through the current assembly variant.
560BOOST_AUTO_TEST_CASE( VariantAwareEvaluation )
561{
563
564 BOARD brd;
565
566 FOOTPRINT fp( &brd );
567 fp.SetValue( wxT( "BaseVal" ) );
568
569 FOOTPRINT_VARIANT* variant = fp.AddVariant( wxT( "V1" ) );
570 variant->SetDNP( true );
571 variant->SetFieldValue( wxT( "Value" ), wxT( "VariantVal" ) );
572
573 brd.AddVariant( wxT( "V1" ) );
574 brd.SetCurrentVariant( wxT( "V1" ) );
575
576 testEvalExpr( wxT( "A.Do_not_Populate == 1" ), VAL( 1.0 ), false, &fp, &fp );
577 testEvalExpr( wxT( "A.getField('Value') == 'VariantVal'" ), VAL( 1.0 ), false, &fp, &fp );
578
579 // Without a current variant the base values apply.
580 brd.SetCurrentVariant( wxEmptyString );
581
582 testEvalExpr( wxT( "A.Do_not_Populate == 0" ), VAL( 1.0 ), false, &fp, &fp );
583 testEvalExpr( wxT( "A.getField('Value') == 'BaseVal'" ), VAL( 1.0 ), false, &fp, &fp );
584}
585
586
587// Reference resolves as a property with the fallback above, so the query normalizer
588// must leave it alone. getField() has no such fallback.
589BOOST_AUTO_TEST_CASE( FieldAliasNormalization )
590{
591 std::vector<PROPERTY_ROW_DATA> rows;
592
593 BOOST_CHECK_EQUAL( normalizeQueryFieldAliases( wxS( "A.Reference == 'R1'" ), rows ), wxS( "A.Reference == 'R1'" ) );
594
595 // A field with no property behind it needs the rewrite to resolve at all.
596 BOOST_CHECK_EQUAL( normalizeQueryFieldAliases( wxS( "A.Value == 'X'" ), rows ),
597 wxS( "A.getField('Value') == 'X'" ) );
598
599 PROPERTY_ROW_DATA customField;
600 customField.propertyName = wxS( "My Field" );
601 customField.property = nullptr;
603 customField.isMixed = false;
604
605 BOOST_CHECK_EQUAL( normalizeQueryFieldAliases( wxS( "A.My_Field == 'X'" ), { customField } ),
606 wxS( "A.getField('My Field') == 'X'" ) );
607}
608
609
610BOOST_AUTO_TEST_CASE( ReceiverValidation )
611{
612 expectCompileSuccess( wxT( "L == 'F.Cu'" ) );
613 expectCompileSuccess( wxT( "AB.isCoupledDiffPair()" ) );
614 expectCompileSuccess( wxT( "A.Parent.Reference == 'J1'" ) );
615 expectCompileSuccess( wxT( "A.Width == 1mm" ) );
616
617 expectCompileError( wxT( "L.Width == 1mm" ) );
618 expectCompileError( wxT( "AB.Width == A.Width" ) );
619 expectCompileError( wxT( "AB.Parent.Reference == 'J1'" ) );
620}
621
622
623BOOST_AUTO_TEST_CASE( LayerReceiverLayerField )
624{
625 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
626 PCBEXPR_UCODE ucode;
628
629 compiler.Compile( wxT( "L.Layer == '*.Paste'" ), &ucode, &preflight );
630 BOOST_REQUIRE( !compiler.IsErrorPending() );
631
632 BOARD brd;
633 PCB_TRACK track( &brd );
636
637 paste.SetItems( &track, &track );
638 copper.SetItems( &track, &track );
639
640 BOOST_CHECK_EQUAL( ucode.Run( &paste )->AsDouble(), 1.0 );
641 BOOST_CHECK_EQUAL( ucode.Run( &copper )->AsDouble(), 0.0 );
642}
643
644
645BOOST_AUTO_TEST_CASE( DynamicCourtyardArgument )
646{
648
649 BOARD board;
650 FOOTPRINT* target = new FOOTPRINT( &board );
651 target->SetReference( wxS( "U1" ) );
652 KI_TEST::DrawRect( *target, { 0, 0 }, { pcbIUScale.mmToIU( 4 ), pcbIUScale.mmToIU( 4 ) },
653 0, pcbIUScale.mmToIU( 0.05 ), F_CrtYd );
654 board.Add( target );
655
656 FOOTPRINT* other = new FOOTPRINT( &board );
657 other->SetReference( wxS( "U2" ) );
658 board.Add( other );
659
660 PCB_SHAPE graphic( other, SHAPE_T::SEGMENT );
661 graphic.SetLayer( F_Fab );
662 graphic.SetStart( { 0, 0 } );
663 graphic.SetEnd( { pcbIUScale.mmToIU( 1 ), 0 } );
664 graphic.SetWidth( pcbIUScale.mmToIU( 0.05 ) );
665
666 PCB_TEXT targetChild( target );
667 PCB_TEXT otherChild( other );
668
669 for( const wxString& expression : {
670 wxString( "A.intersectsFrontCourtyard(B.Parent)" ),
671 wxString( "A.intersectsCourtyard(B.Parent.Reference)" ),
672 wxString( "A.intersectsFrontCourtyard(B.Parent.getField('Reference'))" ) } )
673 {
674 BOOST_TEST_CONTEXT( expression )
675 {
676 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
677 PCBEXPR_UCODE ucode;
680
681 BOOST_REQUIRE( compiler.Compile( expression, &ucode, &preflight ) );
682 BOOST_REQUIRE_MESSAGE( !compiler.IsErrorPending(), compiler.GetError().message );
683 BOOST_CHECK( ucode.RequiresPairItems() );
684
685 context.SetItems( &graphic, &targetChild );
686 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 1.0 );
687
688 context.SetItems( &graphic, &otherChild );
689 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 0.0 );
690
691 context.SetItems( &graphic, &targetChild );
692 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 1.0 );
693 }
694 }
695}
696
697
698BOOST_AUTO_TEST_CASE( FunctionArgumentValidation )
699{
700 expectCompileError( wxS( "A.intersectsFrontCourtyard()" ) );
701 expectCompileError( wxS( "A.intersectsFrontCourtyard('')" ) );
702 expectCompileError( wxS( "A.memberOfFootprint('')" ) );
703 expectCompileError( wxS( "A.fromTo('U1-1')" ) );
704 expectCompileError( wxS( "A.intersectsFrontCourtyard(B.UnknownProperty)" ) );
705 expectCompileError( wxS( "A.intersectsFrontCourtyard(B.Parent.unknownFunction())" ) );
706}
707
708
709BOOST_AUTO_TEST_CASE( SingleItemRulePreflight )
710{
712
713 for( const wxString& constraint : {
714 wxString( "assertion \"A.Type == 'Graphic'\"" ),
715 wxString( "hole_size (min 0.2mm)" ), wxString( "text_height (min 1mm)" ),
716 wxString( "text_thickness (min 0.1mm)" ),
717 wxString( "track_segment_length (min 1mm)" ), wxString( "annular_width (min 0.1mm)" ),
718 wxString( "solder_mask_expansion (opt 0mm)" ),
719 wxString( "solder_paste_abs_margin (opt 0mm)" ),
720 wxString( "solder_paste_rel_margin (opt 0mm)" ), wxString( "disallow track" ),
721 wxString( "via_diameter (min 0.5mm)" ), wxString( "length (max 100mm)" ),
722 wxString( "net_chain_length (max 100mm)" ), wxString( "stub_length (max 1mm)" ),
723 wxString( "return_path (layer 'B.Cu')" ), wxString( "skew (max 1mm)" ),
724 wxString( "via_count (max 2)" ),
725 wxString( "via_dangling (max 0)" ), wxString( "bridged_mask (min 0)" ),
726 wxString( "microvia_stack_depth (max 2)" ), wxString( "microvia_aspect_ratio (max 1)" ) } )
727 {
728 for( bool conditionFirst : { true, false } )
729 {
730 BOOST_TEST_CONTEXT( constraint << ", condition first: " << conditionFirst )
731 {
732 wxString condition = wxS( "(condition \"B.Type == 'Graphic'\")" );
733 wxString body = wxString::Format( wxS( "(constraint %s)" ), constraint );
734 wxString source = wxS( "(version 1)\n(rule test\n" )
735 + ( conditionFirst ? condition + wxS( "\n" ) + body
736 : body + wxS( "\n" ) + condition ) + wxS( "\n)" );
737 std::vector<std::shared_ptr<DRC_RULE>> rules;
739 wxString controlSource = source;
740 controlSource.Replace( wxS( "B.Type" ), wxS( "A.Type" ) );
741 DRC_RULES_PARSER controlParser( controlSource, wxS( "single item control" ) );
742 controlParser.Parse( rules, &reporter );
743 BOOST_REQUIRE_MESSAGE( !reporter.GetMessages().Contains( wxS( "ERROR:" ) ), reporter.GetMessages() );
744 reporter.Clear();
745
746 DRC_RULES_PARSER parser( source, wxS( "single item preflight" ) );
747 parser.Parse( rules, &reporter );
748 BOOST_CHECK_MESSAGE( reporter.GetMessages().Contains( wxS( "Item 'B'" ) ),
749 reporter.GetMessages() );
750
751 DRC_RULES_PARSER throwingParser( source, wxS( "single item preflight" ) );
752 BOOST_CHECK_EXCEPTION( throwingParser.Parse( rules, nullptr ), PARSE_ERROR,
753 [conditionFirst]( const PARSE_ERROR& error )
754 {
755 return error.What().Contains( wxS( "Item 'B' is not available" ) )
756 && error.lineNumber == ( conditionFirst ? 3 : 4 );
757 } );
758 }
759 }
760 }
761}
762
763
764BOOST_AUTO_TEST_CASE( PairConditionsInSingleItemRules )
765{
767
768 for( const wxString& body : {
769 wxString( "(condition \"A.intersectsFrontCourtyard(B.Parent)\")"
770 "(constraint assertion \"A.Type == 'Graphic'\")" ),
771 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint hole_size (min 0.2mm))" ),
772 wxString( "(constraint assertion \"B.Type == 'Graphic'\")" ),
773 wxString( "(constraint assertion \"A.intersectsFrontCourtyard(B.Parent.getField('Reference'))\")" ),
774 wxString( "(condition \"B.Type == 'Graphic'\")(constraint clearance (min 0.2mm))"
775 "(constraint hole_size (min 0.2mm))" ),
776 wxString( "(condition \"AB.isCoupledDiffPair() && B.Type == 'Track'\")"
777 "(constraint length (max 100mm))" ) } )
778 {
779 BOOST_TEST_CONTEXT( body )
780 {
781 std::vector<std::shared_ptr<DRC_RULE>> rules;
782 DRC_RULES_PARSER parser( wxS( "(version 1)(rule test " ) + body + wxS( ")" ), wxS( "preflight" ) );
783 BOOST_CHECK_EXCEPTION( parser.Parse( rules, nullptr ), PARSE_ERROR,
784 []( const PARSE_ERROR& error )
785 {
786 return error.What().Contains( wxS( "Item 'B' is not available" ) );
787 } );
788 }
789 }
790
791 for( const wxString& body : {
792 wxString( "(condition \"A.Reference == 'B.Width'\")(constraint hole_size (min 0.2mm))" ),
793 wxString( "(constraint assertion \"A.Type == 'Graphic'\")" ),
794 wxString( "(condition \"A.intersectsFrontCourtyard(B.Parent)\")"
795 "(constraint physical_clearance (min 100mm))" ),
796 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint diff_pair_gap (min 0.2mm))" ),
797 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint track_width (min 0.2mm))" ),
798 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint diff_pair_uncoupled (max 1mm))" ),
799 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint length (max 100mm))" ),
800 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint net_chain_length (max 100mm))" ),
801 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint skew (max 1mm))" ),
802 wxString( "(condition \"B.Type == 'Track'\")(constraint track_width (min 0.2mm))" ),
803 wxString( "(condition \"B.Type == 'Track'\")(constraint track_angle (min 45))" ),
804 wxString( "(condition \"B.Type == 'Zone'\")(constraint thermal_relief_gap (min 0.2mm))" ) } )
805 {
806 BOOST_TEST_CONTEXT( body )
807 {
808 std::vector<std::shared_ptr<DRC_RULE>> rules;
810 DRC_RULES_PARSER parser( wxS( "(version 1)(rule test " ) + body + wxS( ")" ), wxS( "preflight" ) );
811 parser.Parse( rules, &reporter );
812 BOOST_CHECK_MESSAGE( !reporter.GetMessages().Contains( wxS( "ERROR:" ) ), reporter.GetMessages() );
813 }
814 }
815}
816
817
818BOOST_AUTO_TEST_CASE( PairItemDependency )
819{
820 auto requiresPairItems =
821 []( const wxString& aExpr )
822 {
823 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
824 PCBEXPR_UCODE ucode;
826
827 BOOST_REQUIRE( compiler.Compile( aExpr, &ucode, &preflight ) );
828 return ucode.RequiresPairItems();
829 };
830
831 BOOST_CHECK( !requiresPairItems( wxT( "A.Width == 1mm" ) ) );
832 BOOST_CHECK( requiresPairItems( wxT( "B.Width == 1mm" ) ) );
833 BOOST_CHECK( requiresPairItems( wxT( "AB.isCoupledDiffPair()" ) ) );
834 BOOST_CHECK( !requiresPairItems( wxT( "A.Reference == 'B.Width'" ) ) );
835}
836
837
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
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:410
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1215
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:3173
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1524
void AddVariant(const wxString &aVariantName)
Definition board.cpp:3205
void IncrementTimeStamp()
Definition board.cpp:449
void SetCopperLayerCount(int aCount)
Definition board.cpp:1144
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1301
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
Variant information for a footprint.
Definition footprint.h:228
void SetDNP(bool aDNP)
Definition footprint.h:243
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:274
void SetDNP(bool aDNP=true)
Definition footprint.h:1064
void SetLocalSolderPasteMarginRatio(std::optional< double > aRatio)
Definition footprint.h:530
void SetReference(const wxString &aReference)
Definition footprint.h:915
void SetValue(const wxString &aValue)
Definition footprint.h:938
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
FOOTPRINT_VARIANT * AddVariant(const wxString &aVariantName)
Add a new variant with the given name.
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:232
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:94
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:97
virtual void SetWidth(int aWidth)
Definition pcb_track.h:91
void SetDrill(int aDrill)
Definition pcb_track.h:798
void SetPosition(const VECTOR2I &aPoint) override
Definition pcb_track.h:600
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:419
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.
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:640
void SetIsRuleArea(bool aEnable)
Definition zone.h:833
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
void SetMinIslandArea(long long int aArea)
Definition zone.h:858
wxString normalizeQueryFieldAliases(const wxString &aExpression, const std::vector< PROPERTY_ROW_DATA > &aRows)
@ NULL_CONSTRAINT
Definition drc_rule.h:50
@ DEGREES_T
Definition eda_angle.h:30
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
std::string source
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)
Macro to generate unique identifier for a type.
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,...
PROPERTY_MATCH_MODE matchMode
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:708