KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_project_file.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
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_project_file_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 fs::path m_tempDir;
54};
55
56
57BOOST_FIXTURE_TEST_SUITE( ProjectFile, PROJECT_FILE_TEST_FIXTURE )
58
59
60
65BOOST_AUTO_TEST_CASE( SaveAsUpdatesTopLevelSheetNames )
66{
67 fs::path oldProjectDir = m_tempDir / "old_project";
68 fs::path newProjectDir = m_tempDir / "new_project";
69 fs::create_directories( oldProjectDir );
70 fs::create_directories( newProjectDir );
71
72 wxString oldProjectPath = wxString( oldProjectDir.string() ) + wxFileName::GetPathSeparator()
73 + wxS( "old_project." ) + FILEEXT::ProjectFileExtension;
74
75 PROJECT_FILE projectFile( oldProjectPath );
76
77 // Add a top-level sheet with name matching the project name
78 TOP_LEVEL_SHEET_INFO sheetInfo;
79 sheetInfo.uuid = KIID();
80 sheetInfo.name = wxS( "old_project" );
81 sheetInfo.filename = wxS( "old_project.kicad_sch" );
82
83 projectFile.GetTopLevelSheets().push_back( sheetInfo );
84
85 // Add another sheet with a custom name that should NOT be changed
86 TOP_LEVEL_SHEET_INFO customSheet;
87 customSheet.uuid = KIID();
88 customSheet.name = wxS( "CustomSheet" );
89 customSheet.filename = wxS( "custom_sheet.kicad_sch" );
90
91 projectFile.GetTopLevelSheets().push_back( customSheet );
92
93 // Perform SaveAs to new project
94 projectFile.SaveAs( wxString( newProjectDir.string() ), wxS( "new_project" ) );
95
96 // Verify the sheet name was updated
97 const std::vector<TOP_LEVEL_SHEET_INFO>& sheets = projectFile.GetTopLevelSheets();
98
99 BOOST_REQUIRE_EQUAL( sheets.size(), 2 );
100
101 // First sheet's name should be updated to match the new project name
102 BOOST_CHECK_EQUAL( sheets[0].name, wxS( "new_project" ) );
103
104 // First sheet's filename should also be updated
105 BOOST_CHECK_EQUAL( sheets[0].filename, wxS( "new_project.kicad_sch" ) );
106
107 // Second sheet's custom name should remain unchanged
108 BOOST_CHECK_EQUAL( sheets[1].name, wxS( "CustomSheet" ) );
109
110 // Second sheet's filename should remain unchanged
111 BOOST_CHECK_EQUAL( sheets[1].filename, wxS( "custom_sheet.kicad_sch" ) );
112}
113
114
124BOOST_AUTO_TEST_CASE( LoadFixesStaleTopLevelSheetReferences )
125{
126 fs::path projectDir = m_tempDir / "my_project";
127 fs::create_directories( projectDir );
128
129 // Write a .kicad_pro that references "default.kicad_sch" (as if copied from a template)
130 std::string proContent = R"({
131 "meta": {
132 "filename": "my_project.kicad_pro",
133 "version": 3
134 },
135 "schematic": {
136 "top_level_sheets": [
137 {
138 "uuid": "00000000-0000-0000-0000-000000000000",
139 "name": "default",
140 "filename": "default.kicad_sch"
141 }
142 ]
143 }
144 })";
145
146 fs::path proPath = projectDir / "my_project.kicad_pro";
147 std::ofstream proFile( proPath );
148 proFile << proContent;
149 proFile.close();
150
151 // Create the renamed schematic file (as if the template copy renamed it)
152 fs::path schPath = projectDir / "my_project.kicad_sch";
153 std::ofstream schFile( schPath );
154 schFile << "(kicad_sch (version 20231120) (generator \"eeschema\") (generator_version \"9.99\")";
155 schFile << " (uuid \"12345678-1234-1234-1234-123456789abc\")";
156 schFile << " (paper \"A4\"))";
157 schFile.close();
158
159 // Load the project using SETTINGS_MANAGER
160 SETTINGS_MANAGER settingsManager;
161 settingsManager.LoadProject( wxString( proPath.string() ) );
162
163 PROJECT& project = settingsManager.Prj();
164 PROJECT_FILE& projectFile = project.GetProjectFile();
165
166 const std::vector<TOP_LEVEL_SHEET_INFO>& sheets = projectFile.GetTopLevelSheets();
167
168 BOOST_REQUIRE_EQUAL( sheets.size(), 1 );
169
170 // The filename should have been corrected from "default.kicad_sch" to "my_project.kicad_sch"
171 BOOST_CHECK_EQUAL( sheets[0].filename, wxS( "my_project.kicad_sch" ) );
172
173 // The name should also be updated
174 BOOST_CHECK_EQUAL( sheets[0].name, wxS( "my_project" ) );
175}
176
177
181BOOST_AUTO_TEST_CASE( LoadPreservesValidTopLevelSheetReferences )
182{
183 fs::path projectDir = m_tempDir / "valid_project";
184 fs::create_directories( projectDir );
185
186 // Write a .kicad_pro with valid references
187 std::string proContent = R"({
188 "meta": {
189 "filename": "valid_project.kicad_pro",
190 "version": 3
191 },
192 "schematic": {
193 "top_level_sheets": [
194 {
195 "uuid": "00000000-0000-0000-0000-000000000000",
196 "name": "valid_project",
197 "filename": "valid_project.kicad_sch"
198 }
199 ]
200 }
201 })";
202
203 fs::path proPath = projectDir / "valid_project.kicad_pro";
204 std::ofstream proFile( proPath );
205 proFile << proContent;
206 proFile.close();
207
208 // Create the schematic file that matches the reference
209 fs::path schPath = projectDir / "valid_project.kicad_sch";
210 std::ofstream schFile( schPath );
211 schFile << "(kicad_sch (version 20231120) (generator \"eeschema\") (generator_version \"9.99\")";
212 schFile << " (uuid \"12345678-1234-1234-1234-123456789abc\")";
213 schFile << " (paper \"A4\"))";
214 schFile.close();
215
216 SETTINGS_MANAGER settingsManager;
217 settingsManager.LoadProject( wxString( proPath.string() ) );
218
219 PROJECT& project = settingsManager.Prj();
220 PROJECT_FILE& projectFile = project.GetProjectFile();
221
222 const std::vector<TOP_LEVEL_SHEET_INFO>& sheets = projectFile.GetTopLevelSheets();
223
224 BOOST_REQUIRE_EQUAL( sheets.size(), 1 );
225
226 // References should be unchanged
227 BOOST_CHECK_EQUAL( sheets[0].filename, wxS( "valid_project.kicad_sch" ) );
228 BOOST_CHECK_EQUAL( sheets[0].name, wxS( "valid_project" ) );
229}
230
231
245BOOST_AUTO_TEST_CASE( LoadProjectByAbsolutePathIsStable )
246{
247 fs::path projectDir = m_tempDir / "jobset_project";
248 fs::create_directories( projectDir );
249
250 std::string proContent = R"({
251 "meta": {
252 "filename": "jobset_project.kicad_pro",
253 "version": 3
254 }
255 })";
256
257 fs::path proPath = projectDir / "jobset_project.kicad_pro";
258 std::ofstream proFile( proPath );
259 proFile << proContent;
260 proFile.close();
261
262 wxFileName absFn( wxString( proPath.string() ) );
263 absFn.MakeAbsolute();
264 wxString absPath = absFn.GetFullPath();
265
266 SETTINGS_MANAGER settingsManager;
267
268 // Load as the fixed jobset runner does: by absolute path.
269 BOOST_REQUIRE( settingsManager.LoadProject( absPath ) );
270
271 PROJECT* heldProject = settingsManager.GetProject( absPath );
272 BOOST_REQUIRE( heldProject != nullptr );
273
274 // Simulate the kiface board loader resolving and (re)loading the project by its absolute path.
275 BOOST_REQUIRE( settingsManager.LoadProject( absPath, true ) );
276
277 // The second load must be a no-op for the held pointer; the project must not have been evicted.
278 PROJECT* afterReload = settingsManager.GetProject( absPath );
279 BOOST_CHECK( afterReload == heldProject );
280
281 // The held pointer must still resolve to the same project name (i.e. it was not freed).
282 BOOST_CHECK_EQUAL( heldProject->GetProjectFullName(), absPath );
283}
284
285
295BOOST_AUTO_TEST_CASE( UnloadProjectSavesToOwnDirectory )
296{
297 fs::path projADir = m_tempDir / "proj_a";
298 fs::path projBDir = m_tempDir / "proj_b";
299 fs::create_directories( projADir );
300 fs::create_directories( projBDir );
301
302 // Shared basename in different directories reproduces the cross-project clobber. The
303 // matching schematic keeps LoadFromFile from flagging the project as migrated, which
304 // would otherwise suppress the auto-save under test.
305 const std::string projectName = "shared_name";
306
307 auto writeProject = [&]( const fs::path& aDir )
308 {
309 std::string content = "{\n"
310 " \"meta\": {\n"
311 " \"filename\": \"" + projectName + ".kicad_pro\",\n"
312 " \"version\": 3\n"
313 " },\n"
314 " \"schematic\": {\n"
315 " \"top_level_sheets\": [\n"
316 " {\n"
317 " \"uuid\": \"00000000-0000-0000-0000-000000000000\",\n"
318 " \"name\": \"" + projectName + "\",\n"
319 " \"filename\": \"" + projectName + ".kicad_sch\"\n"
320 " }\n"
321 " ]\n"
322 " }\n"
323 "}\n";
324 std::ofstream out( aDir / ( projectName + ".kicad_pro" ) );
325 out << content;
326 out.close();
327
328 std::ofstream sch( aDir / ( projectName + ".kicad_sch" ) );
329 sch << "(kicad_sch (version 20231120) (generator \"eeschema\") (generator_version \"9.99\")";
330 sch << " (uuid \"12345678-1234-1234-1234-123456789abc\") (paper \"A4\"))";
331 sch.close();
332 };
333
334 fs::path proAPath = projADir / ( projectName + ".kicad_pro" );
335 fs::path proBPath = projBDir / ( projectName + ".kicad_pro" );
336 writeProject( projADir );
337 writeProject( projBDir );
338
340
341 // A becomes the active project; B is loaded but left non-active so both are resident.
342 BOOST_REQUIRE( mgr.LoadProject( wxString( proAPath.string() ), true ) );
343 BOOST_REQUIRE( mgr.LoadProject( wxString( proBPath.string() ), false ) );
344
345 PROJECT* projB = mgr.GetProject( wxString( proBPath.string() ) );
346 BOOST_REQUIRE( projB != nullptr );
347
348 // Prj() must be A so that a Prj()-based path resolution would target the wrong directory.
349 BOOST_REQUIRE_EQUAL( mgr.Prj().GetProjectFullName(), wxString( proAPath.string() ) );
350
351 // Mark B's project file so the save has something distinctive to persist.
352 projB->GetProjectFile().m_TextVars[wxS( "OWNER" )] = wxS( "proj_b" );
353
354 BOOST_REQUIRE( mgr.UnloadProject( projB, true ) );
355
356 auto readFile = []( const fs::path& aPath )
357 {
358 std::ifstream in( aPath );
359 std::stringstream buffer;
360 buffer << in.rdbuf();
361 return buffer.str();
362 };
363
364 // B's own file must have received B's marker.
365 std::string savedB = readFile( proBPath );
366 BOOST_CHECK_MESSAGE( savedB.find( "OWNER" ) != std::string::npos,
367 "unloaded project must be saved to its own directory" );
368
369 // A's identically named file must not have been clobbered with B's data.
370 std::string savedA = readFile( proAPath );
371 BOOST_CHECK_MESSAGE( savedA.find( "OWNER" ) == std::string::npos,
372 "active project's file must not receive the unloaded project's data" );
373}
374
375
const char * name
Definition kiid.h:44
The backing store for a PROJECT, in JSON format.
std::map< wxString, wxString > m_TextVars
bool SaveAs(const wxString &aDirectory, const wxString &aFile)
std::vector< TOP_LEVEL_SHEET_INFO > & GetTopLevelSheets()
Container for project specific data.
Definition project.h:62
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:177
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:200
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
static const std::string ProjectFileExtension
static bool readFile(const wxString &aFileName, wxString &aOut, size_t aLimit=0)
Read a file into aOut.
Information about a top-level schematic sheet.
KIID uuid
Unique identifier for the sheet.
wxString name
Display name for the sheet.
wxString filename
Relative path to the sheet file.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_AUTO_TEST_CASE(SaveAsUpdatesTopLevelSheetNames)
Test that SaveAs updates top-level sheet names when they match the old project name.
BOOST_CHECK_EQUAL(result, "25.4")
Definition of file extensions used in Kicad.