KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pcb_differ.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 <eda_units.h>
34#include <pad.h>
35#include <pcb_field.h>
36#include <pcb_track.h>
37#include <footprint.h>
39
40#include <json_common.h>
41
42#include <map>
43
44
45using namespace KICAD_DIFF;
46
47
57{
59 {
60 KI_TEST::LoadBoard( m_settingsA, "complex_hierarchy", m_before );
61 KI_TEST::LoadBoard( m_settingsB, "complex_hierarchy", m_after );
62
65 }
66
69 std::unique_ptr<BOARD> m_before;
70 std::unique_ptr<BOARD> m_after;
71};
72
73
74BOOST_FIXTURE_TEST_SUITE( PcbDiffer, PCB_DIFFER_FIXTURE )
75
76
77BOOST_AUTO_TEST_CASE( TwoFreshLoadsAreIdentical )
78{
79 PCB_DIFFER differ( m_before.get(), m_after.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
80 DOCUMENT_DIFF result = differ.Diff();
81
82 BOOST_CHECK_EQUAL( result.docType.ToStdString(), "kicad_pcb" );
83 BOOST_CHECK_MESSAGE( result.Empty(), "Two fresh loads of the same fixture should produce no diff; "
84 "got " << result.changes.size()
85 << " change records" );
86}
87
88
89BOOST_AUTO_TEST_CASE( RemovingAnItemSurfacesAsRemoved )
90{
91 BOARD_ITEM_SET items = m_after->GetItemSet();
92 BOOST_REQUIRE( !items.empty() );
93
94 // Pick the first track from the fixture and remove it from the after-board.
95 PCB_TRACK* victim = nullptr;
96
97 for( BOARD_ITEM* item : items )
98 {
99 if( item && item->Type() == PCB_TRACE_T )
100 {
101 victim = static_cast<PCB_TRACK*>( item );
102 break;
103 }
104 }
105
106 BOOST_REQUIRE( victim );
107 KIID victimUuid = victim->m_Uuid;
108 m_after->Remove( victim, REMOVE_MODE::NORMAL );
109
110 PCB_DIFFER differ( m_before.get(), m_after.get() );
111 DOCUMENT_DIFF result = differ.Diff();
112
113 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
114 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::REMOVED );
115 BOOST_CHECK_EQUAL( result.changes[0].id.back().AsString().ToStdString(), victimUuid.AsString().ToStdString() );
116
117 delete victim;
118}
119
120
121BOOST_AUTO_TEST_CASE( WidthChangeProducesPropertyDelta )
122{
123 BOARD_ITEM_SET items = m_after->GetItemSet();
124 PCB_TRACK* subject = nullptr;
125
126 for( BOARD_ITEM* item : items )
127 {
128 if( item && item->Type() == PCB_TRACE_T )
129 {
130 subject = static_cast<PCB_TRACK*>( item );
131 break;
132 }
133 }
134
135 BOOST_REQUIRE( subject );
136
137 int originalWidth = subject->GetWidth();
138 int newWidth = originalWidth + 50000;
139 subject->SetWidth( newWidth );
140
141 PCB_DIFFER differ( m_before.get(), m_after.get() );
142 DOCUMENT_DIFF result = differ.Diff();
143
144 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
145 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::MODIFIED );
146 BOOST_CHECK_EQUAL( result.changes[0].id.back().AsString().ToStdString(), subject->m_Uuid.AsString().ToStdString() );
147
148 bool foundWidthDelta = false;
149
150 for( const PROPERTY_DELTA& d : result.changes[0].properties )
151 {
152 if( d.name.Lower().Contains( wxS( "width" ) ) )
153 {
154 foundWidthDelta = true;
155 BOOST_CHECK_EQUAL( d.before.AsInt(), originalWidth );
156 BOOST_CHECK_EQUAL( d.after.AsInt(), newWidth );
157 }
158 }
159
160 BOOST_CHECK_MESSAGE( foundWidthDelta, "Expected Width property delta after SetWidth() on a track" );
161}
162
163
164BOOST_AUTO_TEST_CASE( TitleBlockEditSurfacesAsDocumentChange )
165{
166 // Both sides are set here, so the fixture's own revision cannot hide a missing delta.
167 m_before->GetTitleBlock().SetRevision( wxS( "A" ) );
168 m_after->GetTitleBlock().SetRevision( wxS( "B" ) );
169
170 PCB_DIFFER differ( m_before.get(), m_after.get() );
171 DOCUMENT_DIFF result = differ.Diff();
172
173 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
174 BOOST_CHECK( result.changes[0].id.empty() );
175 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::MODIFIED );
176
177 bool foundRevisionDelta = false;
178
179 for( const PROPERTY_DELTA& d : result.changes[0].properties )
180 {
181 if( d.name == DOC_PROP_REVISION )
182 {
183 foundRevisionDelta = true;
184 BOOST_CHECK_EQUAL( d.before.AsString().ToStdString(), "A" );
185 BOOST_CHECK_EQUAL( d.after.AsString().ToStdString(), "B" );
186 }
187 }
188
189 BOOST_CHECK_MESSAGE( foundRevisionDelta, "Expected a title block delta after changing the board revision" );
190}
191
192
193BOOST_AUTO_TEST_CASE( RoutingChangesCarryNetNamesForPresentationGrouping )
194{
195 BOARD_ITEM_SET items = m_after->GetItemSet();
196 std::map<wxString, PCB_TRACK*> tracksByNet;
197 wxString selectedNet;
198 PCB_TRACK* first = nullptr;
199 PCB_TRACK* second = nullptr;
200
201 for( BOARD_ITEM* item : items )
202 {
203 if( !item || ( item->Type() != PCB_TRACE_T && item->Type() != PCB_VIA_T ) )
204 continue;
205
206 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
207
208 if( track->GetNetCode() <= 0 || track->GetNetname().IsEmpty() )
209 continue;
210
211 wxString netName = track->GetNetname();
212
213 if( tracksByNet.contains( netName ) )
214 {
215 selectedNet = netName;
216 first = tracksByNet[netName];
217 second = track;
218 break;
219 }
220
221 tracksByNet[netName] = track;
222 }
223
224 BOOST_REQUIRE( first );
225 BOOST_REQUIRE( second );
226
227 first->SetWidth( first->GetWidth() + 25000 );
228 second->SetWidth( second->GetWidth() + 50000 );
229
230 PCB_DIFFER differ( m_before.get(), m_after.get() );
231 DOCUMENT_DIFF result = differ.Diff();
232
233 std::size_t selectedNetChanges = 0;
234
235 for( const ITEM_CHANGE& change : result.changes )
236 {
237 if( change.refdes == selectedNet )
238 {
239 ++selectedNetChanges;
240 BOOST_CHECK( change.typeName == wxS( "PCB_TRACK" ) || change.typeName == wxS( "PCB_VIA" ) );
241 }
242 }
243
244 BOOST_CHECK_EQUAL( selectedNetChanges, 2u );
245}
246
247
248BOOST_AUTO_TEST_CASE( ChildEditOnFootprintNestsUnderFootprintRecord )
249{
250 BOARD_ITEM_SET items = m_after->GetItemSet();
251 FOOTPRINT* fp = nullptr;
252
253 for( BOARD_ITEM* item : items )
254 {
255 if( item && item->Type() == PCB_FOOTPRINT_T )
256 {
257 fp = static_cast<FOOTPRINT*>( item );
258
259 if( !fp->Pads().empty() )
260 break;
261
262 fp = nullptr;
263 }
264 }
265
266 BOOST_REQUIRE( fp );
267 BOOST_REQUIRE( !fp->Pads().empty() );
268
269 PAD* pad = fp->Pads().front();
270 pad->SetNumber( pad->GetNumber() + wxS( "X" ) );
271
272 PCB_DIFFER differ( m_before.get(), m_after.get() );
273 DOCUMENT_DIFF result = differ.Diff();
274
275 // We expect exactly one parent change (the footprint), with one child change (the pad).
276 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
277 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::MODIFIED );
278 BOOST_CHECK_EQUAL( result.changes[0].id.back().AsString().ToStdString(), fp->m_Uuid.AsString().ToStdString() );
279 BOOST_REQUIRE_EQUAL( result.changes[0].children.size(), 1u );
280 BOOST_CHECK( result.changes[0].children[0].kind == CHANGE_KIND::MODIFIED );
281 BOOST_CHECK_EQUAL( result.changes[0].children[0].id.back().AsString().ToStdString(),
282 pad->m_Uuid.AsString().ToStdString() );
283}
284
285
286BOOST_AUTO_TEST_CASE( FootprintMoveDisplaysInUserUnits )
287{
288 FOOTPRINT* fp = nullptr;
289
290 for( BOARD_ITEM* item : m_after->GetItemSet() )
291 {
292 if( item && item->Type() == PCB_FOOTPRINT_T )
293 {
294 fp = static_cast<FOOTPRINT*>( item );
295 break;
296 }
297 }
298
299 BOOST_REQUIRE( fp );
300
301 const int before = fp->GetX();
302 const int after = before + pcbIUScale.mmToIU( 0.5 );
303 fp->SetX( after );
304
305 PCB_DIFFER differ( m_before.get(), m_after.get() );
306 DOCUMENT_DIFF result = differ.Diff();
307
308 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
309
311 bool found = false;
312
313 for( const PROPERTY_DELTA& d : result.changes[0].properties )
314 {
315 if( d.name != wxS( "Position X" ) )
316 continue;
317
318 const wxString expectedBefore = EDA_UNIT_UTILS::UI::StringFromValue( pcbIUScale, EDA_UNITS::MM, before, true );
319 const wxString expectedAfter = EDA_UNIT_UTILS::UI::StringFromValue( pcbIUScale, EDA_UNITS::MM, after, true );
320
321 found = true;
324 }
325
326 BOOST_CHECK( found );
327}
328
329
330BOOST_AUTO_TEST_CASE( FieldMatchesAfterUuidChanges )
331{
332 BOOST_REQUIRE( !m_before->Footprints().empty() );
333 FOOTPRINT* beforeFp = m_before->Footprints().front();
334 auto* afterFp = dynamic_cast<FOOTPRINT*>( m_after->ResolveItem( beforeFp->m_Uuid, true ) );
335 BOOST_REQUIRE( afterFp );
336
337 PCB_FIELD& beforeField = beforeFp->Value();
338 PCB_FIELD& afterField = afterFp->Value();
339 afterField.SetUuid( KIID() );
340
341 // A library refresh may assign a new UUID without changing the field itself.
342 PCB_DIFFER unchangedDiffer( m_before.get(), m_after.get() );
343 BOOST_CHECK( unchangedDiffer.Diff().Empty() );
344
345 afterField.SetText( wxS( "QA_CHANGED_VALUE" ) );
346 PCB_DIFFER differ( m_before.get(), m_after.get() );
347 DOCUMENT_DIFF diff = differ.Diff();
348
349 BOOST_REQUIRE_EQUAL( diff.changes.size(), 1u );
350 BOOST_REQUIRE_EQUAL( diff.changes[0].children.size(), 1u );
351 const ITEM_CHANGE& change = diff.changes[0].children[0];
352 BOOST_CHECK( change.kind == CHANGE_KIND::MODIFIED );
353 BOOST_REQUIRE_EQUAL( change.id.size(), 2u );
354 BOOST_CHECK( change.id.at( 0 ) == beforeFp->m_Uuid );
355
356 // The same diff ID must find each side's field despite their different actual UUIDs.
357 BOOST_CHECK( FindPcbDiffItem( m_before.get(), change.id.back() ) == &beforeField );
358 BOOST_CHECK( FindPcbDiffItem( m_after.get(), change.id.back() ) == &afterField );
359 BOOST_CHECK( FindPcbDiffItem( m_before.get(), beforeField.m_Uuid ) == &beforeField );
360 BOOST_CHECK( FindPcbDiffItem( m_after.get(), afterField.m_Uuid ) == &afterField );
361}
362
363
364BOOST_AUTO_TEST_CASE( SameNamedFieldsHaveDistinctDiffIdsAcrossFootprints )
365{
366 int changed = 0;
367
368 for( FOOTPRINT* fp : m_after->Footprints() )
369 {
370 fp->Value().SetText( wxS( "QA_VALUE_" ) + fp->GetReference() );
371
372 if( ++changed == 2 )
373 break;
374 }
375
376 BOOST_REQUIRE_EQUAL( changed, 2 );
377 PCB_DIFFER differ( m_before.get(), m_after.get() );
378 DOCUMENT_DIFF diff = differ.Diff();
379
380 BOOST_REQUIRE_EQUAL( diff.changes.size(), 2u );
381 BOOST_REQUIRE_EQUAL( diff.changes[0].children.size(), 1u );
382 BOOST_REQUIRE_EQUAL( diff.changes[1].children.size(), 1u );
383 const ITEM_CHANGE& first = diff.changes[0].children[0];
384 const ITEM_CHANGE& second = diff.changes[1].children[0];
385 BOOST_REQUIRE_EQUAL( first.id.size(), 2u );
386 BOOST_REQUIRE_EQUAL( second.id.size(), 2u );
387
388 // Canvas lookups use only the last path component, so the parent paths alone are insufficient.
389 BOOST_CHECK( first.id.back() != second.id.back() );
390
391 for( const ITEM_CHANGE& parent : diff.changes )
392 {
393 auto* fp = dynamic_cast<FOOTPRINT*>( m_after->ResolveItem( parent.id.back(), true ) );
394 BOOST_REQUIRE( fp );
395 BOOST_CHECK( FindPcbDiffItem( m_after.get(), parent.children[0].id.back() ) == &fp->Value() );
396 }
397}
398
399
400BOOST_AUTO_TEST_CASE( DiffOutputIsDeterministic )
401{
402 BOARD_ITEM_SET items = m_after->GetItemSet();
403 PCB_TRACK* subject = nullptr;
404
405 for( BOARD_ITEM* item : items )
406 {
407 if( item && item->Type() == PCB_TRACE_T )
408 {
409 subject = static_cast<PCB_TRACK*>( item );
410 break;
411 }
412 }
413
414 BOOST_REQUIRE( subject );
415 subject->SetWidth( subject->GetWidth() + 25000 );
416
417 PCB_DIFFER differ1( m_before.get(), m_after.get() );
418 PCB_DIFFER differ2( m_before.get(), m_after.get() );
419
420 DOCUMENT_DIFF r1 = differ1.Diff();
421 DOCUMENT_DIFF r2 = differ2.Diff();
422
423 BOOST_CHECK_EQUAL( r1.ToJson().dump(), r2.ToJson().dump() );
424}
425
426
427BOOST_AUTO_TEST_CASE( DiffJsonRoundTrip )
428{
429 BOARD_ITEM_SET items = m_after->GetItemSet();
430 PCB_TRACK* subject = nullptr;
431
432 for( BOARD_ITEM* item : items )
433 {
434 if( item && item->Type() == PCB_TRACE_T )
435 {
436 subject = static_cast<PCB_TRACK*>( item );
437 break;
438 }
439 }
440
441 BOOST_REQUIRE( subject );
442 subject->SetWidth( subject->GetWidth() + 30000 );
443
444 PCB_DIFFER differ( m_before.get(), m_after.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
445 DOCUMENT_DIFF result = differ.Diff();
446
447 nlohmann::json j = result.ToJson();
449
450 BOOST_CHECK_EQUAL( back.path.ToStdString(), result.path.ToStdString() );
451 BOOST_CHECK_EQUAL( back.docType.ToStdString(), result.docType.ToStdString() );
452 BOOST_REQUIRE_EQUAL( back.changes.size(), result.changes.size() );
453 BOOST_CHECK( back.changes[0].kind == result.changes[0].kind );
454 BOOST_CHECK_EQUAL( back.changes[0].properties.size(), result.changes[0].properties.size() );
455}
456
457
458BOOST_AUTO_TEST_CASE( ExtractedGeometryCarriesBoardLayers )
459{
460 const KIGFX::COLOR4D color( 0.4, 0.4, 0.4, 0.6 );
461 DOCUMENT_GEOMETRY geometry = ExtractBoardGeometry( *m_after, color );
462
463 BOOST_CHECK( !geometry.Empty() );
464
465 LSET expectedTrackLayers;
466
467 for( const PCB_TRACK* track : m_after->Tracks() )
468 {
469 if( track )
470 expectedTrackLayers |= track->GetLayerSet();
471 }
472
473 BOOST_REQUIRE( expectedTrackLayers.any() );
474
475 const LSET geometryLayers = GeometryLayerSet( geometry );
476 BOOST_CHECK( ( geometryLayers & expectedTrackLayers ).any() );
477
478 bool segmentWithLayer = false;
479
480 for( const DOCUMENT_SEGMENT& segment : geometry.segments )
481 {
482 if( segment.layers.any() )
483 {
484 segmentWithLayer = true;
485 break;
486 }
487 }
488
489 BOOST_CHECK( segmentWithLayer );
490}
491
492
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
std::set< BOARD_ITEM *, CompareByUuid > BOARD_ITEM_SET
Set of BOARD_ITEMs ordered by UUID.
Definition board.h:395
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
void SetUuid(const KIID &aUuid)
int GetX() const
Definition board_item.h:134
void SetX(int aX)
Definition board_item.h:156
const KIID m_Uuid
Definition eda_item.h:599
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:831
std::deque< PAD * > & Pads()
Definition footprint.h:355
wxString ToDisplayString() const
Human-readable representation with no unit context.
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.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
Definition kiid.h:46
wxString AsString() const
Definition kiid.cpp:264
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
Definition pad.h:61
virtual void SetWidth(int aWidth)
Definition pcb_track.h:91
virtual int GetWidth() const
Definition pcb_track.h:92
A type-safe container of any type.
Definition ki_any.h:92
KICOMMON_API wxString StringFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Return the string from aValue according to aUnits (inch, mm ...) for display.
LSET GeometryLayerSet(const DOCUMENT_GEOMETRY &aGeometry)
Return the union of every non-empty layer set carried by the geometry.
const EDA_IU_SCALE & IuScaleForDocKind(DOC_KIND aKind)
Internal-unit scale a document kind's coordinates are expressed in.
Definition diff_scene.h:256
const wxString DOC_PROP_REVISION
BOARD_ITEM * FindPcbDiffItem(const BOARD *aBoard, const KIID &aId)
Resolve an actual UUID or a footprint field's diff ID on a board.
DOCUMENT_GEOMETRY ExtractBoardGeometry(const BOARD &aBoard, const KIGFX::COLOR4D &aColor)
Extract a coarse outline of a BOARD into a DOCUMENT_GEOMETRY for use as background context in DIFF_SC...
DOC_KIND DocKindFromDocType(const wxString &aDocType)
Map a DOCUMENT_DIFF::docType tag written by the differs ("kicad_pcb", "kicad_sch",...
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
const int scale
The full set of changes between two parsed documents of one type.
nlohmann::json ToJson() const
static DOCUMENT_DIFF FromJson(const nlohmann::json &aJson)
std::vector< ITEM_CHANGE > changes
Aggregate of background geometry extracted from one source document.
Definition diff_scene.h:163
std::vector< DOCUMENT_SEGMENT > segments
Definition diff_scene.h:164
Stroked line segment from one of the source documents.
Definition diff_scene.h:117
One change record on a single item.
std::optional< wxString > refdes
std::vector< ITEM_CHANGE > children
Single (name, before, after) triple for one mutated property on an item.
Fixture: loads the same canonical board into two separate BOARD instances so tests can mutate one and...
std::unique_ptr< BOARD > m_before
SETTINGS_MANAGER m_settingsA
SETTINGS_MANAGER m_settingsB
std::unique_ptr< BOARD > m_after
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(TwoFreshLoadsAreIdentical)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88