KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_sym_lib_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 <lib_symbol.h>
32#include <sch_item.h>
33#include <sch_pin.h>
34#include <sch_shape.h>
35
36#include <nlohmann/json.hpp>
37
38#include <wx/filename.h>
39
40
41using namespace KICAD_DIFF;
42
43
44BOOST_AUTO_TEST_SUITE( SymLibDiffer )
45
46
47static wxString getFixturePath()
48{
49 // qa/data/eeschema/.. -> qa/data/libraries/Device.kicad_sym
50 wxFileName fn( KI_TEST::GetEeschemaTestDataDir(), wxEmptyString );
51 fn.RemoveLastDir();
52 fn.AppendDir( wxS( "libraries" ) );
53 fn.SetFullName( wxS( "Device.kicad_sym" ) );
54 return fn.GetFullPath();
55}
56
57
58BOOST_AUTO_TEST_CASE( IdenticalLibrariesDiffEmpty )
59{
60 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
61 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
62
63 BOOST_REQUIRE( !mapA.empty() );
64
65 SYM_LIB_DIFFER differ( mapA, mapB, wxS( "Device.kicad_sym" ) );
66 DOCUMENT_DIFF result = differ.Diff();
67
68 BOOST_CHECK( result.Empty() );
69 BOOST_CHECK_EQUAL( result.docType.ToStdString(), "kicad_sym" );
70}
71
72
73BOOST_AUTO_TEST_CASE( SymbolNameOnlyInBeforeShowsRemoved )
74{
75 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
76 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
77
78 // Drop one symbol from after to simulate removal.
79 BOOST_REQUIRE( !mapB.empty() );
80 wxString victimName = mapB.begin()->first;
81 mapB.erase( mapB.begin() );
82
83 SYM_LIB_DIFFER differ( mapA, mapB );
84 DOCUMENT_DIFF result = differ.Diff();
85
86 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
87 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::REMOVED );
88 BOOST_CHECK_EQUAL( result.changes[0].typeName.ToStdString(), "LIB_SYMBOL" );
89 // Identity for library symbols is name-derived; the KIID_PATH carries a
90 // synthetic KIID hash of the name rather than a registered UUID. We
91 // only assert the change exists, not the exact id encoding.
92 (void) victimName;
93}
94
95
96BOOST_AUTO_TEST_CASE( SymbolChangesCarryDrawableBBoxes )
97{
98 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
99 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
100
101 BOOST_REQUIRE( !ownersB.empty() );
102 LIB_SYMBOL* subject = ownersB.front().get();
103 BOOST_REQUIRE( !subject->GetDrawItems().empty() );
104
105 SCH_ITEM& firstItem = *subject->GetDrawItems().begin();
106 firstItem.Move( VECTOR2I( 1000, 0 ) );
107
108 SYM_LIB_DIFFER differ( mapA, mapB );
109 DOCUMENT_DIFF result = differ.Diff();
110
111 BOOST_REQUIRE_EQUAL( result.changes.size(), 1u );
112 BOOST_CHECK( result.changes[0].kind == CHANGE_KIND::MODIFIED );
113 BOOST_CHECK_GT( result.changes[0].bbox.GetWidth(), 0 );
114 BOOST_CHECK_GT( result.changes[0].bbox.GetHeight(), 0 );
115}
116
117
118BOOST_AUTO_TEST_CASE( ExtractSymbolGeometryProducesDrawableContext )
119{
120 auto [owners, map] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
121
122 BOOST_REQUIRE( !owners.empty() );
123
124 DOCUMENT_GEOMETRY geometry = ExtractSymbolGeometry( *owners.front(), KIGFX::COLOR4D( 0.38, 0.38, 0.38, 0.55 ) );
125
126 BOOST_CHECK( !geometry.Empty() );
127 BOOST_CHECK( BBoxFromGeometry( geometry ).has_value() );
128}
129
130
131BOOST_AUTO_TEST_CASE( DiffJsonRoundTrip )
132{
133 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
134 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
135
136 if( !mapB.empty() )
137 mapB.erase( mapB.begin() );
138
139 SYM_LIB_DIFFER differ( mapA, mapB, wxS( "lib.kicad_sym" ) );
140 DOCUMENT_DIFF result = differ.Diff();
141
142 nlohmann::json j = result.ToJson();
144 BOOST_CHECK_EQUAL( back.changes.size(), result.changes.size() );
145 BOOST_CHECK_EQUAL( back.docType.ToStdString(), "kicad_sym" );
146}
147
148
149// Library item KIID_PATH must be deterministic across calls -- LibraryItemKiidPath
150// is a hash-based synthetic UUID, and a regression that swaps the hash
151// algorithm would silently desync differ <-> applier KIID matching.
152BOOST_AUTO_TEST_CASE( LibraryItemKiidPathIsDeterministicForSameName )
153{
154 KIID_PATH a = LibraryItemKiidPath( wxS( "Device:R" ) );
155 KIID_PATH b = LibraryItemKiidPath( wxS( "Device:R" ) );
156
157 BOOST_REQUIRE_EQUAL( a.size(), 1u );
158 BOOST_REQUIRE_EQUAL( b.size(), 1u );
159 BOOST_CHECK( a == b );
160}
161
162
163BOOST_AUTO_TEST_CASE( LibraryItemKiidPathDiffersForDifferentNames )
164{
165 KIID_PATH a = LibraryItemKiidPath( wxS( "Device:R" ) );
166 KIID_PATH b = LibraryItemKiidPath( wxS( "Device:C" ) );
167
168 BOOST_CHECK( !( a == b ) );
169}
170
171
172// Multi-symbol library: every symbol must surface as its own change record
173// when removed. Currently the smoke test removes one symbol; here we
174// remove three and verify all three changes are emitted.
175BOOST_AUTO_TEST_CASE( MultipleSymbolRemovalsAllEmitChanges )
176{
177 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
178 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
179
180 BOOST_REQUIRE_GE( mapB.size(), 3u );
181
182 std::vector<wxString> victims;
183
184 for( int i = 0; i < 3 && !mapB.empty(); ++i )
185 {
186 victims.push_back( mapB.begin()->first );
187 mapB.erase( mapB.begin() );
188 }
189
190 SYM_LIB_DIFFER differ( mapA, mapB );
191 DOCUMENT_DIFF result = differ.Diff();
192
193 BOOST_CHECK_EQUAL( result.changes.size(), victims.size() );
194
195 for( const ITEM_CHANGE& c : result.changes )
196 {
197 BOOST_CHECK( c.kind == CHANGE_KIND::REMOVED );
198 BOOST_CHECK_EQUAL( c.typeName.ToStdString(), "LIB_SYMBOL" );
199 }
200}
201
202
203// Output ordering must be deterministic for the same input pair so JSON
204// diffs are byte-stable across runs.
205BOOST_AUTO_TEST_CASE( OutputOrderingIsDeterministicAcrossRuns )
206{
207 auto [ownersA1, mapA1] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
208 auto [ownersB1, mapB1] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
209 auto [ownersA2, mapA2] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
210 auto [ownersB2, mapB2] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
211
212 BOOST_REQUIRE_GE( mapB1.size(), 2u );
213
214 // Same mutation on both run pairs.
215 mapB1.erase( mapB1.begin() );
216 mapB2.erase( mapB2.begin() );
217
218 SYM_LIB_DIFFER differ1( mapA1, mapB1 );
219 SYM_LIB_DIFFER differ2( mapA2, mapB2 );
220
221 DOCUMENT_DIFF r1 = differ1.Diff();
222 DOCUMENT_DIFF r2 = differ2.Diff();
223
224 BOOST_CHECK_EQUAL( r1.ToJson().dump(), r2.ToJson().dump() );
225}
226
227
228BOOST_AUTO_TEST_CASE( ModifiedSymbolCarriesPinChildDelta )
229{
230 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
231 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
232
233 LIB_SYMBOL* victim = nullptr;
234
235 for( const std::unique_ptr<LIB_SYMBOL>& owner : ownersB )
236 {
237 if( owner && !owner->IsDerived() && !owner->GetPins().empty() )
238 {
239 victim = owner.get();
240 break;
241 }
242 }
243
244 BOOST_REQUIRE( victim );
245
246 victim->GetPins().front()->Move( VECTOR2I( 1270, 0 ) );
247
248 SYM_LIB_DIFFER differ( mapA, mapB );
249 DOCUMENT_DIFF result = differ.Diff();
250
251 const ITEM_CHANGE* change = nullptr;
252
253 for( const ITEM_CHANGE& c : result.changes )
254 {
255 if( c.kind == CHANGE_KIND::MODIFIED && c.refdes && *c.refdes == victim->GetName() )
256 {
257 change = &c;
258 break;
259 }
260 }
261
262 BOOST_REQUIRE( change );
263
264 bool foundPinChild = false;
265
266 for( const ITEM_CHANGE& child : change->children )
267 {
268 if( child.typeName == wxS( "Pin" ) && !child.properties.empty() )
269 foundPinChild = true;
270 }
271
272 BOOST_CHECK( foundPinChild );
273}
274
275
276BOOST_AUTO_TEST_CASE( PinNameChangeIsDetected )
277{
278 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
279 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
280
281 LIB_SYMBOL* victim = nullptr;
282
283 for( const std::unique_ptr<LIB_SYMBOL>& owner : ownersB )
284 {
285 if( owner && !owner->IsDerived() && !owner->GetPins().empty() )
286 {
287 victim = owner.get();
288 break;
289 }
290 }
291
292 BOOST_REQUIRE( victim );
293
294 victim->GetPins().front()->SetName( wxS( "QA_RENAMED_PIN" ) );
295
296 SYM_LIB_DIFFER differ( mapA, mapB );
297 DOCUMENT_DIFF result = differ.Diff();
298
299 const ITEM_CHANGE* change = nullptr;
300
301 for( const ITEM_CHANGE& c : result.changes )
302 {
303 if( c.kind == CHANGE_KIND::MODIFIED && c.refdes && *c.refdes == victim->GetName() )
304 {
305 change = &c;
306 break;
307 }
308 }
309
310 BOOST_REQUIRE( change );
311
312 bool foundPinDelta = false;
313
314 for( const ITEM_CHANGE& child : change->children )
315 {
316 if( child.typeName == wxS( "Pin" ) && !child.properties.empty() )
317 foundPinDelta = true;
318 }
319
320 BOOST_CHECK( foundPinDelta );
321}
322
323
324BOOST_AUTO_TEST_CASE( AddedGraphicYieldsSingleAddedElement )
325{
326 auto [ownersA, mapA] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
327 auto [ownersB, mapB] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
328
329 LIB_SYMBOL* victim = nullptr;
330
331 for( const std::unique_ptr<LIB_SYMBOL>& owner : ownersB )
332 {
333 if( owner && !owner->IsDerived() )
334 {
335 victim = owner.get();
336 break;
337 }
338 }
339
340 BOOST_REQUIRE( victim );
341
343 rect->SetStart( VECTOR2I( 5080, 5080 ) );
344 rect->SetEnd( VECTOR2I( 7620, 7620 ) );
345 victim->AddDrawItem( rect );
346
347 SYM_LIB_DIFFER differ( mapA, mapB );
348 DOCUMENT_DIFF result = differ.Diff();
349
350 const ITEM_CHANGE* change = nullptr;
351
352 for( const ITEM_CHANGE& c : result.changes )
353 {
354 if( c.kind == CHANGE_KIND::MODIFIED && c.refdes && *c.refdes == victim->GetName() )
355 {
356 change = &c;
357 break;
358 }
359 }
360
361 BOOST_REQUIRE( change );
362
363 int added = 0;
364 int removed = 0;
365 int modified = 0;
366
367 for( const ITEM_CHANGE& child : change->children )
368 {
369 if( child.kind == CHANGE_KIND::ADDED )
370 added++;
371 else if( child.kind == CHANGE_KIND::REMOVED )
372 removed++;
373 else if( child.kind == CHANGE_KIND::MODIFIED )
374 modified++;
375 }
376
377 BOOST_CHECK_EQUAL( added, 1 );
378 BOOST_CHECK_EQUAL( removed, 0 );
379 BOOST_CHECK_EQUAL( modified, 0 );
380}
381
382
383BOOST_AUTO_TEST_CASE( DerivedSymbolKeepsResolvedParentAfterLoad )
384{
385 auto [owners, map] = SYM_LIB_DIFFER::LoadLibrary( getFixturePath() );
386
387 int checked = 0;
388
389 for( const std::unique_ptr<LIB_SYMBOL>& sym : owners )
390 {
391 if( sym->GetParentName().IsEmpty() )
392 continue;
393
394 BOOST_TEST_INFO( sym->GetName().ToStdString() );
395 BOOST_CHECK( sym->IsDerived() );
396
397 std::shared_ptr<LIB_SYMBOL> parent = sym->GetParent().lock();
398 BOOST_REQUIRE( parent );
399 BOOST_CHECK_EQUAL( parent->GetName(), sym->GetParentName() );
400
401 checked++;
402 }
403
404 BOOST_CHECK( checked > 0 );
405}
406
407
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:194
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:236
Diff two .kicad_sym symbol libraries.
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
static std::pair< std::vector< std::unique_ptr< LIB_SYMBOL > >, SYMBOL_MAP > LoadLibrary(const wxString &aPath)
Convenience: load a .kicad_sym path into a SYMBOL_MAP using SCH_IO_KICAD_SEXPR::EnumerateSymbolLib.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
Define a library symbol object.
Definition lib_symbol.h:79
LIB_ITEMS_CONTAINER & GetDrawItems()
Return a reference to the draw item list.
Definition lib_symbol.h:709
wxString GetName() const override
Definition lib_symbol.h:141
std::vector< SCH_PIN * > GetPins() const override
void AddDrawItem(SCH_ITEM *aItem, bool aSort=true)
Add a new draw aItem to the draw object list and sort according to aSort.
bool empty(int aType=UNDEFINED_TYPE) const
ITERATOR begin(int aType=UNDEFINED_TYPE)
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
virtual void Move(const VECTOR2I &aMoveVector)
Move the item by aMoveVector to a new position.
Definition sch_item.h:396
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
DOCUMENT_GEOMETRY ExtractSymbolGeometry(const LIB_SYMBOL &aSymbol, const KIGFX::COLOR4D &aColor, int aUnit, int aBodyStyle)
Extract coarse drawable context from a library symbol for visual symbol diffs.
KIID_PATH LibraryItemKiidPath(const wxString &aName)
Build a deterministic synthetic KIID_PATH from a library item name (symbol name or footprint name).
std::optional< BOX2I > BBoxFromGeometry(const DOCUMENT_GEOMETRY &aGeometry)
Compute the tight bounding box of a DOCUMENT_GEOMETRY, inflating each primitive by half its stroke so...
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
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
One change record on a single item.
std::vector< PROPERTY_DELTA > properties
std::optional< wxString > refdes
std::vector< ITEM_CHANGE > children
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_INFO("Two-port Series .op current = "<< iDevice)
static wxString getFixturePath()
BOOST_AUTO_TEST_CASE(IdenticalLibrariesDiffEmpty)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683