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 <qa_utils/file_utils.h>
25
26#include <atomic>
27#include <optional>
28#include <thread>
29#include <vector>
30
31#include <wx/filename.h>
32#include <wx/utils.h>
33
34#include <common.h>
35#include <env_paths.h>
36#include <env_vars.h>
38#include <pgm_base.h>
39#include <project.h>
41#include <title_block.h>
42
47{
48 // Simple resolver that maps VAR->value, X->5, Y->2
49 std::function<bool( wxString* )> resolver =
50 []( wxString* token ) -> bool
51 {
52 if( *token == wxT( "VAR" ) )
53 {
54 *token = wxT( "value" );
55 return true;
56 }
57 else if( *token == wxT( "X" ) )
58 {
59 *token = wxT( "5" );
60 return true;
61 }
62 else if( *token == wxT( "Y" ) )
63 {
64 *token = wxT( "2" );
65 return true;
66 }
67
68 return false;
69 };
70};
71
72BOOST_FIXTURE_TEST_SUITE( ExpandTextVarsTests, ExpandTextVarsFixture )
73
74BOOST_AUTO_TEST_CASE( AssertionsRemainAvailableOnlyDuringRuleChecks )
75{
76 for( const wxString& prefix : { wxString( "ERC_WARNING" ), wxString( "ERC_ERROR" ),
77 wxString( "DRC_WARNING" ), wxString( "DRC_ERROR" ) } )
78 {
79 const wxString assertion = "${" + prefix + " Check this item}";
80 BOOST_CHECK_EQUAL( ExpandTextVars( assertion, &resolver, FOR_ERC_DRC ), assertion );
81 BOOST_CHECK( ExpandTextVars( assertion, &resolver, FOR_CANVAS ).empty() );
82 BOOST_CHECK_EQUAL( ExpandTextVars( assertion, &resolver, RAW_VALUE ), assertion );
83 }
84}
85
86
87// Basic variable expansion
88BOOST_AUTO_TEST_CASE( SimpleVariable )
89{
90 wxString result = ExpandTextVars( wxT( "${VAR}" ), &resolver, INTERNAL );
91 BOOST_CHECK( result == wxT( "value" ) );
92}
93
94// Multiple variables in one string
95BOOST_AUTO_TEST_CASE( MultipleVariables )
96{
97 wxString result = ExpandTextVars( wxT( "${X}+${Y}" ), &resolver, INTERNAL );
98 BOOST_CHECK( result == wxT( "5+2" ) );
99}
100
101// Escaped variable should produce escape marker (not expanded)
102BOOST_AUTO_TEST_CASE( EscapedVariable )
103{
104 wxString result = ExpandTextVars( wxT( "\\${VAR}" ), &resolver, INTERNAL );
105 // The escape marker should be in the output
106 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
107}
108
109// Escaped variable followed by regular variable - both should be processed correctly
110BOOST_AUTO_TEST_CASE( EscapedThenRegularVariable )
111{
112 wxString result = ExpandTextVars( wxT( "\\${literal}${VAR}" ), &resolver, INTERNAL );
113 // Should have escape marker for literal, and "value" for VAR
114 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
115 BOOST_CHECK( result.Contains( wxT( "value" ) ) );
116}
117
118// Regular variable followed by escaped variable
119BOOST_AUTO_TEST_CASE( RegularThenEscapedVariable )
120{
121 wxString result = ExpandTextVars( wxT( "${VAR}\\${literal}" ), &resolver, INTERNAL );
122 // Should have "value" for VAR and escape marker for literal
123 BOOST_CHECK( result.StartsWith( wxT( "value" ) ) );
124 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
125}
126
127// Issue 22497: Escaped variable inside math expression should not prevent other expansions
128// This is the key test case for the bug fix
129BOOST_AUTO_TEST_CASE( EscapedInsideMathExpression )
130{
131 // First pass: @{\${X}+${Y}} should become @{<<<ESC_DOLLAR:X}+2}
132 // Second pass: the marker should be preserved and +2 should NOT be lost
133 wxString result = ExpandTextVars( wxT( "@{\\${X}+${Y}}" ), &resolver, INTERNAL );
134
135 // The result should contain the escape marker
136 BOOST_CHECK_MESSAGE( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ),
137 "Expected escape marker in result" );
138
139 // The result should also contain +2 (the expanded Y variable)
140 BOOST_CHECK_MESSAGE( result.Contains( wxT( "+2" ) ),
141 "Expected '+2' (from ${Y} expansion) in result" );
142
143 // The result should be @{<<<ESC_DOLLAR:X}+2}
144 BOOST_CHECK( result == wxT( "@{<<<ESC_DOLLAR:X}+2}" ) );
145}
146
147// Nested escaped variable in regular variable reference
148BOOST_AUTO_TEST_CASE( EscapedInsideVariableReference )
149{
150 // ${prefix\${suffix}} - looking up variable with literal ${suffix} in name
151 // This should try to resolve "prefix\${suffix}" which won't resolve,
152 // but the recursive expansion should convert \${suffix} to the marker
153 wxString result = ExpandTextVars( wxT( "${prefix\\${suffix}}" ), &resolver, INTERNAL );
154
155 // The unresolved reference should be preserved with escape marker
156 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
157}
158
159// Multiple escape markers in a math expression
160BOOST_AUTO_TEST_CASE( MultipleEscapedInMathExpression )
161{
162 wxString result = ExpandTextVars( wxT( "@{\\${A}+\\${B}+${Y}}" ), &resolver, INTERNAL );
163
164 // Should have two escape markers and the expanded Y (2)
165 BOOST_CHECK_MESSAGE( result.Contains( wxT( "+2" ) ),
166 "Expected '+2' (from ${Y} expansion) in: " + result );
167
168 // Count escape markers (should be 2)
169 int dollarCount = 0;
170 size_t pos = 0;
171
172 while( ( pos = result.find( wxT( "<<<ESC_DOLLAR:" ), pos ) ) != wxString::npos )
173 {
174 dollarCount++;
175 pos += 14;
176 }
177
178 BOOST_CHECK_EQUAL( dollarCount, 2 );
179}
180
181// Math expression with escaped @ sign
182BOOST_AUTO_TEST_CASE( EscapedAtInExpression )
183{
184 wxString result = ExpandTextVars( wxT( "${VAR}\\@{literal}" ), &resolver, INTERNAL );
185
186 // Should have "value" for VAR and escape marker for @{literal}
187 BOOST_CHECK( result.StartsWith( wxT( "value" ) ) );
188 BOOST_CHECK( result.Contains( wxT( "<<<ESC_AT:" ) ) );
189}
190
191// Escaped followed by escaped (both should be preserved)
192BOOST_AUTO_TEST_CASE( ConsecutiveEscaped )
193{
194 wxString result = ExpandTextVars( wxT( "\\${A}\\${B}" ), &resolver, INTERNAL );
195
196 // Should have two escape markers
197 int dollarCount = 0;
198 size_t pos = 0;
199
200 while( ( pos = result.find( wxT( "<<<ESC_DOLLAR:" ), pos ) ) != wxString::npos )
201 {
202 dollarCount++;
203 pos += 14;
204 }
205
206 BOOST_CHECK_EQUAL( dollarCount, 2 );
207}
208
209// Issue 23599: backslash path separator before text variable should NOT be treated as an escape.
210// This test documents the ExpandTextVars behavior: \${ IS treated as an escape at this level.
211// The fix is applied at call sites that deal with file paths, which normalize backslashes to
212// forward slashes before calling ExpandTextVars.
213BOOST_AUTO_TEST_CASE( BackslashBeforeVariableIsEscape )
214{
215 wxString result = ExpandTextVars( wxT( "subdir\\${VAR}_file.txt" ), &resolver, INTERNAL );
216
217 // ExpandTextVars treats \${ as an escape, so VAR is NOT expanded
218 BOOST_CHECK( result.Contains( wxT( "<<<ESC_DOLLAR:" ) ) );
219}
220
221
222// With forward slashes the variable is expanded normally
223BOOST_AUTO_TEST_CASE( ForwardSlashBeforeVariableExpands )
224{
225 wxString result = ExpandTextVars( wxT( "subdir/${VAR}_file.txt" ), &resolver, INTERNAL );
226
227 BOOST_CHECK( result == wxT( "subdir/value_file.txt" ) );
228}
229
231
232
233// Issue 24776: user-entered file paths with a text variable immediately after a backslash
234// separator (e.g. the Symbol Fields Table BOM export path "Output\BoM\${PROJECTNAME}.csv")
235// must expand. Callers route the path through NormalizeFilePathForTextVars before ExpandTextVars.
236BOOST_FIXTURE_TEST_SUITE( NormalizeFilePathForTextVarsTests, ExpandTextVarsFixture )
237
238BOOST_AUTO_TEST_CASE( BackslashSeparatorBeforeVarExpands )
239{
240 wxString path = NormalizeFilePathForTextVars( wxT( "Output\\BoM\\${VAR}_file.csv" ) );
241 wxString result = ExpandTextVars( path, &resolver, INTERNAL );
242
243 // Only the backslash immediately before the variable is rewritten to a separator; the
244 // variable expands and the earlier literal backslash is preserved.
245 BOOST_CHECK_MESSAGE( !result.Contains( wxT( "<<<ESC_DOLLAR:" ) ),
246 "Variable after backslash separator should expand, not escape. Got: " + result );
247 BOOST_CHECK( result == wxT( "Output\\BoM/value_file.csv" ) );
248}
249
250
251BOOST_AUTO_TEST_CASE( MultipleVarsAfterBackslashSeparators )
252{
253 wxString path = NormalizeFilePathForTextVars( wxT( "Output\\BoM\\${X}_V${Y}.csv" ) );
254 wxString result = ExpandTextVars( path, &resolver, INTERNAL );
255
256 BOOST_CHECK( result == wxT( "Output\\BoM/5_V2.csv" ) );
257}
258
259
260BOOST_AUTO_TEST_CASE( ForwardSlashPathUnchanged )
261{
262 wxString path = NormalizeFilePathForTextVars( wxT( "Output/BoM/${VAR}.csv" ) );
263 wxString result = ExpandTextVars( path, &resolver, INTERNAL );
264
265 BOOST_CHECK( result == wxT( "Output/BoM/value.csv" ) );
266}
267
268
269// Backslashes that do not immediately precede a text variable are a legitimate part of the
270// filename (notably on POSIX) and must survive normalization unchanged.
271BOOST_AUTO_TEST_CASE( NonVariableBackslashesArePreserved )
272{
273 wxString path = NormalizeFilePathForTextVars( wxT( "Output\\BoM\\literal.csv" ) );
274
275 BOOST_CHECK( path == wxT( "Output\\BoM\\literal.csv" ) );
276}
277
279
280
281// Issue 23599: JOB::ResolveOutputPath must expand text variables even when preceded by backslash
282// path separators. This suite tests the fix in ResolveOutputPath that normalizes backslashes
283// before calling ExpandTextVars.
284BOOST_AUTO_TEST_SUITE( JobResolveOutputPath )
285
286BOOST_AUTO_TEST_CASE( BackslashPathSeparatorBeforeTextVar )
287{
289
290 TITLE_BLOCK titleBlock;
291 titleBlock.SetRevision( wxT( "RevA" ) );
292 job.SetTitleBlock( titleBlock );
293
294 // Simulates Windows path with text variable immediately after backslash separator
295 wxString path = wxT( "Board Stats\\${REVISION}_Stats.txt" );
296 wxString result = job.ResolveOutputPath( path, false, nullptr );
297
298 // The variable should be expanded, not escaped
299 BOOST_CHECK_MESSAGE( !result.Contains( wxT( "<<<ESC_DOLLAR:" ) ),
300 "Text variable after backslash path separator should not be escaped. Got: "
301 + result );
302 BOOST_CHECK_MESSAGE( result.Contains( wxT( "RevA" ) ),
303 "Expected resolved REVISION in path. Got: " + result );
304}
305
306
307BOOST_AUTO_TEST_CASE( BackslashPathSeparatorBeforeMultipleTextVars )
308{
310
311 TITLE_BLOCK titleBlock;
312 titleBlock.SetRevision( wxT( "B" ) );
313 titleBlock.SetComment( 0, wxT( "DWG-001" ) );
314 job.SetTitleBlock( titleBlock );
315
316 // The COMMENT1 variable maps to Comment(0) in TITLE_BLOCK
317 wxString path = wxT( "Output\\${COMMENT1}_${REVISION}_file.txt" );
318 wxString result = job.ResolveOutputPath( path, false, nullptr );
319
320 BOOST_CHECK_MESSAGE( result.Contains( wxT( "DWG-001" ) ),
321 "Expected COMMENT1 expanded in path. Got: " + result );
322 BOOST_CHECK_MESSAGE( result.Contains( wxT( "_B_" ) ),
323 "Expected REVISION expanded in path. Got: " + result );
324}
325
326
327BOOST_AUTO_TEST_CASE( TextVarNotFirstInFilename )
328{
330
331 TITLE_BLOCK titleBlock;
332 titleBlock.SetRevision( wxT( "C" ) );
333 job.SetTitleBlock( titleBlock );
334
335 // When there's literal text between the backslash and the variable, it always worked
336 wxString path = wxT( "Output\\Generated_${REVISION}_file.txt" );
337 wxString result = job.ResolveOutputPath( path, false, nullptr );
338
339 BOOST_CHECK_MESSAGE( result.Contains( wxT( "Generated_C_file.txt" ) ),
340 "Expected variable expansion with preceding literal text. Got: " + result );
341}
342
343
357BOOST_AUTO_TEST_CASE( OverlappingPrefix_NormalizePicksLongestPrefix )
358{
359 // NormalizePath only considers env var paths that exist as readable directories.
360 KI_TEST::SCOPED_TEMP_DIR tempDir( wxS( "overlapping-env-vars" ) );
361
362 const wxString innerDir = tempDir.CreateChildDirStr( "V10" ); // .../V10
363 const wxFileName targetFilename( innerDir, wxS( "test.kicad_sym" ) ); // ..../V10/test.kicad_sym
364
365 KI_TEST::SCOPED_PGM_ENV_VAR scopedOuterEnvVar( wxS( "KICAD_QA_3RD_PARTY_OUTER" ), tempDir.PathStr() );
366 KI_TEST::SCOPED_PGM_ENV_VAR scopedInnerEnvVar( wxS( "KICAD_QA_USER_LIB_INNER" ), innerDir );
367
368 // ---- Setup done
369
370 // NormalizePath should pick KICAD_QA_USER_LIB_INNER because it is a deeper match.
371 const wxString normalized = NormalizePath( targetFilename, &Pgm().GetLocalEnvVariables(), wxEmptyString );
372 const wxString expected = wxS( "${KICAD_QA_USER_LIB_INNER}/test.kicad_sym" );
373
375}
376
377
379
380
381
393BOOST_AUTO_TEST_SUITE( TextVarExpressionEvaluatorConcurrency )
394
395BOOST_AUTO_TEST_CASE( ParallelResolveTextVarsWithMathExpressions )
396{
397 std::function<bool( wxString* )> resolver =
398 []( wxString* token ) -> bool
399 {
400 if( *token == wxT( "#" ) )
401 {
402 *token = wxT( "3" );
403 return true;
404 }
405
406 if( *token == wxT( "ROW" ) )
407 {
408 *token = wxT( "4" );
409 return true;
410 }
411
412 return false;
413 };
414
415 const std::vector<wxString> inputs = {
416 wxT( "Out@{(${#}-2)*8+0}" ),
417 wxT( "Net_@{${ROW}*2+1}" ),
418 wxT( "@{(2-2)*8+0}" ),
419 wxT( "${ROW}:@{${ROW}*${ROW}}" ),
420 wxT( "plain_label_no_expr" ),
421 wxT( "@{1+1}_@{2+2}_@{3+3}" ),
422 };
423
424 const unsigned int numThreads = std::max( 4u, std::thread::hardware_concurrency() );
425 const int iterations = 2000;
426
427 std::atomic<bool> failed{ false };
428 std::atomic<int> totalRuns{ 0 };
429 std::vector<std::thread> threads;
430 threads.reserve( numThreads );
431
432 for( unsigned int t = 0; t < numThreads; ++t )
433 {
434 threads.emplace_back(
435 [&, t]()
436 {
437 try
438 {
439 for( int i = 0; i < iterations; ++i )
440 {
441 const wxString& src = inputs[( t + i ) % inputs.size()];
442 int depth = 0;
443 wxString result = ResolveTextVars( src, &resolver, depth );
444 (void) result;
445 totalRuns.fetch_add( 1, std::memory_order_relaxed );
446 }
447 }
448 catch( ... )
449 {
450 failed.store( true, std::memory_order_relaxed );
451 }
452 } );
453 }
454
455 for( auto& th : threads )
456 th.join();
457
458 BOOST_CHECK( !failed.load() );
459 BOOST_CHECK_EQUAL( totalRuns.load(), static_cast<int>( numThreads ) * iterations );
460}
461
463
464
465BOOST_AUTO_TEST_SUITE( EnvVarResolution )
466
467
468BOOST_AUTO_TEST_CASE( ExpandsNestedReferences )
469{
470 const wxString innerPath = "/inner/path";
471
472 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedInnerEnvVar( wxS( "KICAD_QA_INNER" ), innerPath );
473 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedOuterEnvVar( wxS( "KICAD_QA_OUTER" ), wxS( "${KICAD_QA_INNER}/templates" ) );
474
475 wxString expanded = ExpandEnvVarSubstitutions( wxS( "${KICAD_QA_OUTER}" ), nullptr );
476 wxString expected = innerPath + wxS( "/templates" );
477
478 // After expansion the inner reference must be resolved to its concrete path.
479 BOOST_CHECK_EQUAL( expanded, expected );
480}
481
482
483BOOST_AUTO_TEST_CASE( UndefinedReferenceLeavesLiteralMarker )
484{
485 // If a referenced variable is undefined, ExpandEnvVarSubstitutions preserves the
486 // original token. Callers that then mkdir the result would create a literal
487 // "${MISSING}" directory; production code must detect this and bail out.
488
489 const wxString innerPath = "/inner/path";
490
491 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedInnerEnvVar( wxS( "KICAD_QA_INNER" ), std::nullopt );
492 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedOuterEnvVar( wxS( "KICAD_QA_OUTER" ), wxS( "${KICAD_QA_INNER}/templates" ) );
493
494 wxString expanded = ExpandEnvVarSubstitutions( wxS( "${KICAD_QA_OUTER}" ), nullptr );
495 BOOST_CHECK_EQUAL( expanded, "${KICAD_QA_INNER}/templates" );
496}
497
498
499BOOST_AUTO_TEST_CASE( UserVarIsNotTreatedAsVersionedLibraryDir )
500{
501 const wxString stockLibPath = "/some/path/to/stock-libraries";
502 const wxString versionedName = ENV_VAR::GetVersionedEnvVarName( wxS( "FOOTPRINT_DIR" ) );
503
504 // Clear the user var
505 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedUserEnvVar( wxS( "KICAD_USER_FOOTPRINT_DIR" ), std::nullopt );
506 // Set the versioned var to some specific path
507 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedVersionedEnvVar( versionedName, stockLibPath );
508
509 const wxString uri = wxS( "${KICAD_USER_FOOTPRINT_DIR}/conn_custom.pretty" );
510
511 wxString expanded = ExpandEnvVarSubstitutions( uri, nullptr );
512
513 // An unresolved user var must stay literal, never fall back to the library directory.
514 BOOST_CHECK_EQUAL( expanded, uri );
515}
516
517
518/*
519 * A project or board from an older KiCad may reference an older versioned library variable
520 * (for example ${KICAD5_FOOTPRINT_DIR} from a KiCad 5 project). If that exact variable is not
521 * set on this machine, the expander falls back to the current install's equivalent so the path
522 * still resolves.
523 */
524BOOST_AUTO_TEST_CASE( LegacyVersionedVarStillResolvesToCurrentDir )
525{
526 const wxString libPath = "/some/path/to/stock-libraries";
527 const wxString currentVersionedName = ENV_VAR::GetVersionedEnvVarName( wxS( "FOOTPRINT_DIR" ) );
528
529 // Condition: the legacy var is not the same as the current versioned var.
530 BOOST_REQUIRE( wxS( "KICAD5_FOOTPRINT_DIR" ) != currentVersionedName );
531
532 // Ensure legacy var is unset so the direct lookup fails and the fallback runs.
533 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedLegacyEnvVar( wxS( "KICAD5_FOOTPRINT_DIR" ), std::nullopt );
534 // Set the current versioned var, which is what the fallback resolves to.
535 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedVersionedEnvVar( currentVersionedName, libPath );
536
537 wxString expanded =
538 ExpandEnvVarSubstitutions( wxS( "${KICAD5_FOOTPRINT_DIR}/conn_custom.pretty" ), nullptr );
539
540 // The missing fallback resolves to the current versioned footprint directory.
541 BOOST_CHECK_EQUAL( expanded, libPath + wxS( "/conn_custom.pretty" ) );
542}
543
544
545BOOST_AUTO_TEST_CASE( DeprecatedUnversionedAliasStillResolves )
546{
547 // KICAD_SYMBOL_DIR is a documented deprecated alias for the versioned symbol dir.
548 // It must still fall back to the current install even though it carries no
549 // version info.
550 const wxString libPath = "/some/path/to/stock-libraries";
551 const wxString currentVersionedName = ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) );
552
553 // Ensure the "correct" alias is unset so the direct lookup fails and the fallback runs.
554 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedAliasEnvVar( wxS( "KICAD_SYMBOL_DIR" ), std::nullopt );
555 // Set the current versioned var, which is what the fallback resolves to.
556 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedVersionedEnvVar( currentVersionedName, libPath );
557
558 // A user-provided path that uses the deprecated alias.
559 wxString expanded =
560 ExpandEnvVarSubstitutions( wxS( "${KICAD_SYMBOL_DIR}/Device.kicad_sym" ), nullptr );
561
562 // Should have resolved to the current versioned var's value.
563 BOOST_CHECK_EQUAL( expanded, libPath + wxS( "/Device.kicad_sym" ) );
564}
565
566
567BOOST_AUTO_TEST_CASE( IsVersionedEnvVarPredicate )
568{
569 BOOST_CHECK( ENV_VAR::IsVersionedEnvVar( wxS( "KICAD7_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
570 BOOST_CHECK( ENV_VAR::IsVersionedEnvVar( wxS( "KICAD10_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
571
572 BOOST_CHECK( !ENV_VAR::IsVersionedEnvVar( wxS( "KICAD_USER_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
573 BOOST_CHECK( !ENV_VAR::IsVersionedEnvVar( wxS( "KICAD_FOOTPRINT_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
574 BOOST_CHECK( !ENV_VAR::IsVersionedEnvVar( wxS( "KICAD7_SYMBOL_DIR" ), wxS( "FOOTPRINT_DIR" ) ) );
575}
576
578
579
580BOOST_AUTO_TEST_SUITE( ExpandPathVariablesSuite )
581
582BOOST_AUTO_TEST_CASE( LeadingTildeExpandsToHome )
583{
584 wxString home = wxGetHomeDir();
585
586 BOOST_CHECK( ExpandPathVariables( wxS( "~/out/file.zip" ), nullptr ) == home + wxS( "/out/file.zip" ) );
587 BOOST_CHECK( ExpandPathVariables( wxS( "~" ), nullptr ) == home );
588}
589
590BOOST_AUTO_TEST_CASE( TildeInsideNameIsLiteral )
591{
592 BOOST_CHECK( ExpandPathVariables( wxS( "~user/out" ), nullptr ) == wxS( "~user/out" ) );
593 BOOST_CHECK( ExpandPathVariables( wxS( "out/~/file" ), nullptr ) == wxS( "out/~/file" ) );
594}
595
597
598
599// A backslash in a link is a path separator, so it must not read as a variable escape
600BOOST_AUTO_TEST_SUITE( ResolveUriByEnvVarsPaths )
601
602BOOST_AUTO_TEST_CASE( BackslashSeparatorBeforeVarExpands )
603{
604 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedEnvVar( wxS( "QA_DATASHEETS" ), wxS( "Z:" ) );
605
607
608 wxString uri = ResolveUriByEnvVars( wxS( "File:\\\\${QA_DATASHEETS}\\JGNE\\part.pdf" ), &project );
609
610 BOOST_CHECK_EQUAL( uri, wxString( wxS( "File:\\\\Z:\\JGNE\\part.pdf" ) ) );
611}
612
613
614BOOST_AUTO_TEST_CASE( SingleBackslashSeparatorBeforeVarExpands )
615{
616 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedEnvVar( wxS( "QA_DATASHEETS" ), wxS( "Z:" ) );
617
619
620 wxString uri = ResolveUriByEnvVars( wxS( "File:\\${QA_DATASHEETS}\\part.pdf" ), &project );
621
622 BOOST_CHECK_EQUAL( uri, wxString( wxS( "File:\\Z:\\part.pdf" ) ) );
623}
624
625
626BOOST_AUTO_TEST_CASE( ForwardSlashUriStillExpands )
627{
628 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedEnvVar( wxS( "QA_DATASHEETS" ), wxS( "Z:" ) );
629
631
632 wxString uri = ResolveUriByEnvVars( wxS( "File://${QA_DATASHEETS}/part.pdf" ), &project );
633
634 BOOST_CHECK_EQUAL( uri, wxString( wxS( "File://Z:/part.pdf" ) ) );
635}
636
637
638BOOST_AUTO_TEST_CASE( BackslashesAwayFromVarArePreserved )
639{
640 KI_TEST::SCOPED_PROCESS_ENV_VAR scopedEnvVar( wxS( "QA_DATASHEETS" ), wxS( "Z:" ) );
641
643
644 wxString uri = ResolveUriByEnvVars( wxS( "${QA_DATASHEETS}\\JGNE\\sub\\part.pdf" ), &project );
645
646 BOOST_CHECK_EQUAL( uri, wxString( wxS( "Z:\\JGNE\\sub\\part.pdf" ) ) );
647}
648
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
wxString ResolveOutputPath(const wxString &aPath, bool aPathIsDirectory, PROJECT *aProject) const
Definition job.cpp:100
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition job.h:204
RAII helper that sets a KiCad program-level environment variable for the lifetime of the object.
RAII helper that sets a process-level environment variable for the lifetime of the object.
wxString PathStr() const
Get the path to the temporary directory as a wxString.
Definition file_utils.h:64
wxString CreateChildDirStr(const wxString &aName) const
Create and return the path to a direct child directory as a wxString.
Container for project specific data.
Definition project.h:63
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:38
void SetRevision(const wxString &aRevision)
Definition title_block.h:78
void SetComment(int aIdx, const wxString &aComment)
Definition title_block.h:98
wxString ExpandPathVariables(const wxString &aPath, const PROJECT *aProject, const std::function< bool(wxString *)> *aTextResolver)
Expand text variables, environment variables and a leading '~' in a user-entered file path.
Definition common.cpp:798
const wxString ResolveUriByEnvVars(const wxString &aUri, const PROJECT *aProject)
Replace any environment and/or text variables in URIs.
Definition common.cpp:790
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
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
wxString NormalizeFilePathForTextVars(const wxString &aPath)
Normalize a file path so its text variables survive ExpandTextVars.
Definition common.cpp:72
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:333
@ FOR_ERC_DRC
Definition common.h:95
@ FOR_CANVAS
Definition common.h:92
@ RAW_VALUE
Definition common.h:98
@ INTERNAL
Definition common.h:96
static bool empty(const wxTextEntryBase *aCtrl)
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.
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
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
Test fixture for ExpandTextVars tests.
std::function< bool(wxString *)> resolver
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(AssertionsRemainAvailableOnlyDuringRuleChecks)
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")