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>
39#include <pcb_3d_body.h>
44#include <mmh3_hash.h>
45#include <netinfo.h>
46#include <pad.h>
47#include <pcb_barcode.h>
48#include <pcb_group.h>
49#include <pcb_grid_item.h>
50#include <pcb_drill_chart.h>
51#include <pcb_drill_map.h>
52#include <pcb_point.h>
53#include <pcb_reference_image.h>
54#include <pcb_shape.h>
55#include <pcb_table.h>
56#include <pcb_tablecell.h>
57#include <pcb_textbox.h>
58#include <pcb_track.h>
60#include <zone.h>
62#include <downgrade_scan.h>
63#include <downgrade_target.h>
69#include <qa_utils/file_utils.h>
70
71
72BOOST_AUTO_TEST_SUITE( DowngradeOracle )
73
74static const DOWNGRADE_TARGET& kicad9 = KI_TEST::RequireDowngradeTarget( wxT( "9.0" ) );
75static const DOWNGRADE_TARGET& kicad10 = KI_TEST::RequireDowngradeTarget( wxT( "10.0" ) );
76
77
78static void checkExportUsesOnlyKnownTokens( const DOWNGRADE_TARGET& aTarget, const std::string& aTokenFileName )
79{
80 SETTINGS_MANAGER settingsManager;
81
82 const std::string src = KI_TEST::GetPcbnewTestDataDir() + "via_off_center.kicad_pcb";
83
84 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_downgrade_oracle", "" );
85 const std::string dest = ( tmp.Path() / "out.kicad_pcb" ).string();
86
88 BOOST_REQUIRE( ExportBoardToOlderVersion( src, dest, aTarget, report ) );
89
90 std::ifstream file( dest );
91 std::string content( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
92
93 const std::set<std::string> known =
94 KI_TEST::LoadTokenSet( KI_TEST::GetPcbnewTestDataDir() + "../downgrade/" + aTokenFileName );
95 BOOST_REQUIRE( !known.empty() );
96
97 std::vector<std::string> unsupported = KI_TEST::UnknownSexprHeads( content, known );
98
99 if( !unsupported.empty() )
100 {
101 std::string msg = "Downgraded board uses tokens " + aTarget.m_name.ToStdString() + " cannot parse:";
102
103 for( const std::string& token : unsupported )
104 msg += " " + token;
105
106 BOOST_ERROR( msg );
107 }
108}
109
110
111BOOST_AUTO_TEST_CASE( BoardToKicad9UsesOnlyKnownTokens )
112{
113 checkExportUsesOnlyKnownTokens( kicad9, "kicad9_board_tokens.txt" );
114}
115
116
117BOOST_AUTO_TEST_CASE( BoardToKicad10UsesOnlyKnownTokens )
118{
119 checkExportUsesOnlyKnownTokens( kicad10, "kicad10_board_tokens.txt" );
120}
121
122
124{
125 NETINFO_ITEM* net = new NETINFO_ITEM( &aBoard, wxT( "TRANSFORM_SIGNAL" ) );
126 net->SetNetChain( wxT( "TRANSFORM_CHAIN" ) );
127 aBoard.Add( net );
128
129 FOOTPRINT* fp = new FOOTPRINT( &aBoard );
130 fp->EnsureBody().MutableExtruded()->m_height = pcbIUScale.mmToIU( 5 );
131 fp->SetUnitInfo( { { wxT( "A" ), { wxT( "1" ) } }, { wxT( "B" ), { wxT( "2" ) } } } );
133 fp->AddVariant( wxT( "alternate" ) )->SetDNP( true );
134 fp->SetExcludedFromSim( true );
135 fp->SetCustomProperty( wxT( "Manufacturing owner" ), wxT( "QA" ) );
136 aBoard.SetVariantNames( { wxT( "alternate" ) } );
137 aBoard.Add( fp );
138
139 PAD* pad = new PAD( fp );
140 pad->SetNumber( wxT( "1" ) );
141 pad->SetNet( net );
142 pad->SetProperty( PAD_PROP::PRESSFIT );
143 pad->SetSimElectricalType( PAD_SIM_ELECTRICAL_TYPE::SOURCE );
144 pad->SetPadToDieDelay( 1000 );
145 pad->Padstack().FrontPostMachining().mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE;
146 pad->Padstack().FrontPostMachining().size = pcbIUScale.mmToIU( 1.2 );
147 pad->Padstack().FrontPostMachining().depth = pcbIUScale.mmToIU( 0.2 );
148 fp->Add( pad );
149
150 PAD* customPad = new PAD( fp );
151 customPad->SetNumber( wxT( "2" ) );
152 customPad->SetShape( F_Cu, PAD_SHAPE::CUSTOM );
153 PCB_SHAPE* primitive = new PCB_SHAPE( customPad, SHAPE_T::RECTANGLE );
154 primitive->SetStart( VECTOR2I( -1000000, -1000000 ) );
155 primitive->SetEnd( VECTOR2I( 1000000, 1000000 ) );
156 primitive->SetCornerRadius( pcbIUScale.mmToIU( 0.3 ) );
157 primitive->SetFillMode( FILL_T::FILLED_SHAPE );
158 customPad->AddPrimitive( F_Cu, primitive );
159 fp->Add( customPad );
160
161 aBoard.Add( new PCB_GRID_ITEM( &aBoard ) );
162 PCB_DRILL_CHART* chart = new PCB_DRILL_CHART( &aBoard );
163 chart->SetLayer( Dwgs_User );
164 aBoard.Add( chart );
165 PCB_DRILL_MAP* map = new PCB_DRILL_MAP( &aBoard );
166 map->SetLayer( Dwgs_User );
167 aBoard.Add( map );
168 aBoard.GetDesignSettings().GetDrillSymbolProfile().SetName( wxT( "QA symbols" ) );
169
170 for( PCB_GENERATOR* generator : { static_cast<PCB_GENERATOR*>( new PCB_VIA_STITCH( &aBoard ) ),
171 static_cast<PCB_GENERATOR*>( new PCB_VIA_STACK( &aBoard ) ) } )
172 {
173 aBoard.Add( generator );
174 PCB_VIA* via = new PCB_VIA( &aBoard );
175 via->SetWidth( F_Cu, pcbIUScale.mmToIU( 0.6 ) );
176 via->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
177 aBoard.Add( via );
178 generator->AddItem( via );
179 }
180
181 for( BOARD_ITEM_CONTAINER* parent :
182 { static_cast<BOARD_ITEM_CONTAINER*>( &aBoard ), static_cast<BOARD_ITEM_CONTAINER*>( fp ) } )
183 {
184 PCB_SHAPE* ended = new PCB_SHAPE( parent, SHAPE_T::SEGMENT );
185 ended->SetStart( VECTOR2I( 0, 0 ) );
186 ended->SetEnd( VECTOR2I( 10000000, 0 ) );
187 ended->SetWidth( 200000 );
188 ended->SetLayer( F_SilkS );
190 parent->Add( ended );
192 constraint->AddMember( ended->m_Uuid );
193 parent->Add( constraint );
194
196 {
197 PCB_SHAPE* ellipse = new PCB_SHAPE( parent, type );
198 ellipse->SetEllipseCenter( VECTOR2I( 0, 0 ) );
199 ellipse->SetEllipseMajorRadius( pcbIUScale.mmToIU( 5 ) );
200 ellipse->SetEllipseMinorRadius( pcbIUScale.mmToIU( 3 ) );
201 ellipse->SetEllipseStartAngle( ANGLE_0 );
202 ellipse->SetEllipseEndAngle( ANGLE_90 );
203 ellipse->SetWidth( pcbIUScale.mmToIU( 0.3 ) );
204 ellipse->SetLayer( F_SilkS );
205 parent->Add( ellipse );
206 }
207
208 PCB_SHAPE* rounded = new PCB_SHAPE( parent, SHAPE_T::RECTANGLE );
209 rounded->SetStart( VECTOR2I( 0, 0 ) );
210 rounded->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
211 rounded->SetCornerRadius( pcbIUScale.mmToIU( 1 ) );
212 rounded->SetLayer( F_SilkS );
213 parent->Add( rounded );
214
215 PCB_SHAPE* hatch = new PCB_SHAPE( parent, SHAPE_T::RECTANGLE );
216 hatch->SetStart( VECTOR2I( 0, 0 ) );
217 hatch->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
218 hatch->SetFillMode( FILL_T::HATCH );
219 hatch->SetLayer( F_SilkS );
220 parent->Add( hatch );
221
222 PCB_SHAPE* copperHatch = new PCB_SHAPE( parent, SHAPE_T::RECTANGLE );
223 copperHatch->SetStart( VECTOR2I( pcbIUScale.mmToIU( 20 ), 0 ) );
224 copperHatch->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 30 ), pcbIUScale.mmToIU( 6 ) ) );
225 copperHatch->SetFillMode( FILL_T::HATCH );
226 copperHatch->SetLayer( F_Cu );
227 parent->Add( copperHatch );
228
229 PCB_BARCODE* barcode = new PCB_BARCODE( parent );
230 barcode->SetText( wxT( "TEST123" ) );
231 barcode->SetWidth( pcbIUScale.mmToIU( 10 ) );
232 barcode->SetHeight( pcbIUScale.mmToIU( 10 ) );
233 barcode->SetLayer( F_SilkS );
234 parent->Add( barcode );
235
236 PCB_TEXTBOX* textbox = new PCB_TEXTBOX( parent );
237 textbox->SetText( wxT( "Knockout" ) );
238 textbox->SetIsKnockout( true );
239 textbox->SetStart( VECTOR2I( 0, 0 ) );
240 textbox->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
241 textbox->SetLayer( F_SilkS );
242 parent->Add( textbox );
243
244 PCB_TABLE* table = new PCB_TABLE( parent, pcbIUScale.mmToIU( 0.1 ) );
245 table->SetColCount( 1 );
246 table->SetColWidth( 0, pcbIUScale.mmToIU( 10 ) );
247 table->SetRowHeight( 0, pcbIUScale.mmToIU( 6 ) );
248 table->SetLayer( F_SilkS );
249 PCB_TABLECELL* cell = new PCB_TABLECELL( table );
250 cell->SetText( wxT( "Knockout cell" ) );
251 cell->SetIsKnockout( true );
252 cell->SetStart( VECTOR2I( 0, 0 ) );
253 cell->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 6 ) ) );
254 table->AddCell( cell );
255 parent->Add( table );
256
257 ZONE* zone = new ZONE( parent );
258 zone->SetLayer( F_Cu );
259 zone->AppendCorner( VECTOR2I( 0, 0 ), -1 );
260 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), 0 ), -1 );
261 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), -1 );
262 zone->LayerProperties()[F_Cu].hatching_offset = VECTOR2I( 100, 100 );
263 parent->Add( zone );
264 parent->Add( new PCB_POINT( parent, VECTOR2I( 0, 0 ), pcbIUScale.mmToIU( 1 ) ) );
265
266 PCB_GROUP* group = new PCB_GROUP( parent );
267 group->SetName( wxT( "transform group" ) );
268 group->SetDesignBlockLibId( LIB_ID( wxT( "Lib" ), wxT( "Block" ) ) );
269 group->AddItem( hatch );
270 group->AddItem( barcode );
271 parent->Add( group );
272 }
273
274 PCB_VIA* skipVia = new PCB_VIA( &aBoard );
275 skipVia->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.6 ) );
276 skipVia->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
278 skipVia->Padstack().Drill().is_capped = true;
279 aBoard.Add( skipVia );
280
281 PCB_VIA* buriedVia = new PCB_VIA( &aBoard );
282 buriedVia->SetViaType( VIATYPE::BURIED );
283 buriedVia->SetLayerPair( In1_Cu, In2_Cu );
284 buriedVia->SetWidth( PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( 0.6 ) );
285 buriedVia->SetDrill( pcbIUScale.mmToIU( 0.3 ) );
286 aBoard.Add( buriedVia );
287
288 ZONE* linkedArea = new ZONE( &aBoard );
289 linkedArea->SetIsRuleArea( true );
290 linkedArea->SetLayer( F_Cu );
291 linkedArea->SetPlacementAreaEnabled( true );
292 linkedArea->SetLinkSet( KIID() );
293 linkedArea->AppendCorner( VECTOR2I( 0, 0 ), -1 );
294 linkedArea->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 5 ), 0 ), -1 );
295 linkedArea->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) ), -1 );
296 aBoard.Add( linkedArea );
297
298 aBoard.GetDesignSettings().m_ZoneLayerProperties[F_Cu].hatching_offset = VECTOR2I( 100, 100 );
299
301 {
302 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
303 item->SetDielectricModel( DIELECTRIC_MODEL::DJORDJEVIC_SARKAR );
304 }
305
307 wxMemoryBuffer png = KI_TEST::MakePngWithFractionalPixelsPerCm();
308 BOOST_REQUIRE( image->GetReferenceImage().ReadImageFile( png ) );
309 image->GetReferenceImage().SetImageScale( 2.0 );
310 aBoard.Add( image );
311
312 for( BOARD_ITEM* item : aBoard.Drawings() )
313 {
314 if( item->Type() == PCB_DRILL_CHART_T )
315 static_cast<PCB_DRILL_CHART*>( item )->RebuildCells( aBoard );
316 }
317
318 return image;
319}
320
321
322static void checkBoardTransformCoverage( const COMPATIBILITY_REPORT& aReport, const DOWNGRADE_TARGET& aTarget,
323 bool aDropInsteadOfApproximate )
324{
325 // Dates are explicit expectations, independent of the production rule table.
327 aReport, aTarget.m_boardVersion,
328 {
329 { wxT( "Geometric constraints" ), 20260624, DOWNGRADE_BUCKET::DROP },
330 { wxT( "Local grids" ), 20260728, DOWNGRADE_BUCKET::DROP },
331 { wxT( "Via stitching and guarding" ), 20260816, DOWNGRADE_BUCKET::DROP },
332 { wxT( "Microvia stack generators" ), 20260830, DOWNGRADE_BUCKET::DROP },
333 { wxT( "Graphic line endings" ), 20260818, DOWNGRADE_BUCKET::LOWER },
334 { wxT( "Footprint simulation exclusions" ), 20260828, DOWNGRADE_BUCKET::DROP },
335 { wxT( "Custom properties" ), 20260831, DOWNGRADE_BUCKET::DROP },
336 { wxT( "Drill charts and maps" ), 20260901, DOWNGRADE_BUCKET::DROP },
337 { wxT( "Drill symbol configuration" ), 20260901, DOWNGRADE_BUCKET::DROP },
338 { wxT( "Linked placement rule areas" ), 20261018, DOWNGRADE_BUCKET::DROP },
339 { wxT( "Net chains" ), 20260512, DOWNGRADE_BUCKET::DROP },
340 { wxT( "Ellipse graphics" ), 20260508, DOWNGRADE_BUCKET::LOWER },
341 { wxT( "Extruded 3D bodies" ), 20260410, DOWNGRADE_BUCKET::DROP },
342 { wxT( "Pad simulation types" ), 20260521, DOWNGRADE_BUCKET::DROP },
343 { wxT( "Footprint unit metadata" ), 20250909, DOWNGRADE_BUCKET::DROP },
344 { wxT( "Barcodes" ), 20250914, DOWNGRADE_BUCKET::LOWER },
345 { wxT( "PCB variants" ), 20260101, DOWNGRADE_BUCKET::DROP },
346 { wxT( "Jumper pad groups" ), 20250324, DOWNGRADE_BUCKET::DROP },
347 { wxT( "Counterbore / countersink" ), 20251101, DOWNGRADE_BUCKET::DROP },
348 { wxT( "Press-fit pads" ), 20250811, DOWNGRADE_BUCKET::DROP },
349 { wxT( "Skip vias" ), 20250801, DOWNGRADE_BUCKET::LOWER, true },
350 { wxT( "Buried vias" ), 20250926, DOWNGRADE_BUCKET::LOWER, true },
351 { wxT( "Zone layer defaults" ), 20250302, DOWNGRADE_BUCKET::DROP },
352 { wxT( "Pad-to-die delays" ), 20250401, DOWNGRADE_BUCKET::DROP },
353 { wxT( "Hatched shape fills" ), 20250222, DOWNGRADE_BUCKET::LOWER },
354 { wxT( "Hatched copper fills" ), 20250222, DOWNGRADE_BUCKET::LOWER, true },
355 { wxT( "PCB points" ), 20250901, DOWNGRADE_BUCKET::DROP },
356 { wxT( "Via protection" ), 20250228, DOWNGRADE_BUCKET::DROP },
357 { wxT( "Dielectric frequency models" ), 20260511, DOWNGRADE_BUCKET::DROP },
358 { wxT( "Group design block links" ), 20250513, DOWNGRADE_BUCKET::DROP },
359 { wxT( "Knockout text boxes" ), 20250210, DOWNGRADE_BUCKET::LOWER },
360 { wxT( "Rounded rectangles" ), 20250829, DOWNGRADE_BUCKET::LOWER },
361 { wxT( "Knockout table cells" ), 20260603, DOWNGRADE_BUCKET::LOWER },
362 { wxT( "Per-zone layer properties" ), 20250302, DOWNGRADE_BUCKET::DROP },
363 },
364 aDropInsteadOfApproximate );
365}
366
367
369 const std::string& aTokenFileName )
370{
371 SETTINGS_MANAGER settingsManager;
372 const std::set<std::string> known =
373 KI_TEST::LoadTokenSet( KI_TEST::GetPcbnewTestDataDir() + "../downgrade/" + aTokenFileName );
374 BOOST_REQUIRE( !known.empty() );
375
376 for( bool drop : { false, true } )
377 {
378 BOOST_TEST_CONTEXT( "Drop approximations: " << drop )
379 {
380 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_board_transform_oracle" );
381 const std::string src = ( tmp.Path() / "source.kicad_pcb" ).string();
382 const std::string dest = ( tmp.Path() / "out.kicad_pcb" ).string();
384 std::unique_ptr<BOARD> board(
385 io.LoadBoard( KI_TEST::GetPcbnewTestDataDir() + "via_off_center.kicad_pcb", nullptr ) );
386 BOOST_REQUIRE( board );
388 const REFERENCE_IMAGE& ref = image->GetReferenceImage();
389 BOOST_REQUIRE_NE( ref.GetImage().GetPPI(), ref.GetImage().GetLegacyPPI() );
390 const double expectedScale = ref.GetImageScale() * ref.GetImage().GetLegacyPPI() / ref.GetImage().GetPPI();
391 checkBoardTransformCoverage( ClassifyBoardForDowngrade( board.get(), aTarget, drop ), aTarget, drop );
392 io.SaveBoard( src, *board );
393 const std::string sourceContent = KI_TEST::ReadGoldenText( src );
394
396 BOOST_REQUIRE( ExportBoardToOlderVersion( src, dest, aTarget, report, nullptr, wxEmptyString, drop ) );
397 checkBoardTransformCoverage( report, aTarget, drop );
398
399 const std::string content = KI_TEST::ReadGoldenText( dest );
400 const auto unsupported = KI_TEST::UnknownSexprHeads( content, known );
401
402 for( const std::string& token : unsupported )
403 BOOST_ERROR( "Transformed board uses unknown token: " + token );
404
405 BOOST_CHECK( content.find( "(version " + std::to_string( aTarget.m_boardVersion ) + ")" )
406 != std::string::npos );
407 BOOST_CHECK_CLOSE( KI_TEST::SerializedImageScale( content, image->m_Uuid.AsStdString() ), expectedScale,
408 0.1 );
409 std::unique_ptr<BOARD> reloaded( io.LoadBoard( dest, nullptr ) );
410 BOOST_REQUIRE( reloaded );
411 const auto reloadReport = ClassifyBoardForDowngrade( reloaded.get(), aTarget, drop );
412 BOOST_CHECK( !reloadReport.IsBlocked() );
413
414 // Current readers reconstruct false IPC flags in legacy files. These in-memory
415 // upgrades do not mean newer syntax was written.
416 if( aTarget.m_id == wxT( "9.0" ) )
417 {
418 size_t viaCount = 0;
419 size_t buriedCount = 0;
420
421 for( PCB_TRACK* track : reloaded->Tracks() )
422 {
423 if( track->Type() != PCB_VIA_T )
424 continue;
425
426 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
427 const PADSTACK& padstack = via->Padstack();
428 ++viaCount;
429 BOOST_CHECK( padstack.FrontOuterLayers().has_covering == false );
430 BOOST_CHECK( padstack.BackOuterLayers().has_covering == false );
431 BOOST_CHECK( padstack.FrontOuterLayers().has_plugging == false );
432 BOOST_CHECK( padstack.BackOuterLayers().has_plugging == false );
433 BOOST_CHECK( padstack.Drill().is_filled == false );
434 BOOST_CHECK( padstack.Drill().is_capped == false );
435
436 if( via->IsBuriedVia() )
437 {
438 ++buriedCount;
439 BOOST_CHECK( via->GetViaType() == VIATYPE::BLIND );
440 BOOST_CHECK( via->TopLayer() == In1_Cu );
441 BOOST_CHECK( via->BottomLayer() == In2_Cu );
442 }
443 }
444
445 BOOST_CHECK_EQUAL( buriedCount, 1 );
446
447 for( const COMPAT_ENTRY& entry : reloadReport.Entries() )
448 {
449 if( entry.m_feature == wxT( "Via protection" ) )
450 BOOST_CHECK_EQUAL( entry.m_count, viaCount );
451 else
452 BOOST_ERROR( "Unexpected feature after reload: " + entry.m_feature.ToStdString() );
453 }
454 }
455 else
456 {
457 BOOST_CHECK( !reloadReport.IsLossy() );
458 }
459
460 BOOST_CHECK( KI_TEST::ReadGoldenText( src ) == sourceContent );
461 }
462 }
463}
464
465
466BOOST_AUTO_TEST_CASE( TransformedBoardToKicad9UsesOnlyKnownTokens )
467{
468 checkTransformedBoardUsesOnlyKnownTokens( kicad9, "kicad9_board_tokens.txt" );
469}
470
471
472BOOST_AUTO_TEST_CASE( TransformedBoardToKicad10UsesOnlyKnownTokens )
473{
474 checkTransformedBoardUsesOnlyKnownTokens( kicad10, "kicad10_board_tokens.txt" );
475}
476
477
478static void checkNativeBoardGolden( const DOWNGRADE_TARGET& aTarget, const std::string& aRelease,
479 const std::string& aBoard, const std::string& aReference, size_t aExpectedDrops,
480 size_t aExpectedLowerings, bool aDropInsteadOfApproximate = false )
481{
482 SETTINGS_MANAGER settingsManager;
483 const std::string fixture = KI_TEST::GetPcbnewTestDataDir() + "../downgrade/golden/";
484 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_native_golden", "" );
485 const std::string dest = ( tmp.Path() / ( aBoard + ".kicad_pcb" ) ).string();
487
488 // Expected files were saved by native KiCad, never by the exporter under test.
489 BOOST_REQUIRE( ExportBoardToOlderVersion( fixture + "current/" + aBoard + ".kicad_pcb", dest, aTarget, report,
490 nullptr, wxEmptyString, aDropInsteadOfApproximate ) );
491 BOOST_CHECK( !report.IsBlocked() );
492 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::DROP ), aExpectedDrops );
493 BOOST_CHECK_EQUAL( report.Count( DOWNGRADE_BUCKET::LOWER ), aExpectedLowerings );
494 const std::string difference =
495 KI_TEST::GoldenFileDifference( fixture + aRelease + "/" + aReference + ".kicad_pcb", dest );
496 BOOST_CHECK_MESSAGE( difference.empty(), difference );
497}
498
499
500BOOST_AUTO_TEST_CASE( BoardMatchesNativeKicad9Golden )
501{
502 // KiCad 9 drops the explicit disabled via-protection overrides without changing the via.
503 checkNativeBoardGolden( kicad9, "v9", "geometry", "geometry", 1, 0 );
504}
505
506
507BOOST_AUTO_TEST_CASE( BoardMatchesNativeKicad10Golden )
508{
509 checkNativeBoardGolden( kicad10, "v10", "geometry", "geometry", 0, 0 );
510}
511
512
513BOOST_AUTO_TEST_CASE( LoweredGraphicsMatchNativeKicad9Golden )
514{
515 checkNativeBoardGolden( kicad9, "v9", "graphics", "graphics", 0, 2 );
516}
517
518
519BOOST_AUTO_TEST_CASE( GraphicsMatchNativeKicad10Golden )
520{
521 checkNativeBoardGolden( kicad10, "v10", "graphics", "graphics", 0, 0 );
522}
523
524
525BOOST_AUTO_TEST_CASE( DroppedGraphicsMatchNativeKicad9Golden )
526{
527 checkNativeBoardGolden( kicad9, "v9", "graphics", "graphics-drop", 2, 0, true );
528}
529
530
531BOOST_AUTO_TEST_CASE( DropApproximationsKeepsNativeKicad10GraphicsGolden )
532{
533 checkNativeBoardGolden( kicad10, "v10", "graphics", "graphics", 0, 0, true );
534}
535
536
537// A standalone library footprint never reaches the board pass, so the footprint-scoped subset of
538// the rule table gets its own native reference.
539static void checkNativeFootprintGolden( const DOWNGRADE_TARGET& aTarget, const std::string& aRelease,
540 const std::string& aReference, bool aDropInsteadOfApproximate = false )
541{
542 const std::string fixture = KI_TEST::GetPcbnewTestDataDir() + "../downgrade/golden/";
543 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_native_fp_golden", "" );
544 const wxString dest = ( tmp.Path() / "metadata.kicad_mod" ).wstring();
545 const wxString src = wxString::FromUTF8( fixture + "current/metadata.pretty/metadata.kicad_mod" );
546
547 BOOST_REQUIRE( DowngradeFootprintFileToTemp( src, dest, aTarget, aDropInsteadOfApproximate )
549
550 const std::string difference = KI_TEST::GoldenFileDifference(
551 fixture + aRelease + "/" + aReference + ".pretty/metadata.kicad_mod", dest.ToStdString() );
552 BOOST_CHECK_MESSAGE( difference.empty(), difference );
553}
554
555
556// A primitive the writer does not handle used to emit an orphan (width ...) and a surplus
557// closing paren, which left the file unbalanced and unloadable in the target.
558static void checkCustomPadArcSurvives( const DOWNGRADE_TARGET& aTarget )
559{
560 SETTINGS_MANAGER settingsManager;
561 const std::string src = KI_TEST::GetPcbnewTestDataDir() + "../downgrade/regressions/custom_pad_arc.kicad_pcb";
562 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_custom_pad_arc", "" );
563 const std::string dest = ( tmp.Path() / "out.kicad_pcb" ).string();
565
566 BOOST_REQUIRE( ExportBoardToOlderVersion( src, dest, aTarget, report ) );
567
568 // The rotated pad's transform scales differ only by rounding, so the arc stays a circular arc,
569 // which both targets can read. It is written as one and nothing is lowered.
571
572 const std::string out = KI_TEST::ReadGoldenText( dest );
573 BOOST_CHECK_MESSAGE( out.find( "gr_arc" ) != std::string::npos, "the custom pad's arc primitive was dropped" );
574
575 int depth = 0;
576 bool inString = false;
577
578 for( size_t ii = 0; ii < out.size(); ++ii )
579 {
580 if( out[ii] == '"' && ( ii == 0 || out[ii - 1] != '\\' ) )
581 inString = !inString;
582 else if( !inString && out[ii] == '(' )
583 ++depth;
584 else if( !inString && out[ii] == ')' )
585 --depth;
586 }
587
588 BOOST_CHECK_MESSAGE( depth == 0, "exported board is unbalanced by " + std::to_string( depth ) );
589
591 std::unique_ptr<BOARD> reloaded( io.LoadBoard( dest, nullptr ) );
592 BOOST_REQUIRE( reloaded != nullptr );
593 BOOST_REQUIRE_EQUAL( reloaded->Footprints().size(), 1 );
594 BOOST_REQUIRE_EQUAL( reloaded->Footprints().front()->Pads().size(), 1 );
595 BOOST_CHECK_EQUAL( reloaded->Footprints().front()->Pads().front()->GetPrimitives( F_Cu ).size(), 1 );
596}
597
598
599BOOST_AUTO_TEST_CASE( CustomPadArcSurvivesExportToKicad9 )
600{
602}
603
604
605BOOST_AUTO_TEST_CASE( CustomPadArcSurvivesExportToKicad10 )
606{
608}
609
610
611BOOST_AUTO_TEST_CASE( ModernBoardMetadataMatchesNativeKicad9Golden )
612{
613 checkNativeBoardGolden( kicad9, "v9", "modern_metadata", "modern_metadata", 13, 0 );
614}
615
616
617BOOST_AUTO_TEST_CASE( ModernBoardMetadataMatchesNativeKicad10Golden )
618{
619 checkNativeBoardGolden( kicad10, "v10", "modern_metadata", "modern_metadata", 12, 0 );
620}
621
622
623BOOST_AUTO_TEST_CASE( BoardMetadataMatchesNativeKicad9Golden )
624{
625 checkNativeBoardGolden( kicad9, "v9", "metadata", "metadata", 6, 0 );
626}
627
628
629BOOST_AUTO_TEST_CASE( BoardMetadataMatchesNativeKicad10Golden )
630{
631 checkNativeBoardGolden( kicad10, "v10", "metadata", "metadata", 0, 0 );
632}
633
634
635BOOST_AUTO_TEST_CASE( FootprintMatchesNativeKicad9Golden )
636{
637 checkNativeFootprintGolden( kicad9, "v9", "metadata" );
638}
639
640
641BOOST_AUTO_TEST_CASE( FootprintMatchesNativeKicad10Golden )
642{
643 checkNativeFootprintGolden( kicad10, "v10", "metadata" );
644}
645
646
647BOOST_AUTO_TEST_CASE( RequiredReleaseLookupFailsForMissingId )
648{
649 BOOST_CHECK( KI_TEST::RequireDowngradeTarget( wxT( "9.0" ) ).m_id == wxT( "9.0" ) );
650 BOOST_CHECK( KI_TEST::RequireDowngradeTarget( wxT( "10.0" ) ).m_id == wxT( "10.0" ) );
651 BOOST_CHECK_THROW( KI_TEST::RequireDowngradeTarget( wxT( "missing-release" ) ), std::runtime_error );
652}
653
654
655// Guard the oracle itself: normalization must not hide meaningful design changes.
656BOOST_AUTO_TEST_CASE( GoldenComparisonDetectsDesignAndSettingsChanges )
657{
658 SEXPR::PARSER parser;
659 const std::string golden = "(kicad_pcb (version 20241229) (generator_version \"9.0\") "
660 "(setup (pad_to_mask_clearance 0.05)) "
661 "(pad \"1\" (at 100 100 30) (size 2 1) (net 1 \"SIGNAL\")))";
662 auto expected = parser.Parse( golden );
663
664 for( const auto& [from, to] : std::vector<std::pair<std::string, std::string>>{ { "100 100", "0 0" },
665 { "(size 2 1)", "(size 1 2)" },
666 { "SIGNAL", "GND" },
667 { "0.05", "0.1" },
668 { "100 100 30", "100 100 31" },
669 { "20241229", "20260206" } } )
670 {
671 std::string changed = golden;
672 changed.replace( changed.find( from ), from.size(), to );
673 auto actual = parser.Parse( changed );
675 }
676
677 auto metadataOnly = parser.Parse( "(kicad_pcb (version 20241229) (generator_version \"10.99\") "
678 "(setup (pad_to_mask_clearance 0.05)) "
679 "(pad \"1\" (at 100.000001 99.999999 30) (size 2 1) (net 1 \"SIGNAL\")))" );
681
682 const std::string plotGolden = "(kicad_pcb (setup (pcbplotparams (hpglpennumber 1) (hpglpenspeed 20) "
683 "(hpglpendiameter 15) (plotinvisibletext no))))";
684 auto plotExpected = parser.Parse( plotGolden );
685 auto modernPlot = parser.Parse( "(kicad_pcb (setup (pcbplotparams (pngdpi 300) (pngantialias yes))))" );
686 BOOST_CHECK( KI_TEST::GoldenSexprDifference( *plotExpected, *modernPlot ).empty() );
687
688 for( const auto& [from, to] :
689 std::vector<std::pair<std::string, std::string>>{ { "hpglpennumber 1", "hpglpennumber 2" },
690 { "hpglpenspeed 20", "hpglpenspeed 30" },
691 { "hpglpendiameter 15", "hpglpendiameter 16" },
692 { "plotinvisibletext no", "plotinvisibletext yes" } } )
693 {
694 std::string changed = plotGolden;
695 changed.replace( changed.find( from ), from.size(), to );
696 auto nonDefault = parser.Parse( changed );
697 BOOST_CHECK( !KI_TEST::GoldenSexprDifference( *nonDefault, *modernPlot ).empty() );
698 }
699}
700
701
702BOOST_AUTO_TEST_CASE( HistoricalPlotSettingsDoNotUseCurrentFormatters )
703{
704 BOARD board;
705 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_historical_plot", "" );
706
707 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
708 {
709 const wxString path = ( tmp.Path() / ( target.m_id.ToStdString() + ".kicad_pcb" ) ).string();
710 SaveBoardForTarget( &board, path, target );
711 std::ifstream file( path.ToStdString() );
712 std::string text( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
713 BOOST_CHECK( text.find( "(pngdpi " ) == std::string::npos );
714 BOOST_CHECK( text.find( "(pngantialias " ) == std::string::npos );
715
716 if( target.m_id == wxT( "9.0" ) )
717 {
718 BOOST_CHECK( text.find( "(hpglpennumber 1)" ) != std::string::npos );
719 BOOST_CHECK( text.find( "(hpglpenspeed 20)" ) != std::string::npos );
720 BOOST_CHECK( text.find( "(hpglpendiameter 15.000000)" ) != std::string::npos );
721 BOOST_CHECK( text.find( "(plotinvisibletext no)" ) != std::string::npos );
722 }
723 }
724}
725
726
727BOOST_AUTO_TEST_CASE( HistoricalCustomPaperUsesReleasePrecision )
728{
729 BOARD board;
731 page.SetWidthMM( 123.456789 );
732 page.SetHeightMM( 100.123456 );
733 board.SetPageSettings( page );
734 KI_TEST::SCOPED_TEMP_DIR tmp( "kicad_qa_historical_paper", "" );
735
736 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
737 {
738 const wxString path = ( tmp.Path() / ( target.m_id.ToStdString() + ".kicad_pcb" ) ).string();
739 SaveBoardForTarget( &board, path, target );
740 std::ifstream file( path.ToStdString() );
741 std::string text( ( std::istreambuf_iterator<char>( file ) ), std::istreambuf_iterator<char>() );
742 const std::string expected = target.m_id == wxT( "9.0" ) ? "(paper \"User\" 123.457 100.123)"
743 : "(paper \"User\" 123.456789 100.123456)";
744 BOOST_CHECK( text.find( expected ) != std::string::npos );
745 }
746}
747
748
749BOOST_AUTO_TEST_CASE( UnsupportedPlotSelectionIsReportedAndReset )
750{
751 for( const DOWNGRADE_TARGET& target : { kicad9, kicad10 } )
752 {
753 for( bool drop : { false, true } )
754 {
755 BOARD board;
756 PCB_PLOT_PARAMS options = board.GetPlotOptions();
757 options.SetFormat( PLOT_FORMAT::PNG );
758 board.SetPlotOptions( options );
759 COMPATIBILITY_REPORT report = ClassifyBoardForDowngrade( &board, target, drop );
761 BOOST_CHECK( !report.IsBlocked() );
763 DowngradeBoardInPlace( &board, target, drop );
765 BOOST_CHECK( !ClassifyBoardForDowngrade( &board, target, drop ).IsLossy() );
766 }
767 }
768}
769
770
771static std::string historicalTextEffects( const EDA_TEXT& aText, bool aV9, int aControlBits = 0 )
772{
773 STRING_FORMATTER formatter;
774
775 if( aV9 )
776 KICAD_FORMAT::LEGACY::FormatTextV9( &formatter, aText, pcbIUScale, aControlBits );
777 else
778 KICAD_FORMAT::LEGACY::FormatTextV10( &formatter, aText, pcbIUScale, aControlBits );
779
780 return formatter.GetString();
781}
782
783
784BOOST_AUTO_TEST_CASE( HistoricalTextEffectsKeepReleaseHideAndAttributes )
785{
787 text.SetFont( nullptr );
788 text.SetTextSize( VECTOR2I( 1000000, 2000000 ) );
789 text.SetTextThickness( 100000 );
790 text.SetLineSpacing( 1.25 );
791 text.SetItalic( true );
792 text.SetTextColor( KIGFX::COLOR4D( 0.2, 0.4, 0.6, 0.75 ) );
793 text.SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
794 text.SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
795 text.SetMirrored( true );
796 text.SetVisible( false );
797 text.SetHyperlink( wxT( "https://example.com/datasheet" ) );
798
799 const std::string prefix = "(effects(font(size 2 1)(line_spacing 1.25)(thickness 0.1)"
800 "(italic yes)(color 51 102 153 0.75))(justify left bottom mirror)";
801 const std::string href = "(href \"https://example.com/datasheet\"))";
802 BOOST_CHECK_EQUAL( historicalTextEffects( text, true ), prefix + "(hide yes)" + href );
803 BOOST_CHECK_EQUAL( historicalTextEffects( text, false ), prefix + href );
805
806 for( bool v9 : { false, true } )
807 {
808 const std::string omitted = historicalTextEffects( text, v9, ( 1 << 11 ) | ( 1 << 12 ) );
809 BOOST_CHECK( omitted.find( "(color " ) == std::string::npos );
810 BOOST_CHECK( omitted.find( "(href " ) == std::string::npos );
811 BOOST_CHECK( omitted.find( "(justify left bottom mirror)" ) != std::string::npos );
812 }
813
814 BOOST_CHECK_EQUAL( text.GetTextThickness(), 100000 );
815 BOOST_CHECK_EQUAL( text.GetLineSpacing(), 1.25 );
816 BOOST_CHECK( !text.IsVisible() );
817}
818
819
820BOOST_AUTO_TEST_CASE( HistoricalTextThicknessKeepsAutomaticZeroAndExplicitWidths )
821{
823 text.SetFont( nullptr );
824 text.SetTextSize( VECTOR2I( 1000000, 1000000 ) );
825
826 for( bool v9 : { false, true } )
827 {
828 text.SetBold( false );
829 text.SetTextThickness( 0 );
830 BOOST_CHECK( text.GetAutoThickness() );
831 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)))" );
832
833 text.SetTextThickness( 1 );
834 BOOST_CHECK( !text.GetAutoThickness() );
835 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(thickness 0.000001)))" );
836 BOOST_CHECK_EQUAL( text.GetTextThickness(), 1 );
837
838 text.SetTextThickness( 100000 );
839 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(thickness 0.1)))" );
840
841 text.SetBold( true );
842 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(thickness 0.16)(bold yes)))" );
843 BOOST_CHECK_EQUAL( text.GetTextThickness(), 100000 );
844
845 text.SetTextThickness( 0 );
846 BOOST_CHECK_EQUAL( historicalTextEffects( text, v9 ), "(effects(font(size 1 1)(bold yes)))" );
847 BOOST_CHECK_EQUAL( text.GetTextThickness(), 0 );
848 }
849}
850
851
852BOOST_AUTO_TEST_CASE( HistoricalDefaultTextFormattingKeepsNative10AutomaticWidthPredicate )
853{
855 text.SetFont( nullptr );
856 text.SetMultilineAllowed( false );
857 text.SetTextSize( VECTOR2I( 1000000, 1000000 ) );
858 text.SetAutoThickness( true );
859 text.SetVisible( false );
860 BOOST_CHECK( text.GetAutoThickness() );
862
863 text.SetTextThickness( 1 );
864 BOOST_CHECK( !text.GetAutoThickness() );
866
867 text.SetTextThickness( 0 );
868 BOOST_CHECK( text.GetAutoThickness() );
870
871 text.SetBold( true );
873 text.SetBold( false );
874 text.SetMirrored( true );
876}
877
878
879BOOST_AUTO_TEST_CASE( HistoricalTitleAndStrokeKeepPinnedCommonGrammar )
880{
881 TITLE_BLOCK title;
884 BOOST_CHECK( empty.GetString().empty() );
885
886 title.SetTitle( wxT( "Production" ) );
887 title.SetDate( wxT( "2026-10-08" ) );
888 title.SetRevision( wxT( "A" ) );
889 title.SetCompany( wxT( "KiCad" ) );
890 title.SetComment( 0, wxT( "First" ) );
891 title.SetComment( 8, wxT( "Last" ) );
892 STRING_FORMATTER metadata;
893 KICAD_FORMAT::LEGACY::FormatTitle( &metadata, title );
894 BOOST_CHECK_EQUAL( metadata.GetString(), "(title_block(title \"Production\")(date \"2026-10-08\")(rev \"A\")"
895 "(company \"KiCad\")(comment 1 \"First\")(comment 9 \"Last\"))" );
896 BOOST_CHECK( title.GetComment( 8 ) == wxT( "Last" ) );
897
898 for( const auto& [style, token] :
899 std::vector<std::pair<LINE_STYLE, std::string>>{ { LINE_STYLE::DEFAULT, "default" },
900 { LINE_STYLE::SOLID, "solid" },
901 { LINE_STYLE::DASH, "dash" },
902 { LINE_STYLE::DOT, "dot" },
903 { LINE_STYLE::DASHDOT, "dash_dot" },
904 { LINE_STYLE::DASHDOTDOT, "dash_dot_dot" } } )
905 {
906 STROKE_PARAMS stroke( 200000, style );
907 STRING_FORMATTER plain;
909 BOOST_CHECK_EQUAL( plain.GetString(), "(stroke (width 0.2) (type " + token + "))" );
910 stroke.SetColor( KIGFX::COLOR4D( 0.2, 0.4, 0.6, 0.75 ) );
911 STRING_FORMATTER colored;
913 BOOST_CHECK_EQUAL( colored.GetString(), "(stroke (width 0.2) (type " + token + ") (color 51 102 153 0.75))" );
914 }
915}
916
917
918BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesKeepEmptyReleasePolicy )
919{
920 EMBEDDED_FILES files;
921 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
922 file->name = wxT( "metadata.bin" );
924 file->data_hash = "metadata-only";
925 files.AddFile( file );
926
927 STRING_FORMATTER invalidData;
928 BOOST_CHECK_THROW( KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( invalidData, files, true ), IO_ERROR );
929
930 STRING_FORMATTER reference;
931 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( reference, files, false );
932 BOOST_CHECK_EQUAL( reference.GetString(),
933 "(embedded_files (file (name \"metadata.bin\")(type other)(checksum \"metadata-only\")))" );
934
935 for( bool writeData : { false, true } )
936 {
937 STRING_FORMATTER skipped;
938 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( skipped, files, writeData );
939 BOOST_CHECK_EQUAL( skipped.GetString(), "(embedded_files )" );
940 }
941
942 BOOST_REQUIRE( files.GetEmbeddedFile( wxT( "metadata.bin" ) ) );
943 BOOST_CHECK( file->compressedEncodedData.empty() );
944 BOOST_CHECK_EQUAL( file->data_hash, "metadata-only" );
945}
946
947
948BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesKeepValidDataAndNativeChunkDelimiters )
949{
950 EMBEDDED_FILES files;
951 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
952 file->name = wxT( "payload.bin" );
954
955 for( int ii = 0; ii < 4096; ++ii )
956 file->decompressedData.push_back( static_cast<char>( ( ii * 131 + ii / 251 ) % 256 ) );
957
959 BOOST_REQUIRE_GT( file->compressedEncodedData.size(), 76 );
960 files.AddFile( file );
961 const std::string encoded = file->compressedEncodedData;
962 const std::string hash = file->data_hash;
963
964 std::string expected = "(embedded_files (file (name \"payload.bin\")(type other)(data";
965
966 for( size_t first = 0; first < encoded.size(); first += 76 )
967 {
968 const std::string chunk = encoded.substr( first, 76 );
969 expected += "\n";
970 expected += first ? " " : "|";
971 expected += chunk;
972
973 if( first + chunk.size() == encoded.size() )
974 expected += "|";
975
976 expected += "\n";
977 }
978
979 expected += ")(checksum \"" + hash + "\")))";
980
981 for( bool v9 : { false, true } )
982 {
983 STRING_FORMATTER serialized;
984
985 if( v9 )
986 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( serialized, files, true );
987 else
988 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( serialized, files, true );
989
990 BOOST_CHECK_EQUAL( serialized.GetString(), expected );
991 STRING_FORMATTER reference;
992
993 if( v9 )
994 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( reference, files, false );
995 else
996 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( reference, files, false );
997
998 BOOST_CHECK_EQUAL( reference.GetString(),
999 "(embedded_files (file (name \"payload.bin\")(type other)(checksum \"" + hash + "\")))" );
1000 }
1001
1002 BOOST_CHECK_EQUAL( file->compressedEncodedData, encoded );
1003 BOOST_CHECK_EQUAL( file->data_hash, hash );
1004}
1005
1006
1007static std::string historicalEmbeddedFiles( const EMBEDDED_FILES& aFiles, bool aV9, bool aWriteData )
1008{
1009 STRING_FORMATTER formatter;
1010
1011 if( aV9 )
1012 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV9( formatter, aFiles, aWriteData );
1013 else
1014 KICAD_FORMAT::LEGACY::FormatEmbeddedFilesV10( formatter, aFiles, aWriteData );
1015
1016 return formatter.GetString();
1017}
1018
1019
1020static std::string releaseEmbeddedChecksum( const std::vector<char>& aPayload )
1021{
1023 hash.addDataV1( reinterpret_cast<const uint8_t*>( aPayload.data() ), aPayload.size() );
1024 return hash.digest().ToString();
1025}
1026
1027
1028BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesRestoreReleaseChecksumsForAllTailLengths )
1029{
1030 std::vector<size_t> sizes = { 4095, 4096, 4097 };
1031
1032 for( size_t size = 0; size <= 32; ++size )
1033 sizes.push_back( size );
1034
1035 for( size_t size : sizes )
1036 {
1037 EMBEDDED_FILES files;
1038 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
1039 file->name = wxT( "payload.bin" );
1040
1041 for( size_t ii = 0; ii < size; ++ii )
1042 file->decompressedData.push_back( static_cast<char>( ( ii * 131 + ii / 251 ) % 256 ) );
1043
1045 files.AddFile( file );
1046 const auto original = *file;
1047 const std::string checksum = releaseEmbeddedChecksum( original.decompressedData );
1048
1049 if( size % 16 != 0 )
1050 BOOST_CHECK_NE( checksum, original.data_hash );
1051
1052 for( bool v9 : { false, true } )
1053 {
1054 for( bool writeData : { false, true } )
1055 {
1056 BOOST_TEST_CONTEXT( "bytes=" << size << " v9=" << v9 << " data=" << writeData )
1057 {
1058 const std::string output = historicalEmbeddedFiles( files, v9, writeData );
1059 BOOST_CHECK( output.find( "(checksum \"" + checksum + "\")" ) != std::string::npos );
1060 BOOST_CHECK_EQUAL( output.find( "(data" ) != std::string::npos, writeData );
1061
1062 if( !writeData )
1063 {
1064 BOOST_CHECK_EQUAL( output, "(embedded_files (file (name \"payload.bin\")(type other)"
1065 "(checksum \""
1066 + checksum + "\")))" );
1067 }
1068
1069 BOOST_CHECK( file->decompressedData == original.decompressedData );
1070 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1071 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1072 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1073 }
1074 }
1075 }
1076 }
1077}
1078
1079
1080BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesDecodeEncodedOnlyAttachmentsWithoutMutatingSource )
1081{
1082 for( size_t size : { 0u, 1u, 13u, 17u, 31u, 4097u } )
1083 {
1084 EMBEDDED_FILES files;
1085 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
1086 file->name = wxT( "encoded.bin" );
1087 file->decompressedData.assign( size, 'x' );
1088 const std::string checksum = releaseEmbeddedChecksum( file->decompressedData );
1090 file->decompressedData.clear();
1091 files.AddFile( file );
1092 const auto original = *file;
1093
1094 for( bool v9 : { false, true } )
1095 {
1096 for( bool writeData : { false, true } )
1097 {
1098 BOOST_TEST_CONTEXT( "bytes=" << size << " v9=" << v9 << " data=" << writeData )
1099 {
1100 const std::string output = historicalEmbeddedFiles( files, v9, writeData );
1101 BOOST_CHECK( output.find( "(checksum \"" + checksum + "\")" ) != std::string::npos );
1102 BOOST_CHECK_EQUAL( output.find( "(data" ) != std::string::npos, writeData );
1103 BOOST_CHECK( file->decompressedData.empty() );
1104 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1105 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1106 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1107 }
1108 }
1109 }
1110 }
1111}
1112
1113
1114BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesRejectMalformedEncodedOnlyAttachments )
1115{
1116 for( bool badChecksum : { false, true } )
1117 {
1118 EMBEDDED_FILES files;
1119 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
1120 file->name = wxT( "invalid.bin" );
1121 file->decompressedData.assign( 13, 'x' );
1123 file->decompressedData.clear();
1124
1125 if( badChecksum )
1126 file->data_hash = "invalid-checksum";
1127 else
1128 file->compressedEncodedData = "invalid-base64";
1129
1130 files.AddFile( file );
1131 const auto original = *file;
1132
1133 for( bool v9 : { false, true } )
1134 {
1135 for( bool writeData : { false, true } )
1136 {
1137 BOOST_CHECK_THROW( historicalEmbeddedFiles( files, v9, writeData ), IO_ERROR );
1138 BOOST_CHECK( file->decompressedData.empty() );
1139 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1140 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1141 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1142 }
1143 }
1144 }
1145}
1146
1147
1148BOOST_AUTO_TEST_CASE( HistoricalEmbeddedFilesRestoreCommittedWorksheetChecksums )
1149{
1150 SETTINGS_MANAGER settingsManager;
1151 const std::string source = KI_TEST::GetPcbnewTestDataDir() + "issue22102.kicad_pcb";
1152 const std::string sourceBytes = KI_TEST::ReadGoldenText( source );
1154 std::unique_ptr<BOARD> board( io.LoadBoard( source ) );
1155 BOOST_REQUIRE( board );
1156
1157 // The fixture was committed in 2025 before the MMH3 tail-byte correction.
1158 const std::vector<std::pair<wxString, std::string>> checksums = {
1159 { wxT( "SchematicTemplate.kicad_wks" ), "6003EC72C5978E83C182073E67278FCF" },
1160 { wxT( "SchematicTemplateEmpty.kicad_wks" ), "0D054F65E3E0563620A9E16BBBB777DD" }
1161 };
1162
1163 for( const auto& [name, checksum] : checksums )
1164 {
1165 const auto* file = board->GetEmbeddedFile( name );
1166 BOOST_REQUIRE( file );
1167 BOOST_REQUIRE( !file->decompressedData.empty() );
1168 BOOST_CHECK_NE( file->data_hash, checksum );
1169 BOOST_CHECK_EQUAL( releaseEmbeddedChecksum( file->decompressedData ), checksum );
1170 const auto original = *file;
1171
1172 for( bool v9 : { false, true } )
1173 {
1174 for( bool writeData : { false, true } )
1175 {
1176 const std::string output = historicalEmbeddedFiles( *board, v9, writeData );
1177 BOOST_CHECK( output.find( "(name \"" + name.ToStdString() + "\")" ) != std::string::npos );
1178 BOOST_CHECK( output.find( "(checksum \"" + checksum + "\")" ) != std::string::npos );
1179 BOOST_CHECK( output.find( "(checksum \"" + original.data_hash + "\")" ) == std::string::npos );
1180 BOOST_CHECK_EQUAL( output.find( "(data" ) != std::string::npos, writeData );
1181 BOOST_CHECK( file->decompressedData == original.decompressedData );
1182 BOOST_CHECK_EQUAL( file->compressedEncodedData, original.compressedEncodedData );
1183 BOOST_CHECK_EQUAL( file->data_hash, original.data_hash );
1184 BOOST_CHECK_EQUAL( file->is_valid, original.is_valid );
1185 }
1186 }
1187 }
1188
1190}
1191
1192
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:85
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:424
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:383
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:411
void SetPlotOptions(const PCB_PLOT_PARAMS &aOptions)
Definition board.h:1024
void SetVariantNames(const std::vector< wxString > &aNames)
Definition board.h:534
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition board.h:1021
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:1023
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
const DRAWINGS & Drawings() const
Definition board.h:467
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:195
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:193
PCB_3D_BODY & EnsureBody()
void SetExcludedFromSim(bool aExclude=true)
Definition footprint.h:939
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1134
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:908
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
Definition kiid.h:46
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
Definition file_utils.h:61
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:61
FORCE_INLINE void addDataV1(const uint8_t *data, size_t length)
Definition mmh3_hash.h:116
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:157
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:158
MASK_LAYER_PROPS & FrontOuterLayers()
Definition padstack.h:407
void SetUnconnectedLayerMode(UNCONNECTED_LAYER_MODE aMode)
Definition padstack.h:402
DRILL_PROPS & Drill()
Definition padstack.h:360
MASK_LAYER_PROPS & BackOuterLayers()
Definition padstack.h:410
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:181
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:3364
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
EXTRUDED_3D_BODY * MutableExtruded()
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:431
void SetDrill(int aDrill)
Definition pcb_track.h:819
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:423
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:1496
ZONE_LAYER_PROPERTIES & LayerProperties(PCB_LAYER_ID aLayer)
Definition zone.h:146
void SetLinkSet(const KIID &aLinkSet)
Definition zone.h:844
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:675
void SetIsRuleArea(bool aEnable)
Definition zone.h:833
void SetPlacementAreaEnabled(bool aEnabled)
Definition zone.h:835
@ 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:472
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:474
@ 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:124
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:44
std::optional< bool > is_capped
True if the drill hole should be capped.
Definition padstack.h:281
std::optional< bool > is_filled
True if the drill hole should be filled completely.
Definition padstack.h:280
std::optional< bool > has_covering
True if the pad on this side should have covering.
Definition padstack.h:263
std::optional< bool > has_plugging
True if the drill hole should be plugged on this side.
Definition padstack.h:264
std::optional< VECTOR2I > hatching_offset
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_CHECK(res.column_datatype(0)==SQL_NUMERIC||res.column_datatype(0)==SQL_DECIMAL)
const std::string table
BOOST_REQUIRE_EQUAL(rows.size(), 1)
BOOST_CHECK_EQUAL(std::any_cast< std::string >(rows[0].at("val")), "12345678901234567.89")
BOOST_REQUIRE(res.columns()==1)
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_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
@ 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