KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_job_export_pcb_variants.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
27#include <jobs/job_pcb_render.h>
28#include <json_common.h>
30#include <string_utils.h>
31
32#include <filesystem>
33#include <fstream>
34#include <clocale>
35
36
37// Regression coverage for issue #24092: variant fields on JOBSET-resident jobs
38// must round-trip through ToJson/FromJson.
39
40BOOST_AUTO_TEST_SUITE( JobExportPcbVariants )
41
42BOOST_AUTO_TEST_CASE( OdbVariantFileTokenKeepsNamesAndRemovesSeparators )
43{
44 BOOST_CHECK_EQUAL( GetVariantFileToken( wxS( "Variant A" ) ), wxString( wxS( "Variant A" ) ) );
45 BOOST_CHECK_EQUAL( GetVariantFileToken( wxS( "A/B" ) ), wxString( wxS( "A_B" ) ) );
47 BOOST_CHECK_EQUAL( ExpandVariantOutputPath( wxS( "bom-${VARIANT}.csv" ), wxString() ),
48 wxString( wxS( "bom-.csv" ) ) );
49 BOOST_CHECK_EQUAL( ExpandVariantOutputPath( wxS( "out-${VARIANT}.zip" ),
50 GetVariantFileToken( wxS( "A/B" ) ) ),
51 wxString( wxS( "out-A_B.zip" ) ) );
52 BOOST_CHECK_EQUAL( ExpandVariantOutputPath( wxS( "bom-${VARIANT}.csv" ), wxS( "A/B" ) ),
53 wxString( wxS( "bom-A/B.csv" ) ) );
54}
55
56BOOST_AUTO_TEST_CASE( OdbLegacyVariantKeyStillLoads )
57{
59 job.FromJson( nlohmann::json{ { "variant", "Variant A" } } );
60 BOOST_REQUIRE_EQUAL( job.m_variantNames.size(), 1u );
61 BOOST_CHECK_EQUAL( job.m_variantNames.front(), wxString( wxS( "Variant A" ) ) );
63
65 nlohmann::json json;
66 job.ToJson( json );
67 BOOST_CHECK_EQUAL( json.at( "variant_packaging" ).get<std::string>(), "combined" );
68
69 JOB_EXPORT_PCB_ODB loaded;
70 loaded.FromJson( json );
72}
73
74
75BOOST_AUTO_TEST_CASE( FabJobMissingKeysKeepDefaults )
76{
77 nlohmann::json missingKeys = nlohmann::json::object();
78
80 odb.FromJson( missingKeys );
81 nlohmann::json odbJson;
82 odb.ToJson( odbJson );
83 BOOST_CHECK_EQUAL( odbJson.at( "precision" ).get<int>(), 6 );
84 BOOST_CHECK_EQUAL( odbJson.at( "units" ).get<std::string>(), "mm" );
85 BOOST_CHECK( !odbJson.at( "check_zones" ).get<bool>() );
86 BOOST_CHECK( odbJson.at( "board_metadata" ).get<bool>() );
87 BOOST_CHECK_EQUAL( odbJson.at( "variant" ).get<std::string>(), "" );
88
90 ipc.FromJson( missingKeys );
91 nlohmann::json ipcJson;
92 ipc.ToJson( ipcJson );
93 BOOST_CHECK_EQUAL( ipcJson.at( "precision" ).get<int>(), 6 );
94 BOOST_CHECK_EQUAL( ipcJson.at( "units" ).get<std::string>(), "mm" );
95 BOOST_CHECK( !ipcJson.value( "check_zones", true ) );
96
97 BOOST_CHECK_EQUAL( ipcJson.at( "variant" ).get<std::string>(), "" );
98
99 nlohmann::json emptyVariant = { { "variant", "" } };
100 JOB_EXPORT_PCB_ODB legacyOdb;
101 JOB_EXPORT_PCB_IPC2581 legacyIpc;
102 legacyOdb.FromJson( emptyVariant );
103 legacyIpc.FromJson( emptyVariant );
104 BOOST_CHECK( legacyOdb.m_variantNames.empty() );
105 BOOST_CHECK( legacyIpc.m_variantNames.empty() );
106}
107
108
109BOOST_AUTO_TEST_CASE( FabV10JobsLoadUnchanged )
110{
111 std::filesystem::path fixtureDir = std::filesystem::path( KI_TEST::GetTestDataRootDir() ) / "common" / "jobs";
112 std::ifstream odbFile( fixtureDir / "odb_job_v10.json" );
113 std::ifstream ipcFile( fixtureDir / "ipc2581_job_v10.json" );
114 BOOST_REQUIRE( odbFile );
115 BOOST_REQUIRE( ipcFile );
116 nlohmann::json odbV10 = nlohmann::json::parse( odbFile );
117 nlohmann::json ipcV10 = nlohmann::json::parse( ipcFile );
118
120 odb.FromJson( odbV10 );
121 nlohmann::json odbRoundTrip;
122 odb.ToJson( odbRoundTrip );
123
124 for( auto it = odbV10.begin(); it != odbV10.end(); ++it )
125 {
126 BOOST_CHECK_MESSAGE( odbRoundTrip.at( it.key() ) == it.value(), "ODB key " << it.key() );
127 }
128
130 ipc.FromJson( ipcV10 );
131 nlohmann::json ipcRoundTrip;
132 ipc.ToJson( ipcRoundTrip );
133
134 for( auto it = ipcV10.begin(); it != ipcV10.end(); ++it )
135 {
136 BOOST_CHECK_MESSAGE( ipcRoundTrip.at( it.key() ) == it.value(), "IPC key " << it.key() );
137 }
138
139 BOOST_CHECK( odbRoundTrip.contains( "variant_names" ) );
140 BOOST_CHECK( ipcRoundTrip.contains( "variant_names" ) );
141 BOOST_CHECK( odb.m_colMfgPn.IsEmpty() );
142 BOOST_CHECK( ipc.m_colMfgPn.IsEmpty() );
144 BOOST_CHECK( odb.m_productName.IsEmpty() );
146 BOOST_CHECK( !odb.m_sections );
148 BOOST_CHECK( odb.m_layerOverrides.empty() );
152}
153
154
155// Nightlies kept the IPC-2581 function mode under "mode" and wrote empty strings by default
156BOOST_AUTO_TEST_CASE( Ipc2581NightlyContentKeysStillLoad )
157{
158 using DATA_SET = JOB_EXPORT_PCB_FAB::DATA_SET;
159
161 job.FromJson( nlohmann::json{ { "mode", "FABRICATION" },
162 { "sections", "KE" },
163 { "net_names", "anonymize" },
164 { "ref_des", "omit" } } );
165 BOOST_CHECK( job.m_dataSet == DATA_SET::FABRICATION );
167 BOOST_CHECK_EQUAL( *job.m_sections, wxString( wxS( "KE" ) ) );
170
171 nlohmann::json json;
172 job.ToJson( json );
173 BOOST_CHECK_EQUAL( json.at( "data_set" ).get<std::string>(), "fabrication" );
174 BOOST_CHECK( !json.contains( "mode" ) );
175
176 JOB_EXPORT_PCB_IPC2581 reloaded;
177 reloaded.FromJson( json );
178 BOOST_CHECK( reloaded.m_dataSet == DATA_SET::FABRICATION );
179
181 unset.FromJson( nlohmann::json{ { "mode", "" }, { "net_names", "" }, { "ref_des", "" } } );
182 BOOST_CHECK( unset.m_dataSet == DATA_SET::USERDEF );
185
186 // An unknown token must not turn into the broadest data set
188 bogus.FromJson( nlohmann::json{ { "mode", "bogus" } } );
189 BOOST_CHECK( bogus.m_dataSet == DATA_SET::COUNT );
190
191 BOOST_CHECK( JOB_EXPORT_PCB_FAB::DataSetToken( DATA_SET::COUNT ).empty() );
192 BOOST_CHECK_EQUAL( JOB_EXPORT_PCB_FAB::DataSetToken( DATA_SET::FABRICATION ), "fabrication" );
193
194 JOB_EXPORT_PCB_ODB bogusOdb;
195 bogusOdb.FromJson( nlohmann::json{ { "data_set", "bogus" } } );
196 BOOST_CHECK( bogusOdb.m_dataSet == DATA_SET::COUNT );
197}
198
199
200BOOST_AUTO_TEST_CASE( OdbMappedMpnFieldRoundTrips )
201{
203 job.m_colMfgPn = wxS( "MPN" );
204 nlohmann::json json;
205 job.ToJson( json );
206 BOOST_CHECK_EQUAL( json.at( "field_bom_map.mfg_pn" ).get<std::string>(), "MPN" );
207
208 JOB_EXPORT_PCB_ODB loaded;
209 loaded.FromJson( json );
210 BOOST_CHECK_EQUAL( loaded.m_colMfgPn, wxString( wxS( "MPN" ) ) );
211
213 ipc.FromJson( nlohmann::json{ { "field_bom_map.mfg_pn", "IPC-MPN" } } );
214 nlohmann::json ipcJson;
215 ipc.ToJson( ipcJson );
216 BOOST_CHECK_EQUAL( ipcJson.at( "field_bom_map.mfg_pn" ).get<std::string>(), "IPC-MPN" );
217}
218
219
220BOOST_AUTO_TEST_CASE( OdbExportOptionsRoundTrip )
221{
222 nlohmann::json options = { { "origin", "aux" },
223 { "product_name", "Fab Rev A" },
224 { "data_set", "assembly" },
225 { "sections", "ACOP" },
226 { "board_metadata", false },
227 { "net_names", "anonymize" },
228 { "layers", nlohmann::json::array( { { { "layer", "F.Cu" },
229 { "include", false },
230 { "odb_name", "front_copper" },
231 { "odb_type", "SIGNAL" } } } ) } };
233 job.FromJson( options );
234 nlohmann::json roundTrip;
235 job.ToJson( roundTrip );
236
237 for( auto it = options.begin(); it != options.end(); ++it )
238 BOOST_CHECK_MESSAGE( roundTrip.contains( it.key() ) && roundTrip[it.key()] == it.value(), it.key() );
239
240 JOB_EXPORT_PCB_ODB loaded;
241 loaded.FromJson( roundTrip );
242 nlohmann::json again;
243 loaded.ToJson( again );
244 BOOST_CHECK( roundTrip == again );
245}
246
247
248BOOST_AUTO_TEST_CASE( OdbInvalidLayerOverrideRetainsAutomaticMetadata )
249{
251 job.FromJson( nlohmann::json{
252 { "layers", nlohmann::json::array( { { { "layer", "F.Cu" }, { "include", "not-a-boolean" } },
253 { { "layer", "B.Cu" }, { "odb_name", 12 } },
254 { { "layer", "In1.Cu" }, { "odb_type", false } } } ) } } );
255
256 BOOST_REQUIRE_EQUAL( job.m_layerOverrides.size(), 3u );
257
258 for( const ODB_LAYER_OVERRIDE& override : job.m_layerOverrides )
259 BOOST_CHECK( override.m_layer == UNDEFINED_LAYER );
260}
261
262
263BOOST_AUTO_TEST_CASE( OdbLayerOverrideUtf8IgnoresProcessLocale )
264{
266 job.m_layerOverrides.push_back( { F_Cu, true, wxString::FromUTF8( "Résumé_日本" ), wxS( "SIGNAL" ) } );
267 nlohmann::json json;
268
269 {
270 struct RESTORE_LOCALE
271 {
272 std::string m_previous = std::setlocale( LC_CTYPE, nullptr );
273 ~RESTORE_LOCALE() { std::setlocale( LC_CTYPE, m_previous.c_str() ); }
274 } restoreLocale;
275
276 BOOST_REQUIRE( std::setlocale( LC_CTYPE, "C" ) );
277 job.ToJson( json );
278 }
279
280 BOOST_CHECK_EQUAL( json.at( "layers" ).at( 0 ).at( "odb_name" ).get<std::string>(), "Résumé_日本" );
281}
282
283
284BOOST_AUTO_TEST_CASE( Ipc2581CheckZonesOptionExists )
285{
287 nlohmann::json options = { { "check_zones", true } };
288 ipc.FromJson( options );
289 nlohmann::json roundTrip;
290 ipc.ToJson( roundTrip );
291 BOOST_CHECK( roundTrip.value( "check_zones", false ) );
292}
293
294
295BOOST_AUTO_TEST_CASE( Pcb3dVariantRoundTrip )
296{
298 job.m_variant = wxS( "var-A" );
299
300 nlohmann::json j;
301 job.ToJson( j );
302
303 BOOST_CHECK_EQUAL( j.value( "variant", "" ), "var-A" );
304
305 JOB_EXPORT_PCB_3D loaded;
306 loaded.FromJson( j );
307
308 BOOST_CHECK( loaded.m_variant == wxS( "var-A" ) );
309}
310
311
312BOOST_AUTO_TEST_CASE( PcbPlotVariantRoundTrip )
313{
315 job.m_variant = wxS( "VarPlot" );
316
317 nlohmann::json j;
318 job.ToJson( j );
319
320 BOOST_CHECK_EQUAL( j.value( "variant", "" ), "VarPlot" );
321
323 loaded.FromJson( j );
324
325 BOOST_CHECK( loaded.m_variant == wxS( "VarPlot" ) );
326}
327
328
329BOOST_AUTO_TEST_CASE( PcbOdbVariantRoundTrip )
330{
332 job.m_variantNames = { wxS( "VarOdb" ) };
333
334 nlohmann::json j;
335 job.ToJson( j );
336
337 BOOST_CHECK_EQUAL( j.value( "variant", "" ), "VarOdb" );
338
339 JOB_EXPORT_PCB_ODB loaded;
340 loaded.FromJson( j );
341
342 BOOST_REQUIRE_EQUAL( loaded.m_variantNames.size(), 1u );
343 BOOST_CHECK( loaded.m_variantNames.front() == wxS( "VarOdb" ) );
344}
345
346
347BOOST_AUTO_TEST_CASE( PcbIpc2581VariantRoundTrip )
348{
350 job.m_variantNames = { wxS( "VarIpc" ) };
351
352 nlohmann::json j;
353 job.ToJson( j );
354
355 BOOST_CHECK_EQUAL( j.value( "variant", "" ), "VarIpc" );
356
358 loaded.FromJson( j );
359
360 BOOST_REQUIRE_EQUAL( loaded.m_variantNames.size(), 1u );
361 BOOST_CHECK( loaded.m_variantNames.front() == wxS( "VarIpc" ) );
362}
363
364
365BOOST_AUTO_TEST_CASE( Ipc2581ExplicitEmptySectionsRoundTrip )
366{
368 job.FromJson( nlohmann::json{ { "mode", "USERDEF" }, { "sections", "" }, { "custom_sections", true } } );
369
370 nlohmann::json saved;
371 job.ToJson( saved );
372 BOOST_CHECK( saved.value( "custom_sections", false ) );
373 BOOST_CHECK_EQUAL( saved.at( "sections" ).get<std::string>(), "" );
374
376 loaded.FromJson( saved );
377 nlohmann::json roundTrip;
378 loaded.ToJson( roundTrip );
379 BOOST_CHECK( roundTrip.value( "custom_sections", false ) );
380
382 legacy.FromJson( nlohmann::json{ { "sections", "" } } );
383 nlohmann::json legacyJson;
384 legacy.ToJson( legacyJson );
385 BOOST_CHECK( !legacyJson.value( "custom_sections", false ) );
386 BOOST_CHECK( !legacy.m_sections );
387
388 JOB_EXPORT_PCB_IPC2581 legacyNonempty;
389 legacyNonempty.FromJson( nlohmann::json{ { "sections", "K" } } );
390 BOOST_CHECK( legacyNonempty.m_sections );
391}
392
393
394BOOST_AUTO_TEST_CASE( PcbRenderVariantRoundTrip )
395{
396 JOB_PCB_RENDER job;
397 job.m_variant = wxS( "VarRender" );
398
399 nlohmann::json j;
400 job.ToJson( j );
401
402 BOOST_CHECK_EQUAL( j.value( "variant", "" ), "VarRender" );
403
404 JOB_PCB_RENDER loaded;
405 loaded.FromJson( j );
406
407 BOOST_CHECK( loaded.m_variant == wxS( "VarRender" ) );
408}
409
410
411BOOST_AUTO_TEST_CASE( SchNetlistVariantNamesRoundTrip )
412{
414 job.m_variantNames = { wxS( "var1" ), wxS( "var2" ) };
415
416 nlohmann::json j;
417 job.ToJson( j );
418
419 BOOST_REQUIRE( j.contains( "variant_names" ) );
420 BOOST_REQUIRE( j.at( "variant_names" ).is_array() );
421 BOOST_CHECK_EQUAL( j.at( "variant_names" ).size(), 2u );
422
424 loaded.FromJson( j );
425
426 BOOST_REQUIRE_EQUAL( loaded.m_variantNames.size(), 2u );
427 BOOST_CHECK( loaded.m_variantNames[0] == wxS( "var1" ) );
428 BOOST_CHECK( loaded.m_variantNames[1] == wxS( "var2" ) );
429}
430
431
432BOOST_AUTO_TEST_CASE( SchPlotVariantNamesRoundTrip )
433{
434 JOB_EXPORT_SCH_PLOT job( false );
435 job.m_variantNames = { wxS( "var1" ), wxS( "var2" ) };
436
437 nlohmann::json j;
438 job.ToJson( j );
439
440 BOOST_REQUIRE( j.contains( "variant_names" ) );
441 BOOST_REQUIRE( j.at( "variant_names" ).is_array() );
442 BOOST_CHECK_EQUAL( j.at( "variant_names" ).size(), 2u );
443
444 JOB_EXPORT_SCH_PLOT loaded( false );
445 loaded.FromJson( j );
446
447 BOOST_REQUIRE_EQUAL( loaded.m_variantNames.size(), 2u );
448 BOOST_CHECK( loaded.m_variantNames[0] == wxS( "var1" ) );
449 BOOST_CHECK( loaded.m_variantNames[1] == wxS( "var2" ) );
450}
451
452
void FromJson(const nlohmann::json &aJson) override
DATA_SET
Columns of the IPC-2581C function mode table, Table 4 p 80.
static std::string DataSetToken(DATA_SET aDataSet)
Lower case token that the job file and the CLI use for aDataSet Empty when it has none.
void ToJson(nlohmann::json &aJson) const override
std::optional< wxString > m_sections
Section key that replaces the optional sections of the data set Unset lets the data set select them a...
std::vector< wxString > m_variantNames
void FromJson(const nlohmann::json &aJson) override
VARIANT_PACKAGING m_variantPackaging
std::vector< ODB_LAYER_OVERRIDE > m_layerOverrides
std::vector< wxString > m_variantNames
std::vector< wxString > m_variantNames
virtual void FromJson(const nlohmann::json &j)
Definition job.cpp:60
virtual void ToJson(nlohmann::json &j) const
Definition job.cpp:67
static bool empty(const wxTextEntryBase *aCtrl)
nlohmann::json json
Definition gerbview.cpp:50
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
std::string GetTestDataRootDir()
wxString GetDefaultVariantName()
wxString ExpandVariantOutputPath(const wxString &aPath, const wxString &aVariantToken)
wxString GetVariantFileToken(const wxString &aVariantName)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static void roundTrip(const DIFF_VALUE &aValue)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(OdbVariantFileTokenKeepsNamesAndRemovesSeparators)
BOOST_CHECK_EQUAL(result, "25.4")