KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_custom_drc_diff_merge.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
26
31
32#include <board.h>
35
36#include <wx/file.h>
37#include <wx/filename.h>
38#include <wx/stdpaths.h>
39
40
41using namespace KICAD_DIFF;
42
43
44namespace
45{
46
49wxString writeSiblingRules( BOARD& aBoard, const wxString& aContent )
50{
51 wxFileName fn( aBoard.GetFileName() );
53
54 wxFile f;
55 BOOST_REQUIRE( f.Open( fn.GetFullPath(), wxFile::write ) );
56 BOOST_REQUIRE( f.Write( aContent ) );
57 f.Close();
58 return fn.GetFullPath();
59}
60
61
62const ITEM_CHANGE* findDocLevelChange( const DOCUMENT_DIFF& aDiff )
63{
64 for( const ITEM_CHANGE& c : aDiff.changes )
65 {
66 if( c.id.empty() )
67 return &c;
68 }
69
70 return nullptr;
71}
72
73
74const PROPERTY_DELTA* findProperty( const ITEM_CHANGE& aChange, const wxString& aName )
75{
76 for( const PROPERTY_DELTA& p : aChange.properties )
77 {
78 if( p.name == aName )
79 return &p;
80 }
81
82 return nullptr;
83}
84
85} // namespace
86
87
89{
91 {
92 KI_TEST::LoadBoard( m_settingsAnc, "complex_hierarchy", m_ancestor );
93 KI_TEST::LoadBoard( m_settingsOurs, "complex_hierarchy", m_ours );
94 KI_TEST::LoadBoard( m_settingsTheirs, "complex_hierarchy", m_theirs );
95
99
100 // Each board's GetFileName() default points at the same fixture file.
101 // Writing a sibling .kicad_dru would then collide across all three
102 // sides. Move each board to its own temp directory so the sibling
103 // rule files live in distinct places.
104 wxString tmp = wxStandardPaths::Get().GetTempDir();
105 m_ancDir = ( wxFileName( tmp, wxEmptyString ).GetPath() + wxFILE_SEP_PATH
106 + wxS( "kicad_drc_anc_" ) + uniqueSuffix() );
107 m_oursDir = ( wxFileName( tmp, wxEmptyString ).GetPath() + wxFILE_SEP_PATH
108 + wxS( "kicad_drc_ours_" ) + uniqueSuffix() );
109 m_theirsDir = ( wxFileName( tmp, wxEmptyString ).GetPath() + wxFILE_SEP_PATH
110 + wxS( "kicad_drc_theirs_" ) + uniqueSuffix() );
111
112 for( const wxString& d : { m_ancDir, m_oursDir, m_theirsDir } )
113 BOOST_REQUIRE( wxFileName::Mkdir( d, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) );
114
115 m_ancestor->SetFileName( m_ancDir + wxFILE_SEP_PATH + wxS( "board.kicad_pcb" ) );
116 m_ours ->SetFileName( m_oursDir + wxFILE_SEP_PATH + wxS( "board.kicad_pcb" ) );
117 m_theirs ->SetFileName( m_theirsDir + wxFILE_SEP_PATH + wxS( "board.kicad_pcb" ) );
118 }
119
121 {
122 for( const wxString& path : m_createdFiles )
123 {
124 if( wxFileExists( path ) )
125 wxRemoveFile( path );
126 }
127
128 for( const wxString& d : { m_ancDir, m_oursDir, m_theirsDir } )
129 {
130 if( !d.IsEmpty() && wxFileName::DirExists( d ) )
131 wxFileName::Rmdir( d, wxPATH_RMDIR_RECURSIVE );
132 }
133 }
134
135 void recordCreatedFile( const wxString& aPath )
136 {
137 m_createdFiles.push_back( aPath );
138 }
139
140 static wxString uniqueSuffix()
141 {
142 static int counter = 0;
143 return wxString::Format( wxS( "%d_%d" ),
144 static_cast<int>( wxGetUTCTimeMillis().GetValue() & 0xffffff ),
145 ++counter );
146 }
147
151 std::unique_ptr<BOARD> m_ancestor;
152 std::unique_ptr<BOARD> m_ours;
153 std::unique_ptr<BOARD> m_theirs;
154 std::vector<wxString> m_createdFiles;
155 wxString m_ancDir;
156 wxString m_oursDir;
157 wxString m_theirsDir;
158};
159
160
161BOOST_FIXTURE_TEST_SUITE( CustomDrcDiffMerge, CUSTOM_DRC_DIFF_MERGE_FIXTURE )
162
163
164// No sibling .kicad_dru file present on either side -> no DOC_PROP_CUSTOM_RULES
165// delta in the diff. This is the typical git-mergetool temp-blob case.
166BOOST_AUTO_TEST_CASE( NoSiblingFileEmitsNoDelta )
167{
168 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
169 DOCUMENT_DIFF result = differ.Diff();
170
171 if( const ITEM_CHANGE* docChange = findDocLevelChange( result ) )
172 {
173 BOOST_CHECK( findProperty( *docChange, DOC_PROP_CUSTOM_RULES ) == nullptr );
174 }
175}
176
177
178// Ours has a rules file, ancestor doesn't -> delta emitted with non-empty
179// after-summary and "(no custom rules)" before-summary.
180BOOST_AUTO_TEST_CASE( OursAddsRulesEmitsDelta )
181{
182 recordCreatedFile( writeSiblingRules( *m_ours,
183 wxS( "(version 1)\n(rule \"R\" (constraint clearance "
184 "(min 0.3mm)) (condition \"true\"))\n" ) ) );
185
186 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
187 DOCUMENT_DIFF result = differ.Diff();
188
189 const ITEM_CHANGE* docChange = findDocLevelChange( result );
190 BOOST_REQUIRE( docChange );
191
194
195 BOOST_CHECK_EQUAL( delta->before.ToDisplayString(), "(no custom rules)" );
196 BOOST_CHECK( delta->after.ToDisplayString() != "(no custom rules)" );
197}
198
199
200// Both sides have rules but with different content -> delta with distinct
201// before / after summaries.
202BOOST_AUTO_TEST_CASE( DivergentRulesContentEmitsDelta )
203{
204 recordCreatedFile( writeSiblingRules( *m_ancestor,
205 wxS( "(version 1)\n(rule \"A\" (constraint clearance "
206 "(min 0.1mm)) (condition \"true\"))\n" ) ) );
207 recordCreatedFile( writeSiblingRules( *m_ours,
208 wxS( "(version 1)\n(rule \"B\" (constraint clearance "
209 "(min 0.3mm)) (condition \"true\"))\n" ) ) );
210
211 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
212 DOCUMENT_DIFF result = differ.Diff();
213
214 const ITEM_CHANGE* docChange = findDocLevelChange( result );
215 BOOST_REQUIRE( docChange );
216
219
220 BOOST_CHECK( delta->before.ToDisplayString() != delta->after.ToDisplayString() );
221}
222
223
224// Identical rules content on both sides -> no delta.
225BOOST_AUTO_TEST_CASE( IdenticalRulesContentEmitsNoDelta )
226{
227 const wxString sameContent =
228 wxS( "(version 1)\n(rule \"R\" (constraint clearance (min 0.2mm)) "
229 "(condition \"true\"))\n" );
230
231 recordCreatedFile( writeSiblingRules( *m_ancestor, sameContent ) );
232
233 // Both BOARDs point at the same fixture file, so writeSiblingRules with the
234 // ours BOARD overwrites the same .kicad_dru path. Read the produced path
235 // off ours so cleanup still works.
236 recordCreatedFile( writeSiblingRules( *m_ours, sameContent ) );
237
238 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
239 DOCUMENT_DIFF result = differ.Diff();
240
241 if( const ITEM_CHANGE* docChange = findDocLevelChange( result ) )
242 {
243 BOOST_CHECK( findProperty( *docChange, DOC_PROP_CUSTOM_RULES ) == nullptr );
244 }
245}
246
247
248// Applier path: a one-sided rules change is staged on the report for the
249// handler to write out. Ours owns the divergence -> the engine routes to
250// the side that has it.
251BOOST_AUTO_TEST_CASE( ApplierStagesOursRulesOnReport )
252{
253 const wxString oursRules =
254 wxS( "(version 1)\n(rule \"FromOurs\" (constraint clearance (min 0.5mm)) "
255 "(condition \"true\"))\n" );
256 recordCreatedFile( writeSiblingRules( *m_ours, oursRules ) );
257
258 PCB_DIFFER diffAO( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
259 PCB_DIFFER diffAT( m_ancestor.get(), m_theirs.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
260 DOCUMENT_DIFF docAO = diffAO.Diff();
261 DOCUMENT_DIFF docAT = diffAT.Diff();
262
263 KICAD_MERGE_ENGINE engine;
264 MERGE_PLAN plan = engine.Plan( docAO, docAT );
265
266 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(),
267 std::move( plan ) );
268 std::unique_ptr<BOARD> merged = applier.Apply();
269
270 BOOST_REQUIRE( merged );
271 BOOST_CHECK( applier.GetReport().projectFileTouched );
272 BOOST_CHECK( applier.GetReport().customDrcRulesSet );
273 BOOST_CHECK_EQUAL( applier.GetReport().customDrcRules, oursRules );
274}
275
276
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:320
const wxString & GetFileName() const
Definition board.h:357
Three-way merge plan generator.
MERGE_PLAN Plan(const DOCUMENT_DIFF &aAncestorOurs, const DOCUMENT_DIFF &aAncestorTheirs) const
Plan the merge given the canonical pair of diffs.
Diff two already-parsed BOARDs and produce a DOCUMENT_DIFF.
Definition pcb_differ.h:52
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
Materialize a MERGE_PLAN into a real merged BOARD.
std::unique_ptr< BOARD > Apply()
Produce the merged board.
const REPORT & GetReport() const
static const std::string DesignRulesFileExtension
const wxString DOC_PROP_CUSTOM_RULES
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
void recordCreatedFile(const wxString &aPath)
The full set of changes between two parsed documents of one type.
std::vector< ITEM_CHANGE > changes
One change record on a single item.
std::vector< PROPERTY_DELTA > properties
Result of planning a 3-way merge.
bool projectFileTouched
True iff the applier resolved state that lives in the .kicad_pro or a project sibling file.
wxString customDrcRules
Custom DRC rules (.kicad_dru) content the applier resolved.
Single (name, before, after) triple for one mutated property on an item.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(NoSiblingFileEmitsNoDelta)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
static const ITEM_CHANGE * findDocLevelChange(const DOCUMENT_DIFF &aDiff)
Find the synthetic doc-level ITEM_CHANGE (empty KIID_PATH) in a diff result, returning nullptr if non...
static const PROPERTY_DELTA * findProperty(const ITEM_CHANGE &aChange, const wxString &aName)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
int delta
Definition of file extensions used in Kicad.