KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_sch_api_handler.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 <memory>
21
22#include <boost/test/unit_test.hpp>
23
24#include <api/api_handler_sch.h>
25#include <api/api_utils.h>
26#include <api/common/commands/editor_commands.pb.h>
27#include <api/common/envelope.pb.h>
29#include <api/schematic/schematic_types.pb.h>
32
33#include <schematic.h>
34#include <advanced_config.h>
35#include <connection_graph.h>
36#include <sch_label.h>
37#include <sch_line.h>
38#include <sch_screen.h>
39#include <sch_sheet.h>
40#include <set>
41#include <scoped_set_reset.h>
42
43
44namespace
45{
46
47struct API_HANDLER_SCH_FIXTURE
48{
49 SETTINGS_MANAGER m_settingsManager;
50 std::unique_ptr<SCHEMATIC> m_schematic;
51 SCHEMATIC* m_schematicSlot = nullptr;
52 std::shared_ptr<HEADLESS_SCH_CONTEXT> m_context;
53
54 SCHEMATIC* loadSchematic( const wxString& aRelPath )
55 {
56 KI_TEST::LoadSchematic( m_settingsManager, aRelPath, m_schematic );
57 m_schematicSlot = m_schematic.get();
58 m_context = std::make_shared<HEADLESS_SCH_CONTEXT>( &m_schematicSlot,
59 &m_settingsManager.Prj() );
60 return m_schematic.get();
61 }
62};
63
64
65kiapi::common::types::DocumentSpecifier makeDocument( const SCHEMATIC& aSchematic )
66{
67 kiapi::common::types::DocumentSpecifier document;
68 document.set_type( kiapi::common::types::DocumentType::DOCTYPE_SCHEMATIC );
69 kiapi::common::PackProject( *document.mutable_project(), aSchematic.Project() );
70
71 return document;
72}
73
74
75kiapi::common::ApiRequest makeBeginCommitRequest( const SCHEMATIC& aSchematic )
76{
77 kiapi::common::commands::BeginCommit command;
78 *command.mutable_header()->mutable_document() = makeDocument( aSchematic );
79
80 kiapi::common::ApiRequest request;
81 request.mutable_header()->set_client_name( "kicad.qa" );
82 BOOST_REQUIRE( request.mutable_message()->PackFrom( command ) );
83
84 return request;
85}
86
87
88kiapi::common::ApiRequest makeRevertRequest( const SCHEMATIC& aSchematic )
89{
90 kiapi::common::commands::RevertDocument command;
91 *command.mutable_document() = makeDocument( aSchematic );
92
93 kiapi::common::ApiRequest request;
94 request.mutable_header()->set_client_name( "kicad.qa" );
95 BOOST_REQUIRE( request.mutable_message()->PackFrom( command ) );
96
97 return request;
98}
99
100
101}
102
103
104BOOST_FIXTURE_TEST_SUITE( ApiHandlerSch, API_HANDLER_SCH_FIXTURE )
105
106
107BOOST_AUTO_TEST_CASE( RevertDocumentRejectedWithOpenCommit )
108{
109 SCHEMATIC* schematic = loadSchematic( wxS( "api_kitchen_sink" ) );
110
111 API_HANDLER_SCH handler( m_context );
112
113 kiapi::common::ApiRequest beginRequest = makeBeginCommitRequest( *schematic );
114 BOOST_REQUIRE( handler.Handle( beginRequest ).has_value() );
115
116 kiapi::common::ApiRequest request = makeRevertRequest( *schematic );
117 API_RESULT result = handler.Handle( request );
118
119 BOOST_REQUIRE( !result.has_value() );
120 BOOST_CHECK_EQUAL( result.error().status(), kiapi::common::ApiStatusCode::AS_BUSY );
121 BOOST_CHECK( result.error().error_message().find( "commit" ) != std::string::npos );
122}
123
124
125
126BOOST_AUTO_TEST_CASE( CustomPropertyCannotDuplicateSystemProperty )
127{
128 SCHEMATIC* schematic = loadSchematic( wxS( "api_kitchen_sink" ) );
129
130 API_HANDLER_SCH handler( m_context );
131 ApiRequest beginRequest = makeBeginCommitRequest( *schematic );
132 BOOST_REQUIRE( handler.Handle( beginRequest ).has_value() );
133
134 commands::CreateItems command;
135 command.mutable_header()->mutable_document()->CopyFrom( makeDocument( *schematic ) );
136
137 schematic::types::SchematicLine line;
138 line.mutable_id()->set_value( "83618809-06d6-4cbe-ba0e-ec60fe58921c" );
139 line.set_type( schematic::types::SchematicLineType::SLT_WIRE );
140 line.add_custom_properties()->set_key( "linE Width" );
141 line.add_custom_properties()->set_key( "start X" );
142
143 command.add_items()->PackFrom( line );
144
145 ApiRequest request;
146 request.mutable_header()->set_client_name( "kicad.qa" );
147 BOOST_REQUIRE( request.mutable_message()->PackFrom( command ) );
148
149 API_RESULT result = handler.Handle( request );
150 BOOST_REQUIRE( result.has_value() );
151
152 commands::CreateItemsResponse response;
153 BOOST_REQUIRE( result->message().UnpackTo( &response ) );
154 BOOST_REQUIRE_EQUAL( response.created_items_size(), 1 );
155
156 const commands::ItemStatus& status = response.created_items( 0 ).status();
157
158 // Should be rejected; duplicates built in property
159 BOOST_CHECK_EQUAL( status.code(), kiapi::common::commands::ItemStatusCode::ISC_INVALID_DATA );
160 BOOST_CHECK_NE( status.error_message().find( "Invalid custom properties" ), std::string::npos );
161}
162
163
164BOOST_AUTO_TEST_CASE( SymbolFieldTakesPrecedenceOverCustomProperty )
165{
166 SCHEMATIC* schematic = loadSchematic( wxS( "api_kitchen_sink" ) );
167
168 API_HANDLER_SCH handler( m_context );
169 ApiRequest beginRequest = makeBeginCommitRequest( *schematic );
170 BOOST_REQUIRE( handler.Handle( beginRequest ).has_value() );
171
172 commands::CreateItems command;
173 command.mutable_header()->mutable_document()->CopyFrom( makeDocument( *schematic ) );
174
175 schematic::types::SchematicSymbolInstance symbol;
176 symbol.mutable_id()->set_value( "f8688bac-2fcb-4184-ab0e-0cf46a139c43" );
177 symbol.mutable_transform()->set_orientation( schematic::types::SchematicSymbolOrientation::SSO_0 );
178
179 symbol.mutable_definition()->mutable_id()->set_entry_name( "R" );
180
181 schematic::types::SchematicField* field = symbol.add_user_fields();
182 field->set_name( "MPN" );
183 field->mutable_text()->set_text( "123" );
184
185 types::CustomProperty* prop = symbol.add_custom_properties();
186 prop->set_key( "MPN" );
187 prop->set_value( "456" );
188
189 command.add_items()->PackFrom( symbol );
190
191 ApiRequest request;
192 request.mutable_header()->set_client_name( "kicad.qa" );
193 BOOST_REQUIRE( request.mutable_message()->PackFrom( command ) );
194
195 API_RESULT result = handler.Handle( request );
196 BOOST_REQUIRE( result.has_value() );
197
198 commands::CreateItemsResponse response;
199 BOOST_REQUIRE( result->message().UnpackTo( &response ) );
200 BOOST_REQUIRE_EQUAL( response.created_items_size(), 1 );
201
202 const commands::ItemStatus& status = response.created_items( 0 ).status();
203
204 // The field takes precedence: a custom property duplicating a field name is rejected.
205 BOOST_CHECK_EQUAL( status.code(), kiapi::common::commands::ItemStatusCode::ISC_INVALID_DATA );
206 BOOST_CHECK_NE( status.error_message().find( "MPN" ), std::string::npos );
207}
208
209BOOST_AUTO_TEST_CASE( NetlistRebuildsSelectedConnectivityAndFiltersNativeItems )
210{
211 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
212 SCOPED_SET_RESET restore( enabled, false );
213 SCHEMATIC* model = loadSchematic( wxS( "net_chains_four_nets_labeled" ) );
214 const auto path = model->Hierarchy().front();
215 SCH_LABEL* label = nullptr;
216
217 for( SCH_ITEM* item : path.LastScreen()->Items().OfType( SCH_LABEL_T ) )
218 {
219 if( static_cast<SCH_LABEL*>( item )->GetText() == wxS( "SIG" ) )
220 label = static_cast<SCH_LABEL*>( item );
221 }
222
223 BOOST_REQUIRE( label );
224 API_HANDLER_SCH handler( m_context );
225
226 for( bool published : { false, true } )
227 {
228 BOOST_TEST_CONTEXT( "published=" << published )
229 {
230 enabled = published;
231 const wxString name = published ? wxS( "PUBLISHED_API" ) : wxS( "LEGACY_API" );
232 label->SetText( name );
233
234 // Only the engine handler rebuilds; legacy answers from the graph the editor keeps current
235 if( published )
236 model->ConnectionGraph()->Reset();
237 else
238 model->RebuildConnectivity();
239
240 for( bool filtered : { false, true } )
241 {
242 kiapi::schematic::commands::GetSchematicNetlist command;
243 *command.mutable_document() = makeDocument( *model );
244
245 if( filtered )
246 command.add_types( kiapi::common::types::KOT_SCH_LABEL );
247
248 ApiRequest request;
249 request.mutable_header()->set_client_name( "kicad.qa" );
250 BOOST_REQUIRE( request.mutable_message()->PackFrom( command ) );
251 API_RESULT result = handler.Handle( request );
252 BOOST_REQUIRE( result.has_value() );
253 kiapi::schematic::commands::SchematicNetlistResponse response;
254 BOOST_REQUIRE( result->message().UnpackTo( &response ) );
255 std::set<std::string> items;
256
257 for( const auto& net : response.nets() )
258 {
259 if( net.name() != ( wxS( "/" ) + name ).ToStdString() )
260 continue;
261
262 for( const auto& sheet : net.sheets() )
263 {
264 for( const auto& item : sheet.items() )
265 items.insert( item.value() );
266 }
267 }
268
269 BOOST_CHECK( items.contains( label->m_Uuid.AsStdString() ) );
270
271 if( filtered )
272 BOOST_CHECK_EQUAL( items.size(), 1 );
273 else
274 BOOST_CHECK_GT( items.size(), 1 );
275 }
276 }
277 }
278}
279
280BOOST_AUTO_TEST_CASE( NetlistPreservesNativeHierarchyMembershipAndExcludesBuses )
281{
282 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
283 SCOPED_SET_RESET restore( enabled, false );
284 SCHEMATIC* model = loadSchematic( wxS( "netlists/hierarchy_aliases/hierarchy_aliases" ) );
285 auto* root = model->Hierarchy().front().LastScreen();
286 auto labels = root->Items().OfType( SCH_LABEL_T );
287 BOOST_REQUIRE( labels.begin() != labels.end() );
288 auto* unusedBus = static_cast<SCH_LABEL*>( ( *labels.begin() )->Clone() );
289 const_cast<KIID&>( unusedBus->m_Uuid ) = KIID();
290 unusedBus->SetText( wxS( "API_UNUSED[0..1]" ) );
291 unusedBus->SetPosition( VECTOR2I( 0, 0 ) );
292 root->Append( unusedBus );
293 SCH_LINE* undriven = nullptr;
294
295 for( SCH_ITEM* item : root->Items().OfType( SCH_LINE_T ) )
296 {
297 auto* line = static_cast<SCH_LINE*>( item );
298
299 if( line->IsWire() )
300 {
301 undriven = static_cast<SCH_LINE*>( line->Clone() );
302 break;
303 }
304 }
305
306 BOOST_REQUIRE( undriven );
307 const_cast<KIID&>( undriven->m_Uuid ) = KIID();
308 undriven->Move( VECTOR2I( -1000000, -1000000 ) - undriven->GetStartPoint() );
309 root->Append( undriven );
310 API_HANDLER_SCH handler( m_context );
311 using CONTENTS = std::map<std::string, std::map<std::vector<std::string>, std::set<std::string>>>;
312
313 for( bool filtered : { false, true } )
314 {
315 CONTENTS expected;
316
317 for( bool published : { false, true } )
318 {
319 BOOST_TEST_CONTEXT( "published=" << published << " filtered=" << filtered )
320 {
321 enabled = published;
322
323 // Only the engine handler rebuilds; legacy answers from the graph the editor keeps current
324 if( published )
325 model->ConnectionGraph()->Reset();
326 else
327 model->RebuildConnectivity();
328
329 kiapi::schematic::commands::GetSchematicNetlist command;
330 *command.mutable_document() = makeDocument( *model );
331
332 if( filtered )
333 command.add_types( kiapi::common::types::KOT_SCH_PIN );
334
335 ApiRequest request;
336 request.mutable_header()->set_client_name( "kicad.qa" );
337 BOOST_REQUIRE( request.mutable_message()->PackFrom( command ) );
338 API_RESULT result = handler.Handle( request );
339 BOOST_REQUIRE( result.has_value() );
340 kiapi::schematic::commands::SchematicNetlistResponse response;
341 BOOST_REQUIRE( result->message().UnpackTo( &response ) );
342 CONTENTS actual;
343 size_t nestedItems = 0;
344
345 for( const auto& net : response.nets() )
346 {
347 BOOST_CHECK_GT( net.sheets_size(), 0 );
348 BOOST_CHECK( net.name().find( '{' ) == std::string::npos );
349 BOOST_CHECK( net.name().find( "API_UNUSED" ) == std::string::npos );
350
351 for( const auto& sheet : net.sheets() )
352 {
353 std::vector<std::string> path;
354
355 for( const auto& id : sheet.path().path() )
356 path.push_back( id.value() );
357
358 auto& items = actual[net.name()][path];
359
360 for( const auto& item : sheet.items() )
361 {
362 BOOST_CHECK_NE( item.value(), undriven->m_Uuid.AsStdString() );
363 items.insert( item.value() );
364 }
365
366 if( path.size() > 2 )
367 nestedItems += items.size();
368 }
369 }
370
371 BOOST_CHECK( actual.contains( "/S0.BOOT.SDA" ) );
372 BOOST_CHECK( actual.contains( "/S1.BOOT.SDA" ) );
373 BOOST_CHECK_GT( nestedItems, 0 );
374
375 if( published )
376 {
377 BOOST_CHECK( actual == expected );
378 }
379 else
380 {
381 expected = std::move( actual );
382 }
383 }
384 }
385 }
386}
387
388BOOST_AUTO_TEST_CASE( NetlistReportsRebuildFailureAndAllowsRetry )
389{
390 struct FAILING_LABEL : SCH_LABEL
391 {
392 explicit FAILING_LABEL( const SCH_LABEL& source ) : SCH_LABEL( source ) {}
393
394 wxString GetShownText( const SCH_SHEET_PATH* path, RESOLUTION_CONTEXT context, int depth = 0 ) const override
395 {
396 if( fail )
397 throw std::runtime_error( "Native label extraction failed" );
398
399 return SCH_LABEL::GetShownText( path, context, depth );
400 }
401
402 bool fail = false;
403 };
404
405 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
406 SCOPED_SET_RESET restore( enabled, false );
407 SCHEMATIC* model = loadSchematic( wxS( "net_chains_four_nets_labeled" ) );
408 auto* screen = model->Hierarchy().front().LastScreen();
409 auto labels = screen->Items().OfType( SCH_LABEL_T );
410 BOOST_REQUIRE( labels.begin() != labels.end() );
411 auto* original = static_cast<SCH_LABEL*>( *labels.begin() );
412 auto* replacement = new FAILING_LABEL( *original );
413 screen->Remove( original );
414 delete original;
415 screen->Append( replacement );
416 model->ConnectionGraph()->Reset();
417 replacement->fail = true;
418 enabled = true;
419 API_HANDLER_SCH handler( m_context );
420 kiapi::schematic::commands::GetSchematicNetlist command;
421 *command.mutable_document() = makeDocument( *model );
422 ApiRequest request;
423 request.mutable_header()->set_client_name( "kicad.qa" );
424 BOOST_REQUIRE( request.mutable_message()->PackFrom( command ) );
425 API_RESULT result = handler.Handle( request );
426 BOOST_REQUIRE( !result.has_value() );
427 BOOST_CHECK_EQUAL( result.error().status(), ApiStatusCode::AS_UNKNOWN );
428 BOOST_CHECK_EQUAL( result.error().error_message(), "Native label extraction failed" );
429 replacement->fail = false;
430 result = handler.Handle( request );
431 BOOST_REQUIRE( result.has_value() );
432 kiapi::schematic::commands::SchematicNetlistResponse response;
433 BOOST_REQUIRE( result->message().UnpackTo( &response ) );
434 BOOST_CHECK_GT( response.nets_size(), 0 );
435}
436
const char * name
tl::expected< ApiResponse, ApiResponseStatus > API_RESULT
Definition api_handler.h:44
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
API_RESULT Handle(ApiRequest &aMsg)
Attempt to handle the given API request, if a handler exists in this class for the message.
const KIID m_Uuid
Definition eda_item.h:599
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
Definition kiid.h:46
std::string AsStdString() const
Definition kiid.cpp:270
Holds all the data relating to one schematic.
Definition schematic.h:149
PROJECT & Project() const
Return a reference to the project this schematic is part of.
Definition schematic.h:171
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, int aDepth=0) const override
SCH_LABEL(const VECTOR2I &aPos=VECTOR2I(0, 0), const wxString &aText=wxEmptyString)
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
VECTOR2I GetStartPoint() const
Definition sch_line.h:136
void Move(const VECTOR2I &aMoveVector) override
Move the item by aMoveVector to a new position.
Definition sch_line.cpp:247
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
RAII class that sets an value at construction and resets it to the original value at destruction.
RESOLUTION_CONTEXT
Definition common.h:89
static std::string ToStdString(const wxString &aStr)
std::string source
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
KICOMMON_API void PackProject(types::ProjectSpecifier &aOutput, const PROJECT &aInput)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
KIBIS_MODEL * model
VECTOR3I expected(15, 30, 45)
BOOST_AUTO_TEST_CASE(RevertDocumentRejectedWithOpenCommit)
BOOST_TEST_CONTEXT("Test Clearance")
int actual
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_LABEL_T
Definition typeinfo.h:163
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708