KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_sch_downgrade.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21
22#include <cmath>
23#include <climits>
24#include <fstream>
25#include <iterator>
26#include <map>
27
28#include <base_units.h>
29#include <common.h>
30#include <embedded_files.h>
31#include <pin_map.h>
32#include <schematic.h>
33#include <sch_screen.h>
34#include <sch_shape.h>
35#include <sch_group.h>
36#include <sch_line.h>
37#include <sch_pin.h>
38#include <sch_rule_area.h>
39#include <sch_sheet_path.h>
40#include <sch_symbol.h>
41#include <sch_bitmap.h>
42#include <sch_label.h>
43#include <sch_text.h>
44#include <bus_alias.h>
46#include <project.h>
48#include <nlohmann/json.hpp>
50#include <lib_symbol.h>
51#include <sch_file_versions.h>
53#include <downgrade_target.h>
55#include <qa_utils/file_utils.h>
57#include <eeschema_test_utils.h>
58#include <schematic_file_util.h>
59#include <pgm_base.h>
60#include <project_sch.h>
64#include <wx/file.h>
65
66#include <wx/filefn.h>
67
68
69BOOST_AUTO_TEST_SUITE( SchDowngrade )
70
71static const DOWNGRADE_TARGET& kicad9 = KI_TEST::RequireDowngradeTarget( wxT( "9.0" ) );
72static const DOWNGRADE_TARGET& kicad10 = KI_TEST::RequireDowngradeTarget( wxT( "10.0" ) );
73
74
75// A library symbol is downgraded through its own path. Jumper pin groups are dropped for 9.0.
76BOOST_AUTO_TEST_CASE( LibSymbolJumperPinsDroppedForKicad9 )
77{
78 LIB_SYMBOL sym( wxT( "test" ) );
80
82
84}
85
86
87// The schematic ellipse primitive only exists since 20260508, so 10.0 gets an approximation.
88BOOST_AUTO_TEST_CASE( EllipseIsLoweredForKicad10 )
89{
90 SCH_SCREEN screen;
91 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
92 ellipse->SetStart( VECTOR2I( 0, 0 ) );
93 ellipse->SetEnd( VECTOR2I( 500000, 0 ) );
94 screen.Append( ellipse );
95
97
98 BOOST_CHECK( report.IsLossy() );
100}
101
102
103// The target has no way to store an image rotation, so a turned image blocks the export.
104BOOST_AUTO_TEST_CASE( RotatedBitmapBlocks )
105{
106 SCH_SCREEN screen;
107 SCH_BITMAP* bitmap = new SCH_BITMAP();
109 screen.Append( bitmap );
110
112
113 BOOST_CHECK( report.IsBlocked() );
115
116 // An upright image is still exportable.
118 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
119}
120
121
122// The target has no way to store an image opacity, so the export proceeds with full opacity.
123BOOST_AUTO_TEST_CASE( BitmapOpacityIsDropped )
124{
125 SCH_SCREEN screen;
126 SCH_BITMAP* bitmap = new SCH_BITMAP();
127 bitmap->GetReferenceImage().SetOpacity( 0.4 );
128 screen.Append( bitmap );
129
131
132 BOOST_CHECK( !report.IsBlocked() );
134
136
139}
140
141
142BOOST_AUTO_TEST_CASE( EllipseTransformedToPolygon )
143{
144 SCH_SCREEN screen;
145 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
146 ellipse->SetStart( VECTOR2I( 0, 0 ) );
147 ellipse->SetEnd( VECTOR2I( 500000, 0 ) );
148 screen.Append( ellipse );
149
151
152 BOOST_CHECK( ellipse->GetShape() == SHAPE_T::POLY );
153}
154
155
156// An unfilled ellipse must be lowered to its outline polygon, not to stroke debris.
157BOOST_AUTO_TEST_CASE( UnfilledEllipseKeepsOutline )
158{
159 SCH_SCREEN screen;
160 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
161 ellipse->SetEllipseCenter( VECTOR2I( 0, 0 ) );
162 ellipse->SetEllipseMajorRadius( schIUScale.mmToIU( 5 ) );
163 ellipse->SetEllipseMinorRadius( schIUScale.mmToIU( 3 ) );
164 ellipse->SetFillMode( FILL_T::NO_FILL );
165 screen.Append( ellipse );
166
168
169 BOOST_REQUIRE( ellipse->GetShape() == SHAPE_T::POLY );
171 BOOST_CHECK( ellipse->GetFillMode() == FILL_T::NO_FILL );
172
173 double areaMM =
174 std::abs( ellipse->GetPolyShape().Outline( 0 ).Area() ) / ( schIUScale.IU_PER_MM * schIUScale.IU_PER_MM );
175
176 BOOST_CHECK_CLOSE( areaMM, M_PI * 5.0 * 3.0, 2.0 );
177}
178
179
180// An elliptical arc becomes an open polyline running from arc start to arc end.
181BOOST_AUTO_TEST_CASE( EllipseArcLoweredToOpenPolyline )
182{
183 SCH_SCREEN screen;
185 arc->SetEllipseCenter( VECTOR2I( 0, 0 ) );
186 arc->SetEllipseMajorRadius( schIUScale.mmToIU( 5 ) );
187 arc->SetEllipseMinorRadius( schIUScale.mmToIU( 3 ) );
190 screen.Append( arc );
191
193
196
197 const SHAPE_LINE_CHAIN& chain = arc->GetPolyShape().COutline( 0 );
198 BOOST_REQUIRE_GE( chain.PointCount(), 5 );
199
200 // One end sits on the major axis, the other on the minor axis.
201 double firstDist = chain.CPoint( 0 ).EuclideanNorm();
202 double lastDist = chain.CPoint( chain.PointCount() - 1 ).EuclideanNorm();
203 double tolerance = schIUScale.mmToIU( 0.05 );
204
205 BOOST_CHECK( std::abs( firstDist - schIUScale.mmToIU( 5 ) ) < tolerance );
206 BOOST_CHECK( std::abs( lastDist - schIUScale.mmToIU( 3 ) ) < tolerance );
207 BOOST_CHECK( chain.CPoint( 0 ) != chain.CPoint( chain.PointCount() - 1 ) );
208}
209
210
211// Ungrouping must survive nested groups. Members stay on the screen, groups go.
212BOOST_AUTO_TEST_CASE( NestedGroupsUngroupSafely )
213{
214 SCH_SCREEN screen;
215
216 SCH_SHAPE* innerShape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
217 SCH_SHAPE* outerShape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
218 screen.Append( innerShape );
219 screen.Append( outerShape );
220
221 SCH_GROUP* inner = new SCH_GROUP( &screen );
222 inner->AddItem( innerShape );
223 screen.Append( inner );
224
225 SCH_GROUP* outer = new SCH_GROUP( &screen );
226 outer->AddItem( outerShape );
227 outer->AddItem( inner );
228 screen.Append( outer );
229
231
232 DowngradeScreenInPlace( &screen, kicad9 );
233
234 auto groups = screen.Items().OfType( SCH_GROUP_T );
235 BOOST_CHECK( groups.begin() == groups.end() );
236
237 auto shapes = screen.Items().OfType( SCH_SHAPE_T );
238 BOOST_CHECK_EQUAL( std::distance( shapes.begin(), shapes.end() ), 2 );
239
240 BOOST_CHECK( innerShape->GetParentGroup() == nullptr );
241 BOOST_CHECK( outerShape->GetParentGroup() == nullptr );
242}
243
244
245// Hatched fills postdate 9.0, so they become a solid fill for 9.0.
246BOOST_AUTO_TEST_CASE( HatchedFillLoweredForKicad9 )
247{
248 SCH_SCREEN screen;
249 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
250 shape->SetFillMode( FILL_T::HATCH );
251 screen.Append( shape );
252
254
255 DowngradeScreenInPlace( &screen, kicad9 );
256
258}
259
260
261// Symbol bodies are written from the screen library cache, so an ellipse there must be
262// counted and lowered too, not just shapes placed on the screen.
263BOOST_AUTO_TEST_CASE( LibSymbolEllipseLoweredForKicad10 )
264{
265 SCH_SCREEN screen;
266 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
267 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
268 ellipse->SetStart( VECTOR2I( 0, 0 ) );
269 ellipse->SetEnd( VECTOR2I( 500000, 0 ) );
270 symbol->AddDrawItem( ellipse );
271 screen.AddLibSymbol( symbol );
272
274
276
277 BOOST_CHECK( ellipse->GetShape() == SHAPE_T::POLY );
278}
279
280
281// Turning a local power symbol global merges nets across sheets. A netlist change must never
282// ship silently, so the export blocks for 9.0.
283BOOST_AUTO_TEST_CASE( LocalPowerSymbolBlocksKicad9 )
284{
285 SCH_SCREEN screen;
286 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
287 symbol->SetLocalPower();
288 screen.AddLibSymbol( symbol );
289
292
293 DowngradeScreenInPlace( &screen, kicad9 );
294
295 BOOST_CHECK( symbol->IsLocalPower() );
296}
297
298
299// Rule area flags (20250610) postdate 9.0, so they are dropped and reported for 9.0.
300BOOST_AUTO_TEST_CASE( RuleAreaFlagsDroppedForKicad9 )
301{
302 SCH_SCREEN screen;
303 SCH_RULE_AREA* area = new SCH_RULE_AREA();
304 area->SetExcludedFromSim( true );
305 area->SetDNP( true );
306 screen.Append( area );
307
309
310 DowngradeScreenInPlace( &screen, kicad9 );
311
313 BOOST_CHECK( !area->GetDNP() );
314}
315
316
317// Rounded rectangles (20250829) postdate 9.0, so 9.0 gets a polygon that keeps the rounding.
318BOOST_AUTO_TEST_CASE( RoundedRectangleLoweredForKicad9 )
319{
320 SCH_SCREEN screen;
322 rect->SetStart( VECTOR2I( 0, 0 ) );
323 rect->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
324 rect->SetCornerRadius( schIUScale.mmToIU( 1 ) );
326 screen.Append( rect );
327
330
331 DowngradeScreenInPlace( &screen, kicad9 );
332
335
336 // A 10 mm by 6 mm rectangle loses ( 4 - pi ) r^2 to the rounded corners.
337 double areaMM =
338 std::abs( rect->GetPolyShape().Outline( 0 ).Area() ) / ( schIUScale.IU_PER_MM * schIUScale.IU_PER_MM );
339
340 BOOST_CHECK_CLOSE( areaMM, 60.0 - ( 4.0 - M_PI ), 2.0 );
341}
342
343
344// A stacked pin number with escaped characters (20260622) is misread by both targets, so the
345// export must block instead of silently renaming pins.
346BOOST_AUTO_TEST_CASE( EscapedStackedPinBlocksExport )
347{
348 SCH_SCREEN screen;
349 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
350 SCH_PIN* pin = new SCH_PIN( symbol );
351 pin->SetNumber( wxT( "[A\\,1,B]" ) );
352 symbol->AddDrawItem( pin );
353 screen.AddLibSymbol( symbol );
354
357}
358
359
360// Plain stacked notation (20250901) is expanded by 10.0, but 9.0 reads the brackets as one
361// literal pin number and the netlist silently changes. So 9.0 blocks and 10.0 does not.
362BOOST_AUTO_TEST_CASE( PlainStackedPinBlocksKicad9Only )
363{
364 SCH_SCREEN screen;
365 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
366 SCH_PIN* pin = new SCH_PIN( symbol );
367 pin->SetNumber( wxT( "[A1,B2]" ) );
368 symbol->AddDrawItem( pin );
369 screen.AddLibSymbol( symbol );
370
373}
374
375
376// Invalid stacked notation is read as one literal pin number by every KiCad, so it must not
377// block. The escapes rule only fires when the notation actually parses.
378BOOST_AUTO_TEST_CASE( InvalidStackedNotationDoesNotBlock )
379{
380 SCH_SCREEN screen;
381 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
382 SCH_PIN* pin = new SCH_PIN( symbol );
383 pin->SetNumber( wxT( "[A\\,B" ) );
384 symbol->AddDrawItem( pin );
385 screen.AddLibSymbol( symbol );
386
389}
390
391
392// A pin map living only in the screen's library cache is dropped by the transform, so it must
393// be counted for consent too.
394BOOST_AUTO_TEST_CASE( LibCachePinMapsCountedForDrop )
395{
396 SCH_SCREEN screen;
397 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
398 symbol->SetAssociatedFootprints( { { LIB_ID( wxT( "Lib" ), wxT( "FP" ) ), wxT( "map" ) } } );
399 screen.AddLibSymbol( symbol );
400
402
404
405 BOOST_CHECK( screen.GetLibSymbols().begin()->second->GetAssociatedFootprints().empty() );
406}
407
408
409// The 10.0 group format stores its own lock, so a locked group survives a 10.0 export while
410// other locked items are still unlocked and reported.
411BOOST_AUTO_TEST_CASE( GroupLockKeptForKicad10 )
412{
413 SCH_SCREEN screen;
414
415 // A locked item outside the group, so its lock cannot be inherited from the group.
416 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
417 shape->SetLocked( true );
418 screen.Append( shape );
419
420 SCH_SHAPE* member = new SCH_SHAPE( SHAPE_T::RECTANGLE );
421 screen.Append( member );
422
423 SCH_GROUP* group = new SCH_GROUP( &screen );
424 group->AddItem( member );
425 group->SetLocked( true );
426 screen.Append( group );
427
429
431
432 BOOST_CHECK( !shape->IsLocked() );
433 BOOST_CHECK( group->IsLocked() );
434}
435
436
437// A sheet's variant overrides must flatten like a symbol's: exclusion flags and field
438// overrides bake into the base sheet, not just DNP.
439BOOST_AUTO_TEST_CASE( SheetVariantFlattenedIntoSchematic )
440{
441 SCHEMATIC schematic( nullptr );
442 schematic.CreateDefaultScreens();
443
444 SCH_SHEET_LIST rootPaths = schematic.BuildUnorderedSheetList();
445 BOOST_REQUIRE( !rootPaths.empty() );
446
447 SCH_SHEET_PATH rootPath = rootPaths.at( 0 );
448 SCH_SCREEN* rootScreen = rootPath.LastScreen();
449 BOOST_REQUIRE( rootScreen != nullptr );
450
451 SCH_SHEET* child = new SCH_SHEET( &schematic );
452 child->SetScreen( new SCH_SCREEN( &schematic ) );
453 child->SetFileName( wxT( "child.kicad_sch" ) );
454 rootScreen->Append( child );
455
456 child->AddField( SCH_FIELD( child, FIELD_T::SHEET_USER, wxT( "Note" ) ) );
457 child->GetFields().back().SetText( wxT( "base note" ) );
458
459 SCH_SHEET_PATH childPath = rootPath;
460 childPath.push_back( child );
461
462 SCH_SHEET_INSTANCE instance;
463 instance.m_Path = childPath.Path();
464
465 SCH_SHEET_VARIANT variant( wxT( "V1" ) );
466 variant.InitializeAttributes( *child );
467 variant.m_ExcludedFromBOM = true;
468 variant.m_Fields[wxT( "Note" )] = wxT( "variant note" );
469 instance.m_Variants[wxT( "V1" )] = variant;
470
471 child->AddInstance( instance );
472
473 BOOST_CHECK( !child->GetExcludedFromBOM() );
474
475 wxString conflict;
476 BOOST_REQUIRE( FlattenSchematicVariant( schematic, wxT( "V1" ), &conflict ) );
477
479 BOOST_CHECK( child->GetFieldText( wxT( "Note" ) ) == wxT( "variant note" ) );
480}
481
482
483// The 9.0 target opens a single root sheet, so a second top-level sheet blocks that export.
484// 10.0 has the flat hierarchy and stays clean.
485BOOST_AUTO_TEST_CASE( MultipleTopLevelSheetsBlockKicad9 )
486{
487 SCHEMATIC schematic( nullptr );
488 schematic.CreateDefaultScreens();
489
490 SCH_SHEET* second = new SCH_SHEET( &schematic );
491 second->SetScreen( new SCH_SCREEN( &schematic ) );
492 schematic.AddTopLevelSheet( second );
493
495 0 );
497 1 );
498}
499
500
501// A variant selecting a different library symbol has no representation in a target that keeps
502// variants but not the choice, so that export must refuse. A target that removes variants
503// wholesale loses the choice along with them and needs only the drop row.
504BOOST_AUTO_TEST_CASE( AlternateVariantSymbolBlocksTargetThatKeepsVariants )
505{
506 SCHEMATIC schematic( nullptr );
507 schematic.CreateDefaultScreens();
508
509 SCH_SHEET_LIST paths = schematic.BuildUnorderedSheetList();
510 BOOST_REQUIRE( !paths.empty() );
511
512 SCH_SHEET_PATH path = paths.at( 0 );
513 SCH_SCREEN* screen = path.LastScreen();
514 BOOST_REQUIRE( screen != nullptr );
515
516 SCH_SYMBOL* symbol = new SCH_SYMBOL();
517 screen->Append( symbol );
518
519 SCH_SYMBOL_INSTANCE instance;
520 instance.m_Path = path.Path();
521
522 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
523 variant.m_SymbolOverride = LIB_ID( wxT( "Device" ), wxT( "Regulator_5pin" ) );
524 instance.m_Variants[wxT( "Alt" )] = variant;
525
526 symbol->AddHierarchicalReference( instance );
527
529 1 );
531 0 );
532}
533
534
535// A pin-to-pad override stored on a variant record must be counted and cleared like the base one.
536BOOST_AUTO_TEST_CASE( VariantPinMapOverrideDroppedForKicad10 )
537{
538 SCH_SCREEN screen;
539 SCH_SYMBOL* symbol = new SCH_SYMBOL();
540
541 SCH_SYMBOL_INSTANCE instance;
542 instance.m_Path.push_back( KIID() );
543
544 SCH_SYMBOL_VARIANT variant( wxT( "V1" ) );
546 instance.m_Variants[wxT( "V1" )] = variant;
547
548 symbol->AddHierarchicalReference( instance );
549 screen.Append( symbol );
550
552
554
555 BOOST_REQUIRE_EQUAL( symbol->GetInstances().size(), 1 );
556 const auto& variants = symbol->GetInstances()[0].m_Variants;
557 BOOST_REQUIRE( variants.count( wxT( "V1" ) ) );
558 BOOST_CHECK( variants.at( wxT( "V1" ) ).m_PinMapOverride.IsDefault() );
559}
560
561
562// Named body styles postdate 9.0, so their names are dropped and reported for 9.0.
563BOOST_AUTO_TEST_CASE( NamedBodyStylesDroppedForKicad9 )
564{
565 SCH_SCREEN screen;
566 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
567 symbol->SetBodyStyleNames( { wxT( "styleA" ), wxT( "styleB" ) } );
568 screen.AddLibSymbol( symbol );
569
571
572 DowngradeScreenInPlace( &screen, kicad9 );
573
574 BOOST_CHECK( symbol->GetBodyStyleNames().empty() );
575}
576
577
578// KiCad 9 only supports selectors 1 and 2. A used third style must retain its drawing
579// and pin positions, not silently fall back to style 1 (even with --force).
580BOOST_AUTO_TEST_CASE( SelectedCustomBodyStyleBakedForKicad9 )
581{
582 SCH_SCREEN screen;
583 LIB_SYMBOL lib( wxT( "test" ) );
584 lib.SetBodyStyleNames( { wxT( "first" ), wxT( "second" ), wxT( "third" ) } );
585
586 for( int style = 1; style <= 3; ++style )
587 {
588 SCH_PIN* pin = new SCH_PIN( &lib );
589 pin->SetNumber( wxT( "1" ) );
590 pin->SetPosition( VECTOR2I( schIUScale.mmToIU( 5 * style ), 0 ) );
591 pin->SetBodyStyle( style );
592 pin->SetUnit( 1 );
593 lib.AddDrawItem( pin );
594 }
595
596 SCH_SYMBOL* base = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
597 SCH_SYMBOL* third = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
598 SCH_SYMBOL* another = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
599 screen.Append( base );
600 screen.Append( third );
601 screen.Append( another );
602
603 BOOST_REQUIRE_EQUAL( third->GetRawPins().size(), 1 );
604 const KIID pinUuid = third->GetRawPins().front()->m_Uuid;
606
607 DowngradeScreenInPlace( &screen, kicad9 );
608
613 BOOST_REQUIRE_EQUAL( third->GetRawPins().size(), 1 );
614 BOOST_CHECK( third->GetRawPins().front()->m_Uuid == pinUuid );
615 BOOST_CHECK( third->GetRawPins().front()->GetPosition() == VECTOR2I( schIUScale.mmToIU( 15 ), 0 ) );
616 BOOST_CHECK( base->GetRawPins().front()->GetPosition() == VECTOR2I( schIUScale.mmToIU( 5 ), 0 ) );
618 BOOST_CHECK_EQUAL( third->GetLibSymbolRef()->GetGraphicalPins().size(), 1 );
619 BOOST_CHECK_EQUAL( screen.GetLibSymbols().size(), 2 );
620}
621
622
623BOOST_AUTO_TEST_CASE( SelectedCustomBodyStyleKeptForKicad10 )
624{
625 SCH_SCREEN screen;
626 LIB_SYMBOL lib( wxT( "test" ) );
627 lib.SetBodyStyleNames( { wxT( "first" ), wxT( "second" ), wxT( "third" ) } );
628 SCH_SYMBOL* symbol = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
629 screen.Append( symbol );
630
631 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
633 BOOST_CHECK_EQUAL( symbol->GetBodyStyle(), 3 );
634 BOOST_CHECK_EQUAL( symbol->GetLibSymbolRef()->GetBodyStyleNames().size(), 3 );
635}
636
637
638BOOST_AUTO_TEST_CASE( PositionFileExclusionsReportedAndDroppedForKicad9 )
639{
640 SCH_SCREEN screen;
641 LIB_SYMBOL lib( wxT( "test" ) );
642 lib.SetExcludedFromPosFiles( true );
643 SCH_SYMBOL* symbol = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
644 screen.Append( symbol );
645
648 DowngradeScreenInPlace( &screen, kicad9 );
652 BOOST_CHECK( !screen.GetLibSymbols().begin()->second->GetExcludedFromPosFiles() );
653}
654
655
656BOOST_AUTO_TEST_CASE( PositionFileExclusionsKeptForKicad10 )
657{
658 SCH_SCREEN screen;
659 LIB_SYMBOL lib( wxT( "test" ) );
660 lib.SetExcludedFromPosFiles( true );
661 SCH_SYMBOL* symbol = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
662 screen.Append( symbol );
663
664 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
670}
671
672
673// Item locking was introduced in format 20260326.
674BOOST_AUTO_TEST_CASE( LockedItemUnlockedForKicad10 )
675{
676 SCH_SCREEN screen;
677 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
678 shape->SetLocked( true );
679 screen.Append( shape );
680
682
684
685 BOOST_CHECK( !shape->IsLocked() );
686}
687
688
689// The schematic fail-closed gate catches post-10.0 tokens and ignores lookalikes.
690BOOST_AUTO_TEST_CASE( VerifyGateCatchesUnsupportedTokens )
691{
692 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(symbol (pin_map_override (mode passthrough)))" ), kicad10 )
693 == wxT( "pin_map_override" ) );
694 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(net_chain \"n\")" ), kicad10 ) == wxT( "net_chain" ) );
695 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(kicad_sch (symbol (pin \"1\")))" ), kicad10 ).IsEmpty() );
696
697 // Value tokens the node scan cannot see must still be caught for 9.0, but not inside strings.
698
699 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(fill (type hatch))" ), kicad9 ) == wxT( "hatch" ) );
700 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(text \"hatch pattern\")" ), kicad9 ).IsEmpty() );
701}
702
703
704// Flattening bakes the chosen variant into the base symbol attributes, so the exported
705// schematic matches that variant after the registry is dropped.
706BOOST_AUTO_TEST_CASE( VariantFlattenedIntoSchematic )
707{
708 SCHEMATIC schematic( nullptr );
709 schematic.CreateDefaultScreens();
710
711 SCH_SHEET_LIST paths = schematic.BuildUnorderedSheetList();
712 BOOST_REQUIRE( !paths.empty() );
713
714 SCH_SHEET_PATH path = paths.at( 0 );
715 SCH_SCREEN* screen = path.LastScreen();
716 BOOST_REQUIRE( screen != nullptr );
717
718 SCH_SYMBOL* symbol = new SCH_SYMBOL();
719 screen->Append( symbol );
720
721 SCH_SYMBOL_INSTANCE instance;
722 instance.m_Path = path.Path();
723 instance.m_Reference = wxT( "R1" );
724
725 SCH_SYMBOL_VARIANT variant( wxT( "V1" ) );
726 variant.InitializeAttributes( *symbol );
727 variant.m_DNP = true;
728 instance.m_Variants[wxT( "V1" )] = variant;
729
730 symbol->AddHierarchicalReference( instance );
731
732 BOOST_CHECK( !symbol->GetDNP() );
733
734 wxString conflict;
735 BOOST_REQUIRE( FlattenSchematicVariant( schematic, wxT( "V1" ), &conflict ) );
736
737 BOOST_CHECK( symbol->GetDNP() );
738}
739
740
741// Coverage guard: a schematic format bump must trigger a denylist review. See the board equivalent.
747
748
749BOOST_FIXTURE_TEST_CASE( EmbeddedFilesSurviveMissingRootInstanceForBothTargets, KI_TEST::SCHEMATIC_TEST_FIXTURE )
750{
751 const std::string fixture = KI_TEST::GetEeschemaTestDataDir() + "/../downgrade/regressions/embedded_files/";
752 auto readFile = []( const std::string& aPath )
753 {
754 std::ifstream file( aPath, std::ios::binary );
755 BOOST_REQUIRE( file.is_open() );
756 return std::string( std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>() );
757 };
758 std::map<std::string, std::string> originals;
759
760 for( const std::string& name : { "embedded_files.kicad_pro", "embedded_files.kicad_sch", "child.kicad_sch" } )
761 {
762 originals.emplace( name, readFile( fixture + name ) );
763 }
764
765 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
766 {
767 BOOST_TEST_CONTEXT( target.m_id )
768 {
769 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_embedded_schematic" );
770 LoadSchematic( wxFileName( fixture + "embedded_files.kicad_sch" ) );
771 BOOST_REQUIRE( m_schematic );
772 SCH_SHEET* root = m_schematic->GetTopLevelSheet( 0 );
773 BOOST_REQUIRE( root );
775 EMBEDDED_FILES expected( *m_schematic->GetEmbeddedFiles(), true );
776 BOOST_REQUIRE( !expected.IsEmpty() );
777
778 // Global payload ownership does not depend on optional sheet-instance bookkeeping.
779 root->RemoveInstance( KIID_PATH() );
780 BOOST_REQUIRE( !root->HasRootInstance() );
781 auto children = root->GetScreen()->Items().OfType( SCH_SHEET_T );
782 BOOST_REQUIRE_EQUAL( std::distance( children.begin(), children.end() ), 1 );
783 SCH_SHEET* child = static_cast<SCH_SHEET*>( *children.begin() );
784 const std::string output = ( tmp.Path() / "embedded_files.kicad_sch" ).string();
785 const std::string childOutput = ( tmp.Path() / "child.kicad_sch" ).string();
786 SaveSchematicForTarget( root, m_schematic.get(), output, target );
787 SaveSchematicForTarget( child, m_schematic.get(), childOutput, target );
788 BOOST_CHECK( readFile( childOutput ).find( "(embedded_files" ) == std::string::npos );
789 BOOST_REQUIRE( wxCopyFile( fixture + "embedded_files.kicad_pro",
790 ( tmp.Path() / "embedded_files.kicad_pro" ).string() ) );
791
792 LoadSchematic( wxFileName( output ) );
793 const EMBEDDED_FILES& actual = *m_schematic->GetEmbeddedFiles();
794 BOOST_REQUIRE_EQUAL( actual.EmbeddedFileMap().size(), expected.EmbeddedFileMap().size() );
795
796 for( const auto& [name, expectedFile] : expected.EmbeddedFileMap() )
797 {
798 EMBEDDED_FILES::EMBEDDED_FILE* actualFile = actual.GetEmbeddedFile( name );
799 BOOST_REQUIRE( actualFile );
800 // Parsing verifies the checksum but does not populate the cached validity flag.
801 BOOST_REQUIRE( expectedFile->Validate() );
802 BOOST_REQUIRE( actualFile->Validate() );
803 BOOST_CHECK( actualFile->is_valid );
804 BOOST_CHECK( actualFile->type == expectedFile->type );
805 BOOST_CHECK_EQUAL( actualFile->data_hash, expectedFile->data_hash );
806 BOOST_CHECK_EQUAL( actualFile->compressedEncodedData, expectedFile->compressedEncodedData );
807 BOOST_CHECK( actualFile->decompressedData == expectedFile->decompressedData );
808 }
809 }
810 }
811
812 for( const auto& [name, original] : originals )
813 BOOST_CHECK( readFile( fixture + name ) == original );
814}
815
816
817BOOST_AUTO_TEST_CASE( DropApproximationsOmitsScreenAndCachedGraphics )
818{
819 SCH_SCREEN screen;
820 LIB_SYMBOL* lib = new LIB_SYMBOL( wxT( "test" ) );
821 const size_t fieldCount = lib->GetDrawItems().size();
823 {
824 SCH_SHAPE* placed = new SCH_SHAPE( type );
825 SCH_SHAPE* cached = new SCH_SHAPE( type );
826 if( type == SHAPE_T::RECTANGLE )
827 {
828 placed->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
829 cached->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
830 placed->SetCornerRadius( schIUScale.mmToIU( 1 ) );
831 cached->SetCornerRadius( schIUScale.mmToIU( 1 ) );
832 }
833 screen.Append( placed );
834 lib->AddDrawItem( cached );
835 }
836 SCH_SHAPE* supported = new SCH_SHAPE( SHAPE_T::CIRCLE );
837 screen.Append( supported );
838 SCH_SHAPE* cachedSupported = new SCH_SHAPE( SHAPE_T::CIRCLE );
839 lib->AddDrawItem( cachedSupported );
840 screen.AddLibSymbol( lib );
841 SCH_GROUP* group = new SCH_GROUP( &screen );
842 for( SCH_ITEM* item : screen.Items().OfType( SCH_SHAPE_T ) )
843 group->AddItem( item );
844 screen.Append( group );
845 const auto& target = kicad9;
846 const auto normal = ClassifyScreenForDowngrade( &screen, target );
847 const auto omit = ClassifyScreenForDowngrade( &screen, target, true );
849 BOOST_CHECK_EQUAL( omit.Count( DOWNGRADE_BUCKET::LOWER ), 0 );
850 BOOST_CHECK_EQUAL( omit.Count( DOWNGRADE_BUCKET::DROP ), 7 );
851 BOOST_REQUIRE_EQUAL( normal.Entries().size(), omit.Entries().size() );
852 for( size_t i = 0; i < normal.Entries().size(); ++i )
853 {
854 BOOST_CHECK( normal.Entries()[i].m_feature == omit.Entries()[i].m_feature );
855 BOOST_CHECK_EQUAL( normal.Entries()[i].m_count, omit.Entries()[i].m_count );
856 BOOST_CHECK( normal.Entries()[i].m_detail != omit.Entries()[i].m_detail );
857 BOOST_CHECK( !omit.Entries()[i].m_detail.IsEmpty() );
858 }
859 DowngradeScreenInPlace( &screen, target, true );
860 auto shapes = screen.Items().OfType( SCH_SHAPE_T );
861 BOOST_REQUIRE_EQUAL( std::distance( shapes.begin(), shapes.end() ), 1 );
862 BOOST_CHECK( *shapes.begin() == supported );
863 BOOST_CHECK( supported->GetParentGroup() == nullptr );
864 BOOST_CHECK_EQUAL( lib->GetDrawItems().size(), fieldCount + 1 );
865 for( const SCH_ITEM& item : lib->GetDrawItems() )
866 {
867 if( item.Type() == SCH_SHAPE_T )
868 BOOST_CHECK( &item == cachedSupported );
869 }
870 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, target, true ).IsLossy() );
871}
872
873
874BOOST_AUTO_TEST_CASE( DropApproximationsOmitsLibraryGraphicsAndHatchFill )
875{
876 LIB_SYMBOL lib( wxT( "test" ) );
877 const size_t fieldCount = lib.GetDrawItems().size();
880 SCH_SHAPE* rounded = new SCH_SHAPE( SHAPE_T::RECTANGLE );
881 rounded->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
882 rounded->SetCornerRadius( schIUScale.mmToIU( 1 ) );
883 lib.AddDrawItem( rounded );
884 SCH_SHAPE* hatch = new SCH_SHAPE( SHAPE_T::RECTANGLE );
885 hatch->SetFillMode( FILL_T::HATCH );
886 lib.AddDrawItem( hatch );
887 const auto& target = kicad9;
890 DowngradeLibSymbolInPlace( &lib, target, true );
891 BOOST_REQUIRE_EQUAL( lib.GetDrawItems().size(), fieldCount + 1 );
892 for( const SCH_ITEM& item : lib.GetDrawItems() )
893 {
894 if( item.Type() == SCH_SHAPE_T )
895 BOOST_CHECK( &item == hatch );
896 }
898}
899
900
901BOOST_AUTO_TEST_CASE( DropApproximationsKeepsOutlineTextAndLockFreeItems )
902{
903 SCH_SCREEN screen;
904 SCH_SHAPE* hatch = new SCH_SHAPE( SHAPE_T::RECTANGLE );
905 hatch->SetFillMode( FILL_T::HATCH );
906 screen.Append( hatch );
907 hatch->SetLocked( true );
908 SCH_RULE_AREA* area = new SCH_RULE_AREA();
909 area->SetFillMode( FILL_T::HATCH );
910 screen.Append( area );
911 const auto& target = kicad9;
912 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( &screen, target, true ).Count( DOWNGRADE_BUCKET::DROP ), 3 );
913 DowngradeScreenInPlace( &screen, target, true );
914 BOOST_CHECK( !hatch->IsLocked() );
917 BOOST_CHECK( screen.CheckIfOnDrawList( hatch ) );
918 BOOST_CHECK( screen.CheckIfOnDrawList( area ) );
919}
920
921
922BOOST_AUTO_TEST_CASE( DropApproximationsOmitsSelectedCustomStyleInstancesOnly )
923{
924 for( const auto& target : { kicad9, kicad10 } )
925 {
926 SCH_SCREEN screen;
927 LIB_SYMBOL lib( wxT( "test" ) );
928 lib.SetBodyStyleNames( { wxT( "first" ), wxT( "second" ), wxT( "third" ) } );
929 for( int style = 1; style <= 3; ++style )
930 {
931 SCH_PIN* pin = new SCH_PIN( &lib );
932 pin->SetNumber( wxT( "1" ) );
933 pin->SetBodyStyle( style );
934 pin->SetUnit( 1 );
935 lib.AddDrawItem( pin );
936 }
937 SCH_SYMBOL* base = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
938 SCH_SYMBOL* second = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 2 );
939 SCH_SYMBOL* third = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
940 screen.Append( base );
941 screen.Append( second );
942 screen.Append( third );
943 SCH_GROUP* group = new SCH_GROUP( &screen );
944 group->AddItem( third );
945 screen.Append( group );
946 const KIID basePin = base->GetRawPins().front()->m_Uuid;
947 const KIID secondPin = second->GetRawPins().front()->m_Uuid;
948 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( &screen, target, true ).Count( DOWNGRADE_BUCKET::LOWER ), 0 );
949 DowngradeScreenInPlace( &screen, target, true );
950 auto symbols = screen.Items().OfType( SCH_SYMBOL_T );
951 BOOST_CHECK_EQUAL( std::distance( symbols.begin(), symbols.end() ), target.m_id == wxT( "9.0" ) ? 2 : 3 );
952 BOOST_CHECK( screen.CheckIfOnDrawList( base ) );
953 BOOST_CHECK( screen.CheckIfOnDrawList( second ) );
954 BOOST_CHECK( base->GetRawPins().front()->m_Uuid == basePin );
955 BOOST_CHECK( second->GetRawPins().front()->m_Uuid == secondPin );
956 BOOST_CHECK_EQUAL( screen.GetLibSymbols().size(), 1 );
957 if( target.m_id == wxT( "10.0" ) )
958 {
959 BOOST_CHECK( screen.CheckIfOnDrawList( third ) );
960 BOOST_CHECK_EQUAL( third->GetBodyStyle(), 3 );
961 }
962 }
963}
964
965
966BOOST_AUTO_TEST_CASE( DropApproximationsCannotBypassSchematicBlockRules )
967{
968 LIB_SYMBOL lib( wxT( "test" ) );
969 lib.SetLocalPower();
970 const auto& target = kicad9;
971 BOOST_CHECK( ClassifyLibSymbolForDowngrade( &lib, target, true ).IsBlocked() );
972 SCH_SCREEN screen;
973 screen.AddLibSymbol( new LIB_SYMBOL( lib ) );
974 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target, true ).IsBlocked() );
975}
976
977
978BOOST_AUTO_TEST_CASE( PostMasterLineEndingsAndPropertiesCoverStandaloneAndCachedSymbols )
979{
980 for( const auto& target : { kicad9, kicad10 } )
981 {
982 for( bool drop : { false, true } )
983 {
984 SCH_SCREEN screen;
985 LIB_SYMBOL* lib = new LIB_SYMBOL( wxT( "post_master" ) );
986 lib->SetCustomProperty( wxT( "Vendor" ), wxT( "Acme" ) );
987 lib->GetField( FIELD_T::VALUE )->SetCustomProperty( wxT( "Owner" ), wxT( "Library" ) );
988 SCH_PIN* pin = new SCH_PIN( lib );
989 pin->SetNumber( wxT( "1" ) );
990 pin->SetCustomProperty( wxT( "Role" ), wxT( "Signal" ) );
991 lib->AddDrawItem( pin );
992 SCH_SHAPE* line = new SCH_SHAPE( SHAPE_T::SEGMENT );
993 line->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
994 line->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
996 lib->AddDrawItem( line );
997 screen.AddLibSymbol( lib );
998 LIB_SYMBOL standalone( *lib );
999
1000 const auto report = ClassifyLibSymbolForDowngrade( &standalone, target, drop );
1001 BOOST_CHECK( !report.IsBlocked() );
1002 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), drop ? 0 : 1 );
1003 BOOST_CHECK_GE( report.Count( DOWNGRADE_BUCKET::DROP ), drop ? 4 : 3 );
1004 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( &screen, target, drop ).Entries().size(),
1005 report.Entries().size() );
1006 DowngradeLibSymbolInPlace( &standalone, target, drop );
1007 DowngradeScreenInPlace( &screen, target, drop );
1008 BOOST_CHECK( !ClassifyLibSymbolForDowngrade( &standalone, target, drop ).IsLossy() );
1009 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, target, drop ).IsLossy() );
1010 BOOST_CHECK( !standalone.HasCustomProperties() );
1012 BOOST_CHECK( !standalone.GetGraphicalPins().front()->HasCustomProperties() );
1013 if( drop )
1014 {
1017 BOOST_CHECK( line->GetEnd() == VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
1018 }
1019 }
1020 }
1021}
1022
1023
1024BOOST_AUTO_TEST_CASE( GraphicLineEndingsKeepGroupsAndDropKeepsOriginalBody )
1025{
1026 for( bool drop : { false, true } )
1027 {
1028 SCH_SCREEN screen;
1029 SCH_LINE* line = new SCH_LINE( VECTOR2I(), LAYER_NOTES );
1030 line->SetEndPoint( VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
1031 line->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
1033 screen.Append( line );
1034 SCH_GROUP* group = new SCH_GROUP( &screen );
1035 group->AddItem( line );
1036 screen.Append( group );
1039 1 );
1040 DowngradeScreenInPlace( &screen, kicad10, drop );
1041 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10, drop ).IsLossy() );
1042 BOOST_CHECK( !group->GetItems().empty() );
1043 for( EDA_ITEM* member : group->GetItems() )
1044 BOOST_CHECK( member->GetParentGroup() == group );
1045 if( drop )
1046 {
1047 BOOST_CHECK( screen.CheckIfOnDrawList( line ) );
1049 BOOST_CHECK( line->GetEndPoint() == VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
1050 }
1051 }
1052}
1053
1054
1055// An ending that strokes to no ink leaves nothing to put back, so the graphic stays and only the
1056// ending goes. The board path already guards this.
1057BOOST_AUTO_TEST_CASE( DegenerateLineEndingKeepsItsGraphic )
1058{
1059 for( const DOWNGRADE_TARGET* target : { &kicad9, &kicad10 } )
1060 {
1061 BOOST_TEST_CONTEXT( target->m_id.ToStdString() )
1062 {
1063 SCH_SCREEN screen;
1065 shape->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
1067 screen.Append( shape );
1068
1069 DowngradeScreenInPlace( &screen, *target );
1070
1071 auto shapes = screen.Items().OfType( SCH_SHAPE_T );
1072 BOOST_REQUIRE_EQUAL( std::distance( shapes.begin(), shapes.end() ), 1 );
1074
1075 LIB_SYMBOL symbol( wxT( "degenerate" ) );
1077 drawing->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
1079 symbol.AddDrawItem( drawing );
1080
1081 DowngradeLibSymbolInPlace( &symbol, *target );
1082
1083 int kept = 0;
1084
1085 for( const SCH_ITEM& drawing : symbol.GetDrawItems() )
1086 {
1087 if( drawing.Type() != SCH_SHAPE_T )
1088 continue;
1089
1090 kept++;
1091 BOOST_CHECK( static_cast<const SCH_SHAPE&>( drawing ).GetEndEndingStyle()
1093 }
1094
1095 BOOST_CHECK_EQUAL( kept, 1 );
1096 }
1097 }
1098}
1099
1100
1101BOOST_AUTO_TEST_CASE( StyledLineEndingsBlockApproximationAndAllowExplicitDrop )
1102{
1103 SCH_SCREEN screen;
1104 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::SEGMENT );
1105 shape->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
1106 shape->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::DASH ) );
1108 screen.Append( shape );
1109 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1110 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10, true ).IsBlocked() );
1111 DowngradeScreenInPlace( &screen, kicad10, true );
1114}
1115
1116
1117BOOST_AUTO_TEST_CASE( UnsupportedPropertyTextBlocksWithoutBlockingEscapedLiterals )
1118{
1119 SCH_SCREEN screen;
1120 SCH_TEXT* text = new SCH_TEXT();
1121 text->SetText( wxT( "${U1:PROPERTY.Vendor}" ) );
1122 screen.Append( text );
1123 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad9 ).IsBlocked() );
1124 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad10, true ).IsBlocked() );
1125 text->SetText( wxT( "\\${PROPERTY.Vendor}" ) );
1126 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1127 text->SetText( wxT( "PROPERTY.Vendor" ) );
1128 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1129}
1130
1131
1132BOOST_AUTO_TEST_CASE( PropertyTextAndUsedProjectDependenciesBakeBeforeMetadataRemoval )
1133{
1134 for( const auto& target : { kicad9, kicad10 } )
1135 {
1136 for( bool drop : { false, true } )
1137 {
1138 SETTINGS_MANAGER settings;
1139 std::unique_ptr<SCHEMATIC> schematic;
1140 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1141 auto symbols = schematic->RootScreen()->Items().OfType( SCH_SYMBOL_T );
1142 BOOST_REQUIRE( symbols.begin() != symbols.end() );
1143 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
1144 symbol->SetCustomProperty( wxT( "Vendor" ), wxT( "Acme" ) );
1145 symbol->GetField( FIELD_T::VALUE )->SetText( wxT( "${Alias} / ${REFERENCE}" ) );
1146 schematic->Project().GetTextVars()[wxT( "Alias" )] = wxT( "${PROPERTY.Vendor}" );
1147 schematic->Project().GetTextVars()[wxT( "Unused" )] = wxT( "${PROPERTY.Missing}" );
1148 auto report = ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop );
1149 BOOST_CHECK( !report.IsBlocked() );
1150 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), 0 );
1151 BOOST_CHECK_GE( report.Count( DOWNGRADE_BUCKET::DROP ), 2 );
1152 DowngradeScreenInPlace( schematic->RootScreen(), target, drop );
1153 BOOST_CHECK( symbol->GetField( FIELD_T::VALUE )->GetText() == wxT( "Acme / ${REFERENCE}" ) );
1154 BOOST_CHECK( !symbol->HasCustomProperties() );
1155 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop ).IsLossy() );
1156 }
1157 }
1158}
1159
1160
1161BOOST_AUTO_TEST_CASE( ExpressionsBakeForNative9AndRemainDynamicForNative10 )
1162{
1163 for( const auto& target : { kicad9, kicad10 } )
1164 {
1165 for( bool drop : { false, true } )
1166 {
1167 SETTINGS_MANAGER settings;
1168 std::unique_ptr<SCHEMATIC> schematic;
1169 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1170 SCH_TEXT* text = new SCH_TEXT();
1171 text->SetText( wxT( "@{1 + 2} / ${PROJECTNAME}" ) );
1172 schematic->RootScreen()->Append( text );
1173 auto report = ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop );
1174 BOOST_CHECK( !report.IsBlocked() );
1175 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), 0 );
1176 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::DROP ), target.m_id == wxT( "9.0" ) ? 1 : 0 );
1177 DowngradeScreenInPlace( schematic->RootScreen(), target, drop );
1178 BOOST_CHECK( text->GetText()
1179 == ( target.m_id == wxT( "9.0" ) ? wxT( "3 / ${PROJECTNAME}" )
1180 : wxT( "@{1 + 2} / ${PROJECTNAME}" ) ) );
1181 text->SetText( wxT( "\\@{1 + 2}" ) );
1182 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( schematic->RootScreen(), target ).IsBlocked(),
1183 target.m_id == wxT( "9.0" ) );
1184 }
1185 }
1186}
1187
1188
1189BOOST_AUTO_TEST_CASE( PropertyTextBakingPreservesEscapedLiteralsAndLiteralReplacementSyntax )
1190{
1191 SETTINGS_MANAGER settings;
1192 std::unique_ptr<SCHEMATIC> schematic;
1193 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1194 auto symbols = schematic->RootScreen()->Items().OfType( SCH_SYMBOL_T );
1195 BOOST_REQUIRE( symbols.begin() != symbols.end() );
1196 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
1197 symbol->SetCustomProperty( wxT( "Vendor" ), wxT( "\\${REFERENCE} \\@{1 + 2}" ) );
1198 SCH_FIELD* field = symbol->GetField( FIELD_T::VALUE );
1199 field->SetText( wxT( "\\${PROPERTY.literal} ${PROPERTY.Vendor}" ) );
1200 const SCH_SHEET_PATH& path = schematic->CurrentSheet();
1201 const wxString shownBefore = field->GetShownText( &path, FOR_CANVAS, wxEmptyString );
1202 wxString resolvedBefore = field->ResolveText( field->GetText(), &path, 0, wxEmptyString );
1203 FinalizeTextVarExpansion( resolvedBefore, INTERNAL );
1204 BOOST_CHECK_EQUAL( resolvedBefore.ToStdString(), std::string( "\\${PROPERTY.literal} \\${REFERENCE} \\3" ) );
1205 BOOST_REQUIRE( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsBlocked() );
1206 DowngradeScreenInPlace( schematic->RootScreen(), kicad10 );
1207 BOOST_CHECK_EQUAL( field->GetText().ToStdString(), resolvedBefore.ToStdString() );
1208 BOOST_CHECK_EQUAL( field->GetShownText( &path, FOR_CANVAS, wxEmptyString ).ToStdString(),
1209 shownBefore.ToStdString() );
1210 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsLossy() );
1211 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), kicad9 ).IsBlocked() );
1212}
1213
1214
1215BOOST_AUTO_TEST_CASE( ContextualTitleShadowsUnusedUnsupportedProjectDefinition )
1216{
1217 SETTINGS_MANAGER settings;
1218 std::unique_ptr<SCHEMATIC> schematic;
1219 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1220 schematic->RootScreen()->GetTitleBlock().SetTitle( wxT( "Ordinary title" ) );
1221 schematic->Project().GetTextVars()[wxT( "TITLE" )] = wxT( "${PROPERTY.Unknown}" );
1222 SCH_TEXT* text = new SCH_TEXT();
1223 text->SetText( wxT( "${TITLE}" ) );
1224 schematic->RootScreen()->Append( text );
1225 const SCH_SHEET_PATH& path = schematic->CurrentSheet();
1226 BOOST_REQUIRE_EQUAL( text->GetShownText( &path, FOR_CANVAS ).ToStdString(), std::string( "Ordinary title" ) );
1227
1228 for( const auto& target : { kicad9, kicad10 } )
1229 {
1230 for( bool drop : { false, true } )
1231 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop ).IsLossy() );
1232 }
1233
1234 BOOST_CHECK_EQUAL( text->GetText().ToStdString(), std::string( "${TITLE}" ) );
1235 BOOST_CHECK_EQUAL( text->GetShownText( &path, FOR_CANVAS ).ToStdString(), std::string( "Ordinary title" ) );
1236}
1237
1238
1239BOOST_AUTO_TEST_CASE( PageBorderDependenciesBlockUnsupportedContextWithoutBlockingOrdinaryText )
1240{
1241 SETTINGS_MANAGER settings;
1242 std::unique_ptr<SCHEMATIC> schematic;
1243 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1244 schematic->Project().GetTextVars()[wxT( "BorderAlias" )] = wxT( "${PROPERTY.Unknown}" );
1245 schematic->RootScreen()->GetTitleBlock().SetTitle( wxT( "${BorderAlias}" ) );
1246 SCH_TEXT* text = new SCH_TEXT();
1247 text->SetText( wxT( "${TITLE}" ) );
1248 schematic->RootScreen()->Append( text );
1249 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsBlocked() );
1250 schematic->RootScreen()->GetTitleBlock().SetTitle( wxT( "Ordinary title" ) );
1251 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsLossy() );
1252 schematic->RootScreen()->GetTitleBlock().SetComment( 8, wxT( "@{1 + 2}" ) );
1253 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), kicad9 ).IsBlocked() );
1254 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsLossy() );
1255}
1256
1257
1258BOOST_AUTO_TEST_CASE( PaddedBusVectorsBlockBeforeNetNamesChange )
1259{
1260 std::vector<wxString> members;
1261 BOOST_REQUIRE( NET_SETTINGS::ParseBusVector( wxT( "D[00..02]" ), nullptr, &members ) );
1262 BOOST_REQUIRE_EQUAL( members.size(), 3 );
1263 BOOST_CHECK( members.front() == wxT( "D00" ) );
1264 SCH_SCREEN screen;
1265 SCH_LABEL* label = new SCH_LABEL();
1266 label->SetText( wxT( "D[00..02]" ) );
1267 screen.Append( label );
1268 for( const auto& target : { kicad9, kicad10 } )
1269 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target ).IsBlocked() );
1270 label->SetText( wxT( "D[0..2]" ) );
1271 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1272 SCHEMATIC schematic( nullptr );
1273 schematic.CreateDefaultScreens();
1274 auto alias = std::make_shared<BUS_ALIAS>();
1275 alias->SetName( wxT( "Data" ) );
1276 alias->SetMembers( { wxT( "D[00..02]" ) } );
1277 schematic.AddBusAlias( alias );
1278 BOOST_CHECK( ClassifySchematicStructureForDowngrade( schematic, kicad10 ).IsBlocked() );
1279}
1280
1281
1282BOOST_AUTO_TEST_CASE( RetainedAlternateVariantDefinitionsReportTheirRemoval )
1283{
1284 SCH_SCREEN screen;
1285 SCH_SYMBOL* symbol = new SCH_SYMBOL();
1286 SCH_SYMBOL_INSTANCE instance;
1287 instance.m_Path.push_back( KIID() );
1288 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
1289 variant.m_SymbolOverride = LIB_ID( wxT( "Device" ), wxT( "Alternate" ) );
1290 instance.m_Variants[variant.m_Name] = variant;
1291 symbol->AddHierarchicalReference( instance );
1292 screen.Append( symbol );
1294 DowngradeScreenInPlace( &screen, kicad10 );
1295 BOOST_CHECK( !symbol->GetInstances().front().m_Variants.at( variant.m_Name ).m_SymbolOverride );
1296 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
1297}
1298
1299
1300BOOST_AUTO_TEST_CASE( FrozenBusParsersCannotReinterpretMalformedOrOverflowingVectors )
1301{
1302 SCH_SCREEN screen;
1303 SCH_LABEL* label = new SCH_LABEL();
1304 screen.Append( label );
1305 for( const auto& target : { kicad9, kicad10 } )
1306 {
1307 for( const wxString& malformed : { wxT( "D[..2]" ), wxT( "D[1..]" ), wxT( "D[1..2" ) } )
1308 {
1309 BOOST_REQUIRE( !NET_SETTINGS::ParseBusVector( malformed, nullptr, nullptr ) );
1310 label->SetText( malformed );
1311 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target ).IsBlocked() );
1312 }
1313 label->SetText( wxString::Format( wxT( "D[%ld..%ld]" ), LONG_MAX - 1, LONG_MAX ) );
1314 BOOST_REQUIRE( NET_SETTINGS::ParseBusVector( label->GetText(), nullptr, nullptr ) );
1315 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target ).IsBlocked() );
1316 for( const wxString& ordinary :
1317 { wxT( "D[1..2]" ), wxT( "D[foo]" ), wxT( "D[..0]" ), wxT( "text [literal]" ) } )
1318 {
1319 label->SetText( ordinary );
1320 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, target ).IsLossy() );
1321 }
1322 }
1323 label->SetText( wxT( "D[1..2]+" ) );
1324 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad9 ).IsBlocked() );
1325 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
1326 label->SetText( wxT( "I^{2}C[1..2]" ) );
1327 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad9 ).IsLossy() );
1328 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1329}
1330
1331
1332BOOST_AUTO_TEST_CASE( UsedProjectBusVariablesAreCheckedWithNativeNetnameResolution )
1333{
1334 for( const auto& target : { kicad9, kicad10 } )
1335 {
1336 SETTINGS_MANAGER settings;
1337 std::unique_ptr<SCHEMATIC> schematic;
1338 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1339 SCH_LABEL* label = new SCH_LABEL();
1340 label->SetText( wxT( "${BUS}" ) );
1341 schematic->RootScreen()->Append( label );
1342 for( const wxString& bus : { wxT( "D[01..3]" ), wxT( "D[..2]" ), wxT( "D[1..2" ) } )
1343 {
1344 schematic->Project().GetTextVars()[wxT( "BUS" )] = bus;
1345 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), target ).IsBlocked() );
1346 }
1347 for( const wxString& ordinary : { wxT( "D[1..3]" ), wxT( "D[foo]" ) } )
1348 {
1349 schematic->Project().GetTextVars()[wxT( "BUS" )] = ordinary;
1350 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), target ).IsLossy() );
1351 }
1352 }
1353}
1354
1355
1356BOOST_AUTO_TEST_CASE( UnresolvedSelectedAlternateRefusesFlatteningBeforeMutation )
1357{
1358 SCHEMATIC schematic( nullptr );
1359 schematic.CreateDefaultScreens();
1360 SCH_SHEET_PATH path = schematic.BuildUnorderedSheetList().front();
1361 SCH_SYMBOL* symbol = new SCH_SYMBOL();
1362 path.LastScreen()->Append( symbol );
1363 SCH_SYMBOL_INSTANCE instance;
1364 instance.m_Path = path.Path();
1365 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
1366 variant.m_DNP = true;
1367 variant.m_SymbolOverride = LIB_ID( wxT( "Missing" ), wxT( "Alternate" ) );
1368 instance.m_Variants[variant.m_Name] = variant;
1369 symbol->AddHierarchicalReference( instance );
1370 wxString reason;
1371 BOOST_CHECK( !FlattenSchematicVariant( schematic, variant.m_Name, &reason ) );
1372 BOOST_CHECK( reason.Contains( wxT( "Missing:Alternate" ) ) );
1373 BOOST_CHECK( !symbol->GetDNP() );
1374}
1375
1376
1377BOOST_AUTO_TEST_CASE( SelectedAlternateBakesNativeLibraryFieldsPinMapAndPinIdentities )
1378{
1379 SETTINGS_MANAGER settings;
1380 std::unique_ptr<SCHEMATIC> schematic;
1381 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1382 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_variant_library" );
1383 SYMBOL_LIBRARY_ADAPTER* adapter = PROJECT_SCH::SymbolLibAdapter( &schematic->Project() );
1384 BOOST_REQUIRE( adapter );
1385 const auto previousTable = adapter->ProjectTable();
1386 struct TABLE_RESTORE
1387 {
1388 LIBRARY_MANAGER& manager;
1389 wxString directory;
1390 ~TABLE_RESTORE() { manager.LoadProjectTables( directory, { LIBRARY_TABLE_TYPE::SYMBOL } ); }
1391 } restore{ Pgm().GetLibraryManager(),
1392 previousTable && *previousTable ? wxFileName( ( *previousTable )->Path() ).GetPath() : wxString() };
1393 const wxString directory = tmp.Path().string();
1394 wxFile tableFile( directory + wxT( "/sym-lib-table" ), wxFile::write );
1395 BOOST_REQUIRE( tableFile.IsOpened() );
1396 BOOST_REQUIRE( tableFile.Write( wxT( "(sym_lib_table (version 7))\n" ) ) );
1397 tableFile.Close();
1398 restore.manager.LoadProjectTables( directory, { LIBRARY_TABLE_TYPE::SYMBOL } );
1399 LIBRARY_TABLE* table = adapter->ProjectTable().value_or( nullptr );
1401 LIBRARY_TABLE_ROW& row = table->InsertRow();
1402 row.SetNickname( wxT( "VariantLibrary" ) );
1403 row.SetURI( wxString( KI_TEST::GetEeschemaTestDataDir() ) + wxT( "libs/4xxx.kicad_sym" ) );
1404 row.SetType( wxT( "KiCad" ) );
1406 adapter->LoadOne( row.Nickname() );
1407 LIB_SYMBOL* alternate = adapter->LoadSymbol( row.Nickname(), wxT( "4001" ) );
1408 BOOST_REQUIRE( alternate );
1409 LIB_SYMBOL base( *alternate );
1410 base.SetName( wxT( "base" ) );
1411 base.SetLibId( LIB_ID( wxT( "Embedded" ), base.GetName() ) );
1412 base.GetField( FIELD_T::VALUE )->SetText( wxT( "Base value" ) );
1413 SCH_SYMBOL* symbol = new SCH_SYMBOL( base, base.GetLibId(), nullptr, 1, BODY_STYLE::BASE );
1414 schematic->RootScreen()->Append( symbol );
1415 const SCH_SHEET_PATH path = schematic->BuildUnorderedSheetList().front();
1416 symbol->AddHierarchicalReference( path.Path(), wxT( "U99" ), 1 );
1417 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
1418 variant.InitializeAttributes( *symbol );
1419 variant.m_SymbolOverride = LIB_ID( row.Nickname(), wxT( "4001" ) );
1420 variant.m_Fields[wxT( "Footprint" )] = wxEmptyString;
1422 symbol->AddVariant( path, variant );
1423 std::vector<KIID> pins;
1424 for( const auto& pin : symbol->GetRawPins() )
1425 pins.push_back( pin->m_Uuid );
1426 const wxString value = symbol->GetFieldText( wxT( "Value" ), &path, variant.m_Name );
1427 BOOST_REQUIRE( value != wxT( "Base value" ) );
1428 wxString reason;
1429 BOOST_REQUIRE_MESSAGE( FlattenSchematicVariant( *schematic, variant.m_Name, &reason ), reason );
1430 BOOST_CHECK( symbol->GetLibId() == *variant.m_SymbolOverride );
1431 BOOST_CHECK( symbol->GetField( FIELD_T::VALUE )->GetText() == value );
1432 BOOST_CHECK( symbol->GetField( FIELD_T::FOOTPRINT )->GetText().IsEmpty() );
1433 BOOST_CHECK( symbol->GetPinMapOverride() == variant.m_PinMapOverride );
1434 BOOST_REQUIRE_EQUAL( symbol->GetRawPins().size(), pins.size() );
1435 for( size_t i = 0; i < pins.size(); ++i )
1436 BOOST_CHECK( symbol->GetRawPins()[i]->m_Uuid == pins[i] );
1437 auto cached = schematic->RootScreen()->GetLibSymbols().find( symbol->GetSchSymbolLibraryName() );
1438 BOOST_REQUIRE( cached != schematic->RootScreen()->GetLibSymbols().end() );
1439 BOOST_CHECK( cached->second->GetField( FIELD_T::VALUE )->GetText()
1440 == alternate->GetField( FIELD_T::VALUE )->GetText() );
1441}
1442
1443
1444BOOST_AUTO_TEST_CASE( PostMasterUnsupportedTokenBackstop )
1445{
1446 for( const auto& target : { kicad9, kicad10 } )
1447 {
1448 for( const wxString& token :
1449 { wxT( "start_shape" ), wxT( "end_shape" ), wxT( "symbol_override" ), wxT( "custom_property" ) } )
1450 {
1451 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(" ) + token + wxT( " \"x\")" ), target ) == token );
1452 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(text \"(" ) + token + wxT( " x)\")" ), target ).IsEmpty() );
1453 }
1454 }
1455}
1456
1457
1458static nlohmann::json currentErcExclusionDocument()
1459{
1460 return {
1461 { "erc",
1462 { { "meta", { { "version", 1 } } },
1463 { "erc_exclusions",
1464 nlohmann::json::array(
1465 { { { "marker",
1466 { { "error_type", "ERCET_PIN_NOT_CONNECTED" },
1467 { "position", { { "x_nm", "12300" }, { "y_nm", "-45600" } } },
1468 { "items", nlohmann::json::array(
1469 { { { "value", "11111111-1111-1111-1111-111111111111" } } } ) } } },
1470 { "comment", "accepted" } } } ) } } }
1471 };
1472}
1473
1474
1475BOOST_AUTO_TEST_CASE( CurrentErcExclusionsMigrateExactlyAndRemainIdempotent )
1476{
1477 for( const auto& target : { kicad9, kicad10 } )
1478 {
1479 nlohmann::json document = currentErcExclusionDocument();
1480 COMPATIBILITY_REPORT report;
1481 DowngradeProjectFileJson( document, target, report );
1482 BOOST_CHECK_EQUAL( document["erc"]["meta"]["version"].get<int>(), 0 );
1483 const auto& exclusion = document["erc"]["erc_exclusions"].at( 0 );
1484 BOOST_CHECK( exclusion
1485 == nlohmann::json::array( { "pin_not_connected|123|-456|11111111-1111-1111-1111-111111111111|"
1486 "00000000-0000-0000-0000-000000000000|||",
1487 "accepted" } ) );
1488 BOOST_CHECK( !report.IsLossy() );
1489 const nlohmann::json once = document;
1490 COMPATIBILITY_REPORT repeated;
1491 DowngradeProjectFileJson( document, target, repeated );
1492 BOOST_CHECK( document == once );
1493 BOOST_CHECK( !repeated.IsLossy() );
1494 }
1495}
1496
1497
1498BOOST_AUTO_TEST_CASE( ErcExclusionsPreserveInstancePathsAndChildTextKeys )
1499{
1500 for( const auto& target : { kicad9, kicad10 } )
1501 {
1502 nlohmann::json document = currentErcExclusionDocument();
1503 auto& marker = document["erc"]["erc_exclusions"][0]["marker"];
1504 const nlohmann::json path = {
1505 { "path", nlohmann::json::array( { { { "value", "22222222-2222-2222-2222-222222222222" } } } ) }
1506 };
1507 marker["sheet_specific_path"] = path;
1508 marker["main_item_sheet_path"] = path;
1509 marker["aux_item_sheet_path"] = path;
1510 COMPATIBILITY_REPORT pathReport;
1511 nlohmann::json allPaths = document;
1512 DowngradeProjectFileJson( allPaths, target, pathReport );
1513 BOOST_REQUIRE_EQUAL( allPaths["erc"]["erc_exclusions"].size(), 1 );
1514 BOOST_CHECK_EQUAL( allPaths["erc"]["erc_exclusions"][0][0].get<std::string>(),
1515 "pin_not_connected|123|-456|11111111-1111-1111-1111-111111111111|"
1516 "00000000-0000-0000-0000-000000000000|/22222222-2222-2222-2222-222222222222|"
1517 "/22222222-2222-2222-2222-222222222222|/22222222-2222-2222-2222-222222222222" );
1518 BOOST_CHECK( !pathReport.IsLossy() );
1519 marker.erase( "aux_item_sheet_path" );
1520 marker["error_type"] = "ERCET_GENERIC_WARNING";
1521 marker["child"] = { { "text_value", "raw warning text" } };
1522 COMPATIBILITY_REPORT report;
1523 DowngradeProjectFileJson( document, target, report );
1524 BOOST_REQUIRE_EQUAL( document["erc"]["erc_exclusions"].size(), 1 );
1525 BOOST_CHECK_EQUAL( document["erc"]["erc_exclusions"][0][0].get<std::string>(),
1526 "generic-warning|123|-456|11111111-1111-1111-1111-111111111111|raw warning "
1527 "text|/22222222-2222-2222-2222-222222222222|/22222222-2222-2222-2222-222222222222|" );
1528 BOOST_CHECK( !report.IsLossy() );
1529 }
1530}
1531
1532
1533BOOST_AUTO_TEST_CASE( UnrepresentableErcExclusionsAreReportedAndDropped )
1534{
1535 for( const auto& target : { kicad9, kicad10 } )
1536 {
1537 for( int invalid = 0; invalid < 4; ++invalid )
1538 {
1539 nlohmann::json document = currentErcExclusionDocument();
1540 auto& marker = document["erc"]["erc_exclusions"][0]["marker"];
1541 if( invalid == 0 )
1542 marker["error_type"] = "ERCET_EMPTY_LABEL_NAME";
1543 else if( invalid == 1 )
1544 marker["items"][0]["value"] = "malformed-id";
1545 else if( invalid == 2 )
1546 marker["position"]["x_nm"] = "12301";
1547 else
1548 {
1549 marker["error_type"] = "ERCET_GENERIC_WARNING";
1550 marker["child"] = { { "text_value", "cannot|encode" } };
1551 }
1552 COMPATIBILITY_REPORT report;
1553 DowngradeProjectFileJson( document, target, report );
1554 BOOST_CHECK( !report.IsBlocked() );
1556 BOOST_CHECK( document["erc"]["erc_exclusions"].empty() );
1557 }
1558 }
1559}
1560
1561
1562BOOST_AUTO_TEST_CASE( UnreviewedProjectSchemasBlockWithoutMutation )
1563{
1564 for( const auto& target : { kicad9, kicad10 } )
1565 {
1566 for( int schema = 0; schema < 5; ++schema )
1567 {
1568 nlohmann::json document = currentErcExclusionDocument();
1569 if( schema == 0 )
1570 document["meta"]["version"] = 5;
1571 else if( schema == 1 )
1572 document["board"]["design_settings"]["meta"]["version"] = 4;
1573 else if( schema == 2 )
1574 document["erc"]["meta"]["version"] = 2;
1575 else if( schema == 3 )
1576 document["tuning_profiles"]["meta"]["version"] = 3;
1577 else
1578 document["net_settings"]["meta"]["version"] = 6;
1579 const nlohmann::json original = document;
1580 COMPATIBILITY_REPORT report;
1581 DowngradeProjectFileJson( document, target, report );
1582 BOOST_CHECK( report.IsBlocked() );
1583 BOOST_CHECK( document == original );
1584 }
1585 }
1586}
1587
1588
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:130
What an export to a given target will keep, approximate, drop, or block.
bool IsLossy() const
True if anything is approximated or dropped. This is what triggers the lossy warning.
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
int Count(DOWNGRADE_BUCKET aBucket) const
const std::vector< COMPAT_ENTRY > & Entries() const
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
bool HasCustomProperties() const
Definition eda_item.h:269
void SetCustomProperty(const wxString &aKey, const wxString &aValue)
Definition eda_item.h:255
virtual void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:337
void SetEndEnding(const LINE_ENDING &aEnding)
Definition eda_shape.h:189
FILL_T GetFillMode() const
Definition eda_shape.h:150
virtual void SetCornerRadius(int aRadius)
SHAPE_POLY_SET & GetPolyShape()
virtual void SetEllipseEndAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:424
SHAPE_T GetShape() const
Definition eda_shape.h:177
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:333
virtual void SetEllipseCenter(const VECTOR2I &aPt)
Definition eda_shape.h:378
virtual void SetEllipseMinorRadius(int aR)
Definition eda_shape.h:396
virtual void SetEllipseMajorRadius(int aR)
Definition eda_shape.h:387
virtual void SetEllipseStartAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:415
LINE_ENDING_STYLE GetEndEndingStyle() const
Definition eda_shape.h:194
void SetFillMode(FILL_T aFill)
virtual void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:287
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
Definition kiid.h:46
A generic fixture for loading schematics and associated settings for qa tests.
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
Definition file_utils.h:61
std::optional< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
void LoadProjectTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the project library tables in the given list, or all tables if no list is given
void SetNickname(const wxString &aNickname)
void SetType(const wxString &aType)
void SetURI(const wxString &aUri)
void SetScope(LIBRARY_TABLE_SCOPE aScope)
const wxString & Nickname() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
Define a library symbol object.
Definition lib_symbol.h:114
const LIB_ID & GetLibId() const override
Definition lib_symbol.h:190
void SetDuplicatePinNumbersAreJumpers(bool aEnabled)
Definition lib_symbol.h:885
SCH_FIELD * GetField(const wxString &aFieldName)
Find a field within this symbol matching aFieldName; return nullptr if not found.
void SetAssociatedFootprints(std::vector< ASSOCIATED_FOOTPRINT > aList)
Definition lib_symbol.h:272
void SetBodyStyleNames(const std::vector< wxString > &aBodyStyleNames)
Definition lib_symbol.h:913
LIB_ITEMS_CONTAINER & GetDrawItems()
Return a reference to the draw item list.
Definition lib_symbol.h:843
wxString GetName() const override
Definition lib_symbol.h:183
bool IsLocalPower() const override
const std::vector< wxString > & GetBodyStyleNames() const
void SetLocalPower()
int GetBodyStyleCount() const override
The body styles are a property of the drawings, which a derived symbol inherits from its root symbol ...
void SetLibId(const LIB_ID &aLibId)
bool GetDuplicatePinNumbersAreJumpers() const
Definition lib_symbol.h:884
std::vector< SCH_PIN * > GetGraphicalPins(int aUnit=0, int aBodyStyle=0) const override
Graphical pins: Return schematic pin objects as drawn (unexpanded), filtered by unit/body.
void AddDrawItem(SCH_ITEM *aItem, bool aSort=true)
Add a new draw aItem to the draw object list and sort according to aSort.
virtual void SetName(const wxString &aName)
Decorative shape (arrowhead, circle, square) at the start or end of a graphic line,...
Definition line_ending.h:62
size_t size(int aType=UNDEFINED_TYPE) const
static bool ParseBusVector(const wxString &aBus, wxString *aName, std::vector< wxString > *aMemberList)
Parse a bus vector (e.g.
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:125
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
double GetOpacity() const
void SetOrientation(const EDA_ANGLE &aAngle)
Set the rotation of the image content relative to the stored raster.
void SetOpacity(double aOpacity)
Holds all the data relating to one schematic.
Definition schematic.h:153
void CreateDefaultScreens()
void AddTopLevelSheet(SCH_SHEET *aSheet)
Add a new top-level sheet to the schematic.
SCH_SHEET_LIST BuildUnorderedSheetList() const
void AddBusAlias(std::shared_ptr< BUS_ALIAS > aAlias)
Object to handle a bitmap image that can be inserted in a schematic.
Definition sch_bitmap.h:36
REFERENCE_IMAGE & GetReferenceImage()
Definition sch_bitmap.h:50
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:139
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) const
void SetText(const wxString &aText) override
A set of SCH_ITEMs (i.e., without duplicates).
Definition sch_group.h:48
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
void SetLocked(bool aLocked) override
Definition sch_item.h:262
int GetBodyStyle() const
Definition sch_item.h:253
bool IsLocked() const override
Definition sch_item.cpp:179
wxString ResolveText(const wxString &aText, const SCH_SHEET_PATH *aPath, int aDepth=0) const
Definition sch_item.cpp:464
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
VECTOR2I GetEndPoint() const
Definition sch_line.h:145
LINE_ENDING_STYLE GetStartEndingStyle() const
Definition sch_line.h:207
void SetStartEnding(const LINE_ENDING &aEnding)
Definition sch_line.h:202
virtual void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_line.h:199
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:146
bool GetExcludedFromSim(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
void SetDNP(bool aDNP, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
void SetExcludedFromSim(bool aExcludeFromSim, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear the exclude from simulation flag.
bool GetDNP(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Set or clear the 'Do Not Populate' flag.
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
void AddLibSymbol(LIB_SYMBOL *aLibSymbol)
Add aLibSymbol to the library symbol map.
const std::map< wxString, LIB_SYMBOL * > & GetLibSymbols() const
Fetch a list of unique LIB_SYMBOL object pointers required to properly render each SCH_SYMBOL in this...
Definition sch_screen.h:565
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
bool CheckIfOnDrawList(const SCH_ITEM *aItem) const
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_shape.cpp:98
STROKE_PARAMS GetStroke() const override
Definition sch_shape.h:59
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
SCH_SCREEN * LastScreen()
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
Variant information for a schematic sheet.
void InitializeAttributes(const SCH_SHEET &aSheet)
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:50
wxString GetFieldText(const SCH_FIELD *aField, const wxString &aFieldName, const SCH_SHEET_PATH *aPath, const wxString &aVariantName) const
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:434
void RemoveInstance(const KIID_PATH &aInstancePath)
bool HasRootInstance() const
Check to see if this sheet has a root sheet instance.
std::vector< SCH_FIELD > & GetFields()
Return a reference to the vector holding the sheet's fields.
Definition sch_sheet.h:93
bool GetExcludedFromBOM(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
void AddInstance(const SCH_SHEET_INSTANCE &aInstance)
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:187
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add aFieldName to the list of fields.
Variant information for a schematic symbol.
PIN_MAP_INSTANCE_OVERRIDE m_PinMapOverride
Per-instance pin-to-pad map override for this variant (issue #2282).
std::optional< LIB_ID > m_SymbolOverride
Alternate library symbol to substitute for the base symbol in this variant, if any.
void InitializeAttributes(const SCH_SYMBOL &aSymbol)
Schematic symbol object.
Definition sch_symbol.h:76
std::vector< std::unique_ptr< SCH_PIN > > & GetRawPins()
Definition sch_symbol.h:715
const std::vector< SCH_SYMBOL_INSTANCE > & GetInstances() const
Definition sch_symbol.h:136
wxString GetSchSymbolLibraryName() const
PIN_MAP_INSTANCE_OVERRIDE GetPinMapOverride(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
void AddHierarchicalReference(const KIID_PATH &aPath, const wxString &aRef, int aUnit)
Add a full hierarchical reference to this symbol.
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:166
bool GetExcludedFromPosFiles(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
void AddVariant(const SCH_SHEET_PATH &aInstance, const SCH_SYMBOL_VARIANT &aVariant)
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:185
virtual bool GetDNP(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Set or clear the 'Do Not Populate' flag.
wxString GetFieldText(const SCH_FIELD *aField, const wxString &aFieldName, const SCH_SHEET_PATH *aPath, const wxString &aVariantName) const
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
double Area(bool aAbsolute=true) const
Return the area of this chain.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Simple container to manage line stroke parameters.
LINE_STYLE GetLineStyle() const
An interface to the global shared library manager that is schematic-specific and linked to one projec...
std::optional< LIB_STATUS > LoadOne(LIB_DATA *aLib) override
Loads or reloads the given library, if it exists.
LIB_SYMBOL * LoadSymbol(const wxString &aNickname, const wxString &aName)
Load a LIB_SYMBOL having aName from the library given by aNickname.
bool GetExcludedFromPosFiles(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Definition symbol.h:231
void SetExcludedFromPosFiles(bool aExclude, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear exclude from position files flag.
Definition symbol.h:225
wxString m_Name
bool m_ExcludedFromBOM
std::map< wxString, wxString > m_Fields
void FinalizeTextVarExpansion(wxString &aText, RESOLUTION_CONTEXT aContext)
Definition common.cpp:88
@ FOR_CANVAS
Definition common.h:92
@ INTERNAL
Definition common.h:96
@ BLOCK
Cannot be represented at all, export to this target is not possible.
@ LOWER
Approximated down to a simpler equivalent.
@ DROP
No equivalent, left out.
static bool empty(const wxTextEntryBase *aCtrl)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:472
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:474
@ NO_FILL
Definition eda_fill.h:30
@ HATCH
Definition eda_fill.h:34
@ FILLED_SHAPE
Fill with object color.
Definition eda_fill.h:31
SHAPE_T
Definition eda_shape.h:54
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
@ LAYER_DEVICE
Definition layer_ids.h:497
@ LAYER_NOTES
Definition layer_ids.h:498
Test utilities for timestamps.
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:461
static bool readFile(const wxString &aFileName, wxString &aOut, size_t aLimit=0)
Read a file into aOut.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
void DowngradeProjectFileJson(nlohmann::json &aDoc, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport)
Migrate a .kicad_pro document down to what the target release expects.
COMPATIBILITY_REPORT ClassifySchematicStructureForDowngrade(const SCHEMATIC &aSchematic, const DOWNGRADE_TARGET &aTarget)
Schematic-wide structure the per-screen rules cannot see. Does not modify it.
void DowngradeScreenInPlace(SCH_SCREEN *aScreen, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Approximate or remove, in place, what the target cannot represent.
bool FlattenSchematicVariant(SCHEMATIC &aSchematic, const wxString &aVariantName, wxString *aConflict)
Bake a variant into the base attributes of every symbol and sheet.
int SymbolLibDowngradeCoveredVersion()
The symbol library format the denylist was generated against.
COMPATIBILITY_REPORT ClassifyLibSymbolForDowngrade(const LIB_SYMBOL *aSymbol, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a library symbol would lose if exported to an older KiCad. Does not modify it.
void DowngradeLibSymbolInPlace(LIB_SYMBOL *aSymbol, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Symbol-scoped downgrade for standalone .kicad_sym files the screen pass never sees.
void SaveSchematicForTarget(SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a schematic sheet file stamped for the target, using the writer for that version.
wxString FindUnsupportedSchToken(const wxString &aSerialized, const DOWNGRADE_TARGET &aTarget)
Scan serialized schematic text for tokens the target cannot parse.
int SchDowngradeCoveredVersion()
The format the denylist was generated against. A format bump forces a review.
COMPATIBILITY_REPORT ClassifyScreenForDowngrade(const SCH_SCREEN *aScreen, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a schematic screen would lose if exported to an older KiCad.
#define SEXPR_SYMBOL_LIB_FILE_VERSION
This file contains the file format version information for the s-expression schematic and symbol libr...
#define SEXPR_SCHEMATIC_FILE_VERSION
Schematic file version.
Class to handle a set of SCH_ITEMs.
static void LoadSchematic(SCHEMATIC *aSchematic, SCH_SHEET *aRootSheet, const wxString &aFileName, REPORTER *aReporter)
@ BASE
Definition sch_item.h:64
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
An older KiCad release we can export a project back to.
std::vector< char > decompressedData
PIN_MAP_OVERRIDE_MODE m_Mode
Definition pin_map.h:201
A simple container for sheet instance information.
std::map< wxString, SCH_SHEET_VARIANT > m_Variants
A list of sheet variants.
A simple container for schematic symbol instance information.
std::map< wxString, SCH_SYMBOL_VARIANT > m_Variants
A list of symbol variants.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_CHECK(res.column_datatype(0)==SQL_NUMERIC||res.column_datatype(0)==SQL_DECIMAL)
const std::string table
BOOST_REQUIRE_EQUAL(rows.size(), 1)
BOOST_CHECK_EQUAL(std::any_cast< std::string >(rows[0].at("val")), "12345678901234567.89")
BOOST_REQUIRE_MESSAGE(dc.CacheTableInfo(table, { "part_id", "val" }), dc.GetLastError())
BOOST_REQUIRE(res.columns()==1)
BOOST_AUTO_TEST_SUITE_END()
std::string path
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
static nlohmann::json currentErcExclusionDocument()
static const DOWNGRADE_TARGET & kicad10
static const DOWNGRADE_TARGET & kicad9
BOOST_FIXTURE_TEST_CASE(EmbeddedFilesSurviveMissingRootInstanceForBothTargets, KI_TEST::SCHEMATIC_TEST_FIXTURE)
BOOST_AUTO_TEST_CASE(LibSymbolJumperPinsDroppedForKicad9)
const SHAPE_LINE_CHAIN chain
BOOST_TEST_CONTEXT("Test Clearance")
int actual
#define M_PI
@ SCH_GROUP_T
Definition typeinfo.h:169
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_SHAPE_T
Definition typeinfo.h:145
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708