KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_text_eval_parser_vcs.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
28
31#include <git/git_backend.h>
32#include <git/libgit_backend.h>
33
34#include <chrono>
35#include <regex>
36
41{
43 {
45 m_backend->Init();
47 }
48
50 {
51 SetGitBackend( nullptr );
52 m_backend->Shutdown();
53 delete m_backend;
54 }
55
57};
58
59BOOST_FIXTURE_TEST_SUITE( TextEvalParserVcs, VCS_TEST_FIXTURE )
60
61
64BOOST_AUTO_TEST_CASE( VcsIdentifierFormatting )
65{
66 EXPRESSION_EVALUATOR evaluator;
67
68 struct TestCase
69 {
70 std::string expression;
71 int expectedLength;
72 bool shouldError;
73 };
74
75 const std::vector<TestCase> cases = {
76 // Different identifier lengths
77 { "@{vcsidentifier()}", 40, false }, // Full SHA-1
78 { "@{vcsidentifier(40)}", 40, false }, // Explicit full
79 { "@{vcsidentifier(7)}", 7, false }, // Short
80 { "@{vcsidentifier(8)}", 8, false }, // 8 chars
81 { "@{vcsidentifier(12)}", 12, false }, // Medium
82 { "@{vcsidentifier(4)}", 4, false }, // Minimum (clamped)
83
84 // File variants
85 { "@{vcsfileidentifier(\".\")}", 40, false },
86 { "@{vcsfileidentifier(\".\", 8)}", 8, false },
87 };
88
89 for( const auto& testCase : cases )
90 {
91 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
92
93 if( testCase.shouldError )
94 {
95 BOOST_CHECK( evaluator.HasErrors() );
96 }
97 else
98 {
99 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(), "Error in expression: " + testCase.expression + " Errors: "
100 + evaluator.GetErrorSummary().ToStdString() );
101
102 // If in a git repo, validate format
103 if( !result.IsEmpty() )
104 {
105 BOOST_CHECK_EQUAL( result.Length(), testCase.expectedLength );
106
107 // Should be valid hex
108 std::regex hexPattern( "^[0-9a-f]+$" );
109 BOOST_CHECK( std::regex_match( result.ToStdString(), hexPattern ) );
110 }
111 }
112 }
113}
114
118BOOST_AUTO_TEST_CASE( VcsBranchAndAuthorInfo )
119{
120 EXPRESSION_EVALUATOR evaluator;
121
122 auto branch = evaluator.Evaluate( "@{vcsbranch()}" );
123 auto author = evaluator.Evaluate( "@{vcsauthor()}" );
124 auto authorEmail = evaluator.Evaluate( "@{vcsauthoremail()}" );
125 auto committer = evaluator.Evaluate( "@{vcscommitter()}" );
126 auto committerEmail = evaluator.Evaluate( "@{vcscommitteremail()}" );
127
128 BOOST_CHECK( !evaluator.HasErrors() );
129
130 // If in a VCS repo, validate email format
131 if( !authorEmail.IsEmpty() )
132 {
133 BOOST_CHECK( authorEmail.Contains( "@" ) );
134 }
135
136 if( !committerEmail.IsEmpty() )
137 {
138 BOOST_CHECK( committerEmail.Contains( "@" ) );
139 }
140
141 // File variants
142 auto fileAuthor = evaluator.Evaluate( "@{vcsfileauthor(\".\")}" );
143 auto fileAuthorEmail = evaluator.Evaluate( "@{vcsfileauthoremail(\".\")}" );
144 auto fileCommitter = evaluator.Evaluate( "@{vcsfilecommitter(\".\")}" );
145 auto fileCommitterEmail = evaluator.Evaluate( "@{vcsfilecommitteremail(\".\")}" );
146
147 BOOST_CHECK( !evaluator.HasErrors() );
148
149 // Validate file variant email formats
150 if( !fileAuthorEmail.IsEmpty() )
151 {
152 BOOST_CHECK( fileAuthorEmail.Contains( "@" ) );
153 }
154
155 if( !fileCommitterEmail.IsEmpty() )
156 {
157 BOOST_CHECK( fileCommitterEmail.Contains( "@" ) );
158 }
159}
160
164BOOST_AUTO_TEST_CASE( VcsDirtyStatus )
165{
166 EXPRESSION_EVALUATOR evaluator;
167
168 struct TestCase
169 {
170 std::string expression;
171 bool shouldError;
172 };
173
174 const std::vector<TestCase> cases = {
175 // Dirty status (returns 0 or 1)
176 { "@{vcsdirty()}", false },
177 { "@{vcsdirty(0)}", false }, // Exclude untracked
178 { "@{vcsdirty(1)}", false }, // Include untracked
179 };
180
181 for( const auto& testCase : cases )
182 {
183 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
184
185 if( testCase.shouldError )
186 {
187 BOOST_CHECK( evaluator.HasErrors() );
188 }
189 else
190 {
191 BOOST_CHECK( !evaluator.HasErrors() );
192
193 // If in a repo, validate format - dirty status should be 0 or 1
194 if( !result.IsEmpty() )
195 {
196 BOOST_CHECK( result == "0" || result == "1" );
197 }
198 }
199 }
200}
201
205BOOST_AUTO_TEST_CASE( VcsDirtySuffix )
206{
207 EXPRESSION_EVALUATOR evaluator;
208
209 struct TestCase
210 {
211 std::string expression;
212 bool shouldError;
213 };
214
215 const std::vector<TestCase> cases = {
216 // Dirty suffix (returns string or empty)
217 { "@{vcsdirtysuffix()}", false },
218 { "@{vcsdirtysuffix(\"-modified\")}", false },
219 { "@{vcsdirtysuffix(\"+\", 1)}", false },
220 };
221
222 for( const auto& testCase : cases )
223 {
224 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
225
226 if( testCase.shouldError )
227 {
228 BOOST_CHECK( evaluator.HasErrors() );
229 }
230 else
231 {
232 BOOST_CHECK( !evaluator.HasErrors() );
233 // Suffix can be any string or empty, just validate no errors
234 }
235 }
236}
237
241BOOST_AUTO_TEST_CASE( VcsLabelsAndDistance )
242{
243 EXPRESSION_EVALUATOR evaluator;
244
245 struct TestCase
246 {
247 std::string expression;
248 bool shouldError;
249 };
250
251 const std::vector<TestCase> cases = {
252 // Label functions
253 { "@{vcsnearestlabel()}", false },
254 { "@{vcsnearestlabel(\"\")}", false },
255 { "@{vcsnearestlabel(\"v*\")}", false }, // Match v* labels
256 { "@{vcsnearestlabel(\"\", 0)}", false }, // Annotated only
257 { "@{vcsnearestlabel(\"\", 1)}", false }, // Any labels
258
259 // Distance functions
260 { "@{vcslabeldistance()}", false },
261 { "@{vcslabeldistance(\"v*\")}", false },
262 { "@{vcslabeldistance(\"\", 1)}", false },
263 };
264
265 for( const auto& testCase : cases )
266 {
267 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
268
269 if( testCase.shouldError )
270 {
271 BOOST_CHECK( evaluator.HasErrors() );
272 }
273 else
274 {
275 BOOST_CHECK( !evaluator.HasErrors() );
276
277 // Distance should be a valid number if not empty
278 if( !result.IsEmpty() && testCase.expression.find( "distance" ) != std::string::npos )
279 {
280 std::regex numberPattern( "^[0-9]+$" );
281 BOOST_CHECK( std::regex_match( result.ToStdString(), numberPattern ) );
282 }
283 }
284 }
285}
286
290BOOST_AUTO_TEST_CASE( VcsCommitDate )
291{
292 EXPRESSION_EVALUATOR evaluator;
293
294 struct TestCase
295 {
296 std::string expression;
297 std::regex pattern;
298 bool shouldError;
299 };
300
301 const std::vector<TestCase> cases = {
302 // Different date formats
303 { "@{vcscommitdate()}", std::regex( "^([0-9]{4}-[0-9]{2}-[0-9]{2}|)$" ), false },
304 { "@{vcscommitdate(\"ISO\")}", std::regex( "^([0-9]{4}-[0-9]{2}-[0-9]{2}|)$" ), false },
305 { "@{vcscommitdate(\"US\")}", std::regex( "^([0-9]{2}/[0-9]{2}/[0-9]{4}|)$" ), false },
306 { "@{vcscommitdate(\"EU\")}", std::regex( "^([0-9]{2}/[0-9]{2}/[0-9]{4}|)$" ), false },
307
308 // File variant
309 { "@{vcsfilecommitdate(\".\")}", std::regex( "^([0-9]{4}-[0-9]{2}-[0-9]{2}|)$" ), false },
310 };
311
312 for( const auto& testCase : cases )
313 {
314 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
315
316 if( testCase.shouldError )
317 {
318 BOOST_CHECK( evaluator.HasErrors() );
319 }
320 else
321 {
322 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(), "Error in expression: " + testCase.expression + " Errors: "
323 + evaluator.GetErrorSummary().ToStdString() );
324
325 // Validate format if result is not empty
326 BOOST_CHECK( std::regex_match( result.ToStdString(), testCase.pattern ) );
327 }
328 }
329}
330
334BOOST_AUTO_TEST_CASE( VcsPerformance )
335{
336 EXPRESSION_EVALUATOR evaluator;
337
338 // Test that VCS operations are reasonably fast
339 auto start = std::chrono::high_resolution_clock::now();
340
341 // Perform many VCS operations
342 for( int i = 0; i < 100; ++i )
343 {
344 auto result = evaluator.Evaluate( "@{vcsidentifier(7)}" );
345 BOOST_CHECK( !evaluator.HasErrors() );
346 }
347
348 auto end = std::chrono::high_resolution_clock::now();
349 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( end - start );
350
351 // Should complete in reasonable time (less than 2 seconds for 100 operations)
352 BOOST_CHECK_LT( duration.count(), 2000 );
353}
354
358BOOST_AUTO_TEST_CASE( VcsMixedExpressions )
359{
360 EXPRESSION_EVALUATOR evaluator;
361 evaluator.SetVariable( wxString( "PROJECT" ), wxString( "MyProject" ) );
362
363 struct TestCase
364 {
365 std::string expression;
366 bool shouldError;
367 };
368
369 const std::vector<TestCase> cases = {
370 // Mixed with text
371 { "Version: @{vcsbranch()}", false },
372 { "Commit: @{vcsidentifier(7)}", false },
373 { "Author: @{vcsauthor()} <@{vcsauthoremail()}>", false },
374
375 // Mixed with variables
376 { "${PROJECT} @{vcsbranch()}", false },
377 { "Built from @{vcsnearestlabel()}@{vcsdirtysuffix()}", false },
378
379 // Mixed with calculations
380 { "Distance: @{vcslabeldistance() + 0}", false },
381 };
382
383 for( const auto& testCase : cases )
384 {
385 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
386
387 if( testCase.shouldError )
388 {
389 BOOST_CHECK( evaluator.HasErrors() );
390 }
391 else
392 {
393 BOOST_CHECK( !evaluator.HasErrors() );
394 BOOST_CHECK( !result.IsEmpty() );
395 }
396 }
397}
398
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.
void SetGitBackend(GIT_BACKEND *aBackend)
Fixture to set up and tear down the git backend for VCS tests.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE_END()
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(VcsIdentifierFormatting)
Test VCS identifier functions with various lengths.