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
31
34#include <git/git_backend.h>
35#include <git/libgit_backend.h>
36#include <git2.h>
37
38#include <chrono>
39#include <fstream>
40#include <regex>
41
42#include <wx/dir.h>
43#include <wx/filename.h>
44#include <wx/utils.h>
45
46
47static const char* TEST_AUTHOR_NAME = "Test Author";
49static const char* TEST_COMMIT_MSG = "Initial test commit";
50
51
57{
59 {
61 m_backend->Init();
63
64 m_originalDir = wxGetCwd();
65
66 m_tempDir = wxFileName::GetTempDir() + wxFileName::GetPathSeparator()
67 + wxString::Format( "kicad_vcs_test_%ld", wxGetProcessId() );
68
69 wxFileName::Mkdir( m_tempDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
70
72
73 if( m_repoReady )
74 wxSetWorkingDirectory( m_tempDir );
75 }
76
78 {
79 wxSetWorkingDirectory( m_originalDir );
80
81 if( wxFileName::DirExists( m_tempDir ) )
82 wxFileName::Rmdir( m_tempDir, wxPATH_RMDIR_RECURSIVE );
83
84 SetGitBackend( nullptr );
85 m_backend->Shutdown();
86 delete m_backend;
87 }
88
89 bool repoReady() const { return m_repoReady; }
90
91private:
92 bool initRepo()
93 {
94 git_repository* repo = nullptr;
95
96 if( git_repository_init( &repo, m_tempDir.ToUTF8().data(), 0 ) != 0 )
97 return false;
98
99 // Configure author identity
100 git_config* config = nullptr;
101
102 if( git_repository_config( &config, repo ) == 0 )
103 {
104 git_config_set_string( config, "user.name", TEST_AUTHOR_NAME );
105 git_config_set_string( config, "user.email", TEST_AUTHOR_EMAIL );
106 git_config_free( config );
107 }
108
109 // Write a file into the working tree
110 wxString filePath = m_tempDir + wxFileName::GetPathSeparator() + wxT( "test.txt" );
111
112 {
113 std::ofstream f( filePath.ToStdString() );
114 f << "test content\n";
115 }
116
117 // Stage it
118 git_index* index = nullptr;
119
120 if( git_repository_index( &index, repo ) != 0 )
121 {
122 git_repository_free( repo );
123 return false;
124 }
125
126 git_index_add_bypath( index, "test.txt" );
127 git_index_write( index );
128
129 // Build a tree from the index
130 git_oid treeOid;
131
132 if( git_index_write_tree( &treeOid, index ) != 0 )
133 {
134 git_index_free( index );
135 git_repository_free( repo );
136 return false;
137 }
138
139 git_index_free( index );
140
141 git_tree* tree = nullptr;
142
143 if( git_tree_lookup( &tree, repo, &treeOid ) != 0 )
144 {
145 git_repository_free( repo );
146 return false;
147 }
148
149 // Create the initial commit (no parents)
150 git_signature* sig = nullptr;
151
152 if( git_signature_now( &sig, TEST_AUTHOR_NAME, TEST_AUTHOR_EMAIL ) != 0 )
153 {
154 git_tree_free( tree );
155 git_repository_free( repo );
156 return false;
157 }
158
159 git_oid commitOid;
160 int err = git_commit_create_v( &commitOid, repo, "HEAD", sig, sig, nullptr,
161 TEST_COMMIT_MSG, tree, 0 );
162
163 git_signature_free( sig );
164 git_tree_free( tree );
165 git_repository_free( repo );
166 return err == 0;
167 }
168
171 wxString m_tempDir;
173};
174
175
176BOOST_FIXTURE_TEST_SUITE( TextEvalParserVcs, VCS_TEST_FIXTURE )
177
178
181BOOST_AUTO_TEST_CASE( VcsIdentifierFormatting )
182{
183 BOOST_TEST_REQUIRE( repoReady() );
184
185 EXPRESSION_EVALUATOR evaluator;
186
187 struct TestCase
188 {
189 std::string expression;
190 int expectedLength;
191 };
192
193 const std::vector<TestCase> cases = {
194 { "@{vcsidentifier()}", 40 },
195 { "@{vcsidentifier(40)}", 40 },
196 { "@{vcsidentifier(7)}", 7 },
197 { "@{vcsidentifier(8)}", 8 },
198 { "@{vcsidentifier(12)}", 12 },
199 { "@{vcsidentifier(4)}", 4 },
200
201 { "@{vcsfileidentifier(\".\")}", 40 },
202 { "@{vcsfileidentifier(\".\", 8)}", 8 },
203 };
204
205 std::regex hexPattern( "^[0-9a-f]+$" );
206
207 for( const auto& testCase : cases )
208 {
209 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
210
211 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
212 "Error in expression: " + testCase.expression + " Errors: "
213 + evaluator.GetErrorSummary().ToStdString() );
214
215 BOOST_CHECK_EQUAL( result.Length(), testCase.expectedLength );
216 BOOST_CHECK( std::regex_match( result.ToStdString(), hexPattern ) );
217 }
218}
219
223BOOST_AUTO_TEST_CASE( VcsBranchAndAuthorInfo )
224{
225 BOOST_TEST_REQUIRE( repoReady() );
226
227 EXPRESSION_EVALUATOR evaluator;
228
229 auto branch = evaluator.Evaluate( "@{vcsbranch()}" );
230 BOOST_CHECK( !evaluator.HasErrors() );
231 BOOST_CHECK( !branch.IsEmpty() );
232
233 auto authorEmail = evaluator.Evaluate( "@{vcsauthoremail()}" );
234 BOOST_CHECK( !evaluator.HasErrors() );
235 BOOST_CHECK_EQUAL( authorEmail, TEST_AUTHOR_EMAIL );
236
237 auto committerEmail = evaluator.Evaluate( "@{vcscommitteremail()}" );
238 BOOST_CHECK( !evaluator.HasErrors() );
239 BOOST_CHECK_EQUAL( committerEmail, TEST_AUTHOR_EMAIL );
240
241 auto author = evaluator.Evaluate( "@{vcsauthor()}" );
242 BOOST_CHECK( !evaluator.HasErrors() );
244
245 auto committer = evaluator.Evaluate( "@{vcscommitter()}" );
246 BOOST_CHECK( !evaluator.HasErrors() );
248
249 // File variants should return the same values since there's only one commit
250 auto fileAuthorEmail = evaluator.Evaluate( "@{vcsfileauthoremail(\".\")}" );
251 BOOST_CHECK( !evaluator.HasErrors() );
252 BOOST_CHECK_EQUAL( fileAuthorEmail, TEST_AUTHOR_EMAIL );
253
254 auto fileCommitterEmail = evaluator.Evaluate( "@{vcsfilecommitteremail(\".\")}" );
255 BOOST_CHECK( !evaluator.HasErrors() );
256 BOOST_CHECK_EQUAL( fileCommitterEmail, TEST_AUTHOR_EMAIL );
257}
258
262BOOST_AUTO_TEST_CASE( VcsDirtyStatus )
263{
264 BOOST_TEST_REQUIRE( repoReady() );
265
266 EXPRESSION_EVALUATOR evaluator;
267
268 struct TestCase
269 {
270 std::string expression;
271 };
272
273 const std::vector<TestCase> cases = {
274 { "@{vcsdirty()}" },
275 { "@{vcsdirty(0)}" },
276 { "@{vcsdirty(1)}" },
277 };
278
279 for( const auto& testCase : cases )
280 {
281 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
282
283 BOOST_CHECK( !evaluator.HasErrors() );
284 BOOST_CHECK( result == "0" || result == "1" );
285 }
286}
287
291BOOST_AUTO_TEST_CASE( VcsDirtySuffix )
292{
293 BOOST_TEST_REQUIRE( repoReady() );
294
295 EXPRESSION_EVALUATOR evaluator;
296
297 const std::vector<std::string> cases = {
298 "@{vcsdirtysuffix()}",
299 "@{vcsdirtysuffix(\"-modified\")}",
300 "@{vcsdirtysuffix(\"+\", 1)}",
301 };
302
303 for( const auto& expr : cases )
304 {
305 evaluator.Evaluate( wxString::FromUTF8( expr ) );
306 BOOST_CHECK( !evaluator.HasErrors() );
307 }
308}
309
313BOOST_AUTO_TEST_CASE( VcsLabelsAndDistance )
314{
315 BOOST_TEST_REQUIRE( repoReady() );
316
317 EXPRESSION_EVALUATOR evaluator;
318
319 const std::vector<std::string> cases = {
320 "@{vcsnearestlabel()}",
321 "@{vcsnearestlabel(\"\")}",
322 "@{vcsnearestlabel(\"v*\")}",
323 "@{vcsnearestlabel(\"\", 0)}",
324 "@{vcsnearestlabel(\"\", 1)}",
325
326 "@{vcslabeldistance()}",
327 "@{vcslabeldistance(\"v*\")}",
328 "@{vcslabeldistance(\"\", 1)}",
329 };
330
331 std::regex numberPattern( "^[0-9]+$" );
332
333 for( const auto& expr : cases )
334 {
335 auto result = evaluator.Evaluate( wxString::FromUTF8( expr ) );
336 BOOST_CHECK( !evaluator.HasErrors() );
337
338 if( !result.IsEmpty() && expr.find( "distance" ) != std::string::npos )
339 {
340 BOOST_CHECK( std::regex_match( result.ToStdString(), numberPattern ) );
341 }
342 }
343}
344
348BOOST_AUTO_TEST_CASE( VcsCommitDate )
349{
350 BOOST_TEST_REQUIRE( repoReady() );
351
352 EXPRESSION_EVALUATOR evaluator;
353
354 struct TestCase
355 {
356 std::string expression;
357 std::regex pattern;
358 };
359
360 const std::vector<TestCase> cases = {
361 { "@{vcscommitdate()}", std::regex( "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ) },
362 { "@{vcscommitdate(\"ISO\")}", std::regex( "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ) },
363 { "@{vcscommitdate(\"US\")}", std::regex( "^[0-9]{2}/[0-9]{2}/[0-9]{4}$" ) },
364 { "@{vcscommitdate(\"EU\")}", std::regex( "^[0-9]{2}/[0-9]{2}/[0-9]{4}$" ) },
365
366 { "@{vcsfilecommitdate(\".\")}", std::regex( "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ) },
367 };
368
369 for( const auto& testCase : cases )
370 {
371 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
372
373 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
374 "Error in expression: " + testCase.expression + " Errors: "
375 + evaluator.GetErrorSummary().ToStdString() );
376
377 BOOST_CHECK_MESSAGE( std::regex_match( result.ToStdString(), testCase.pattern ),
378 "Bad date format for " + testCase.expression + ": "
379 + result.ToStdString() );
380 }
381}
382
386BOOST_AUTO_TEST_CASE( VcsPerformance )
387{
388 BOOST_TEST_REQUIRE( repoReady() );
389
390 EXPRESSION_EVALUATOR evaluator;
391
392 auto start = std::chrono::high_resolution_clock::now();
393
394 for( int i = 0; i < 100; ++i )
395 {
396 auto result = evaluator.Evaluate( "@{vcsidentifier(7)}" );
397 BOOST_CHECK( !evaluator.HasErrors() );
398 }
399
400 auto end = std::chrono::high_resolution_clock::now();
401 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( end - start );
402
403 BOOST_CHECK_LT( duration.count(), 2000 );
404}
405
409BOOST_AUTO_TEST_CASE( VcsMixedExpressions )
410{
411 BOOST_TEST_REQUIRE( repoReady() );
412
413 EXPRESSION_EVALUATOR evaluator;
414 evaluator.SetVariable( wxString( "PROJECT" ), wxString( "MyProject" ) );
415
416 const std::vector<std::string> cases = {
417 "Version: @{vcsbranch()}",
418 "Commit: @{vcsidentifier(7)}",
419 "Author: @{vcsauthor()} <@{vcsauthoremail()}>",
420
421 "${PROJECT} @{vcsbranch()}",
422 "Built from @{vcsnearestlabel()}@{vcsdirtysuffix()}",
423
424 "Distance: @{vcslabeldistance() + 0}",
425 };
426
427 for( const auto& expr : cases )
428 {
429 auto result = evaluator.Evaluate( wxString::FromUTF8( expr ) );
430
431 BOOST_CHECK( !evaluator.HasErrors() );
432 BOOST_CHECK( !result.IsEmpty() );
433 }
434}
435
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.
void SetGitBackend(GIT_BACKEND *aBackend)
Fixture that creates a temporary git repo with one committed file.
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")
static const char * TEST_AUTHOR_NAME
static const char * TEST_AUTHOR_EMAIL
BOOST_AUTO_TEST_CASE(VcsIdentifierFormatting)
Test VCS identifier functions with various lengths.
static const char * TEST_COMMIT_MSG