KiCad PCB EDA Suite
Loading...
Searching...
No Matches
git_test_fixture.h
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
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
20#ifndef QA_GIT_TEST_FIXTURE_H
21#define QA_GIT_TEST_FIXTURE_H
22
23#include <boost/test/unit_test.hpp>
25#include <qa_utils/file_utils.h>
27
28#include <wx/file.h>
29#include <wx/filename.h>
30
31#include <filesystem>
32#include <string>
33#include <utility>
34#include <vector>
35
36namespace KI_TEST
37{
38
41{
42public:
43 explicit SCOPED_GIT_CONFIG_SEARCH_PATH( const std::string& aPath )
44 {
45 for( git_config_level_t level :
46 { GIT_CONFIG_LEVEL_PROGRAMDATA, GIT_CONFIG_LEVEL_SYSTEM, GIT_CONFIG_LEVEL_XDG, GIT_CONFIG_LEVEL_GLOBAL } )
47 {
48 git_buf previous = GIT_BUF_INIT;
49 BOOST_REQUIRE_EQUAL( git_libgit2_opts( GIT_OPT_GET_SEARCH_PATH, level, &previous ), GIT_OK );
50 m_saved.emplace_back( level, previous.ptr ? previous.ptr : "" );
51 git_buf_dispose( &previous );
52 }
53
54 bool overridden = true;
55
56 for( const auto& [level, searchPath] : m_saved )
57 overridden &= git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, level, aPath.c_str() ) == GIT_OK;
58
59 // A throwing constructor skips the destructor, so undo a partial override here
60 if( !overridden )
61 restore();
62
63 BOOST_REQUIRE( overridden );
64 }
65
67
70
71private:
72 void restore()
73 {
74 for( const auto& [level, searchPath] : m_saved )
75 git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, level, searchPath.c_str() );
76 }
77
78 std::vector<std::pair<git_config_level_t, std::string>> m_saved;
79};
80
81
85{
86public:
88 m_tempDir( "kicad-qa-git" ),
89 m_configDir( "kicad-qa-gitconfig" ),
90 m_configSearchPath( toWxString( m_configDir.Path() ).utf8_string() )
91 {
92 // Resolve temporary-directory symlinks to match libgit2's workdir path.
93 m_tempPath = toWxString( std::filesystem::canonical( m_tempDir.Path() ) );
94 }
95
96 const wxString& TempPath() const { return m_tempPath; }
97 LIBGIT_BACKEND* Backend() const { return m_backend.Backend(); }
98
99private:
100 static wxString toWxString( const std::filesystem::path& aPath )
101 {
102#ifdef __WINDOWS__
103 return aPath.wstring();
104#else
105 return wxString::FromUTF8( aPath.native() );
106#endif
107 }
108
109 // Declared after the backend so the search paths are set after libgit2 starts and restored before it stops
114 wxString m_tempPath;
115};
116
117
119inline KIGIT::GitRepositoryPtr InitGitRepository( const wxString& aPath, const char* aBranch = "main",
120 const char* aAuthorName = "QA Test",
121 const char* aAuthorEmail = "[email protected]" )
122{
123 git_repository_init_options options = GIT_REPOSITORY_INIT_OPTIONS_INIT;
124 options.flags = GIT_REPOSITORY_INIT_MKPATH;
125 options.initial_head = aBranch;
126
127 git_repository* repo = nullptr;
128 int result = git_repository_init_ext( &repo, aPath.utf8_str(), &options );
129 KIGIT::GitRepositoryPtr repoPtr( repo );
130 BOOST_REQUIRE_EQUAL( result, GIT_OK );
131
132 git_config* config = nullptr;
133 BOOST_REQUIRE_EQUAL( git_repository_config( &config, repo ), GIT_OK );
134 KIGIT::GitConfigPtr configPtr( config );
135 BOOST_REQUIRE_EQUAL( git_config_set_string( config, "user.name", aAuthorName ), GIT_OK );
136 BOOST_REQUIRE_EQUAL( git_config_set_string( config, "user.email", aAuthorEmail ), GIT_OK );
137
138 return repoPtr;
139}
140
141
143inline void WriteGitFile( const wxString& aRepoPath, const wxString& aRelativePath, const std::string& aContents )
144{
145 wxFileName path( aRepoPath + wxFileName::GetPathSeparator() + aRelativePath );
146
147 if( !wxFileName::DirExists( path.GetPath() ) )
148 BOOST_REQUIRE( wxFileName::Mkdir( path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
149
150 wxFile file( path.GetFullPath(), wxFile::write );
151 BOOST_REQUIRE( file.IsOpened() );
152 BOOST_REQUIRE_EQUAL( file.Write( aContents.data(), aContents.size() ), aContents.size() );
153 BOOST_REQUIRE( file.Close() );
154}
155
156
158inline git_oid CommitGitIndex( git_repository* aRepo, const wxString& aMessage )
159{
160 git_index* index = nullptr;
161 BOOST_REQUIRE_EQUAL( git_repository_index( &index, aRepo ), GIT_OK );
162 KIGIT::GitIndexPtr indexPtr( index );
163
164 git_oid treeOid;
165 BOOST_REQUIRE_EQUAL( git_index_write_tree( &treeOid, index ), GIT_OK );
166
167 git_tree* tree = nullptr;
168 BOOST_REQUIRE_EQUAL( git_tree_lookup( &tree, aRepo, &treeOid ), GIT_OK );
169 KIGIT::GitTreePtr treePtr( tree );
170
171 git_signature* signature = nullptr;
172 BOOST_REQUIRE_EQUAL( git_signature_default( &signature, aRepo ), GIT_OK );
173 KIGIT::GitSignaturePtr signaturePtr( signature );
174
175 git_oid headOid;
176 int result = git_reference_name_to_id( &headOid, aRepo, "HEAD" );
177 KIGIT::GitCommitPtr parent;
178
179 if( result == GIT_OK )
180 {
181 git_commit* commit = nullptr;
182 BOOST_REQUIRE_EQUAL( git_commit_lookup( &commit, aRepo, &headOid ), GIT_OK );
183 parent.reset( commit );
184 }
185 else
186 {
187 BOOST_REQUIRE( result == GIT_ENOTFOUND || result == GIT_EUNBORNBRANCH );
188 }
189
190 const git_commit* parents[] = { parent.get() };
191 git_oid commitOid;
192 BOOST_REQUIRE_EQUAL( git_commit_create( &commitOid, aRepo, "HEAD", signature, signature, nullptr,
193 aMessage.utf8_str(), tree, parent ? 1 : 0, parent ? parents : nullptr ),
194 GIT_OK );
195 return commitOid;
196}
197
198
200inline git_oid CommitGitFile( git_repository* aRepo, const wxString& aPath, const wxString& aMessage )
201{
202 {
203 git_index* index = nullptr;
204 BOOST_REQUIRE_EQUAL( git_repository_index( &index, aRepo ), GIT_OK );
205 KIGIT::GitIndexPtr indexPtr( index );
206 BOOST_REQUIRE_EQUAL( git_index_add_bypath( index, aPath.utf8_str() ), GIT_OK );
207 BOOST_REQUIRE_EQUAL( git_index_write( index ), GIT_OK );
208 }
209
210 return CommitGitIndex( aRepo, aMessage );
211}
212
213
215inline git_oid CommitAllGitFiles( git_repository* aRepo, const wxString& aMessage )
216{
217 {
218 git_index* index = nullptr;
219 BOOST_REQUIRE_EQUAL( git_repository_index( &index, aRepo ), GIT_OK );
220 KIGIT::GitIndexPtr indexPtr( index );
221 BOOST_REQUIRE_EQUAL( git_index_add_all( index, nullptr, GIT_INDEX_ADD_DEFAULT, nullptr, nullptr ), GIT_OK );
222 BOOST_REQUIRE_EQUAL( git_index_write( index ), GIT_OK );
223 }
224
225 return CommitGitIndex( aRepo, aMessage );
226}
227
228} // namespace KI_TEST
229
230#endif // QA_GIT_TEST_FIXTURE_H
int index
static wxString toWxString(const std::filesystem::path &aPath)
SCOPED_GIT_BACKEND m_backend
SCOPED_GIT_CONFIG_SEARCH_PATH m_configSearchPath
LIBGIT_BACKEND * Backend() const
const wxString & TempPath() const
Point every config level above the repository at one directory, restoring the previous paths afterwar...
std::vector< std::pair< git_config_level_t, std::string > > m_saved
SCOPED_GIT_CONFIG_SEARCH_PATH(const std::string &aPath)
SCOPED_GIT_CONFIG_SEARCH_PATH(const SCOPED_GIT_CONFIG_SEARCH_PATH &)=delete
SCOPED_GIT_CONFIG_SEARCH_PATH & operator=(const SCOPED_GIT_CONFIG_SEARCH_PATH &)=delete
Install a real backend for a test and restore the previous backend afterwards.
#define GIT_BUF_INIT
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
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_commit, decltype([](git_commit *aCommit) { git_commit_free(aCommit); })> GitCommitPtr
A unique pointer for git_commit objects with automatic cleanup.
std::unique_ptr< git_config, decltype([](git_config *aConfig) { git_config_free(aConfig); })> GitConfigPtr
A unique pointer for git_config objects with automatic cleanup.
std::unique_ptr< git_signature, decltype([](git_signature *aSignature) { git_signature_free(aSignature); })> GitSignaturePtr
A unique pointer for git_signature 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.
Test utilities for timestamps.
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.
void WriteGitFile(const wxString &aRepoPath, const wxString &aRelativePath, const std::string &aContents)
Write test data without staging it. Create parent directories when needed.
git_oid CommitAllGitFiles(git_repository *aRepo, const wxString &aMessage)
Stage all changes, including deletions, and commit them.
git_oid CommitGitFile(git_repository *aRepo, const wxString &aPath, const wxString &aMessage)
Stage one file and commit the index. Other staged changes are kept.
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
std::string path
wxString result
Test unit parsing edge cases and error handling.