KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_project_file_patch.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 3
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/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 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
24#include <boost/test/unit_test.hpp>
25
29
30#include <json_common.h>
31
32#include <wx/file.h>
33#include <wx/filename.h>
34#include <wx/stdpaths.h>
35#include <wx/datetime.h>
36
37
38using namespace KICAD_DIFF;
39using nlohmann::json;
40
41
42BOOST_AUTO_TEST_SUITE( ProjectFilePatch )
43
44
45// Each DOC_PROP that lives in the project file must map to a json pointer.
46BOOST_AUTO_TEST_CASE( DocPropPointerMappingDrcSeverities )
47{
49 BOOST_REQUIRE( p.has_value() );
50 BOOST_CHECK_EQUAL( *p, "/board/design_settings/rule_severities" );
51}
52
53
54BOOST_AUTO_TEST_CASE( DocPropPointerMappingErcSeverities )
55{
57 BOOST_REQUIRE( p.has_value() );
58 BOOST_CHECK_EQUAL( *p, "/erc" );
59}
60
61
62BOOST_AUTO_TEST_CASE( DocPropPointerMappingDrawingSheet )
63{
65 BOOST_REQUIRE( p.has_value() );
66 BOOST_CHECK_EQUAL( *p, "/pcbnew/page_layout_descr_file" );
67}
68
69
70BOOST_AUTO_TEST_CASE( DocPropPointerMappingDrawingSheetSchematic )
71{
73 BOOST_REQUIRE( p.has_value() );
74 BOOST_CHECK_EQUAL( *p, "/schematic/page_layout_descr_file" );
75}
76
77
78BOOST_AUTO_TEST_CASE( DocPropPointerMappingNetClasses )
79{
81 BOOST_REQUIRE( p.has_value() );
82 BOOST_CHECK_EQUAL( *p, "/net_settings" );
83}
84
85
86// DOC_PROPs whose content lives in a sibling file (not .kicad_pro) must NOT
87// have a pointer mapping -- the patch code would silently misroute them
88// otherwise.
89BOOST_AUTO_TEST_CASE( DocPropPointerMappingCustomRulesIsAbsent )
90{
91 BOOST_CHECK( !DocPropJsonPointer( DOC_PROP_CUSTOM_RULES ).has_value() );
92}
93
94
95BOOST_AUTO_TEST_CASE( DocPropPointerMappingFpLibTableIsAbsent )
96{
97 BOOST_CHECK( !DocPropJsonPointer( DOC_PROP_FP_LIB_TABLE ).has_value() );
98}
99
100
101BOOST_AUTO_TEST_CASE( DocPropPointerMappingSymLibTableIsAbsent )
102{
103 BOOST_CHECK( !DocPropJsonPointer( DOC_PROP_SYM_LIB_TABLE ).has_value() );
104}
105
106
107// Patching DRC severities copies just that sub-tree; other fields in target
108// are preserved.
109BOOST_AUTO_TEST_CASE( ApplyPatchPreservesNonTargetFields )
110{
111 json target = {
112 { "board", { { "design_settings", { { "rule_severities", { { "old_key", "warning" } } } } } } },
113 { "text_variables", { { "USER_VAR", "user value" } } },
114 { "schematic", { { "legacy_lib_dir", "/old/path" } } }
115 };
116
117 json source = {
118 { "board", { { "design_settings", { { "rule_severities", { { "new_key", "error" } } } } } } }
119 };
120
122
123 // Patched field: replaced.
124 BOOST_CHECK_EQUAL( target["board"]["design_settings"]["rule_severities"]["new_key"], "error" );
125 BOOST_CHECK( !target["board"]["design_settings"]["rule_severities"].contains( "old_key" ) );
126
127 // Non-patched fields: preserved.
128 BOOST_CHECK_EQUAL( target["text_variables"]["USER_VAR"], "user value" );
129 BOOST_CHECK_EQUAL( target["schematic"]["legacy_lib_dir"], "/old/path" );
130}
131
132
133// Patching a DOC_PROP whose pointer is missing in the source returns false
134// and leaves target untouched.
135BOOST_AUTO_TEST_CASE( ApplyPatchNoOpWhenSourceMissingField )
136{
137 json target = { { "board", { { "design_settings", { { "rule_severities", { { "k", "warning" } } } } } } } };
138 json source = { { "unrelated", "value" } };
139
140 BOOST_CHECK( !ApplyProjectFilePatch( target, source, DOC_PROP_DRC_SEVERITIES ) );
141 BOOST_CHECK_EQUAL( target["board"]["design_settings"]["rule_severities"]["k"], "warning" );
142}
143
144
145// A DOC_PROP not in the mapping table returns false even when the JSON
146// contains a similarly-named field.
147BOOST_AUTO_TEST_CASE( ApplyPatchUnmappedPropertyReturnsFalse )
148{
149 json target = json::object();
150 json source = { { "anything", 1 } };
151
152 BOOST_CHECK( !ApplyProjectFilePatch( target, source, DOC_PROP_CUSTOM_RULES ) );
153}
154
155
156// Patching a nested object (net_settings) copies the whole sub-tree.
157BOOST_AUTO_TEST_CASE( ApplyPatchNetClassesCopiesSubTree )
158{
159 json target = {
160 { "net_settings", { { "classes", json::array() } } },
161 { "schematic", { { "preserved", true } } }
162 };
163
164 json source = {
165 { "net_settings", {
166 { "classes", { { { "name", "HighSpeed" }, { "priority", 1 } } } },
167 { "netclass_patterns", { { { "pattern", "DDR_*" }, { "netclass", "HighSpeed" } } } }
168 } }
169 };
170
172
173 BOOST_CHECK_EQUAL( target["net_settings"]["classes"].size(), 1u );
174 BOOST_CHECK_EQUAL( target["net_settings"]["netclass_patterns"].size(), 1u );
175 BOOST_CHECK_EQUAL( target["schematic"]["preserved"], true );
176}
177
178
179BOOST_AUTO_TEST_CASE( ApplyPatchDrawingSheetSchematicDoesNotTouchPcbnew )
180{
181 json target = {
182 { "pcbnew", { { "page_layout_descr_file", "target-pcb.kicad_wks" } } },
183 { "schematic", { { "page_layout_descr_file", "target-sch.kicad_wks" } } }
184 };
185
186 json source = {
187 { "pcbnew", { { "page_layout_descr_file", "source-pcb.kicad_wks" } } },
188 { "schematic", { { "page_layout_descr_file", "source-sch.kicad_wks" } } }
189 };
190
192
193 BOOST_CHECK_EQUAL( target["schematic"]["page_layout_descr_file"], "source-sch.kicad_wks" );
194 BOOST_CHECK_EQUAL( target["pcbnew"]["page_layout_descr_file"], "target-pcb.kicad_wks" );
195}
196
197
198// File orchestrator: missing output file -> writes source verbatim (with the
199// requested fields treated as identity copies).
200BOOST_AUTO_TEST_CASE( ApplyPatchesNoExistingFileWritesSource )
201{
202 wxString tmpDir = wxStandardPaths::Get().GetTempDir() + wxFILE_SEP_PATH
203 + wxS( "kicad_patch_" )
204 + wxString::Format( wxS( "%d" ),
205 static_cast<int>( wxDateTime::Now().GetValue().GetValue() & 0xffffff ) );
206 BOOST_REQUIRE( wxFileName::Mkdir( tmpDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
207
208 const wxString outputPath = tmpDir + wxFILE_SEP_PATH + wxS( "test.kicad_pro" );
209 BOOST_REQUIRE( !wxFileExists( outputPath ) );
210
211 json source = { { "board", { { "design_settings", { { "rule_severities", { { "k", "error" } } } } } } } };
212
213 std::set<wxString> docProps = { DOC_PROP_DRC_SEVERITIES };
214 BOOST_REQUIRE( ApplyProjectFilePatches( outputPath, source, docProps ) );
215
216 BOOST_REQUIRE( wxFileExists( outputPath ) );
217
218 wxFile in( outputPath );
219 wxString content;
220 in.ReadAll( &content );
221 json parsed = json::parse( content.ToStdString() );
222 BOOST_CHECK_EQUAL( parsed["board"]["design_settings"]["rule_severities"]["k"], "error" );
223
224 wxRemoveFile( outputPath );
225 wxFileName::Rmdir( tmpDir );
226}
227
228
229BOOST_AUTO_TEST_CASE( ApplyPatchesEmptyPropertySetDoesNotCreateOutput )
230{
231 wxString tmpDir = wxStandardPaths::Get().GetTempDir() + wxFILE_SEP_PATH
232 + wxS( "kicad_patch_" )
233 + wxString::Format( wxS( "%d" ),
234 static_cast<int>( wxDateTime::Now().GetValue().GetValue() & 0xffffff ) );
235 BOOST_REQUIRE( wxFileName::Mkdir( tmpDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
236
237 const wxString outputPath = tmpDir + wxFILE_SEP_PATH + wxS( "test.kicad_pro" );
238 BOOST_REQUIRE( !wxFileExists( outputPath ) );
239
240 json source = { { "text_variables", { { "USER_VAR", "source value" } } } };
241 std::set<wxString> docProps;
242
243 BOOST_REQUIRE( ApplyProjectFilePatches( outputPath, source, docProps ) );
244 BOOST_CHECK( !wxFileExists( outputPath ) );
245
246 if( wxFileExists( outputPath ) )
247 wxRemoveFile( outputPath );
248
249 wxFileName::Rmdir( tmpDir );
250}
251
252
253// File orchestrator: existing output file is patched -- only the touched
254// fields are updated, all others preserved verbatim.
255BOOST_AUTO_TEST_CASE( ApplyPatchesPreservesExistingNonTargetFields )
256{
257 wxString tmpDir = wxStandardPaths::Get().GetTempDir() + wxFILE_SEP_PATH
258 + wxS( "kicad_patch_" )
259 + wxString::Format( wxS( "%d" ),
260 static_cast<int>( wxDateTime::Now().GetValue().GetValue() & 0xffffff ) );
261 BOOST_REQUIRE( wxFileName::Mkdir( tmpDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
262
263 const wxString outputPath = tmpDir + wxFILE_SEP_PATH + wxS( "test.kicad_pro" );
264
265 json existing = {
266 { "text_variables", { { "USER_VAR", "user value" } } },
267 { "board", { { "design_settings", { { "rule_severities", { { "old", "warning" } } } } } } }
268 };
269
270 {
271 wxFile out;
272 BOOST_REQUIRE( out.Open( outputPath, wxFile::write ) );
273 BOOST_REQUIRE( out.Write( wxString::FromUTF8( existing.dump( 2 ) ) ) );
274 }
275
276 json source = { { "board", { { "design_settings", { { "rule_severities", { { "new", "error" } } } } } } } };
277
278 std::set<wxString> docProps = { DOC_PROP_DRC_SEVERITIES };
279 BOOST_REQUIRE( ApplyProjectFilePatches( outputPath, source, docProps ) );
280
281 wxFile in( outputPath );
282 wxString content;
283 in.ReadAll( &content );
284 json parsed = json::parse( content.ToStdString() );
285
286 BOOST_CHECK_EQUAL( parsed["board"]["design_settings"]["rule_severities"]["new"], "error" );
287 BOOST_CHECK( !parsed["board"]["design_settings"]["rule_severities"].contains( "old" ) );
288 BOOST_CHECK_EQUAL( parsed["text_variables"]["USER_VAR"], "user value" );
289
290 wxRemoveFile( outputPath );
291 wxFileName::Rmdir( tmpDir );
292}
293
294
295BOOST_AUTO_TEST_CASE( ApplyPatchesMissingRequestedSourceFieldFails )
296{
297 wxString tmpDir = wxStandardPaths::Get().GetTempDir() + wxFILE_SEP_PATH
298 + wxS( "kicad_patch_" )
299 + wxString::Format( wxS( "%d" ),
300 static_cast<int>( wxDateTime::Now().GetValue().GetValue() & 0xffffff ) );
301 BOOST_REQUIRE( wxFileName::Mkdir( tmpDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
302
303 const wxString outputPath = tmpDir + wxFILE_SEP_PATH + wxS( "test.kicad_pro" );
304
305 json existing = { { "text_variables", { { "USER_VAR", "user value" } } } };
306
307 {
308 wxFile out;
309 BOOST_REQUIRE( out.Open( outputPath, wxFile::write ) );
310 BOOST_REQUIRE( out.Write( wxString::FromUTF8( existing.dump( 2 ) ) ) );
311 }
312
313 json source = { { "board", json::object() } };
314 std::set<wxString> docProps = { DOC_PROP_DRC_SEVERITIES };
315
316 BOOST_CHECK( !ApplyProjectFilePatches( outputPath, source, docProps ) );
317
318 wxFile in( outputPath );
319 wxString content;
320 in.ReadAll( &content );
321 json parsed = json::parse( content.ToStdString() );
322 BOOST_CHECK_EQUAL( parsed["text_variables"]["USER_VAR"], "user value" );
323
324 wxRemoveFile( outputPath );
325 wxFileName::Rmdir( tmpDir );
326}
327
328
329// Realistically-shaped .kicad_pro: rule_severities is a sibling of other
330// design_settings params under board.design_settings. The surgical patch must
331// touch only rule_severities and leave its siblings byte-identical -- a full
332// SaveProjectCopy fallback (the symptom of a wrong pointer) would clobber them.
333BOOST_AUTO_TEST_CASE( ApplyPatchRealisticProjectShapeIsSurgical )
334{
335 json existing = {
336 { "board", {
337 { "design_settings", {
338 { "rule_severities", { { "copper_edge_clearance", "error" } } },
339 { "rules", { { "min_copper_edge_clearance", 0.5 } } },
340 { "track_widths", json::array( { 0.2, 0.25 } ) }
341 } }
342 } },
343 { "meta", { { "filename", "board.kicad_pro" } } }
344 };
345
346 json source = {
347 { "board", {
348 { "design_settings", {
349 { "rule_severities", { { "copper_edge_clearance", "warning" } } },
350 { "rules", { { "min_copper_edge_clearance", 99.0 } } },
351 { "track_widths", json::array( { 9.0 } ) }
352 } }
353 } }
354 };
355
357
358 // Targeted severity took the source value.
359 BOOST_CHECK_EQUAL( existing["board"]["design_settings"]["rule_severities"]["copper_edge_clearance"],
360 "warning" );
361
362 // Unrelated design_settings siblings are byte-identical to the original,
363 // proving the patch did not fall back to a full copy of the source subtree.
364 BOOST_CHECK_EQUAL( existing["board"]["design_settings"]["rules"]["min_copper_edge_clearance"], 0.5 );
365 BOOST_CHECK_EQUAL( existing["board"]["design_settings"]["track_widths"].dump(),
366 json::array( { 0.2, 0.25 } ).dump() );
367 BOOST_CHECK_EQUAL( existing["meta"]["filename"], "board.kicad_pro" );
368}
369
370
nlohmann::json json
Definition gerbview.cpp:49
const wxString DOC_PROP_ERC_SEVERITIES
const wxString DOC_PROP_SYM_LIB_TABLE
const wxString DOC_PROP_NET_CLASSES
bool ApplyProjectFilePatch(nlohmann::json &aTarget, const nlohmann::json &aSource, const wxString &aDocProp, DOC_KIND aKind)
Copy the JSON sub-tree located at the pointer for aDocProp from aSource into aTarget.
const wxString DOC_PROP_CUSTOM_RULES
const wxString DOC_PROP_FP_LIB_TABLE
const wxString DOC_PROP_DRAWING_SHEET
const wxString DOC_PROP_DRC_SEVERITIES
std::optional< std::string > DocPropJsonPointer(const wxString &aDocProp, DOC_KIND aKind)
Return the JSON pointer (RFC 6901, slash-separated) under which aDocProp is persisted in a ....
bool ApplyProjectFilePatches(const wxString &aOutputProPath, const nlohmann::json &aSource, const std::set< wxString > &aDocProps, DOC_KIND aKind)
Higher-level orchestrator: load the existing aOutputProPath as JSON (or start from aSource if the fil...
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(DocPropPointerMappingDrcSeverities)
BOOST_CHECK_EQUAL(result, "25.4")
wxString dump(const wxArrayString &aArray)
Debug helper for printing wxArrayString contents.