KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_sch_downgrade_oracle.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// Schematic version of the board oracle in pcbnew. A downgraded schematic must use only node heads
21// the target release knows, taken from its real keyword files under qa/data/downgrade.
22
23#include <boost/test/unit_test.hpp>
24
25#include <algorithm>
26#include <array>
27#include <cctype>
28#include <fstream>
29#include <functional>
30#include <iterator>
31#include <map>
32#include <memory>
33#include <set>
34#include <string>
35#include <vector>
36
37#include <bitmap_base.h>
38#include <embedded_files.h>
39#include <font/font.h>
40#include <base_units.h>
41#include <mmh3_hash.h>
42#include <schematic.h>
43#include <sch_bitmap.h>
44#include <sch_connection.h>
45#include <sch_group.h>
46#include <sch_rule_area.h>
47#include <sch_screen.h>
48#include <sch_sheet.h>
49#include <lib_symbol.h>
50#include <sch_symbol.h>
51#include <sch_pin.h>
52#include <sch_shape.h>
53#include <sch_line.h>
54#include <sch_textbox.h>
55#include <connection_graph.h>
58#include <cli/exit_codes.h>
60#include <richio.h>
62#include <downgrade_scan.h>
63#include <downgrade_target.h>
67
68#include <wx/filefn.h>
69#include <wx/filename.h>
70#include <wx/image.h>
71#include <wx/imagpng.h>
72#include <wx/mstream.h>
73
78#include <eeschema_test_utils.h>
79#include <schematic_file_util.h>
80
81
82BOOST_AUTO_TEST_SUITE( SchDowngradeOracle )
83
84static const DOWNGRADE_TARGET& kicad9 = KI_TEST::RequireDowngradeTarget( wxT( "9.0" ) );
85static const DOWNGRADE_TARGET& kicad10 = KI_TEST::RequireDowngradeTarget( wxT( "10.0" ) );
86
87
88static void checkExportUsesOnlyKnownTokens( const DOWNGRADE_TARGET& aTarget, const std::string& aTokenFileName )
89{
90 SETTINGS_MANAGER settingsManager;
91
92 std::unique_ptr<SCHEMATIC> schematic;
93 KI_TEST::LoadSchematic( settingsManager, wxT( "NoConnectOnPin" ), schematic );
94 BOOST_REQUIRE( schematic != nullptr );
95
96 SCH_SCREENS allScreens( schematic->Root() );
97
98 for( SCH_SCREEN* screen = allScreens.GetFirst(); screen; screen = allScreens.GetNext() )
99 DowngradeScreenInPlace( screen, aTarget );
100
101 wxString dest = wxFileName::CreateTempFileName( wxT( "kicad_qa_sch_downgrade" ) );
102 wxRemoveFile( dest );
103 dest += wxT( ".kicad_sch" );
104
105 SaveSchematicForTarget( schematic->GetTopLevelSheet(), schematic.get(), dest, aTarget );
106
107 std::ifstream file( dest.ToStdString() );
108 std::string content( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
109 file.close();
110 wxRemoveFile( dest );
111
112 // The stamp proves the dispatch picked the right writer, not just a parseable one.
113 BOOST_CHECK_MESSAGE( content.find( "(version " + std::to_string( aTarget.m_schVersion ) + ")" )
114 != std::string::npos,
115 "Downgraded schematic does not carry the target version stamp" );
116
117 const std::string tokenPath = KI_TEST::GetEeschemaTestDataDir() + "/../downgrade/" + aTokenFileName;
118 const std::set<std::string> known = KI_TEST::LoadTokenSet( tokenPath );
119 BOOST_REQUIRE( !known.empty() );
120
121 std::vector<std::string> unsupported = KI_TEST::UnknownSexprHeads( content, known );
122
123 if( !unsupported.empty() )
124 {
125 std::string msg = "Downgraded schematic uses tokens " + aTarget.m_name.ToStdString() + " cannot parse:";
126
127 for( const std::string& token : unsupported )
128 msg += " " + token;
129
130 BOOST_ERROR( msg );
131 }
132}
133
134
135BOOST_AUTO_TEST_CASE( SchToKicad9UsesOnlyKnownTokens )
136{
137 checkExportUsesOnlyKnownTokens( kicad9, "kicad9_sch_tokens.txt" );
138}
139
140
141BOOST_AUTO_TEST_CASE( SchToKicad10UsesOnlyKnownTokens )
142{
143 checkExportUsesOnlyKnownTokens( kicad10, "kicad10_sch_tokens.txt" );
144}
145
146
147// Exercise the real .kicad_sym write path used to downgrade a symbol library, and confirm the result
148// carries the target stamp and reloads cleanly.
149BOOST_AUTO_TEST_CASE( SymbolLibraryFileDowngradeRoundTrips )
150{
151 wxString dest = wxFileName::CreateTempFileName( wxT( "kicad_qa_symlib" ) );
152 wxRemoveFile( dest );
153 dest += wxT( ".kicad_sym" );
154
155 const wxString src = KI_TEST::GetEeschemaTestDataDir() + "/../libraries/test_project/Device.kicad_sym";
156
157 {
159 std::vector<LIB_SYMBOL*> symbols;
160 pi.EnumerateSymbolLib( symbols, src );
161 BOOST_REQUIRE( !symbols.empty() );
162
163 for( LIB_SYMBOL* symbol : symbols )
165
166 SaveSymbolLibraryForTarget( symbols, dest, kicad9 );
167 }
168
169 // The written file must declare the target version and reload without error.
170 std::ifstream file( dest.ToStdString() );
171 std::string content( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
172 file.close();
173
174 BOOST_CHECK( content.find( "(version " + std::to_string( kicad9.m_symLibVersion ) + ")" ) != std::string::npos );
175
176 SCH_IO_KICAD_SEXPR reader;
177 std::vector<LIB_SYMBOL*> reloaded;
178 BOOST_CHECK_NO_THROW( reader.EnumerateSymbolLib( reloaded, dest ) );
179 BOOST_CHECK( !reloaded.empty() );
180
181 wxRemoveFile( dest );
182}
183
184
185// The v10 symbol library path gets the same round-trip check as v9.
186BOOST_AUTO_TEST_CASE( SymbolLibraryV10RoundTrips )
187{
188 SETTINGS_MANAGER settingsManager;
189
190 wxString dest = wxFileName::CreateTempFileName( wxT( "kicad_qa_symlib10" ) );
191 wxRemoveFile( dest );
192 dest += wxT( ".kicad_sym" );
193
194 const wxString src = KI_TEST::GetEeschemaTestDataDir() + "/../libraries/test_project/Device.kicad_sym";
195
196 {
198 std::vector<LIB_SYMBOL*> symbols;
199 pi.EnumerateSymbolLib( symbols, src );
200 BOOST_REQUIRE( !symbols.empty() );
201
202 for( LIB_SYMBOL* symbol : symbols )
204
205 SaveSymbolLibraryForTarget( symbols, dest, kicad10 );
206 }
207
208 std::ifstream file( dest.ToStdString() );
209 std::string content( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
210 file.close();
211 wxRemoveFile( dest );
212
213 BOOST_CHECK( content.find( "(version " + std::to_string( kicad10.m_symLibVersion ) + ")" ) != std::string::npos );
214}
215
216
217// The write must put a parent before symbols that inherit from it. In this fixture "C" extends
218// "CAP" but sorts before it by name.
219BOOST_AUTO_TEST_CASE( SymbolLibraryWritesParentBeforeChild )
220{
221 wxString dest = wxFileName::CreateTempFileName( wxT( "kicad_qa_symlib_inherit" ) );
222 wxRemoveFile( dest );
223 dest += wxT( ".kicad_sym" );
224
225 const wxString src =
226 KI_TEST::GetEeschemaTestDataDir() + "/spice_netlists/legacy_pspice/schematic_libspice.kicad_sym";
227
228 {
230 std::vector<LIB_SYMBOL*> symbols;
231 pi.EnumerateSymbolLib( symbols, src );
232 BOOST_REQUIRE( !symbols.empty() );
233
234 std::stable_sort( symbols.begin(), symbols.end(),
235 []( const LIB_SYMBOL* a, const LIB_SYMBOL* b )
236 {
237 return a->GetInheritanceDepth() < b->GetInheritanceDepth();
238 } );
239
240 for( LIB_SYMBOL* symbol : symbols )
242
243 SaveSymbolLibraryForTarget( symbols, dest, kicad9 );
244 }
245
246 std::ifstream file( dest.ToStdString() );
247 std::string content( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
248 file.close();
249 wxRemoveFile( dest );
250
251 size_t parentPos = content.find( "(symbol \"CAP\"" );
252 size_t childRef = content.find( "(extends \"CAP\")" );
253
254 BOOST_REQUIRE( parentPos != std::string::npos );
255 BOOST_REQUIRE( childRef != std::string::npos );
256 BOOST_CHECK( parentPos < childRef );
257}
258
259
260// The target computes the image PPI the old truncating way, so the stored scale must be
261// migrated back or the image renders at the wrong size there.
262BOOST_AUTO_TEST_CASE( ImageScaleMigratedForV9 )
263{
264 SETTINGS_MANAGER settingsManager;
265
266 std::unique_ptr<SCHEMATIC> schematic;
267 KI_TEST::LoadSchematic( settingsManager, wxT( "NoConnectOnPin" ), schematic );
268 BOOST_REQUIRE( schematic != nullptr );
269
270 SCH_SHEET* sheet = schematic->GetTopLevelSheet();
271 SCH_BITMAP* otherBitmap = new SCH_BITMAP();
272 wxMemoryBuffer buf = KI_TEST::MakePngWithFractionalPixelsPerCm();
273 BOOST_REQUIRE( otherBitmap->GetReferenceImage().ReadImageFile( buf ) );
274 otherBitmap->GetReferenceImage().SetImageScale( 0.5 );
275 sheet->GetScreen()->Append( otherBitmap );
276
277 SCH_BITMAP* bitmap = new SCH_BITMAP();
278
279 REFERENCE_IMAGE& ref = bitmap->GetReferenceImage();
280 BOOST_REQUIRE( ref.ReadImageFile( buf ) );
281 ref.SetImageScale( 2.0 );
282 sheet->GetScreen()->Append( bitmap );
283
284 int ppi = ref.GetImage().GetPPI();
285 int legacyPPI = ref.GetImage().GetLegacyPPI();
286 BOOST_REQUIRE( legacyPPI > 0 && ppi != legacyPPI );
287
288 double originalScale = ref.GetImageScale();
289
290 wxString dest = wxFileName::CreateTempFileName( wxT( "kicad_qa_sch_imgscale" ) );
291 wxRemoveFile( dest );
292 dest += wxT( ".kicad_sch" );
293
295 SaveSchematicForTarget( sheet, schematic.get(), dest, kicad9 );
296
297 std::ifstream file( dest.ToStdString() );
298 std::string content( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
299 file.close();
300 wxRemoveFile( dest );
301
302 const double written = KI_TEST::SerializedImageScale( content, bitmap->m_Uuid.AsStdString() );
303 BOOST_CHECK_CLOSE( written, originalScale * legacyPPI / ppi, 0.1 );
304 BOOST_CHECK_CLOSE( KI_TEST::SerializedImageScale( content, otherBitmap->m_Uuid.AsStdString() ),
305 0.5 * legacyPPI / ppi, 0.1 );
306}
307
308
309// The downgrade job snapshots and restores a live project's settings pointers around a transient
310// SCHEMATIC. That is only sound while ~SCHEMATIC leaves the project alone, so pin that here.
311BOOST_AUTO_TEST_CASE( SchematicDtorLeavesProjectSettingsAlone )
312{
313 SETTINGS_MANAGER settingsManager;
314
315 std::unique_ptr<SCHEMATIC> schematic;
316 KI_TEST::LoadSchematic( settingsManager, wxT( "NoConnectOnPin" ), schematic );
317 BOOST_REQUIRE( schematic != nullptr );
318
319 PROJECT& project = schematic->Project();
320 PROJECT_FILE& projectFile = project.GetProjectFile();
321
322 ERC_SETTINGS* erc = projectFile.m_ErcSettings;
323 SCHEMATIC_SETTINGS* settings = projectFile.m_SchematicSettings;
324
325 schematic.reset();
326
327 BOOST_CHECK( projectFile.m_ErcSettings == erc );
328 BOOST_CHECK( projectFile.m_SchematicSettings == settings );
329}
330
331
332static std::string downgradeDataPath()
333{
334 return KI_TEST::GetEeschemaTestDataDir() + "/../downgrade/";
335}
336
337
338static int runDowngradeJob( JOB_SCH_DOWNGRADE& aJob, REPORTER& aReporter )
339{
340 EESCHEMA_JOBS_HANDLER handler( nullptr );
341 handler.SetReporter( &aReporter );
342 return handler.JobDowngrade( &aJob );
343}
344
345
347{
348 LIB_SYMBOL lib( wxT( "TransformSymbol" ) );
349 lib.SetBodyStyleNames( { wxT( "first" ), wxT( "second" ), wxT( "third" ) } );
350 lib.SetAssociatedFootprints( { { LIB_ID( wxT( "Lib" ), wxT( "FP" ) ), wxT( "map" ) } } );
352 lib.SetExcludedFromPosFiles( true );
353 lib.SetCustomProperty( wxT( "Vendor" ), wxT( "Acme" ) );
354 lib.GetField( FIELD_T::VALUE )->SetCustomProperty( wxT( "Source" ), wxT( "Library" ) );
355
356 for( int style = 1; style <= 3; ++style )
357 {
358 SCH_PIN* pin = new SCH_PIN( &lib );
359 pin->SetNumber( wxT( "1" ) );
360 pin->SetName( wxT( "SIGNAL" ) );
361 pin->SetBodyStyle( style );
362 pin->SetUnit( 1 );
363 pin->SetPosition( VECTOR2I( schIUScale.mmToIU( 5 * style ), 0 ) );
364 pin->SetCustomProperty( wxT( "Role" ), wxT( "Signal" ) );
365 lib.AddDrawItem( pin );
366 }
367
368 for( bool inLibrary : { false, true } )
369 {
370 auto addShape = [&]( SCH_SHAPE* aShape )
371 {
372 if( inLibrary )
373 lib.AddDrawItem( aShape );
374 else
375 aScreen.Append( aShape );
376 };
377
378 if( inLibrary )
379 {
380 SCH_SHAPE* ended = new SCH_SHAPE( SHAPE_T::POLY );
381 ended->SetPolyPoints( { VECTOR2I(), VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) } );
382 ended->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
384 addShape( ended );
385 }
386 else
387 {
388 SCH_LINE* ended = new SCH_LINE( VECTOR2I(), LAYER_NOTES );
389 ended->SetEndPoint( VECTOR2I( schIUScale.mmToIU( 10 ), 0 ) );
390 ended->SetStroke( STROKE_PARAMS( schIUScale.mmToIU( 0.2 ), LINE_STYLE::SOLID ) );
392 aScreen.Append( ended );
393 }
394
396 {
397 SCH_SHAPE* ellipse = new SCH_SHAPE( type );
398 ellipse->SetEllipseCenter( VECTOR2I( 0, 0 ) );
399 ellipse->SetEllipseMajorRadius( schIUScale.mmToIU( 5 ) );
400 ellipse->SetEllipseMinorRadius( schIUScale.mmToIU( 3 ) );
401 ellipse->SetEllipseStartAngle( ANGLE_0 );
402 ellipse->SetEllipseEndAngle( ANGLE_90 );
403 addShape( ellipse );
404 }
405
406 SCH_SHAPE* rounded = new SCH_SHAPE( SHAPE_T::RECTANGLE );
407 rounded->SetStart( VECTOR2I( 0, 0 ) );
408 rounded->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
409 rounded->SetCornerRadius( schIUScale.mmToIU( 1 ) );
410 addShape( rounded );
411
412 SCH_SHAPE* hatch = new SCH_SHAPE( SHAPE_T::RECTANGLE );
413 hatch->SetStart( VECTOR2I( 0, 0 ) );
414 hatch->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
415 hatch->SetFillMode( FILL_T::HATCH );
416 addShape( hatch );
417 }
418
419 SCH_SYMBOL* third = nullptr;
420
421 for( int style = 1; style <= 3; ++style )
422 {
423 SCH_SYMBOL* symbol = new SCH_SYMBOL( lib, lib.GetLibId(), nullptr, 1, style );
424 symbol->SetExcludedFromPosFiles( true );
425 symbol->SetCustomProperty( wxT( "Owner" ), wxT( "Placed" ) );
426 aScreen.Append( symbol );
427
428 if( style == 3 )
429 third = symbol;
430 }
431
433 locked->SetStart( VECTOR2I( 0, 0 ) );
434 locked->SetEnd( VECTOR2I( schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) ) );
435 locked->SetLocked( true );
436 aScreen.Append( locked );
437
438 SCH_TEXTBOX* textbox = new SCH_TEXTBOX();
439 textbox->SetText( wxT( "Hatched text box" ) );
440 textbox->SetStart( VECTOR2I( 0, 0 ) );
441 textbox->SetEnd( VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 6 ) ) );
442 textbox->SetFillMode( FILL_T::HATCH );
443 aScreen.Append( textbox );
444
445 SCH_RULE_AREA* area = new SCH_RULE_AREA();
446 area->SetPolyPoints( { VECTOR2I( 0, 0 ), VECTOR2I( schIUScale.mmToIU( 10 ), 0 ),
447 VECTOR2I( schIUScale.mmToIU( 10 ), schIUScale.mmToIU( 10 ) ), VECTOR2I( 0, 0 ) } );
448 area->SetDNP( true );
449 area->SetExcludedFromSim( true );
450 area->SetFillMode( FILL_T::HATCH );
451 aScreen.Append( area );
452
453 SCH_GROUP* group = new SCH_GROUP( &aScreen );
454 group->AddItem( third );
455 group->AddItem( textbox );
456 aScreen.Append( group );
457
458 SCH_BITMAP* bitmap = new SCH_BITMAP();
459 wxMemoryBuffer png = KI_TEST::MakePngWithFractionalPixelsPerCm();
461 bitmap->GetReferenceImage().SetImageScale( 2.0 );
462 aScreen.Append( bitmap );
463 return bitmap;
464}
465
466
468 bool aDropInsteadOfApproximate )
469{
471 aReport, aTarget.m_schVersion,
472 {
473 { wxT( "Selected custom body styles" ), 20250827, DOWNGRADE_BUCKET::LOWER },
474 { wxT( "Locked items" ), 20260326, DOWNGRADE_BUCKET::LOWER },
475 { wxT( "Ellipse graphics" ), 20260508, DOWNGRADE_BUCKET::LOWER },
476 { wxT( "Hatched shape fills" ), 20250222, DOWNGRADE_BUCKET::LOWER },
477 { wxT( "Pin-to-pad maps" ), 20260629, DOWNGRADE_BUCKET::DROP },
478 { wxT( "Groups" ), 20250513, DOWNGRADE_BUCKET::LOWER },
479 { wxT( "Jumper pin groups" ), 20250324, DOWNGRADE_BUCKET::DROP },
480 { wxT( "Named body styles" ), 20250827, DOWNGRADE_BUCKET::DROP },
481 { wxT( "Position file exclusions" ), 20260101, DOWNGRADE_BUCKET::DROP },
482 { wxT( "Rounded rectangles" ), 20250829, DOWNGRADE_BUCKET::LOWER },
483 { wxT( "Rule area flags" ), 20250610, DOWNGRADE_BUCKET::DROP },
484 { wxT( "Line endings" ), 20260818, DOWNGRADE_BUCKET::LOWER },
485 { wxT( "Custom user properties" ), 20260830, DOWNGRADE_BUCKET::DROP },
486 },
487 aDropInsteadOfApproximate );
488}
489
490
492 const std::string& aTokenFileName )
493{
494 const auto known = KI_TEST::LoadTokenSet( downgradeDataPath() + aTokenFileName );
495 BOOST_REQUIRE( !known.empty() );
496
497 for( bool drop : { false, true } )
498 {
499 BOOST_TEST_CONTEXT( "Drop approximations: " << drop )
500 {
501 SETTINGS_MANAGER settings;
502 std::unique_ptr<SCHEMATIC> schematic;
503 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
504 BOOST_REQUIRE( schematic );
505 SCH_BITMAP* bitmap = addTransformFeatures( *schematic->RootScreen() );
506 const REFERENCE_IMAGE& ref = bitmap->GetReferenceImage();
507 BOOST_REQUIRE_NE( ref.GetImage().GetPPI(), ref.GetImage().GetLegacyPPI() );
508 const double expectedScale = ref.GetImageScale() * ref.GetImage().GetLegacyPPI() / ref.GetImage().GetPPI();
509 checkSchematicTransformCoverage( ClassifyScreenForDowngrade( schematic->RootScreen(), aTarget, drop ),
510 aTarget, drop );
511
512 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_sch_transform_oracle" );
513 const std::string src = ( tmp.GetPath() / "source.kicad_sch" ).string();
515 io.SaveSchematicFile( src, schematic->GetTopLevelSheet(), schematic.get() );
516 const std::string sourceContent = KI_TEST::ReadGoldenText( src );
517
519 job.m_filename = src;
520 job.m_outputDir = ( tmp.GetPath() / "export" ).string();
521 job.m_target = aTarget.m_id;
522 job.m_force = true;
525 BOOST_REQUIRE_MESSAGE( runDowngradeJob( job, reporter ) == CLI::EXIT_CODES::SUCCESS,
526 reporter.GetMessages().ToStdString() );
527 checkSchematicTransformCoverage( job.m_report, aTarget, drop );
528
529 const std::string dest = ( tmp.GetPath() / "export/source.kicad_sch" ).string();
530 const std::string content = KI_TEST::ReadGoldenText( dest );
531 const auto unsupported = KI_TEST::UnknownSexprHeads( content, known );
532
533 for( const std::string& token : unsupported )
534 BOOST_ERROR( "Transformed schematic uses unknown token: " + token );
535
536 BOOST_CHECK( content.find( "(version " + std::to_string( aTarget.m_schVersion ) + ")" )
537 != std::string::npos );
538 BOOST_CHECK_CLOSE( KI_TEST::SerializedImageScale( content, bitmap->m_Uuid.AsStdString() ), expectedScale,
539 0.1 );
540 std::ifstream outputFile( dest );
541 auto reloaded = KI_TEST::ReadSchematicFromStream( outputFile, &settings.Prj() );
542 BOOST_REQUIRE( reloaded );
543 BOOST_CHECK( !ClassifyScreenForDowngrade( reloaded->RootScreen(), aTarget, drop ).IsLossy() );
544 BOOST_CHECK( KI_TEST::ReadGoldenText( src ) == sourceContent );
545 }
546 }
547}
548
549
550BOOST_AUTO_TEST_CASE( TransformedSchematicToKicad9UsesOnlyKnownTokens )
551{
552 checkTransformedSchematicUsesOnlyKnownTokens( kicad9, "kicad9_sch_tokens.txt" );
553}
554
555
556BOOST_AUTO_TEST_CASE( TransformedSchematicToKicad10UsesOnlyKnownTokens )
557{
558 checkTransformedSchematicUsesOnlyKnownTokens( kicad10, "kicad10_sch_tokens.txt" );
559}
560
561
562using GOLDEN_NET_CONNECTIONS = std::map<std::pair<std::string, std::string>, std::array<std::string, 4>>;
563
564
565static std::string netlistValue( const SEXPR::SEXPR& aNode, const std::string& aHead )
566{
567 for( const SEXPR::SEXPR* child : *aNode.GetChildren() )
568 {
569 if( KI_TEST::GoldenNodeHead( *child ) == aHead && child->GetNumberOfChildren() == 2 )
570 return child->GetChild( 1 )->GetString();
571 }
572
573 throw std::runtime_error( "Missing netlist field " + aHead );
574}
575
576
577static GOLDEN_NET_CONNECTIONS netlistConnections( const std::string& aText )
578{
579 SEXPR::PARSER parser;
580 auto root = parser.Parse( aText );
582
583 for( const SEXPR::SEXPR* nets : *root->GetChildren() )
584 {
585 if( KI_TEST::GoldenNodeHead( *nets ) != "nets" )
586 continue;
587
588 for( const SEXPR::SEXPR* net : *nets->GetChildren() )
589 {
590 if( KI_TEST::GoldenNodeHead( *net ) != "net" )
591 continue;
592
593 const std::string name = netlistValue( *net, "name" );
594 const std::string netClass = netlistValue( *net, "class" );
595
596 for( const SEXPR::SEXPR* node : *net->GetChildren() )
597 {
598 if( KI_TEST::GoldenNodeHead( *node ) != "node" )
599 continue;
600
601 const auto key = std::make_pair( netlistValue( *node, "ref" ), netlistValue( *node, "pin" ) );
602 if( !result.emplace( key,
603 std::array<std::string, 4>{ name, netClass, netlistValue( *node, "pinfunction" ),
604 netlistValue( *node, "pintype" ) } )
605 .second )
606 {
607 throw std::runtime_error( "Duplicate reference/pin in golden netlist" );
608 }
609 }
610 }
611 }
612
613 return result;
614}
615
616
617static void checkNativeSchematicGolden( const DOWNGRADE_TARGET& aTarget, const std::string& aRelease,
618 bool aDropInsteadOfApproximate = false )
619{
620 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_sch_downgrade_golden" );
622 job.m_filename = downgradeDataPath() + "golden/current/body_styles.kicad_sch";
623 job.m_outputDir = ( tmp.GetPath() / "export" ).string();
624 job.m_target = aTarget.m_id;
625 job.m_force = true;
626 job.m_dropInsteadOfApproximate = aDropInsteadOfApproximate;
628 BOOST_REQUIRE_EQUAL( runDowngradeJob( job, reporter ), CLI::EXIT_CODES::SUCCESS );
629 const std::string output = ( tmp.GetPath() / "export/body_styles.kicad_sch" ).string();
630
631 JOB_SCH_DOWNGRADE repeated;
632 repeated.m_filename = job.m_filename;
633 repeated.m_outputDir = ( tmp.GetPath() / "repeated" ).string();
634 repeated.m_target = job.m_target;
635 repeated.m_force = true;
636 repeated.m_dropInsteadOfApproximate = aDropInsteadOfApproximate;
637 BOOST_REQUIRE_EQUAL( runDowngradeJob( repeated, reporter ), CLI::EXIT_CODES::SUCCESS );
638 BOOST_CHECK( KI_TEST::ReadGoldenText( output )
639 == KI_TEST::ReadGoldenText( ( tmp.GetPath() / "repeated/body_styles.kicad_sch" ).string() ) );
640
641 if( aRelease == "v10" )
642 {
643 BOOST_CHECK( !job.m_report.IsLossy() );
644 const std::string difference =
645 KI_TEST::GoldenFileDifference( downgradeDataPath() + "golden/v10/body_styles.kicad_sch", output );
646 BOOST_CHECK_MESSAGE( difference.empty(), difference );
647 }
648 else
649 {
650 BOOST_CHECK_EQUAL( job.m_report.Count( DOWNGRADE_BUCKET::LOWER ), aDropInsteadOfApproximate ? 0 : 1 );
651 BOOST_CHECK_EQUAL( job.m_report.Count( DOWNGRADE_BUCKET::DROP ), aDropInsteadOfApproximate ? 3 : 2 );
652 }
653
654 SETTINGS_MANAGER settings;
655 settings.LoadProject( ( tmp.GetPath() / "body_styles.kicad_pro" ).string() );
656 std::ifstream outputFile( output );
657 auto schematic = KI_TEST::ReadSchematicFromStream( outputFile, &settings.Prj() );
658 schematic->RootScreen()->SetFileName( output );
659 SCH_SHEET_LIST sheets = schematic->BuildSheetListSortedByPageNumbers();
660 schematic->ConnectionGraph()->Recalculate( sheets, true );
661 NETLIST_EXPORTER_KICAD exporter( schematic.get(), nullptr );
663 exporter.Format( &netlist, GNL_NETS );
664 const auto actualConnections = netlistConnections( netlist.GetString() );
665 auto expectedConnections = netlistConnections(
666 KI_TEST::ReadGoldenText( downgradeDataPath() + "golden/" + aRelease + "/body_styles.net" ) );
667
668 // KiCad 9 records just the pin name. Newer netlist writers append its number.
669 // Normalize only that known native-version difference in the reference. The
670 // raw pin names are also checked directly against the independent design.
671 if( aRelease == "v9" )
672 {
673 for( auto& [key, connection] : expectedConnections )
674 {
675 if( !connection[2].empty() )
676 connection[2] += "_" + key.second;
677 }
678 }
679
680 if( aRelease == "v9" && aDropInsteadOfApproximate )
681 {
682 // The independent native design still defines the unchanged supported subset.
683 expectedConnections.erase( { "U1", "1" } );
684 expectedConnections.erase( { "U1", "2" } );
685 BOOST_CHECK( KI_TEST::ReadGoldenText( output ).find( "__KiCad9_body_" ) == std::string::npos );
686 }
687 BOOST_REQUIRE_EQUAL( expectedConnections.size(), aRelease == "v9" && aDropInsteadOfApproximate ? 3 : 5 );
688 BOOST_CHECK( actualConnections == expectedConnections );
689
690 // The independently authored v9 design uses plain symbols for the same drawings.
691 // Compare active shapes and pin UUIDs as well as the real v9-generated netlist.
692 SETTINGS_MANAGER referenceSettings;
693 std::unique_ptr<SCHEMATIC> reference;
694 KI_TEST::LoadSchematic( referenceSettings, wxT( "../downgrade/golden/v9/body_styles" ), reference );
695 std::map<KIID, SCH_SYMBOL*> expectedSymbols;
696 for( SCH_ITEM* item : reference->RootScreen()->Items().OfType( SCH_SYMBOL_T ) )
697 expectedSymbols.emplace( item->m_Uuid, static_cast<SCH_SYMBOL*>( item ) );
698
699 size_t checked = 0;
700 for( SCH_ITEM* item : schematic->RootScreen()->Items().OfType( SCH_SYMBOL_T ) )
701 {
702 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
703 BOOST_REQUIRE( expectedSymbols.count( symbol->m_Uuid ) );
704 SCH_SYMBOL* expected = expectedSymbols.at( symbol->m_Uuid );
705 BOOST_CHECK( symbol->GetPosition() == expected->GetPosition() );
706 BOOST_CHECK_EQUAL( symbol->GetOrientation(), expected->GetOrientation() );
707 BOOST_CHECK_EQUAL( symbol->GetRawPins().size(), expected->GetRawPins().size() );
708
709 for( const auto& pin : symbol->GetRawPins() )
710 {
711 const SCH_PIN* expectedPin = expected->GetPin( pin->GetNumber() );
712 BOOST_REQUIRE( expectedPin != nullptr );
713 BOOST_CHECK( pin->m_Uuid == expectedPin->m_Uuid );
714 BOOST_CHECK( pin->GetPosition() == expectedPin->GetPosition() );
715 BOOST_CHECK_EQUAL( pin->GetName(), expectedPin->GetName() );
716 }
717
718 auto activeShapes = []( SCH_SYMBOL* aSymbol )
719 {
720 std::vector<const SCH_SHAPE*> shapes;
721 for( const SCH_ITEM& drawing : aSymbol->GetLibSymbolRef()->GetDrawItems() )
722 {
723 if( drawing.Type() == SCH_SHAPE_T
724 && ( !drawing.GetBodyStyle() || drawing.GetBodyStyle() == aSymbol->GetBodyStyle() ) )
725 {
726 shapes.push_back( static_cast<const SCH_SHAPE*>( &drawing ) );
727 }
728 }
729 return shapes;
730 };
731
732 const auto shapes = activeShapes( symbol );
733 const auto expectedShapes = activeShapes( expected );
734 BOOST_REQUIRE_EQUAL( shapes.size(), expectedShapes.size() );
735 for( size_t i = 0; i < shapes.size(); ++i )
736 {
737 BOOST_CHECK( shapes[i]->GetShape() == expectedShapes[i]->GetShape() );
738 BOOST_CHECK( shapes[i]->GetStart() == expectedShapes[i]->GetStart() );
739 BOOST_CHECK( shapes[i]->GetEnd() == expectedShapes[i]->GetEnd() );
740 BOOST_CHECK_EQUAL( shapes[i]->GetStroke().GetWidth(), expectedShapes[i]->GetStroke().GetWidth() );
741 BOOST_CHECK( shapes[i]->GetStroke().GetLineStyle() == expectedShapes[i]->GetStroke().GetLineStyle() );
742 BOOST_CHECK( shapes[i]->GetStroke().GetColor() == expectedShapes[i]->GetStroke().GetColor() );
743 BOOST_CHECK( shapes[i]->GetFillMode() == expectedShapes[i]->GetFillMode() );
744 }
745 checked++;
746 }
747 BOOST_CHECK_EQUAL( checked, aRelease == "v9" && aDropInsteadOfApproximate ? 2 : 3 );
748}
749
750
751// A standalone symbol library never reaches the screen pass, so the symbol-scoped subset of the
752// rule table gets its own native reference. The derived symbol sorts before its parent by name,
753// which the format forbids, so the reference also pins the parents-first order.
754static void checkNativeSymbolLibraryGolden( const DOWNGRADE_TARGET& aTarget, const std::string& aRelease )
755{
756 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_native_symlib_golden", "" );
757 const wxString dest = ( tmp.GetPath() / "metadata.kicad_sym" ).wstring();
758 const wxString src = wxString::FromUTF8( downgradeDataPath() + "golden/current/metadata.kicad_sym" );
759
761 std::vector<LIB_SYMBOL*> symbols;
762 pi.EnumerateSymbolLib( symbols, src );
763 BOOST_REQUIRE_EQUAL( symbols.size(), 3 );
764
765 std::stable_sort( symbols.begin(), symbols.end(),
766 []( const LIB_SYMBOL* a, const LIB_SYMBOL* b )
767 {
768 return a->GetInheritanceDepth() < b->GetInheritanceDepth();
769 } );
770
771 for( LIB_SYMBOL* symbol : symbols )
772 DowngradeLibSymbolInPlace( symbol, aTarget );
773
774 SaveSymbolLibraryForTarget( symbols, dest, aTarget );
775
776 const std::string difference = KI_TEST::GoldenFileDifference(
777 downgradeDataPath() + "golden/" + aRelease + "/metadata.kicad_sym", dest.ToStdString() );
778 BOOST_CHECK_MESSAGE( difference.empty(), difference );
779}
780
781
782BOOST_AUTO_TEST_CASE( SymbolLibraryMatchesNativeKicad9Golden )
783{
785}
786
787
788BOOST_AUTO_TEST_CASE( SymbolLibraryMatchesNativeKicad10Golden )
789{
791}
792
793
794BOOST_AUTO_TEST_CASE( SchematicMatchesNativeKicad10Golden )
795{
797}
798
799
800BOOST_AUTO_TEST_CASE( BakedStylesMatchIndependentKicad9DesignAndNetlist )
801{
803}
804
805
806BOOST_AUTO_TEST_CASE( DroppedStylesKeepIndependentKicad9SupportedDesignAndNetlist )
807{
808 checkNativeSchematicGolden( kicad9, "v9", true );
809}
810
811
812BOOST_AUTO_TEST_CASE( DropApproximationsKeepsNativeKicad10SchematicGolden )
813{
814 checkNativeSchematicGolden( kicad10, "v10", true );
815}
816
817
818BOOST_FIXTURE_TEST_CASE( StandaloneKicad10ExportKeepsProjectBusAliasConnectivity, KI_TEST::SCHEMATIC_TEST_FIXTURE )
819{
820 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_bus_alias_export" );
821 const std::string fixture = downgradeDataPath() + "regressions/bus_alias_connectivity/";
822 const std::string source = fixture + "prefix_bus_alias.kicad_sch";
823 const std::string original = KI_TEST::ReadGoldenText( source );
824 const std::string originalProject = KI_TEST::ReadGoldenText( fixture + "prefix_bus_alias.kicad_pro" );
826 job.m_filename = source;
827 job.m_outputDir = ( tmp.GetPath() / "export" ).string();
828 job.m_target = kicad10.m_id;
830 BOOST_REQUIRE_MESSAGE( runDowngradeJob( job, reporter ) == CLI::EXIT_CODES::SUCCESS,
831 reporter.GetMessages().ToStdString() );
832 BOOST_CHECK( !job.m_report.IsLossy() );
833 const std::string project = ( tmp.GetPath() / "export/prefix_bus_alias.kicad_pro" ).string();
834 BOOST_REQUIRE( std::filesystem::exists( project ) );
835 const auto document = nlohmann::json::parse( KI_TEST::ReadGoldenText( project ) );
836 BOOST_CHECK( document.at( "schematic" ).at( "bus_aliases" ).at( "Bus1" )
837 == nlohmann::json( { "D[0..7]", "CTL1", "CTL2" } ) );
838
839 wxFileName output( ( tmp.GetPath() / "export/prefix_bus_alias.kicad_sch" ).string() );
840 LoadSchematic( output );
841 BOOST_REQUIRE( m_schematic );
842 SCH_SHEET_LIST sheets = m_schematic->BuildSheetListSortedByPageNumbers();
843 std::map<std::pair<std::string, std::string>, wxString> connections;
844
845 for( const SCH_SHEET_PATH& sheet : sheets )
846 {
847 for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
848 {
849 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
850 const std::string reference = symbol->GetRef( &sheet ).ToStdString();
851
852 for( SCH_PIN* pin : symbol->GetPins( &sheet ) )
853 {
854 const SCH_CONNECTION* connection = pin->Connection( &sheet );
855 BOOST_REQUIRE( connection );
856 BOOST_REQUIRE( connection->IsNet() );
857 BOOST_CHECK( !connection->Name().IsEmpty() );
858 BOOST_REQUIRE( connections
859 .emplace( std::make_pair( reference, pin->GetNumber().ToStdString() ),
860 connection->Name() )
861 .second );
862 }
863 }
864 }
865
866 BOOST_REQUIRE_EQUAL( connections.size(), 20 );
867 std::set<wxString> netNames;
868
869 for( int pin = 1; pin <= 10; ++pin )
870 {
871 const std::string number = std::to_string( pin );
872 const auto& first = connections.at( { "J1", number } );
873 const auto& second = connections.at( { "J2", number } );
874 BOOST_CHECK( first == second );
875 netNames.insert( first );
876 }
877
878 BOOST_CHECK_EQUAL( netNames.size(), 10 );
879 BOOST_CHECK( KI_TEST::ReadGoldenText( source ) == original );
880 BOOST_CHECK( KI_TEST::ReadGoldenText( fixture + "prefix_bus_alias.kicad_pro" ) == originalProject );
881}
882
883
884BOOST_FIXTURE_TEST_CASE( SelectedVariantPreviewMatchesExportedPositionExclusionLosses, KI_TEST::SCHEMATIC_TEST_FIXTURE )
885{
886 const std::string fixture = KI_TEST::GetEeschemaTestDataDir() + "/NoConnectOnPin.kicad_sch";
887 const std::string originalFixture = KI_TEST::ReadGoldenText( fixture );
888
889 for( bool baseExcluded : { false, true } )
890 {
891 BOOST_TEST_CONTEXT( "Base position-file exclusion: " << baseExcluded )
892 {
893 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_variant_preview" );
894 const std::string source = ( tmp.GetPath() / "variant.kicad_sch" ).string();
895 BOOST_REQUIRE( wxCopyFile( fixture, source ) );
896 LoadSchematic( wxFileName( source ) );
897 SCH_SHEET_LIST paths = m_schematic->BuildUnorderedSheetList();
898 BOOST_REQUIRE_EQUAL( paths.size(), 1 );
899 auto symbols = paths[0].LastScreen()->Items().OfType( SCH_SYMBOL_T );
900 BOOST_REQUIRE_EQUAL( std::distance( symbols.begin(), symbols.end() ), 1 );
901 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
902 symbol->SetExcludedFromPosFiles( baseExcluded );
903 SCH_SYMBOL_VARIANT variant( wxT( "V1" ) );
904 variant.InitializeAttributes( *symbol );
905 variant.m_ExcludedFromPosFiles = !baseExcluded;
906 symbol->AddVariant( paths[0], variant );
907 BOOST_REQUIRE( symbol->GetVariant( paths[0], variant.m_Name ).has_value() );
908 m_schematic->AddVariant( variant.m_Name );
910 io.SaveSchematicFile( source, paths[0].Last(), m_schematic.get() );
911 const std::string original = KI_TEST::ReadGoldenText( source );
912 BOOST_REQUIRE( original.find( "(variant" ) != std::string::npos );
913
914 JOB_SCH_DOWNGRADE baseJob;
915 baseJob.m_filename = source;
916 baseJob.m_outputDir = ( tmp.GetPath() / "base-preview" ).string();
917 baseJob.m_target = kicad9.m_id;
918 baseJob.m_dryRun = true;
920 BOOST_REQUIRE_EQUAL( runDowngradeJob( baseJob, reporter ), CLI::EXIT_CODES::SUCCESS );
921 BOOST_CHECK_EQUAL( baseJob.m_report.Count( DOWNGRADE_BUCKET::DROP ), baseExcluded ? 2 : 1 );
922 BOOST_CHECK( !std::filesystem::exists( tmp.GetPath() / "base-preview" ) );
923
924 JOB_SCH_DOWNGRADE selectedJob;
925 selectedJob.m_filename = source;
926 selectedJob.m_outputDir = ( tmp.GetPath() / "selected" ).string();
927 selectedJob.m_target = kicad9.m_id;
928 selectedJob.m_variant = variant.m_Name;
929 selectedJob.m_dryRun = true;
930 BOOST_REQUIRE_EQUAL( runDowngradeJob( selectedJob, reporter ), CLI::EXIT_CODES::SUCCESS );
931 const int selectedDrops = baseExcluded ? 1 : 2;
932 BOOST_CHECK_EQUAL( selectedJob.m_report.Count( DOWNGRADE_BUCKET::DROP ), selectedDrops );
934 BOOST_CHECK( !selectedJob.m_report.IsBlocked() );
935 BOOST_CHECK_EQUAL( selectedJob.m_foundVariants.size(), 1 );
936 BOOST_CHECK( !std::filesystem::exists( tmp.GetPath() / "selected" ) );
937
938 selectedJob.m_dryRun = false;
939 selectedJob.m_force = true;
940 BOOST_REQUIRE_MESSAGE( runDowngradeJob( selectedJob, reporter ) == CLI::EXIT_CODES::SUCCESS,
941 reporter.GetMessages().ToStdString() );
942 BOOST_CHECK_EQUAL( selectedJob.m_report.Count( DOWNGRADE_BUCKET::DROP ), selectedDrops );
943 BOOST_CHECK( std::filesystem::exists( tmp.GetPath() / "selected/variant.kicad_sch" ) );
944 BOOST_CHECK( KI_TEST::ReadGoldenText( source ) == original );
945 BOOST_CHECK( !std::filesystem::exists( tmp.GetPath() / "variant.kicad_pro" ) );
946 }
947 }
948
949 BOOST_CHECK( KI_TEST::ReadGoldenText( fixture ) == originalFixture );
950}
951
952
953BOOST_FIXTURE_TEST_CASE( SharedSheetVariantConflictRefusesPreviewAndExportBeforeWriting,
955{
956 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_variant_conflict" );
957 const std::string fixture = KI_TEST::GetEeschemaTestDataDir() + "/NoConnectOnPin.kicad_sch";
958 const std::string originalFixture = KI_TEST::ReadGoldenText( fixture );
959 const std::string source = ( tmp.GetPath() / "variant.kicad_sch" ).string();
960 const std::string childSource = ( tmp.GetPath() / "shared.kicad_sch" ).string();
961 BOOST_REQUIRE( wxCopyFile( fixture, source ) );
962 LoadSchematic( wxFileName( source ) );
963 SCH_SHEET* root = m_schematic->GetTopLevelSheet( 0 );
964 SCH_SCREEN* sharedScreen = root->GetScreen();
965 auto symbols = sharedScreen->Items().OfType( SCH_SYMBOL_T );
966 BOOST_REQUIRE_EQUAL( std::distance( symbols.begin(), symbols.end() ), 1 );
967 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( *symbols.begin() );
968 SCH_SHEET_PATH rootPath = m_schematic->BuildUnorderedSheetList()[0];
969 symbol->RemoveInstance( rootPath );
970 SCH_SHEET* first = new SCH_SHEET( m_schematic.get() );
971 SCH_SHEET* second = new SCH_SHEET( m_schematic.get() );
972 first->SetScreen( sharedScreen );
973 second->SetScreen( sharedScreen );
974 first->SetName( wxT( "First" ) );
975 second->SetName( wxT( "Second" ) );
976 first->SetFileName( wxT( "shared.kicad_sch" ) );
977 second->SetFileName( wxT( "shared.kicad_sch" ) );
978 sharedScreen->SetFileName( childSource );
979 root->SetScreen( new SCH_SCREEN( m_schematic.get() ) );
980 const_cast<KIID&>( root->m_Uuid ) = root->GetScreen()->GetUuid();
981 root->GetScreen()->SetFileName( source );
982 root->GetScreen()->Append( first );
983 root->GetScreen()->Append( second );
984 m_schematic->RefreshHierarchy();
985 SCH_SHEET_LIST paths = m_schematic->BuildUnorderedSheetList();
986 BOOST_REQUIRE_EQUAL( paths.size(), 3 );
987 int reference = 1;
988
989 for( SCH_SHEET_PATH& path : paths )
990 {
991 if( path.LastScreen() != sharedScreen )
992 continue;
993
994 path.SetPageNumber( wxString::Format( wxT( "%d" ), reference + 1 ) );
995 SCH_SYMBOL_INSTANCE instance;
996 instance.m_Path = path.Path();
997 instance.m_Reference = wxString::Format( wxT( "TP%d" ), reference++ );
998 symbol->AddHierarchicalReference( instance );
999
1000 if( path.Last() == first )
1001 {
1002 SCH_SYMBOL_VARIANT variant( wxT( "V1" ) );
1003 variant.InitializeAttributes( *symbol );
1004 variant.m_DNP = true;
1005 symbol->AddVariant( path, variant );
1006 }
1007 }
1008
1009 BOOST_REQUIRE_EQUAL( reference, 3 );
1010 m_schematic->AddVariant( wxT( "V1" ) );
1012 io.SaveSchematicFile( source, root, m_schematic.get() );
1013 io.SaveSchematicFile( childSource, first, m_schematic.get() );
1014 const std::string original = KI_TEST::ReadGoldenText( source );
1015 const std::string originalChild = KI_TEST::ReadGoldenText( childSource );
1016 LoadSchematic( wxFileName( source ) );
1017 wxString conflict;
1018 BOOST_REQUIRE( !FlattenSchematicVariant( *m_schematic, wxT( "V1" ), &conflict ) );
1019
1020 for( bool dryRun : { true, false } )
1021 {
1023 job.m_filename = source;
1024 job.m_outputDir = ( tmp.GetPath() / "out" ).string();
1025 job.m_target = kicad9.m_id;
1026 job.m_variant = wxT( "V1" );
1027 job.m_force = true;
1028 job.m_dryRun = dryRun;
1031 BOOST_CHECK( reporter.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) );
1032 BOOST_CHECK( !std::filesystem::exists( tmp.GetPath() / "out" ) );
1033 BOOST_CHECK( KI_TEST::ReadGoldenText( source ) == original );
1034 BOOST_CHECK( KI_TEST::ReadGoldenText( childSource ) == originalChild );
1035 BOOST_CHECK( !std::filesystem::exists( tmp.GetPath() / "variant.kicad_pro" ) );
1036 }
1037
1038 BOOST_CHECK( KI_TEST::ReadGoldenText( fixture ) == originalFixture );
1039}
1040
1041
1042BOOST_AUTO_TEST_CASE( DropApproximationsDryRunAndConsentDoNotWrite )
1043{
1044 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_sch_downgrade_golden" );
1046 BOOST_CHECK( !job.m_dropInsteadOfApproximate );
1047 job.m_filename = downgradeDataPath() + "golden/current/body_styles.kicad_sch";
1048 const std::string original = KI_TEST::ReadGoldenText( job.m_filename.ToStdString() );
1049 job.m_outputDir = ( tmp.GetPath() / "export" ).string();
1050 job.m_target = wxT( "9.0" );
1051 job.m_dropInsteadOfApproximate = true;
1052 job.m_dryRun = true;
1054 BOOST_REQUIRE_EQUAL( runDowngradeJob( job, reporter ), CLI::EXIT_CODES::SUCCESS );
1057 BOOST_CHECK( std::filesystem::is_empty( tmp.GetPath() ) );
1058 job.m_dryRun = false;
1060 BOOST_CHECK( std::filesystem::is_empty( tmp.GetPath() ) );
1061 BOOST_CHECK( KI_TEST::ReadGoldenText( job.m_filename.ToStdString() ) == original );
1062}
1063
1064
1065BOOST_AUTO_TEST_CASE( PublicSchematicJobsRequireFreshOutputsBeforeAnyWrite )
1066{
1067 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_sch_copy_only" );
1068 const auto source = tmp.GetPath() / "source.kicad_sch";
1069 const auto existing = tmp.GetPath() / "existing";
1070 const auto fixture = std::filesystem::path( KI_TEST::GetEeschemaTestDataDir() ) / "NoConnectOnPin.kicad_sch";
1071 std::filesystem::copy_file( fixture, source );
1072 std::filesystem::create_directory( existing );
1073 std::filesystem::copy_file( fixture, existing / source.filename() );
1074 const std::string original = KI_TEST::ReadGoldenText( source.string() );
1075
1076 for( const auto& target : { kicad9, kicad10 } )
1077 {
1078 for( bool drop : { false, true } )
1079 {
1081 BOOST_CHECK( !job.m_inPlace );
1082 job.m_filename = source.string();
1083 job.m_target = target.m_id;
1084 job.m_force = true;
1085 job.m_dropInsteadOfApproximate = drop;
1088 BOOST_CHECK( KI_TEST::ReadGoldenText( source.string() ) == original );
1089 job.m_outputDir = existing.string();
1091 BOOST_CHECK( KI_TEST::ReadGoldenText( source.string() ) == original );
1092 BOOST_CHECK( KI_TEST::ReadGoldenText( ( existing / source.filename() ).string() ) == original );
1093 BOOST_CHECK_EQUAL( std::distance( std::filesystem::directory_iterator( existing ),
1094 std::filesystem::directory_iterator() ),
1095 1 );
1096 }
1097 }
1098}
1099
1100
1101BOOST_AUTO_TEST_CASE( PositionFileExclusionRequiresConsent )
1102{
1103 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_sch_downgrade_golden" );
1105 job.m_filename = downgradeDataPath() + "regressions/position_exclusion.kicad_sch";
1106 const std::string original = KI_TEST::ReadGoldenText( job.m_filename.ToStdString() );
1107 job.m_outputDir = ( tmp.GetPath() / "export" ).string();
1108 job.m_target = wxT( "9.0" );
1112 BOOST_CHECK( job.m_report.IsLossy() );
1113 BOOST_CHECK( std::filesystem::is_empty( tmp.GetPath() ) );
1114
1115 job.m_force = true;
1116 BOOST_REQUIRE_EQUAL( runDowngradeJob( job, reporter ), CLI::EXIT_CODES::SUCCESS );
1117 const std::string output = ( tmp.GetPath() / "export/position_exclusion.kicad_sch" ).string();
1118 BOOST_CHECK( KI_TEST::ReadGoldenText( output ).find( "in_pos_files" ) == std::string::npos );
1119 BOOST_CHECK( KI_TEST::ReadGoldenText( job.m_filename.ToStdString() ) == original );
1120}
1121
1122
1123BOOST_AUTO_TEST_CASE( IncompleteSecondaryHierarchyFailsBeforeWriting )
1124{
1125 const std::string fixture = downgradeDataPath() + "regressions/incomplete_hierarchy/";
1126 for( int failure = 0; failure < 3; ++failure )
1127 {
1128 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_sch_downgrade_golden" );
1129 for( const std::string& name :
1130 { "project.kicad_pro", "project.kicad_sch", "sibling.kicad_sch", "broken.kicad_sch" } )
1131 {
1132 // 0: malformed child, 1: missing child, 2: missing secondary top-level root.
1133 if( ( failure >= 1 && name == "broken.kicad_sch" ) || ( failure == 2 && name == "sibling.kicad_sch" ) )
1134 {
1135 continue;
1136 }
1137 BOOST_REQUIRE( wxCopyFile( fixture + name, ( tmp.GetPath() / name ).string() ) );
1138 }
1139
1140 for( bool dryRun : { false, true } )
1141 {
1143 job.m_filename = ( tmp.GetPath() / "project.kicad_sch" ).string();
1144 job.m_outputDir = ( tmp.GetPath() / "out" ).string();
1145 job.m_target = wxT( "10.0" );
1146 job.m_force = true;
1147 job.m_dryRun = dryRun;
1150 BOOST_CHECK( reporter.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) );
1151 BOOST_CHECK( !std::filesystem::exists( tmp.GetPath() / "out" ) );
1152 BOOST_CHECK( KI_TEST::ReadGoldenText( ( tmp.GetPath() / "project.kicad_sch" ).string() )
1153 == KI_TEST::ReadGoldenText( fixture + "project.kicad_sch" ) );
1154 }
1155 }
1156}
1157
1158
1159static std::vector<const SEXPR::SEXPR*> rawNodes( const SEXPR::SEXPR& aRoot, const std::string& aHead )
1160{
1161 std::vector<const SEXPR::SEXPR*> nodes;
1162 std::function<void( const SEXPR::SEXPR& )> visit = [&]( const SEXPR::SEXPR& node )
1163 {
1164 if( !node.IsList() )
1165 return;
1166 if( KI_TEST::GoldenNodeHead( node ) == aHead )
1167 nodes.push_back( &node );
1168 for( const SEXPR::SEXPR* child : *node.GetChildren() )
1169 visit( *child );
1170 };
1171 visit( aRoot );
1172 return nodes;
1173}
1174
1175
1176static double rawNumber( const SEXPR::SEXPR& aNode )
1177{
1178 return aNode.IsInteger() ? aNode.GetLongInteger() : aNode.GetDouble();
1179}
1180
1181
1182BOOST_AUTO_TEST_CASE( NormalizedClosedPolygonsKeepClosingEdgeInEveryWriterPath )
1183{
1184 for( const auto& target : { kicad9, kicad10 } )
1185 {
1186 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_closed_polygon_writer" );
1187 SETTINGS_MANAGER settings;
1188 std::unique_ptr<SCHEMATIC> schematic;
1189 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1190 SCH_SHAPE polygon( SHAPE_T::POLY );
1191 polygon.SetPolyPoints(
1192 { VECTOR2I(), VECTOR2I( schIUScale.mmToIU( 10 ), 0 ), VECTOR2I( 0, schIUScale.mmToIU( 10 ) ) } );
1193 polygon.GetPolyShape().Outline( 0 ).SetClosed( true );
1194 polygon.SetFillMode( FILL_T::NO_FILL );
1195 schematic->RootScreen()->Append( new SCH_SHAPE( polygon ) );
1196 LIB_SYMBOL* lib = new LIB_SYMBOL( wxT( "closed_polygon" ) );
1197 lib->AddDrawItem( new SCH_SHAPE( polygon ) );
1198 schematic->RootScreen()->AddLibSymbol( lib );
1199 const std::string libraryPath = ( tmp.GetPath() / "polygon.kicad_sym" ).string();
1200 const std::string schematicPath = ( tmp.GetPath() / "polygon.kicad_sch" ).string();
1201 SaveSymbolLibraryForTarget( { lib }, libraryPath, target );
1202 SaveSchematicForTarget( schematic->GetTopLevelSheet(), schematic.get(), schematicPath, target );
1203 for( const auto& path : { libraryPath, schematicPath } )
1204 {
1205 SEXPR::PARSER parser;
1206 auto root = parser.Parse( KI_TEST::ReadGoldenText( path ) );
1207 const auto polylines = rawNodes( *root, "polyline" );
1208 BOOST_REQUIRE( !polylines.empty() );
1209 size_t closedCount = 0;
1210 for( const auto* polyline : polylines )
1211 {
1212 const auto* points = KI_TEST::FindGoldenChild( *polyline, "pts" );
1213 if( !points || points->GetNumberOfChildren() < 4 )
1214 continue;
1215 BOOST_REQUIRE_EQUAL( points->GetNumberOfChildren(), 5 );
1216 const auto* first = points->GetChild( 1 );
1217 const auto* last = points->GetChild( 4 );
1218 BOOST_CHECK_EQUAL( rawNumber( *first->GetChild( 1 ) ), rawNumber( *last->GetChild( 1 ) ) );
1219 BOOST_CHECK_EQUAL( rawNumber( *first->GetChild( 2 ) ), rawNumber( *last->GetChild( 2 ) ) );
1220 ++closedCount;
1221 }
1222 BOOST_CHECK_EQUAL( closedCount, path == libraryPath ? 1 : 2 );
1223 }
1224 }
1225}
1226
1227
1228BOOST_AUTO_TEST_CASE( ExplicitBoldStrokeThicknessUsesLegacyMeaningWithoutMutatingSource )
1229{
1230 for( const auto& target : { kicad9, kicad10 } )
1231 {
1232 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_bold_writer" );
1233 LIB_SYMBOL lib( wxT( "bold_text" ) );
1234 SCH_FIELD* field = lib.GetField( FIELD_T::VALUE );
1235 field->SetBold( true );
1236 field->SetTextThickness( 100000 );
1237 const std::string path = ( tmp.GetPath() / "bold.kicad_sym" ).string();
1238 SaveSymbolLibraryForTarget( { &lib }, path, target );
1239 SEXPR::PARSER parser;
1240 auto root = parser.Parse( KI_TEST::ReadGoldenText( path ) );
1241 auto widths = rawNodes( *root, "thickness" );
1242 BOOST_REQUIRE_EQUAL( widths.size(), 1 );
1243 BOOST_CHECK_CLOSE( rawNumber( *widths.front()->GetChild( 1 ) ), 160000.0 / schIUScale.IU_PER_MM, 0.00001 );
1244 BOOST_CHECK_EQUAL( field->GetTextThickness(), 100000 );
1245 field->SetTextThickness( 0 );
1246 SaveSymbolLibraryForTarget( { &lib }, path, target );
1247 auto automatic = parser.Parse( KI_TEST::ReadGoldenText( path ) );
1248 BOOST_CHECK( rawNodes( *automatic, "thickness" ).empty() );
1249 BOOST_CHECK_EQUAL( field->GetTextThickness(), 0 );
1250 KIFONT::FONT* outline = KIFONT::FONT::GetFont( wxT( "Arial" ), true );
1251 if( !outline->IsStroke() )
1252 {
1253 field->SetFont( outline );
1254 field->SetTextThickness( 100000 );
1255 SaveSymbolLibraryForTarget( { &lib }, path, target );
1256 auto outlined = parser.Parse( KI_TEST::ReadGoldenText( path ) );
1257 auto outlineWidths = rawNodes( *outlined, "thickness" );
1258 BOOST_REQUIRE_EQUAL( outlineWidths.size(), 1 );
1259 BOOST_CHECK_CLOSE( rawNumber( *outlineWidths.front()->GetChild( 1 ) ), 100000.0 / schIUScale.IU_PER_MM,
1260 0.00001 );
1261 BOOST_CHECK_EQUAL( field->GetTextThickness(), 100000 );
1262 }
1263 }
1264}
1265
1266
1267BOOST_AUTO_TEST_CASE( DerivedSymbolKeywordsPreserveRawOverridesInsteadOfInheritedValues )
1268{
1269 for( const auto& target : { kicad9, kicad10 } )
1270 {
1271 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_derived_keywords" );
1272 LIB_SYMBOL parent( wxT( "parent" ) );
1273 parent.SetKeyWords( wxT( "parent inherited keywords" ) );
1274 LIB_SYMBOL empty( wxT( "empty_derived" ) );
1275 empty.SetParent( &parent );
1276 LIB_SYMBOL explicitKeywords( wxT( "explicit_derived" ) );
1277 explicitKeywords.SetParent( &parent );
1278 explicitKeywords.SetKeyWords( wxT( "child raw keywords" ) );
1279 BOOST_REQUIRE( empty.GetRawKeyWords().IsEmpty() );
1280 BOOST_REQUIRE( empty.GetKeyWords() == parent.GetKeyWords() );
1281 const std::string path = ( tmp.GetPath() / "derived.kicad_sym" ).string();
1282 SaveSymbolLibraryForTarget( { &parent, &empty, &explicitKeywords }, path, target );
1284 LIB_SYMBOL* reloadedEmpty = io.LoadSymbol( path, empty.GetName() );
1285 LIB_SYMBOL* reloadedExplicit = io.LoadSymbol( path, explicitKeywords.GetName() );
1286 BOOST_REQUIRE( reloadedEmpty );
1287 BOOST_REQUIRE( reloadedExplicit );
1288 BOOST_CHECK( reloadedEmpty->GetRawKeyWords().IsEmpty() );
1289 BOOST_CHECK( reloadedExplicit->GetRawKeyWords() == wxT( "child raw keywords" ) );
1290 }
1291}
1292
1293
1294BOOST_AUTO_TEST_CASE( EmbeddedAttachmentsUseLegacyChecksumsInSchematicCacheAndLibraryFiles )
1295{
1296 for( const auto& target : { kicad9, kicad10 } )
1297 {
1298 for( size_t payloadSize : { size_t( 13 ), size_t( 4099 ) } )
1299 {
1300 BOOST_TEST_CONTEXT( "target=" << target.m_id.ToStdString() << " payload_size=" << payloadSize )
1301 {
1302 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_embedded_schematic_writer" );
1303 SETTINGS_MANAGER settings;
1304 std::unique_ptr<SCHEMATIC> schematic;
1305 KI_TEST::LoadSchematic( settings, wxT( "NoConnectOnPin" ), schematic );
1306 BOOST_REQUIRE( schematic );
1307
1308 auto attachment = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
1309 attachment->name = wxT( "nonaligned.bin" );
1310
1311 for( size_t i = 0; i < payloadSize; ++i )
1312 attachment->decompressedData.push_back( static_cast<char>( ( i * 131 + i / 251 ) % 256 ) );
1313
1315 BOOST_REQUIRE( attachment->Validate() );
1316 const auto originalPayload = attachment->decompressedData;
1317 const std::string originalEncoded = attachment->compressedEncodedData;
1318 const std::string originalHash = attachment->data_hash;
1319 MMH3_HASH legacyHash( EMBEDDED_FILES::Seed() );
1320 legacyHash.addDataV1( reinterpret_cast<const uint8_t*>( originalPayload.data() ),
1321 originalPayload.size() );
1322 const std::string expectedHash = legacyHash.digest().ToString();
1323 BOOST_REQUIRE_NE( expectedHash, originalHash );
1324
1325 schematic->GetEmbeddedFiles()->AddFile( attachment );
1326 LIB_SYMBOL* lib = new LIB_SYMBOL( wxT( "attached_symbol" ) );
1327 lib->GetEmbeddedFiles()->AddFile( attachment );
1328 const wxString cacheKey = lib->GetLibId().Format().wx_str();
1329 schematic->RootScreen()->AddLibSymbol( lib );
1330
1331 auto checkSourceUnchanged = [&]()
1332 {
1333 BOOST_CHECK( attachment->decompressedData == originalPayload );
1334 BOOST_CHECK_EQUAL( attachment->compressedEncodedData, originalEncoded );
1335 BOOST_CHECK_EQUAL( attachment->data_hash, originalHash );
1336 BOOST_CHECK( attachment->is_valid );
1337 };
1338
1339 auto checkSerializedChecksum = [&]( const std::string& path, size_t expectedCount )
1340 {
1341 const std::string content = KI_TEST::ReadGoldenText( path );
1342 const std::string checksum = "(checksum \"" + expectedHash + "\")";
1343 size_t count = 0;
1344
1345 for( size_t pos = content.find( checksum ); pos != std::string::npos;
1346 pos = content.find( checksum, pos + checksum.size() ) )
1347 {
1348 ++count;
1349 }
1350
1351 BOOST_CHECK_EQUAL( count, expectedCount );
1352 BOOST_CHECK( content.find( "(checksum \"" + originalHash + "\")" ) == std::string::npos );
1353 };
1354
1355 auto checkReloadedAttachment = [&]( const EMBEDDED_FILES& files )
1356 {
1357 auto* reloaded = files.GetEmbeddedFile( attachment->name );
1358 BOOST_REQUIRE( reloaded );
1359 BOOST_CHECK( reloaded->Validate() );
1360 BOOST_CHECK( reloaded->decompressedData == originalPayload );
1361 BOOST_CHECK_EQUAL( reloaded->compressedEncodedData, originalEncoded );
1362 BOOST_CHECK_EQUAL( reloaded->data_hash, originalHash );
1363 };
1364
1365 const std::string libraryPath = ( tmp.GetPath() / "attached_symbol.kicad_sym" ).string();
1366 const std::string schematicPath = ( tmp.GetPath() / "attached_schematic.kicad_sch" ).string();
1367 SaveSymbolLibraryForTarget( { lib }, libraryPath, target );
1368 checkSourceUnchanged();
1369 checkSerializedChecksum( libraryPath, 1 );
1371 LIB_SYMBOL* reloadedLibrary = io.LoadSymbol( libraryPath, lib->GetName() );
1372 BOOST_REQUIRE( reloadedLibrary );
1373 checkReloadedAttachment( *reloadedLibrary->GetEmbeddedFiles() );
1374
1375 SaveSchematicForTarget( schematic->GetTopLevelSheet(), schematic.get(), schematicPath, target );
1376 checkSourceUnchanged();
1377 checkSerializedChecksum( schematicPath, 2 );
1378 std::ifstream outputFile( schematicPath );
1379 auto reloadedSchematic = KI_TEST::ReadSchematicFromStream( outputFile, &settings.Prj() );
1380 BOOST_REQUIRE( reloadedSchematic );
1381 checkReloadedAttachment( *reloadedSchematic->GetEmbeddedFiles() );
1382 const auto& reloadedCache = reloadedSchematic->RootScreen()->GetLibSymbols();
1383 const auto cachedSymbol = reloadedCache.find( cacheKey );
1384 BOOST_REQUIRE( cachedSymbol != reloadedCache.end() );
1385 checkReloadedAttachment( *cachedSymbol->second->GetEmbeddedFiles() );
1386 checkSourceUnchanged();
1387 }
1388 }
1389 }
1390}
1391
1392
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:130
int GetPPI() const
int GetLegacyPPI() const
Recompute the PPI the way it was computed before the pixels/cm truncation fix.
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 KIID m_Uuid
Definition eda_item.h:599
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
virtual void SetCornerRadius(int aRadius)
SHAPE_POLY_SET & GetPolyShape()
virtual void SetEllipseEndAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:416
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
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
void SetFillMode(FILL_T aFill)
virtual void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:279
virtual void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:250
void SetBold(bool aBold)
Set the text to be bold - this will also update the font if needed.
Definition eda_text.cpp:310
virtual int GetTextThickness() const
Definition eda_text.h:159
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
void SetFont(KIFONT::FONT *aFont)
Definition eda_text.cpp:471
Handle Eeschema job dispatches.
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
EMBEDDED_FILE * AddFile(const wxFileName &aName, bool aOverwrite)
Load a file from disk and adds it to the collection.
static uint32_t Seed()
static RETURN_CODE CompressAndEncode(EMBEDDED_FILE &aFile)
Take data from the decompressedData buffer and compresses it using ZSTD into the compressedEncodedDat...
Container for ERC settings.
void SetReporter(REPORTER *aReporter)
std::vector< wxString > m_foundVariants
Variant names found in the schematic when the target drops variants.
wxString m_outputDir
Write downgraded sheets under this fresh folder.
wxString m_variant
Flatten this variant into the export. Empty means the base design.
bool m_inPlace
Internal only: convert the coordinator's already-copied export files.
bool m_dryRun
Only classify and fill m_report, do not write anything.
wxString m_filename
Root schematic file (may be empty to downgrade only libraries)
COMPATIBILITY_REPORT m_report
Filled by the handler so the caller can show it.
wxString m_target
Target release name or id, e.g. "KiCad 10.0" or "10.0". Empty means the newest.
bool m_dropInsteadOfApproximate
Omit features that would otherwise be approximated.
bool m_force
Export even when features must be dropped or approximated.
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:94
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:143
virtual bool IsStroke() const
Definition font.h:101
Definition kiid.h:46
std::string AsStdString() const
Definition kiid.cpp:270
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
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
UTF8 Format() const
Definition lib_id.cpp:132
Define a library symbol object.
Definition lib_symbol.h:114
const wxString & GetRawKeyWords() const
Return only this symbol's keywords, without inheriting from its parent.
Definition lib_symbol.h:218
const LIB_ID & GetLibId() const override
Definition lib_symbol.h:190
wxString GetKeyWords() const override
Definition lib_symbol.h:220
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
void SetParent(LIB_SYMBOL *aParent=nullptr)
wxString GetName() const override
Definition lib_symbol.h:183
void SetKeyWords(const wxString &aKeyWords)
EMBEDDED_FILES * GetEmbeddedFiles() override
void AddDrawItem(SCH_ITEM *aItem, bool aSort=true)
Add a new draw aItem to the draw object list and sort according to aSort.
Decorative shape (arrowhead, circle, square) at the start or end of a graphic line,...
Definition line_ending.h:62
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void addDataV1(const uint8_t *data, size_t length)
Definition mmh3_hash.h:95
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
Generate the KiCad netlist format supported by Pcbnew.
void Format(OUTPUTFORMATTER *aOutputFormatter, int aCtl)
Output this s-expression netlist into aOutputFormatter.
The backing store for a PROJECT, in JSON format.
ERC_SETTINGS * m_ErcSettings
Eeschema params.
SCHEMATIC_SETTINGS * m_SchematicSettings
Container for project specific data.
Definition project.h:63
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
bool ReadImageFile(const wxString &aFullFilename)
Read and store an image file.
const BITMAP_BASE & GetImage() const
Get the underlying image.
double GetImageScale() const
void SetImageScale(double aScale)
Set the image "zoom" value.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
These are loaded from Eeschema settings but then overwritten by the project settings.
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
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
bool IsNet() const
wxString Name(bool aIgnoreSheet=false) const
A set of SCH_ITEMs (i.e., without duplicates).
Definition sch_group.h:48
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void SaveSchematicFile(const wxString &aFileName, SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aSchematic to a storage file in a format that this SCH_IO implementation knows about,...
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
LIB_SYMBOL * LoadSymbol(const wxString &aLibraryPath, const wxString &aAliasName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a LIB_SYMBOL object having aPartName from the aLibraryPath containing a library format that this...
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
void SetEndEnding(const LINE_ENDING &aEnding)
Definition sch_line.h:205
virtual void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_line.h:199
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:146
const wxString & GetName() const
Definition sch_pin.cpp:527
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:378
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.
Container class that holds multiple SCH_SCREEN objects in a hierarchy.
Definition sch_screen.h:818
SCH_SCREEN * GetNext()
SCH_SCREEN * GetFirst()
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
const KIID & GetUuid() const
Definition sch_screen.h:594
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition sch_shape.cpp:98
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...
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 SetName(const wxString &aName)
Definition sch_sheet.h:143
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Variant information for a schematic symbol.
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
void RemoveInstance(const SCH_SHEET_PATH &aInstancePath)
std::optional< SCH_SYMBOL_VARIANT > GetVariant(const SCH_SHEET_PATH &aInstance, const wxString &aVariantName) const
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
void AddHierarchicalReference(const KIID_PATH &aPath, const wxString &aRef, int aUnit)
Add a full hierarchical reference to this symbol.
VECTOR2I GetPosition() const override
Definition sch_symbol.h:900
void SetExcludedFromPosFiles(bool aEnable, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
void AddVariant(const SCH_SHEET_PATH &aInstance, const SCH_SYMBOL_VARIANT &aVariant)
int GetOrientation() const override
Get the display symbol orientation.
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
std::unique_ptr< SEXPR > Parse(const std::string &aString)
SEXPR_VECTOR const * GetChildren() const
Definition sexpr.cpp:40
bool IsInteger() const
Definition sexpr.h:53
int64_t GetLongInteger() const
Definition sexpr.cpp:97
double GetDouble() const
Definition sexpr.cpp:107
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:430
Simple container to manage line stroke parameters.
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 wx_str() const
Definition utf8.cpp:41
wxString m_Name
bool m_ExcludedFromPosFiles
A wrapper for reporting to a wxString object.
Definition reporter.h:242
@ 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
SHAPE_T
Definition eda_shape.h:54
@ ELLIPSE
Definition eda_shape.h:62
@ 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
std::string source
@ LAYER_NOTES
Definition layer_ids.h:489
static const int ERR_ARGS
Definition exit_codes.h:31
static const int ERR_INVALID_INPUT_FILE
Definition exit_codes.h:33
static const int SUCCESS
Definition exit_codes.h:29
static const int ERR_UNKNOWN
Definition exit_codes.h:32
Test utilities for timestamps.
std::unique_ptr< SCHEMATIC > ReadSchematicFromStream(std::istream &aStream, PROJECT *aProject)
std::string GoldenNodeHead(const SEXPR::SEXPR &aNode)
std::set< std::string > LoadTokenSet(const std::string &aFileName)
Read a release keyword file from qa/data/downgrade, one token per line.
std::string GoldenFileDifference(const std::string &aExpected, const std::string &aActual)
std::string ReadGoldenText(const std::string &aPath)
void CheckTransformCoverage(const COMPATIBILITY_REPORT &aReport, int aTargetVersion, std::initializer_list< EXPECTED_DOWNGRADE_FEATURE > aFeatures, bool aDropInsteadOfApproximate)
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
const SEXPR::SEXPR * FindGoldenChild(const SEXPR::SEXPR &aNode, const std::string &aHead)
wxMemoryBuffer MakePngWithFractionalPixelsPerCm()
A tiny PNG whose embedded resolution gives a fractional pixels/cm, so the legacy truncating PPI diffe...
double SerializedImageScale(const std::string &aContent, const std::string &aUuid)
std::vector< std::string > UnknownSexprHeads(const std::string &aContent, const std::set< std::string > &aKnown, const std::set< std::string > &aHarmless={})
Every node head in the serialized text that is not in the known or harmless sets.
STL namespace.
@ RPT_SEVERITY_ERROR
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.
void SaveSymbolLibraryForTarget(const std::vector< LIB_SYMBOL * > &aSymbols, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a .kicad_sym library stamped for the target.
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.
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.
Class to handle a set of SCH_ITEMs.
static void LoadSchematic(SCHEMATIC *aSchematic, SCH_SHEET *aRootSheet, const wxString &aFileName)
An older KiCad release we can export a project back to.
int m_schVersion
SEXPR_SCHEMATIC_FILE_VERSION for the target.
wxString m_id
Stable machine key for CLI and jobsets, e.g. "10.0".
std::string ToString() const
Definition hash_128.h:43
A simple container for schematic symbol instance information.
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string netlist
std::string path
IbisParser parser & reporter
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
static const DOWNGRADE_TARGET & kicad10
static const DOWNGRADE_TARGET & kicad9
static std::vector< const SEXPR::SEXPR * > rawNodes(const SEXPR::SEXPR &aRoot, const std::string &aHead)
static int runDowngradeJob(JOB_SCH_DOWNGRADE &aJob, REPORTER &aReporter)
static void checkNativeSymbolLibraryGolden(const DOWNGRADE_TARGET &aTarget, const std::string &aRelease)
static GOLDEN_NET_CONNECTIONS netlistConnections(const std::string &aText)
static void checkExportUsesOnlyKnownTokens(const DOWNGRADE_TARGET &aTarget, const std::string &aTokenFileName)
static void checkNativeSchematicGolden(const DOWNGRADE_TARGET &aTarget, const std::string &aRelease, bool aDropInsteadOfApproximate=false)
static SCH_BITMAP * addTransformFeatures(SCH_SCREEN &aScreen)
static void checkSchematicTransformCoverage(const COMPATIBILITY_REPORT &aReport, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
static std::string downgradeDataPath()
BOOST_AUTO_TEST_CASE(SchToKicad9UsesOnlyKnownTokens)
BOOST_FIXTURE_TEST_CASE(StandaloneKicad10ExportKeepsProjectBusAliasConnectivity, KI_TEST::SCHEMATIC_TEST_FIXTURE)
static void checkTransformedSchematicUsesOnlyKnownTokens(const DOWNGRADE_TARGET &aTarget, const std::string &aTokenFileName)
static double rawNumber(const SEXPR::SEXPR &aNode)
static std::string netlistValue(const SEXPR::SEXPR &aNode, const std::string &aHead)
std::map< std::pair< std::string, std::string >, std::array< std::string, 4 > > GOLDEN_NET_CONNECTIONS
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_SHAPE_T
Definition typeinfo.h:145
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708