KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_api_jobs.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <fstream>
21#include <set>
22#include <sstream>
23#include <string>
24
25#include <wx/filename.h>
26
27#include <boost/test/unit_test.hpp>
28#include <qa_utils/file_utils.h>
30
31#include "api_e2e_utils.h"
32
33#include <api/board/board_jobs.pb.h>
34#include <api/board/board_types.pb.h>
35#include <api/schematic/schematic_jobs.pb.h>
36
37
44bool textFilesMatch( const wxString& aGoldenPath, const wxString& aGeneratedPath, int aSkipLines )
45{
46 std::ifstream goldenStream( aGoldenPath.ToStdString() );
47 std::ifstream generatedStream( aGeneratedPath.ToStdString() );
48
49 if( !goldenStream.is_open() )
50 {
51 BOOST_TEST_MESSAGE( "Cannot open golden file: " + aGoldenPath );
52 return false;
53 }
54
55 if( !generatedStream.is_open() )
56 {
57 BOOST_TEST_MESSAGE( "Cannot open generated file: " + aGeneratedPath );
58 return false;
59 }
60
61 std::string goldenLine, generatedLine;
62
63 for( int i = 0; i < aSkipLines; ++i )
64 {
65 std::getline( goldenStream, goldenLine );
66 std::getline( generatedStream, generatedLine );
67 }
68
69 int lineNum = aSkipLines + 1;
70
71 while( std::getline( goldenStream, goldenLine ) )
72 {
73 if( !std::getline( generatedStream, generatedLine ) )
74 {
75 BOOST_TEST_MESSAGE( "Generated file is shorter than golden at line " << lineNum );
76 return false;
77 }
78
79 if( goldenLine != generatedLine )
80 {
81 BOOST_TEST_MESSAGE( "Mismatch at line " << lineNum << ":\n"
82 << " golden: " << goldenLine << "\n"
83 << " generated: " << generatedLine );
84 return false;
85 }
86
87 ++lineNum;
88 }
89
90 if( std::getline( generatedStream, generatedLine ) )
91 {
92 BOOST_TEST_MESSAGE( "Generated file is longer than golden after line " << lineNum );
93 return false;
94 }
95
96 return true;
97}
98
99
100static size_t countOccurrences( const std::string& aHaystack, const std::string& aNeedle )
101{
102 size_t count = 0;
103
104 for( size_t pos = aHaystack.find( aNeedle ); pos != std::string::npos;
105 pos = aHaystack.find( aNeedle, pos + aNeedle.size() ) )
106 {
107 ++count;
108 }
109
110 return count;
111}
112
113
117static std::set<std::string> collectColours( const std::string& aSvg )
118{
119 std::set<std::string> colours;
120
121 for( size_t pos = aSvg.find( '#' ); pos != std::string::npos; pos = aSvg.find( '#', pos + 1 ) )
122 {
123 std::string colour = aSvg.substr( pos, 7 );
124
125 if( colour.size() < 7 )
126 continue;
127
128 if( colour.find_first_not_of( "0123456789ABCDEFabcdef", 1 ) != std::string::npos )
129 continue;
130
131 colours.insert( colour );
132 }
133
134 return colours;
135}
136
137
138BOOST_AUTO_TEST_SUITE( ApiJobs )
139
140
142{
143 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
144
145 wxString testDataDir =
146 wxString::FromUTF8( KI_TEST::GetTestDataRootDir() ) + wxS( "cli/artwork_generation_regressions/" );
147
148 wxFileName boardPath( testDataDir, wxS( "ZoneFill-4.0.7.kicad_pcb" ) );
149
150 kiapi::common::types::DocumentSpecifier document;
151
152 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPath.GetFullPath(), &document ),
153 "OpenDocument failed: " + Client().LastError() );
154
155
156 KI_TEST::SCOPED_TEMP_DIR tempDir( "api_job_svg" );
157 wxFileName outputPath( tempDir.ChildPathStr( "plot.svg" ) );
158
159 kiapi::board::jobs::RunBoardJobExportSvg request;
160 *request.mutable_job_settings()->mutable_document() = document;
161 request.mutable_job_settings()->set_output_path( outputPath.GetFullPath().ToUTF8().data() );
162
163 request.mutable_plot_settings()->add_layers( kiapi::board::types::BL_F_Cu );
164 request.mutable_plot_settings()->set_black_and_white( true );
165 request.mutable_plot_settings()->set_plot_drawing_sheet( false );
166 request.mutable_plot_settings()->set_drill_marks( kiapi::board::jobs::PDM_FULL );
167 request.set_page_mode( kiapi::board::jobs::BJPM_EACH_LAYER_OWN_FILE );
168 request.set_precision( 4 );
169
170 kiapi::common::types::RunJobResponse response;
171 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ), "RunJob failed: " + Client().LastError() );
172
173 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
174 "Job failed: " + wxString::FromUTF8( response.message() ) );
175
176 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0, "Job returned no output paths" );
177
178 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
179 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedPath ), "Generated SVG does not exist: " + generatedPath );
180
181 // Plot fidelity is covered by the kicad-cli SVG regression tests, which rasterize and compare
182 // against golden artwork. All this test has to establish is that the job honoured the request.
183 const std::string svg = KI_TEST::LoadStringData( generatedPath );
184
185 BOOST_REQUIRE_MESSAGE( !svg.empty(), "Generated SVG is empty or unreadable: " + generatedPath );
186 BOOST_CHECK( svg.find( "<svg" ) != std::string::npos );
187 BOOST_CHECK( svg.find( "</svg>" ) != std::string::npos );
188
189 // The board page is A4, and the four decimals are the requested precision
190 BOOST_CHECK( svg.find( "width=\"297.0022mm\" height=\"210.0072mm\"" ) != std::string::npos );
191
192 std::set<std::string> colours = collectColours( svg );
193 BOOST_REQUIRE_MESSAGE( !colours.empty(), "Plot declares no colours at all" );
194
195 for( const std::string& colour : colours )
196 {
197 BOOST_CHECK_MESSAGE( colour == "#000000" || colour == "#FFFFFF",
198 "black_and_white was requested but the plot contains " + colour );
199 }
200
201 // F.Cu of this board is dense; a near-empty plot means the layer selection was dropped
202 BOOST_CHECK_GT( countOccurrences( svg, "<path" ), 100u );
203}
204
205
206// Reads the pixel dimensions out of the IHDR chunk of a PNG file.
207// Returns true on success; on failure aWidth/aHeight are untouched.
208static bool pngDimensions( const wxString& aPath, uint32_t& aWidth, uint32_t& aHeight )
209{
210 std::ifstream stream( aPath.ToStdString(), std::ios::binary );
211
212 if( !stream.is_open() )
213 return false;
214
215 char header[8];
216
217 if( !stream.read( header, 8 ) || memcmp( header, "\x89PNG\r\n\x1a\n", 8 ) != 0 )
218 return false;
219
220 char ihdr[16];
221
222 if( !stream.read( ihdr, 16 ) )
223 return false;
224
225 auto readBE32 = []( const char* aBytes )
226 {
227 return ( static_cast<uint32_t>( static_cast<unsigned char>( aBytes[0] ) ) << 24 )
228 | ( static_cast<uint32_t>( static_cast<unsigned char>( aBytes[1] ) ) << 16 )
229 | ( static_cast<uint32_t>( static_cast<unsigned char>( aBytes[2] ) ) << 8 )
230 | ( static_cast<uint32_t>( static_cast<unsigned char>( aBytes[3] ) ) );
231 };
232
233 aWidth = readBE32( ihdr + 8 );
234 aHeight = readBE32( ihdr + 12 );
235 return true;
236}
237
238
240{
241 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
242
243 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetTestDataRootDir() ) + wxS( "cli/basic_test/" );
244
245 wxFileName schPath( testDataDir, wxS( "basic_test.kicad_sch" ) );
246
247 kiapi::common::types::DocumentSpecifier document;
248
249 BOOST_REQUIRE_MESSAGE(
250 Client().OpenDocument( schPath.GetFullPath(), kiapi::common::types::DOCTYPE_SCHEMATIC, &document ),
251 "OpenDocument failed: " + Client().LastError() );
252
253 // --- All-sheets mode: output path is a directory
254 KI_TEST::SCOPED_TEMP_DIR tempDir( "api_job_sch_png" );
255 wxString outputDir = tempDir.CreateChildDirStr( "all" ) + wxFileName::GetPathSeparator();
256
257 {
258 kiapi::schematic::jobs::RunSchematicJobExportPng request;
259 *request.mutable_job_settings()->mutable_document() = document;
260 request.mutable_job_settings()->set_output_path( outputDir.ToUTF8().data() );
261 request.mutable_plot_settings()->set_sheet_mode( kiapi::schematic::jobs::SJSM_ALL_SHEETS );
262 request.mutable_plot_settings()->set_black_and_white( true );
263 request.set_dpi( 150 );
264
265 kiapi::common::types::RunJobResponse response;
266 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ),
267 "RunJob failed: " + Client().LastError() );
268
269 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
270 "Job failed: " + wxString::FromUTF8( response.message() ) );
271 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0, "Job returned no output paths" );
272
273 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
274 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedPath ),
275 "Generated PNG does not exist: " + generatedPath );
276
277 // basic_test.kicad_sch is A4 landscape; 297mm at 150dpi = 1754px (+/- rounding)
278 uint32_t width = 0, height = 0;
279 BOOST_REQUIRE_MESSAGE( pngDimensions( generatedPath, width, height ),
280 "Generated file is not a valid PNG: " + generatedPath );
281 BOOST_CHECK_MESSAGE( width >= 1750 && width <= 1758,
282 "Unexpected PNG width " << width << " for 150dpi A4 landscape" );
283 BOOST_CHECK_MESSAGE( height >= 1236 && height <= 1244,
284 "Unexpected PNG height " << height << " for 150dpi A4 landscape" );
285 }
286
287 // --- Single-sheet mode: output path is a file name
288 wxFileName singlePath( tempDir.ChildPathStr( "single.png" ) );
289
290 {
291 kiapi::schematic::jobs::RunSchematicJobExportPng request;
292 *request.mutable_job_settings()->mutable_document() = document;
293 request.mutable_job_settings()->set_output_path( singlePath.GetFullPath().ToUTF8().data() );
294 request.mutable_plot_settings()->set_sheet_mode( kiapi::schematic::jobs::SJSM_SINGLE_SHEET );
295 request.mutable_plot_settings()->set_black_and_white( true );
296 request.set_dpi( 300 );
297
298 kiapi::common::types::RunJobResponse response;
299 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ),
300 "Single-sheet RunJob failed: " + Client().LastError() );
301
302 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
303 "Single-sheet job failed: " + wxString::FromUTF8( response.message() ) );
304 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0,
305 "Single-sheet job returned no output paths" );
306
307 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
308 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedPath ),
309 "Generated PNG does not exist: " + generatedPath );
310
311 // 297mm at 300dpi = 3508px (+/- rounding)
312 uint32_t width = 0, height = 0;
313 BOOST_REQUIRE_MESSAGE( pngDimensions( generatedPath, width, height ),
314 "Generated file is not a valid PNG: " + generatedPath );
315 BOOST_CHECK_MESSAGE( width >= 3500 && width <= 3516,
316 "Unexpected PNG width " << width << " for 300dpi A4 landscape" );
317
318 // The single-file output path must be honored exactly
319 BOOST_CHECK_MESSAGE( generatedPath == singlePath.GetFullPath(),
320 "Single-sheet output path not honored: " + generatedPath );
321 }
322
323 // --- Out-of-range dpi is rejected with a bad-request error
324 {
325 kiapi::schematic::jobs::RunSchematicJobExportPng request;
326 *request.mutable_job_settings()->mutable_document() = document;
327 request.mutable_job_settings()->set_output_path( outputDir.ToUTF8().data() );
328 request.mutable_plot_settings()->set_sheet_mode( kiapi::schematic::jobs::SJSM_ALL_SHEETS );
329 request.set_dpi( 10 );
330
331 kiapi::common::types::RunJobResponse response;
332 BOOST_CHECK_MESSAGE( !Client().RunJob( request, &response ),
333 "Out-of-range dpi was not rejected" );
334 }
335
336 // --- Omitted dpi falls back to the GUI default (300)
337 {
338 wxString defaultDir = tempDir.CreateChildDirStr( "default" ) + wxFileName::GetPathSeparator();
339
340 kiapi::schematic::jobs::RunSchematicJobExportPng request;
341 *request.mutable_job_settings()->mutable_document() = document;
342 request.mutable_job_settings()->set_output_path( defaultDir.ToUTF8().data() );
343 request.mutable_plot_settings()->set_sheet_mode( kiapi::schematic::jobs::SJSM_ALL_SHEETS );
344
345 kiapi::common::types::RunJobResponse response;
346 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ),
347 "Default-dpi RunJob failed: " + Client().LastError() );
348 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
349 "Default-dpi job failed: " + wxString::FromUTF8( response.message() ) );
350 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0,
351 "Default-dpi job returned no output paths" );
352
353 uint32_t width = 0, height = 0;
354 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
355 BOOST_REQUIRE_MESSAGE( pngDimensions( generatedPath, width, height ),
356 "Generated file is not a valid PNG: " + generatedPath );
357 BOOST_CHECK_MESSAGE( width >= 3500 && width <= 3516,
358 "Default dpi should be 300; got width " << width );
359 }
360}
361
362
364{
365 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
366
367 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetTestDataRootDir() ) + wxS( "cli/basic_test/" );
368
369 wxFileName boardPath( testDataDir, wxS( "basic_test.kicad_pcb" ) );
370
371 kiapi::common::types::DocumentSpecifier document;
372
373 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPath.GetFullPath(), &document ),
374 "OpenDocument failed: " + Client().LastError() );
375
376 KI_TEST::SCOPED_TEMP_DIR tempDir( "api_job_drill" );
377 wxString outputDir = tempDir.CreateChildDirStr( "drill" ) + wxFileName::GetPathSeparator();
378
379 kiapi::board::jobs::RunBoardJobExportDrill request;
380 *request.mutable_job_settings()->mutable_document() = document;
381 request.mutable_job_settings()->set_output_path( outputDir.ToUTF8().data() );
382 request.set_format( kiapi::board::jobs::DF_EXCELLON );
383
384 kiapi::common::types::RunJobResponse response;
385 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ), "RunJob failed: " + Client().LastError() );
386
387 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
388 "Job failed: " + wxString::FromUTF8( response.message() ) );
389
390 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0, "Job returned no output paths" );
391 wxString generatedDrillPath;
392
393 for( int i = 0; i < response.output_path_size(); ++i )
394 {
395 wxString outputPath = wxString::FromUTF8( response.output_path( i ) );
396
397 if( outputPath.EndsWith( wxS( ".drl" ) ) )
398 {
399 generatedDrillPath = outputPath;
400 break;
401 }
402 }
403
404 BOOST_REQUIRE_MESSAGE( !generatedDrillPath.IsEmpty(), "No .drl file found in job output" );
405 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedDrillPath ),
406 "Generated drill file does not exist: " + generatedDrillPath );
407
408 wxString goldenPath = testDataDir + wxS( "basic_test_excellon_inches.drl" );
409 BOOST_CHECK_MESSAGE( textFilesMatch( goldenPath, generatedDrillPath, 5 ),
410 "Drill output does not match golden file" );
411}
412
413
415{
416 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
417
418 wxString testDataDir =
419 wxString::FromUTF8( KI_TEST::GetTestDataRootDir() ) + wxS( "cli/artwork_generation_regressions/" );
420
421 wxFileName boardPath( testDataDir, wxS( "ZoneFill-4.0.7.kicad_pcb" ) );
422
423 kiapi::common::types::DocumentSpecifier document;
424
425 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPath.GetFullPath(), &document ),
426 "OpenDocument failed: " + Client().LastError() );
427
428 KI_TEST::SCOPED_TEMP_DIR tempDir( "api_job_pcb_png" );
429
430 // --- MULTI mode: one file per layer into a directory
431 {
432 wxString outputDir = tempDir.CreateChildDirStr( "multi" ) + wxFileName::GetPathSeparator();
433
434 kiapi::board::jobs::RunBoardJobExportPng request;
435 *request.mutable_job_settings()->mutable_document() = document;
436 request.mutable_job_settings()->set_output_path( outputDir.ToUTF8().data() );
437 request.mutable_plot_settings()->add_layers( kiapi::board::types::BL_F_Cu );
438 request.mutable_plot_settings()->set_black_and_white( true );
439 request.set_page_mode( kiapi::board::jobs::BJPM_EACH_LAYER_OWN_FILE );
440 request.set_dpi( 150 );
441
442 kiapi::common::types::RunJobResponse response;
443 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ),
444 "RunJob failed: " + Client().LastError() );
445
446 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
447 "Job failed: " + wxString::FromUTF8( response.message() ) );
448 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0, "Job returned no output paths" );
449
450 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
451 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedPath ),
452 "Generated PNG does not exist: " + generatedPath );
453
454 uint32_t width = 0, height = 0;
455 BOOST_REQUIRE_MESSAGE( pngDimensions( generatedPath, width, height ),
456 "Generated file is not a valid PNG: " + generatedPath );
457 BOOST_CHECK_MESSAGE( width >= 1750 && width <= 1758,
458 "Unexpected PNG width " << width << " for 150dpi A4 landscape" );
459 }
460
461 // --- SINGLE mode: all layers composited into one file
462 {
463 wxFileName outputPath( tempDir.ChildPathStr( "single.png" ) );
464
465 kiapi::board::jobs::RunBoardJobExportPng request;
466 *request.mutable_job_settings()->mutable_document() = document;
467 request.mutable_job_settings()->set_output_path( outputPath.GetFullPath().ToUTF8().data() );
468 request.mutable_plot_settings()->add_layers( kiapi::board::types::BL_F_Cu );
469 request.mutable_plot_settings()->add_layers( kiapi::board::types::BL_F_SilkS );
470 request.mutable_plot_settings()->set_black_and_white( true );
471 request.set_page_mode( kiapi::board::jobs::BJPM_ALL_LAYERS_ONE_PAGE );
472 request.set_dpi( 300 );
473
474 kiapi::common::types::RunJobResponse response;
475 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ),
476 "Single-mode RunJob failed: " + Client().LastError() );
477
478 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
479 "Single-mode job failed: " + wxString::FromUTF8( response.message() ) );
480 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0,
481 "Single-mode job returned no output paths" );
482
483 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
484 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedPath ),
485 "Generated PNG does not exist: " + generatedPath );
486 BOOST_CHECK_MESSAGE( generatedPath == outputPath.GetFullPath(),
487 "Single-mode output path not honored: " + generatedPath );
488
489 uint32_t width = 0, height = 0;
490 BOOST_REQUIRE_MESSAGE( pngDimensions( generatedPath, width, height ),
491 "Generated file is not a valid PNG: " + generatedPath );
492 BOOST_CHECK_MESSAGE( width >= 3500 && width <= 3516,
493 "Unexpected PNG width " << width << " for 300dpi A4 landscape" );
494 }
495}
496
497
499{
500 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
501
502 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetTestDataRootDir() ) + wxS( "cli/basic_test/" );
503
504 wxFileName schPath( testDataDir, wxS( "basic_test.kicad_sch" ) );
505
506 kiapi::common::types::DocumentSpecifier document;
507
508 BOOST_REQUIRE_MESSAGE(
509 Client().OpenDocument( schPath.GetFullPath(), kiapi::common::types::DOCTYPE_SCHEMATIC, &document ),
510 "OpenDocument failed: " + Client().LastError() );
511
512 KI_TEST::SCOPED_TEMP_DIR tempDir( "api_job_netlist" );
513 wxFileName outputPath( tempDir.ChildPathStr( "netlist.cadstar" ) );
514
515 kiapi::schematic::jobs::RunSchematicJobExportNetlist request;
516 *request.mutable_job_settings()->mutable_document() = document;
517 request.mutable_job_settings()->set_output_path( outputPath.GetFullPath().ToUTF8().data() );
518 request.set_format( kiapi::schematic::jobs::SNF_CADSTAR );
519
520 kiapi::common::types::RunJobResponse response;
521 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ), "RunJob failed: " + Client().LastError() );
522
523 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
524 "Job failed: " + wxString::FromUTF8( response.message() ) );
525
526 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0, "Job returned no output paths" );
527
528 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
529 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedPath ),
530 "Generated netlist does not exist: " + generatedPath );
531
532 // 3 header lines to skip (contain timestamp/version)
533 wxString goldenPath = testDataDir + wxS( "basic_test.netlist.cadstar" );
534 BOOST_CHECK_MESSAGE( textFilesMatch( goldenPath, generatedPath, 3 ), "Netlist output does not match golden file" );
535}
536
537
539{
540 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
541
542 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetTestDataRootDir() ) + wxS( "cli/variants/" );
543
544 wxFileName schPath( testDataDir, wxS( "variants.kicad_sch" ) );
545
546 kiapi::common::types::DocumentSpecifier document;
547
548 BOOST_REQUIRE_MESSAGE(
549 Client().OpenDocument( schPath.GetFullPath(), kiapi::common::types::DOCTYPE_SCHEMATIC, &document ),
550 "OpenDocument failed: " + Client().LastError() );
551
552 KI_TEST::SCOPED_TEMP_DIR tempDir( "api_job_bom" );
553 wxFileName outputPath( tempDir.ChildPathStr( "bom.csv" ) );
554
555 kiapi::schematic::jobs::RunSchematicJobExportBOM request;
556 *request.mutable_job_settings()->mutable_document() = document;
557 request.mutable_job_settings()->set_output_path( outputPath.GetFullPath().ToUTF8().data() );
558 request.set_exclude_dnp( true );
559
560 request.mutable_format()->set_preset_name( "CSV" );
561
562 auto* refField = request.mutable_fields()->add_fields();
563 refField->set_name( "Reference" );
564 refField->set_label( "Refs" );
565 refField->set_group_by( false );
566
567 auto* valField = request.mutable_fields()->add_fields();
568 valField->set_name( "Value" );
569 valField->set_label( "Value" );
570 valField->set_group_by( false );
571
572 kiapi::common::types::RunJobResponse response;
573 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ), "RunJob failed: " + Client().LastError() );
574
575 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
576 "Job failed: " + wxString::FromUTF8( response.message() ) );
577
578 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0, "Job returned no output paths" );
579
580 wxString generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
581 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( generatedPath ), "Generated BOM does not exist: " + generatedPath );
582
583 wxString goldenPath = testDataDir + wxS( "variants_default.bom.csv" );
584 BOOST_CHECK_MESSAGE( textFilesMatch( goldenPath, generatedPath, 0 ), "BOM output does not match golden file" );
585
586 request.mutable_format()->clear_preset_name();
587 request.mutable_format()->set_field_delimiter( "," );
588 request.mutable_format()->set_string_delimiter( "\"" );
589 request.mutable_format()->set_ref_delimiter( "," );
590 request.mutable_format()->set_ref_range_delimiter( "" );
591 request.mutable_format()->set_include_byte_order_mark( true );
592 response.Clear();
593
594 BOOST_REQUIRE_MESSAGE( Client().RunJob( request, &response ),
595 "BOM job with byte order mark failed: " + Client().LastError() );
596
597 BOOST_REQUIRE_MESSAGE( response.status() == kiapi::common::types::JS_SUCCESS,
598 "BOM job with byte order mark failed: " + wxString::FromUTF8( response.message() ) );
599 BOOST_REQUIRE_MESSAGE( response.output_path_size() > 0, "BOM job with byte order mark returned no output paths" );
600
601 generatedPath = wxString::FromUTF8( response.output_path( 0 ) );
602 std::ifstream generatedStream( generatedPath.ToStdString(), std::ios::binary );
603 std::ifstream goldenStream( goldenPath.ToStdString(), std::ios::binary );
604 BOOST_REQUIRE( generatedStream.is_open() );
605 BOOST_REQUIRE( goldenStream.is_open() );
606
607 std::ostringstream generatedContents;
608 std::ostringstream goldenContents;
609 generatedContents << generatedStream.rdbuf();
610 goldenContents << goldenStream.rdbuf();
611
612 const std::string utf8ByteOrderMark( "\xEF\xBB\xBF", 3 );
613 const std::string bom = generatedContents.str();
614 BOOST_REQUIRE_GE( bom.size(), utf8ByteOrderMark.size() );
615 BOOST_CHECK_EQUAL( bom.substr( 0, utf8ByteOrderMark.size() ), utf8ByteOrderMark );
616 BOOST_CHECK_EQUAL( bom.substr( utf8ByteOrderMark.size() ), goldenContents.str() );
617
618 generatedStream.close();
619 goldenStream.close();
620}
621
622
Per-test fixture for API E2E tests.
wxString ChildPathStr(const wxString &aName) const
Get the path to a direct child of the temporary directory as a wxString, without creating the child.
wxString CreateChildDirStr(const wxString &aName) const
Create and return the path to a direct child directory as a wxString.
std::string GetTestDataRootDir()
std::string LoadStringData(const wxString &aPath)
Load the contents of a file into a string.
BOOST_FIXTURE_TEST_CASE(ServerStartsAndResponds, API_SERVER_E2E_FIXTURE)
static std::set< std::string > collectColours(const std::string &aSvg)
Return every distinct #RRGGBB literal appearing in aSvg.
bool textFilesMatch(const wxString &aGoldenPath, const wxString &aGeneratedPath, int aSkipLines)
Compare two text files line-by-line, optionally skipping the first aSkipLines lines of each file (to ...
static bool pngDimensions(const wxString &aPath, uint32_t &aWidth, uint32_t &aHeight)
BOOST_FIXTURE_TEST_CASE(ExportBoardSvg, API_SERVER_E2E_FIXTURE)
static size_t countOccurrences(const std::string &aHaystack, const std::string &aNeedle)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")