KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_api_e2e.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 "api_e2e_utils.h"
21
22#include <utility>
23
24#include <boost/test/unit_test.hpp>
25#include <qa_utils/file_utils.h>
26
27#include <magic_enum.hpp>
28
29#include <wx/filefn.h>
30#include <wx/filename.h>
31
32#include <api/board/board.pb.h>
33#include <api/board/board_commands.pb.h>
34#include <api/board/board_rules.pb.h>
35#include <pcb_field.h>
36
37
39{
40public:
42 m_tempRoot( "kicad_api_e2e" )
43 {
44 }
45
46 bool Create( wxString* aError, const wxString& aRulesName = wxS( "api_kitchen_sink.kicad_dru" ) )
47 {
48 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() );
49 wxFileName srcPcb( testDataDir, wxS( "api_kitchen_sink.kicad_pcb" ) );
50 wxFileName srcPro( testDataDir, wxS( "api_kitchen_sink.kicad_pro" ) );
51 wxFileName srcDru( testDataDir + wxFileName::GetPathSeparator() + aRulesName );
52
53 wxFileName dstPcb( m_tempRoot.PathStr(), srcPcb.GetFullName() );
54 wxFileName dstPro( m_tempRoot.PathStr(), srcPro.GetFullName() );
55 wxFileName dstDru( m_tempRoot.PathStr(), wxS( "api_kitchen_sink.kicad_dru" ) );
56
57 if( !wxCopyFile( srcPcb.GetFullPath(), dstPcb.GetFullPath(), true )
58 || !wxCopyFile( srcPro.GetFullPath(), dstPro.GetFullPath(), true )
59 || !wxCopyFile( srcDru.GetFullPath(), dstDru.GetFullPath(), true ) )
60 {
61 if( aError )
62 *aError = wxS( "Failed to copy fixture files into temporary directory" );
63
64 return false;
65 }
66
67 m_boardPath = dstPcb.GetFullPath();
68 return true;
69 }
70
71 const wxString& BoardPath() const { return m_boardPath; }
72
73private:
75 wxString m_boardPath;
76};
77
78
79bool SendGetBoardDesignRules( API_TEST_CLIENT& aClient, const kiapi::common::types::DocumentSpecifier& aDocument,
80 kiapi::board::commands::BoardDesignRulesResponse* aOut, wxString* aError = nullptr )
81{
82 kiapi::board::commands::GetBoardDesignRules request;
83 *request.mutable_board() = aDocument;
84
85 kiapi::common::ApiResponse response;
86
87 if( !aClient.SendCommand( request, &response ) )
88 {
89 if( aError )
90 *aError = aClient.LastError();
91
92 return false;
93 }
94
95 if( response.status().status() != kiapi::common::AS_OK )
96 {
97 if( aError )
98 *aError = response.status().error_message();
99
100 return false;
101 }
102
103 if( !response.message().UnpackTo( aOut ) )
104 {
105 if( aError )
106 *aError = wxS( "Failed to unpack BoardDesignRulesResponse" );
107
108 return false;
109 }
110
111 return true;
112}
113
114
115bool SendGetCustomDesignRules( API_TEST_CLIENT& aClient, const kiapi::common::types::DocumentSpecifier& aDocument,
116 kiapi::board::commands::CustomRulesResponse* aOut, wxString* aError = nullptr )
117{
118 kiapi::board::commands::GetCustomDesignRules request;
119 *request.mutable_board() = aDocument;
120
121 kiapi::common::ApiResponse response;
122
123 if( !aClient.SendCommand( request, &response ) )
124 {
125 if( aError )
126 *aError = aClient.LastError();
127
128 return false;
129 }
130
131 if( response.status().status() != kiapi::common::AS_OK )
132 {
133 if( aError )
134 *aError = response.status().error_message();
135
136 return false;
137 }
138
139 if( !response.message().UnpackTo( aOut ) )
140 {
141 if( aError )
142 *aError = wxS( "Failed to unpack CustomRulesResponse" );
143
144 return false;
145 }
146
147 return true;
148}
149
150
151BOOST_AUTO_TEST_SUITE( ApiE2E )
152
153
155{
156 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
157
158 BOOST_CHECK_MESSAGE( Client().GetVersion(), "GetVersion failed: " + Client().LastError() );
159}
160
161
163{
164 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
165
166 kiapi::common::types::DocumentSpecifier document;
167 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() );
168 wxFileName boardPath( testDataDir, wxS( "api_kitchen_sink.kicad_pcb" ) );
169
170 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPath.GetFullPath(), &document ),
171 "OpenDocument failed: " + Client().LastError() );
172
173 BOOST_REQUIRE( document.type() == kiapi::common::types::DOCTYPE_PCB );
174 BOOST_REQUIRE( boardPath.GetFullName().Matches( document.board_filename() ) );
175
176 int footprintCount = 0;
177
178 BOOST_REQUIRE_MESSAGE( Client().GetItemsCount( document, kiapi::common::types::KOT_PCB_FOOTPRINT, &footprintCount ),
179 "GetItems failed: " + Client().LastError() );
180
181 BOOST_CHECK_GT( footprintCount, 0 );
182}
183
184
185// A footprint stripped of its reference or value is a state neither the editor nor the const
186// field accessors can cope with, so the deletion has to be refused outright
187BOOST_FIXTURE_TEST_CASE( DeleteMandatoryFieldIsRefused, API_SERVER_E2E_FIXTURE )
188{
189 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
190
191 kiapi::common::types::DocumentSpecifier document;
192 wxFileName boardPath( wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ), wxS( "api_kitchen_sink.kicad_pcb" ) );
193
194 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPath.GetFullPath(), &document ),
195 "OpenDocument failed: " + Client().LastError() );
196
197 FOOTPRINT footprint( nullptr );
198
199 BOOST_REQUIRE_MESSAGE( Client().GetFirstFootprint( document, &footprint ),
200 "GetFirstFootprint failed: " + Client().LastError() );
201
202 const PCB_FIELD* reference = std::as_const( footprint ).GetField( FIELD_T::REFERENCE );
203 BOOST_REQUIRE( reference );
204
205 kiapi::common::commands::DeleteItems request;
206 *request.mutable_header()->mutable_document() = document;
207 request.add_item_ids()->set_value( reference->m_Uuid.AsStdString() );
208
209 kiapi::common::ApiResponse response;
210
211 BOOST_REQUIRE_MESSAGE( Client().SendCommand( request, &response ),
212 "DeleteItems failed: " + Client().LastError() );
213 BOOST_REQUIRE_EQUAL( response.status().status(), kiapi::common::AS_OK );
214
215 kiapi::common::commands::DeleteItemsResponse deleteResponse;
216
217 BOOST_REQUIRE( response.message().UnpackTo( &deleteResponse ) );
218 BOOST_REQUIRE_EQUAL( deleteResponse.deleted_items_size(), 1 );
219 BOOST_CHECK_EQUAL( deleteResponse.deleted_items( 0 ).status(),
220 kiapi::common::commands::ItemDeletionStatus::IDS_IMMUTABLE );
221
222 FOOTPRINT after( nullptr );
223
224 BOOST_REQUIRE_MESSAGE( Client().GetFirstFootprint( document, &after ),
225 "GetFirstFootprint after delete failed: " + Client().LastError() );
226
227 const PCB_FIELD* survivor = std::as_const( after ).GetField( FIELD_T::REFERENCE );
228
229 BOOST_REQUIRE( survivor );
230 BOOST_CHECK( survivor->m_Uuid == reference->m_Uuid );
231}
232
233
235{
236 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
237
238 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() );
239 wxFileName boardPathA( testDataDir, wxS( "api_kitchen_sink.kicad_pcb" ) );
240 wxFileName boardPathB( testDataDir, wxS( "padstacks.kicad_pcb" ) );
241
242 kiapi::common::types::DocumentSpecifier documentA;
243
244 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPathA.GetFullPath(), &documentA ),
245 "OpenDocument for first board failed: " + Client().LastError() );
246
247 FOOTPRINT footprintA( nullptr );
248
249 BOOST_REQUIRE_MESSAGE( Client().GetFirstFootprint( documentA, &footprintA ),
250 "GetFirstFootprint for first board failed: " + Client().LastError() );
251
252 // Switching projects requires an explicit close
253 kiapi::common::ApiStatusCode closeStatusA = kiapi::common::AS_UNKNOWN;
254
255 BOOST_REQUIRE_MESSAGE( Client().CloseDocument( &documentA, &closeStatusA ),
256 "CloseDocument for first board failed: " + Client().LastError() );
257 BOOST_CHECK_EQUAL( closeStatusA, kiapi::common::AS_OK );
258
259 kiapi::common::types::DocumentSpecifier documentB;
260
261 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPathB.GetFullPath(), &documentB ),
262 "OpenDocument for second board failed: " + Client().LastError() );
263
264 FOOTPRINT footprintB( nullptr );
265
266 BOOST_REQUIRE_MESSAGE( Client().GetFirstFootprint( documentB, &footprintB ),
267 "GetFirstFootprint for second board failed: " + Client().LastError() );
268
269 BOOST_CHECK_NE( footprintA.Similarity( footprintB ), 1.0 );
270
271 kiapi::common::ApiStatusCode closeStatus = kiapi::common::AS_UNKNOWN;
272
273 BOOST_REQUIRE_MESSAGE( Client().CloseDocument( &documentB, &closeStatus ),
274 "CloseDocument failed: " + Client().LastError() );
275 BOOST_CHECK_EQUAL( closeStatus, kiapi::common::AS_OK );
276
277 BOOST_REQUIRE_MESSAGE( Client().CloseDocument( nullptr, &closeStatus ),
278 "CloseDocument after already closed failed: " + Client().LastError() );
279 BOOST_CHECK_EQUAL( closeStatus, kiapi::common::AS_BAD_REQUEST );
280}
281
282
284{
285 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
286
287 kiapi::common::types::DocumentSpecifier document;
288 wxFileName boardPath( wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ), wxS( "api_kitchen_sink.kicad_pcb" ) );
289
290 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPath.GetFullPath(), &document ),
291 "OpenDocument failed: " + Client().LastError() );
292
293 kiapi::board::commands::BoardDesignRulesResponse rulesResponse;
294 wxString error;
295
296 BOOST_REQUIRE_MESSAGE( SendGetBoardDesignRules( Client(), document, &rulesResponse, &error ),
297 "GetBoardDesignRules failed: " + error );
298
299 BOOST_CHECK_GE( rulesResponse.rules().constraints().min_clearance().value_nm(), 0 );
300 BOOST_CHECK_GE( rulesResponse.rules().constraints().min_track_width().value_nm(), 0 );
301 BOOST_CHECK( rulesResponse.custom_rules_status() == kiapi::board::commands::CRS_VALID );
302}
303
304
306{
307 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
308
309 TEMP_KITCHEN_SINK_COPY fixtureCopy;
310 wxString copyError;
311
312 BOOST_REQUIRE_MESSAGE( fixtureCopy.Create( &copyError ), copyError );
313
314 kiapi::common::types::DocumentSpecifier document;
315
316 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( fixtureCopy.BoardPath(), &document ),
317 "OpenDocument failed: " + Client().LastError() );
318
319 kiapi::board::commands::BoardDesignRulesResponse getResponse;
320
321 BOOST_REQUIRE_MESSAGE( SendGetBoardDesignRules( Client(), document, &getResponse, &copyError ),
322 "Initial GetBoardDesignRules failed: " + copyError );
323
324 int newWidth = getResponse.rules().constraints().min_track_width().value_nm() + 1000;
325
326 kiapi::board::commands::SetBoardDesignRules setRequest;
327 *setRequest.mutable_board() = document;
328 *setRequest.mutable_rules()->mutable_constraints() = getResponse.rules().constraints();
329 setRequest.mutable_rules()->mutable_constraints()->mutable_min_track_width()->set_value_nm( newWidth );
330
331 kiapi::common::ApiResponse setApiResponse;
332
333 BOOST_REQUIRE_MESSAGE( Client().SendCommand( setRequest, &setApiResponse ),
334 "SetBoardDesignRules failed to send: " + Client().LastError() );
335 BOOST_REQUIRE( setApiResponse.status().status() == kiapi::common::AS_OK );
336
337 kiapi::board::commands::BoardDesignRulesResponse setResponse;
338 BOOST_REQUIRE( setApiResponse.message().UnpackTo( &setResponse ) );
339
340 BOOST_CHECK_EQUAL( setResponse.rules().constraints().min_track_width().value_nm(), newWidth );
341
342 kiapi::board::commands::BoardDesignRulesResponse verifyResponse;
343
344 BOOST_REQUIRE_MESSAGE( SendGetBoardDesignRules( Client(), document, &verifyResponse, &copyError ),
345 "Verification GetBoardDesignRules failed: " + copyError );
346
347 BOOST_CHECK_EQUAL( verifyResponse.rules().constraints().min_track_width().value_nm(), newWidth );
348}
349
350
351BOOST_FIXTURE_TEST_CASE( SetBoardDesignRules_ValidationFailure, API_SERVER_E2E_FIXTURE )
352{
353 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
354
355 TEMP_KITCHEN_SINK_COPY fixtureCopy;
356 wxString copyError;
357
358 BOOST_REQUIRE_MESSAGE( fixtureCopy.Create( &copyError ), copyError );
359
360 kiapi::common::types::DocumentSpecifier document;
361
362 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( fixtureCopy.BoardPath(), &document ),
363 "OpenDocument failed: " + Client().LastError() );
364
365 kiapi::board::commands::BoardDesignRulesResponse getResponse;
366
367 BOOST_REQUIRE_MESSAGE( SendGetBoardDesignRules( Client(), document, &getResponse, &copyError ),
368 "Initial GetBoardDesignRules failed: " + copyError );
369
370 kiapi::board::commands::SetBoardDesignRules setRequest;
371 *setRequest.mutable_board() = document;
372 *setRequest.mutable_rules()->mutable_constraints() = getResponse.rules().constraints();
373 setRequest.mutable_rules()->mutable_constraints()->mutable_min_track_width()->set_value_nm( 1000000000 );
374
375 kiapi::common::ApiResponse response;
376
377 BOOST_REQUIRE_MESSAGE( Client().SendCommand( setRequest, &response ),
378 "SetBoardDesignRules failed to send: " + Client().LastError() );
379
380 BOOST_CHECK( response.status().status() == kiapi::common::AS_BAD_REQUEST );
381 BOOST_CHECK( !response.status().error_message().empty() );
382}
383
384
385BOOST_FIXTURE_TEST_CASE( SetBoardDesignRules_SeverityOverrides, API_SERVER_E2E_FIXTURE )
386{
387 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
388
389 TEMP_KITCHEN_SINK_COPY fixtureCopy;
390 wxString copyError;
391
392 BOOST_REQUIRE_MESSAGE( fixtureCopy.Create( &copyError ), copyError );
393
394 kiapi::common::types::DocumentSpecifier document;
395
396 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( fixtureCopy.BoardPath(), &document ),
397 "OpenDocument failed: " + Client().LastError() );
398
399 kiapi::board::commands::BoardDesignRulesResponse getResponse;
400 BOOST_REQUIRE_MESSAGE( SendGetBoardDesignRules( Client(), document, &getResponse, &copyError ),
401 "Initial GetBoardDesignRules failed: " + copyError );
402
403 auto applySeverityAndVerify = [&]( kiapi::common::types::RuleSeverity aSeverity )
404 {
405 kiapi::board::commands::SetBoardDesignRules setRequest;
406 *setRequest.mutable_board() = document;
407
408 kiapi::board::DrcSeveritySetting* setting = setRequest.mutable_rules()->add_severities();
409 setting->set_rule_type( kiapi::board::DrcErrorType::DRCET_UNCONNECTED_ITEMS );
410 setting->set_severity( aSeverity );
411
412 kiapi::common::ApiResponse setApiResponse;
413 BOOST_REQUIRE_MESSAGE( Client().SendCommand( setRequest, &setApiResponse ),
414 "SetBoardDesignRules failed to send: " + Client().LastError() );
415 BOOST_REQUIRE( setApiResponse.status().status() == kiapi::common::AS_OK );
416
417 kiapi::board::commands::BoardDesignRulesResponse verifyResponse;
418 BOOST_REQUIRE_MESSAGE( SendGetBoardDesignRules( Client(), document, &verifyResponse, &copyError ),
419 "Verification GetBoardDesignRules failed: " + copyError );
420
421 bool found = false;
422
423 for( const kiapi::board::DrcSeveritySetting& severity : verifyResponse.rules().severities() )
424 {
425 if( severity.rule_type() == kiapi::board::DrcErrorType::DRCET_UNCONNECTED_ITEMS )
426 {
427 found = true;
428 BOOST_CHECK( severity.severity() == aSeverity );
429 break;
430 }
431 }
432
433 BOOST_CHECK( found );
434 };
435
436 applySeverityAndVerify( kiapi::common::types::RS_IGNORE );
437 applySeverityAndVerify( kiapi::common::types::RS_ERROR );
438}
439
440
441BOOST_FIXTURE_TEST_CASE( GetCustomDesignRules_ReturnsFixtureRules, API_SERVER_E2E_FIXTURE )
442{
443 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
444
445 kiapi::common::types::DocumentSpecifier document;
446 wxFileName boardPath( wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ), wxS( "api_kitchen_sink.kicad_pcb" ) );
447
448 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( boardPath.GetFullPath(), &document ),
449 "OpenDocument failed: " + Client().LastError() );
450
451 kiapi::board::commands::CustomRulesResponse response;
452 wxString error;
453
454 BOOST_REQUIRE_MESSAGE( SendGetCustomDesignRules( Client(), document, &response, &error ),
455 "GetCustomDesignRules failed: " + error );
456
457 BOOST_CHECK( response.status() == kiapi::board::commands::CRS_VALID );
458 BOOST_CHECK_GT( response.rules_size(), 0 );
459
460 bool foundFixtureRule = false;
461
462 for( const kiapi::board::CustomRule& rule : response.rules() )
463 {
464 if( rule.name() == "myrule" )
465 {
466 foundFixtureRule = true;
467 BOOST_CHECK_EQUAL( rule.condition(), "A.Name == 'test'" );
468 BOOST_CHECK_EQUAL( rule.constraints_size(), 2 );
469 BOOST_REQUIRE( !rule.comments().empty() );
470 break;
471 }
472 }
473
474 BOOST_CHECK( foundFixtureRule );
475}
476
477
478BOOST_FIXTURE_TEST_CASE( SetCustomDesignRules_RoundTripSingleRule, API_SERVER_E2E_FIXTURE )
479{
480 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
481
482 TEMP_KITCHEN_SINK_COPY fixtureCopy;
483 wxString copyError;
484
485 BOOST_REQUIRE_MESSAGE( fixtureCopy.Create( &copyError ), copyError );
486
487 kiapi::common::types::DocumentSpecifier document;
488
489 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( fixtureCopy.BoardPath(), &document ),
490 "OpenDocument failed: " + Client().LastError() );
491
492 kiapi::board::commands::CustomRulesResponse response;
493 wxString error;
494
495 BOOST_REQUIRE_MESSAGE( SendGetCustomDesignRules( Client(), document, &response, &error ),
496 "GetCustomDesignRules failed: " + error );
497
498 BOOST_CHECK( response.status() == kiapi::board::commands::CRS_VALID );
499 BOOST_CHECK_GT( response.rules_size(), 0 );
500
501 kiapi::board::commands::SetCustomDesignRules setRequest;
502 *setRequest.mutable_board() = document;
503
504 setRequest.mutable_rules()->CopyFrom( response.rules() );
505
506 kiapi::board::CustomRule* rule = setRequest.add_rules();
507 rule->set_name( "api_roundtrip_rule" );
508 rule->set_condition( "A.NetClass == 'HV'" );
509 rule->set_layer_mode( kiapi::board::CRLM_OUTER );
510 rule->set_severity( kiapi::common::types::RS_WARNING );
511
512 kiapi::board::CustomRuleConstraint* annular = rule->add_constraints();
513 annular->set_type( kiapi::board::CRCT_TRACK_WIDTH );
514 annular->mutable_numeric()->set_min( 3000000 );
515 annular->mutable_numeric()->set_opt( 4000000 );
516 annular->mutable_numeric()->set_max( 5000000 );
517
518 kiapi::board::CustomRuleConstraint* disallow = rule->add_constraints();
519 disallow->set_type( kiapi::board::CRCT_DISALLOW );
520 disallow->mutable_disallow()->add_types( kiapi::board::CRDT_BLIND_VIAS );
521
522 kiapi::common::ApiResponse setApiResponse;
523
524 BOOST_REQUIRE_MESSAGE( Client().SendCommand( setRequest, &setApiResponse ),
525 "SetCustomDesignRules failed to send: " + Client().LastError() );
526 BOOST_REQUIRE( setApiResponse.status().status() == kiapi::common::AS_OK );
527
528 kiapi::board::commands::CustomRulesResponse setResponse;
529 BOOST_REQUIRE( setApiResponse.message().UnpackTo( &setResponse ) );
530
531 int num_rules = setResponse.rules_size();
532 BOOST_CHECK( setResponse.status() == kiapi::board::commands::CRS_VALID );
533 BOOST_CHECK_EQUAL( num_rules, response.rules_size() + 1 );
534 BOOST_CHECK_EQUAL( setResponse.rules( num_rules - 1 ).name(), "api_roundtrip_rule" );
535}
536
537
538BOOST_FIXTURE_TEST_CASE( SetCustomDesignRules_RoundTripIsLossless, API_SERVER_E2E_FIXTURE )
539{
540 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
541
542 TEMP_KITCHEN_SINK_COPY fixtureCopy;
543 wxString copyError;
544
545 BOOST_REQUIRE_MESSAGE( fixtureCopy.Create( &copyError, wxS( "issue25846/custom_rules.kicad_dru" ) ),
546 copyError );
547
548 kiapi::common::types::DocumentSpecifier document;
549
550 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( fixtureCopy.BoardPath(), &document ),
551 "OpenDocument failed: " + Client().LastError() );
552
553 kiapi::board::commands::CustomRulesResponse getResponse;
554 wxString error;
555
556 BOOST_REQUIRE_MESSAGE( SendGetCustomDesignRules( Client(), document, &getResponse, &error ),
557 "GetCustomDesignRules failed: " + error );
558 BOOST_REQUIRE( getResponse.status() == kiapi::board::commands::CRS_VALID );
559 BOOST_REQUIRE_GT( getResponse.rules_size(), 0 );
560
561 bool sawReturnPath = false;
562
563 for( const kiapi::board::CustomRule& rule : getResponse.rules() )
564 {
565 for( const kiapi::board::CustomRuleConstraint& constraint : rule.constraints() )
566 {
567 if( constraint.type() != kiapi::board::CRCT_NET_CHAIN_RETURN_PATH )
568 continue;
569
570 sawReturnPath = true;
571 BOOST_CHECK_EQUAL( constraint.return_path().reference_layer(), "B.Cu" );
572 BOOST_CHECK_EQUAL( constraint.return_path().reference_net(), "GND" );
573 }
574 }
575
576 BOOST_CHECK( sawReturnPath );
577
578 kiapi::board::commands::SetCustomDesignRules setRequest;
579 *setRequest.mutable_board() = document;
580 setRequest.mutable_rules()->CopyFrom( getResponse.rules() );
581
582 kiapi::common::ApiResponse setApiResponse;
583
584 BOOST_REQUIRE_MESSAGE( Client().SendCommand( setRequest, &setApiResponse ),
585 "SetCustomDesignRules failed to send: " + Client().LastError() );
586 BOOST_REQUIRE_MESSAGE( setApiResponse.status().status() == kiapi::common::AS_OK,
587 setApiResponse.status().error_message() );
588
589 kiapi::board::commands::CustomRulesResponse setResponse;
590 BOOST_REQUIRE( setApiResponse.message().UnpackTo( &setResponse ) );
591 BOOST_REQUIRE( setResponse.status() == kiapi::board::commands::CRS_VALID );
592 BOOST_REQUIRE_EQUAL( setResponse.rules_size(), getResponse.rules_size() );
593
594 for( int ii = 0; ii < getResponse.rules_size(); ++ii )
595 {
596 BOOST_CHECK_MESSAGE( setResponse.rules( ii ).SerializeAsString() == getResponse.rules( ii ).SerializeAsString(),
597 getResponse.rules( ii ).name() << " changed in the round trip" );
598 }
599}
600
601
602BOOST_FIXTURE_TEST_CASE( OpenProjectWithBoardAndSchematic, API_SERVER_E2E_FIXTURE )
603{
604 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
605
606 wxString testDataDir = wxString::FromUTF8( KI_TEST::GetTestDataRootDir() ) + wxS( "cli/basic_test/" );
607
608 kiapi::common::types::DocumentSpecifier projectDoc;
609
610 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( testDataDir + wxS( "basic_test.kicad_pro" ),
611 kiapi::common::types::DOCTYPE_PROJECT, &projectDoc ),
612 "OpenDocument for project failed: " + Client().LastError() );
613
614 BOOST_REQUIRE( projectDoc.type() == kiapi::common::types::DOCTYPE_PROJECT );
615
616 kiapi::common::types::DocumentSpecifier boardDoc;
617
618 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( testDataDir + wxS( "basic_test.kicad_pcb" ),
619 kiapi::common::types::DOCTYPE_PCB, &boardDoc ),
620 "OpenDocument for board failed: " + Client().LastError() );
621
622 BOOST_REQUIRE( boardDoc.type() == kiapi::common::types::DOCTYPE_PCB );
623 BOOST_REQUIRE( !boardDoc.board_filename().empty() );
624
625 int footprintCount = 0;
626 BOOST_REQUIRE_MESSAGE( Client().GetItemsCount( boardDoc, kiapi::common::types::KOT_PCB_FOOTPRINT, &footprintCount ),
627 "GetItems for board failed: " + Client().LastError() );
628 BOOST_CHECK_GT( footprintCount, 0 );
629
630 kiapi::common::types::DocumentSpecifier schDoc;
631
632 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( testDataDir + wxS( "basic_test.kicad_sch" ),
633 kiapi::common::types::DOCTYPE_SCHEMATIC, &schDoc ),
634 "OpenDocument for schematic failed: " + Client().LastError() );
635
636 BOOST_REQUIRE( schDoc.type() == kiapi::common::types::DOCTYPE_SCHEMATIC );
637
638 // Verify the board is still open by querying it again.
639 footprintCount = 0;
640 BOOST_REQUIRE_MESSAGE( Client().GetItemsCount( boardDoc, kiapi::common::types::KOT_PCB_FOOTPRINT, &footprintCount ),
641 "GetItems for board after opening schematic failed: " + Client().LastError() );
642 BOOST_CHECK_GT( footprintCount, 0 );
643
644 // Verify that opening a document from a different project is rejected.
645 wxString otherDataDir = wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() );
646
647 kiapi::common::types::DocumentSpecifier otherBoardDoc;
648
649 BOOST_REQUIRE_MESSAGE(
650 !Client().OpenDocument( otherDataDir + wxS( "api_kitchen_sink.kicad_pcb" ),
651 kiapi::common::types::DOCTYPE_PCB, &otherBoardDoc ),
652 "OpenDocument for a different-project board should have failed" );
653
654 BOOST_CHECK( Client().LastError().Contains( wxS( "already open" ) ) );
655}
656
657
659{
660 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
661
662 auto test =
663 [&]( kiapi::common::types::DocumentType aType, const wxString& aExpectedExt )
664 {
665 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_api_e2e_createdoc" );
666
667 wxString tempFn = tempDir.ChildPathStr( "new_doc" );
668 wxFileName fileWithExt( tempDir.ChildPathStr( "new_doc" + aExpectedExt ) );
669 wxFileName projectWithExt( tempDir.ChildPathStr( "new_doc.kicad_pro" ) );
670
671 kiapi::common::types::DocumentSpecifier document;
672
673 BOOST_REQUIRE_MESSAGE( Client().CreateDocument( tempFn, aType, &document ),
674 "CreateDocument failed: " + Client().LastError() );
675
676 BOOST_CHECK( document.type() == aType );
677 BOOST_CHECK( document.project().name() == projectWithExt.GetName() );
678 BOOST_CHECK( document.project().path() == projectWithExt.GetPath( wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME ) );
679
680 kiapi::common::ApiResponse response;
681 kiapi::common::commands::GetDocumentModifiedState state;
682 state.mutable_document()->CopyFrom( document );
683 BOOST_CHECK( Client().SendCommand( state, &response ) );
684
685 // Just creating the document doesn't save it
686 BOOST_CHECK( !wxFileName( fileWithExt ).FileExists() );
687 BOOST_CHECK( !wxFileName( projectWithExt ).FileExists() );
688
689 kiapi::common::commands::SaveDocument save;
690 save.mutable_document()->CopyFrom( document );
691 BOOST_CHECK( Client().SendCommand( save, &response ) );
692
693 BOOST_CHECK( wxFileName( fileWithExt ).FileExists() );
694 BOOST_CHECK( wxFileName( projectWithExt ).FileExists() );
695 };
696
697 std::map<kiapi::common::types::DocumentType, wxString> cases = {
698 { kiapi::common::types::DOCTYPE_PCB, wxS( ".kicad_pcb" )},
699 { kiapi::common::types::DOCTYPE_SCHEMATIC, wxS( ".kicad_sch" ) }
700 };
701
702 for( const auto& [docType, ext] : cases )
703 {
704 BOOST_TEST_CONTEXT( magic_enum::enum_name( docType ) )
705 {
706 test( docType, ext );
707 }
708 }
709}
710
711
712BOOST_FIXTURE_TEST_CASE( CreateDocumentRejectsUnsavedModifications, API_SERVER_E2E_FIXTURE )
713{
714 using namespace kiapi::common::commands;
715
716 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
717
718 auto test =
719 [&]( kiapi::common::types::DocumentType aType, const wxString& aExistingPath )
720 {
721 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_api_e2e_createdoc" );
722 wxString docFileName = tempDir.ChildPathStr( "new_doc" );
723
724 kiapi::common::types::DocumentSpecifier existingDoc;
725
726 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( aExistingPath, aType, &existingDoc ),
727 "OpenDocument failed: " + Client().LastError() );
728
729 SetPageSettings modify;
730 modify.mutable_document()->CopyFrom( existingDoc );
731 modify.mutable_page_settings()->set_orientation( kiapi::common::types::PO_PORTRAIT );
732 kiapi::common::ApiResponse response;
733 BOOST_CHECK( Client().SendCommand( modify, &response ) );
734
735 GetDocumentModifiedState stateQuery;
736 stateQuery.mutable_document()->CopyFrom( existingDoc );
737 BOOST_CHECK( Client().SendCommand( stateQuery, &response ) );
738 GetDocumentModifiedStateResponse state;
739 BOOST_CHECK( response.message().UnpackTo( &state ) );
740 BOOST_CHECK( state.state() == DocumentModifiedState::DMS_MODIFIED );
741
742 kiapi::common::types::DocumentSpecifier newDoc;
743
744 BOOST_REQUIRE( !Client().CreateDocument( docFileName, aType, &newDoc ) );
745
746 BOOST_CHECK( Client().LastError().Contains( wxS( "save or revert" ) ) );
747
748 kiapi::common::commands::RevertDocument revert;
749 revert.mutable_document()->CopyFrom( existingDoc );
750 BOOST_CHECK( Client().SendCommand( revert, &response ) );
751
752 BOOST_REQUIRE( Client().CreateDocument( docFileName, aType, &newDoc ) );
753
754 BOOST_CHECK( Client().CloseAllDocuments() );
755 };
756
757 std::map<kiapi::common::types::DocumentType, wxString> cases = {
758 { kiapi::common::types::DOCTYPE_PCB,
759 wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ) + wxS( "api_kitchen_sink.kicad_pcb" ) },
760 { kiapi::common::types::DOCTYPE_SCHEMATIC,
761 wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() ) + wxS( "api_kitchen_sink.kicad_sch" ) }
762 };
763
764 for( const auto& [docType, path] : cases )
765 {
766 BOOST_TEST_CONTEXT( magic_enum::enum_name( docType ) )
767 {
768 test( docType, path );
769 }
770 }
771}
772
773
775{
776 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
777
778 auto test =
779 [&]( kiapi::common::types::DocumentType aType, const wxString& aSourcePath,
780 const wxString& aSourceName, const wxString& aDocumentExt )
781 {
782 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_api_e2e_saveas" );
783
784 wxString sourceCopy = tempDir.ChildPathStr( aSourceName );
785 BOOST_REQUIRE( wxCopyFile( aSourcePath, sourceCopy, true ) );
786
787 kiapi::common::types::DocumentSpecifier document;
788
789 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( sourceCopy, aType, &document ),
790 "OpenDocument failed: " + Client().LastError() );
791
792 wxFileName newDocFile( tempDir.ChildPathStr( "renamed" + aDocumentExt ) );
793 wxFileName newProjectFile( tempDir.ChildPathStr( "renamed.kicad_pro" ) );
794
795 kiapi::common::commands::SaveDocumentAs saveAs;
796 saveAs.mutable_document()->CopyFrom( document );
797 saveAs.set_path( tempDir.ChildPathStr( "renamed" ).ToStdString() );
798
799 kiapi::common::ApiResponse response;
800 BOOST_REQUIRE_MESSAGE( Client().SendCommand( saveAs, &response ),
801 "SaveDocumentAs failed to send: " + Client().LastError() );
802 BOOST_REQUIRE( response.status().status() == kiapi::common::AS_OK );
803
804 BOOST_CHECK( newDocFile.FileExists() );
805 BOOST_CHECK( newProjectFile.FileExists() );
806
807 kiapi::common::commands::GetOpenDocuments openDocs;
808 openDocs.set_type( aType );
809 BOOST_REQUIRE( Client().SendCommand( openDocs, &response ) );
810
811 kiapi::common::commands::GetOpenDocumentsResponse docsResponse;
812 BOOST_REQUIRE( response.message().UnpackTo( &docsResponse ) );
813 BOOST_REQUIRE_EQUAL( docsResponse.documents_size(), 1 );
814
815 kiapi::common::types::DocumentSpecifier renamedDoc = docsResponse.documents( 0 );
816
817 // TODO(JE) clean up
818 // pcb reports the project directory without a trailing separator, schematic via
819 // PackProject which uses GetProjectPath() including the separator
820 wxString expectedProjectPath = ( aType == kiapi::common::types::DOCTYPE_PCB )
821 ? newProjectFile.GetPath()
822 : newProjectFile.GetPath( wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME );
823
824 BOOST_CHECK_EQUAL( renamedDoc.project().path(), expectedProjectPath.ToStdString() );
825 BOOST_CHECK_EQUAL( renamedDoc.project().name(), newProjectFile.GetName().ToStdString() );
826
827 if( aType == kiapi::common::types::DOCTYPE_PCB )
828 BOOST_CHECK_EQUAL( renamedDoc.board_filename(), newDocFile.GetFullName().ToStdString() );
829
830 // The old specifier must now be rejected: the document identity has changed
831 kiapi::common::commands::GetDocumentModifiedState stateQuery;
832 stateQuery.mutable_document()->CopyFrom( document );
833 BOOST_CHECK( Client().SendCommand( stateQuery, &response ) );
834 BOOST_CHECK( response.status().status() != kiapi::common::AS_OK );
835
836 // The new specifier works, and saving to the new location succeeds
837 stateQuery.mutable_document()->CopyFrom( renamedDoc );
838 BOOST_CHECK( Client().SendCommand( stateQuery, &response ) );
839 BOOST_REQUIRE( response.status().status() == kiapi::common::AS_OK );
840
841 kiapi::common::commands::GetDocumentModifiedStateResponse state;
842 BOOST_REQUIRE( response.message().UnpackTo( &state ) );
843 BOOST_CHECK( state.state() == kiapi::common::commands::DocumentModifiedState::DMS_UNMODIFIED );
844
845 kiapi::common::commands::SaveDocument save;
846 save.mutable_document()->CopyFrom( renamedDoc );
847 BOOST_CHECK( Client().SendCommand( save, &response ) );
848 BOOST_CHECK( response.status().status() == kiapi::common::AS_OK );
849
850 // Closing with the new specifier must succeed; the server's bookkeeping follows the rename
851 kiapi::common::ApiStatusCode closeStatus = kiapi::common::AS_UNKNOWN;
852 BOOST_REQUIRE_MESSAGE( Client().CloseDocument( &renamedDoc, &closeStatus ),
853 "CloseDocument after SaveAs failed: " + Client().LastError() );
854 BOOST_CHECK_EQUAL( closeStatus, kiapi::common::AS_OK );
855 };
856
857 struct SaveAsCase
858 {
859 wxString sourcePath;
860 wxString sourceName;
861 wxString documentExt;
862 };
863
864 std::map<kiapi::common::types::DocumentType, SaveAsCase> cases = {
865 { kiapi::common::types::DOCTYPE_PCB,
866 { wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ) + wxS( "api_kitchen_sink.kicad_pcb" ),
867 wxS( "api_kitchen_sink.kicad_pcb" ), wxS( ".kicad_pcb" ) } },
868 { kiapi::common::types::DOCTYPE_SCHEMATIC,
869 { wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() ) + wxS( "api_kitchen_sink.kicad_sch" ),
870 wxS( "api_kitchen_sink.kicad_sch" ), wxS( ".kicad_sch" ) } }
871 };
872
873 for( const auto& [docType, source] : cases )
874 {
875 BOOST_TEST_CONTEXT( magic_enum::enum_name( docType ) )
876 {
877 test( docType, source.sourcePath, source.sourceName, source.documentExt );
878 }
879 }
880}
881
882
883BOOST_FIXTURE_TEST_CASE( SaveDocumentAsRejectsBadRequests, API_SERVER_E2E_FIXTURE )
884{
885 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
886
887 wxString sourcePath = wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ) + wxS( "api_kitchen_sink.kicad_pcb" );
888
889 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_api_e2e_saveas_reject" );
890
891 kiapi::common::types::DocumentSpecifier document;
892
893 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( sourcePath, &document ),
894 "OpenDocument failed: " + Client().LastError() );
895
896 kiapi::common::ApiResponse response;
897
898 // An empty path is invalid
899 kiapi::common::commands::SaveDocumentAs emptyPath;
900 emptyPath.mutable_document()->CopyFrom( document );
901 BOOST_CHECK( Client().SendCommand( emptyPath, &response ) );
902 BOOST_CHECK( response.status().status() == kiapi::common::AS_BAD_REQUEST );
903 BOOST_CHECK( wxString( response.status().error_message() ).Contains( wxS( "path is required" ) ) );
904
905 // A stale document specifier is rejected by validation before any file is written
906 kiapi::common::commands::SaveDocumentAs staleDoc;
907 staleDoc.mutable_document()->CopyFrom( document );
908 staleDoc.mutable_document()->set_board_filename( "no_such_board.kicad_pcb" );
909 staleDoc.set_path( tempDir.ChildPathStr( "never_written" ).ToStdString() );
910 BOOST_CHECK( Client().SendCommand( staleDoc, &response ) );
911 BOOST_CHECK( response.status().status() == kiapi::common::AS_BAD_REQUEST );
912
913 BOOST_CHECK( !wxFileName( tempDir.ChildPathStr( "never_written.kicad_pcb" ) ).FileExists() );
914
915 Client().CloseAllDocuments();
916}
917
918
919BOOST_FIXTURE_TEST_CASE( SaveDocumentAsSwitchesProject, API_SERVER_E2E_FIXTURE )
920{
921 BOOST_REQUIRE_MESSAGE( Start(), LastError() );
922
923 wxString sourcePath = wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ) + wxS( "api_kitchen_sink.kicad_pcb" );
924
925 KI_TEST::SCOPED_TEMP_DIR tempDir( "kicad_api_e2e_saveas_project" );
926
927 kiapi::common::types::DocumentSpecifier document;
928
929 BOOST_REQUIRE_MESSAGE( Client().OpenDocument( sourcePath, &document ),
930 "OpenDocument failed: " + Client().LastError() );
931
932 // Save into a new subdirectory: the document and its project move there
933 wxString subdir = tempDir.CreateChildDirStr( "subdir" );
934
935 kiapi::common::commands::SaveDocumentAs saveAs;
936 saveAs.mutable_document()->CopyFrom( document );
937 saveAs.set_path( ( subdir + wxS( "/moved" ) ).ToStdString() );
938
939 kiapi::common::ApiResponse response;
940 BOOST_REQUIRE_MESSAGE( Client().SendCommand( saveAs, &response ),
941 "SaveDocumentAs failed to send: " + Client().LastError() );
942 BOOST_REQUIRE( response.status().status() == kiapi::common::AS_OK );
943
944 BOOST_CHECK( wxFileName( subdir + wxS( "/moved.kicad_pcb" ) ).FileExists() );
945 BOOST_CHECK( wxFileName( subdir + wxS( "/moved.kicad_pro" ) ).FileExists() );
946
947 // The project must have switched
948 kiapi::common::types::DocumentSpecifier otherBoard;
949 BOOST_REQUIRE_MESSAGE( !Client().OpenDocument( sourcePath, &otherBoard ),
950 "Opening a board from the old project directory should have failed" );
951 BOOST_CHECK( Client().LastError().Contains( wxS( "already open" ) ) );
952
953 Client().CloseAllDocuments();
954}
955
957
Per-test fixture for API E2E tests.
const wxString & LastError() const
bool SendCommand(const T &aMessage, kiapi::common::ApiResponse *aResponse)
const KIID m_Uuid
Definition eda_item.h:599
double Similarity(const BOARD_ITEM &aOther) const override
Return a measure of how likely the other object is to represent the same object.
std::string AsStdString() const
Definition kiid.cpp:270
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.
const wxString & BoardPath() const
bool Create(wxString *aError, const wxString &aRulesName=wxS("api_kitchen_sink.kicad_dru"))
KI_TEST::SCOPED_TEMP_DIR m_tempRoot
static std::string ToStdString(const wxString &aStr)
std::string source
std::string GetTestDataRootDir()
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
@ REFERENCE
Field Reference of part, i.e. "IC21".
bool SendGetCustomDesignRules(API_TEST_CLIENT &aClient, const kiapi::common::types::DocumentSpecifier &aDocument, kiapi::board::commands::CustomRulesResponse *aOut, wxString *aError=nullptr)
BOOST_FIXTURE_TEST_CASE(ServerStartsAndResponds, API_SERVER_E2E_FIXTURE)
bool SendGetBoardDesignRules(API_TEST_CLIENT &aClient, const kiapi::common::types::DocumentSpecifier &aDocument, kiapi::board::commands::BoardDesignRulesResponse *aOut, wxString *aError=nullptr)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")