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 checkColor( COLOR4D( 1.0, 0.0, 0.0, 1.0 ), wxT( "rgb(255, 0, 0)" ) );
308}
309
310
311BOOST_AUTO_TEST_CASE( ExpressionPropertyTypesAreSupported )
312{
314 propMgr.Rebuild();
315
316 std::set<std::pair<TYPE_ID, PROPERTY_BASE*>> seen;
317 std::map<wxString, std::set<LIBEVAL::VAR_TYPE_T>> fieldTypes;
318
319 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
320 {
321 if( !propMgr.IsOfType( cls.type, TYPE_HASH( BOARD_ITEM ) ) )
322 continue;
323
324 for( PROPERTY_BASE* prop : cls.properties )
325 {
326 if( !seen.emplace( cls.type, prop ).second )
327 continue;
328
330
331 BOOST_CHECK_MESSAGE( kind != PCBEXPR_PROPERTY_KIND::UNSUPPORTED,
332 "Unsupported expression property: class=" << cls.name.mb_str()
333 << ", property=" << prop->Name().mb_str()
334 << ", type_hash=" << prop->TypeHash() );
335
337 fieldTypes[prop->Name()].insert( PCBEXPR_VAR_REF::ExpressionType( kind ) );
338 }
339 }
340
341 // PCB_TARGET::Shape is numeric while EDA_SHAPE::Shape is an enum, so a global
342 // expression reference cannot have one stable public type.
343 auto shapeTypes = fieldTypes.find( wxT( "Shape" ) );
344 BOOST_REQUIRE( shapeTypes != fieldTypes.end() );
345 BOOST_CHECK_EQUAL( shapeTypes->second.size(), 2u );
346 fieldTypes.erase( shapeTypes );
347
348 for( const auto& [field, types] : fieldTypes )
349 {
350 BOOST_CHECK_MESSAGE( types.size() == 1, "Incompatible expression types for property: " << field.mb_str() );
351 }
352
353 expectCompileError( wxT( "A.Shape" ) );
354}
355
356
357BOOST_AUTO_TEST_CASE( IntrospectedPropertyAvailability )
358{
360 propMgr.Rebuild();
361
362 BOARD brd;
363 ZONE zone( &brd );
364
365 zone.SetLayer( F_Cu );
366 zone.SetAssignedPriority( 7 );
367 zone.SetIsRuleArea( true );
368
369 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 0.0 ), false, &zone );
370
371 zone.SetIsRuleArea( false );
372
373 testEvalExpr( wxT( "A.Priority == 7" ), VAL( 1.0 ), false, &zone );
374}
375
376
377BOOST_AUTO_TEST_CASE( InNetChainClassWildcard )
378{
380 propMgr.Rebuild();
381
382 BOARD brd;
383
384 std::shared_ptr<NET_SETTINGS> netSettings = brd.GetDesignSettings().m_NetSettings;
385 netSettings->SetNetChainClass( wxT( "ChainHS" ), wxT( "HighSpeed" ) );
386
387 NETINFO_ITEM* netUnclassified = new NETINFO_ITEM( &brd, "netA", 1 );
388 NETINFO_ITEM* netClassified = new NETINFO_ITEM( &brd, "netB", 2 );
389 NETINFO_ITEM* netNoChain = new NETINFO_ITEM( &brd, "netC", 3 );
390
391 netUnclassified->SetNetChain( wxT( "ChainOrphan" ) );
392 netClassified->SetNetChain( wxT( "ChainHS" ) );
393
394 PCB_TRACK trackUnclassified( &brd );
395 PCB_TRACK trackClassified( &brd );
396 PCB_TRACK trackNoChain( &brd );
397
398 trackUnclassified.SetNet( netUnclassified );
399 trackClassified.SetNet( netClassified );
400 trackNoChain.SetNet( netNoChain );
401
402 // A chain with no class assignment must not match any inNetChainClass() pattern,
403 // including the '*' wildcard.
404 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackUnclassified, &trackUnclassified );
405 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 0.0 ), false, &trackUnclassified, &trackUnclassified );
406
407 // Net with no chain at all must not match either.
408 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 0.0 ), false, &trackNoChain, &trackNoChain );
409
410 // Properly classified chain must match both wildcard and exact patterns.
411 testEvalExpr( wxT( "A.inNetChainClass('*')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
412 testEvalExpr( wxT( "A.inNetChainClass('HighSpeed')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
413 testEvalExpr( wxT( "A.inNetChainClass('High*')" ), VAL( 1.0 ), false, &trackClassified, &trackClassified );
414 testEvalExpr( wxT( "A.inNetChainClass('LowSpeed')" ), VAL( 0.0 ), false, &trackClassified, &trackClassified );
415}
416
417BOOST_AUTO_TEST_CASE( StackedMicroviaExpression )
418{
420 propMgr.Rebuild();
421
422 BOARD brd;
423 brd.SetCopperLayerCount( 4 );
424
425 auto microvia = []( BOARD* aBoard, const VECTOR2I& aPos, PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom )
426 {
427 PCB_VIA* via = new PCB_VIA( aBoard );
428
429 via->SetViaType( VIATYPE::MICROVIA );
430 via->SetPosition( aPos );
431 via->SetLayerPair( aTop, aBottom );
432 via->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.25 ) );
433 via->SetDrill( pcbIUScale.mmToIU( 0.1 ) );
434 aBoard->Add( via );
435
436 return via;
437 };
438
439 VECTOR2I origin( 0, 0 );
440 VECTOR2I away( pcbIUScale.mmToIU( 10 ), 0 );
441
442 PCB_VIA* upperHop = microvia( &brd, origin, F_Cu, In1_Cu );
443 PCB_VIA* lowerHop = microvia( &brd, origin, In1_Cu, In2_Cu );
444 PCB_VIA* lone = microvia( &brd, away, F_Cu, In1_Cu );
445
446 PCB_VIA* through = new PCB_VIA( &brd );
447
448 through->SetViaType( VIATYPE::THROUGH );
449 through->SetPosition( origin );
450 through->SetLayerPair( F_Cu, B_Cu );
451 through->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.6 ) );
452 through->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
453 brd.Add( through );
454
455 // Both hops of a stack are in it, not just the one on top.
456 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, upperHop, upperHop );
457 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, lowerHop, lowerHop );
458
459 // A microvia landing on nothing is not a stack.
460 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 0.0 ), false, lone, lone );
461
462 // A through via sharing the position of a stack is not part of it.
463 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 0.0 ), false, through, through );
464
465 // The predicate composes with the rest of the language.
466 testEvalExpr( wxT( "A.isMicroVia() && !A.isStackedVia()" ), VAL( 1.0 ), false, lone, lone );
467
468 // The relation is cached for the whole board, so an edit has to drop it.
469 PCB_VIA* landing = microvia( &brd, away, In1_Cu, In2_Cu );
470 brd.IncrementTimeStamp();
471
472 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, lone, lone );
473 testEvalExpr( wxT( "A.isStackedVia()" ), VAL( 1.0 ), false, landing, landing );
474}
475
476BOOST_AUTO_TEST_CASE( ParentNavigation )
477{
479 propMgr.Rebuild();
480
481 BOARD brd;
482
483 FOOTPRINT fp( &brd );
484 fp.SetReference( wxT( "J1" ) );
485
486 // A text item living inside the footprint. Its direct parent is the footprint, so
487 // "A.Parent" navigates to J1.
488 PCB_TEXT* text = new PCB_TEXT( &fp );
489 text->SetText( wxT( "J1" ) );
490 fp.Add( text );
491
492 // The headline capability: getField() only returns a value when its receiver is a
493 // footprint, so this passes solely because navigation steps from the text to its parent.
494 testEvalExpr( wxT( "A.Parent.getField('Reference') == 'J1'" ), VAL( 1.0 ), false, text, text );
495
496 // The exact pattern from the issue (parent field compared to the text's own value).
497 testEvalExpr( wxT( "A.Parent.getField('Reference') == A.Text" ), VAL( 1.0 ), false, text, text );
498
499 // The parent resolves to a footprint object that can be type-queried.
500 testEvalExpr( wxT( "A.Parent.Type == 'Footprint'" ), VAL( 1.0 ), false, text, text );
501
502 // Regression: the terminal "Parent" string still yields the parent footprint reference,
503 // i.e. the object and the string refer to the same parent.
504 testEvalExpr( wxT( "A.Parent == 'J1'" ), VAL( 1.0 ), false, text, text );
505
506 // Without navigation getField() has a non-footprint receiver and returns "", so this must
507 // NOT match - it guards against the navigation silently applying to the base item.
508 testEvalExpr( wxT( "A.getField('Reference') == 'J1'" ), VAL( 0.0 ), false, text, text );
509
510 // Error cases. These are code-generation errors (not parse errors), which the shared
511 // testEvalExpr does not observe through Compile()'s return value, so check the pending-error
512 // status directly. None of these expressions contain a bare number, so the only error that
513 // can be raised is the unrecognized item/property we are testing for.
514 // An unknown property on the parent, an unknown navigation step, and navigation on the
515 // layer pseudo-item (which has no parent).
516 expectCompileError( wxT( "A.Parent.bogusProperty" ) );
517 expectCompileError( wxT( "A.bogus.Reference == 'J1'" ) );
518 expectCompileError( wxT( "L.Parent.Type == 'Footprint'" ) );
519}
520
521
522// A property missing on a child item resolves against its parent footprint.
523BOOST_AUTO_TEST_CASE( PropertyParentFallback )
524{
526
527 BOARD brd;
528
529 FOOTPRINT fp( &brd );
530 fp.SetReference( wxT( "J1" ) );
531
532 PCB_TEXT* text = new PCB_TEXT( &fp );
533 fp.Add( text );
534
535 testEvalExpr( wxT( "A.Reference == 'J1'" ), VAL( 1.0 ), false, text, text );
536 testEvalExpr( wxT( "A.Reference == 'J2'" ), VAL( 0.0 ), false, text, text );
537}
538
539
540// A bool property must be readable in a rule.
541BOOST_AUTO_TEST_CASE( BoolPropertyEvaluation )
542{
544
545 BOARD brd;
546
547 FOOTPRINT fp( &brd );
548
549 testEvalExpr( wxT( "A.Do_not_Populate == 0" ), VAL( 1.0 ), false, &fp, &fp );
550
551 fp.SetDNP( true );
552
553 testEvalExpr( wxT( "A.Do_not_Populate == 1" ), VAL( 1.0 ), false, &fp, &fp );
554}
555
556
557// Queries read flags and fields through the current assembly variant.
558BOOST_AUTO_TEST_CASE( VariantAwareEvaluation )
559{
561
562 BOARD brd;
563
564 FOOTPRINT fp( &brd );
565 fp.SetValue( wxT( "BaseVal" ) );
566
567 FOOTPRINT_VARIANT* variant = fp.AddVariant( wxT( "V1" ) );
568 variant->SetDNP( true );
569 variant->SetFieldValue( wxT( "Value" ), wxT( "VariantVal" ) );
570
571 brd.AddVariant( wxT( "V1" ) );
572 brd.SetCurrentVariant( wxT( "V1" ) );
573
574 testEvalExpr( wxT( "A.Do_not_Populate == 1" ), VAL( 1.0 ), false, &fp, &fp );
575 testEvalExpr( wxT( "A.getField('Value') == 'VariantVal'" ), VAL( 1.0 ), false, &fp, &fp );
576
577 // Without a current variant the base values apply.
578 brd.SetCurrentVariant( wxEmptyString );
579
580 testEvalExpr( wxT( "A.Do_not_Populate == 0" ), VAL( 1.0 ), false, &fp, &fp );
581 testEvalExpr( wxT( "A.getField('Value') == 'BaseVal'" ), VAL( 1.0 ), false, &fp, &fp );
582}
583
584
585// Reference resolves as a property with the fallback above, so the query normalizer
586// must leave it alone. getField() has no such fallback.
587BOOST_AUTO_TEST_CASE( FieldAliasNormalization )
588{
589 std::vector<PROPERTY_ROW_DATA> rows;
590
591 BOOST_CHECK_EQUAL( normalizeQueryFieldAliases( wxS( "A.Reference == 'R1'" ), rows ), wxS( "A.Reference == 'R1'" ) );
592
593 // A field with no property behind it needs the rewrite to resolve at all.
594 BOOST_CHECK_EQUAL( normalizeQueryFieldAliases( wxS( "A.Value == 'X'" ), rows ),
595 wxS( "A.getField('Value') == 'X'" ) );
596
597 PROPERTY_ROW_DATA customField;
598 customField.propertyName = wxS( "My Field" );
599 customField.property = nullptr;
601 customField.isMixed = false;
602
603 BOOST_CHECK_EQUAL( normalizeQueryFieldAliases( wxS( "A.My_Field == 'X'" ), { customField } ),
604 wxS( "A.getField('My Field') == 'X'" ) );
605}
606
607
608BOOST_AUTO_TEST_CASE( ReceiverValidation )
609{
610 expectCompileSuccess( wxT( "L == 'F.Cu'" ) );
611 expectCompileSuccess( wxT( "AB.isCoupledDiffPair()" ) );
612 expectCompileSuccess( wxT( "A.Parent.Reference == 'J1'" ) );
613 expectCompileSuccess( wxT( "A.Width == 1mm" ) );
614
615 expectCompileError( wxT( "L.Width == 1mm" ) );
616 expectCompileError( wxT( "AB.Width == A.Width" ) );
617 expectCompileError( wxT( "AB.Parent.Reference == 'J1'" ) );
618}
619
620
621BOOST_AUTO_TEST_CASE( LayerReceiverLayerField )
622{
623 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
624 PCBEXPR_UCODE ucode;
626
627 compiler.Compile( wxT( "L.Layer == '*.Paste'" ), &ucode, &preflight );
628 BOOST_REQUIRE( !compiler.IsErrorPending() );
629
630 BOARD brd;
631 PCB_TRACK track( &brd );
634
635 paste.SetItems( &track, &track );
636 copper.SetItems( &track, &track );
637
638 BOOST_CHECK_EQUAL( ucode.Run( &paste )->AsDouble(), 1.0 );
639 BOOST_CHECK_EQUAL( ucode.Run( &copper )->AsDouble(), 0.0 );
640}
641
642
643BOOST_AUTO_TEST_CASE( DynamicCourtyardArgument )
644{
646
647 BOARD board;
648 FOOTPRINT* target = new FOOTPRINT( &board );
649 target->SetReference( wxS( "U1" ) );
650 KI_TEST::DrawRect( *target, { 0, 0 }, { pcbIUScale.mmToIU( 4 ), pcbIUScale.mmToIU( 4 ) },
651 0, pcbIUScale.mmToIU( 0.05 ), F_CrtYd );
652 board.Add( target );
653
654 FOOTPRINT* other = new FOOTPRINT( &board );
655 other->SetReference( wxS( "U2" ) );
656 board.Add( other );
657
658 PCB_SHAPE graphic( other, SHAPE_T::SEGMENT );
659 graphic.SetLayer( F_Fab );
660 graphic.SetStart( { 0, 0 } );
661 graphic.SetEnd( { pcbIUScale.mmToIU( 1 ), 0 } );
662 graphic.SetWidth( pcbIUScale.mmToIU( 0.05 ) );
663
664 PCB_TEXT targetChild( target );
665 PCB_TEXT otherChild( other );
666
667 for( const wxString& expression : {
668 wxString( "A.intersectsFrontCourtyard(B.Parent)" ),
669 wxString( "A.intersectsCourtyard(B.Parent.Reference)" ),
670 wxString( "A.intersectsFrontCourtyard(B.Parent.getField('Reference'))" ) } )
671 {
672 BOOST_TEST_CONTEXT( expression )
673 {
674 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
675 PCBEXPR_UCODE ucode;
678
679 BOOST_REQUIRE( compiler.Compile( expression, &ucode, &preflight ) );
680 BOOST_REQUIRE_MESSAGE( !compiler.IsErrorPending(), compiler.GetError().message );
681 BOOST_CHECK( ucode.RequiresPairItems() );
682
683 context.SetItems( &graphic, &targetChild );
684 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 1.0 );
685
686 context.SetItems( &graphic, &otherChild );
687 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 0.0 );
688
689 context.SetItems( &graphic, &targetChild );
690 BOOST_CHECK_EQUAL( ucode.Run( &context )->AsDouble(), 1.0 );
691 }
692 }
693}
694
695
696BOOST_AUTO_TEST_CASE( FunctionArgumentValidation )
697{
698 expectCompileError( wxS( "A.intersectsFrontCourtyard()" ) );
699 expectCompileError( wxS( "A.intersectsFrontCourtyard('')" ) );
700 expectCompileError( wxS( "A.memberOfFootprint('')" ) );
701 expectCompileError( wxS( "A.fromTo('U1-1')" ) );
702 expectCompileError( wxS( "A.intersectsFrontCourtyard(B.UnknownProperty)" ) );
703 expectCompileError( wxS( "A.intersectsFrontCourtyard(B.Parent.unknownFunction())" ) );
704}
705
706
707BOOST_AUTO_TEST_CASE( SingleItemRulePreflight )
708{
710
711 for( const wxString& constraint : {
712 wxString( "assertion \"A.Type == 'Graphic'\"" ),
713 wxString( "hole_size (min 0.2mm)" ), wxString( "text_height (min 1mm)" ),
714 wxString( "text_thickness (min 0.1mm)" ),
715 wxString( "track_segment_length (min 1mm)" ), wxString( "annular_width (min 0.1mm)" ),
716 wxString( "solder_mask_expansion (opt 0mm)" ),
717 wxString( "solder_paste_abs_margin (opt 0mm)" ),
718 wxString( "solder_paste_rel_margin (opt 0mm)" ), wxString( "disallow track" ),
719 wxString( "via_diameter (min 0.5mm)" ), wxString( "length (max 100mm)" ),
720 wxString( "net_chain_length (max 100mm)" ), wxString( "stub_length (max 1mm)" ),
721 wxString( "return_path (layer 'B.Cu')" ), wxString( "skew (max 1mm)" ),
722 wxString( "via_count (max 2)" ),
723 wxString( "via_dangling (max 0)" ), wxString( "bridged_mask (min 0)" ),
724 wxString( "microvia_stack_depth (max 2)" ), wxString( "microvia_aspect_ratio (max 1)" ) } )
725 {
726 for( bool conditionFirst : { true, false } )
727 {
728 BOOST_TEST_CONTEXT( constraint << ", condition first: " << conditionFirst )
729 {
730 wxString condition = wxS( "(condition \"B.Type == 'Graphic'\")" );
731 wxString body = wxString::Format( wxS( "(constraint %s)" ), constraint );
732 wxString source = wxS( "(version 1)\n(rule test\n" )
733 + ( conditionFirst ? condition + wxS( "\n" ) + body
734 : body + wxS( "\n" ) + condition ) + wxS( "\n)" );
735 std::vector<std::shared_ptr<DRC_RULE>> rules;
737 wxString controlSource = source;
738 controlSource.Replace( wxS( "B.Type" ), wxS( "A.Type" ) );
739 DRC_RULES_PARSER controlParser( controlSource, wxS( "single item control" ) );
740 controlParser.Parse( rules, &reporter );
741 BOOST_REQUIRE_MESSAGE( !reporter.GetMessages().Contains( wxS( "ERROR:" ) ), reporter.GetMessages() );
742 reporter.Clear();
743
744 DRC_RULES_PARSER parser( source, wxS( "single item preflight" ) );
745 parser.Parse( rules, &reporter );
746 BOOST_CHECK_MESSAGE( reporter.GetMessages().Contains( wxS( "Item 'B'" ) ),
747 reporter.GetMessages() );
748
749 DRC_RULES_PARSER throwingParser( source, wxS( "single item preflight" ) );
750 BOOST_CHECK_EXCEPTION( throwingParser.Parse( rules, nullptr ), PARSE_ERROR,
751 [conditionFirst]( const PARSE_ERROR& error )
752 {
753 return error.What().Contains( wxS( "Item 'B' is not available" ) )
754 && error.lineNumber == ( conditionFirst ? 3 : 4 );
755 } );
756 }
757 }
758 }
759}
760
761
762BOOST_AUTO_TEST_CASE( PairConditionsInSingleItemRules )
763{
765
766 for( const wxString& body : {
767 wxString( "(condition \"A.intersectsFrontCourtyard(B.Parent)\")"
768 "(constraint assertion \"A.Type == 'Graphic'\")" ),
769 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint hole_size (min 0.2mm))" ),
770 wxString( "(constraint assertion \"B.Type == 'Graphic'\")" ),
771 wxString( "(constraint assertion \"A.intersectsFrontCourtyard(B.Parent.getField('Reference'))\")" ),
772 wxString( "(condition \"B.Type == 'Graphic'\")(constraint clearance (min 0.2mm))"
773 "(constraint hole_size (min 0.2mm))" ),
774 wxString( "(condition \"AB.isCoupledDiffPair() && B.Type == 'Track'\")"
775 "(constraint length (max 100mm))" ) } )
776 {
777 BOOST_TEST_CONTEXT( body )
778 {
779 std::vector<std::shared_ptr<DRC_RULE>> rules;
780 DRC_RULES_PARSER parser( wxS( "(version 1)(rule test " ) + body + wxS( ")" ), wxS( "preflight" ) );
781 BOOST_CHECK_EXCEPTION( parser.Parse( rules, nullptr ), PARSE_ERROR,
782 []( const PARSE_ERROR& error )
783 {
784 return error.What().Contains( wxS( "Item 'B' is not available" ) );
785 } );
786 }
787 }
788
789 for( const wxString& body : {
790 wxString( "(condition \"A.Reference == 'B.Width'\")(constraint hole_size (min 0.2mm))" ),
791 wxString( "(constraint assertion \"A.Type == 'Graphic'\")" ),
792 wxString( "(condition \"A.intersectsFrontCourtyard(B.Parent)\")"
793 "(constraint physical_clearance (min 100mm))" ),
794 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint diff_pair_gap (min 0.2mm))" ),
795 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint track_width (min 0.2mm))" ),
796 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint diff_pair_uncoupled (max 1mm))" ),
797 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint length (max 100mm))" ),
798 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint net_chain_length (max 100mm))" ),
799 wxString( "(condition \"AB.isCoupledDiffPair()\")(constraint skew (max 1mm))" ),
800 wxString( "(condition \"B.Type == 'Track'\")(constraint track_width (min 0.2mm))" ),
801 wxString( "(condition \"B.Type == 'Track'\")(constraint track_angle (min 45))" ),
802 wxString( "(condition \"B.Type == 'Zone'\")(constraint thermal_relief_gap (min 0.2mm))" ) } )
803 {
804 BOOST_TEST_CONTEXT( body )
805 {
806 std::vector<std::shared_ptr<DRC_RULE>> rules;
808 DRC_RULES_PARSER parser( wxS( "(version 1)(rule test " ) + body + wxS( ")" ), wxS( "preflight" ) );
809 parser.Parse( rules, &reporter );
810 BOOST_CHECK_MESSAGE( !reporter.GetMessages().Contains( wxS( "ERROR:" ) ), reporter.GetMessages() );
811 }
812 }
813}
814
815
816BOOST_AUTO_TEST_CASE( PairItemDependency )
817{
818 auto requiresPairItems =
819 []( const wxString& aExpr )
820 {
821 PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER() );
822 PCBEXPR_UCODE ucode;
824
825 BOOST_REQUIRE( compiler.Compile( aExpr, &ucode, &preflight ) );
826 return ucode.RequiresPairItems();
827 };
828
829 BOOST_CHECK( !requiresPairItems( wxT( "A.Width == 1mm" ) ) );
830 BOOST_CHECK( requiresPairItems( wxT( "B.Width == 1mm" ) ) );
831 BOOST_CHECK( requiresPairItems( wxT( "AB.isCoupledDiffPair()" ) ) );
832 BOOST_CHECK( !requiresPairItems( wxT( "A.Reference == 'B.Width'" ) ) );
833}
834
835
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:3170
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1521
void AddVariant(const wxString &aVariantName)
Definition board.cpp:3202
void IncrementTimeStamp()
Definition board.cpp:446
void SetCopperLayerCount(int aCount)
Definition board.cpp:1141
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1298
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.
static PROPERTY_MANAGER & Instance()
void Rebuild()
Rebuild the list of all registered properties.
std::vector< CLASS_INFO > GetAllClasses()
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