KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_expand_text_vars.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
20
21#define BOOST_TEST_NO_MAIN
22#include <boost/test/unit_test.hpp>
23#include <atomic>
24#include <filesystem>
25#include <optional>
26#include <thread>
27#include <vector>
28#include <common.h>
29#include <env_paths.h>
30#include <env_vars.h>
32#include <pgm_base.h>
34#include <title_block.h>
35#include <wx/filename.h>
36#include <wx/utils.h>
37
42{
43 // Simple resolver that maps VAR->value, X->5, Y->2
44 std::function<bool( wxString* )> resolver =
45 []( wxString* token ) -> bool
46 {
47 if( *token == wxT( "VAR" ) )
48 {
49 *token = wxT( "value" );
50 return true;
51 }
52 else if( *token == wxT( "X" ) )
53 {
54 *token = wxT( "5" );
55 return true;
56 }
57 else if( *token == wxT( "Y" ) )
58 {
59 *token = wxT( "2" );
60 return true;
61 }
62
63 return false;
64 };
65};
66
67BOOST_FIXTURE_TEST_SUITE( ExpandTextVarsTests, ExpandTextVarsFixture )
68
69// Basic variable expansion
70BOOST_AUTO_TEST_CASE( SimpleVariable )
71{
72 wxString result = ExpandTextVars( wxT( "${VAR}" ), &resolver );
73 BOOST_CHECK( result == wxT( "value" ) );
74}
75
76// Multiple variables in one string
77BOOST_AUTO_TEST_CASE( MultipleVariables )
78{
79 wxString result = ExpandTextVars( wxT( "${X}+${Y}" ), &resolver );
80 BOOST_CHECK( result == wxT( "5+2" ) );
81}
82
83// Escaped variable should produce escape marker (not expanded)
84BOOST_AUTO_TEST_CASE( EscapedVariable )
85{
86 wxString result = ExpandTextVars( wxT( "\\${VAR}" ), &resolver );
87 // The escape marker should be in the output
88 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
89}
90
91// Escaped variable followed by regular variable - both should be processed correctly
92BOOST_AUTO_TEST_CASE( EscapedThenRegularVariable )
93{
94 wxString result = ExpandTextVars( wxT( "\\${literal}${VAR}" ), &resolver );
95 // Should have escape marker for literal, and "value" for VAR
96 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
97 BOOST_CHECK( result.Contains( wxT( "value" ) ) );
98}
99
100// Regular variable followed by escaped variable
101BOOST_AUTO_TEST_CASE( RegularThenEscapedVariable )
102{
103 wxString result = ExpandTextVars( wxT( "${VAR}\\${literal}" ), &resolver );
104 // Should have "value" for VAR and escape marker for literal
105 BOOST_CHECK( result.StartsWith( wxT( "value" ) ) );
106 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
107}
108
109// Issue 22497: Escaped variable inside math expression should not prevent other expansions
110// This is the key test case for the bug fix
111BOOST_AUTO_TEST_CASE( EscapedInsideMathExpression )
112{
113 // First pass: @{\${X}+${Y}} should become @{<<<ESC_DOLLAR:X}+2}
114 // Second pass: the marker should be preserved and +2 should NOT be lost
115 wxString result = ExpandTextVars( wxT( "@{\\${X}+${Y}}" ), &resolver );
116
117 // The result should contain the escape marker
118 BOOST_CHECK_MESSAGE( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ),
119 "Expected escape marker in result" );
120
121 // The result should also contain +2 (the expanded Y variable)
122 BOOST_CHECK_MESSAGE( result.Contains( wxT( "+2" ) ),
123 "Expected '+2' (from ${Y} expansion) in result" );
124
125 // The result should be @{<<<ESC_DOLLAR:X}+2}
126 BOOST_CHECK( result == wxT( "@{<<<ESC_DOLLAR:X}+2}" ) );
127}
128
129// Nested escaped variable in regular variable reference
130BOOST_AUTO_TEST_CASE( EscapedInsideVariableReference )
131{
132 // ${prefix\${suffix}} - looking up variable with literal ${suffix} in name
133 // This should try to resolve "prefix\${suffix}" which won't resolve,
134 // but the recursive expansion should convert \${suffix} to the marker
135 wxString result = ExpandTextVars( wxT( "${prefix\\${suffix}}" ), &resolver );
136
137 // The unresolved reference should be preserved with escape marker
138 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
139}
140
141// Multiple escape markers in a math expression
142BOOST_AUTO_TEST_CASE( MultipleEscapedInMathExpression )
143{
144 wxString result = ExpandTextVars( wxT( "@{\\${A}+\\${B}+${Y}}" ), &resolver );
145
146 // Should have two escape markers and the expanded Y (2)
147 BOOST_CHECK_MESSAGE( result.Contains( wxT( "+2" ) ),
148 "Expected '+2' (from ${Y} expansion) in: " + result );
149
150 // Count escape markers (should be 2)
151 int dollarCount = 0;
152 size_t pos = 0;
153
154 while( ( pos = result.find( wxT( "<<<ESC_DOLLAR:" ), pos ) ) != wxString::npos )
155 {
156 dollarCount++;
157 pos += 14;
158 }
159
160 BOOST_CHECK_EQUAL( dollarCount, 2 );
161}
162
163// Math expression with escaped @ sign
164BOOST_AUTO_TEST_CASE( EscapedAtInExpression )
165{
166 wxString result = ExpandTextVars( wxT( "${VAR}\\@{literal}" ), &resolver );
167
168 // Should have "value" for VAR and escape marker for @{literal}
169 BOOST_CHECK( result.StartsWith( wxT( "value" ) ) );
170 BOOST_CHECK( result.Contains( wxT( "<<<ESC_AT:" ) ) );
171}
172
173// Escaped followed by escaped (both should be preserved)
174BOOST_AUTO_TEST_CASE( ConsecutiveEscaped )
175{
176 wxString result = ExpandTextVars( wxT( "\\${A}\\${B}" ), &resolver );
177
178 // Should have two escape markers
179 int dollarCount = 0;
180 size_t pos = 0;
181
182 while( ( pos = result.find( wxT( "<<<ESC_DOLLAR:" ), pos ) ) != wxString::npos )
183 {
184 dollarCount++;
185 pos += 14;
186 }
187
188 BOOST_CHECK_EQUAL( dollarCount, 2 );
189}
190
191// Issue 23599: backslash path separator before text variable should NOT be treated as an escape.
192// This test documents the ExpandTextVars behavior: \${ IS treated as an escape at this level.
193// The fix is applied at call sites that deal with file paths, which normalize backslashes to
194// forward slashes before calling ExpandTextVars.
195BOOST_AUTO_TEST_CASE( BackslashBeforeVariableIsEscape )
196{
197 wxString result = ExpandTextVars( wxT( "subdir\\${VAR}_file.txt" ), &resolver );
198
199 // ExpandTextVars treats \${ as an escape, so VAR is NOT expanded
200 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
201}
202
203
204// With forward slashes the variable is expanded normally
205BOOST_AUTO_TEST_CASE( ForwardSlashBeforeVariableExpands )
206{
207 wxString result = ExpandTextVars( wxT( "subdir/${VAR}_file.txt" ), &resolver );
208
209 BOOST_CHECK( result == wxT( "subdir/value_file.txt" ) );
210}
211
213
214
215// Issue 24776: user-entered file paths with a text variable immediately after a backslash
216// separator (e.g. the Symbol Fields Table BOM export path "Output\BoM\${PROJECTNAME}.csv")
217// must expand. Callers route the path through NormalizeFilePathForTextVars before ExpandTextVars.
218BOOST_FIXTURE_TEST_SUITE( NormalizeFilePathForTextVarsTests, ExpandTextVarsFixture )
219
220BOOST_AUTO_TEST_CASE( BackslashSeparatorBeforeVarExpands )
221{
222 wxString path = NormalizeFilePathForTextVars( wxT( "Output\\BoM\\${VAR}_file.csv" ) );
223 wxString result = ExpandTextVars( path, &resolver );
224
225 // Only the backslash immediately before the variable is rewritten to a separator; the
226 // variable expands and the earlier literal backslash is preserved.
227 BOOST_CHECK_MESSAGE( !result.Contains( wxT( "<<<ESC_DOLLAR:" ) ),
228 "Variable after backslash separator should expand, not escape. Got: " + result );
229 BOOST_CHECK( result == wxT( "Output\\BoM/value_file.csv" ) );
230}
231
232
233BOOST_AUTO_TEST_CASE( MultipleVarsAfterBackslashSeparators )
234{
235 wxString path = NormalizeFilePathForTextVars( wxT( "Output\\BoM\\${X}_V${Y}.csv" ) );
236 wxString result = ExpandTextVars( path, &resolver );
237
238 BOOST_CHECK( result == wxT( "Output\\BoM/5_V2.csv" ) );
239}
240
241
242BOOST_AUTO_TEST_CASE( ForwardSlashPathUnchanged )
243{
244 wxString path = NormalizeFilePathForTextVars( wxT( "Output/BoM/${VAR}.csv" ) );
245 wxString result = ExpandTextVars( path, &resolver );
246
247 BOOST_CHECK( result == wxT( "Output/BoM/value.csv" ) );
248}
249
250
251// Backslashes that do not immediately precede a text variable are a legitimate part of the
252// filename (notably on POSIX) and must survive normalization unchanged.
253BOOST_AUTO_TEST_CASE( NonVariableBackslashesArePreserved )
254{
255 wxString path = NormalizeFilePathForTextVars( wxT( "Output\\BoM\\literal.csv" ) );
256
257 BOOST_CHECK( path == wxT( "Output\\BoM\\literal.csv" ) );
258}
259
261
262
263// Issue 23599: JOB::ResolveOutputPath must expand text variables even when preceded by backslash
264// path separators. This suite tests the fix in ResolveOutputPath that normalizes backslashes
265// before calling ExpandTextVars.
266BOOST_AUTO_TEST_SUITE( JobResolveOutputPath )
267
268BOOST_AUTO_TEST_CASE( BackslashPathSeparatorBeforeTextVar )
269{
271
272 TITLE_BLOCK titleBlock;
273 titleBlock.SetRevision( wxT( "RevA" ) );
274 job.SetTitleBlock( titleBlock );
275
276 // Simulates Windows path with text variable immediately after backslash separator
277 wxString path = wxT( "Board Stats\\${REVISION}_Stats.txt" );
278 wxString result = job.ResolveOutputPath( path, false, nullptr );
279
280 // The variable should be expanded, not escaped
281 BOOST_CHECK_MESSAGE( !result.Contains( wxT( "<<<ESC_DOLLAR:" ) ),
282 "Text variable after backslash path separator should not be escaped. Got: "
283 + result );
284 BOOST_CHECK_MESSAGE( result.Contains( wxT( "RevA" ) ),
285 "Expected resolved REVISION in path. Got: " + result );
286}
287
288
289BOOST_AUTO_TEST_CASE( BackslashPathSeparatorBeforeMultipleTextVars )
290{
292
293 TITLE_BLOCK titleBlock;
294 titleBlock.SetRevision( wxT( "B" ) );
295 titleBlock.SetComment( 0, wxT( "DWG-001" ) );
296 job.SetTitleBlock( titleBlock );
297
298 // The COMMENT1 variable maps to Comment(0) in TITLE_BLOCK
299 wxString path = wxT( "Output\\${COMMENT1}_${REVISION}_file.txt" );
300 wxString result = job.ResolveOutputPath( path, false, nullptr );
301
302 BOOST_CHECK_MESSAGE( result.Contains( wxT( "DWG-001" ) ),
303 "Expected COMMENT1 expanded in path. Got: " + result );
304 BOOST_CHECK_MESSAGE( result.Contains( wxT( "_B_" ) ),
305 "Expected REVISION expanded in path. Got: " + result );
306}
307
308
309BOOST_AUTO_TEST_CASE( TextVarNotFirstInFilename )
310{
312
313 TITLE_BLOCK titleBlock;
314 titleBlock.SetRevision( wxT( "C" ) );
315 job.SetTitleBlock( titleBlock );
316
317 // When there's literal text between the backslash and the variable, it always worked
318 wxString path = wxT( "Output\\Generated_${REVISION}_file.txt" );
319 wxString result = job.ResolveOutputPath( path, false, nullptr );
320
321 BOOST_CHECK_MESSAGE( result.Contains( wxT( "Generated_C_file.txt" ) ),
322 "Expected variable expansion with preceding literal text. Got: " + result );
323}
324
326
327
328
342{
343 wxString rootDir;
344 wxString outerDir;
345 wxString innerDir;
346 wxString targetDir;
347
349 {
350 std::filesystem::path tmp = std::filesystem::temp_directory_path() /
351 "kicad_qa_overlap_env_vars";
352 std::error_code ec;
353 std::filesystem::remove_all( tmp, ec );
354 std::filesystem::create_directories( tmp / "V10" / "symbols", ec );
355
356 rootDir = wxString::FromUTF8( tmp.string() );
358 innerDir = wxString::FromUTF8( ( tmp / "V10" ).string() );
359 targetDir = wxString::FromUTF8( ( tmp / "V10" / "symbols" ).string() );
360
361 wxSetEnv( wxS( "KICAD_QA_3RD_PARTY_OUTER" ), outerDir );
362 wxSetEnv( wxS( "KICAD_QA_USER_LIB_INNER" ), innerDir );
363 }
364
366 {
367 wxUnsetEnv( wxS( "KICAD_QA_3RD_PARTY_OUTER" ) );
368 wxUnsetEnv( wxS( "KICAD_QA_USER_LIB_INNER" ) );
369
370 std::filesystem::path tmp = std::filesystem::temp_directory_path() /
371 "kicad_qa_overlap_env_vars";
372 std::error_code ec;
373 std::filesystem::remove_all( tmp, ec );
374 }
375
377 {
378 ENV_VAR_MAP map;
379 map[wxS( "KICAD_QA_3RD_PARTY_OUTER" )] = ENV_VAR_ITEM( outerDir );
380 map[wxS( "KICAD_QA_USER_LIB_INNER" )] = ENV_VAR_ITEM( innerDir );
381 return map;
382 }
383};
384
385
386BOOST_FIXTURE_TEST_SUITE( OverlappingEnvVarPaths, OverlappingEnvVarsFixture )
387
388
389BOOST_AUTO_TEST_CASE( NormalizePicksLongestPrefix )
390{
391 wxFileName target( targetDir, wxS( "test.kicad_sym" ) );
392 ENV_VAR_MAP envMap = BuildEnvMap();
393
394 wxString normalized = NormalizePath( target, &envMap, wxEmptyString );
395
396 // NormalizePath should pick KICAD_QA_USER_LIB_INNER because it is a deeper match.
397 BOOST_CHECK_MESSAGE(
398 normalized == wxS( "${KICAD_QA_USER_LIB_INNER}/symbols/test.kicad_sym" ),
399 wxString::Format( wxS( "Expected '%s' but got '%s'" ),
400 wxS( "${KICAD_QA_USER_LIB_INNER}/symbols/test.kicad_sym" ),
401 normalized ) );
402}
403
404
405BOOST_AUTO_TEST_CASE( RoundTripPreservesAbsolutePath )
406{
407 wxFileName target( targetDir, wxS( "test.kicad_sym" ) );
408 ENV_VAR_MAP envMap = BuildEnvMap();
409
410 wxString normalized = NormalizePath( target, &envMap, wxEmptyString );
411 wxString expanded = ExpandEnvVarSubstitutions( normalized, nullptr );
412
413 wxFileName expandedFn( expanded );
414 expandedFn.Normalize( wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE );
415
416 wxFileName originalFn( target );
417 originalFn.Normalize( wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE );
418
419 BOOST_CHECK_MESSAGE(
420 expandedFn.GetFullPath() == originalFn.GetFullPath(),
421 wxString::Format(
422 wxS( "Round-trip mismatch: normalized='%s' expanded='%s' original='%s'" ),
423 normalized, expandedFn.GetFullPath(), originalFn.GetFullPath() ) );
424}
425
426
428
429
430
442BOOST_AUTO_TEST_SUITE( TextVarExpressionEvaluatorConcurrency )
443
444BOOST_AUTO_TEST_CASE( ParallelResolveTextVarsWithMathExpressions )
445{
446 std::function<bool( wxString* )> resolver =
447 []( wxString* token ) -> bool
448 {
449 if( *token == wxT( "#" ) )
450 {
451 *token = wxT( "3" );
452 return true;
453 }
454
455 if( *token == wxT( "ROW" ) )
456 {
457 *token = wxT( "4" );
458 return true;
459 }
460
461 return false;
462 };
463
464 const std::vector<wxString> inputs = {
465 wxT( "Out@{(${#}-2)*8+0}" ),
466 wxT( "Net_@{${ROW}*2+1}" ),
467 wxT( "@{(2-2)*8+0}" ),
468 wxT( "${ROW}:@{${ROW}*${ROW}}" ),
469 wxT( "plain_label_no_expr" ),
470 wxT( "@{1+1}_@{2+2}_@{3+3}" ),
471 };
472
473 const unsigned int numThreads = std::max( 4u, std::thread::hardware_concurrency() );
474 const int iterations = 2000;
475
476 std::atomic<bool> failed{ false };
477 std::atomic<int> totalRuns{ 0 };
478 std::vector<std::thread> threads;
479 threads.reserve( numThreads );
480
481 for( unsigned int t = 0; t < numThreads; ++t )
482 {
483 threads.emplace_back(
484 [&, t]()
485 {
486 try
487 {
488 for( int i = 0; i < iterations; ++i )
489 {
490 const wxString& src = inputs[( t + i ) % inputs.size()];
491 int depth = 0;
492 wxString result = ResolveTextVars( src, &resolver, depth );
493 (void) result;
494 totalRuns.fetch_add( 1, std::memory_order_relaxed );
495 }
496 }
497 catch( ... )
498 {
499 failed.store( true, std::memory_order_relaxed );
500 }
501 } );
502 }
503
504 for( auto& th : threads )
505 th.join();
506
507 BOOST_CHECK( !failed.load() );
508 BOOST_CHECK_EQUAL( totalRuns.load(), static_cast<int>( numThreads ) * iterations );
509}
510
512
513
514
524{
525 wxString innerPath;
526 std::optional<wxString> oldInner;
527 std::optional<wxString> oldOuter;
528
530 {
531 wxString existing;
532
533 if( wxGetEnv( wxS( "KICAD_QA_INNER" ), &existing ) )
534 oldInner = existing;
535
536 if( wxGetEnv( wxS( "KICAD_QA_OUTER" ), &existing ) )
537 oldOuter = existing;
538
539 innerPath = wxString::FromUTF8(
540 ( std::filesystem::temp_directory_path() / "kicad-qa-24244" ).generic_string() );
541
542 wxSetEnv( wxS( "KICAD_QA_INNER" ), innerPath );
543 wxSetEnv( wxS( "KICAD_QA_OUTER" ), wxS( "${KICAD_QA_INNER}/templates" ) );
544 }
545
547 {
548 if( oldInner )
549 wxSetEnv( wxS( "KICAD_QA_INNER" ), *oldInner );
550 else
551 wxUnsetEnv( wxS( "KICAD_QA_INNER" ) );
552
553 if( oldOuter )
554 wxSetEnv( wxS( "KICAD_QA_OUTER" ), *oldOuter );
555 else
556 wxUnsetEnv( wxS( "KICAD_QA_OUTER" ) );
557 }
558};
559
560BOOST_FIXTURE_TEST_SUITE( EnvVarRecursiveExpansion, EnvVarRecursiveExpansionFixture )
561
562BOOST_AUTO_TEST_CASE( ExpandsNestedReferences )
563{
564 wxString rawValue;
565 BOOST_REQUIRE( wxGetEnv( wxS( "KICAD_QA_OUTER" ), &rawValue ) );
566
567 // The raw value should still contain the unexpanded reference.
568 BOOST_CHECK( rawValue.Contains( wxS( "${KICAD_QA_INNER}" ) ) );
569
570 wxString expanded = ExpandEnvVarSubstitutions( rawValue, nullptr );
571 wxString expected = innerPath + wxS( "/templates" );
572
573 // After expansion the inner reference must be resolved to its concrete path.
574 BOOST_CHECK_MESSAGE( expanded == expected,
575 wxString::Format( wxS( "Expected '%s', got '%s'" ), expected, expanded ) );
576}
577
578
579BOOST_AUTO_TEST_CASE( UndefinedReferenceLeavesLiteralMarker )
580{
581 // If a referenced variable is undefined, ExpandEnvVarSubstitutions preserves the
582 // original token. Callers that then mkdir the result would create a literal
583 // "${MISSING}" directory; production code must detect this and bail out.
584 wxUnsetEnv( wxS( "KICAD_QA_INNER" ) );
585
586 wxString rawValue;
587 BOOST_REQUIRE( wxGetEnv( wxS( "KICAD_QA_OUTER" ), &rawValue ) );
588
589 wxString expanded = ExpandEnvVarSubstitutions( rawValue, nullptr );
590 BOOST_CHECK( expanded.Contains( wxS( "${" ) ) );
591
592 // Restore so the fixture destructor sees a known state.
593 wxSetEnv( wxS( "KICAD_QA_INNER" ), innerPath );
594}
595
597
598
599
610{
612 wxString stockDir;
613 std::optional<wxString> oldVersioned;
614 std::optional<wxString> oldUser;
615 std::optional<wxString> oldLegacy;
616
618 {
619 versionedName = ENV_VAR::GetVersionedEnvVarName( wxS( "FOOTPRINT_DIR" ) );
620 stockDir = wxString::FromUTF8(
621 ( std::filesystem::temp_directory_path() / "kicad-qa-24460-stock.pretty" ).generic_string() );
622
623 wxString existing;
624
625 if( wxGetEnv( versionedName, &existing ) )
626 oldVersioned = existing;
627
628 if( wxGetEnv( wxS( "KICAD_USER_FOOTPRINT_DIR" ), &existing ) )
629 oldUser = existing;
630
631 if( wxGetEnv( wxS( "KICAD5_FOOTPRINT_DIR" ), &existing ) )
632 oldLegacy = existing;
633
634 // The current install advertises a stock footprint directory; a stale user var and an
635 // older versioned var are both absent.
636 wxSetEnv( versionedName, stockDir );
637 wxUnsetEnv( wxS( "KICAD_USER_FOOTPRINT_DIR" ) );
638 wxUnsetEnv( wxS( "KICAD5_FOOTPRINT_DIR" ) );
639 }
640
642 {
643 auto restore = [&]( const wxString& aName, const std::optional<wxString>& aOld )
644 {
645 if( aOld )
646 wxSetEnv( aName, *aOld );
647 else
648 wxUnsetEnv( aName );
649 };
650
651 restore( versionedName, oldVersioned );
652 restore( wxS( "KICAD_USER_FOOTPRINT_DIR" ), oldUser );
653 restore( wxS( "KICAD5_FOOTPRINT_DIR" ), oldLegacy );
654 }
655};
656
657BOOST_FIXTURE_TEST_SUITE( VersionedEnvVarFallback, VersionedEnvVarFallbackFixture )
658
659BOOST_AUTO_TEST_CASE( UserVarIsNotTreatedAsVersionedLibraryDir )
660{
661 const wxString uri = wxS( "${KICAD_USER_FOOTPRINT_DIR}/conn_custom.pretty" );
662
663 wxString expanded = ExpandEnvVarSubstitutions( uri, nullptr );
664
665 // An unresolved user var must stay literal, never the stock library directory.
666 BOOST_CHECK_EQUAL( expanded, uri );
667 BOOST_CHECK( !expanded.Contains( stockDir ) );
668}
669
670
671BOOST_AUTO_TEST_CASE( LegacyVersionedVarStillResolvesToCurrentDir )
672{
673 BOOST_REQUIRE( wxS( "KICAD5_FOOTPRINT_DIR" ) != versionedName );
674
675 wxString expanded =
676 ExpandEnvVarSubstitutions( wxS( "${KICAD5_FOOTPRINT_DIR}/conn_custom.pretty" ), nullptr );
677
678 BOOST_CHECK_EQUAL( expanded, stockDir + wxS( "/conn_custom.pretty" ) );
679}
680
681
682BOOST_AUTO_TEST_CASE( DeprecatedUnversionedAliasStillResolves )
683{
684 // KICAD_SYMBOL_DIR is a documented deprecated alias for the versioned symbol dir; it must
685 // still fall back to the current install even though it carries no version digits.
686 wxString symbolName = ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) );
687 std::optional<wxString> oldSymbol;
688 std::optional<wxString> oldAlias;
689 wxString existing;
690
691 if( wxGetEnv( symbolName, &existing ) )
692 oldSymbol = existing;
693
694 if( wxGetEnv( wxS( "KICAD_SYMBOL_DIR" ), &existing ) )
695 oldAlias = existing;
696
697 wxSetEnv( symbolName, stockDir );
698 wxUnsetEnv( wxS( "KICAD_SYMBOL_DIR" ) );
699
700 wxString expanded =
701 ExpandEnvVarSubstitutions( wxS( "${KICAD_SYMBOL_DIR}/Device.kicad_sym" ), nullptr );
702
703 BOOST_CHECK_EQUAL( expanded, stockDir + wxS( "/Device.kicad_sym" ) );
704
705 if( oldSymbol )
706 wxSetEnv( symbolName, *oldSymbol );
707 else
708 wxUnsetEnv( symbolName );
709
710 if( oldAlias )
711 wxSetEnv( wxS( "KICAD_SYMBOL_DIR" ), *oldAlias );
712 else
713 wxUnsetEnv( wxS( "KICAD_SYMBOL_DIR" ) );
714}
715
716
717BOOST_AUTO_TEST_CASE( IsVersionedEnvVarPredicate )
718{
719 BOOST_CHECK( ENV_VAR::IsVersionedEnvVar( wxS( "KICAD7_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
720 BOOST_CHECK( ENV_VAR::IsVersionedEnvVar( wxS( "KICAD10_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
721
722 BOOST_CHECK( !ENV_VAR::IsVersionedEnvVar( wxS( "KICAD_USER_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
723 BOOST_CHECK( !ENV_VAR::IsVersionedEnvVar( wxS( "KICAD_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
724 BOOST_CHECK( !ENV_VAR::IsVersionedEnvVar( wxS( "KICAD7_SYMBOL_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
725}
726
KiCad uses environment variables internally for determining the base paths for libraries,...
wxString ResolveOutputPath(const wxString &aPath, bool aPathIsDirectory, PROJECT *aProject) const
Definition job.cpp:100
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition job.h:204
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:37
void SetRevision(const wxString &aRevision)
Definition title_block.h:77
void SetComment(int aIdx, const wxString &aComment)
Definition title_block.h:97
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:721
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition common.cpp:59
wxString NormalizeFilePathForTextVars(const wxString &aPath)
Normalize a file path so its text variables survive ExpandTextVars.
Definition common.cpp:70
wxString ResolveTextVars(const wxString &aSource, const std::function< bool(wxString *)> *aResolver, int &aDepth)
Multi-pass text variable expansion and math expression evaluation.
Definition common.cpp:310
The common library.
wxString NormalizePath(const wxFileName &aFilePath, const ENV_VAR_MAP *aEnvVars, const wxString &aProjectPath)
Normalize a file path to an environmental variable, if possible.
Definition env_paths.cpp:73
Helper functions to substitute paths with environmental variables.
Functions related to environment variables, including help functions.
static FILENAME_RESOLVER * resolver
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
KICOMMON_API bool IsVersionedEnvVar(const wxString &aName, const wxString &aBaseName)
Test whether a name is a versioned KiCad environment variable for the given base, i....
Definition env_vars.cpp:87
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:78
see class PGM_BASE
Regression test for KiCad GitLab issue #24244.
Test fixture for ExpandTextVars tests.
std::function< bool(wxString *)> resolver
Regression tests for overlapping-prefix environment variables.
Regression test for KiCad GitLab issue #24460.
std::optional< wxString > oldVersioned
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(SimpleVariable)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
VECTOR3I expected(15, 30, 45)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")