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_label.h>
42#include <sch_text.h>
43#include <bus_alias.h>
45#include <project.h>
47#include <nlohmann/json.hpp>
49#include <lib_symbol.h>
50#include <sch_file_versions.h>
52#include <downgrade_target.h>
56#include <eeschema_test_utils.h>
57#include <schematic_file_util.h>
58#include <pgm_base.h>
59#include <project_sch.h>
63#include <wx/file.h>
64
65#include <wx/filefn.h>
66
67
68BOOST_AUTO_TEST_SUITE( SchDowngrade )
69
70static const DOWNGRADE_TARGET& kicad9 = KI_TEST::RequireDowngradeTarget( wxT( "9.0" ) );
71static const DOWNGRADE_TARGET& kicad10 = KI_TEST::RequireDowngradeTarget( wxT( "10.0" ) );
72
73
74// A library symbol is downgraded through its own path. Jumper pin groups are dropped for 9.0.
75BOOST_AUTO_TEST_CASE( LibSymbolJumperPinsDroppedForKicad9 )
76{
77 LIB_SYMBOL sym( wxT( "test" ) );
79
81
82 BOOST_CHECK( !sym.GetDuplicatePinNumbersAreJumpers() );
83}
84
85
86// The schematic ellipse primitive only exists since 20260508, so 10.0 gets an approximation.
87BOOST_AUTO_TEST_CASE( EllipseIsLoweredForKicad10 )
88{
89 SCH_SCREEN screen;
90 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
91 ellipse->SetStart( VECTOR2I( 0, 0 ) );
92 ellipse->SetEnd( VECTOR2I( 500000, 0 ) );
93 screen.Append( ellipse );
94
96
97 BOOST_CHECK( report.IsLossy() );
99}
100
101
102BOOST_AUTO_TEST_CASE( EllipseTransformedToPolygon )
103{
104 SCH_SCREEN screen;
105 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
106 ellipse->SetStart( VECTOR2I( 0, 0 ) );
107 ellipse->SetEnd( VECTOR2I( 500000, 0 ) );
108 screen.Append( ellipse );
109
111
112 BOOST_CHECK( ellipse->GetShape() == SHAPE_T::POLY );
113}
114
115
116// An unfilled ellipse must be lowered to its outline polygon, not to stroke debris.
117BOOST_AUTO_TEST_CASE( UnfilledEllipseKeepsOutline )
118{
119 SCH_SCREEN screen;
120 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
121 ellipse->SetEllipseCenter( VECTOR2I( 0, 0 ) );
122 ellipse->SetEllipseMajorRadius( schIUScale.mmToIU( 5 ) );
123 ellipse->SetEllipseMinorRadius( schIUScale.mmToIU( 3 ) );
124 ellipse->SetFillMode( FILL_T::NO_FILL );
125 screen.Append( ellipse );
126
128
129 BOOST_REQUIRE( ellipse->GetShape() == SHAPE_T::POLY );
130 BOOST_REQUIRE_EQUAL( ellipse->GetPolyShape().OutlineCount(), 1 );
131 BOOST_CHECK( ellipse->GetFillMode() == FILL_T::NO_FILL );
132
133 double areaMM =
134 std::abs( ellipse->GetPolyShape().Outline( 0 ).Area() ) / ( schIUScale.IU_PER_MM * schIUScale.IU_PER_MM );
135
136 BOOST_CHECK_CLOSE( areaMM, M_PI * 5.0 * 3.0, 2.0 );
137}
138
139
140// An elliptical arc becomes an open polyline running from arc start to arc end.
141BOOST_AUTO_TEST_CASE( EllipseArcLoweredToOpenPolyline )
142{
143 SCH_SCREEN screen;
145 arc->SetEllipseCenter( VECTOR2I( 0, 0 ) );
146 arc->SetEllipseMajorRadius( schIUScale.mmToIU( 5 ) );
147 arc->SetEllipseMinorRadius( schIUScale.mmToIU( 3 ) );
150 screen.Append( arc );
151
153
155 BOOST_REQUIRE_EQUAL( arc->GetPolyShape().OutlineCount(), 1 );
156
157 const SHAPE_LINE_CHAIN& chain = arc->GetPolyShape().COutline( 0 );
158 BOOST_REQUIRE_GE( chain.PointCount(), 5 );
159
160 // One end sits on the major axis, the other on the minor axis.
161 double firstDist = chain.CPoint( 0 ).EuclideanNorm();
162 double lastDist = chain.CPoint( chain.PointCount() - 1 ).EuclideanNorm();
163 double tolerance = schIUScale.mmToIU( 0.05 );
164
165 BOOST_CHECK( std::abs( firstDist - schIUScale.mmToIU( 5 ) ) < tolerance );
166 BOOST_CHECK( std::abs( lastDist - schIUScale.mmToIU( 3 ) ) < tolerance );
167 BOOST_CHECK( chain.CPoint( 0 ) != chain.CPoint( chain.PointCount() - 1 ) );
168}
169
170
171// Ungrouping must survive nested groups. Members stay on the screen, groups go.
172BOOST_AUTO_TEST_CASE( NestedGroupsUngroupSafely )
173{
174 SCH_SCREEN screen;
175
176 SCH_SHAPE* innerShape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
177 SCH_SHAPE* outerShape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
178 screen.Append( innerShape );
179 screen.Append( outerShape );
180
181 SCH_GROUP* inner = new SCH_GROUP( &screen );
182 inner->AddItem( innerShape );
183 screen.Append( inner );
184
185 SCH_GROUP* outer = new SCH_GROUP( &screen );
186 outer->AddItem( outerShape );
187 outer->AddItem( inner );
188 screen.Append( outer );
189
191
192 DowngradeScreenInPlace( &screen, kicad9 );
193
194 auto groups = screen.Items().OfType( SCH_GROUP_T );
195 BOOST_CHECK( groups.begin() == groups.end() );
196
197 auto shapes = screen.Items().OfType( SCH_SHAPE_T );
198 BOOST_CHECK_EQUAL( std::distance( shapes.begin(), shapes.end() ), 2 );
199
200 BOOST_CHECK( innerShape->GetParentGroup() == nullptr );
201 BOOST_CHECK( outerShape->GetParentGroup() == nullptr );
202}
203
204
205// Hatched fills postdate 9.0, so they become a solid fill for 9.0.
206BOOST_AUTO_TEST_CASE( HatchedFillLoweredForKicad9 )
207{
208 SCH_SCREEN screen;
209 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
210 shape->SetFillMode( FILL_T::HATCH );
211 screen.Append( shape );
212
214
215 DowngradeScreenInPlace( &screen, kicad9 );
216
217 BOOST_CHECK( shape->GetFillMode() == FILL_T::FILLED_SHAPE );
218}
219
220
221// Symbol bodies are written from the screen library cache, so an ellipse there must be
222// counted and lowered too, not just shapes placed on the screen.
223BOOST_AUTO_TEST_CASE( LibSymbolEllipseLoweredForKicad10 )
224{
225 SCH_SCREEN screen;
226 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
227 SCH_SHAPE* ellipse = new SCH_SHAPE( SHAPE_T::ELLIPSE );
228 ellipse->SetStart( VECTOR2I( 0, 0 ) );
229 ellipse->SetEnd( VECTOR2I( 500000, 0 ) );
230 symbol->AddDrawItem( ellipse );
231 screen.AddLibSymbol( symbol );
232
234
236
237 BOOST_CHECK( ellipse->GetShape() == SHAPE_T::POLY );
238}
239
240
241// Turning a local power symbol global merges nets across sheets. A netlist change must never
242// ship silently, so the export blocks for 9.0.
243BOOST_AUTO_TEST_CASE( LocalPowerSymbolBlocksKicad9 )
244{
245 SCH_SCREEN screen;
246 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
247 symbol->SetLocalPower();
248 screen.AddLibSymbol( symbol );
249
252
253 DowngradeScreenInPlace( &screen, kicad9 );
254
255 BOOST_CHECK( symbol->IsLocalPower() );
256}
257
258
259// Rule area flags (20250610) postdate 9.0, so they are dropped and reported for 9.0.
260BOOST_AUTO_TEST_CASE( RuleAreaFlagsDroppedForKicad9 )
261{
262 SCH_SCREEN screen;
263 SCH_RULE_AREA* area = new SCH_RULE_AREA();
264 area->SetExcludedFromSim( true );
265 area->SetDNP( true );
266 screen.Append( area );
267
269
270 DowngradeScreenInPlace( &screen, kicad9 );
271
272 BOOST_CHECK( !area->GetExcludedFromSim() );
273 BOOST_CHECK( !area->GetDNP() );
274}
275
276
277// Rounded rectangles (20250829) postdate 9.0, so 9.0 gets a polygon that keeps the rounding.
278BOOST_AUTO_TEST_CASE( RoundedRectangleLoweredForKicad9 )
279{
280 SCH_SCREEN screen;
282 rect->SetStart( VECTOR2I( 0, 0 ) );
283 rect->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
284 rect->SetCornerRadius( schIUScale.mmToIU( 1 ) );
286 screen.Append( rect );
287
290
291 DowngradeScreenInPlace( &screen, kicad9 );
292
294 BOOST_REQUIRE_EQUAL( rect->GetPolyShape().OutlineCount(), 1 );
295
296 // A 10 mm by 6 mm rectangle loses ( 4 - pi ) r^2 to the rounded corners.
297 double areaMM =
298 std::abs( rect->GetPolyShape().Outline( 0 ).Area() ) / ( schIUScale.IU_PER_MM * schIUScale.IU_PER_MM );
299
300 BOOST_CHECK_CLOSE( areaMM, 60.0 - ( 4.0 - M_PI ), 2.0 );
301}
302
303
304// A stacked pin number with escaped characters (20260622) is misread by both targets, so the
305// export must block instead of silently renaming pins.
306BOOST_AUTO_TEST_CASE( EscapedStackedPinBlocksExport )
307{
308 SCH_SCREEN screen;
309 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
310 SCH_PIN* pin = new SCH_PIN( symbol );
311 pin->SetNumber( wxT( "[A\\,1,B]" ) );
312 symbol->AddDrawItem( pin );
313 screen.AddLibSymbol( symbol );
314
317}
318
319
320// Plain stacked notation (20250901) is expanded by 10.0, but 9.0 reads the brackets as one
321// literal pin number and the netlist silently changes. So 9.0 blocks and 10.0 does not.
322BOOST_AUTO_TEST_CASE( PlainStackedPinBlocksKicad9Only )
323{
324 SCH_SCREEN screen;
325 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
326 SCH_PIN* pin = new SCH_PIN( symbol );
327 pin->SetNumber( wxT( "[A1,B2]" ) );
328 symbol->AddDrawItem( pin );
329 screen.AddLibSymbol( symbol );
330
333}
334
335
336// Invalid stacked notation is read as one literal pin number by every KiCad, so it must not
337// block. The escapes rule only fires when the notation actually parses.
338BOOST_AUTO_TEST_CASE( InvalidStackedNotationDoesNotBlock )
339{
340 SCH_SCREEN screen;
341 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
342 SCH_PIN* pin = new SCH_PIN( symbol );
343 pin->SetNumber( wxT( "[A\\,B" ) );
344 symbol->AddDrawItem( pin );
345 screen.AddLibSymbol( symbol );
346
349}
350
351
352// A pin map living only in the screen's library cache is dropped by the transform, so it must
353// be counted for consent too.
354BOOST_AUTO_TEST_CASE( LibCachePinMapsCountedForDrop )
355{
356 SCH_SCREEN screen;
357 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
358 symbol->SetAssociatedFootprints( { { LIB_ID( wxT( "Lib" ), wxT( "FP" ) ), wxT( "map" ) } } );
359 screen.AddLibSymbol( symbol );
360
362
364
365 BOOST_CHECK( screen.GetLibSymbols().begin()->second->GetAssociatedFootprints().empty() );
366}
367
368
369// The 10.0 group format stores its own lock, so a locked group survives a 10.0 export while
370// other locked items are still unlocked and reported.
371BOOST_AUTO_TEST_CASE( GroupLockKeptForKicad10 )
372{
373 SCH_SCREEN screen;
374
375 // A locked item outside the group, so its lock cannot be inherited from the group.
376 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
377 shape->SetLocked( true );
378 screen.Append( shape );
379
380 SCH_SHAPE* member = new SCH_SHAPE( SHAPE_T::RECTANGLE );
381 screen.Append( member );
382
383 SCH_GROUP* group = new SCH_GROUP( &screen );
384 group->AddItem( member );
385 group->SetLocked( true );
386 screen.Append( group );
387
389
391
392 BOOST_CHECK( !shape->IsLocked() );
393 BOOST_CHECK( group->IsLocked() );
394}
395
396
397// A sheet's variant overrides must flatten like a symbol's: exclusion flags and field
398// overrides bake into the base sheet, not just DNP.
399BOOST_AUTO_TEST_CASE( SheetVariantFlattenedIntoSchematic )
400{
401 SCHEMATIC schematic( nullptr );
402 schematic.CreateDefaultScreens();
403
404 SCH_SHEET_LIST rootPaths = schematic.BuildUnorderedSheetList();
405 BOOST_REQUIRE( !rootPaths.empty() );
406
407 SCH_SHEET_PATH rootPath = rootPaths.at( 0 );
408 SCH_SCREEN* rootScreen = rootPath.LastScreen();
409 BOOST_REQUIRE( rootScreen != nullptr );
410
411 SCH_SHEET* child = new SCH_SHEET( &schematic );
412 child->SetScreen( new SCH_SCREEN( &schematic ) );
413 child->SetFileName( wxT( "child.kicad_sch" ) );
414 rootScreen->Append( child );
415
416 child->AddField( SCH_FIELD( child, FIELD_T::USER, wxT( "Note" ) ) );
417 child->GetFields().back().SetText( wxT( "base note" ) );
418
419 SCH_SHEET_PATH childPath = rootPath;
420 childPath.push_back( child );
421
422 SCH_SHEET_INSTANCE instance;
423 instance.m_Path = childPath.Path();
424
425 SCH_SHEET_VARIANT variant( wxT( "V1" ) );
426 variant.InitializeAttributes( *child );
427 variant.m_ExcludedFromBOM = true;
428 variant.m_Fields[wxT( "Note" )] = wxT( "variant note" );
429 instance.m_Variants[wxT( "V1" )] = variant;
430
431 child->AddInstance( instance );
432
433 BOOST_CHECK( !child->GetExcludedFromBOM() );
434
435 wxString conflict;
436 BOOST_REQUIRE( FlattenSchematicVariant( schematic, wxT( "V1" ), &conflict ) );
437
438 BOOST_CHECK( child->GetExcludedFromBOM() );
439 BOOST_CHECK( child->GetFieldText( wxT( "Note" ) ) == wxT( "variant note" ) );
440}
441
442
443// The 9.0 target opens a single root sheet, so a second top-level sheet blocks that export.
444// 10.0 has the flat hierarchy and stays clean.
445BOOST_AUTO_TEST_CASE( MultipleTopLevelSheetsBlockKicad9 )
446{
447 SCHEMATIC schematic( nullptr );
448 schematic.CreateDefaultScreens();
449
450 SCH_SHEET* second = new SCH_SHEET( &schematic );
451 second->SetScreen( new SCH_SCREEN( &schematic ) );
452 schematic.AddTopLevelSheet( second );
453
455 0 );
457 1 );
458}
459
460
461// A variant selecting a different library symbol has no representation in a target that keeps
462// variants but not the choice, so that export must refuse. A target that removes variants
463// wholesale loses the choice along with them and needs only the drop row.
464BOOST_AUTO_TEST_CASE( AlternateVariantSymbolBlocksTargetThatKeepsVariants )
465{
466 SCHEMATIC schematic( nullptr );
467 schematic.CreateDefaultScreens();
468
469 SCH_SHEET_LIST paths = schematic.BuildUnorderedSheetList();
470 BOOST_REQUIRE( !paths.empty() );
471
472 SCH_SHEET_PATH path = paths.at( 0 );
473 SCH_SCREEN* screen = path.LastScreen();
474 BOOST_REQUIRE( screen != nullptr );
475
476 SCH_SYMBOL* symbol = new SCH_SYMBOL();
477 screen->Append( symbol );
478
479 SCH_SYMBOL_INSTANCE instance;
480 instance.m_Path = path.Path();
481
482 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
483 variant.m_SymbolOverride = LIB_ID( wxT( "Device" ), wxT( "Regulator_5pin" ) );
484 instance.m_Variants[wxT( "Alt" )] = variant;
485
486 symbol->AddHierarchicalReference( instance );
487
489 1 );
491 0 );
492}
493
494
495// A pin-to-pad override stored on a variant record must be counted and cleared like the base one.
496BOOST_AUTO_TEST_CASE( VariantPinMapOverrideDroppedForKicad10 )
497{
498 SCH_SCREEN screen;
499 SCH_SYMBOL* symbol = new SCH_SYMBOL();
500
501 SCH_SYMBOL_INSTANCE instance;
502 instance.m_Path.push_back( KIID() );
503
504 SCH_SYMBOL_VARIANT variant( wxT( "V1" ) );
506 instance.m_Variants[wxT( "V1" )] = variant;
507
508 symbol->AddHierarchicalReference( instance );
509 screen.Append( symbol );
510
512
514
515 BOOST_REQUIRE_EQUAL( symbol->GetInstances().size(), 1 );
516 const auto& variants = symbol->GetInstances()[0].m_Variants;
517 BOOST_REQUIRE( variants.count( wxT( "V1" ) ) );
518 BOOST_CHECK( variants.at( wxT( "V1" ) ).m_PinMapOverride.IsDefault() );
519}
520
521
522// Named body styles postdate 9.0, so their names are dropped and reported for 9.0.
523BOOST_AUTO_TEST_CASE( NamedBodyStylesDroppedForKicad9 )
524{
525 SCH_SCREEN screen;
526 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxT( "test" ) );
527 symbol->SetBodyStyleNames( { wxT( "styleA" ), wxT( "styleB" ) } );
528 screen.AddLibSymbol( symbol );
529
531
532 DowngradeScreenInPlace( &screen, kicad9 );
533
534 BOOST_CHECK( symbol->GetBodyStyleNames().empty() );
535}
536
537
538// KiCad 9 only supports selectors 1 and 2. A used third style must retain its drawing
539// and pin positions, not silently fall back to style 1 (even with --force).
540BOOST_AUTO_TEST_CASE( SelectedCustomBodyStyleBakedForKicad9 )
541{
542 SCH_SCREEN screen;
543 LIB_SYMBOL lib( wxT( "test" ) );
544 lib.SetBodyStyleNames( { wxT( "first" ), wxT( "second" ), wxT( "third" ) } );
545
546 for( int style = 1; style <= 3; ++style )
547 {
548 SCH_PIN* pin = new SCH_PIN( &lib );
549 pin->SetNumber( wxT( "1" ) );
550 pin->SetPosition( VECTOR2I( schIUScale.mmToIU( 5 * style ), 0 ) );
551 pin->SetBodyStyle( style );
552 pin->SetUnit( 1 );
553 lib.AddDrawItem( pin );
554 }
555
556 SCH_SYMBOL* base = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
557 SCH_SYMBOL* third = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
558 SCH_SYMBOL* another = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
559 screen.Append( base );
560 screen.Append( third );
561 screen.Append( another );
562
563 BOOST_REQUIRE_EQUAL( third->GetRawPins().size(), 1 );
564 const KIID pinUuid = third->GetRawPins().front()->m_Uuid;
566
567 DowngradeScreenInPlace( &screen, kicad9 );
568
571 BOOST_CHECK( third->GetSchSymbolLibraryName() != base->GetSchSymbolLibraryName() );
572 BOOST_CHECK( third->GetSchSymbolLibraryName() == another->GetSchSymbolLibraryName() );
573 BOOST_REQUIRE_EQUAL( third->GetRawPins().size(), 1 );
574 BOOST_CHECK( third->GetRawPins().front()->m_Uuid == pinUuid );
575 BOOST_CHECK( third->GetRawPins().front()->GetPosition() == VECTOR2I( schIUScale.mmToIU( 15 ), 0 ) );
576 BOOST_CHECK( base->GetRawPins().front()->GetPosition() == VECTOR2I( schIUScale.mmToIU( 5 ), 0 ) );
578 BOOST_CHECK_EQUAL( third->GetLibSymbolRef()->GetGraphicalPins().size(), 1 );
579 BOOST_CHECK_EQUAL( screen.GetLibSymbols().size(), 2 );
580}
581
582
583BOOST_AUTO_TEST_CASE( SelectedCustomBodyStyleKeptForKicad10 )
584{
585 SCH_SCREEN screen;
586 LIB_SYMBOL lib( wxT( "test" ) );
587 lib.SetBodyStyleNames( { wxT( "first" ), wxT( "second" ), wxT( "third" ) } );
588 SCH_SYMBOL* symbol = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
589 screen.Append( symbol );
590
591 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
593 BOOST_CHECK_EQUAL( symbol->GetBodyStyle(), 3 );
594 BOOST_CHECK_EQUAL( symbol->GetLibSymbolRef()->GetBodyStyleNames().size(), 3 );
595}
596
597
598BOOST_AUTO_TEST_CASE( PositionFileExclusionsReportedAndDroppedForKicad9 )
599{
600 SCH_SCREEN screen;
601 LIB_SYMBOL lib( wxT( "test" ) );
602 lib.SetExcludedFromPosFiles( true );
603 SCH_SYMBOL* symbol = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
604 screen.Append( symbol );
605
608 DowngradeScreenInPlace( &screen, kicad9 );
610 BOOST_CHECK( !symbol->GetExcludedFromPosFiles() );
611 BOOST_CHECK( !lib.GetExcludedFromPosFiles() );
612 BOOST_CHECK( !screen.GetLibSymbols().begin()->second->GetExcludedFromPosFiles() );
613}
614
615
616BOOST_AUTO_TEST_CASE( PositionFileExclusionsKeptForKicad10 )
617{
618 SCH_SCREEN screen;
619 LIB_SYMBOL lib( wxT( "test" ) );
620 lib.SetExcludedFromPosFiles( true );
621 SCH_SYMBOL* symbol = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
622 screen.Append( symbol );
623
624 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
625 BOOST_CHECK( !ClassifyLibSymbolForDowngrade( &lib, kicad10 ).IsLossy() );
628 BOOST_CHECK( symbol->GetExcludedFromPosFiles() );
629 BOOST_CHECK( lib.GetExcludedFromPosFiles() );
630}
631
632
633// Item locking was introduced in format 20260326.
634BOOST_AUTO_TEST_CASE( LockedItemUnlockedForKicad10 )
635{
636 SCH_SCREEN screen;
637 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::RECTANGLE );
638 shape->SetLocked( true );
639 screen.Append( shape );
640
642
644
645 BOOST_CHECK( !shape->IsLocked() );
646}
647
648
649// The schematic fail-closed gate catches post-10.0 tokens and ignores lookalikes.
650BOOST_AUTO_TEST_CASE( VerifyGateCatchesUnsupportedTokens )
651{
652 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(symbol (pin_map_override (mode passthrough)))" ), kicad10 )
653 == wxT( "pin_map_override" ) );
654 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(net_chain \"n\")" ), kicad10 ) == wxT( "net_chain" ) );
655 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(kicad_sch (symbol (pin \"1\")))" ), kicad10 ).IsEmpty() );
656
657 // Value tokens the node scan cannot see must still be caught for 9.0, but not inside strings.
658
659 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(fill (type hatch))" ), kicad9 ) == wxT( "hatch" ) );
660 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(text \"hatch pattern\")" ), kicad9 ).IsEmpty() );
661}
662
663
664// Flattening bakes the chosen variant into the base symbol attributes, so the exported
665// schematic matches that variant after the registry is dropped.
666BOOST_AUTO_TEST_CASE( VariantFlattenedIntoSchematic )
667{
668 SCHEMATIC schematic( nullptr );
669 schematic.CreateDefaultScreens();
670
671 SCH_SHEET_LIST paths = schematic.BuildUnorderedSheetList();
672 BOOST_REQUIRE( !paths.empty() );
673
674 SCH_SHEET_PATH path = paths.at( 0 );
675 SCH_SCREEN* screen = path.LastScreen();
676 BOOST_REQUIRE( screen != nullptr );
677
678 SCH_SYMBOL* symbol = new SCH_SYMBOL();
679 screen->Append( symbol );
680
681 SCH_SYMBOL_INSTANCE instance;
682 instance.m_Path = path.Path();
683 instance.m_Reference = wxT( "R1" );
684
685 SCH_SYMBOL_VARIANT variant( wxT( "V1" ) );
686 variant.InitializeAttributes( *symbol );
687 variant.m_DNP = true;
688 instance.m_Variants[wxT( "V1" )] = variant;
689
690 symbol->AddHierarchicalReference( instance );
691
692 BOOST_CHECK( !symbol->GetDNP() );
693
694 wxString conflict;
695 BOOST_REQUIRE( FlattenSchematicVariant( schematic, wxT( "V1" ), &conflict ) );
696
697 BOOST_CHECK( symbol->GetDNP() );
698}
699
700
701// Coverage guard: a schematic format bump must trigger a denylist review. See the board equivalent.
707
708
709BOOST_FIXTURE_TEST_CASE( EmbeddedFilesSurviveMissingRootInstanceForBothTargets, KI_TEST::SCHEMATIC_TEST_FIXTURE )
710{
711 const std::string fixture = KI_TEST::GetEeschemaTestDataDir() + "/../downgrade/regressions/embedded_files/";
712 auto readFile = []( const std::string& aPath )
713 {
714 std::ifstream file( aPath, std::ios::binary );
715 BOOST_REQUIRE( file.is_open() );
716 return std::string( std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>() );
717 };
718 std::map<std::string, std::string> originals;
719
720 for( const std::string& name : { "embedded_files.kicad_pro", "embedded_files.kicad_sch", "child.kicad_sch" } )
721 {
722 originals.emplace( name, readFile( fixture + name ) );
723 }
724
725 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
726 {
727 BOOST_TEST_CONTEXT( target.m_id )
728 {
729 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_embedded_schematic" );
730 LoadSchematic( wxFileName( fixture + "embedded_files.kicad_sch" ) );
731 BOOST_REQUIRE( m_schematic );
732 SCH_SHEET* root = m_schematic->GetTopLevelSheet( 0 );
733 BOOST_REQUIRE( root );
735 EMBEDDED_FILES expected( *m_schematic->GetEmbeddedFiles(), true );
736 BOOST_REQUIRE( !expected.IsEmpty() );
737
738 // Global payload ownership does not depend on optional sheet-instance bookkeeping.
739 root->RemoveInstance( KIID_PATH() );
740 BOOST_REQUIRE( !root->HasRootInstance() );
741 auto children = root->GetScreen()->Items().OfType( SCH_SHEET_T );
742 BOOST_REQUIRE_EQUAL( std::distance( children.begin(), children.end() ), 1 );
743 SCH_SHEET* child = static_cast<SCH_SHEET*>( *children.begin() );
744 const std::string output = ( tmp.GetPath() / "embedded_files.kicad_sch" ).string();
745 const std::string childOutput = ( tmp.GetPath() / "child.kicad_sch" ).string();
746 SaveSchematicForTarget( root, m_schematic.get(), output, target );
747 SaveSchematicForTarget( child, m_schematic.get(), childOutput, target );
748 BOOST_CHECK( readFile( childOutput ).find( "(embedded_files" ) == std::string::npos );
749 BOOST_REQUIRE( wxCopyFile( fixture + "embedded_files.kicad_pro",
750 ( tmp.GetPath() / "embedded_files.kicad_pro" ).string() ) );
751
752 LoadSchematic( wxFileName( output ) );
753 const EMBEDDED_FILES& actual = *m_schematic->GetEmbeddedFiles();
754 BOOST_REQUIRE_EQUAL( actual.EmbeddedFileMap().size(), expected.EmbeddedFileMap().size() );
755
756 for( const auto& [name, expectedFile] : expected.EmbeddedFileMap() )
757 {
758 EMBEDDED_FILES::EMBEDDED_FILE* actualFile = actual.GetEmbeddedFile( name );
759 BOOST_REQUIRE( actualFile );
760 // Parsing verifies the checksum but does not populate the cached validity flag.
761 BOOST_REQUIRE( expectedFile->Validate() );
762 BOOST_REQUIRE( actualFile->Validate() );
763 BOOST_CHECK( actualFile->is_valid );
764 BOOST_CHECK( actualFile->type == expectedFile->type );
765 BOOST_CHECK_EQUAL( actualFile->data_hash, expectedFile->data_hash );
766 BOOST_CHECK_EQUAL( actualFile->compressedEncodedData, expectedFile->compressedEncodedData );
767 BOOST_CHECK( actualFile->decompressedData == expectedFile->decompressedData );
768 }
769 }
770 }
771
772 for( const auto& [name, original] : originals )
773 BOOST_CHECK( readFile( fixture + name ) == original );
774}
775
776
777BOOST_AUTO_TEST_CASE( DropApproximationsOmitsScreenAndCachedGraphics )
778{
779 SCH_SCREEN screen;
780 LIB_SYMBOL* lib = new LIB_SYMBOL( wxT( "test" ) );
781 const size_t fieldCount = lib->GetDrawItems().size();
783 {
784 SCH_SHAPE* placed = new SCH_SHAPE( type );
785 SCH_SHAPE* cached = new SCH_SHAPE( type );
786 if( type == SHAPE_T::RECTANGLE )
787 {
788 placed->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
789 cached->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
790 placed->SetCornerRadius( schIUScale.mmToIU( 1 ) );
791 cached->SetCornerRadius( schIUScale.mmToIU( 1 ) );
792 }
793 screen.Append( placed );
794 lib->AddDrawItem( cached );
795 }
796 SCH_SHAPE* supported = new SCH_SHAPE( SHAPE_T::CIRCLE );
797 screen.Append( supported );
798 SCH_SHAPE* cachedSupported = new SCH_SHAPE( SHAPE_T::CIRCLE );
799 lib->AddDrawItem( cachedSupported );
800 screen.AddLibSymbol( lib );
801 SCH_GROUP* group = new SCH_GROUP( &screen );
802 for( SCH_ITEM* item : screen.Items().OfType( SCH_SHAPE_T ) )
803 group->AddItem( item );
804 screen.Append( group );
805 const auto& target = kicad9;
806 const auto normal = ClassifyScreenForDowngrade( &screen, target );
807 const auto omit = ClassifyScreenForDowngrade( &screen, target, true );
809 BOOST_CHECK_EQUAL( omit.Count( DOWNGRADE_BUCKET::LOWER ), 0 );
810 BOOST_CHECK_EQUAL( omit.Count( DOWNGRADE_BUCKET::DROP ), 7 );
811 BOOST_REQUIRE_EQUAL( normal.Entries().size(), omit.Entries().size() );
812 for( size_t i = 0; i < normal.Entries().size(); ++i )
813 {
814 BOOST_CHECK( normal.Entries()[i].m_feature == omit.Entries()[i].m_feature );
815 BOOST_CHECK_EQUAL( normal.Entries()[i].m_count, omit.Entries()[i].m_count );
816 BOOST_CHECK( normal.Entries()[i].m_detail != omit.Entries()[i].m_detail );
817 BOOST_CHECK( !omit.Entries()[i].m_detail.IsEmpty() );
818 }
819 DowngradeScreenInPlace( &screen, target, true );
820 auto shapes = screen.Items().OfType( SCH_SHAPE_T );
821 BOOST_REQUIRE_EQUAL( std::distance( shapes.begin(), shapes.end() ), 1 );
822 BOOST_CHECK( *shapes.begin() == supported );
823 BOOST_CHECK( supported->GetParentGroup() == nullptr );
824 BOOST_CHECK_EQUAL( lib->GetDrawItems().size(), fieldCount + 1 );
825 for( const SCH_ITEM& item : lib->GetDrawItems() )
826 {
827 if( item.Type() == SCH_SHAPE_T )
828 BOOST_CHECK( &item == cachedSupported );
829 }
830 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, target, true ).IsLossy() );
831}
832
833
834BOOST_AUTO_TEST_CASE( DropApproximationsOmitsLibraryGraphicsAndHatchFill )
835{
836 LIB_SYMBOL lib( wxT( "test" ) );
837 const size_t fieldCount = lib.GetDrawItems().size();
840 SCH_SHAPE* rounded = new SCH_SHAPE( SHAPE_T::RECTANGLE );
841 rounded->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
842 rounded->SetCornerRadius( schIUScale.mmToIU( 1 ) );
843 lib.AddDrawItem( rounded );
844 SCH_SHAPE* hatch = new SCH_SHAPE( SHAPE_T::RECTANGLE );
845 hatch->SetFillMode( FILL_T::HATCH );
846 lib.AddDrawItem( hatch );
847 const auto& target = kicad9;
850 DowngradeLibSymbolInPlace( &lib, target, true );
851 BOOST_REQUIRE_EQUAL( lib.GetDrawItems().size(), fieldCount + 1 );
852 for( const SCH_ITEM& item : lib.GetDrawItems() )
853 {
854 if( item.Type() == SCH_SHAPE_T )
855 BOOST_CHECK( &item == hatch );
856 }
857 BOOST_CHECK( hatch->GetFillMode() == FILL_T::NO_FILL );
858}
859
860
861BOOST_AUTO_TEST_CASE( DropApproximationsKeepsOutlineTextAndLockFreeItems )
862{
863 SCH_SCREEN screen;
864 SCH_SHAPE* hatch = new SCH_SHAPE( SHAPE_T::RECTANGLE );
865 hatch->SetFillMode( FILL_T::HATCH );
866 screen.Append( hatch );
867 hatch->SetLocked( true );
868 SCH_RULE_AREA* area = new SCH_RULE_AREA();
869 area->SetFillMode( FILL_T::HATCH );
870 screen.Append( area );
871 const auto& target = kicad9;
872 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( &screen, target, true ).Count( DOWNGRADE_BUCKET::DROP ), 3 );
873 DowngradeScreenInPlace( &screen, target, true );
874 BOOST_CHECK( !hatch->IsLocked() );
875 BOOST_CHECK( hatch->GetFillMode() == FILL_T::NO_FILL );
876 BOOST_CHECK( area->GetFillMode() == FILL_T::NO_FILL );
877 BOOST_CHECK( screen.CheckIfOnDrawList( hatch ) );
878 BOOST_CHECK( screen.CheckIfOnDrawList( area ) );
879}
880
881
882BOOST_AUTO_TEST_CASE( DropApproximationsOmitsSelectedCustomStyleInstancesOnly )
883{
884 for( const auto& target : { kicad9, kicad10 } )
885 {
886 SCH_SCREEN screen;
887 LIB_SYMBOL lib( wxT( "test" ) );
888 lib.SetBodyStyleNames( { wxT( "first" ), wxT( "second" ), wxT( "third" ) } );
889 for( int style = 1; style <= 3; ++style )
890 {
891 SCH_PIN* pin = new SCH_PIN( &lib );
892 pin->SetNumber( wxT( "1" ) );
893 pin->SetBodyStyle( style );
894 pin->SetUnit( 1 );
895 lib.AddDrawItem( pin );
896 }
897 SCH_SYMBOL* base = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 1 );
898 SCH_SYMBOL* second = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 2 );
899 SCH_SYMBOL* third = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, 3 );
900 screen.Append( base );
901 screen.Append( second );
902 screen.Append( third );
903 SCH_GROUP* group = new SCH_GROUP( &screen );
904 group->AddItem( third );
905 screen.Append( group );
906 const KIID basePin = base->GetRawPins().front()->m_Uuid;
907 const KIID secondPin = second->GetRawPins().front()->m_Uuid;
908 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( &screen, target, true ).Count( DOWNGRADE_BUCKET::LOWER ), 0 );
909 DowngradeScreenInPlace( &screen, target, true );
910 auto symbols = screen.Items().OfType( SCH_SYMBOL_T );
911 BOOST_CHECK_EQUAL( std::distance( symbols.begin(), symbols.end() ), target.m_id == wxT( "9.0" ) ? 2 : 3 );
912 BOOST_CHECK( screen.CheckIfOnDrawList( base ) );
913 BOOST_CHECK( screen.CheckIfOnDrawList( second ) );
914 BOOST_CHECK( base->GetRawPins().front()->m_Uuid == basePin );
915 BOOST_CHECK( second->GetRawPins().front()->m_Uuid == secondPin );
916 BOOST_CHECK_EQUAL( screen.GetLibSymbols().size(), 1 );
917 if( target.m_id == wxT( "10.0" ) )
918 {
919 BOOST_CHECK( screen.CheckIfOnDrawList( third ) );
920 BOOST_CHECK_EQUAL( third->GetBodyStyle(), 3 );
921 }
922 }
923}
924
925
926BOOST_AUTO_TEST_CASE( DropApproximationsCannotBypassSchematicBlockRules )
927{
928 LIB_SYMBOL lib( wxT( "test" ) );
929 lib.SetLocalPower();
930 const auto& target = kicad9;
931 BOOST_CHECK( ClassifyLibSymbolForDowngrade( &lib, target, true ).IsBlocked() );
932 SCH_SCREEN screen;
933 screen.AddLibSymbol( new LIB_SYMBOL( lib ) );
934 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target, true ).IsBlocked() );
935}
936
937
938BOOST_AUTO_TEST_CASE( PostMasterLineEndingsAndPropertiesCoverStandaloneAndCachedSymbols )
939{
940 for( const auto& target : { kicad9, kicad10 } )
941 {
942 for( bool drop : { false, true } )
943 {
944 SCH_SCREEN screen;
945 LIB_SYMBOL* lib = new LIB_SYMBOL( wxT( "post_master" ) );
946 lib->SetCustomProperty( wxT( "Vendor" ), wxT( "Acme" ) );
947 lib->GetField( FIELD_T::VALUE )->SetCustomProperty( wxT( "Owner" ), wxT( "Library" ) );
948 SCH_PIN* pin = new SCH_PIN( lib );
949 pin->SetNumber( wxT( "1" ) );
950 pin->SetCustomProperty( wxT( "Role" ), wxT( "Signal" ) );
951 lib->AddDrawItem( pin );
952 SCH_SHAPE* line = new SCH_SHAPE( SHAPE_T::SEGMENT );
953 line->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
954 line->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
956 lib->AddDrawItem( line );
957 screen.AddLibSymbol( lib );
958 LIB_SYMBOL standalone( *lib );
959
960 const auto report = ClassifyLibSymbolForDowngrade( &standalone, target, drop );
961 BOOST_CHECK( !report.IsBlocked() );
962 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), drop ? 0 : 1 );
963 BOOST_CHECK_GE( report.Count( DOWNGRADE_BUCKET::DROP ), drop ? 4 : 3 );
964 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( &screen, target, drop ).Entries().size(),
965 report.Entries().size() );
966 DowngradeLibSymbolInPlace( &standalone, target, drop );
967 DowngradeScreenInPlace( &screen, target, drop );
968 BOOST_CHECK( !ClassifyLibSymbolForDowngrade( &standalone, target, drop ).IsLossy() );
969 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, target, drop ).IsLossy() );
970 BOOST_CHECK( !standalone.HasCustomProperties() );
971 BOOST_CHECK( !standalone.GetField( FIELD_T::VALUE )->HasCustomProperties() );
972 BOOST_CHECK( !standalone.GetGraphicalPins().front()->HasCustomProperties() );
973 if( drop )
974 {
975 BOOST_CHECK( line->GetShape() == SHAPE_T::SEGMENT );
976 BOOST_CHECK( line->GetEndEndingStyle() == LINE_ENDING_STYLE::NONE );
977 BOOST_CHECK( line->GetEnd() == VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
978 }
979 }
980 }
981}
982
983
984BOOST_AUTO_TEST_CASE( GraphicLineEndingsKeepGroupsAndDropKeepsOriginalBody )
985{
986 for( bool drop : { false, true } )
987 {
988 SCH_SCREEN screen;
989 SCH_LINE* line = new SCH_LINE( VECTOR2I(), LAYER_NOTES );
990 line->SetEndPoint( VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
991 line->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
993 screen.Append( line );
994 SCH_GROUP* group = new SCH_GROUP( &screen );
995 group->AddItem( line );
996 screen.Append( group );
999 1 );
1000 DowngradeScreenInPlace( &screen, kicad10, drop );
1001 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10, drop ).IsLossy() );
1002 BOOST_CHECK( !group->GetItems().empty() );
1003 for( EDA_ITEM* member : group->GetItems() )
1004 BOOST_CHECK( member->GetParentGroup() == group );
1005 if( drop )
1006 {
1007 BOOST_CHECK( screen.CheckIfOnDrawList( line ) );
1008 BOOST_CHECK( line->GetStartEndingStyle() == LINE_ENDING_STYLE::NONE );
1009 BOOST_CHECK( line->GetEndPoint() == VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
1010 }
1011 }
1012}
1013
1014
1015// An ending that strokes to no ink leaves nothing to put back, so the graphic stays and only the
1016// ending goes. The board path already guards this.
1017BOOST_AUTO_TEST_CASE( DegenerateLineEndingKeepsItsGraphic )
1018{
1019 for( const DOWNGRADE_TARGET* target : { &kicad9, &kicad10 } )
1020 {
1021 BOOST_TEST_CONTEXT( target->m_id.ToStdString() )
1022 {
1023 SCH_SCREEN screen;
1025 shape->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
1027 screen.Append( shape );
1028
1029 DowngradeScreenInPlace( &screen, *target );
1030
1031 auto shapes = screen.Items().OfType( SCH_SHAPE_T );
1032 BOOST_REQUIRE_EQUAL( std::distance( shapes.begin(), shapes.end() ), 1 );
1033 BOOST_CHECK( shape->GetEndEndingStyle() == LINE_ENDING_STYLE::NONE );
1034
1035 LIB_SYMBOL symbol( wxT( "degenerate" ) );
1037 drawing->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
1039 symbol.AddDrawItem( drawing );
1040
1041 DowngradeLibSymbolInPlace( &symbol, *target );
1042
1043 int kept = 0;
1044
1045 for( const SCH_ITEM& drawing : symbol.GetDrawItems() )
1046 {
1047 if( drawing.Type() != SCH_SHAPE_T )
1048 continue;
1049
1050 kept++;
1051 BOOST_CHECK( static_cast<const SCH_SHAPE&>( drawing ).GetEndEndingStyle()
1053 }
1054
1055 BOOST_CHECK_EQUAL( kept, 1 );
1056 }
1057 }
1058}
1059
1060
1061BOOST_AUTO_TEST_CASE( StyledLineEndingsBlockApproximationAndAllowExplicitDrop )
1062{
1063 SCH_SCREEN screen;
1064 SCH_SHAPE* shape = new SCH_SHAPE( SHAPE_T::SEGMENT );
1065 shape->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
1066 shape->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::DASH ) );
1068 screen.Append( shape );
1069 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1070 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10, true ).IsBlocked() );
1071 DowngradeScreenInPlace( &screen, kicad10, true );
1072 BOOST_CHECK( shape->GetStroke().GetLineStyle() == LINE_STYLE::DASH );
1073 BOOST_CHECK( shape->GetEndEndingStyle() == LINE_ENDING_STYLE::NONE );
1074}
1075
1076
1077BOOST_AUTO_TEST_CASE( UnsupportedPropertyTextBlocksWithoutBlockingEscapedLiterals )
1078{
1079 SCH_SCREEN screen;
1080 SCH_TEXT* text = new SCH_TEXT();
1081 text->SetText( wxT( "${U1:PROPERTY.Vendor}" ) );
1082 screen.Append( text );
1083 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad9 ).IsBlocked() );
1084 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad10, true ).IsBlocked() );
1085 text->SetText( wxT( "\\${PROPERTY.Vendor}" ) );
1086 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1087 text->SetText( wxT( "PROPERTY.Vendor" ) );
1088 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1089}
1090
1091
1092BOOST_AUTO_TEST_CASE( PropertyTextAndUsedProjectDependenciesBakeBeforeMetadataRemoval )
1093{
1094 for( const auto& target : { kicad9, kicad10 } )
1095 {
1096 for( bool drop : { false, true } )
1097 {
1098 SETTINGS_MANAGER settings;
1099 std::unique_ptr<SCHEMATIC> schematic;
1100 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1101 auto symbols = schematic->RootScreen()->Items().OfType( SCH_SYMBOL_T );
1102 BOOST_REQUIRE( symbols.begin() != symbols.end() );
1103 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
1104 symbol->SetCustomProperty( wxT( "Vendor" ), wxT( "Acme" ) );
1105 symbol->GetField( FIELD_T::VALUE )->SetText( wxT( "${Alias} / ${REFERENCE}" ) );
1106 schematic->Project().GetTextVars()[wxT( "Alias" )] = wxT( "${PROPERTY.Vendor}" );
1107 schematic->Project().GetTextVars()[wxT( "Unused" )] = wxT( "${PROPERTY.Missing}" );
1108 auto report = ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop );
1109 BOOST_CHECK( !report.IsBlocked() );
1110 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), 0 );
1111 BOOST_CHECK_GE( report.Count( DOWNGRADE_BUCKET::DROP ), 2 );
1112 DowngradeScreenInPlace( schematic->RootScreen(), target, drop );
1113 BOOST_CHECK( symbol->GetField( FIELD_T::VALUE )->GetText() == wxT( "Acme / ${REFERENCE}" ) );
1114 BOOST_CHECK( !symbol->HasCustomProperties() );
1115 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop ).IsLossy() );
1116 }
1117 }
1118}
1119
1120
1121BOOST_AUTO_TEST_CASE( ExpressionsBakeForNative9AndRemainDynamicForNative10 )
1122{
1123 for( const auto& target : { kicad9, kicad10 } )
1124 {
1125 for( bool drop : { false, true } )
1126 {
1127 SETTINGS_MANAGER settings;
1128 std::unique_ptr<SCHEMATIC> schematic;
1129 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1130 SCH_TEXT* text = new SCH_TEXT();
1131 text->SetText( wxT( "@{1 + 2} / ${PROJECTNAME}" ) );
1132 schematic->RootScreen()->Append( text );
1133 auto report = ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop );
1134 BOOST_CHECK( !report.IsBlocked() );
1135 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), 0 );
1136 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::DROP ), target.m_id == wxT( "9.0" ) ? 1 : 0 );
1137 DowngradeScreenInPlace( schematic->RootScreen(), target, drop );
1138 BOOST_CHECK( text->GetText()
1139 == ( target.m_id == wxT( "9.0" ) ? wxT( "3 / ${PROJECTNAME}" )
1140 : wxT( "@{1 + 2} / ${PROJECTNAME}" ) ) );
1141 text->SetText( wxT( "\\@{1 + 2}" ) );
1142 BOOST_CHECK_EQUAL( ClassifyScreenForDowngrade( schematic->RootScreen(), target ).IsBlocked(),
1143 target.m_id == wxT( "9.0" ) );
1144 }
1145 }
1146}
1147
1148
1149BOOST_AUTO_TEST_CASE( PropertyTextBakingPreservesEscapedLiteralsAndLiteralReplacementSyntax )
1150{
1151 SETTINGS_MANAGER settings;
1152 std::unique_ptr<SCHEMATIC> schematic;
1153 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1154 auto symbols = schematic->RootScreen()->Items().OfType( SCH_SYMBOL_T );
1155 BOOST_REQUIRE( symbols.begin() != symbols.end() );
1156 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
1157 symbol->SetCustomProperty( wxT( "Vendor" ), wxT( "\\${REFERENCE} \\@{1 + 2}" ) );
1158 SCH_FIELD* field = symbol->GetField( FIELD_T::VALUE );
1159 field->SetText( wxT( "\\${PROPERTY.literal} ${PROPERTY.Vendor}" ) );
1160 const SCH_SHEET_PATH& path = schematic->CurrentSheet();
1161 const wxString shownBefore = field->GetShownText( &path, FOR_CANVAS, wxEmptyString );
1162 wxString resolvedBefore = field->ResolveText( field->GetText(), &path, 0, wxEmptyString );
1163 FinalizeTextVarExpansion( resolvedBefore, INTERNAL );
1164 BOOST_CHECK_EQUAL( resolvedBefore.ToStdString(), std::string( "\\${PROPERTY.literal} \\${REFERENCE} \\3" ) );
1165 BOOST_REQUIRE( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsBlocked() );
1166 DowngradeScreenInPlace( schematic->RootScreen(), kicad10 );
1167 BOOST_CHECK_EQUAL( field->GetText().ToStdString(), resolvedBefore.ToStdString() );
1168 BOOST_CHECK_EQUAL( field->GetShownText( &path, FOR_CANVAS, wxEmptyString ).ToStdString(),
1169 shownBefore.ToStdString() );
1170 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsLossy() );
1171 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), kicad9 ).IsBlocked() );
1172}
1173
1174
1175BOOST_AUTO_TEST_CASE( ContextualTitleShadowsUnusedUnsupportedProjectDefinition )
1176{
1177 SETTINGS_MANAGER settings;
1178 std::unique_ptr<SCHEMATIC> schematic;
1179 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1180 schematic->RootScreen()->GetTitleBlock().SetTitle( wxT( "Ordinary title" ) );
1181 schematic->Project().GetTextVars()[wxT( "TITLE" )] = wxT( "${PROPERTY.Unknown}" );
1182 SCH_TEXT* text = new SCH_TEXT();
1183 text->SetText( wxT( "${TITLE}" ) );
1184 schematic->RootScreen()->Append( text );
1185 const SCH_SHEET_PATH& path = schematic->CurrentSheet();
1186 BOOST_REQUIRE_EQUAL( text->GetShownText( &path, FOR_CANVAS ).ToStdString(), std::string( "Ordinary title" ) );
1187
1188 for( const auto& target : { kicad9, kicad10 } )
1189 {
1190 for( bool drop : { false, true } )
1191 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), target, drop ).IsLossy() );
1192 }
1193
1194 BOOST_CHECK_EQUAL( text->GetText().ToStdString(), std::string( "${TITLE}" ) );
1195 BOOST_CHECK_EQUAL( text->GetShownText( &path, FOR_CANVAS ).ToStdString(), std::string( "Ordinary title" ) );
1196}
1197
1198
1199BOOST_AUTO_TEST_CASE( PageBorderDependenciesBlockUnsupportedContextWithoutBlockingOrdinaryText )
1200{
1201 SETTINGS_MANAGER settings;
1202 std::unique_ptr<SCHEMATIC> schematic;
1203 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1204 schematic->Project().GetTextVars()[wxT( "BorderAlias" )] = wxT( "${PROPERTY.Unknown}" );
1205 schematic->RootScreen()->GetTitleBlock().SetTitle( wxT( "${BorderAlias}" ) );
1206 SCH_TEXT* text = new SCH_TEXT();
1207 text->SetText( wxT( "${TITLE}" ) );
1208 schematic->RootScreen()->Append( text );
1209 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsBlocked() );
1210 schematic->RootScreen()->GetTitleBlock().SetTitle( wxT( "Ordinary title" ) );
1211 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsLossy() );
1212 schematic->RootScreen()->GetTitleBlock().SetComment( 8, wxT( "@{1 + 2}" ) );
1213 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), kicad9 ).IsBlocked() );
1214 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), kicad10 ).IsLossy() );
1215}
1216
1217
1218BOOST_AUTO_TEST_CASE( PaddedBusVectorsBlockBeforeNetNamesChange )
1219{
1220 std::vector<wxString> members;
1221 BOOST_REQUIRE( NET_SETTINGS::ParseBusVector( wxT( "D[00..02]" ), nullptr, &members ) );
1222 BOOST_REQUIRE_EQUAL( members.size(), 3 );
1223 BOOST_CHECK( members.front() == wxT( "D00" ) );
1224 SCH_SCREEN screen;
1225 SCH_LABEL* label = new SCH_LABEL();
1226 label->SetText( wxT( "D[00..02]" ) );
1227 screen.Append( label );
1228 for( const auto& target : { kicad9, kicad10 } )
1229 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target ).IsBlocked() );
1230 label->SetText( wxT( "D[0..2]" ) );
1231 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1232 SCHEMATIC schematic( nullptr );
1233 schematic.CreateDefaultScreens();
1234 auto alias = std::make_shared<BUS_ALIAS>();
1235 alias->SetName( wxT( "Data" ) );
1236 alias->SetMembers( { wxT( "D[00..02]" ) } );
1237 schematic.AddBusAlias( alias );
1238 BOOST_CHECK( ClassifySchematicStructureForDowngrade( schematic, kicad10 ).IsBlocked() );
1239}
1240
1241
1242BOOST_AUTO_TEST_CASE( RetainedAlternateVariantDefinitionsReportTheirRemoval )
1243{
1244 SCH_SCREEN screen;
1245 SCH_SYMBOL* symbol = new SCH_SYMBOL();
1246 SCH_SYMBOL_INSTANCE instance;
1247 instance.m_Path.push_back( KIID() );
1248 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
1249 variant.m_SymbolOverride = LIB_ID( wxT( "Device" ), wxT( "Alternate" ) );
1250 instance.m_Variants[variant.m_Name] = variant;
1251 symbol->AddHierarchicalReference( instance );
1252 screen.Append( symbol );
1254 DowngradeScreenInPlace( &screen, kicad10 );
1255 BOOST_CHECK( !symbol->GetInstances().front().m_Variants.at( variant.m_Name ).m_SymbolOverride );
1256 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
1257}
1258
1259
1260BOOST_AUTO_TEST_CASE( FrozenBusParsersCannotReinterpretMalformedOrOverflowingVectors )
1261{
1262 SCH_SCREEN screen;
1263 SCH_LABEL* label = new SCH_LABEL();
1264 screen.Append( label );
1265 for( const auto& target : { kicad9, kicad10 } )
1266 {
1267 for( const wxString& malformed : { wxT( "D[..2]" ), wxT( "D[1..]" ), wxT( "D[1..2" ) } )
1268 {
1269 BOOST_REQUIRE( !NET_SETTINGS::ParseBusVector( malformed, nullptr, nullptr ) );
1270 label->SetText( malformed );
1271 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target ).IsBlocked() );
1272 }
1273 label->SetText( wxString::Format( wxT( "D[%ld..%ld]" ), LONG_MAX - 1, LONG_MAX ) );
1274 BOOST_REQUIRE( NET_SETTINGS::ParseBusVector( label->GetText(), nullptr, nullptr ) );
1275 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, target ).IsBlocked() );
1276 for( const wxString& ordinary :
1277 { wxT( "D[1..2]" ), wxT( "D[foo]" ), wxT( "D[..0]" ), wxT( "text [literal]" ) } )
1278 {
1279 label->SetText( ordinary );
1280 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, target ).IsLossy() );
1281 }
1282 }
1283 label->SetText( wxT( "D[1..2]+" ) );
1284 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad9 ).IsBlocked() );
1285 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad10 ).IsLossy() );
1286 label->SetText( wxT( "I^{2}C[1..2]" ) );
1287 BOOST_CHECK( !ClassifyScreenForDowngrade( &screen, kicad9 ).IsLossy() );
1288 BOOST_CHECK( ClassifyScreenForDowngrade( &screen, kicad10 ).IsBlocked() );
1289}
1290
1291
1292BOOST_AUTO_TEST_CASE( UsedProjectBusVariablesAreCheckedWithNativeNetnameResolution )
1293{
1294 for( const auto& target : { kicad9, kicad10 } )
1295 {
1296 SETTINGS_MANAGER settings;
1297 std::unique_ptr<SCHEMATIC> schematic;
1298 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1299 SCH_LABEL* label = new SCH_LABEL();
1300 label->SetText( wxT( "${BUS}" ) );
1301 schematic->RootScreen()->Append( label );
1302 for( const wxString& bus : { wxT( "D[01..3]" ), wxT( "D[..2]" ), wxT( "D[1..2" ) } )
1303 {
1304 schematic->Project().GetTextVars()[wxT( "BUS" )] = bus;
1305 BOOST_CHECK( ClassifyScreenForDowngrade( schematic->RootScreen(), target ).IsBlocked() );
1306 }
1307 for( const wxString& ordinary : { wxT( "D[1..3]" ), wxT( "D[foo]" ) } )
1308 {
1309 schematic->Project().GetTextVars()[wxT( "BUS" )] = ordinary;
1310 BOOST_CHECK( !ClassifyScreenForDowngrade( schematic->RootScreen(), target ).IsLossy() );
1311 }
1312 }
1313}
1314
1315
1316BOOST_AUTO_TEST_CASE( UnresolvedSelectedAlternateRefusesFlatteningBeforeMutation )
1317{
1318 SCHEMATIC schematic( nullptr );
1319 schematic.CreateDefaultScreens();
1320 SCH_SHEET_PATH path = schematic.BuildUnorderedSheetList().front();
1321 SCH_SYMBOL* symbol = new SCH_SYMBOL();
1322 path.LastScreen()->Append( symbol );
1323 SCH_SYMBOL_INSTANCE instance;
1324 instance.m_Path = path.Path();
1325 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
1326 variant.m_DNP = true;
1327 variant.m_SymbolOverride = LIB_ID( wxT( "Missing" ), wxT( "Alternate" ) );
1328 instance.m_Variants[variant.m_Name] = variant;
1329 symbol->AddHierarchicalReference( instance );
1330 wxString reason;
1331 BOOST_CHECK( !FlattenSchematicVariant( schematic, variant.m_Name, &reason ) );
1332 BOOST_CHECK( reason.Contains( wxT( "Missing:Alternate" ) ) );
1333 BOOST_CHECK( !symbol->GetDNP() );
1334}
1335
1336
1337BOOST_AUTO_TEST_CASE( SelectedAlternateBakesNativeLibraryFieldsPinMapAndPinIdentities )
1338{
1339 SETTINGS_MANAGER settings;
1340 std::unique_ptr<SCHEMATIC> schematic;
1341 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1342 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_variant_library" );
1343 SYMBOL_LIBRARY_ADAPTER* adapter = PROJECT_SCH::SymbolLibAdapter( &schematic->Project() );
1344 BOOST_REQUIRE( adapter );
1345 const auto previousTable = adapter->ProjectTable();
1346 struct TABLE_RESTORE
1347 {
1348 LIBRARY_MANAGER& manager;
1349 wxString directory;
1350 ~TABLE_RESTORE() { manager.LoadProjectTables( directory, { LIBRARY_TABLE_TYPE::SYMBOL } ); }
1351 } restore{ Pgm().GetLibraryManager(),
1352 previousTable && *previousTable ? wxFileName( ( *previousTable )->Path() ).GetPath() : wxString() };
1353 const wxString directory = tmp.GetPath().string();
1354 wxFile tableFile( directory + wxT( "/sym-lib-table" ), wxFile::write );
1355 BOOST_REQUIRE( tableFile.IsOpened() );
1356 BOOST_REQUIRE( tableFile.Write( wxT( "(sym_lib_table (version 7))\n" ) ) );
1357 tableFile.Close();
1358 restore.manager.LoadProjectTables( directory, { LIBRARY_TABLE_TYPE::SYMBOL } );
1359 LIBRARY_TABLE* table = adapter->ProjectTable().value_or( nullptr );
1361 LIBRARY_TABLE_ROW& row = table->InsertRow();
1362 row.SetNickname( wxT( "VariantLibrary" ) );
1363 row.SetURI( wxString( KI_TEST::GetEeschemaTestDataDir() ) + wxT( "libs/4xxx.kicad_sym" ) );
1364 row.SetType( wxT( "KiCad" ) );
1366 adapter->LoadOne( row.Nickname() );
1367 LIB_SYMBOL* alternate = adapter->LoadSymbol( row.Nickname(), wxT( "4001" ) );
1368 BOOST_REQUIRE( alternate );
1369 LIB_SYMBOL base( *alternate );
1370 base.SetName( wxT( "base" ) );
1371 base.SetLibId( LIB_ID( wxT( "Embedded" ), base.GetName() ) );
1372 base.GetField( FIELD_T::VALUE )->SetText( wxT( "Base value" ) );
1373 SCH_SYMBOL* symbol = new SCH_SYMBOL( base, base.GetLibId(), nullptr, 1, BODY_STYLE::BASE );
1374 schematic->RootScreen()->Append( symbol );
1375 const SCH_SHEET_PATH path = schematic->BuildUnorderedSheetList().front();
1376 symbol->AddHierarchicalReference( path.Path(), wxT( "U99" ), 1 );
1377 SCH_SYMBOL_VARIANT variant( wxT( "Alt" ) );
1378 variant.InitializeAttributes( *symbol );
1379 variant.m_SymbolOverride = LIB_ID( row.Nickname(), wxT( "4001" ) );
1380 variant.m_Fields[wxT( "Footprint" )] = wxEmptyString;
1382 symbol->AddVariant( path, variant );
1383 std::vector<KIID> pins;
1384 for( const auto& pin : symbol->GetRawPins() )
1385 pins.push_back( pin->m_Uuid );
1386 const wxString value = symbol->GetFieldText( wxT( "Value" ), &path, variant.m_Name );
1387 BOOST_REQUIRE( value != wxT( "Base value" ) );
1388 wxString reason;
1389 BOOST_REQUIRE_MESSAGE( FlattenSchematicVariant( *schematic, variant.m_Name, &reason ), reason );
1390 BOOST_CHECK( symbol->GetLibId() == *variant.m_SymbolOverride );
1391 BOOST_CHECK( symbol->GetField( FIELD_T::VALUE )->GetText() == value );
1392 BOOST_CHECK( symbol->GetField( FIELD_T::FOOTPRINT )->GetText().IsEmpty() );
1393 BOOST_CHECK( symbol->GetPinMapOverride() == variant.m_PinMapOverride );
1394 BOOST_REQUIRE_EQUAL( symbol->GetRawPins().size(), pins.size() );
1395 for( size_t i = 0; i < pins.size(); ++i )
1396 BOOST_CHECK( symbol->GetRawPins()[i]->m_Uuid == pins[i] );
1397 auto cached = schematic->RootScreen()->GetLibSymbols().find( symbol->GetSchSymbolLibraryName() );
1398 BOOST_REQUIRE( cached != schematic->RootScreen()->GetLibSymbols().end() );
1399 BOOST_CHECK( cached->second->GetField( FIELD_T::VALUE )->GetText()
1400 == alternate->GetField( FIELD_T::VALUE )->GetText() );
1401}
1402
1403
1404BOOST_AUTO_TEST_CASE( PostMasterUnsupportedTokenBackstop )
1405{
1406 for( const auto& target : { kicad9, kicad10 } )
1407 {
1408 for( const wxString& token :
1409 { wxT( "start_shape" ), wxT( "end_shape" ), wxT( "symbol_override" ), wxT( "custom_property" ) } )
1410 {
1411 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(" ) + token + wxT( " \"x\")" ), target ) == token );
1412 BOOST_CHECK( FindUnsupportedSchToken( wxT( "(text \"(" ) + token + wxT( " x)\")" ), target ).IsEmpty() );
1413 }
1414 }
1415}
1416
1417
1418static nlohmann::json currentErcExclusionDocument()
1419{
1420 return {
1421 { "erc",
1422 { { "meta", { { "version", 1 } } },
1423 { "erc_exclusions",
1424 nlohmann::json::array(
1425 { { { "marker",
1426 { { "error_type", "ERCET_PIN_NOT_CONNECTED" },
1427 { "position", { { "x_nm", "12300" }, { "y_nm", "-45600" } } },
1428 { "items", nlohmann::json::array(
1429 { { { "value", "11111111-1111-1111-1111-111111111111" } } } ) } } },
1430 { "comment", "accepted" } } } ) } } }
1431 };
1432}
1433
1434
1435BOOST_AUTO_TEST_CASE( CurrentErcExclusionsMigrateExactlyAndRemainIdempotent )
1436{
1437 for( const auto& target : { kicad9, kicad10 } )
1438 {
1439 nlohmann::json document = currentErcExclusionDocument();
1440 COMPATIBILITY_REPORT report;
1441 DowngradeProjectFileJson( document, target, report );
1442 BOOST_CHECK_EQUAL( document["erc"]["meta"]["version"].get<int>(), 0 );
1443 const auto& exclusion = document["erc"]["erc_exclusions"].at( 0 );
1444 BOOST_CHECK( exclusion
1445 == nlohmann::json::array( { "pin_not_connected|123|-456|11111111-1111-1111-1111-111111111111|"
1446 "00000000-0000-0000-0000-000000000000|||",
1447 "accepted" } ) );
1448 BOOST_CHECK( !report.IsLossy() );
1449 const nlohmann::json once = document;
1450 COMPATIBILITY_REPORT repeated;
1451 DowngradeProjectFileJson( document, target, repeated );
1452 BOOST_CHECK( document == once );
1453 BOOST_CHECK( !repeated.IsLossy() );
1454 }
1455}
1456
1457
1458BOOST_AUTO_TEST_CASE( ErcExclusionsPreserveInstancePathsAndChildTextKeys )
1459{
1460 for( const auto& target : { kicad9, kicad10 } )
1461 {
1462 nlohmann::json document = currentErcExclusionDocument();
1463 auto& marker = document["erc"]["erc_exclusions"][0]["marker"];
1464 const nlohmann::json path = {
1465 { "path", nlohmann::json::array( { { { "value", "22222222-2222-2222-2222-222222222222" } } } ) }
1466 };
1467 marker["sheet_specific_path"] = path;
1468 marker["main_item_sheet_path"] = path;
1469 marker["aux_item_sheet_path"] = path;
1470 COMPATIBILITY_REPORT pathReport;
1471 nlohmann::json allPaths = document;
1472 DowngradeProjectFileJson( allPaths, target, pathReport );
1473 BOOST_REQUIRE_EQUAL( allPaths["erc"]["erc_exclusions"].size(), 1 );
1474 BOOST_CHECK_EQUAL( allPaths["erc"]["erc_exclusions"][0][0].get<std::string>(),
1475 "pin_not_connected|123|-456|11111111-1111-1111-1111-111111111111|"
1476 "00000000-0000-0000-0000-000000000000|/22222222-2222-2222-2222-222222222222|"
1477 "/22222222-2222-2222-2222-222222222222|/22222222-2222-2222-2222-222222222222" );
1478 BOOST_CHECK( !pathReport.IsLossy() );
1479 marker.erase( "aux_item_sheet_path" );
1480 marker["error_type"] = "ERCET_GENERIC_WARNING";
1481 marker["child"] = { { "text_value", "raw warning text" } };
1482 COMPATIBILITY_REPORT report;
1483 DowngradeProjectFileJson( document, target, report );
1484 BOOST_REQUIRE_EQUAL( document["erc"]["erc_exclusions"].size(), 1 );
1485 BOOST_CHECK_EQUAL( document["erc"]["erc_exclusions"][0][0].get<std::string>(),
1486 "generic-warning|123|-456|11111111-1111-1111-1111-111111111111|raw warning "
1487 "text|/22222222-2222-2222-2222-222222222222|/22222222-2222-2222-2222-222222222222|" );
1488 BOOST_CHECK( !report.IsLossy() );
1489 }
1490}
1491
1492
1493BOOST_AUTO_TEST_CASE( UnrepresentableErcExclusionsAreReportedAndDropped )
1494{
1495 for( const auto& target : { kicad9, kicad10 } )
1496 {
1497 for( int invalid = 0; invalid < 4; ++invalid )
1498 {
1499 nlohmann::json document = currentErcExclusionDocument();
1500 auto& marker = document["erc"]["erc_exclusions"][0]["marker"];
1501 if( invalid == 0 )
1502 marker["error_type"] = "ERCET_EMPTY_LABEL_NAME";
1503 else if( invalid == 1 )
1504 marker["items"][0]["value"] = "malformed-id";
1505 else if( invalid == 2 )
1506 marker["position"]["x_nm"] = "12301";
1507 else
1508 {
1509 marker["error_type"] = "ERCET_GENERIC_WARNING";
1510 marker["child"] = { { "text_value", "cannot|encode" } };
1511 }
1512 COMPATIBILITY_REPORT report;
1513 DowngradeProjectFileJson( document, target, report );
1514 BOOST_CHECK( !report.IsBlocked() );
1516 BOOST_CHECK( document["erc"]["erc_exclusions"].empty() );
1517 }
1518 }
1519}
1520
1521
1522BOOST_AUTO_TEST_CASE( UnreviewedProjectSchemasBlockWithoutMutation )
1523{
1524 for( const auto& target : { kicad9, kicad10 } )
1525 {
1526 for( int schema = 0; schema < 5; ++schema )
1527 {
1528 nlohmann::json document = currentErcExclusionDocument();
1529 if( schema == 0 )
1530 document["meta"]["version"] = 5;
1531 else if( schema == 1 )
1532 document["board"]["design_settings"]["meta"]["version"] = 4;
1533 else if( schema == 2 )
1534 document["erc"]["meta"]["version"] = 2;
1535 else if( schema == 3 )
1536 document["tuning_profiles"]["meta"]["version"] = 3;
1537 else
1538 document["net_settings"]["meta"]["version"] = 6;
1539 const nlohmann::json original = document;
1540 COMPATIBILITY_REPORT report;
1541 DowngradeProjectFileJson( document, target, report );
1542 BOOST_CHECK( report.IsBlocked() );
1543 BOOST_CHECK( document == original );
1544 }
1545 }
1546}
1547
1548
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:329
void SetEndEnding(const LINE_ENDING &aEnding)
Definition eda_shape.h:181
FILL_T GetFillMode() const
Definition eda_shape.h:148
virtual void SetCornerRadius(int aRadius)
SHAPE_POLY_SET & GetPolyShape()
virtual void SetEllipseEndAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:416
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
virtual void SetEllipseCenter(const VECTOR2I &aPt)
Definition eda_shape.h:370
virtual void SetEllipseMinorRadius(int aR)
Definition eda_shape.h:388
virtual void SetEllipseMajorRadius(int aR)
Definition eda_shape.h:379
virtual void SetEllipseStartAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:407
LINE_ENDING_STYLE GetEndEndingStyle() const
Definition eda_shape.h:186
void SetFillMode(FILL_T aFill)
virtual void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:279
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.
A temporary directory deleted when it goes out of scope.
const std::filesystem::path & GetPath() const
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.
Holds all the data relating to one schematic.
Definition schematic.h:149
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)
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:557
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:57
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:48
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:392
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:91
bool GetExcludedFromBOM(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
wxString GetFieldText(const wxString &aFieldName, const SCH_SHEET_PATH *aPath=nullptr, const wxString &aVariantName=wxEmptyString) const
void AddInstance(const SCH_SHEET_INSTANCE &aInstance)
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
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:73
std::vector< std::unique_ptr< SCH_PIN > > & GetRawPins()
Definition sch_symbol.h:678
const std::vector< SCH_SYMBOL_INSTANCE > & GetInstances() const
Definition sch_symbol.h:133
wxString GetFieldText(const wxString &aFieldName, const SCH_SHEET_PATH *aPath=nullptr, const wxString &aVariantName=wxEmptyString) const
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:163
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:182
virtual bool GetDNP(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Set or clear the 'Do Not Populate' flag.
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:90
@ INTERNAL
Definition common.h:94
@ 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:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
@ 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:488
@ LAYER_NOTES
Definition layer_ids.h:489
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:437
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)
@ 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.
@ USER
The field ID hasn't been set yet; field is invalid.
@ 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_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
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
BOOST_CHECK_EQUAL(result, "25.4")
#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