KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_diff_types.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
24#include <boost/test/unit_test.hpp>
25
27
28#include <nlohmann/json.hpp>
29
30#include <cstdio>
31
32#ifndef _WIN32
33#include <unistd.h>
34#endif
35
36
37using namespace KICAD_DIFF;
38
39
40BOOST_AUTO_TEST_SUITE( DiffTypes )
41
42
43// Helper: round-trip a value through JSON and verify equality
44static void roundTrip( const DIFF_VALUE& aValue )
45{
46 nlohmann::json j = aValue.ToJson();
48 BOOST_CHECK_MESSAGE( out == aValue,
49 "DIFF_VALUE round-trip mismatch for type "
50 << static_cast<int>( aValue.GetType() ) );
51
52 // Bit-identical JSON string is the strongest guarantee — same JSON twice in a row.
53 nlohmann::json j2 = out.ToJson();
54 BOOST_CHECK_EQUAL( j.dump(), j2.dump() );
55}
56
57
58BOOST_AUTO_TEST_CASE( DiffValueNone )
59{
60 DIFF_VALUE v;
61 BOOST_CHECK( v.GetType() == DIFF_VALUE::T::NONE );
62 roundTrip( v );
63}
64
65
66BOOST_AUTO_TEST_CASE( DiffValueBool )
67{
70 BOOST_CHECK( DIFF_VALUE::FromBool( true ) != DIFF_VALUE::FromBool( false ) );
71}
72
73
74BOOST_AUTO_TEST_CASE( DiffValueInt )
75{
78 roundTrip( DIFF_VALUE::FromInt( 2'147'483'647 ) );
79}
80
81
82BOOST_AUTO_TEST_CASE( DiffValueInt64 )
83{
85 roundTrip( DIFF_VALUE::FromInt64( 9'223'372'036'854'775'807LL ) );
86 roundTrip( DIFF_VALUE::FromInt64( -9'223'372'036'854'775'807LL ) );
87}
88
89
90BOOST_AUTO_TEST_CASE( DiffValueDouble )
91{
93 roundTrip( DIFF_VALUE::FromDouble( 3.141592653589793 ) );
95}
96
97
98BOOST_AUTO_TEST_CASE( DiffValueString )
99{
100 roundTrip( DIFF_VALUE::FromString( std::string( "hello" ) ) );
101 roundTrip( DIFF_VALUE::FromString( wxString::FromUTF8( "héllo wörld" ) ) );
102 roundTrip( DIFF_VALUE::FromString( std::string( "" ) ) );
103}
104
105
106BOOST_AUTO_TEST_CASE( DiffValueKiid )
107{
109 KIID id;
111}
112
113
114BOOST_AUTO_TEST_CASE( DiffValueVector2I )
115{
118 roundTrip( DIFF_VALUE::FromVector2I( VECTOR2I( 1'000'000, -1'000'000 ) ) );
119}
120
121
122BOOST_AUTO_TEST_CASE( DiffValueBox2I )
123{
124 BOX2I b( VECTOR2I( 10, 20 ), VECTOR2I( 100, 200 ) );
126}
127
128
129BOOST_AUTO_TEST_CASE( DiffValueColor )
130{
131 roundTrip( DIFF_VALUE::FromColor( KIGFX::COLOR4D( 0.1, 0.2, 0.3, 0.4 ) ) );
132 roundTrip( DIFF_VALUE::FromColor( KIGFX::COLOR4D( 0.0, 0.0, 0.0, 0.0 ) ) );
133 roundTrip( DIFF_VALUE::FromColor( KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 ) ) );
134}
135
136
143
144
145BOOST_AUTO_TEST_CASE( DiffValueEnum )
146{
147 roundTrip( DIFF_VALUE::FromEnum( 7, "SEVEN" ) );
149}
150
151
152BOOST_AUTO_TEST_CASE( DiffValuePolygonSet )
153{
155
156 // One polygon: a square outline with a smaller square hole.
157 std::vector<VECTOR2I> outline = { { 0, 0 }, { 100, 0 }, { 100, 100 }, { 0, 100 } };
158 std::vector<VECTOR2I> hole = { { 25, 25 }, { 75, 25 }, { 75, 75 }, { 25, 75 } };
159 ps.push_back( { outline, hole } );
160
161 // A second, hole-free triangle in a different location.
162 ps.push_back( { { { 200, 200 }, { 300, 200 }, { 250, 300 } } } );
163
165
167 BOOST_CHECK( v.GetType() == DIFF_VALUE::T::POLYGON_SET );
168 BOOST_CHECK( v.AsPolygonSet() == ps );
169}
170
171
172BOOST_AUTO_TEST_CASE( DiffValueEmptyPolygonSet )
173{
175}
176
177
178BOOST_AUTO_TEST_CASE( PropertyDeltaRoundTrip )
179{
181 d.name = wxS( "Position" );
183 d.after = DIFF_VALUE::FromVector2I( VECTOR2I( 1000, 2000 ) );
184
185 nlohmann::json j = d.ToJson();
187 BOOST_CHECK( out == d );
188}
189
190
191BOOST_AUTO_TEST_CASE( PropertyDeltaRoundTripAsymmetric )
192{
193 // Asymmetric before/after slots: NONE means the property was absent on
194 // that side. Each row exercises one real-world delta shape so a
195 // serializer that collapses NONE to a default value or drops the type
196 // discriminator trips on the per-half GetType() checks.
197 struct CASE
198 {
199 const char* label;
200 wxString name;
201 DIFF_VALUE before;
202 DIFF_VALUE after;
203 DIFF_VALUE::T beforeType;
204 DIFF_VALUE::T afterType;
205 };
206
207 const CASE cases[] = {
208 // Optional property newly set.
209 { "added", wxS( "SolderMaskMargin" ), DIFF_VALUE(),
212 // Optional property cleared.
213 { "removed", wxS( "SolderMaskMargin" ), DIFF_VALUE::FromInt( 50000 ),
216 // Semantic upgrade across types (numeric width to "auto").
217 { "type-change", wxS( "Width" ), DIFF_VALUE::FromInt( 100 ),
220 };
221
222 for( const CASE& c : cases )
223 {
224 BOOST_TEST_CONTEXT( c.label )
225 {
227 d.name = c.name;
228 d.before = c.before;
229 d.after = c.after;
230
232 BOOST_CHECK( out == d );
233 BOOST_CHECK( out.before.GetType() == c.beforeType );
234 BOOST_CHECK( out.after.GetType() == c.afterType );
235 }
236 }
237}
238
239
240BOOST_AUTO_TEST_CASE( ItemChangeRoundTrip )
241{
243
244 ITEM_CHANGE c;
245 c.id = KIID_PATH( wxS( "/" ) + KIID().AsString() );
246 c.typeName = wxS( "PCB_TRACK" );
248 c.bbox = BOX2I( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) );
249 c.refdes = wxS( "R1" );
250
252 d.name = wxS( "Width" );
253 d.before = DIFF_VALUE::FromInt( 100000 );
254 d.after = DIFF_VALUE::FromInt( 200000 );
255 c.properties.push_back( d );
256
257 nlohmann::json j = c.ToJson();
259 BOOST_CHECK( out == c );
260}
261
262
263BOOST_AUTO_TEST_CASE( DocumentDiffRoundTrip )
264{
266 d.path = wxS( "board.kicad_pcb" );
267 d.docType = wxS( "kicad_pcb" );
268 BOOST_CHECK( d.Empty() );
269
270 ITEM_CHANGE c;
271 c.id = KIID_PATH();
272 c.typeName = wxS( "PCB_VIA" );
274 c.bbox = BOX2I();
275 d.changes.push_back( c );
276
277 BOOST_CHECK( !d.Empty() );
278 BOOST_CHECK_EQUAL( d.Size(), 1u );
279
280 nlohmann::json j = d.ToJson();
282 BOOST_CHECK_EQUAL( out.path.ToStdString(), d.path.ToStdString() );
283 BOOST_CHECK_EQUAL( out.docType.ToStdString(), d.docType.ToStdString() );
284 // Size alone could pass on a reader that returned default-constructed
285 // ITEM_CHANGEs — pin the actual content via operator==.
286 BOOST_REQUIRE_EQUAL( out.changes.size(), d.changes.size() );
287 BOOST_CHECK( out.changes[0] == d.changes[0] );
288}
289
290
291BOOST_AUTO_TEST_CASE( ProjectDiffEmpty )
292{
293 PROJECT_DIFF p;
294 BOOST_CHECK( p.Empty() );
295
297 p.documents.push_back( d );
298 BOOST_CHECK( p.Empty() );
299
300 ITEM_CHANGE c;
302 c.typeName = wxS( "PCB_VIA" );
303 p.documents.front().changes.push_back( c );
304 BOOST_CHECK( !p.Empty() );
305}
306
307
308BOOST_AUTO_TEST_CASE( ProjectDiffJsonRoundTrip )
309{
310 // PROJECT_DIFF::ToJson/FromJson were previously only exercised via
311 // higher-level pipelines. Pin the two-document round-trip directly
312 // with non-default bbox / refdes / properties / children payloads so
313 // a regression that DROPS any of those (leaving the default values
314 // that match across both sides) is still caught by the equality
315 // assertion below.
316 KIID::SeedGenerator( 700 );
317 KIID idA, idB, childKiid;
318 const KIID_PATH pathA = KIID_PATH( wxS( "/" ) + idA.AsString() );
319 const KIID_PATH pathB = KIID_PATH( wxS( "/" ) + idB.AsString() );
320
321 PROJECT_DIFF p;
322
323 DOCUMENT_DIFF docA;
324 docA.path = wxS( "board.kicad_pcb" );
325 docA.docType = wxS( "kicad_pcb" );
326
327 ITEM_CHANGE c1;
328 c1.id = pathA;
329 c1.typeName = wxS( "PCB_VIA" );
331 c1.bbox = BOX2I( VECTOR2I( 100, 200 ), VECTOR2I( 50, 50 ) );
332 docA.changes.push_back( c1 );
333 p.documents.push_back( docA );
334
335 DOCUMENT_DIFF docB;
336 docB.path = wxS( "sheet.kicad_sch" );
337 docB.docType = wxS( "kicad_sch" );
338
339 ITEM_CHANGE c2;
340 c2.id = pathB;
341 c2.typeName = wxS( "SCH_LABEL" );
343 c2.bbox = BOX2I( VECTOR2I( 0, 0 ), VECTOR2I( 30, 10 ) );
344 c2.refdes = wxS( "NET_A" );
345
347 d.name = wxS( "Width" );
348 d.before = DIFF_VALUE::FromInt( 100 );
349 d.after = DIFF_VALUE::FromInt( 200 );
350 c2.properties.push_back( d );
351
352 ITEM_CHANGE childChange;
353 childChange.id = KIID_PATH( wxS( "/" ) + childKiid.AsString() );
354 childChange.typeName = wxS( "SCH_PIN" );
355 childChange.kind = CHANGE_KIND::MODIFIED;
356 c2.children.push_back( childChange );
357
358 docB.changes.push_back( c2 );
359 p.documents.push_back( docB );
360
362
363 BOOST_REQUIRE_EQUAL( out.documents.size(), 2u );
364 // Pin path + docType per document so an array-iteration regression
365 // (e.g. duplicating documents[0]) trips. Size-only would miss that.
366 BOOST_CHECK_EQUAL( out.documents[0].path.ToStdString(), "board.kicad_pcb" );
367 BOOST_CHECK_EQUAL( out.documents[0].docType.ToStdString(), "kicad_pcb" );
368 BOOST_CHECK_EQUAL( out.documents[1].path.ToStdString(), "sheet.kicad_sch" );
369 BOOST_CHECK_EQUAL( out.documents[1].docType.ToStdString(), "kicad_sch" );
370
371 // Full ITEM_CHANGE equality subsumes id / typeName / kind / bbox /
372 // properties / refdes / children — typeName-only checks would pass if
373 // the round-trip dropped/mangled any other field (e.g. MODIFIED
374 // collapsing to default ADDED on c2).
375 BOOST_REQUIRE_EQUAL( out.documents[0].changes.size(), 1u );
376 BOOST_REQUIRE_EQUAL( out.documents[1].changes.size(), 1u );
377 BOOST_CHECK( out.documents[0].changes[0] == c1 );
378 BOOST_CHECK( out.documents[1].changes[0] == c2 );
379}
380
381
382BOOST_AUTO_TEST_CASE( ChangeKindStringRoundTrip )
383{
384 BOOST_CHECK( ChangeKindFromString( "added" ) == CHANGE_KIND::ADDED );
385 BOOST_CHECK( ChangeKindFromString( "removed" ) == CHANGE_KIND::REMOVED );
386 BOOST_CHECK( ChangeKindFromString( "modified" ) == CHANGE_KIND::MODIFIED );
387 BOOST_CHECK( ChangeKindFromString( "collision" ) == CHANGE_KIND::COLLISION );
388 BOOST_CHECK( ChangeKindFromString( "duplicate_uuid" ) == CHANGE_KIND::DUPLICATE_UUID );
389
390 BOOST_CHECK_THROW( ChangeKindFromString( "garbage" ), std::invalid_argument );
391}
392
393
394BOOST_AUTO_TEST_CASE( DeterministicJsonOutput )
395{
398 BOOST_CHECK_EQUAL( v1.ToJson().dump(), v2.ToJson().dump() );
399}
400
401
402BOOST_AUTO_TEST_CASE( JsonEncodesUtf8NotLocale )
403{
404 // Non-ASCII content (UTF-8) must round-trip through JSON regardless of the
405 // host locale. Using ToStdString() would invoke locale-dependent encoding
406 // on Windows; we standardize on UTF-8 for the wire format.
407 DIFF_VALUE v = DIFF_VALUE::FromString( wxString::FromUTF8( "héllo wörld µ" ) );
408
409 nlohmann::json j = v.ToJson();
410
411 // The JSON dump should contain the UTF-8 bytes verbatim (escaped by JSON
412 // for non-ASCII as \u sequences, but representing the same logical string).
414 BOOST_CHECK( back == v );
415
417 d.name = wxString::FromUTF8( "Réf" );
418 d.before = v;
419 d.after = DIFF_VALUE::FromString( wxString::FromUTF8( "föo" ) );
421 BOOST_CHECK( dback == d );
422}
423
424
425#ifndef _WIN32
426BOOST_AUTO_TEST_CASE( WriteDiffOutputStdoutWritesContent )
427{
428 fflush( stdout );
429
430 int pipeFd[2];
431 BOOST_REQUIRE_EQUAL( pipe( pipeFd ), 0 );
432
433 const int savedStdout = dup( STDOUT_FILENO );
434 BOOST_REQUIRE( savedStdout >= 0 );
435
436 BOOST_REQUIRE_EQUAL( dup2( pipeFd[1], STDOUT_FILENO ), STDOUT_FILENO );
437 close( pipeFd[1] );
438
439 const std::string content = "diff A B\nhéllo\n";
440 BOOST_CHECK( WriteDiffOutput( content, wxEmptyString ) );
441 fflush( stdout );
442
443 BOOST_REQUIRE_EQUAL( dup2( savedStdout, STDOUT_FILENO ), STDOUT_FILENO );
444 close( savedStdout );
445
446 char buffer[256] = {};
447 ssize_t bytesRead = read( pipeFd[0], buffer, sizeof( buffer ) - 1 );
448 close( pipeFd[0] );
449
450 BOOST_REQUIRE( bytesRead >= 0 );
451 BOOST_CHECK_EQUAL( std::string( buffer, static_cast<std::size_t>( bytesRead ) ), content );
452}
453#endif
454
455
456// DIFF_VALUE inequality coverage --------------------------------------------
457//
458// Round-trip tests above exercise `operator==` on identical values. None
459// (except Bool) pin that DIFFERENT values compare unequal. A regression
460// where operator== returns true for everything would corrupt every
461// downstream consumer (PROPERTY_DELTA / ITEM_CHANGE / merge engine
462// auto-resolve) — pin each type explicitly.
463
464BOOST_AUTO_TEST_CASE( DiffValueInequalityAcrossPayloads )
465{
466 BOOST_CHECK( DIFF_VALUE::FromInt( 1 ) != DIFF_VALUE::FromInt( 2 ) );
467 BOOST_CHECK( DIFF_VALUE::FromInt64( 1LL ) != DIFF_VALUE::FromInt64( 2LL ) );
468 BOOST_CHECK( DIFF_VALUE::FromDouble( 1.0 ) != DIFF_VALUE::FromDouble( 2.0 ) );
469 BOOST_CHECK( DIFF_VALUE::FromString( wxS( "a" ) )
470 != DIFF_VALUE::FromString( wxS( "b" ) ) );
471
472 KIID::SeedGenerator( 600 );
473 KIID k1, k2;
474 // Defensive: confirm the seeded generator produced distinct KIIDs so the
475 // KIID-inequality assertion below tests what it claims to test.
476 BOOST_REQUIRE( k1 != k2 );
477 BOOST_CHECK( DIFF_VALUE::FromKiid( k1 ) != DIFF_VALUE::FromKiid( k2 ) );
478
479 BOOST_CHECK( DIFF_VALUE::FromVector2I( VECTOR2I( 1, 2 ) )
480 != DIFF_VALUE::FromVector2I( VECTOR2I( 3, 4 ) ) );
481 BOOST_CHECK( DIFF_VALUE::FromBox2I( BOX2I( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) ) )
482 != DIFF_VALUE::FromBox2I( BOX2I( VECTOR2I( 0, 0 ), VECTOR2I( 20, 20 ) ) ) );
483 BOOST_CHECK( DIFF_VALUE::FromColor( KIGFX::COLOR4D( 1, 0, 0, 1 ) )
484 != DIFF_VALUE::FromColor( KIGFX::COLOR4D( 0, 1, 0, 1 ) ) );
486 BOOST_CHECK( DIFF_VALUE::FromEnum( 1, "A" ) != DIFF_VALUE::FromEnum( 2, "B" ) );
487}
488
489
490BOOST_AUTO_TEST_CASE( DiffValueInequalityAcrossTypes )
491{
492 // Same logical "1" but different DIFF_VALUE::T discriminator must NOT
493 // compare equal — otherwise the merge engine could auto-resolve a
494 // type change as a no-op.
495 BOOST_CHECK( DIFF_VALUE::FromInt( 1 ) != DIFF_VALUE::FromInt64( 1LL ) );
496 BOOST_CHECK( DIFF_VALUE::FromInt( 1 ) != DIFF_VALUE::FromDouble( 1.0 ) );
497 BOOST_CHECK( DIFF_VALUE::FromBool( true) != DIFF_VALUE::FromInt( 1 ) );
498 BOOST_CHECK( DIFF_VALUE::FromString( wxS( "1" ) ) != DIFF_VALUE::FromInt( 1 ) );
499
500 DIFF_VALUE none;
501 BOOST_CHECK( none != DIFF_VALUE::FromInt( 0 ) );
502 BOOST_CHECK( none != DIFF_VALUE::FromBool( false ) );
503}
504
505
506BOOST_AUTO_TEST_CASE( DiffValueNoneEqualsNone )
507{
508 // Two default-constructed values must compare equal — the merge engine
509 // relies on this to skip "absent on both sides" properties.
510 DIFF_VALUE a, b;
511 BOOST_CHECK( a == b );
512}
513
514
515BOOST_AUTO_TEST_CASE( PropertyDeltaEqualityFieldSensitive )
516{
518 a.name = wxS( "Width" );
519 a.before = DIFF_VALUE::FromInt( 10 );
520 a.after = DIFF_VALUE::FromInt( 20 );
521
522 PROPERTY_DELTA b = a;
523 BOOST_CHECK( a == b );
524
525 b.name = wxS( "Layer" );
526 BOOST_CHECK( a != b );
527
528 b = a;
529 b.before = DIFF_VALUE::FromInt( 99 );
530 BOOST_CHECK( a != b );
531
532 b = a;
533 b.after = DIFF_VALUE::FromInt( 99 );
534 BOOST_CHECK( a != b );
535}
536
537
538BOOST_AUTO_TEST_CASE( ItemChangeEqualityChildrenSensitive )
539{
540 // ITEM_CHANGE::operator== must recurse into children — without that,
541 // a footprint edit that only changed a nested pad would compare equal
542 // to one that didn't. Three layers of pin:
543 // 1. no children vs one child -> unequal (catches size-blind eq)
544 // 2. one child on each side with DIFFERENT content -> unequal
545 // (catches size-only eq that doesn't recurse)
546 // 3. matching children -> equal again
547 KIID::SeedGenerator( 601 );
548 KIID parent, padIdA, padIdB;
549
550 ITEM_CHANGE a;
551 a.id = KIID_PATH( wxS( "/" ) + parent.AsString() );
552 a.typeName = wxS( "FOOTPRINT" );
554
555 ITEM_CHANGE b = a;
556 BOOST_CHECK( a == b );
557
558 ITEM_CHANGE child;
559 child.id = KIID_PATH( wxS( "/" ) + padIdA.AsString() );
560 child.typeName = wxS( "PAD" );
562 b.children.push_back( child );
563 BOOST_CHECK( a != b );
564
565 // Both sides now have ONE child, but the children differ. A size-only
566 // operator== would falsely report equal here — the recursive content
567 // comparison is what catches the divergence.
568 ITEM_CHANGE differentChild;
569 differentChild.id = KIID_PATH( wxS( "/" ) + padIdB.AsString() );
570 differentChild.typeName = wxS( "PAD" );
571 differentChild.kind = CHANGE_KIND::MODIFIED;
572 a.children.push_back( differentChild );
573 BOOST_CHECK( a != b );
574
575 // Now make A's child match B's: equal again.
576 a.children.back() = child;
577 BOOST_CHECK( a == b );
578}
579
580
const char * name
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
A typed sum value used to carry the before/after of any single property.
static DIFF_VALUE FromLayer(PCB_LAYER_ID aLayer)
static DIFF_VALUE FromDouble(double aValue)
static DIFF_VALUE FromEnum(int aValue, const std::string &aLabel)
static DIFF_VALUE FromInt64(int64_t aValue)
static DIFF_VALUE FromInt(int aValue)
static DIFF_VALUE FromBox2I(const BOX2I &aValue)
const PolygonSet & AsPolygonSet() const
static DIFF_VALUE FromKiid(const KIID &aValue)
static DIFF_VALUE FromColor(const KIGFX::COLOR4D &aValue)
nlohmann::json ToJson() const
static DIFF_VALUE FromBool(bool aValue)
static DIFF_VALUE FromString(const wxString &aValue)
std::vector< std::vector< std::vector< VECTOR2I > > > PolygonSet
static DIFF_VALUE FromJson(const nlohmann::json &aJson)
static DIFF_VALUE FromPolygonSet(PolygonSet aValue)
static DIFF_VALUE FromVector2I(const VECTOR2I &aValue)
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
Definition kiid.h:44
static void SeedGenerator(unsigned int aSeed)
Re-initialize the UUID generator with a given seed (for testing or QA purposes)
Definition kiid.cpp:331
wxString AsString() const
Definition kiid.cpp:242
@ Edge_Cuts
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:60
CHANGE_KIND ChangeKindFromString(const std::string &aKind)
bool WriteDiffOutput(const std::string &aContent, const wxString &aOutputPath)
Write diff/merge text output to aOutputPath, or to stdout when the path is empty.
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
One change record on a single item.
static ITEM_CHANGE FromJson(const nlohmann::json &aJson)
std::vector< PROPERTY_DELTA > properties
std::optional< wxString > refdes
std::vector< ITEM_CHANGE > children
nlohmann::json ToJson() const
Aggregated project-level diff covering many documents.
nlohmann::json ToJson() const
std::vector< DOCUMENT_DIFF > documents
static PROJECT_DIFF FromJson(const nlohmann::json &aJson)
Single (name, before, after) triple for one mutated property on an item.
static PROPERTY_DELTA FromJson(const nlohmann::json &aJson)
nlohmann::json ToJson() const
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(DiffValueNone)
static void roundTrip(const DIFF_VALUE &aValue)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I v1(5, 5, 5)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2I v2(1, 0)
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683