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
29
30#include <board.h>
31#include <board_item.h>
32#include <pad.h>
33#include <pcb_field.h>
34#include <pcb_track.h>
35#include <footprint.h>
37
38#include <json_common.h>
39
40#include <map>
41
42
43using namespace KICAD_DIFF;
44
45
55{
57 {
58 KI_TEST::LoadBoard( m_settingsA, "complex_hierarchy", m_before );
59 KI_TEST::LoadBoard( m_settingsB, "complex_hierarchy", m_after );
60
63 }
64
67 std::unique_ptr<BOARD> m_before;
68 std::unique_ptr<BOARD> m_after;
69};
70
71
72BOOST_FIXTURE_TEST_SUITE( PcbDiffer, PCB_DIFFER_FIXTURE )
73
74
75BOOST_AUTO_TEST_CASE( TwoFreshLoadsAreIdentical )
76{
77 PCB_DIFFER differ( m_before.get(), m_after.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
78 DOCUMENT_DIFF result = differ.Diff();
79
80 BOOST_CHECK_EQUAL( result.docType.ToStdString(), "kicad_pcb" );
81 BOOST_CHECK_MESSAGE( result.Empty(), "Two fresh loads of the same fixture should produce no diff; "
82 "got " << result.changes.size()
83 << " change records" );
84}
85
86
87BOOST_AUTO_TEST_CASE( RemovingAnItemSurfacesAsRemoved )
88{
89 BOARD_ITEM_SET items = m_after->GetItemSet();
90 BOOST_REQUIRE( !items.empty() );
91
92 // Pick the first track from the fixture and remove it from the after-board.
93 PCB_TRACK* victim = nullptr;
94
95 for( BOARD_ITEM* item : items )
96 {
97 if( item && item->Type() == PCB_TRACE_T )
98 {
99 victim = static_cast<PCB_TRACK*>( item );
100 break;
101 }
102 }
103
104 BOOST_REQUIRE( victim );
105 KIID victimUuid = victim->m_Uuid;
106 m_after->Remove( victim, REMOVE_MODE::NORMAL );
107
108 PCB_DIFFER differ( m_before.get(), m_after.get() );
109 DOCUMENT_DIFF result = differ.Diff();
110
111 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
112 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::REMOVED );
113 BOOST_CHECK_EQUAL( result.changes[0].id.back().AsString().ToStdString(), victimUuid.AsString().ToStdString() );
114
115 delete victim;
116}
117
118
119BOOST_AUTO_TEST_CASE( WidthChangeProducesPropertyDelta )
120{
121 BOARD_ITEM_SET items = m_after->GetItemSet();
122 PCB_TRACK* subject = nullptr;
123
124 for( BOARD_ITEM* item : items )
125 {
126 if( item && item->Type() == PCB_TRACE_T )
127 {
128 subject = static_cast<PCB_TRACK*>( item );
129 break;
130 }
131 }
132
133 BOOST_REQUIRE( subject );
134
135 int originalWidth = subject->GetWidth();
136 int newWidth = originalWidth + 50000;
137 subject->SetWidth( newWidth );
138
139 PCB_DIFFER differ( m_before.get(), m_after.get() );
140 DOCUMENT_DIFF result = differ.Diff();
141
142 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
143 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::MODIFIED );
144 BOOST_CHECK_EQUAL( result.changes[0].id.back().AsString().ToStdString(), subject->m_Uuid.AsString().ToStdString() );
145
146 bool foundWidthDelta = false;
147
148 for( const PROPERTY_DELTA& d : result.changes[0].properties )
149 {
150 if( d.name.Lower().Contains( wxS( "width" ) ) )
151 {
152 foundWidthDelta = true;
153 BOOST_CHECK_EQUAL( d.before.AsInt(), originalWidth );
154 BOOST_CHECK_EQUAL( d.after.AsInt(), newWidth );
155 }
156 }
157
158 BOOST_CHECK_MESSAGE( foundWidthDelta, "Expected Width property delta after SetWidth() on a track" );
159}
160
161
162BOOST_AUTO_TEST_CASE( RoutingChangesCarryNetNamesForPresentationGrouping )
163{
164 BOARD_ITEM_SET items = m_after->GetItemSet();
165 std::map<wxString, PCB_TRACK*> tracksByNet;
166 wxString selectedNet;
167 PCB_TRACK* first = nullptr;
168 PCB_TRACK* second = nullptr;
169
170 for( BOARD_ITEM* item : items )
171 {
172 if( !item || ( item->Type() != PCB_TRACE_T && item->Type() != PCB_VIA_T ) )
173 continue;
174
175 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
176
177 if( track->GetNetCode() <= 0 || track->GetNetname().IsEmpty() )
178 continue;
179
180 wxString netName = track->GetNetname();
181
182 if( tracksByNet.contains( netName ) )
183 {
184 selectedNet = netName;
185 first = tracksByNet[netName];
186 second = track;
187 break;
188 }
189
190 tracksByNet[netName] = track;
191 }
192
193 BOOST_REQUIRE( first );
194 BOOST_REQUIRE( second );
195
196 first->SetWidth( first->GetWidth() + 25000 );
197 second->SetWidth( second->GetWidth() + 50000 );
198
199 PCB_DIFFER differ( m_before.get(), m_after.get() );
200 DOCUMENT_DIFF result = differ.Diff();
201
202 std::size_t selectedNetChanges = 0;
203
204 for( const ITEM_CHANGE& change : result.changes )
205 {
206 if( change.refdes == selectedNet )
207 {
208 ++selectedNetChanges;
209 BOOST_CHECK( change.typeName == wxS( "PCB_TRACK" ) || change.typeName == wxS( "PCB_VIA" ) );
210 }
211 }
212
213 BOOST_CHECK_EQUAL( selectedNetChanges, 2u );
214}
215
216
217BOOST_AUTO_TEST_CASE( ChildEditOnFootprintNestsUnderFootprintRecord )
218{
219 BOARD_ITEM_SET items = m_after->GetItemSet();
220 FOOTPRINT* fp = nullptr;
221
222 for( BOARD_ITEM* item : items )
223 {
224 if( item && item->Type() == PCB_FOOTPRINT_T )
225 {
226 fp = static_cast<FOOTPRINT*>( item );
227
228 if( !fp->Pads().empty() )
229 break;
230
231 fp = nullptr;
232 }
233 }
234
235 BOOST_REQUIRE( fp );
236 BOOST_REQUIRE( !fp->Pads().empty() );
237
238 PAD* pad = fp->Pads().front();
239 pad->SetNumber( pad->GetNumber() + wxS( "X" ) );
240
241 PCB_DIFFER differ( m_before.get(), m_after.get() );
242 DOCUMENT_DIFF result = differ.Diff();
243
244 // We expect exactly one parent change (the footprint), with one child change (the pad).
245 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
246 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::MODIFIED );
247 BOOST_CHECK_EQUAL( result.changes[0].id.back().AsString().ToStdString(), fp->m_Uuid.AsString().ToStdString() );
248 BOOST_REQUIRE_EQUAL( result.changes[0].children.size(), 1u );
249 BOOST_CHECK( result.changes[0].children[0].kind == CHANGE_KIND::MODIFIED );
250 BOOST_CHECK_EQUAL( result.changes[0].children[0].id.back().AsString().ToStdString(),
251 pad->m_Uuid.AsString().ToStdString() );
252}
253
254
255BOOST_AUTO_TEST_CASE( FieldMatchesAfterUuidChanges )
256{
257 BOOST_REQUIRE( !m_before->Footprints().empty() );
258 FOOTPRINT* beforeFp = m_before->Footprints().front();
259 auto* afterFp = dynamic_cast<FOOTPRINT*>( m_after->ResolveItem( beforeFp->m_Uuid, true ) );
260 BOOST_REQUIRE( afterFp );
261
262 PCB_FIELD& beforeField = beforeFp->Value();
263 PCB_FIELD& afterField = afterFp->Value();
264 afterField.SetUuid( KIID() );
265
266 // A library refresh may assign a new UUID without changing the field itself.
267 PCB_DIFFER unchangedDiffer( m_before.get(), m_after.get() );
268 BOOST_CHECK( unchangedDiffer.Diff().Empty() );
269
270 afterField.SetText( wxS( "QA_CHANGED_VALUE" ) );
271 PCB_DIFFER differ( m_before.get(), m_after.get() );
272 DOCUMENT_DIFF diff = differ.Diff();
273
274 BOOST_REQUIRE_EQUAL( diff.changes.size(), 1u );
275 BOOST_REQUIRE_EQUAL( diff.changes[0].children.size(), 1u );
276 const ITEM_CHANGE& change = diff.changes[0].children[0];
277 BOOST_CHECK( change.kind == CHANGE_KIND::MODIFIED );
278 BOOST_REQUIRE_EQUAL( change.id.size(), 2u );
279 BOOST_CHECK( change.id.at( 0 ) == beforeFp->m_Uuid );
280
281 // The same diff ID must find each side's field despite their different actual UUIDs.
282 BOOST_CHECK( FindPcbDiffItem( m_before.get(), change.id.back() ) == &beforeField );
283 BOOST_CHECK( FindPcbDiffItem( m_after.get(), change.id.back() ) == &afterField );
284 BOOST_CHECK( FindPcbDiffItem( m_before.get(), beforeField.m_Uuid ) == &beforeField );
285 BOOST_CHECK( FindPcbDiffItem( m_after.get(), afterField.m_Uuid ) == &afterField );
286}
287
288
289BOOST_AUTO_TEST_CASE( SameNamedFieldsHaveDistinctDiffIdsAcrossFootprints )
290{
291 int changed = 0;
292
293 for( FOOTPRINT* fp : m_after->Footprints() )
294 {
295 fp->Value().SetText( wxS( "QA_VALUE_" ) + fp->GetReference() );
296
297 if( ++changed == 2 )
298 break;
299 }
300
301 BOOST_REQUIRE_EQUAL( changed, 2 );
302 PCB_DIFFER differ( m_before.get(), m_after.get() );
303 DOCUMENT_DIFF diff = differ.Diff();
304
305 BOOST_REQUIRE_EQUAL( diff.changes.size(), 2u );
306 BOOST_REQUIRE_EQUAL( diff.changes[0].children.size(), 1u );
307 BOOST_REQUIRE_EQUAL( diff.changes[1].children.size(), 1u );
308 const ITEM_CHANGE& first = diff.changes[0].children[0];
309 const ITEM_CHANGE& second = diff.changes[1].children[0];
310 BOOST_REQUIRE_EQUAL( first.id.size(), 2u );
311 BOOST_REQUIRE_EQUAL( second.id.size(), 2u );
312
313 // Canvas lookups use only the last path component, so the parent paths alone are insufficient.
314 BOOST_CHECK( first.id.back() != second.id.back() );
315
316 for( const ITEM_CHANGE& parent : diff.changes )
317 {
318 auto* fp = dynamic_cast<FOOTPRINT*>( m_after->ResolveItem( parent.id.back(), true ) );
319 BOOST_REQUIRE( fp );
320 BOOST_CHECK( FindPcbDiffItem( m_after.get(), parent.children[0].id.back() ) == &fp->Value() );
321 }
322}
323
324
325BOOST_AUTO_TEST_CASE( DiffOutputIsDeterministic )
326{
327 BOARD_ITEM_SET items = m_after->GetItemSet();
328 PCB_TRACK* subject = nullptr;
329
330 for( BOARD_ITEM* item : items )
331 {
332 if( item && item->Type() == PCB_TRACE_T )
333 {
334 subject = static_cast<PCB_TRACK*>( item );
335 break;
336 }
337 }
338
339 BOOST_REQUIRE( subject );
340 subject->SetWidth( subject->GetWidth() + 25000 );
341
342 PCB_DIFFER differ1( m_before.get(), m_after.get() );
343 PCB_DIFFER differ2( m_before.get(), m_after.get() );
344
345 DOCUMENT_DIFF r1 = differ1.Diff();
346 DOCUMENT_DIFF r2 = differ2.Diff();
347
348 BOOST_CHECK_EQUAL( r1.ToJson().dump(), r2.ToJson().dump() );
349}
350
351
352BOOST_AUTO_TEST_CASE( DiffJsonRoundTrip )
353{
354 BOARD_ITEM_SET items = m_after->GetItemSet();
355 PCB_TRACK* subject = nullptr;
356
357 for( BOARD_ITEM* item : items )
358 {
359 if( item && item->Type() == PCB_TRACE_T )
360 {
361 subject = static_cast<PCB_TRACK*>( item );
362 break;
363 }
364 }
365
366 BOOST_REQUIRE( subject );
367 subject->SetWidth( subject->GetWidth() + 30000 );
368
369 PCB_DIFFER differ( m_before.get(), m_after.get(), wxS( "complex_hierarchy.kicad_pcb" ) );
370 DOCUMENT_DIFF result = differ.Diff();
371
372 nlohmann::json j = result.ToJson();
374
375 BOOST_CHECK_EQUAL( back.path.ToStdString(), result.path.ToStdString() );
376 BOOST_CHECK_EQUAL( back.docType.ToStdString(), result.docType.ToStdString() );
377 BOOST_REQUIRE_EQUAL( back.changes.size(), result.changes.size() );
378 BOOST_CHECK( back.changes[0].kind == result.changes[0].kind );
379 BOOST_CHECK_EQUAL( back.changes[0].properties.size(), result.changes[0].properties.size() );
380}
381
382
383BOOST_AUTO_TEST_CASE( ExtractedGeometryCarriesBoardLayers )
384{
385 const KIGFX::COLOR4D color( 0.4, 0.4, 0.4, 0.6 );
386 DOCUMENT_GEOMETRY geometry = ExtractBoardGeometry( *m_after, color );
387
388 BOOST_CHECK( !geometry.Empty() );
389
390 LSET expectedTrackLayers;
391
392 for( const PCB_TRACK* track : m_after->Tracks() )
393 {
394 if( track )
395 expectedTrackLayers |= track->GetLayerSet();
396 }
397
398 BOOST_REQUIRE( expectedTrackLayers.any() );
399
400 const LSET geometryLayers = GeometryLayerSet( geometry );
401 BOOST_CHECK( ( geometryLayers & expectedTrackLayers ).any() );
402
403 bool segmentWithLayer = false;
404
405 for( const DOCUMENT_SEGMENT& segment : geometry.segments )
406 {
407 if( segment.layers.any() )
408 {
409 segmentWithLayer = true;
410 break;
411 }
412 }
413
414 BOOST_CHECK( segmentWithLayer );
415}
416
417
std::set< BOARD_ITEM *, CompareByUuid > BOARD_ITEM_SET
Set of BOARD_ITEMs ordered by UUID.
Definition board.h:394
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
void SetUuid(const KIID &aUuid)
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:947
std::deque< PAD * > & Pads()
Definition footprint.h:405
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
LSET GeometryLayerSet(const DOCUMENT_GEOMETRY &aGeometry)
Return the union of every non-empty layer set carried by the geometry.
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...
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.
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