KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_sch_merge_applier.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
30
31#include <schematic.h>
32#include <sch_screen.h>
33#include <sch_sheet.h>
34#include <sch_sheet_path.h>
35#include <sch_symbol.h>
37
38
39using namespace KICAD_DIFF;
40
41
43{
45 {
46 KI_TEST::LoadSchematic( m_sa, "issue18606/issue18606", m_ancestor );
47 KI_TEST::LoadSchematic( m_so, "issue18606/issue18606", m_ours );
48 KI_TEST::LoadSchematic( m_st, "issue18606/issue18606", m_theirs );
52 }
53
55 std::unique_ptr<SCHEMATIC> m_ancestor;
56 std::unique_ptr<SCHEMATIC> m_ours;
57 std::unique_ptr<SCHEMATIC> m_theirs;
58};
59
60
61BOOST_FIXTURE_TEST_SUITE( SchMergeApplier, SCH_APPLIER_FIXTURE )
62
63
64BOOST_AUTO_TEST_CASE( EmptyPlanIsNoOp )
65{
66 SCH_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
67 SCH_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
68
69 KICAD_MERGE_ENGINE engine;
70 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
71 BOOST_CHECK( plan.actions.empty() );
72
73 SCH_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
74 BOOST_CHECK( applier.Apply() );
75
76 // Differ from result against ours should still be empty.
77 SCH_DIFFER sanity( m_ancestor.get(), m_ours.get() );
78 DOCUMENT_DIFF sanityResult = sanity.Diff();
79 BOOST_CHECK( sanityResult.Empty() );
80}
81
82
83BOOST_AUTO_TEST_CASE( DrawingSheetResolutionMarksOnlyDrawingSheetProjectField )
84{
85 BOOST_REQUIRE( m_ancestor->IsValid() );
86 BOOST_REQUIRE( m_ours->IsValid() );
87 BOOST_REQUIRE( m_theirs->IsValid() );
88
89 m_ancestor->Settings().m_SchDrawingSheetFileName = wxS( "ancestor.kicad_wks" );
90 m_ours->Settings().m_SchDrawingSheetFileName = wxS( "ours.kicad_wks" );
91 m_theirs->Settings().m_SchDrawingSheetFileName = wxS( "ancestor.kicad_wks" );
92
93 MERGE_PLAN plan;
94 plan.actions.push_back( { KIID_PATH(), ITEM_RES::TAKE_OURS, {} } );
95
96 SCH_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
97 BOOST_CHECK( applier.Apply() );
98
99 BOOST_CHECK_EQUAL( m_ancestor->Settings().m_SchDrawingSheetFileName, "ours.kicad_wks" );
100 BOOST_CHECK( applier.GetReport().projectFileTouched );
101 BOOST_CHECK( applier.GetReport().drawingSheetFileTouched );
102 BOOST_CHECK( !applier.GetReport().ercSeveritiesTouched );
103}
104
105
106BOOST_AUTO_TEST_CASE( TakeOursAppliesOurChange )
107{
108 // Mutate ours to give the engine a one-sided modification to propagate.
109 SCH_SHEET_LIST sheets = m_ours->BuildSheetListSortedByPageNumbers();
110 SCH_SYMBOL* subject = nullptr;
111 KIID subjectUuid;
112
113 for( const SCH_SHEET_PATH& path : sheets )
114 {
115 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
116 {
117 subject = static_cast<SCH_SYMBOL*>( item );
118 subjectUuid = subject->m_Uuid;
119 break;
120 }
121
122 if( subject )
123 break;
124 }
125
126 BOOST_REQUIRE( subject );
127 VECTOR2I origPos = subject->GetPosition();
128 subject->SetPosition( origPos + VECTOR2I( 5000, 0 ) );
129
130 SCH_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
131 SCH_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
132
133 KICAD_MERGE_ENGINE engine;
134 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
135 BOOST_REQUIRE( !plan.actions.empty() );
136
137 SCH_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
138 BOOST_CHECK( applier.Apply() );
139
140 // After applying, ancestor should contain a symbol with the same UUID and
141 // moved position.
142 SCH_SYMBOL* found = nullptr;
143
144 for( const SCH_SHEET_PATH& path : m_ancestor->BuildSheetListSortedByPageNumbers() )
145 {
146 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
147 {
148 if( item->m_Uuid == subjectUuid )
149 found = static_cast<SCH_SYMBOL*>( item );
150 }
151 }
152
153 BOOST_REQUIRE( found );
154 BOOST_CHECK_EQUAL( found->GetPosition().x, origPos.x + 5000 );
155}
156
157
158BOOST_AUTO_TEST_CASE( DeleteRemovesItem )
159{
160 // Find and delete the same symbol from both ours and theirs so the plan
161 // emits DELETE.
162 KIID victimUuid;
163
164 auto findFirstSymbol = []( SCHEMATIC* aSch ) -> SCH_SYMBOL*
165 {
166 for( const SCH_SHEET_PATH& path : aSch->BuildSheetListSortedByPageNumbers() )
167 {
168 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
169 return static_cast<SCH_SYMBOL*>( item );
170 }
171
172 return nullptr;
173 };
174
175 SCH_SYMBOL* victimOurs = findFirstSymbol( m_ours.get() );
176 BOOST_REQUIRE( victimOurs );
177 victimUuid = victimOurs->m_Uuid;
178
179 auto removeByUuid = []( SCHEMATIC* aSch, const KIID& aUuid )
180 {
182 {
183 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
184 {
185 if( item->m_Uuid == aUuid )
186 {
187 path.LastScreen()->DeleteItem( item );
188 return;
189 }
190 }
191 }
192 };
193
194 removeByUuid( m_ours.get(), victimUuid );
195 removeByUuid( m_theirs.get(), victimUuid );
196
197 SCH_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
198 SCH_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
199
200 KICAD_MERGE_ENGINE engine;
201 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
202
203 SCH_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
204 BOOST_CHECK( applier.Apply() );
205
206 // Ancestor should no longer have a symbol with that UUID.
207 for( const SCH_SHEET_PATH& path : m_ancestor->BuildSheetListSortedByPageNumbers() )
208 {
209 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
210 {
211 BOOST_CHECK( item->m_Uuid != victimUuid );
212 }
213 }
214
215 BOOST_CHECK_GE( applier.GetReport().itemsDeleted, 1u );
216}
217
218
219// MERGE_PROPS splicer on an orthogonal-edits conflict: ours mutates X,
220// theirs mutates Y of the same symbol's position. The applier's per-
221// property splicer is expected to combine the two deltas (X from ours,
222// Y from theirs) rather than picking a single side wholesale. Documents
223// the actual MERGE_PROPS behaviour as observed in practice.
224BOOST_AUTO_TEST_CASE( OrthogonalSymbolPositionEditsSpliceViaMergeProps )
225{
226 SCH_SHEET_LIST sheets = m_ours->BuildSheetListSortedByPageNumbers();
227 SCH_SYMBOL* oursSubject = nullptr;
228 KIID subjectUuid;
229
230 for( const SCH_SHEET_PATH& path : sheets )
231 {
232 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
233 {
234 oursSubject = static_cast<SCH_SYMBOL*>( item );
235 subjectUuid = oursSubject->m_Uuid;
236 break;
237 }
238
239 if( oursSubject )
240 break;
241 }
242
243 BOOST_REQUIRE( oursSubject );
244 VECTOR2I origPos = oursSubject->GetPosition();
245 oursSubject->SetPosition( origPos + VECTOR2I( 1000, 0 ) );
246
247 // Mutate the matching symbol on theirs to a different position to force
248 // a conflict.
249 for( const SCH_SHEET_PATH& path : m_theirs->BuildSheetListSortedByPageNumbers() )
250 {
251 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
252 {
253 if( item->m_Uuid == subjectUuid )
254 {
255 static_cast<SCH_SYMBOL*>( item )->SetPosition( origPos + VECTOR2I( 0, 1000 ) );
256 break;
257 }
258 }
259 }
260
261 SCH_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
262 SCH_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
263 KICAD_MERGE_ENGINE engine;
264 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
265
266 SCH_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
267 BOOST_CHECK( applier.Apply() );
268
269 // The merged ancestor's symbol must take one of the diverged sides;
270 // ancestor's original position is a regression signal.
271 SCH_SYMBOL* found = nullptr;
272
273 for( const SCH_SHEET_PATH& path : m_ancestor->BuildSheetListSortedByPageNumbers() )
274 {
275 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
276 {
277 if( item->m_Uuid == subjectUuid )
278 found = static_cast<SCH_SYMBOL*>( item );
279 }
280 }
281
282 BOOST_REQUIRE( found );
283
284 // Position decomposes into scalar X / Y properties. Ours touched X
285 // (delta +1000, 0), theirs touched Y (delta 0, +1000). The MERGE_PROPS
286 // splicer applies each delta to its respective field, producing the
287 // hybrid (+1000, +1000).
288 BOOST_CHECK_EQUAL( found->GetPosition().x, origPos.x + 1000 );
289 BOOST_CHECK_EQUAL( found->GetPosition().y, origPos.y + 1000 );
290}
291
292
const KIID m_Uuid
Definition eda_item.h:531
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 SCHEMATICs and produce a DOCUMENT_DIFF.
Definition sch_differ.h:55
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
Materialize a MERGE_PLAN into a merged SCHEMATIC by mutating the ancestor in place.
bool Apply()
Apply the plan to the ancestor.
const REPORT & GetReport() const
Definition kiid.h:44
Holds all the data relating to one schematic.
Definition schematic.h:90
SCH_SHEET_LIST BuildSheetListSortedByPageNumbers() const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Schematic symbol object.
Definition sch_symbol.h:69
void SetPosition(const VECTOR2I &aPosition) override
Definition sch_symbol.h:886
VECTOR2I GetPosition() const override
Definition sch_symbol.h:885
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
The full set of changes between two parsed documents of one type.
Result of planning a 3-way merge.
std::vector< ITEM_RESOLUTION > actions
bool projectFileTouched
True iff the applier resolved state that lives in the .kicad_pro.
std::unique_ptr< SCHEMATIC > m_ancestor
std::unique_ptr< SCHEMATIC > m_ours
std::unique_ptr< SCHEMATIC > m_theirs
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
BOOST_AUTO_TEST_CASE(EmptyPlanIsNoOp)
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_SYMBOL_T
Definition typeinfo.h:169
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683