KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_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// A downgraded board must use only node heads the target release knows, taken from its real
21// keyword files under qa/data/downgrade.
22
23#include <boost/test/unit_test.hpp>
24
25#include <cctype>
26#include <fstream>
27#include <iterator>
28#include <memory>
29#include <set>
30#include <string>
31#include <utility>
32#include <vector>
33
34#include <base_units.h>
35#include <board.h>
38#include <footprint.h>
43#include <mmh3_hash.h>
44#include <netinfo.h>
45#include <pad.h>
46#include <pcb_barcode.h>
47#include <pcb_group.h>
48#include <pcb_grid_item.h>
49#include <pcb_drill_chart.h>
50#include <pcb_drill_map.h>
51#include <pcb_point.h>
52#include <pcb_reference_image.h>
53#include <pcb_shape.h>
54#include <pcb_table.h>
55#include <pcb_tablecell.h>
56#include <pcb_textbox.h>
57#include <pcb_track.h>
59#include <zone.h>
61#include <downgrade_scan.h>
62#include <downgrade_target.h>
68
69
70BOOST_AUTO_TEST_SUITE( DowngradeOracle )
71
72static const DOWNGRADE_TARGET& kicad9 = KI_TEST::RequireDowngradeTarget( wxT( "9.0" ) );
73static const DOWNGRADE_TARGET& kicad10 = KI_TEST::RequireDowngradeTarget( wxT( "10.0" ) );
74
75
76static void checkExportUsesOnlyKnownTokens( const DOWNGRADE_TARGET& aTarget, const std::string& aTokenFileName )
77{
78 SETTINGS_MANAGER settingsManager;
79
80 const std::string src = KI_TEST::GetPcbnewTestDataDir() + "via_off_center.kicad_pcb";
81
82 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_downgrade_oracle", "" );
83 const std::string dest = ( tmp.GetPath() / "out.kicad_pcb" ).string();
84
86 BOOST_REQUIRE( ExportBoardToOlderVersion( src, dest, aTarget, report ) );
87
88 std::ifstream file( dest );
89 std::string content( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
90
91 const std::set<std::string> known =
92 KI_TEST::LoadTokenSet( KI_TEST::GetPcbnewTestDataDir() + "../downgrade/" + aTokenFileName );
93 BOOST_REQUIRE( !known.empty() );
94
95 std::vector<std::string> unsupported = KI_TEST::UnknownSexprHeads( content, known );
96
97 if( !unsupported.empty() )
98 {
99 std::string msg = "Downgraded board uses tokens " + aTarget.m_name.ToStdString() + " cannot parse:";
100
101 for( const std::string& token : unsupported )
102 msg += " " + token;
103
104 BOOST_ERROR( msg );
105 }
106}
107
108
109BOOST_AUTO_TEST_CASE( BoardToKicad9UsesOnlyKnownTokens )
110{
111 checkExportUsesOnlyKnownTokens( kicad9, "kicad9_board_tokens.txt" );
112}
113
114
115BOOST_AUTO_TEST_CASE( BoardToKicad10UsesOnlyKnownTokens )
116{
117 checkExportUsesOnlyKnownTokens( kicad10, "kicad10_board_tokens.txt" );
118}
119
120
122{
123 NETINFO_ITEM* net = new NETINFO_ITEM( &aBoard, wxT( "TRANSFORM_SIGNAL" ) );
124 net->SetNetChain( wxT( "TRANSFORM_CHAIN" ) );
125 aBoard.Add( net );
126
127 FOOTPRINT* fp = new FOOTPRINT( &aBoard );
128 fp->EnsureExtrudedBody().m_height = pcbIUScale.mmToIU( 5 );
129 fp->SetUnitInfo( { { wxT( "A" ), { wxT( "1" ) } }, { wxT( "B" ), { wxT( "2" ) } } } );
131 fp->AddVariant( wxT( "alternate" ) )->SetDNP( true );
132 fp->SetExcludedFromSim( true );
133 fp->SetCustomProperty( wxT( "Manufacturing owner" ), wxT( "QA" ) );
134 aBoard.SetVariantNames( { wxT( "alternate" ) } );
135 aBoard.Add( fp );
136
137 PAD* pad = new PAD( fp );
138 pad->SetNumber( wxT( "1" ) );
139 pad->SetNet( net );
140 pad->SetProperty( PAD_PROP::PRESSFIT );
141 pad->SetSimElectricalType( PAD_SIM_ELECTRICAL_TYPE::SOURCE );
142 pad->SetPadToDieDelay( 1000 );
143 pad->Padstack().FrontPostMachining().mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE;
144 pad->Padstack().FrontPostMachining().size = pcbIUScale.mmToIU( 1.2 );
145 pad->Padstack().FrontPostMachining().depth = pcbIUScale.mmToIU( 0.2 );
146 fp->Add( pad );
147
148 PAD* customPad = new PAD( fp );
149 customPad->SetNumber( wxT( "2" ) );
150 customPad->SetShape( F_Cu, PAD_SHAPE::CUSTOM );
151 PCB_SHAPE* primitive = new PCB_SHAPE( customPad, SHAPE_T::RECTANGLE );
152 primitive->SetStart( VECTOR2I( -1000000, -1000000 ) );
153 primitive->SetEnd( VECTOR2I( 1000000, 1000000 ) );
154 primitive->SetCornerRadius( pcbIUScale.mmToIU( 0.3 ) );
155 primitive->SetFillMode( FILL_T::FILLED_SHAPE );
156 customPad->AddPrimitive( F_Cu, primitive );
157 fp->Add( customPad );
158
159 aBoard.Add( new PCB_GRID_ITEM( &aBoard ) );
160 PCB_DRILL_CHART* chart = new PCB_DRILL_CHART( &aBoard );
161 chart->SetLayer( Dwgs_User );
162 aBoard.Add( chart );
163 PCB_DRILL_MAP* map = new PCB_DRILL_MAP( &aBoard );
164 map->SetLayer( Dwgs_User );
165 aBoard.Add( map );
166 aBoard.GetDesignSettings().GetDrillSymbolProfile().SetName( wxT( "QA symbols" ) );
167
168 for( PCB_GENERATOR* generator : { static_cast<PCB_GENERATOR*>( new PCB_VIA_STITCH( &aBoard ) ),
169 static_cast<PCB_GENERATOR*>( new PCB_VIA_STACK( &aBoard ) ) } )
170 {
171 aBoard.Add( generator );
172 PCB_VIA* via = new PCB_VIA( &aBoard );
173 via->SetWidth( F_Cu, pcbIUScale.mmToIU( 0.6 ) );
174 via->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
175 aBoard.Add( via );
176 generator->AddItem( via );
177 }
178
179 for( BOARD_ITEM_CONTAINER* parent :
180 { static_cast<BOARD_ITEM_CONTAINER*>( &aBoard ), static_cast<BOARD_ITEM_CONTAINER*>( fp ) } )
181 {
182 PCB_SHAPE* ended = new PCB_SHAPE( parent, SHAPE_T::SEGMENT );
183 ended->SetStart( VECTOR2I( 0, 0 ) );
184 ended->SetEnd( VECTOR2I( 10000000, 0 ) );
185 ended->SetWidth( 200000 );
186 ended->SetLayer( F_SilkS );
188 parent->Add( ended );
190 constraint->AddMember( ended->m_Uuid );
191 parent->Add( constraint );
192
194 {
195 PCB_SHAPE* ellipse = new PCB_SHAPE( parent, type );
196 ellipse->SetEllipseCenter( VECTOR2I( 0, 0 ) );
197 ellipse->SetEllipseMajorRadius( pcbIUScale.mmToIU( 5 ) );
198 ellipse->SetEllipseMinorRadius( pcbIUScale.mmToIU( 3 ) );
199 ellipse->SetEllipseStartAngle( ANGLE_0 );
200 ellipse->SetEllipseEndAngle( ANGLE_90 );
201 ellipse->SetWidth( pcbIUScale.mmToIU( 0.3 ) );
202 ellipse->SetLayer( F_SilkS );
203 parent->Add( ellipse );
204 }
205
206 PCB_SHAPE* rounded = new PCB_SHAPE( parent, SHAPE_T::RECTANGLE );
207 rounded->SetStart( VECTOR2I( 0, 0 ) );
208 rounded->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
209 rounded->SetCornerRadius( pcbIUScale.mmToIU( 1 ) );
210 rounded->SetLayer( F_SilkS );
211 parent->Add( rounded );
212
213 PCB_SHAPE* hatch = new PCB_SHAPE( parent, SHAPE_T::RECTANGLE );
214 hatch->SetStart( VECTOR2I( 0, 0 ) );
215 hatch->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
216 hatch->SetFillMode( FILL_T::HATCH );
217 hatch->SetLayer( F_SilkS );
218 parent->Add( hatch );
219
220 PCB_SHAPE* copperHatch = new PCB_SHAPE( parent, SHAPE_T::RECTANGLE );
221 copperHatch->SetStart( VECTOR2I( pcbIUScale.mmToIU( 20 ), 0 ) );
222 copperHatch->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 6 ) ) );
223 copperHatch->SetFillMode( FILL_T::HATCH );
224 copperHatch->SetLayer( F_Cu );
225 parent->Add( copperHatch );
226
227 PCB_BARCODE* barcode = new PCB_BARCODE( parent );
228 barcode->SetText( wxT( "TEST123" ) );
229 barcode->SetWidth( pcbIUScale.mmToIU( 10 ) );
230 barcode->SetHeight( pcbIUScale.mmToIU( 10 ) );
231 barcode->SetLayer( F_SilkS );
232 parent->Add( barcode );
233
234 PCB_TEXTBOX* textbox = new PCB_TEXTBOX( parent );
235 textbox->SetText( wxT( "Knockout" ) );
236 textbox->SetIsKnockout( true );
237 textbox->SetStart( VECTOR2I( 0, 0 ) );
238 textbox->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
239 textbox->SetLayer( F_SilkS );
240 parent->Add( textbox );
241
242 PCB_TABLE* table = new PCB_TABLE( parent, pcbIUScale.mmToIU( 0.1 ) );
243 table->SetColCount( 1 );
244 table->SetColWidth( 0, pcbIUScale.mmToIU( 10 ) );
245 table->SetRowHeight( 0, pcbIUScale.mmToIU( 6 ) );
246 table->SetLayer( F_SilkS );
247 PCB_TABLECELL* cell = new PCB_TABLECELL( table );
248 cell->SetText( wxT( "Knockout cell" ) );
249 cell->SetIsKnockout( true );
250 cell->SetStart( VECTOR2I( 0, 0 ) );
251 cell->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
252 table->AddCell( cell );
253 parent->Add( table );
254
255 ZONE* zone = new ZONE( parent );
256 zone->SetLayer( F_Cu );
257 zone->AppendCorner( VECTOR2I( 0, 0 ), -1 );
258 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), 0 ), -1 );
259 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), -1 );
260 zone->LayerProperties()[F_Cu].hatching_offset = VECTOR2I( 100, 100 );
261 parent->Add( zone );
262 parent->Add( new PCB_POINT( parent, VECTOR2I( 0, 0 ), pcbIUScale.mmToIU( 1 ) ) );
263
264 PCB_GROUP* group = new PCB_GROUP( parent );
265 group->SetName( wxT( "transform group" ) );
266 group->SetDesignBlockLibId( LIB_ID( wxT( "Lib" ), wxT( "Block" ) ) );
267 group->AddItem( hatch );
268 group->AddItem( barcode );
269 parent->Add( group );
270 }
271
272 PCB_VIA* skipVia = new PCB_VIA( &aBoard );
273 skipVia->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.6 ) );
274 skipVia->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
276 skipVia->Padstack().Drill().is_capped = true;
277 aBoard.Add( skipVia );
278
279 PCB_VIA* buriedVia = new PCB_VIA( &aBoard );
280 buriedVia->SetViaType( VIATYPE::BURIED );
281 buriedVia->SetLayerPair( In1_Cu, In2_Cu );
282 buriedVia->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.6 ) );
283 buriedVia->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
284 aBoard.Add( buriedVia );
285
286 aBoard.GetDesignSettings().m_ZoneLayerProperties[F_Cu].hatching_offset = VECTOR2I( 100, 100 );
287
289 {
290 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
291 item->SetDielectricModel( DIELECTRIC_MODEL::DJORDJEVIC_SARKAR );
292 }
293
295 wxMemoryBuffer png = KI_TEST::MakePngWithFractionalPixelsPerCm();
296 BOOST_REQUIRE( image->GetReferenceImage().ReadImageFile( png ) );
297 image->GetReferenceImage().SetImageScale( 2.0 );
298 aBoard.Add( image );
299
300 for( BOARD_ITEM* item : aBoard.Drawings() )
301 {
302 if( item->Type() == PCB_DRILL_CHART_T )
303 static_cast<PCB_DRILL_CHART*>( item )->RebuildCells( aBoard );
304 }
305
306 return image;
307}
308
309
310static void checkBoardTransformCoverage( const COMPATIBILITY_REPORT& aReport, const DOWNGRADE_TARGET& aTarget,
311 bool aDropInsteadOfApproximate )
312{
313 // Dates are explicit expectations, independent of the production rule table.
315 aReport, aTarget.m_boardVersion,
316 {
317 { wxT( "Geometric constraints" ), 20260624, DOWNGRADE_BUCKET::DROP },
318 { wxT( "Local grids" ), 20260728, DOWNGRADE_BUCKET::DROP },
319 { wxT( "Via stitching and guarding" ), 20260816, DOWNGRADE_BUCKET::DROP },
320 { wxT( "Microvia stack generators" ), 20260830, DOWNGRADE_BUCKET::DROP },
321 { wxT( "Graphic line endings" ), 20260818, DOWNGRADE_BUCKET::LOWER },
322 { wxT( "Footprint simulation exclusions" ), 20260828, DOWNGRADE_BUCKET::DROP },
323 { wxT( "Custom properties" ), 20260831, DOWNGRADE_BUCKET::DROP },
324 { wxT( "Drill charts and maps" ), 20260901, DOWNGRADE_BUCKET::DROP },
325 { wxT( "Drill symbol configuration" ), 20260901, DOWNGRADE_BUCKET::DROP },
326 { wxT( "Net chains" ), 20260512, DOWNGRADE_BUCKET::DROP },
327 { wxT( "Ellipse graphics" ), 20260508, DOWNGRADE_BUCKET::LOWER },
328 { wxT( "Extruded 3D bodies" ), 20260410, DOWNGRADE_BUCKET::DROP },
329 { wxT( "Pad simulation types" ), 20260521, DOWNGRADE_BUCKET::DROP },
330 { wxT( "Footprint unit metadata" ), 20250909, DOWNGRADE_BUCKET::DROP },
331 { wxT( "Barcodes" ), 20250914, DOWNGRADE_BUCKET::LOWER },
332 { wxT( "PCB variants" ), 20260101, DOWNGRADE_BUCKET::DROP },
333 { wxT( "Jumper pad groups" ), 20250324, DOWNGRADE_BUCKET::DROP },
334 { wxT( "Counterbore / countersink" ), 20251101, DOWNGRADE_BUCKET::DROP },
335 { wxT( "Press-fit pads" ), 20250811, DOWNGRADE_BUCKET::DROP },
336 { wxT( "Skip vias" ), 20250801, DOWNGRADE_BUCKET::LOWER, true },
337 { wxT( "Buried vias" ), 20250926, DOWNGRADE_BUCKET::LOWER, true },
338 { wxT( "Zone layer defaults" ), 20250302, DOWNGRADE_BUCKET::DROP },
339 { wxT( "Pad-to-die delays" ), 20250401, DOWNGRADE_BUCKET::DROP },
340 { wxT( "Hatched shape fills" ), 20250222, DOWNGRADE_BUCKET::LOWER },
341 { wxT( "Hatched copper fills" ), 20250222, DOWNGRADE_BUCKET::LOWER, true },
342 { wxT( "PCB points" ), 20250901, DOWNGRADE_BUCKET::DROP },
343 { wxT( "Via protection" ), 20250228, DOWNGRADE_BUCKET::DROP },
344 { wxT( "Dielectric frequency models" ), 20260511, DOWNGRADE_BUCKET::DROP },
345 { wxT( "Group design block links" ), 20250513, DOWNGRADE_BUCKET::DROP },
346 { wxT( "Knockout text boxes" ), 20250210, DOWNGRADE_BUCKET::LOWER },
347 { wxT( "Rounded rectangles" ), 20250829, DOWNGRADE_BUCKET::LOWER },
348 { wxT( "Knockout table cells" ), 20260603, DOWNGRADE_BUCKET::LOWER },
349 { wxT( "Per-zone layer properties" ), 20250302, DOWNGRADE_BUCKET::DROP },
350 },
351 aDropInsteadOfApproximate );
352}
353
354
356 const std::string& aTokenFileName )
357{
358 SETTINGS_MANAGER settingsManager;
359 const std::set<std::string> known =
360 KI_TEST::LoadTokenSet( KI_TEST::GetPcbnewTestDataDir() + "../downgrade/" + aTokenFileName );
361 BOOST_REQUIRE( !known.empty() );
362
363 for( bool drop : { false, true } )
364 {
365 BOOST_TEST_CONTEXT( "Drop approximations: " << drop )
366 {
367 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_board_transform_oracle" );
368 const std::string src = ( tmp.GetPath() / "source.kicad_pcb" ).string();
369 const std::string dest = ( tmp.GetPath() / "out.kicad_pcb" ).string();
371 std::unique_ptr<BOARD> board(
372 io.LoadBoard( KI_TEST::GetPcbnewTestDataDir() + "via_off_center.kicad_pcb", nullptr ) );
373 BOOST_REQUIRE( board );
375 const REFERENCE_IMAGE& ref = image->GetReferenceImage();
376 BOOST_REQUIRE_NE( ref.GetImage().GetPPI(), ref.GetImage().GetLegacyPPI() );
377 const double expectedScale = ref.GetImageScale() * ref.GetImage().GetLegacyPPI() / ref.GetImage().GetPPI();
378 checkBoardTransformCoverage( ClassifyBoardForDowngrade( board.get(), aTarget, drop ), aTarget, drop );
379 io.SaveBoard( src, *board );
380 const std::string sourceContent = KI_TEST::ReadGoldenText( src );
381
383 BOOST_REQUIRE( ExportBoardToOlderVersion( src, dest, aTarget, report, nullptr, wxEmptyString, drop ) );
384 checkBoardTransformCoverage( report, aTarget, drop );
385
386 const std::string content = KI_TEST::ReadGoldenText( dest );
387 const auto unsupported = KI_TEST::UnknownSexprHeads( content, known );
388
389 for( const std::string& token : unsupported )
390 BOOST_ERROR( "Transformed board uses unknown token: " + token );
391
392 BOOST_CHECK( content.find( "(version " + std::to_string( aTarget.m_boardVersion ) + ")" )
393 != std::string::npos );
394 BOOST_CHECK_CLOSE( KI_TEST::SerializedImageScale( content, image->m_Uuid.AsStdString() ), expectedScale,
395 0.1 );
396 std::unique_ptr<BOARD> reloaded( io.LoadBoard( dest, nullptr ) );
397 BOOST_REQUIRE( reloaded );
398 const auto reloadReport = ClassifyBoardForDowngrade( reloaded.get(), aTarget, drop );
399 BOOST_CHECK( !reloadReport.IsBlocked() );
400
401 // Current readers reconstruct false IPC flags in legacy files. These in-memory
402 // upgrades do not mean newer syntax was written.
403 if( aTarget.m_id == wxT( "9.0" ) )
404 {
405 size_t viaCount = 0;
406 size_t buriedCount = 0;
407
408 for( PCB_TRACK* track : reloaded->Tracks() )
409 {
410 if( track->Type() != PCB_VIA_T )
411 continue;
412
413 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
414 const PADSTACK& padstack = via->Padstack();
415 ++viaCount;
416 BOOST_CHECK( padstack.FrontOuterLayers().has_covering == false );
417 BOOST_CHECK( padstack.BackOuterLayers().has_covering == false );
418 BOOST_CHECK( padstack.FrontOuterLayers().has_plugging == false );
419 BOOST_CHECK( padstack.BackOuterLayers().has_plugging == false );
420 BOOST_CHECK( padstack.Drill().is_filled == false );
421 BOOST_CHECK( padstack.Drill().is_capped == false );
422
423 if( via->IsBuriedVia() )
424 {
425 ++buriedCount;
426 BOOST_CHECK( via->GetViaType() == VIATYPE::BLIND );
427 BOOST_CHECK( via->TopLayer() == In1_Cu );
428 BOOST_CHECK( via->BottomLayer() == In2_Cu );
429 }
430 }
431
432 BOOST_CHECK_EQUAL( buriedCount, 1 );
433
434 for( const COMPAT_ENTRY& entry : reloadReport.Entries() )
435 {
436 if( entry.m_feature == wxT( "Via protection" ) )
437 BOOST_CHECK_EQUAL( entry.m_count, viaCount );
438 else
439 BOOST_ERROR( "Unexpected feature after reload: " + entry.m_feature.ToStdString() );
440 }
441 }
442 else
443 {
444 BOOST_CHECK( !reloadReport.IsLossy() );
445 }
446
447 BOOST_CHECK( KI_TEST::ReadGoldenText( src ) == sourceContent );
448 }
449 }
450}
451
452
453BOOST_AUTO_TEST_CASE( TransformedBoardToKicad9UsesOnlyKnownTokens )
454{
455 checkTransformedBoardUsesOnlyKnownTokens( kicad9, "kicad9_board_tokens.txt" );
456}
457
458
459BOOST_AUTO_TEST_CASE( TransformedBoardToKicad10UsesOnlyKnownTokens )
460{
461 checkTransformedBoardUsesOnlyKnownTokens( kicad10, "kicad10_board_tokens.txt" );
462}
463
464
465static void checkNativeBoardGolden( const DOWNGRADE_TARGET& aTarget, const std::string& aRelease,
466 const std::string& aBoard, const std::string& aReference, size_t aExpectedDrops,
467 size_t aExpectedLowerings, bool aDropInsteadOfApproximate = false )
468{
469 SETTINGS_MANAGER settingsManager;
470 const std::string fixture = KI_TEST::GetPcbnewTestDataDir() + "../downgrade/golden/";
471 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_native_golden", "" );
472 const std::string dest = ( tmp.GetPath() / ( aBoard + ".kicad_pcb" ) ).string();
474
475 // Expected files were saved by native KiCad, never by the exporter under test.
476 BOOST_REQUIRE( ExportBoardToOlderVersion( fixture + "current/" + aBoard + ".kicad_pcb", dest, aTarget, report,
477 nullptr, wxEmptyString, aDropInsteadOfApproximate ) );
478 BOOST_CHECK( !report.IsBlocked() );
479 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::DROP ), aExpectedDrops );
480 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), aExpectedLowerings );
481 const std::string difference =
482 KI_TEST::GoldenFileDifference( fixture + aRelease + "/" + aReference + ".kicad_pcb", dest );
483 BOOST_CHECK_MESSAGE( difference.empty(), difference );
484}
485
486
487BOOST_AUTO_TEST_CASE( BoardMatchesNativeKicad9Golden )
488{
489 // KiCad 9 drops the explicit disabled via-protection overrides without changing the via.
490 checkNativeBoardGolden( kicad9, "v9", "geometry", "geometry", 1, 0 );
491}
492
493
494BOOST_AUTO_TEST_CASE( BoardMatchesNativeKicad10Golden )
495{
496 checkNativeBoardGolden( kicad10, "v10", "geometry", "geometry", 0, 0 );
497}
498
499
500BOOST_AUTO_TEST_CASE( LoweredGraphicsMatchNativeKicad9Golden )
501{
502 checkNativeBoardGolden( kicad9, "v9", "graphics", "graphics", 0, 2 );
503}
504
505
506BOOST_AUTO_TEST_CASE( GraphicsMatchNativeKicad10Golden )
507{
508 checkNativeBoardGolden( kicad10, "v10", "graphics", "graphics", 0, 0 );
509}
510
511
512BOOST_AUTO_TEST_CASE( DroppedGraphicsMatchNativeKicad9Golden )
513{
514 checkNativeBoardGolden( kicad9, "v9", "graphics", "graphics-drop", 2, 0, true );
515}
516
517
518BOOST_AUTO_TEST_CASE( DropApproximationsKeepsNativeKicad10GraphicsGolden )
519{
520 checkNativeBoardGolden( kicad10, "v10", "graphics", "graphics", 0, 0, true );
521}
522
523
524// A standalone library footprint never reaches the board pass, so the footprint-scoped subset of
525// the rule table gets its own native reference.
526static void checkNativeFootprintGolden( const DOWNGRADE_TARGET& aTarget, const std::string& aRelease,
527 const std::string& aReference, bool aDropInsteadOfApproximate = false )
528{
529 const std::string fixture = KI_TEST::GetPcbnewTestDataDir() + "../downgrade/golden/";
530 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_native_fp_golden", "" );
531 const wxString dest = ( tmp.GetPath() / "metadata.kicad_mod" ).wstring();
532 const wxString src = wxString::FromUTF8( fixture + "current/metadata.pretty/metadata.kicad_mod" );
533
534 BOOST_REQUIRE( DowngradeFootprintFileToTemp( src, dest, aTarget, aDropInsteadOfApproximate )
536
537 const std::string difference = KI_TEST::GoldenFileDifference(
538 fixture + aRelease + "/" + aReference + ".pretty/metadata.kicad_mod", dest.ToStdString() );
539 BOOST_CHECK_MESSAGE( difference.empty(), difference );
540}
541
542
543// A primitive the writer does not handle used to emit an orphan (width ...) and a surplus
544// closing paren, which left the file unbalanced and unloadable in the target.
545static void checkCustomPadArcSurvives( const DOWNGRADE_TARGET& aTarget )
546{
547 SETTINGS_MANAGER settingsManager;
548 const std::string src = KI_TEST::GetPcbnewTestDataDir() + "../downgrade/regressions/custom_pad_arc.kicad_pcb";
549 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_custom_pad_arc", "" );
550 const std::string dest = ( tmp.GetPath() / "out.kicad_pcb" ).string();
552
553 BOOST_REQUIRE( ExportBoardToOlderVersion( src, dest, aTarget, report ) );
554
555 // The primitive is an elliptical arc, so the target gets the approximating polygon. The
556 // report has to say so; it used to predict no loss at all.
558
559 const std::string out = KI_TEST::ReadGoldenText( dest );
560 BOOST_CHECK_MESSAGE( out.find( "gr_poly" ) != std::string::npos,
561 "the custom pad's primitive was dropped instead of approximated" );
562
563 int depth = 0;
564 bool inString = false;
565
566 for( size_t ii = 0; ii < out.size(); ++ii )
567 {
568 if( out[ii] == '"' && ( ii == 0 || out[ii - 1] != '\\' ) )
569 inString = !inString;
570 else if( !inString && out[ii] == '(' )
571 ++depth;
572 else if( !inString && out[ii] == ')' )
573 --depth;
574 }
575
576 BOOST_CHECK_MESSAGE( depth == 0, "exported board is unbalanced by " + std::to_string( depth ) );
577
579 std::unique_ptr<BOARD> reloaded( io.LoadBoard( dest, nullptr ) );
580 BOOST_REQUIRE( reloaded != nullptr );
581 BOOST_REQUIRE_EQUAL( reloaded->Footprints().size(), 1 );
582 BOOST_REQUIRE_EQUAL( reloaded->Footprints().front()->Pads().size(), 1 );
583 BOOST_CHECK_EQUAL( reloaded->Footprints().front()->Pads().front()->GetPrimitives( F_Cu ).size(), 1 );
584}
585
586
587BOOST_AUTO_TEST_CASE( CustomPadArcSurvivesExportToKicad9 )
588{
590}
591
592
593BOOST_AUTO_TEST_CASE( CustomPadArcSurvivesExportToKicad10 )
594{
596}
597
598
599BOOST_AUTO_TEST_CASE( ModernBoardMetadataMatchesNativeKicad9Golden )
600{
601 checkNativeBoardGolden( kicad9, "v9", "modern_metadata", "modern_metadata", 13, 0 );
602}
603
604
605BOOST_AUTO_TEST_CASE( ModernBoardMetadataMatchesNativeKicad10Golden )
606{
607 checkNativeBoardGolden( kicad10, "v10", "modern_metadata", "modern_metadata", 12, 0 );
608}
609
610
611BOOST_AUTO_TEST_CASE( BoardMetadataMatchesNativeKicad9Golden )
612{
613 checkNativeBoardGolden( kicad9, "v9", "metadata", "metadata", 6, 0 );
614}
615
616
617BOOST_AUTO_TEST_CASE( BoardMetadataMatchesNativeKicad10Golden )
618{
619 checkNativeBoardGolden( kicad10, "v10", "metadata", "metadata", 0, 0 );
620}
621
622
623BOOST_AUTO_TEST_CASE( FootprintMatchesNativeKicad9Golden )
624{
625 checkNativeFootprintGolden( kicad9, "v9", "metadata" );
626}
627
628
629BOOST_AUTO_TEST_CASE( FootprintMatchesNativeKicad10Golden )
630{
631 checkNativeFootprintGolden( kicad10, "v10", "metadata" );
632}
633
634
635BOOST_AUTO_TEST_CASE( RequiredReleaseLookupFailsForMissingId )
636{
637 BOOST_CHECK( KI_TEST::RequireDowngradeTarget( wxT( "9.0" ) ).m_id == wxT( "9.0" ) );
638 BOOST_CHECK( KI_TEST::RequireDowngradeTarget( wxT( "10.0" ) ).m_id == wxT( "10.0" ) );
639 BOOST_CHECK_THROW( KI_TEST::RequireDowngradeTarget( wxT( "missing-release" ) ), std::runtime_error );
640}
641
642
643// Guard the oracle itself: normalization must not hide meaningful design changes.
644BOOST_AUTO_TEST_CASE( GoldenComparisonDetectsDesignAndSettingsChanges )
645{
646 SEXPR::PARSER parser;
647 const std::string golden = "(kicad_pcb (version 20241229) (generator_version \"9.0\") "
648 "(setup (pad_to_mask_clearance 0.05)) "
649 "(pad \"1\" (at 100 100 30) (size 2 1) (net 1 \"SIGNAL\")))";
650 auto expected = parser.Parse( golden );
651
652 for( const auto& [from, to] : std::vector<std::pair<std::string, std::string>>{ { "100 100", "0 0" },
653 { "(size 2 1)", "(size 1 2)" },
654 { "SIGNAL", "GND" },
655 { "0.05", "0.1" },
656 { "100 100 30", "100 100 31" },
657 { "20241229", "20260206" } } )
658 {
659 std::string changed = golden;
660 changed.replace( changed.find( from ), from.size(), to );
661 auto actual = parser.Parse( changed );
662 BOOST_CHECK( !KI_TEST::GoldenSexprDifference( *expected, *actual ).empty() );
663 }
664
665 auto metadataOnly = parser.Parse( "(kicad_pcb (version 20241229) (generator_version \"10.99\") "
666 "(setup (pad_to_mask_clearance 0.05)) "
667 "(pad \"1\" (at 100.000001 99.999999 30) (size 2 1) (net 1 \"SIGNAL\")))" );
668 BOOST_CHECK( KI_TEST::GoldenSexprDifference( *expected, *metadataOnly ).empty() );
669
670 const std::string plotGolden = "(kicad_pcb (setup (pcbplotparams (hpglpennumber 1) (hpglpenspeed 20) "
671 "(hpglpendiameter 15) (plotinvisibletext no))))";
672 auto plotExpected = parser.Parse( plotGolden );
673 auto modernPlot = parser.Parse( "(kicad_pcb (setup (pcbplotparams (pngdpi 300) (pngantialias yes))))" );
674 BOOST_CHECK( KI_TEST::GoldenSexprDifference( *plotExpected, *modernPlot ).empty() );
675
676 for( const auto& [from, to] :
677 std::vector<std::pair<std::string, std::string>>{ { "hpglpennumber 1", "hpglpennumber 2" },
678 { "hpglpenspeed 20", "hpglpenspeed 30" },
679 { "hpglpendiameter 15", "hpglpendiameter 16" },
680 { "plotinvisibletext no", "plotinvisibletext yes" } } )
681 {
682 std::string changed = plotGolden;
683 changed.replace( changed.find( from ), from.size(), to );
684 auto nonDefault = parser.Parse( changed );
685 BOOST_CHECK( !KI_TEST::GoldenSexprDifference( *nonDefault, *modernPlot ).empty() );
686 }
687}
688
689
690BOOST_AUTO_TEST_CASE( HistoricalPlotSettingsDoNotUseCurrentFormatters )
691{
692 BOARD board;
693 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_historical_plot", "" );
694
695 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
696 {
697 const wxString path = ( tmp.GetPath() / ( target.m_id.ToStdString() + ".kicad_pcb" ) ).string();
698 SaveBoardForTarget( &board, path, target );
699 std::ifstream file( path.ToStdString() );
700 std::string text( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
701 BOOST_CHECK( text.find( "(pngdpi " ) == std::string::npos );
702 BOOST_CHECK( text.find( "(pngantialias " ) == std::string::npos );
703
704 if( target.m_id == wxT( "9.0" ) )
705 {
706 BOOST_CHECK( text.find( "(hpglpennumber 1)" ) != std::string::npos );
707 BOOST_CHECK( text.find( "(hpglpenspeed 20)" ) != std::string::npos );
708 BOOST_CHECK( text.find( "(hpglpendiameter 15.000000)" ) != std::string::npos );
709 BOOST_CHECK( text.find( "(plotinvisibletext no)" ) != std::string::npos );
710 }
711 }
712}
713
714
715BOOST_AUTO_TEST_CASE( HistoricalCustomPaperUsesReleasePrecision )
716{
717 BOARD board;
719 page.SetWidthMM( 123.456789 );
720 page.SetHeightMM( 100.123456 );
721 board.SetPageSettings( page );
722 KI_TEST::TEMPORARY_DIRECTORY tmp( "kicad_qa_historical_paper", "" );
723
724 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
725 {
726 const wxString path = ( tmp.GetPath() / ( target.m_id.ToStdString() + ".kicad_pcb" ) ).string();
727 SaveBoardForTarget( &board, path, target );
728 std::ifstream file( path.ToStdString() );
729 std::string text( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
730 const std::string expected = target.m_id == wxT( "9.0" ) ? "(paper \"User\" 123.457 100.123)"
731 : "(paper \"User\" 123.456789 100.123456)";
732 BOOST_CHECK( text.find( expected ) != std::string::npos );
733 }
734}
735
736
737BOOST_AUTO_TEST_CASE( UnsupportedPlotSelectionIsReportedAndReset )
738{
739 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
740 {
741 for( bool drop : { false, true } )
742 {
743 BOARD board;
744 PCB_PLOT_PARAMS options = board.GetPlotOptions();
745 options.SetFormat( PLOT_FORMAT::PNG );
746 board.SetPlotOptions( options );
747 COMPATIBILITY_REPORT report = ClassifyBoardForDowngrade( &board, target, drop );
749 BOOST_CHECK( !report.IsBlocked() );
750 BOOST_CHECK( board.GetPlotOptions().GetFormat() == PLOT_FORMAT::PNG );
751 DowngradeBoardInPlace( &board, target, drop );
752 BOOST_CHECK( board.GetPlotOptions().GetFormat() == PLOT_FORMAT::GERBER );
753 BOOST_CHECK( !ClassifyBoardForDowngrade( &board, target, drop ).IsLossy() );
754 }
755 }
756}
757
758
759static std::string historicalTextEffects( const EDA_TEXT& aText, bool aV9, int aControlBits = 0 )
760{
761 STRING_FORMATTER formatter;
762
763 if( aV9 )
764 KICAD_FORMAT::LEGACY::FormatTextV9( &formatter, aText, pcbIUScale, aControlBits );
765 else
766 KICAD_FORMAT::LEGACY::FormatTextV10( &formatter, aText, pcbIUScale, aControlBits );
767
768 return formatter.GetString();
769}
770
771
772BOOST_AUTO_TEST_CASE( HistoricalTextEffectsKeepReleaseHideAndAttributes )
773{
775 text.SetFont( nullptr );
776 text.SetTextSize( VECTOR2I( 1000000, 2000000 ) );
777 text.SetTextThickness( 100000 );
778 text.SetLineSpacing( 1.25 );
779 text.SetItalic( true );
780 text.SetTextColor( KIGFX::COLOR4D( 0.2, 0.4, 0.6, 0.75 ) );
781 text.SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
782 text.SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
783 text.SetMirrored( true );
784 text.SetVisible( false );
785 text.SetHyperlink( wxT( "https://example.com/datasheet" ) );
786
787 const std::string prefix = "(effects(font(size 2 1)(line_spacing 1.25)(thickness 0.1)"
788 "(italic yes)(color 51 102 153 0.75))(justify left bottom mirror)";
789 const std::string href = "(href \"https://example.com/datasheet\"))";
790 BOOST_CHECK_EQUAL( historicalTextEffects( text, true ), prefix + "(hide yes)" + href );
791 BOOST_CHECK_EQUAL( historicalTextEffects( text, false ), prefix + href );
793
794 for( bool v9 : { false, true } )
795 {
796 const std::string omitted = historicalTextEffects( text, v9, ( 1 << 11 ) | ( 1 << 12 ) );
797 BOOST_CHECK( omitted.find( "(color " ) == std::string::npos );
798 BOOST_CHECK( omitted.find( "(href " ) == std::string::npos );
799 BOOST_CHECK( omitted.find( "(justify left bottom mirror)" ) != std::string::npos );
800 }
801
802 BOOST_CHECK_EQUAL( text.GetTextThickness(), 100000 );
803 BOOST_CHECK_EQUAL( text.GetLineSpacing(), 1.25 );
804 BOOST_CHECK( !text.IsVisible() );
805}
806
807
808BOOST_AUTO_TEST_CASE( HistoricalTextThicknessKeepsAutomaticZeroAndExplicitWidths )
809{
811 text.SetFont( nullptr );
812 text.SetTextSize( VECTOR2I( 1000000, 1000000 ) );
813
814 for( bool v9 : { false, true } )
815 {
816 text.SetBold( false );
817 text.SetTextThickness( 0 );
818 BOOST_CHECK( text.GetAutoThickness() );
819 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)))" );
820
821 text.SetTextThickness( 1 );
822 BOOST_CHECK( !text.GetAutoThickness() );
823 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(thickness 0.000001)))" );
824 BOOST_CHECK_EQUAL( text.GetTextThickness(), 1 );
825
826 text.SetTextThickness( 100000 );
827 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(thickness 0.1)))" );
828
829 text.SetBold( true );
830 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(thickness 0.16)(bold yes)))" );
831 BOOST_CHECK_EQUAL( text.GetTextThickness(), 100000 );
832
833 text.SetTextThickness( 0 );
834 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(bold yes)))" );
835 BOOST_CHECK_EQUAL( text.GetTextThickness(), 0 );
836 }
837}
838
839
840BOOST_AUTO_TEST_CASE( HistoricalDefaultTextFormattingKeepsNative10AutomaticWidthPredicate )
841{
843 text.SetFont( nullptr );
844 text.SetMultilineAllowed( false );
845 text.SetTextSize( VECTOR2I( 1000000, 1000000 ) );
846 text.SetAutoThickness( true );
847 text.SetVisible( false );
848 BOOST_CHECK( text.GetAutoThickness() );
850
851 text.SetTextThickness( 1 );
852 BOOST_CHECK( !text.GetAutoThickness() );
854
855 text.SetTextThickness( 0 );
856 BOOST_CHECK( text.GetAutoThickness() );
858
859 text.SetBold( true );
861 text.SetBold( false );
862 text.SetMirrored( true );
864}
865
866
867BOOST_AUTO_TEST_CASE( HistoricalTitleAndStrokeKeepPinnedCommonGrammar )
868{
869 TITLE_BLOCK title;
872 BOOST_CHECK( empty.GetString().empty() );
873
874 title.SetTitle( wxT( "Production" ) );
875 title.SetDate( wxT( "2026-10-08" ) );
876 title.SetRevision( wxT( "A" ) );
877 title.SetCompany( wxT( "KiCad" ) );
878 title.SetComment( 0, wxT( "First" ) );
879 title.SetComment( 8, wxT( "Last" ) );
880 STRING_FORMATTER metadata;
881 KICAD_FORMAT::LEGACY::FormatTitle( &metadata, title );
882 BOOST_CHECK_EQUAL( metadata.GetString(), "(title_block(title \"Production\")(date \"2026-10-08\")(rev \"A\")"
883 "(company \"KiCad\")(comment 1 \"First\")(comment 9 \"Last\"))" );
884 BOOST_CHECK( title.GetComment( 8 ) == wxT( "Last" ) );
885
886 for( const auto& [style, token] :
887 std::vector<std::pair<LINE_STYLE, std::string>>{ { LINE_STYLE::DEFAULT, "default" },
888 { LINE_STYLE::SOLID, "solid" },
889 { LINE_STYLE::DASH, "dash" },
890 { LINE_STYLE::DOT, "dot" },
891 { LINE_STYLE::DASHDOT, "dash_dot" },
892 { LINE_STYLE::DASHDOTDOT, "dash_dot_dot" } } )
893 {
894 STROKE_PARAMS stroke( 200000, style );
895 STRING_FORMATTER plain;
897 BOOST_CHECK_EQUAL( plain.GetString(), "(stroke (width 0.2) (type " + token + "))" );
898 stroke.SetColor( KIGFX::COLOR4D( 0.2, 0.4, 0.6, 0.75 ) );
899 STRING_FORMATTER colored;
901 BOOST_CHECK_EQUAL( colored.GetString(), "(stroke (width 0.2) (type " + token + ") (color 51 102 153 0.75))" );
902 }
903}
904
905
906BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesKeepEmptyReleasePolicy )
907{
908 EMBEDDED_FILES files;
909 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
910 file->name = wxT( "metadata.bin" );
912 file->data_hash = "metadata-only";
913 files.AddFile( file );
914
915 STRING_FORMATTER invalidData;
916 BOOST_CHECK_THROW( KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( invalidData, files, true ), IO_ERROR );
917
918 STRING_FORMATTER reference;
919 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( reference, files, false );
920 BOOST_CHECK_EQUAL( reference.GetString(),
921 "(embedded_files (file (name \"metadata.bin\")(type other)(checksum \"metadata-only\")))" );
922
923 for( bool writeData : { false, true } )
924 {
925 STRING_FORMATTER skipped;
926 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( skipped, files, writeData );
927 BOOST_CHECK_EQUAL( skipped.GetString(), "(embedded_files )" );
928 }
929
930 BOOST_REQUIRE( files.GetEmbeddedFile( wxT( "metadata.bin" ) ) );
931 BOOST_CHECK( file->compressedEncodedData.empty() );
932 BOOST_CHECK_EQUAL( file->data_hash, "metadata-only" );
933}
934
935
936BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesKeepValidDataAndNativeChunkDelimiters )
937{
938 EMBEDDED_FILES files;
939 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
940 file->name = wxT( "payload.bin" );
942
943 for( int ii = 0; ii < 4096; ++ii )
944 file->decompressedData.push_back( static_cast<char>( ( ii * 131 + ii / 251 ) % 256 ) );
945
947 BOOST_REQUIRE_GT( file->compressedEncodedData.size(), 76 );
948 files.AddFile( file );
949 const std::string encoded = file->compressedEncodedData;
950 const std::string hash = file->data_hash;
951
952 std::string expected = "(embedded_files (file (name \"payload.bin\")(type other)(data";
953
954 for( size_t first = 0; first < encoded.size(); first += 76 )
955 {
956 const std::string chunk = encoded.substr( first, 76 );
957 expected += "\n";
958 expected += first ? " " : "|";
959 expected += chunk;
960
961 if( first + chunk.size() == encoded.size() )
962 expected += "|";
963
964 expected += "\n";
965 }
966
967 expected += ")(checksum \"" + hash + "\")))";
968
969 for( bool v9 : { false, true } )
970 {
971 STRING_FORMATTER serialized;
972
973 if( v9 )
974 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( serialized, files, true );
975 else
976 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( serialized, files, true );
977
978 BOOST_CHECK_EQUAL( serialized.GetString(), expected );
979 STRING_FORMATTER reference;
980
981 if( v9 )
982 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( reference, files, false );
983 else
984 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( reference, files, false );
985
986 BOOST_CHECK_EQUAL( reference.GetString(),
987 "(embedded_files (file (name \"payload.bin\")(type other)(checksum \"" + hash + "\")))" );
988 }
989
990 BOOST_CHECK_EQUAL( file->compressedEncodedData, encoded );
991 BOOST_CHECK_EQUAL( file->data_hash, hash );
992}
993
994
995static std::string historicalEmbeddedFiles( const EMBEDDED_FILES& aFiles, bool aV9, bool aWriteData )
996{
997 STRING_FORMATTER formatter;
998
999 if( aV9 )
1000 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( formatter, aFiles, aWriteData );
1001 else
1002 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( formatter, aFiles, aWriteData );
1003
1004 return formatter.GetString();
1005}
1006
1007
1008static std::string releaseEmbeddedChecksum( const std::vector<char>& aPayload )
1009{
1011 hash.addDataV1( reinterpret_cast<const uint8_t*>( aPayload.data() ), aPayload.size() );
1012 return hash.digest().ToString();
1013}
1014
1015
1016BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesRestoreReleaseChecksumsForAllTailLengths )
1017{
1018 std::vector<size_t> sizes = { 4095, 4096, 4097 };
1019
1020 for( size_t size = 0; size <= 32; ++size )
1021 sizes.push_back( size );
1022
1023 for( size_t size : sizes )
1024 {
1025 EMBEDDED_FILES files;
1026 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
1027 file->name = wxT( "payload.bin" );
1028
1029 for( size_t ii = 0; ii < size; ++ii )
1030 file->decompressedData.push_back( static_cast<char>( ( ii * 131 + ii / 251 ) % 256 ) );
1031
1033 files.AddFile( file );
1034 const auto original = *file;
1035 const std::string checksum = releaseEmbeddedChecksum( original.decompressedData );
1036
1037 if( size % 16 != 0 )
1038 BOOST_CHECK_NE( checksum, original.data_hash );
1039
1040 for( bool v9 : { false, true } )
1041 {
1042 for( bool writeData : { false, true } )
1043 {
1044 BOOST_TEST_CONTEXT( "bytes=" << size << " v9=" << v9 << " data=" << writeData )
1045 {
1046 const std::string output = historicalEmbeddedFiles( files, v9, writeData );
1047 BOOST_CHECK( output.find( "(checksum \"" + checksum + "\")" ) != std::string::npos );
1048 BOOST_CHECK_EQUAL( output.find( "(data" ) != std::string::npos, writeData );
1049
1050 if( !writeData )
1051 {
1052 BOOST_CHECK_EQUAL( output, "(embedded_files (file (name \"payload.bin\")(type other)"
1053 "(checksum \""
1054 + checksum + "\")))" );
1055 }
1056
1057 BOOST_CHECK( file->decompressedData == original.decompressedData );
1058 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1059 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1060 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1061 }
1062 }
1063 }
1064 }
1065}
1066
1067
1068BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesDecodeEncodedOnlyAttachmentsWithoutMutatingSource )
1069{
1070 for( size_t size : { 0u, 1u, 13u, 17u, 31u, 4097u } )
1071 {
1072 EMBEDDED_FILES files;
1073 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
1074 file->name = wxT( "encoded.bin" );
1075 file->decompressedData.assign( size, 'x' );
1076 const std::string checksum = releaseEmbeddedChecksum( file->decompressedData );
1078 file->decompressedData.clear();
1079 files.AddFile( file );
1080 const auto original = *file;
1081
1082 for( bool v9 : { false, true } )
1083 {
1084 for( bool writeData : { false, true } )
1085 {
1086 BOOST_TEST_CONTEXT( "bytes=" << size << " v9=" << v9 << " data=" << writeData )
1087 {
1088 const std::string output = historicalEmbeddedFiles( files, v9, writeData );
1089 BOOST_CHECK( output.find( "(checksum \"" + checksum + "\")" ) != std::string::npos );
1090 BOOST_CHECK_EQUAL( output.find( "(data" ) != std::string::npos, writeData );
1091 BOOST_CHECK( file->decompressedData.empty() );
1092 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1093 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1094 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1095 }
1096 }
1097 }
1098 }
1099}
1100
1101
1102BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesRejectMalformedEncodedOnlyAttachments )
1103{
1104 for( bool badChecksum : { false, true } )
1105 {
1106 EMBEDDED_FILES files;
1107 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
1108 file->name = wxT( "invalid.bin" );
1109 file->decompressedData.assign( 13, 'x' );
1111 file->decompressedData.clear();
1112
1113 if( badChecksum )
1114 file->data_hash = "invalid-checksum";
1115 else
1116 file->compressedEncodedData = "invalid-base64";
1117
1118 files.AddFile( file );
1119 const auto original = *file;
1120
1121 for( bool v9 : { false, true } )
1122 {
1123 for( bool writeData : { false, true } )
1124 {
1125 BOOST_CHECK_THROW( historicalEmbeddedFiles( files, v9, writeData ), IO_ERROR );
1126 BOOST_CHECK( file->decompressedData.empty() );
1127 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1128 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1129 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1130 }
1131 }
1132 }
1133}
1134
1135
1136BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesRestoreCommittedWorksheetChecksums )
1137{
1138 SETTINGS_MANAGER settingsManager;
1139 const std::string source = KI_TEST::GetPcbnewTestDataDir() + "issue22102.kicad_pcb";
1140 const std::string sourceBytes = KI_TEST::ReadGoldenText( source );
1142 std::unique_ptr<BOARD> board( io.LoadBoard( source ) );
1143 BOOST_REQUIRE( board );
1144
1145 // The fixture was committed in 2025 before the MMH3 tail-byte correction.
1146 const std::vector<std::pair<wxString, std::string>> checksums = {
1147 { wxT( "SchematicTemplate.kicad_wks" ), "6003EC72C5978E83C182073E67278FCF" },
1148 { wxT( "SchematicTemplateEmpty.kicad_wks" ), "0D054F65E3E0563620A9E16BBBB777DD" }
1149 };
1150
1151 for( const auto& [name, checksum] : checksums )
1152 {
1153 const auto* file = board->GetEmbeddedFile( name );
1154 BOOST_REQUIRE( file );
1155 BOOST_REQUIRE( !file->decompressedData.empty() );
1156 BOOST_CHECK_NE( file->data_hash, checksum );
1157 BOOST_CHECK_EQUAL( releaseEmbeddedChecksum( file->decompressedData ), checksum );
1158 const auto original = *file;
1159
1160 for( bool v9 : { false, true } )
1161 {
1162 for( bool writeData : { false, true } )
1163 {
1164 const std::string output = historicalEmbeddedFiles( *board, v9, writeData );
1165 BOOST_CHECK( output.find( "(name \"" + name.ToStdString() + "\")" ) != std::string::npos );
1166 BOOST_CHECK( output.find( "(checksum \"" + checksum + "\")" ) != std::string::npos );
1167 BOOST_CHECK( output.find( "(checksum \"" + original.data_hash + "\")" ) == std::string::npos );
1168 BOOST_CHECK_EQUAL( output.find( "(data" ) != std::string::npos, writeData );
1169 BOOST_CHECK( file->decompressedData == original.decompressedData );
1170 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1171 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1172 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1173 }
1174 }
1175 }
1176
1178}
1179
1180
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
COMPATIBILITY_REPORT ClassifyBoardForDowngrade(const BOARD *aBoard, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a board would lose if exported to an older KiCad.
void SaveBoardForTarget(BOARD *aBoard, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a board stamped for the target, using the writer for that version.
bool ExportBoardToOlderVersion(const wxString &aSrcFile, const wxString &aDestFile, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport, wxString *aUnsupportedToken, const wxString &aVariant, bool aDropInsteadOfApproximate)
Load a board, downgrade the copy, and write it stamped for the target.
DOWNGRADE_FILE_RESULT DowngradeFootprintFileToTemp(const wxString &aSrcFile, const wxString &aTmpFile, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Load one .kicad_mod, classify it against the rule table, and write the downgraded copy to aTmpFile.
void DowngradeBoardInPlace(BOARD *aBoard, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Approximate or remove, in place, what the target cannot represent.
General utilities for PCB file IO for QA programs.
@ BS_ITEM_TYPE_DIELECTRIC
int GetPPI() const
int GetLegacyPPI() const
Recompute the PPI the way it was computed before the pixels/cm truncation fix.
std::map< PCB_LAYER_ID, ZONE_LAYER_PROPERTIES > m_ZoneLayerProperties
DRILL_SYMBOL_PROFILE & GetDrillSymbolProfile()
BOARD_STACKUP & GetStackupDescriptor()
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:415
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
Manage one layer needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
void SetPlotOptions(const PCB_PLOT_PARAMS &aOptions)
Definition board.h:1022
void SetVariantNames(const std::vector< wxString > &aNames)
Definition board.h:533
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1521
void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition board.h:1019
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:1021
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1298
const DRAWINGS & Drawings() const
Definition board.h:466
What an export to a given target will keep, approximate, drop, or block.
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
int Count(DOWNGRADE_BUCKET aBucket) const
void SetName(const wxString &aName)
const KIID m_Uuid
Definition eda_item.h:599
void SetCustomProperty(const wxString &aKey, const wxString &aValue)
Definition eda_item.h:255
void SetEndEndingStyle(LINE_ENDING_STYLE aStyle)
Definition eda_shape.h:187
void SetFillMode(FILL_T aFill)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
EMBEDDED_FILE * GetEmbeddedFile(const wxString &aName) const
Returns the embedded file with the given name or nullptr if it does not exist.
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...
void SetDNP(bool aDNP)
Definition footprint.h:243
void SetExcludedFromSim(bool aExclude=true)
Definition footprint.h:1055
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1244
EXTRUDED_3D_BODY & EnsureExtrudedBody()
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
void SetUnitInfo(const std::vector< FP_UNIT_INFO > &aUnits)
Definition footprint.h:1024
FOOTPRINT_VARIANT * AddVariant(const wxString &aVariantName)
Add a new variant with the given name.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
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
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
Handle the data for a net.
Definition netinfo.h:50
void SetNetChain(const wxString &aNetChain)
Definition netinfo.h:123
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:156
MASK_LAYER_PROPS & FrontOuterLayers()
Definition padstack.h:379
void SetUnconnectedLayerMode(UNCONNECTED_LAYER_MODE aMode)
Definition padstack.h:374
DRILL_PROPS & Drill()
Definition padstack.h:356
MASK_LAYER_PROPS & BackOuterLayers()
Definition padstack.h:382
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:196
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition pad.h:142
void AddPrimitive(PCB_LAYER_ID aLayer, PCB_SHAPE *aPrimitive)
Add item to the custom shape primitives list.
Definition pad.cpp:3728
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
void SetWidthMM(double aWidthInMM)
Definition page_info.h:135
void SetHeightMM(double aHeightInMM)
Definition page_info.h:140
void SetWidth(int aWidth)
void SetHeight(int aHeight)
void SetLayer(PCB_LAYER_ID aLayer) override
Set the drawing layer for the barcode and its text.
void SetText(const wxString &aText)
Set the barcode content text to encode.
A geometric constraint between board items (issue #2329).
void AddMember(const KIID &aItem, CONSTRAINT_ANCHOR aAnchor=CONSTRAINT_ANCHOR::WHOLE, int aIndex=-1)
A drill chart placed on the board, kept in step with the holes.
Turns on drill symbols at the holes, for one layer.
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
A PCB_IO derivation for saving and loading Pcbnew s-expression formatted files.
void SaveBoard(const wxString &aFileName, BOARD &aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
Parameters and options when plotting/printing a board.
PLOT_FORMAT GetFormat() const
void SetFormat(PLOT_FORMAT aFormat)
A PCB_POINT is a 0-dimensional point that is used to mark a position on a PCB, or more usually a foot...
Definition pcb_point.h:39
Object to handle a bitmap image that can be inserted in a PCB.
void SetEllipseCenter(const VECTOR2I &aPt) override
void SetCornerRadius(int aCornerRadius) override
void SetWidth(int aWidth) override
void SetEllipseStartAngle(const EDA_ANGLE &aA) override
void SetEllipseEndAngle(const EDA_ANGLE &aA) override
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetEllipseMinorRadius(int aR) override
void SetStart(const VECTOR2I &aStart) override
void SetEllipseMajorRadius(int aR) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
A microvia stack: several single hop microvias, plus connecting traces for staggered stacks,...
const PADSTACK & Padstack() const
Definition pcb_track.h:427
void SetDrill(int aDrill)
Definition pcb_track.h:798
void SetLayerPair(PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer)
For a via m_layer contains the top layer, the other layer is in m_bottomLayer/.
void SetViaType(VIATYPE aViaType)
Definition pcb_track.h:419
void SetWidth(int aWidth) override
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
const BITMAP_BASE & GetImage() const
Get the underlying image.
double GetImageScale() const
std::unique_ptr< SEXPR > Parse(const std::string &aString)
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:430
const std::string & GetString()
Definition richio.h:453
Simple container to manage line stroke parameters.
void SetColor(const KIGFX::COLOR4D &aColor)
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:38
void SetRevision(const wxString &aRevision)
Definition title_block.h:78
void SetComment(int aIdx, const wxString &aComment)
Definition title_block.h:98
void SetTitle(const wxString &aTitle)
Definition title_block.h:55
const wxString & GetComment(int aIdx) const
void SetCompany(const wxString &aCompany)
Definition title_block.h:88
void SetDate(const wxString &aDate)
Set the date field, and defaults to the current time and date.
Definition title_block.h:68
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool AppendCorner(const VECTOR2I &aPosition, int aHoleIdx, bool aAllowDuplication=false)
Add a new corner to the zone outline (to the main outline or a hole)
Definition zone.cpp:1464
ZONE_LAYER_PROPERTIES & LayerProperties(PCB_LAYER_ID aLayer)
Definition zone.h:146
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:640
@ LOWER
Approximated down to a simpler equivalent.
@ DROP
No equivalent, left out.
static bool empty(const wxTextEntryBase *aCtrl)
@ CONVERTED
The temp file holds the downgraded copy.
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
@ 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
std::string source
@ Dwgs_User
Definition layer_ids.h:103
@ In2_Cu
Definition layer_ids.h:63
@ F_SilkS
Definition layer_ids.h:96
@ In1_Cu
Definition layer_ids.h:62
@ F_Cu
Definition layer_ids.h:60
void FormatTitle(OUTPUTFORMATTER *aOut, const TITLE_BLOCK &aTitle)
bool IsDefaultTextV10(const EDA_TEXT &aText)
void FormatEmbeddedFilesV9(OUTPUTFORMATTER &aOut, const EMBEDDED_FILES &aFiles, bool aWriteData)
void FormatTextV9(OUTPUTFORMATTER *aOut, const EDA_TEXT &aText, const EDA_IU_SCALE &aScale, int aControlBits)
void FormatTextV10(OUTPUTFORMATTER *aOut, const EDA_TEXT &aText, const EDA_IU_SCALE &aScale, int aControlBits)
void FormatStroke(OUTPUTFORMATTER *aOut, const STROKE_PARAMS &aStroke, const EDA_IU_SCALE &aScale)
constexpr int OMIT_HIDE
void FormatEmbeddedFilesV10(OUTPUTFORMATTER &aOut, const EMBEDDED_FILES &aFiles, bool aWriteData)
Test utilities for timestamps.
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
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)
const DOWNGRADE_TARGET & RequireDowngradeTarget(const wxString &aId)
std::string GoldenSexprDifference(const SEXPR::SEXPR &aExpected, const SEXPR::SEXPR &aActual, const std::string &aPath="", const std::string &aParent="", size_t aIndex=0)
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.
@ PRESSFIT
a PTH with a hole diameter with tight tolerances for press fit pin
Definition padstack.h:122
BARCODE class definition.
@ HORIZONTAL
A segment (or two points) is horizontal.
Class to handle a set of BOARD_ITEMs.
One line in the compatibility report.
An older KiCad release we can export a project back to.
int m_boardVersion
SEXPR_BOARD_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
std::optional< bool > is_capped
True if the drill hole should be capped.
Definition padstack.h:279
std::optional< bool > is_filled
True if the drill hole should be filled completely.
Definition padstack.h:278
std::optional< bool > has_covering
True if the pad on this side should have covering.
Definition padstack.h:261
std::optional< bool > has_plugging
True if the drill hole should be plugged on this side.
Definition padstack.h:262
std::optional< VECTOR2I > hatching_offset
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static void checkBoardTransformCoverage(const COMPATIBILITY_REPORT &aReport, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
static void checkExportUsesOnlyKnownTokens(const DOWNGRADE_TARGET &aTarget, const std::string &aTokenFileName)
static void checkNativeFootprintGolden(const DOWNGRADE_TARGET &aTarget, const std::string &aRelease, const std::string &aReference, bool aDropInsteadOfApproximate=false)
static void checkNativeBoardGolden(const DOWNGRADE_TARGET &aTarget, const std::string &aRelease, const std::string &aBoard, const std::string &aReference, size_t aExpectedDrops, size_t aExpectedLowerings, bool aDropInsteadOfApproximate=false)
static void checkTransformedBoardUsesOnlyKnownTokens(const DOWNGRADE_TARGET &aTarget, const std::string &aTokenFileName)
static std::string releaseEmbeddedChecksum(const std::vector< char > &aPayload)
BOOST_AUTO_TEST_CASE(BoardToKicad9UsesOnlyKnownTokens)
static std::string historicalTextEffects(const EDA_TEXT &aText, bool aV9, int aControlBits=0)
static PCB_REFERENCE_IMAGE * addTransformFeatures(BOARD &aBoard)
static void checkCustomPadArcSurvives(const DOWNGRADE_TARGET &aTarget)
static std::string historicalEmbeddedFiles(const EMBEDDED_FILES &aFiles, bool aV9, bool aWriteData)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
VECTOR3I expected(15, 30, 45)
static const DOWNGRADE_TARGET & kicad10
static const DOWNGRADE_TARGET & kicad9
static void checkExportUsesOnlyKnownTokens(const DOWNGRADE_TARGET &aTarget, const std::string &aTokenFileName)
BOOST_TEST_CONTEXT("Test Clearance")
int actual
BOOST_CHECK_EQUAL(result, "25.4")
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_DRILL_CHART_T
class PCB_DRILL_CHART, a live drill chart derived from PCB_TABLE
Definition typeinfo.h:239
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708