KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pcb_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 <board.h>
32#include <board_item.h>
33#include <pad.h>
34#include <pcb_field.h>
35#include <pcb_track.h>
36#include <footprint.h>
37#include <zone.h>
40
41
42using namespace KICAD_DIFF;
43
44
51{
53 {
54 KI_TEST::LoadBoard( m_sa, "complex_hierarchy", m_ancestor );
55 KI_TEST::LoadBoard( m_so, "complex_hierarchy", m_ours );
56 KI_TEST::LoadBoard( m_st, "complex_hierarchy", m_theirs );
60 }
61
63 std::unique_ptr<BOARD> m_ancestor;
64 std::unique_ptr<BOARD> m_ours;
65 std::unique_ptr<BOARD> m_theirs;
66};
67
68
69BOOST_FIXTURE_TEST_SUITE( PcbMergeApplier, PCB_APPLIER_FIXTURE )
70
71
72BOOST_AUTO_TEST_CASE( IdenticalInputsProduceIdenticalOutput )
73{
74 PCB_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
75 PCB_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
76 DOCUMENT_DIFF ourD = ourDiffer.Diff();
77 DOCUMENT_DIFF theirD = theirDiffer.Diff();
78
79 KICAD_MERGE_ENGINE engine;
80 MERGE_PLAN plan = engine.Plan( ourD, theirD );
81 BOOST_CHECK( plan.Resolved() );
82 BOOST_CHECK( plan.actions.empty() );
83
84 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
85 std::unique_ptr<BOARD> merged = applier.Apply();
86 BOOST_REQUIRE( merged );
87
88 // Item counts should match.
89 BOOST_CHECK_EQUAL( merged->GetItemSet().size(),
90 m_ancestor->GetItemSet().size() );
91}
92
93
94BOOST_AUTO_TEST_CASE( DrawingSheetResolutionMarksOnlyDrawingSheetProjectField )
95{
96 BOOST_REQUIRE( m_ancestor->GetProject() );
97 BOOST_REQUIRE( m_ours->GetProject() );
98 BOOST_REQUIRE( m_theirs->GetProject() );
99
100 m_ancestor->GetProject()->GetProjectFile().m_BoardDrawingSheetFile = wxS( "ancestor.kicad_wks" );
101 m_ours->GetProject()->GetProjectFile().m_BoardDrawingSheetFile = wxS( "ours.kicad_wks" );
102 m_theirs->GetProject()->GetProjectFile().m_BoardDrawingSheetFile = wxS( "ancestor.kicad_wks" );
103
104 MERGE_PLAN plan;
105 plan.actions.push_back( { KIID_PATH(), ITEM_RES::TAKE_OURS, {} } );
106
107 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
108 std::unique_ptr<BOARD> merged = applier.Apply();
109 BOOST_REQUIRE( merged );
110
111 BOOST_CHECK( applier.GetReport().projectFileTouched );
112 BOOST_CHECK( applier.GetReport().drawingSheetFileSet );
113 BOOST_CHECK_EQUAL( applier.GetReport().drawingSheetFile, "ours.kicad_wks" );
114 BOOST_CHECK( !applier.GetReport().drcSeveritiesTouched );
115 BOOST_CHECK( !applier.GetReport().netClassesTouched );
116}
117
118
119BOOST_AUTO_TEST_CASE( OneSidedAddedItemFlowsThroughToMerge )
120{
121 // Remove an item from ancestor so it appears as "added on both ours and theirs".
122 BOARD_ITEM_SET items = m_ancestor->GetItemSet();
123 PCB_TRACK* victim = nullptr;
124
125 for( BOARD_ITEM* item : items )
126 {
127 if( item && item->Type() == PCB_TRACE_T )
128 {
129 victim = static_cast<PCB_TRACK*>( item );
130 break;
131 }
132 }
133
134 BOOST_REQUIRE( victim );
135 KIID victimUuid = victim->m_Uuid;
136 m_ancestor->Remove( victim, REMOVE_MODE::NORMAL );
137 delete victim;
138
139 PCB_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
140 PCB_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
141
142 KICAD_MERGE_ENGINE engine;
143 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
144
145 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
146 std::unique_ptr<BOARD> merged = applier.Apply();
147 BOOST_REQUIRE( merged );
148
149 // The item should be present in the merged board (it's in ours and theirs).
150 bool found = false;
151
152 for( BOARD_ITEM* item : merged->GetItemSet() )
153 {
154 if( item && item->m_Uuid == victimUuid )
155 {
156 found = true;
157 break;
158 }
159 }
160
161 BOOST_CHECK( found );
162}
163
164
165BOOST_AUTO_TEST_CASE( TakeOursAppliesOurChange )
166{
167 BOARD_ITEM_SET items = m_ours->GetItemSet();
168 PCB_TRACK* subject = nullptr;
169
170 for( BOARD_ITEM* item : items )
171 {
172 if( item && item->Type() == PCB_TRACE_T )
173 {
174 subject = static_cast<PCB_TRACK*>( item );
175 break;
176 }
177 }
178
179 BOOST_REQUIRE( subject );
180 int originalWidth = subject->GetWidth();
181 int newWidth = originalWidth + 75000;
182 KIID subjectUuid = subject->m_Uuid;
183 subject->SetWidth( newWidth );
184
185 PCB_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
186 PCB_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
187
188 KICAD_MERGE_ENGINE engine;
189 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
190
191 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
192 std::unique_ptr<BOARD> merged = applier.Apply();
193 BOOST_REQUIRE( merged );
194
195 // The merged board should have our width on the subject track.
196 bool found = false;
197
198 for( BOARD_ITEM* item : merged->GetItemSet() )
199 {
200 if( item && item->m_Uuid == subjectUuid && item->Type() == PCB_TRACE_T )
201 {
202 BOOST_CHECK_EQUAL( static_cast<PCB_TRACK*>( item )->GetWidth(), newWidth );
203 found = true;
204 break;
205 }
206 }
207
208 BOOST_CHECK( found );
209 BOOST_CHECK_GE( applier.GetReport().itemsTakenOurs, 1u );
210}
211
212
213BOOST_AUTO_TEST_CASE( DeleteRemovesItemFromMergedBoard )
214{
215 // Remove a track on both sides so the plan emits DELETE.
216 BOARD_ITEM* victimOurs = nullptr;
217
218 for( BOARD_ITEM* item : m_ours->GetItemSet() )
219 {
220 if( item && item->Type() == PCB_TRACE_T )
221 {
222 victimOurs = item;
223 break;
224 }
225 }
226
227 BOOST_REQUIRE( victimOurs );
228 KIID victimUuid = victimOurs->m_Uuid;
229
230 BOARD_ITEM* victimTheirs = nullptr;
231
232 for( BOARD_ITEM* item : m_theirs->GetItemSet() )
233 {
234 if( item && item->m_Uuid == victimUuid )
235 {
236 victimTheirs = item;
237 break;
238 }
239 }
240
241 BOOST_REQUIRE( victimTheirs );
242
243 m_ours->Remove( victimOurs, REMOVE_MODE::NORMAL );
244 delete victimOurs;
245 m_theirs->Remove( victimTheirs, REMOVE_MODE::NORMAL );
246 delete victimTheirs;
247
248 PCB_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
249 PCB_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
250
251 KICAD_MERGE_ENGINE engine;
252 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
253
254 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
255 std::unique_ptr<BOARD> merged = applier.Apply();
256 BOOST_REQUIRE( merged );
257
258 // The deleted item should NOT be in the merged board.
259 for( BOARD_ITEM* item : merged->GetItemSet() )
260 {
261 BOOST_CHECK( !item || item->m_Uuid != victimUuid );
262 }
263
264 BOOST_CHECK_GE( applier.GetReport().itemsDeleted, 1u );
265}
266
267
268BOOST_AUTO_TEST_CASE( ApplierReportTracksZoneRefillFlag )
269{
270 PCB_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
271 PCB_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
272
273 KICAD_MERGE_ENGINE engine;
274 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
275
276 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
277 std::unique_ptr<BOARD> merged = applier.Apply();
278 BOOST_REQUIRE( merged );
279
281 plan.requiresZoneRefill );
284}
285
286
287// TAKE_THEIRS on a zone preserves theirs' priority + keepout fields, so the
288// applier doesn't silently fall back to ancestor's values on the non-track
289// path. Mirrors TakeOursAppliesOurChange but for ZONE + theirs side.
290BOOST_AUTO_TEST_CASE( TakeTheirsZonePreservesTheirsPriorityAndKeepout )
291{
292 ZONE* victim = nullptr;
293
294 for( BOARD_ITEM* item : m_theirs->GetItemSet() )
295 {
296 if( item && item->Type() == PCB_ZONE_T )
297 {
298 victim = static_cast<ZONE*>( item );
299 break;
300 }
301 }
302
303 if( !victim )
304 {
305 BOOST_TEST_MESSAGE( "Fixture has no zones; skipping" );
306 return;
307 }
308
309 KIID subjectUuid = victim->m_Uuid;
310 unsigned int originalPri = victim->GetAssignedPriority();
311 bool originalKeepoutTracks = victim->GetDoNotAllowTracks();
312
313 // Mutate theirs: distinct priority and keepout flag.
314 victim->SetAssignedPriority( originalPri + 7 );
315 victim->SetDoNotAllowTracks( !originalKeepoutTracks );
316
317 PCB_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
318 PCB_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
319 KICAD_MERGE_ENGINE engine;
320 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
321
322 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
323 std::unique_ptr<BOARD> merged = applier.Apply();
324 BOOST_REQUIRE( merged );
325
326 bool found = false;
327
328 for( BOARD_ITEM* item : merged->GetItemSet() )
329 {
330 if( item && item->m_Uuid == subjectUuid && item->Type() == PCB_ZONE_T )
331 {
332 ZONE* z = static_cast<ZONE*>( item );
333 BOOST_CHECK_EQUAL( z->GetAssignedPriority(), originalPri + 7 );
334 BOOST_CHECK_EQUAL( z->GetDoNotAllowTracks(), !originalKeepoutTracks );
335 found = true;
336 break;
337 }
338 }
339
340 BOOST_CHECK( found );
341}
342
343
344// MERGE_PROPS fallback: the property splicer that would interleave per-
345// property resolutions between sides isn't fully implemented yet. The
346// documented fallback is "take ours' value for any property not explicitly
347// resolved", which behaves as TAKE_OURS in practice. Pin that fallback
348// explicitly so a future implementation change (or accidental regression)
349// is loud.
350BOOST_AUTO_TEST_CASE( MergePropsFallsBackToOursOnConflictingFields )
351{
352 PCB_TRACK* subject = nullptr;
353
354 for( BOARD_ITEM* item : m_ours->GetItemSet() )
355 {
356 if( item && item->Type() == PCB_TRACE_T )
357 {
358 subject = static_cast<PCB_TRACK*>( item );
359 break;
360 }
361 }
362
363 BOOST_REQUIRE( subject );
364 int oursWidth = subject->GetWidth() + 50000;
365 int theirsWidth = subject->GetWidth() + 75000;
366 KIID subjectUuid = subject->m_Uuid;
367
368 subject->SetWidth( oursWidth );
369
370 // Mutate theirs's matching track to a different width to force a
371 // conflict, which the engine may route through MERGE_PROPS or
372 // (more often) TAKE_OURS via the auto-resolver.
373 for( BOARD_ITEM* item : m_theirs->GetItemSet() )
374 {
375 if( item && item->m_Uuid == subjectUuid && item->Type() == PCB_TRACE_T )
376 {
377 static_cast<PCB_TRACK*>( item )->SetWidth( theirsWidth );
378 break;
379 }
380 }
381
382 PCB_DIFFER ourDiffer( m_ancestor.get(), m_ours.get() );
383 PCB_DIFFER theirDiffer( m_ancestor.get(), m_theirs.get() );
384 KICAD_MERGE_ENGINE engine;
385 MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
386
387 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
388 std::unique_ptr<BOARD> merged = applier.Apply();
389 BOOST_REQUIRE( merged );
390
391 // Either outcome -- ours's width or theirs's width -- is valid depending
392 // on whether the auto-resolver picked TAKE_OURS or MERGE_PROPS -> ours
393 // fallback. Ancestor's original width is NOT valid; the applier must
394 // pick one of the diverged sides.
395 for( BOARD_ITEM* item : merged->GetItemSet() )
396 {
397 if( item && item->m_Uuid == subjectUuid && item->Type() == PCB_TRACE_T )
398 {
399 int mergedWidth = static_cast<PCB_TRACK*>( item )->GetWidth();
400 BOOST_CHECK( mergedWidth == oursWidth || mergedWidth == theirsWidth );
401 break;
402 }
403 }
404}
405
406
407// Characterize the full property-resolution loop (OURS / THEIRS / ANCESTOR /
408// CUSTOM plus the failed-count path) that the applier delegates to the shared
409// KICAD_DIFF::ApplyPropertyResolutions. The auto-resolver never emits CUSTOM
410// and rarely emits a failing property, so a hand-built MERGE_PLAN is the only
411// way to pin all four source kinds and the failure tally in one place. This is
412// a behavior-preserving safety net for the shared-helper refactor.
413BOOST_AUTO_TEST_CASE( MergePropsResolutionKindsAndFailedCount )
414{
415 auto firstTrack = []( BOARD* aBoard ) -> PCB_TRACK*
416 {
417 for( BOARD_ITEM* item : aBoard->GetItemSet() )
418 {
419 if( item && item->Type() == PCB_TRACE_T )
420 return static_cast<PCB_TRACK*>( item );
421 }
422
423 return nullptr;
424 };
425
426 PCB_TRACK* ancTrack = firstTrack( m_ancestor.get() );
427 BOOST_REQUIRE( ancTrack );
428
429 KIID subjectUuid = ancTrack->m_Uuid;
430
431 auto trackByUuid = [&]( BOARD* aBoard ) -> PCB_TRACK*
432 {
433 for( BOARD_ITEM* item : aBoard->GetItemSet() )
434 {
435 if( item && item->Type() == PCB_TRACE_T && item->m_Uuid == subjectUuid )
436 return static_cast<PCB_TRACK*>( item );
437 }
438
439 return nullptr;
440 };
441
442 PCB_TRACK* oursTrack = trackByUuid( m_ours.get() );
443 PCB_TRACK* theirsTrack = trackByUuid( m_theirs.get() );
444 BOOST_REQUIRE( oursTrack );
445 BOOST_REQUIRE( theirsTrack );
446
447 // Distinct width / start-x / end-x on each side so the chosen source is
448 // unambiguous in the merged output.
449 const int oursWidth = ancTrack->GetWidth() + 11000;
450 const int theirsStart = ancTrack->GetStartX() + 22000;
451 const int ancEndX = ancTrack->GetEndX();
452 const int customEndY = ancTrack->GetEndY() + 33000;
453
454 oursTrack->SetWidth( oursWidth );
455 theirsTrack->SetStartX( theirsStart );
456
457 // Hand-build a MERGE_PROPS action: Width from OURS, Start X from THEIRS,
458 // End X from ANCESTOR, End Y from a CUSTOM payload, and one resolution
459 // naming a property that doesn't exist on PCB_TRACK to exercise the
460 // failed-count path.
461 ITEM_RESOLUTION action;
462 action.id.push_back( subjectUuid );
464
465 auto addProp = [&]( const wxString& aName, PROP_RES aKind )
466 {
468 res.name = aName;
469 res.kind = aKind;
470 action.props.push_back( res );
471 };
472
473 addProp( wxS( "Width" ), PROP_RES::OURS );
474 addProp( wxS( "Start X" ), PROP_RES::THEIRS );
475 addProp( wxS( "End X" ), PROP_RES::ANCESTOR );
476
477 PROPERTY_RESOLUTION customRes;
478 customRes.name = wxS( "End Y" );
479 customRes.kind = PROP_RES::CUSTOM;
480 customRes.customValue = DIFF_VALUE::FromInt( customEndY );
481 action.props.push_back( customRes );
482
483 addProp( wxS( "NoSuchProperty" ), PROP_RES::OURS );
484
485 MERGE_PLAN plan;
486 plan.actions.push_back( action );
487
488 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
489 std::unique_ptr<BOARD> merged = applier.Apply();
490 BOOST_REQUIRE( merged );
491
492 PCB_TRACK* mergedTrack = nullptr;
493
494 for( BOARD_ITEM* item : merged->GetItemSet() )
495 {
496 if( item && item->Type() == PCB_TRACE_T && item->m_Uuid == subjectUuid )
497 {
498 mergedTrack = static_cast<PCB_TRACK*>( item );
499 break;
500 }
501 }
502
503 BOOST_REQUIRE( mergedTrack );
504 BOOST_CHECK_EQUAL( mergedTrack->GetWidth(), oursWidth );
505 BOOST_CHECK_EQUAL( mergedTrack->GetStartX(), theirsStart );
506 BOOST_CHECK_EQUAL( mergedTrack->GetEndX(), ancEndX );
507 BOOST_CHECK_EQUAL( mergedTrack->GetEndY(), customEndY );
508
509 // Four real properties applied (Width, Start X, End X, End Y); the bogus
510 // property name lands in the failed tally.
513}
514
515
516// Characterize footprint-child resolution by KIID. The applier's internal
517// item lookup must resolve not only top-level items but also footprint
518// children (pads, fields, graphics, zones) under their own UUIDs, because the
519// child-MERGE_PROPS post-pass looks up each child on ours/theirs/ancestor by
520// its UUID. This test hand-builds a MERGE_PROPS action targeting a pad inside
521// a footprint and verifies the resolution lands, proving the applier resolved
522// the pad child on the source boards by KIID. Behavior-preserving safety net
523// for the BOARD::ResolveItem delegation refactor.
524BOOST_AUTO_TEST_CASE( ResolvesFootprintChildByKiid )
525{
526 // Find a footprint with a copper pad in ours. The "Pad Number" property
527 // is only available on copper pads.
528 FOOTPRINT* fp = nullptr;
529 PAD* pad = nullptr;
530
531 for( FOOTPRINT* candidate : m_ours->Footprints() )
532 {
533 if( !candidate )
534 continue;
535
536 for( PAD* candidatePad : candidate->Pads() )
537 {
538 if( candidatePad && candidatePad->IsOnCopperLayer() )
539 {
540 fp = candidate;
541 pad = candidatePad;
542 break;
543 }
544 }
545
546 if( pad )
547 break;
548 }
549
550 BOOST_REQUIRE( fp );
552
553 const KIID fpUuid = fp->m_Uuid;
554 const KIID padUuid = pad->m_Uuid;
555
556 // Distinct pad number on ours so the resolved source is unambiguous.
557 const wxString oursPadNumber = wxS( "QA_CHILD_RESOLVE" );
558 pad->SetNumber( oursPadNumber );
559
560 // Hand-build a child MERGE_PROPS action keyed by [footprint, pad] path,
561 // taking the pad number from OURS.
562 ITEM_RESOLUTION action;
563 action.id.push_back( fpUuid );
564 action.id.push_back( padUuid );
566
567 PROPERTY_RESOLUTION numberRes;
568 numberRes.name = wxS( "Pad Number" );
569 numberRes.kind = PROP_RES::OURS;
570 action.props.push_back( numberRes );
571
572 MERGE_PLAN plan;
573 plan.actions.push_back( action );
574
575 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
576 std::unique_ptr<BOARD> merged = applier.Apply();
577 BOOST_REQUIRE( merged );
578
579 // Locate the cloned pad child on the merged board and confirm the OURS
580 // value flowed through, which requires findItem to resolve both the
581 // top-level footprint and the pad child by KIID.
582 PAD* mergedPad = nullptr;
583
584 for( FOOTPRINT* mergedFp : merged->Footprints() )
585 {
586 if( !mergedFp || mergedFp->m_Uuid != fpUuid )
587 continue;
588
589 for( PAD* candidate : mergedFp->Pads() )
590 {
591 if( candidate->m_Uuid == padUuid )
592 {
593 mergedPad = candidate;
594 break;
595 }
596 }
597
598 break;
599 }
600
601 BOOST_REQUIRE( mergedPad );
602 BOOST_CHECK_EQUAL( mergedPad->GetNumber(), oursPadNumber );
603}
604
605
606// Regression: a footprint child added on theirs (while the parent is carried
607// from ancestor/ours) must survive the merge. The applier clones the parent
608// from ancestor, which lacks the theirs-added child, so a TAKE_THEIRS child
609// resolution must clone it in rather than silently dropping it.
610BOOST_AUTO_TEST_CASE( ChildAddedOnTheirsSurvivesMerge )
611{
612 FOOTPRINT* ancFp = nullptr;
613 PAD* victimPad = nullptr;
614
615 for( FOOTPRINT* candidate : m_ancestor->Footprints() )
616 {
617 if( candidate && !candidate->Pads().empty() )
618 {
619 ancFp = candidate;
620 victimPad = candidate->Pads().front();
621 break;
622 }
623 }
624
625 BOOST_REQUIRE( ancFp );
626 BOOST_REQUIRE( victimPad );
627
628 const KIID fpUuid = ancFp->m_Uuid;
629 const KIID padUuid = victimPad->m_Uuid;
630
631 // Drop the pad from the ancestor (= the merge base) so the same pad, still
632 // present on theirs, behaves as an added-on-theirs child.
633 ancFp->Remove( victimPad, REMOVE_MODE::NORMAL );
634 delete victimPad;
635
636 ITEM_RESOLUTION action;
637 action.id.push_back( fpUuid );
638 action.id.push_back( padUuid );
640
641 MERGE_PLAN plan;
642 plan.actions.push_back( action );
643
644 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
645 std::unique_ptr<BOARD> merged = applier.Apply();
646 BOOST_REQUIRE( merged );
647
648 bool found = false;
649
650 for( FOOTPRINT* mergedFp : merged->Footprints() )
651 {
652 if( !mergedFp || mergedFp->m_Uuid != fpUuid )
653 continue;
654
655 for( PAD* pad : mergedFp->Pads() )
656 {
657 if( pad && pad->m_Uuid == padUuid )
658 found = true;
659 }
660 }
661
662 BOOST_CHECK( found );
663}
664
665
666// Regression: a footprint child modified only on ours (while the parent has no
667// top-level action) must replace the ancestor-cloned child. The top-level
668// loop clones the parent from ancestor, so TAKE_OURS cannot be a no-op in the
669// child-resolution post-pass.
670BOOST_AUTO_TEST_CASE( ChildTakeOursResolutionReplacesAncestorChild )
671{
672 FOOTPRINT* fp = nullptr;
673 PAD* pad = nullptr;
674
675 for( FOOTPRINT* candidate : m_ours->Footprints() )
676 {
677 if( !candidate )
678 continue;
679
680 for( PAD* candidatePad : candidate->Pads() )
681 {
682 if( candidatePad && candidatePad->IsOnCopperLayer() )
683 {
684 fp = candidate;
685 pad = candidatePad;
686 break;
687 }
688 }
689
690 if( pad )
691 break;
692 }
693
694 BOOST_REQUIRE( fp );
696
697 const KIID fpUuid = fp->m_Uuid;
698 const KIID padUuid = pad->m_Uuid;
699 const wxString oursPadNumber = wxS( "QA_CHILD_TAKE_OURS" );
700 pad->SetNumber( oursPadNumber );
701
702 ITEM_RESOLUTION action;
703 action.id.push_back( fpUuid );
704 action.id.push_back( padUuid );
705 action.kind = ITEM_RES::TAKE_OURS;
706
707 MERGE_PLAN plan;
708 plan.actions.push_back( action );
709
710 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
711 std::unique_ptr<BOARD> merged = applier.Apply();
712 BOOST_REQUIRE( merged );
713
714 PAD* mergedPad = nullptr;
715
716 for( FOOTPRINT* mergedFp : merged->Footprints() )
717 {
718 if( !mergedFp || mergedFp->m_Uuid != fpUuid )
719 continue;
720
721 for( PAD* candidate : mergedFp->Pads() )
722 {
723 if( candidate && candidate->m_Uuid == padUuid )
724 {
725 mergedPad = candidate;
726 break;
727 }
728 }
729
730 break;
731 }
732
733 BOOST_REQUIRE( mergedPad );
734 BOOST_CHECK_EQUAL( mergedPad->GetNumber(), oursPadNumber );
735}
736
737
738// Regression: a footprint child DELETE resolution must actually remove the
739// child from the merged footprint. The parent is cloned from ancestor (which
740// still has the child), so the post-pass has to drop it explicitly.
741BOOST_AUTO_TEST_CASE( ChildDeleteResolutionRemovesChildFromMerge )
742{
743 FOOTPRINT* ancFp = nullptr;
744 PAD* victimPad = nullptr;
745
746 for( FOOTPRINT* candidate : m_ancestor->Footprints() )
747 {
748 if( candidate && !candidate->Pads().empty() )
749 {
750 ancFp = candidate;
751 victimPad = candidate->Pads().front();
752 break;
753 }
754 }
755
756 BOOST_REQUIRE( ancFp );
757 BOOST_REQUIRE( victimPad );
758
759 const KIID fpUuid = ancFp->m_Uuid;
760 const KIID padUuid = victimPad->m_Uuid;
761
762 ITEM_RESOLUTION action;
763 action.id.push_back( fpUuid );
764 action.id.push_back( padUuid );
766
767 MERGE_PLAN plan;
768 plan.actions.push_back( action );
769
770 PCB_MERGE_APPLIER applier( m_ancestor.get(), m_ours.get(), m_theirs.get(), plan );
771 std::unique_ptr<BOARD> merged = applier.Apply();
772 BOOST_REQUIRE( merged );
773
774 for( FOOTPRINT* mergedFp : merged->Footprints() )
775 {
776 if( !mergedFp || mergedFp->m_Uuid != fpUuid )
777 continue;
778
779 for( PAD* pad : mergedFp->Pads() )
780 BOOST_CHECK( !pad || pad->m_Uuid != padUuid );
781 }
782}
783
784
std::set< BOARD_ITEM *, CompareByUuid > BOARD_ITEM_SET
Set of BOARD_ITEMs ordered by UUID.
Definition board.h:304
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:80
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:320
const KIID m_Uuid
Definition eda_item.h:531
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
static DIFF_VALUE FromInt(int aValue)
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
Definition kiid.h:44
Definition pad.h:61
const wxString & GetNumber() const
Definition pad.h:143
int GetEndX() const
Definition pcb_track.h:104
int GetEndY() const
Definition pcb_track.h:105
void SetStartX(int aX)
Definition pcb_track.h:95
int GetStartX() const
Definition pcb_track.h:98
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
virtual int GetWidth() const
Definition pcb_track.h:87
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetDoNotAllowTracks() const
Definition zone.h:810
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:816
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
unsigned GetAssignedPriority() const
Definition zone.h:122
PROP_RES
Resolution kind for a single property of a single item.
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
The full set of changes between two parsed documents of one type.
std::vector< PROPERTY_RESOLUTION > props
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 or a project sibling file.
wxString drawingSheetFile
Drawing sheet path the applier resolved (from a doc-level resolution).
Three-board fixture: ancestor, ours, theirs all start from the same fixture load.
std::unique_ptr< BOARD > m_ours
std::unique_ptr< BOARD > m_ancestor
std::unique_ptr< BOARD > m_theirs
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I res
BOOST_AUTO_TEST_CASE(IdenticalInputsProduceIdenticalOutput)
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
arc1_slc SetWidth(0)
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:89