KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_text_eval_parser_integration.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
24
26#include <qa_utils/file_utils.h>
30
31// Code under test
32#include <common.h>
38#include <title_block.h>
39#include <wx/filefn.h>
40#include <wx/filename.h>
41
42#include <fmt/ranges.h>
43#include <chrono>
44#include <regex>
45
49BOOST_AUTO_TEST_SUITE( TextEvalParserIntegration )
50
51BOOST_AUTO_TEST_CASE( VcsNativeProjectContextPaths )
52{
53 SCOPED_GIT_BACKEND backend;
54 KI_TEST::SCOPED_TEMP_DIR owner( "text-vcs-owner" );
55 const wxString projectFile = owner.PathStr() + "/multinetclasses.kicad_pro";
56 const wxString source = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() )
57 + "/netlists/multinetclasses/multinetclasses.kicad_pro";
58 BOOST_REQUIRE( wxCopyFile( source, projectFile ) );
59 BOOST_REQUIRE( wxMkdir( owner.PathStr() + "/sub" ) );
60 BOOST_REQUIRE( wxCopyFile( source, owner.PathStr() + "/sub/multinetclasses.kicad_pro" ) );
62 KI_TEST::InitGitRepository( owner.PathStr(), "owner", "Connectivity QA", "[email protected]" );
63
64 // Stage the project files even if the user's ignore rules match them.
65 git_index* rawIndex = nullptr;
66 BOOST_REQUIRE_EQUAL( git_repository_index( &rawIndex, repo.get() ), GIT_OK );
67 KIGIT::GitIndexPtr index( rawIndex );
68 BOOST_REQUIRE_EQUAL( git_index_add_bypath( index.get(), "multinetclasses.kicad_pro" ), GIT_OK );
69 BOOST_REQUIRE_EQUAL( git_index_add_bypath( index.get(), "sub/multinetclasses.kicad_pro" ), GIT_OK );
70 BOOST_REQUIRE_EQUAL( git_index_write( index.get() ), GIT_OK );
71 KI_TEST::CommitGitIndex( repo.get(), "Native project" );
72 const wxString previous = TEXT_EVAL_VCS::GetContextPath();
73 const auto evaluateVcs = []( const wxString& expression )
74 {
75 EXPRESSION_EVALUATOR evaluator;
76 const wxString result = evaluator.Evaluate( expression );
77 BOOST_REQUIRE_MESSAGE( !evaluator.HasErrors(), evaluator.GetErrorSummary() );
78 return result.ToStdString( wxConvUTF8 );
79 };
80 const auto commitHash = [&]( const std::string& path = "." )
81 {
82 wxString quoted = wxString::FromUTF8( path );
83 quoted.Replace( "\\", "\\\\" );
84 quoted.Replace( "\"", "\\\"" );
85 return evaluateVcs( wxString::Format( "@{vcsfileidentifier(\"%s\")}", quoted ) );
86 };
87
88 {
90 const auto hash = commitHash();
91 BOOST_REQUIRE_EQUAL( hash.size(), 40u );
92 BOOST_CHECK_EQUAL( evaluateVcs( "@{vcsbranch()}" ), "owner" );
93 BOOST_CHECK_EQUAL( commitHash( "multinetclasses.kicad_pro" ), hash );
94 BOOST_CHECK_EQUAL( commitHash( projectFile.ToStdString( wxConvUTF8 ) ), hash );
95 BOOST_CHECK_EQUAL( commitHash( "untracked.kicad_pro" ), "<unknown>" );
96
97 {
98 TEXT_EVAL_VCS::CONTEXT_PATH_SCOPE fileContext( owner.PathStr() + "/sub/multinetclasses.kicad_pro" );
99 BOOST_CHECK( TEXT_EVAL_VCS::GetContextIsFile() );
100 BOOST_CHECK_EQUAL( commitHash( "multinetclasses.kicad_pro" ), hash );
101 BOOST_CHECK_EQUAL( commitHash( "../multinetclasses.kicad_pro" ), hash );
102 BOOST_REQUIRE( wxRemoveFile( owner.PathStr() + "/sub/multinetclasses.kicad_pro" ) );
103 BOOST_CHECK_EQUAL( commitHash( "multinetclasses.kicad_pro" ), hash );
104 }
105
107 BOOST_CHECK( !TEXT_EVAL_VCS::GetContextIsFile() );
108 }
109
111
112 {
114 TEXT_EVAL::ENVIRONMENT environment;
115 TEXT_EVAL::ENVIRONMENT_SCOPE frame( environment );
117 wxString projectHash;
118
119 {
120 TEXT_EVAL::SOURCE_SCOPE collect( environment, sources );
121 projectHash = KIGIT::PROJECT_GIT_UTILS::GetCurrentHash( projectFile, false );
122 BOOST_CHECK_EQUAL( evaluateVcs( "@{vcsbranch()}" ), "owner" );
123 }
124
125 git_oid changedId = KI_TEST::CommitGitIndex( repo.get(), "Next native project" );
126 git_reference* rawBranch = nullptr;
127 BOOST_REQUIRE_EQUAL( git_reference_create( &rawBranch, repo.get(), "refs/heads/changed",
128 &changedId, 0, nullptr ), 0 );
129 git_reference_free( rawBranch );
130 BOOST_REQUIRE_EQUAL( git_repository_set_head( repo.get(), "refs/heads/changed" ), 0 );
131 BOOST_CHECK_EQUAL( KIGIT::PROJECT_GIT_UTILS::GetCurrentHash( projectFile, false ), projectHash );
132 BOOST_CHECK_EQUAL( commitHash(), projectHash.ToStdString( wxConvUTF8 ) );
133 BOOST_CHECK_EQUAL( evaluateVcs( "@{vcsbranch()}" ), "owner" );
134
135 for( const auto& [key, value] : sources.vcsValues )
136 BOOST_CHECK( TEXT_EVAL_VCS::ReadSource( key ) == value );
137
138 {
139 TEXT_EVAL::ENVIRONMENT refreshed;
140 TEXT_EVAL::ENVIRONMENT_SCOPE refreshedFrame( refreshed );
141 BOOST_CHECK_EQUAL( evaluateVcs( "@{vcsbranch()}" ), "changed" );
142 BOOST_CHECK( KIGIT::PROJECT_GIT_UTILS::GetCurrentHash( projectFile, false ) != projectHash );
143 }
144
145 BOOST_CHECK_EQUAL( evaluateVcs( "@{vcsbranch()}" ), "owner" );
146 }
147}
148
149
150BOOST_AUTO_TEST_CASE( EnvironmentFrameCapturesDynamicSources )
151{
152 const wxDateTime time( 3, wxDateTime::Jan, 2001, 4, 5, 6 );
153 TEXT_EVAL::ENVIRONMENT environment( time );
155 BOOST_CHECK( TEXT_EVAL::ENVIRONMENT::Current() == nullptr );
156
157 {
158 TEXT_EVAL::ENVIRONMENT_SCOPE frame( environment );
159 TEXT_EVAL::SOURCE_SCOPE collect( environment, sources );
160 BOOST_CHECK_EQUAL( TITLE_BLOCK::GetCurrentDate(), time.FormatISODate() );
161 BOOST_CHECK_EQUAL( TITLE_BLOCK::GetCurrentTimeHHMMSS(), time.Format( "%Hh%Mm%Ss" ) );
163 EXPRESSION_EVALUATOR evaluator;
164 BOOST_CHECK_EQUAL( evaluator.Evaluate( "@{format(now(), 0)}" ),
165 wxString::Format( "%lld", static_cast<long long>( time.GetTicks() ) ) );
166 BOOST_CHECK_EQUAL( evaluator.Evaluate( "@{format(today(), 0)}" ),
167 wxString::Format( "%lld", static_cast<long long>( time.GetTicks() ) / ( 24 * 3600 ) ) );
168 TEXT_EVAL::ENVIRONMENT other( wxDateTime( 4, wxDateTime::Feb, 2002 ) );
169
170 {
171 TEXT_EVAL::ENVIRONMENT_SCOPE nested( other );
172 BOOST_CHECK_EQUAL( TITLE_BLOCK::GetCurrentDate(), wxString( "2002-02-04" ) );
173 }
174
175 BOOST_CHECK_EQUAL( TITLE_BLOCK::GetCurrentDate(), time.FormatISODate() );
176 evaluator.Evaluate( "@{random()}" );
177 BOOST_CHECK( sources.randomUsed );
178 BOOST_REQUIRE( sources.time );
179 BOOST_CHECK( *sources.time == time );
180
181 const wxString envName = wxS( "KICAD_QA_TEXT_EVAL_SOURCE" );
182
183 {
184 KI_TEST::SCOPED_PROCESS_ENV_VAR scoped( envName, wxS( "captured" ) );
185 const wxString expanded = ExpandEnvVarSubstitutions( wxS( "${KICAD_QA_TEXT_EVAL_SOURCE}" ), nullptr );
186
187 BOOST_CHECK_EQUAL( expanded, wxS( "captured" ) );
188
189 scoped.ClearValue();
190 BOOST_CHECK( sources.environmentVariables[envName] == wxString( wxS( "captured" ) ) );
191 }
192 }
193
194 BOOST_CHECK( TEXT_EVAL::ENVIRONMENT::Current() == nullptr );
195 const wxString liveBefore = wxDateTime::Now().FormatISODate();
196 const wxString outside = TITLE_BLOCK::GetCurrentDate();
197 BOOST_CHECK( outside == liveBefore || outside == wxDateTime::Now().FormatISODate() );
198}
199
200
204BOOST_AUTO_TEST_CASE( RealWorldScenarios )
205{
206 EXPRESSION_EVALUATOR evaluator;
207
208 // Set up variables that might be used in actual KiCad projects
209 evaluator.SetVariable( "board_width", 100.0 );
210 evaluator.SetVariable( "board_height", 80.0 );
211 evaluator.SetVariable( "trace_width", 0.2 );
212 evaluator.SetVariable( "component_count", 45.0 );
213 evaluator.SetVariable( "revision", 3.0 );
214 evaluator.SetVariable( std::string("project_name"), std::string("My PCB Project") );
215 evaluator.SetVariable( std::string("designer"), std::string("John Doe") );
216
217 struct TestCase {
218 std::string expression;
219 std::string expectedPattern; // Can be exact match or regex pattern
220 bool isRegex;
221 bool shouldError;
222 std::string description;
223 };
224
225 const std::vector<TestCase> cases = {
226 // Board dimension calculations
227 {
228 "Board area: @{${board_width} * ${board_height}} mm²",
229 "Board area: 8000 mm²",
230 false, false,
231 "Board area calculation"
232 },
233 {
234 "Perimeter: @{2 * (${board_width} + ${board_height})} mm",
235 "Perimeter: 360 mm",
236 false, false,
237 "Board perimeter calculation"
238 },
239 {
240 "Diagonal: @{format(sqrt(pow(${board_width}, 2) + pow(${board_height}, 2)), 1)} mm",
241 "Diagonal: 128.1 mm",
242 false, false,
243 "Board diagonal calculation"
244 },
245
246 // Text formatting scenarios
247 {
248 "Project: ${project_name} | Designer: ${designer} | Rev: @{${revision}}",
249 "Project: My PCB Project | Designer: John Doe | Rev: 3",
250 false, false,
251 "Title block information"
252 },
253 {
254 "Components: @{${component_count}} | Density: @{format(${component_count} / (${board_width} * ${board_height} / 10000), 2)} per cm²",
255 "Components: 45 | Density: 56.25 per cm²",
256 false, false,
257 "Component density calculation"
258 },
259
260 // Date-based revision tracking
261 {
262 "Created: @{dateformat(today())} | Build: @{today()} days since epoch",
263 R"(Created: \d{4}-\d{2}-\d{2} \| Build: \d+ days since epoch)",
264 true, false,
265 "Date-based tracking"
266 },
267
268 // Conditional formatting
269 {
270 "Status: @{if(${component_count} > 50, \"Complex\", \"Simple\")} design",
271 "Status: Simple design",
272 false, false,
273 "Conditional design complexity"
274 },
275 {
276 "Status: @{if(${trace_width} >= 0.2, \"Standard\", \"Fine pitch\")} (@{${trace_width}}mm)",
277 "Status: Standard (0.2mm)",
278 false, false,
279 "Conditional trace width description"
280 },
281
282 // Multi-line documentation
283 {
284 "PCB Summary:\n- Size: @{${board_width}}×@{${board_height}}mm\n- Area: @{${board_width} * ${board_height}}mm²\n- Components: @{${component_count}}",
285 "PCB Summary:\n- Size: 100×80mm\n- Area: 8000mm²\n- Components: 45",
286 false, false,
287 "Multi-line documentation"
288 },
289
290 // Error scenarios - undefined variables error and return unchanged
291 {
292 "Invalid: @{${undefined_var}} test",
293 "Invalid: @{${undefined_var}} test",
294 false, true,
295 "Undefined variable behavior"
296 },
297 };
298
299 for( const auto& testCase : cases )
300 {
301 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
302
303 if( testCase.shouldError )
304 {
305 BOOST_CHECK_MESSAGE( evaluator.HasErrors(),
306 "Expected error for: " + testCase.description );
307 }
308 else
309 {
310 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
311 "Unexpected error for: " + testCase.description +
312 " - " + evaluator.GetErrorSummary().ToStdString() );
313
314 if( testCase.isRegex )
315 {
316 std::regex pattern( testCase.expectedPattern );
317 BOOST_CHECK_MESSAGE( std::regex_match( result.ToStdString( wxConvUTF8 ), pattern ),
318 "Result '" + result.ToStdString( wxConvUTF8 ) + "' doesn't match pattern '" +
319 testCase.expectedPattern + "' for: " + testCase.description );
320 }
321 else
322 {
323 BOOST_CHECK_MESSAGE( result.ToStdString( wxConvUTF8 ) == testCase.expectedPattern,
324 "Expected '" + testCase.expectedPattern + "' but got '" +
325 result.ToStdString( wxConvUTF8 ) + "' for: " + testCase.description );
326 }
327 }
328 }
329}
330
334BOOST_AUTO_TEST_CASE( CallbackVariableResolution )
335{
336 // Create evaluator with custom callback
337 auto variableCallback =
338 []( const std::string& varName ) -> calc_parser::RESULT<calc_parser::VALUE>
339 {
340 if( varName == "dynamic_value" )
342 else if( varName == "dynamic_string" )
343 return calc_parser::MakeValue<calc_parser::VALUE>( std::string( "Hello from callback" ) );
344 else if( varName == "computed_value" )
345 return calc_parser::MakeValue<calc_parser::VALUE>( std::sin( 3.14159 / 4 ) * 100.0 ); // Should be about 70.7
346 else
347 return calc_parser::MakeError<calc_parser::VALUE>( "Variable '" + varName + "' not found in callback" );
348 };
349
350 EXPRESSION_EVALUATOR evaluator( variableCallback, false );
351
352 struct TestCase {
353 std::string expression;
354 std::string expected;
355 double tolerance;
356 bool shouldError;
357 };
358
359 const std::vector<TestCase> cases = {
360 { "@{${dynamic_value}}", "42", 0, false },
361 { "Message: ${dynamic_string}", "Message: Hello from callback", 0, false },
362 { "@{format(${computed_value}, 1)}", "70.7", 0.1, false },
363 { "@{${dynamic_value} + ${computed_value}}", "112.7", 0.1, false },
364 { "${nonexistent}", "${nonexistent}", 0, true },
365 };
366
367 for( const auto& testCase : cases )
368 {
369 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
370
371 if( testCase.shouldError )
372 {
373 BOOST_CHECK( evaluator.HasErrors() );
374 }
375 else
376 {
377 BOOST_CHECK( !evaluator.HasErrors() );
378
379 if( testCase.tolerance > 0 )
380 {
381 // For floating point comparisons, extract the number
382 std::regex numberRegex( R"([\d.]+)" );
383 std::smatch match;
384 std::string resultStr = result.ToStdString( wxConvUTF8 );
385 if( std::regex_search( resultStr, match, numberRegex ) )
386 {
387 double actualValue = std::stod( match[0].str() );
388 double expectedValue = std::stod( testCase.expected );
389 BOOST_CHECK_CLOSE( actualValue, expectedValue, testCase.tolerance * 100 );
390 }
391 }
392 else
393 {
394 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
395 }
396 }
397 }
398}
399
403BOOST_AUTO_TEST_CASE( ThreadSafety )
404{
405 // Create multiple evaluators that could be used in different threads
406 std::vector<std::unique_ptr<EXPRESSION_EVALUATOR>> evaluators;
407
408 for( int i = 0; i < 10; ++i )
409 {
410 auto evaluator = std::make_unique<EXPRESSION_EVALUATOR>();
411 evaluator->SetVariable( "thread_id", static_cast<double>( i ) );
412 evaluator->SetVariable( "multiplier", 5.0 );
413 evaluators.push_back( std::move( evaluator ) );
414 }
415
416 // Test that each evaluator maintains its own state
417 for( int i = 0; i < 10; ++i )
418 {
419 auto result = evaluators[i]->Evaluate( "@{${thread_id} * ${multiplier}}" );
420 BOOST_CHECK( !evaluators[i]->HasErrors() );
421
422 double expected = static_cast<double>( i * 5 );
423 double actual = std::stod( result.ToStdString( wxConvUTF8 ) );
424 BOOST_CHECK_CLOSE( actual, expected, 0.001 );
425 }
426}
427
431BOOST_AUTO_TEST_CASE( MemoryManagement )
432{
433 EXPRESSION_EVALUATOR evaluator;
434
435 // Test large nested expressions
436 std::string complexExpression = "@{";
437 for( int i = 0; i < 100; ++i )
438 {
439 if( i > 0 ) complexExpression += " + ";
440 complexExpression += std::to_string( i );
441 }
442 complexExpression += "}";
443
444 auto result = evaluator.Evaluate( wxString::FromUTF8( complexExpression ) );
445 BOOST_CHECK( !evaluator.HasErrors() );
446
447 // Sum of 0..99 is 4950
448 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), std::string( "4950" ) );
449
450 // Test many small expressions
451 for( int i = 0; i < 1000; ++i )
452 {
453 auto expr = "@{" + std::to_string( i ) + " * 2}";
454 auto result = evaluator.Evaluate( wxString::FromUTF8( expr ) );
455 BOOST_CHECK( !evaluator.HasErrors() );
456 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), std::to_string( i * 2 ) );
457 }
458}
459
463BOOST_AUTO_TEST_CASE( ParsingEdgeCases )
464{
465 EXPRESSION_EVALUATOR evaluator;
466
467 struct TestCase {
468 std::string expression;
469 std::string expected;
470 bool shouldError;
471 double precision;
472 std::string description;
473 };
474
475 const std::vector<TestCase> cases = {
476 // Whitespace handling
477 { "@{ 2 + 3 }", "5", false, 0.0, "Spaces in expression" },
478 { "@{\t2\t+\t3\t}", "5", false, 0.0, "Tabs in expression" },
479 { "@{\n2\n+\n3\n}", "5", false, 0.0, "Newlines in expression" },
480
481 // String escaping and special characters
482 { "@{\"Hello\\\"World\\\"\"}", "Hello\"World\"", false, 0.0, "Escaped quotes in string" },
483 { "@{\"Line1\\nLine2\"}", "Line1\nLine2", false, 0.0, "Newline in string" },
484
485 // Multiple calculations in complex text
486 { "A: @{1+1}, B: @{2*2}, C: @{3^2}", "A: 2, B: 4, C: 9", false, 0.0, "Multiple calculations" },
487
488 // Edge cases with parentheses
489 { "@{((((2))))}", "2", false, 0.0, "Multiple nested parentheses" },
490 { "@{(2 + 3) * (4 + 5)}", "45", false, 0.0, "Grouped operations" },
491
492 // Empty and minimal expressions
493 { "No calculations here", "No calculations here", false, 0.0, "Plain text" },
494 { "", "", false, 0.0, "Empty string" },
495 { "@{0}", "0", false, 0.0, "Zero value" },
496 { "@{-0}", "0", false, 0.0, "Negative zero" },
497
498 // Precision and rounding edge cases
499 { "@{0.1 + 0.2}", "0.3", false, 0.01, "Floating point precision" },
500 { "@{1.0 / 3.0}", "0.333333", false, 0.01, "Repeating decimal" },
501
502 // Large numbers
503 { "@{1000000 * 1000000}", "1e+12", false, 0.01, "Large number result" },
504
505 // Error recovery - malformed expressions left unchanged, valid ones evaluated
506 { "Good @{2+2} bad @{2+} good @{3+3}", "Good 4 bad @{2+} good 6", true, 0.0, "Error recovery" },
507 };
508
509 for( const auto& testCase : cases )
510 {
511 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
512
513 if( testCase.shouldError )
514 {
515 BOOST_CHECK_MESSAGE( evaluator.HasErrors(), "Expected error for: " + testCase.description );
516 }
517 else
518 {
519 if( testCase.precision > 0.0 )
520 {
521 // For floating point comparisons, extract the number
522 std::regex numberRegex( R"([\d.eE+-]+)" );
523 std::smatch match;
524 std::string resultStr = result.ToStdString( wxConvUTF8 );
525 if( std::regex_search( resultStr, match, numberRegex ) )
526 {
527 double actualValue = std::stod( match[0].str() );
528 double expectedValue = std::stod( testCase.expected );
529 BOOST_CHECK_CLOSE( actualValue, expectedValue, testCase.precision * 100 );
530 }
531 }
532 else
533 {
534 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
535 "Unexpected error for: " + testCase.description +
536 " - " + evaluator.GetErrorSummary().ToStdString() );
537 BOOST_CHECK_MESSAGE( result.ToStdString( wxConvUTF8 ) == testCase.expected,
538 "Expected '" + testCase.expected + "' but got '" +
539 result.ToStdString( wxConvUTF8 ) + "' for: " + testCase.description );
540 }
541 }
542 }
543}
544
548BOOST_AUTO_TEST_CASE( RealWorldPerformance )
549{
550 EXPRESSION_EVALUATOR evaluator;
551
552 // Set up variables for a typical PCB project
553 evaluator.SetVariable( "board_layers", 4.0 );
554 evaluator.SetVariable( "component_count", 150.0 );
555 evaluator.SetVariable( "net_count", 200.0 );
556 evaluator.SetVariable( "via_count", 300.0 );
557 evaluator.SetVariable( "board_width", 120.0 );
558 evaluator.SetVariable( "board_height", 80.0 );
559
560 // Simulate processing many text objects (like in a real PCB layout)
561 std::vector<std::string> expressions = {
562 "Layer @{${board_layers}}/4",
563 "Components: @{${component_count}}",
564 "Nets: @{${net_count}}",
565 "Vias: @{${via_count}}",
566 "Area: @{${board_width} * ${board_height}} mm²",
567 "Density: @{format(${component_count} / (${board_width} * ${board_height} / 100), 1)} /cm²",
568 "Via density: @{format(${via_count} / (${board_width} * ${board_height} / 100), 1)} /cm²",
569 "Layer utilization: @{format(${net_count} / ${board_layers}, 1)} nets/layer",
570 "Design complexity: @{if(${component_count} > 100, \"High\", \"Low\")}",
571 "Board aspect ratio: @{format(${board_width} / ${board_height}, 2)}:1",
572 };
573
574 auto start = std::chrono::high_resolution_clock::now();
575
576 std::set<std::string> errors;
577
578 // Process expressions many times (simulating real usage)
579 for( int iteration = 0; iteration < 100; ++iteration )
580 {
581 for( const auto& expr : expressions )
582 {
583 auto result = evaluator.Evaluate( wxString::FromUTF8( expr ) );
584
585 if( evaluator.HasErrors() || result.empty() )
586 errors.insert( expr );
587 }
588 }
589
590 BOOST_REQUIRE_MESSAGE( errors.empty(), fmt::format( "Evaluation of expressions had errors: {}", fmt::join( errors, ", " ) ) );
591
592 auto end = std::chrono::high_resolution_clock::now();
593 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( end - start );
594
595 // Sanitizer instrumentation changes execution cost, not the evaluation contract.
596#if defined( KICAD_SANITIZE_THREADS ) || defined( KICAD_SANITIZE_ADDRESS )
597 BOOST_TEST_MESSAGE( "Instrumented expression evaluation: " << duration.count() << " ms" );
598#else
599 BOOST_CHECK_LT( duration.count(), 100 );
600#endif
601
602 // Test that results are consistent
603 for( auto& expr : expressions )
604 {
605 auto result1 = evaluator.Evaluate( wxString::FromUTF8( expr ) );
606 auto result2 = evaluator.Evaluate( wxString::FromUTF8( expr ) );
607 BOOST_CHECK_EQUAL( result1, result2 );
608 }
609}
610
int index
High-level wrapper for evaluating mathematical and string expressions in wxString format.
wxString Evaluate(const wxString &aInput)
Main evaluation function - processes input string and evaluates all '@{}' expressions.
bool HasErrors() const
Check if the last evaluation had errors.
wxString GetErrorSummary() const
Get detailed error information from the last evaluation.
void SetVariable(const wxString &aName, double aValue)
Set a numeric variable for use in expressions.
static wxString GetCurrentHash(const wxString &aProjectFile, bool aShort)
Return the current HEAD commit hash for the repository containing aProjectFile.
RAII helper that sets a process-level environment variable for the lifetime of the object.
void ClearValue()
Clear the environment variable for the lifetime of this object.
wxString PathStr() const
Get the path to the temporary directory as a wxString.
Definition file_utils.h:64
Install a real backend for a test and restore the previous backend afterwards.
Make an environment current on this thread for the lifetime of the scope.
A text evaluation frame with one frozen clock and memoized external queries.
static ENVIRONMENT * Current()
Record every source read through aEnvironment into aValues, including memo hits.
RAII helper that sets the VCS context path on construction and restores the previous value on destruc...
static wxString GetCurrentTimeLocale()
static wxString GetCurrentTimeHHMMSS()
static wxString GetCurrentDate()
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject, bool aAllowEscapes)
Replace any environment variable & text variable references with their values.
Definition common.cpp:777
std::string source
std::unique_ptr< git_repository, decltype([](git_repository *aRepo) { git_repository_free(aRepo); })> GitRepositoryPtr
A unique pointer for git_repository objects with automatic cleanup.
std::unique_ptr< git_index, decltype([](git_index *aIndex) { git_index_free(aIndex); })> GitIndexPtr
A unique pointer for git_index objects with automatic cleanup.
git_oid CommitGitIndex(git_repository *aRepo, const wxString &aMessage)
Commit the current index, using HEAD as the parent when it exists.
KIGIT::GitRepositoryPtr InitGitRepository(const wxString &aPath, const char *aBranch="main", const char *aAuthorName="QA Test", const char *aAuthorEmail="[email protected]")
Pin the branch and author so repository setup does not depend on the user's Git config.
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
wxString GetContextPath()
Return the current context path for repo-scoped VCS queries.
bool GetContextIsFile()
File/directory classification captured when the current context was activated.
TEXT_EVAL::ENVIRONMENT::VCS_VALUE ReadSource(const TEXT_EVAL::ENVIRONMENT::VCS_KEY &aKey)
Re-read an owned query descriptor using any active frame memo.
RESULT< T > MakeError(std::string aMsg)
RESULT< T > MakeValue(T aVal)
std::map< VCS_KEY, VCS_VALUE > vcsValues
std::map< wxString, std::optional< wxString > > environmentVariables
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
VECTOR3I expected(15, 30, 45)
VECTOR2I end
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
int actual
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(VcsNativeProjectContextPaths)
Declare the test suite.