KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_project_template.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, see <https://www.gnu.org/licenses/>.
18 */
19
24
26#include <project_template.h>
27
28#include <wx/dir.h>
29#include <wx/filename.h>
30
31#include <filesystem>
32#include <fstream>
33#include <sstream>
34
35namespace fs = std::filesystem;
36
37
39{
40public:
42 {
43 m_tempDir = fs::temp_directory_path() / "kicad_template_test";
44 fs::remove_all( m_tempDir );
45 fs::create_directories( m_tempDir );
46 }
47
49 {
50 fs::remove_all( m_tempDir );
51 }
52
53 void CreateTemplateStructure( const std::string& templateName,
54 const std::vector<std::string>& subdirs,
55 const std::vector<std::string>& files )
56 {
57 fs::path templatePath = m_tempDir / templateName;
58 fs::create_directories( templatePath );
59
60 fs::path metaPath = templatePath / "meta";
61 fs::create_directories( metaPath );
62
63 std::ofstream infoFile( ( metaPath / "info.html" ).string() );
64 infoFile << "<html><head><title>Test Template</title></head><body></body></html>";
65 infoFile.close();
66
67 for( const auto& subdir : subdirs )
68 {
69 fs::create_directories( templatePath / subdir );
70 }
71
72 for( const auto& file : files )
73 {
74 fs::path filePath = templatePath / file;
75 fs::create_directories( filePath.parent_path() );
76 std::ofstream f( filePath.string() );
77 f << "test content";
78 f.close();
79 }
80 }
81
82 fs::path m_tempDir;
83};
84
85
86BOOST_FIXTURE_TEST_SUITE( ProjectTemplate, PROJECT_TEMPLATE_TEST_FIXTURE )
87
88
89BOOST_AUTO_TEST_CASE( DirectoriesRenamedCorrectly )
90{
91 // Create a template with subdirectories that should be renamed
92 CreateTemplateStructure(
93 "issue22289",
94 { "issue22289-dir", "issue22289-backups", "other-dir" },
95 { "issue22289.kicad_pro", "issue22289.kicad_sch", "issue22289-dir/test.kicad_sym" } );
96
97 fs::path templatePath = m_tempDir / "issue22289";
98 fs::path destPath = m_tempDir / "myproject";
99 fs::create_directories( destPath );
100
101 PROJECT_TEMPLATE tmpl( wxString::FromUTF8( templatePath.string() ) );
102
103 // GetDestinationFiles expects a wxFileName with the project file path
104 wxFileName newProjectPath;
105 newProjectPath.SetPath( wxString::FromUTF8( destPath.string() ) );
106 newProjectPath.SetName( wxS( "myproject" ) );
107 newProjectPath.SetExt( wxS( "kicad_pro" ) );
108
109 std::vector<wxFileName> destFiles;
110 tmpl.GetDestinationFiles( newProjectPath, destFiles );
111
112 bool foundRenamedDir = false;
113 bool foundRenamedFile = false;
114 bool foundOtherDir = false;
115
116 for( const wxFileName& destFile : destFiles )
117 {
118 wxString fullPath = destFile.GetFullPath();
119
120 if( fullPath.Contains( wxS( "myproject-dir" ) ) )
121 foundRenamedDir = true;
122
123 if( fullPath.Contains( wxS( "issue22289-dir" ) ) )
124 BOOST_FAIL( "Directory should have been renamed from issue22289-dir to myproject-dir" );
125
126 if( fullPath.Contains( wxS( "other-dir" ) ) )
127 foundOtherDir = true;
128
129 if( destFile.GetName() == wxS( "myproject" ) && destFile.GetExt() == wxS( "kicad_pro" ) )
130 foundRenamedFile = true;
131 }
132
133 BOOST_CHECK_MESSAGE( foundRenamedDir, "Should find myproject-dir in destination files" );
134 BOOST_CHECK_MESSAGE( foundRenamedFile, "Should find myproject.kicad_pro in destination files" );
135 BOOST_CHECK_MESSAGE( foundOtherDir, "Should preserve other-dir (not matching template name)" );
136}
137
138
139BOOST_AUTO_TEST_CASE( CreateProjectRenamesDirectories )
140{
141 CreateTemplateStructure(
142 "testtemplate",
143 { "testtemplate-lib", "testtemplate" },
144 { "testtemplate.kicad_pro", "testtemplate-lib/component.kicad_sym",
145 "testtemplate/nested.txt" } );
146
147 fs::path templatePath = m_tempDir / "testtemplate";
148 fs::path destPath = m_tempDir / "newproject";
149 fs::create_directories( destPath );
150
151 PROJECT_TEMPLATE tmpl( wxString::FromUTF8( templatePath.string() ) );
152
153 // CreateProject expects a wxFileName with the project file path (including extension)
154 wxFileName newProjectPath;
155 newProjectPath.SetPath( wxString::FromUTF8( destPath.string() ) );
156 newProjectPath.SetName( wxS( "newproject" ) );
157 newProjectPath.SetExt( wxS( "kicad_pro" ) );
158
159 wxString errorMsg;
160 bool result = tmpl.CreateProject( newProjectPath, &errorMsg );
161
162 BOOST_CHECK_MESSAGE( result, "CreateProject should succeed: " + errorMsg.ToStdString() );
163
164 BOOST_CHECK( fs::exists( destPath / "newproject.kicad_pro" ) );
165 BOOST_CHECK( fs::exists( destPath / "newproject-lib" ) );
166 BOOST_CHECK( fs::exists( destPath / "newproject-lib" / "component.kicad_sym" ) );
167
168 BOOST_CHECK_MESSAGE( !fs::exists( destPath / "testtemplate-lib" ),
169 "Old directory name should not exist" );
170}
171
172
173BOOST_AUTO_TEST_CASE( ExactMatchDirectoryRenamed )
174{
175 // Test that a directory exactly matching the template name is renamed
176 CreateTemplateStructure( "mytemplate", { "mytemplate" },
177 { "mytemplate.kicad_pro", "mytemplate/subfile.txt" } );
178
179 fs::path templatePath = m_tempDir / "mytemplate";
180 fs::path destPath = m_tempDir / "finalproject";
181 fs::create_directories( destPath );
182
183 PROJECT_TEMPLATE tmpl( wxString::FromUTF8( templatePath.string() ) );
184
185 // GetDestinationFiles expects a wxFileName with the project file path
186 wxFileName newProjectPath;
187 newProjectPath.SetPath( wxString::FromUTF8( destPath.string() ) );
188 newProjectPath.SetName( wxS( "finalproject" ) );
189 newProjectPath.SetExt( wxS( "kicad_pro" ) );
190
191 std::vector<wxFileName> destFiles;
192 tmpl.GetDestinationFiles( newProjectPath, destFiles );
193
194 bool foundExactRenamedDir = false;
195
196 for( const wxFileName& destFile : destFiles )
197 {
198 wxString fullPath = destFile.GetFullPath();
199
200 if( fullPath.Contains( wxS( "/finalproject/finalproject/" ) )
201 || fullPath.Contains( wxS( "\\finalproject\\finalproject\\" ) ) )
202 {
203 foundExactRenamedDir = true;
204 }
205
206 if( fullPath.Contains( wxS( "/finalproject/mytemplate/" ) )
207 || fullPath.Contains( wxS( "\\finalproject\\mytemplate\\" ) ) )
208 {
209 BOOST_FAIL( "Exact match directory should be renamed from mytemplate to finalproject" );
210 }
211 }
212
213 BOOST_CHECK_MESSAGE( foundExactRenamedDir, "Should find renamed subdirectory finalproject" );
214}
215
216
217BOOST_AUTO_TEST_CASE( TemplateWithoutMetaDirSetsErrorTitle )
218{
219 // Issue #23623: user template directories that are just regular KiCad projects
220 // (no meta/info.html) should not crash. The constructor should set an error
221 // title and GetTitle() should return that error without trying to open a
222 // nonexistent file.
223 fs::path noMetaPath = m_tempDir / "no_meta_template";
224 fs::create_directories( noMetaPath );
225
226 std::ofstream f( ( noMetaPath / "no_meta_template.kicad_pro" ).string() );
227 f << "{}";
228 f.close();
229
230 PROJECT_TEMPLATE tmpl( wxString::FromUTF8( noMetaPath.string() ) );
231
232 wxString* title = tmpl.GetTitle();
233 BOOST_REQUIRE( title != nullptr );
234 BOOST_CHECK_MESSAGE( !title->IsEmpty(), "Template without meta dir should have an error title" );
235
236 // Calling GetTitle() again should return the same cached result without
237 // attempting to open meta/info.html
238 wxString* title2 = tmpl.GetTitle();
239 BOOST_CHECK( *title == *title2 );
240}
241
242
243BOOST_AUTO_TEST_CASE( TemplateWithMetaDirButNoInfoHtml )
244{
245 // meta directory exists but info.html is missing
246 fs::path tmplPath = m_tempDir / "meta_no_html";
247 fs::create_directories( tmplPath / "meta" );
248
249 std::ofstream f( ( tmplPath / "meta_no_html.kicad_pro" ).string() );
250 f << "{}";
251 f.close();
252
253 PROJECT_TEMPLATE tmpl( wxString::FromUTF8( tmplPath.string() ) );
254
255 wxString* title = tmpl.GetTitle();
256 BOOST_REQUIRE( title != nullptr );
257 BOOST_CHECK_MESSAGE( !title->IsEmpty(),
258 "Template with meta dir but no info.html should have an error title" );
259}
260
261
262// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/24343
263// EnsureDefaultProjectTemplate must seed the built-in "default" template under the given base
264// directory and yield a directory that PROJECT_TEMPLATE can load as a valid template.
265BOOST_AUTO_TEST_CASE( EnsureDefaultTemplateSeedsBaseDir )
266{
267 wxString baseDir = wxString::FromUTF8( ( m_tempDir / "default_seed" ).string() );
268 wxFileName seeded = EnsureDefaultProjectTemplate( baseDir );
269
270 BOOST_REQUIRE_MESSAGE( seeded.IsOk(), "Seeding the default template should succeed" );
271
272 // The default template must live directly under the requested base directory, not somewhere
273 // derived from an environment variable.
274 fs::path expected = m_tempDir / "default_seed" / "default";
275 BOOST_CHECK( fs::exists( expected ) );
276 BOOST_CHECK( fs::exists( expected / "meta" / "info.html" ) );
277 BOOST_CHECK( fs::exists( expected / "default.kicad_pro" ) );
278
279 // PROJECT_TEMPLATE must recognize the seeded directory as a valid template (meta dir and
280 // info.html present, so no error is reported).
281 PROJECT_TEMPLATE tmpl( seeded.GetPath() );
282
283 BOOST_CHECK_MESSAGE( tmpl.GetError().IsEmpty(),
284 "Seeded default template should load without error: " + tmpl.GetError() );
285
286 wxString* title = tmpl.GetTitle();
287
288 BOOST_REQUIRE( title != nullptr );
289 BOOST_CHECK( !title->IsEmpty() );
290}
291
292
293// An empty base directory must not create anything and must report failure.
294BOOST_AUTO_TEST_CASE( EnsureDefaultTemplateRejectsEmptyBaseDir )
295{
296 wxFileName seeded = EnsureDefaultProjectTemplate( wxEmptyString );
297
298 BOOST_CHECK( !seeded.IsOk() );
299}
300
301
302// Calling EnsureDefaultProjectTemplate twice must be idempotent and not clobber existing content.
303BOOST_AUTO_TEST_CASE( EnsureDefaultTemplateIsIdempotent )
304{
305 wxString baseDir = wxString::FromUTF8( ( m_tempDir / "default_idempotent" ).string() );
306
307 wxFileName first = EnsureDefaultProjectTemplate( baseDir );
308 BOOST_REQUIRE( first.IsOk() );
309
310 // Overwrite the project file with custom content to verify it survives a second call.
311 fs::path proFile = m_tempDir / "default_idempotent" / "default" / "default.kicad_pro";
312 {
313 std::ofstream f( proFile.string() );
314 f << "{\"custom\":true}";
315 }
316
317 wxFileName second = EnsureDefaultProjectTemplate( baseDir );
318 BOOST_REQUIRE( second.IsOk() );
319 BOOST_CHECK_EQUAL( first.GetPath(), second.GetPath() );
320
321 std::ifstream in( proFile.string() );
322 std::stringstream buffer;
323 buffer << in.rdbuf();
324
325 BOOST_CHECK_EQUAL( buffer.str(), std::string( "{\"custom\":true}" ) );
326}
327
328
void CreateTemplateStructure(const std::string &templateName, const std::vector< std::string > &subdirs, const std::vector< std::string > &files)
A class which provides project template functionality.
size_t GetDestinationFiles(const wxFileName &aNewProjectPath, std::vector< wxFileName > &aDestFiles)
Fetch the list of destination files to be copied when the new project is created.
wxString * GetTitle()
Get the title of the project (extracted from the html title tag)
bool CreateProject(wxFileName &aNewProjectPath, wxString *aErrorMsg=nullptr)
Copies and renames all template files to create a new project.
const wxString & GetError() const
wxFileName EnsureDefaultProjectTemplate(const wxString &aBaseDir)
Seed the built-in "default" project template under aBaseDir, creating the directory tree and minimal ...
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_AUTO_TEST_CASE(DirectoriesRenamedCorrectly)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")