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
25#include <qa_utils/file_utils.h>
27
32
33#include <board.h>
36
37#include <wx/file.h>
38#include <wx/filename.h>
39#include <wx/stdpaths.h>
40
41
42using namespace KICAD_DIFF;
43
44
45namespace
46{
47
50wxString writeSiblingRules( BOARD& aBoard, const wxString& aContent )
51{
52 wxFileName fn( aBoard.GetFileName() );
54
55 wxFile f;
56 BOOST_REQUIRE( f.Open( fn.GetFullPath(), wxFile::write ) );
57 BOOST_REQUIRE( f.Write( aContent ) );
58 f.Close();
59 return fn.GetFullPath();
60}
61
62
63const ITEM_CHANGE* findDocLevelChange( const DOCUMENT_DIFF& aDiff )
64{
65 for( const ITEM_CHANGE& c : aDiff.changes )
66 {
67 if( c.id.empty() )
68 return &c;
69 }
70
71 return nullptr;
72}
73
74
75const PROPERTY_DELTA* findProperty( const ITEM_CHANGE& aChange, const wxString& aName )
76{
77 for( const PROPERTY_DELTA& p : aChange.properties )
78 {
79 if( p.name == aName )
80 return &p;
81 }
82
83 return nullptr;
84}
85
86} // namespace
87
88
90{
92 m_ancDir( wxS( "kicad_drc_anc" ) ),
93 m_oursDir( wxS( "kicad_drc_ours" ) ),
94 m_theirsDir( wxS( "kicad_drc_theirs" ) )
95 {
96 KI_TEST::LoadBoard( m_settingsAnc, "complex_hierarchy", m_ancestor );
97 KI_TEST::LoadBoard( m_settingsOurs, "complex_hierarchy", m_ours );
98 KI_TEST::LoadBoard( m_settingsTheirs, "complex_hierarchy", m_theirs );
99
103
104 // Each board's GetFileName() default points at the same fixture file.
105 // Writing a sibling .kicad_dru would then collide across all three
106 // sides. Move each board to its own temp directory so the sibling
107 // rule files live in distinct places.
108 m_ancestor->SetFileName( m_ancDir.PathStr() + wxFILE_SEP_PATH + wxS( "board.kicad_pcb" ) );
109 m_ours->SetFileName( m_oursDir.PathStr() + wxFILE_SEP_PATH + wxS( "board.kicad_pcb" ) );
110 m_theirs->SetFileName( m_theirsDir.PathStr() + wxFILE_SEP_PATH + wxS( "board.kicad_pcb" ) );
111 }
112
114 {
115 for( const wxString& path : m_createdFiles )
116 {
117 if( wxFileExists( path ) )
118 wxRemoveFile( path );
119 }
120
121 }
122
123 void recordCreatedFile( const wxString& aPath )
124 {
125 m_createdFiles.push_back( aPath );
126 }
127
131 std::unique_ptr<BOARD> m_ancestor;
132 std::unique_ptr<BOARD> m_ours;
133 std::unique_ptr<BOARD> m_theirs;
134 std::vector<wxString> m_createdFiles;
138};
139
140
141BOOST_FIXTURE_TEST_SUITE( CustomDrcDiffMerge, CUSTOM_DRC_DIFF_MERGE_FIXTURE )
142
143
144// No sibling .kicad_dru file present on either side -> no DOC_PROP_CUSTOM_RULES
145// delta in the diff. This is the typical git-mergetool temp-blob case.
146BOOST_AUTO_TEST_CASE( NoSiblingFileEmitsNoDelta )
147{
148 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
149 DOCUMENT_DIFF result = differ.Diff();
150
151 if( const ITEM_CHANGE* docChange = findDocLevelChange( result ) )
152 {
153 BOOST_CHECK( findProperty( *docChange, DOC_PROP_CUSTOM_RULES ) == nullptr );
154 }
155}
156
157
158// Ours has a rules file, ancestor doesn't -> delta emitted with non-empty
159// after-summary and "(no custom rules)" before-summary.
160BOOST_AUTO_TEST_CASE( OursAddsRulesEmitsDelta )
161{
162 recordCreatedFile( writeSiblingRules( *m_ours,
163 wxS( "(version 1)\n(rule \"R\" (constraint clearance "
164 "(min 0.3mm)) (condition \"true\"))\n" ) ) );
165
166 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
167 DOCUMENT_DIFF result = differ.Diff();
168
169 const ITEM_CHANGE* docChange = findDocLevelChange( result );
170 BOOST_REQUIRE( docChange );
171
174
175 BOOST_CHECK_EQUAL( delta->before.ToDisplayString(), "(no custom rules)" );
176 BOOST_CHECK( delta->after.ToDisplayString() != "(no custom rules)" );
177}
178
179
180// Both sides have rules but with different content -> delta with distinct
181// before / after summaries.
182BOOST_AUTO_TEST_CASE( DivergentRulesContentEmitsDelta )
183{
184 recordCreatedFile( writeSiblingRules( *m_ancestor,
185 wxS( "(version 1)\n(rule \"A\" (constraint clearance "
186 "(min 0.1mm)) (condition \"true\"))\n" ) ) );
187 recordCreatedFile( writeSiblingRules( *m_ours,
188 wxS( "(version 1)\n(rule \"B\" (constraint clearance "
189 "(min 0.3mm)) (condition \"true\"))\n" ) ) );
190
191 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
192 DOCUMENT_DIFF result = differ.Diff();
193
194 const ITEM_CHANGE* docChange = findDocLevelChange( result );
195 BOOST_REQUIRE( docChange );
196
199
200 BOOST_CHECK( delta->before.ToDisplayString() != delta->after.ToDisplayString() );
201}
202
203
204// Identical rules content on both sides -> no delta.
205BOOST_AUTO_TEST_CASE( IdenticalRulesContentEmitsNoDelta )
206{
207 const wxString sameContent =
208 wxS( "(version 1)\n(rule \"R\" (constraint clearance (min 0.2mm)) "
209 "(condition \"true\"))\n" );
210
211 recordCreatedFile( writeSiblingRules( *m_ancestor, sameContent ) );
212
213 // Both BOARDs point at the same fixture file, so writeSiblingRules with the
214 // ours BOARD overwrites the same .kicad_dru path. Read the produced path
215 // off ours so cleanup still works.
216 recordCreatedFile( writeSiblingRules( *m_ours, sameContent ) );
217
218 PCB_DIFFER differ( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
219 DOCUMENT_DIFF result = differ.Diff();
220
221 if( const ITEM_CHANGE* docChange = findDocLevelChange( result ) )
222 {
223 BOOST_CHECK( findProperty( *docChange, DOC_PROP_CUSTOM_RULES ) == nullptr );
224 }
225}
226
227
228// Applier path: a one-sided rules change is staged on the report for the
229// handler to write out. Ours owns the divergence -> the engine routes to
230// the side that has it.
231BOOST_AUTO_TEST_CASE( ApplierStagesOursRulesOnReport )
232{
233 const wxString oursRules =
234 wxS( "(version 1)\n(rule \"FromOurs\" (constraint clearance (min 0.5mm)) "
235 "(condition \"true\"))\n" );
236 recordCreatedFile( writeSiblingRules( *m_ours, oursRules ) );
237
238 PCB_DIFFER diffAO( m_ancestor.get(), m_ours.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
239 PCB_DIFFER diffAT( m_ancestor.get(), m_theirs.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
240 DOCUMENT_DIFF docAO = diffAO.Diff();
241 DOCUMENT_DIFF docAT = diffAT.Diff();
242
243 KICAD_MERGE_ENGINE engine;
244 MERGE_PLAN plan = engine.Plan( docAO, docAT );
245
246 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(),
247 std::move( plan ) );
248 std::unique_ptr<BOARD> merged = applier.Apply();
249
250 BOOST_REQUIRE( merged );
251 BOOST_CHECK( applier.GetReport().projectFileTouched );
252 BOOST_CHECK( applier.GetReport().customDrcRulesSet );
253 BOOST_CHECK_EQUAL( applier.GetReport().customDrcRules, oursRules );
254}
255
256
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const wxString & GetFileName() const
Definition board.h:454
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:62
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
static std::vector< PENDING_PROPERTY > plan(const EDA_ITEM &aSource, const EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
Target properties to write, paired with values read off the source.
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.