20#ifndef QA_API_E2E_UTILS_H
21#define QA_API_E2E_UTILS_H
28#include <google/protobuf/any.pb.h>
29#include <magic_enum.hpp>
30#include <wx/filename.h>
31#include <wx/process.h>
43#include <api/common/envelope.pb.h>
44#include <api/common/commands/base_commands.pb.h>
45#include <api/common/commands/editor_commands.pb.h>
46#include <api/common/commands/project_commands.pb.h>
47#include <api/common/types/base_types.pb.h>
48#include <api/common/types/enums.pb.h>
49#include <api/common/types/jobs.pb.h>
56#ifndef QA_KICAD_CLI_PATH
57#define QA_KICAD_CLI_PATH "kicad-cli"
82 static void drainStream( wxInputStream* aStream, wxString& aDest )
84 if( !aStream || !aStream->CanRead() )
87 constexpr size_t chunkSize = 1024;
88 char buffer[chunkSize + 1] = { 0 };
90 while( aStream->CanRead() )
92 aStream->Read( buffer, chunkSize );
93 size_t bytesRead = aStream->LastRead();
98 buffer[bytesRead] =
'\0';
99 aDest += wxString::FromUTF8( buffer );
104 std::function<void(
int,
const wxString&,
const wxString& )>
m_callback;
118 bool Ping( kiapi::common::ApiStatusCode* aStatusOut =
nullptr )
120 kiapi::common::commands::Ping ping;
121 kiapi::common::ApiResponse response;
123 if( !
send( ping, response ) )
127 *aStatusOut = response.status().status();
134 kiapi::common::commands::GetVersion request;
135 kiapi::common::ApiResponse response;
137 if( !
send( request, response ) )
140 if( response.status().status() != kiapi::common::AS_OK )
146 kiapi::common::commands::GetVersionResponse version;
148 if( !response.message().UnpackTo( &version ) )
150 m_lastError = wxS(
"Failed to unpack GetVersionResponse" );
157 bool OpenDocument(
const wxString& aPath, kiapi::common::types::DocumentType aType,
158 kiapi::common::types::DocumentSpecifier* aDocument =
nullptr )
160 kiapi::common::commands::OpenDocument request;
161 request.set_type( aType );
162 request.set_path( aPath.ToStdString() );
164 kiapi::common::ApiResponse response;
166 if( !
send( request, response ) )
169 if( response.status().status() != kiapi::common::AS_OK )
175 kiapi::common::commands::OpenDocumentResponse openResponse;
177 if( !response.message().UnpackTo( &openResponse ) )
179 m_lastError = wxS(
"Failed to unpack OpenDocumentResponse" );
184 *aDocument = openResponse.document();
189 bool CreateDocument(
const wxString& aPath, kiapi::common::types::DocumentType aType,
190 kiapi::common::types::DocumentSpecifier* aDocument =
nullptr )
192 kiapi::common::commands::CreateDocument request;
193 request.set_type( aType );
194 request.set_path( aPath.ToStdString() );
196 kiapi::common::ApiResponse response;
198 if( !
send( request, response ) )
201 if( response.status().status() != kiapi::common::AS_OK )
207 kiapi::common::commands::OpenDocumentResponse openResponse;
209 if( !response.message().UnpackTo( &openResponse ) )
211 m_lastError = wxS(
"Failed to unpack OpenDocumentResponse" );
216 *aDocument = openResponse.document();
222 bool OpenDocument(
const wxString& aPath, kiapi::common::types::DocumentSpecifier* aDocument =
nullptr )
224 return OpenDocument( aPath, kiapi::common::types::DOCTYPE_PCB, aDocument );
227 bool GetItemsCount(
const kiapi::common::types::DocumentSpecifier& aDocument,
228 kiapi::common::types::KiCadObjectType aType,
int* aCount )
230 kiapi::common::commands::GetItems request;
231 *request.mutable_header()->mutable_document() = aDocument;
232 request.add_types( aType );
234 kiapi::common::ApiResponse response;
236 if( !
send( request, response ) )
239 if( response.status().status() != kiapi::common::AS_OK )
245 kiapi::common::commands::GetItemsResponse itemsResponse;
247 if( !response.message().UnpackTo( &itemsResponse ) )
249 m_lastError = wxS(
"Failed to unpack GetItemsResponse" );
253 if( itemsResponse.status() != kiapi::common::types::IRS_OK )
255 m_lastError = wxString::Format( wxS(
"GetItems returned non-OK status: %d" ),
256 static_cast<int>( itemsResponse.status() ) );
260 *aCount = itemsResponse.items_size();
266 wxCHECK( aFootprint,
false );
268 kiapi::common::commands::GetItems request;
269 *request.mutable_header()->mutable_document() = aDocument;
270 request.add_types( kiapi::common::types::KOT_PCB_FOOTPRINT );
272 kiapi::common::ApiResponse response;
274 if( !
send( request, response ) )
280 if( response.status().status() != kiapi::common::AS_OK )
286 kiapi::common::commands::GetItemsResponse itemsResponse;
288 if( !response.message().UnpackTo( &itemsResponse ) )
290 m_lastError = wxS(
"Failed to unpack GetItemsResponse" );
294 if( itemsResponse.status() != kiapi::common::types::IRS_OK )
296 m_lastError = wxString::Format( wxS(
"GetItems returned non-OK status: %s" ),
297 magic_enum::enum_name( itemsResponse.status() ) );
301 if( itemsResponse.items_size() == 0 )
303 m_lastError = wxS(
"GetItems returned no footprints" );
307 if( !aFootprint->
Deserialize( itemsResponse.items( 0 ) ) )
309 m_lastError = wxS(
"Failed to deserialize first footprint from GetItems response" );
323 wxCHECK( aFootprint,
false );
325 kiapi::common::commands::GetItems request;
326 *request.mutable_header()->mutable_document() = aDocument;
327 request.add_types( kiapi::common::types::KOT_PCB_FOOTPRINT );
329 kiapi::common::ApiResponse response;
331 if( !
send( request, response ) )
337 if( response.status().status() != kiapi::common::AS_OK )
343 kiapi::common::commands::GetItemsResponse itemsResponse;
345 if( !response.message().UnpackTo( &itemsResponse ) )
347 m_lastError = wxS(
"Failed to unpack GetItemsResponse" );
351 if( itemsResponse.status() != kiapi::common::types::IRS_OK )
353 m_lastError = wxString::Format( wxS(
"GetItems returned non-OK status: %s" ),
354 magic_enum::enum_name( itemsResponse.status() ) );
358 for(
const google::protobuf::Any& item : itemsResponse.items() )
367 if( reference && reference->
GetText() == aReference )
369 *aFootprint = std::move( candidate );
374 m_lastError = wxS(
"No footprint with reference " ) + aReference;
378 bool CloseDocument(
const kiapi::common::types::DocumentSpecifier* aDocument,
379 kiapi::common::ApiStatusCode* aStatus =
nullptr )
381 kiapi::common::commands::CloseDocument request;
384 *request.mutable_document() = *aDocument;
386 kiapi::common::ApiResponse response;
388 if( !
send( request, response ) )
395 *aStatus = response.status().status();
397 if( response.status().status() != kiapi::common::AS_OK )
405 kiapi::common::commands::CloseAllDocuments request;
406 kiapi::common::ApiResponse response;
408 if( !
send( request, response ) )
411 if( response.status().status() != kiapi::common::AS_OK )
423 template <
typename T>
424 bool RunJob(
const T& aJobRequest, kiapi::common::types::RunJobResponse* aResponse )
426 kiapi::common::ApiResponse apiResponse;
428 if( !
send( aJobRequest, apiResponse ) )
431 if( apiResponse.status().status() != kiapi::common::AS_OK )
433 m_lastError = apiResponse.status().error_message();
437 if( !apiResponse.message().UnpackTo( aResponse ) )
439 m_lastError = wxS(
"Failed to unpack RunJobResponse" );
446 template <
typename T>
447 bool SendCommand(
const T& aMessage, kiapi::common::ApiResponse* aResponse )
449 return send( aMessage, *aResponse );
453 bool send(
const google::protobuf::Message& aRequest, kiapi::common::ApiResponse& aResponse )
455 if( !
m_client.Send( aRequest, aResponse,
"kicad.qa" ) )
531 wxProcess::Kill(
m_pid, wxSIGKILL );
533 wxProcess::Kill(
m_pid, wxSIGKILL, wxKILL_CHILDREN );
548 m_socketPath = wxString::Format( wxS(
"/tmp/kicad-api-e2e-%ld.sock" ), wxGetProcessId() );
550 wxString tempPath = wxFileName::CreateTempFileName( wxS(
"kicad-api-e2e" ) );
552 if( wxFileExists( tempPath ) )
553 wxRemoveFile( tempPath );
571 kill(
static_cast<pid_t
>(
m_pid ), SIGTERM );
573 kill(
static_cast<pid_t
>(
m_pid ), SIGKILL );
580 wxProcess::Kill(
m_pid, wxSIGKILL, wxKILL_CHILDREN );
595 if( !wxFileExists( aCliPath ) )
598 *aError = wxS(
"kicad-cli path does not exist: " ) + aCliPath;
608 [
this](
int aStatus,
const wxString& aOutput,
const wxString& aErrOutput )
620 std::vector<const wchar_t*> args = { aCliPath.wc_str(), wxS(
"api-server" ), wxS(
"--socket" ),
631 *aError = wxS(
"Failed to launch kicad-cli api-server" );
641 auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds( 15 );
643 while( std::chrono::steady_clock::now() < deadline )
651 *aError = wxS(
"kicad-cli process exited before ready" );
658 wxLogTrace(
traceApi,
"kicad-cli connect attempt failed, will retry" );
662 kiapi::common::ApiStatusCode pingStatus = kiapi::common::AS_UNKNOWN;
666 if( pingStatus == kiapi::common::AS_OK )
669 if( pingStatus != kiapi::common::AS_NOT_READY )
673 *aError = wxString::Format( wxS(
"Ping returned unexpected status: %s" ),
674 magic_enum::enum_name( pingStatus ) );
680 wxLogTrace(
traceApi,
"kicad-cli connected but returned AS_NOT_READY, will retry" );
684 wxLogTrace(
traceApi,
"kicad-cli ping attempt failed, will retry" );
689 *aError = wxS(
"Timed out waiting for kicad-cli to start up" );
710 pid_t
result = waitpid(
static_cast<pid_t
>(
m_pid ), &status, WNOHANG );
717 if( WIFSIGNALED( status ) )
721 strsignal( WTERMSIG( status ) ) ) );
723 else if( WIFEXITED( status ) )
726 WEXITSTATUS( status ) ) );
733 return wxProcess::Exists(
m_pid );
771 bool Start(
const wxString& aCliPathOverride = wxString() )
773 wxString cliPath = aCliPathOverride.IsEmpty() ?
m_cliPath : aCliPathOverride;
#define QA_KICAD_CLI_PATH
General utilities for PCB file IO for QA programs.
bool Start(const wxString &aCliPathOverride=wxString())
Starts the API server if not already running. If running, closes any open documents.
~API_SERVER_E2E_FIXTURE()
const wxString & LastError() const
API_TEST_CLIENT & Client()
Manages a single shared kicad-cli api-server process and NNG client across all E2E tests.
API_TEST_CLIENT & Client()
API_SERVER_PROCESS * m_process
bool connectAndWaitReady(wxString *aError)
API_SERVER_MANAGER & operator=(const API_SERVER_MANAGER &)=delete
static API_SERVER_MANAGER & Instance()
bool isProcessAlive() const
API_SERVER_MANAGER(const API_SERVER_MANAGER &)=delete
void collectServerOutput()
bool startServerProcess(const wxString &aCliPath, wxString *aError)
void Reset()
Kill the current server and reset state so EnsureReady() will start fresh.
bool EnsureReady(const wxString &aCliPath, wxString *aError)
Ensure the api-server is running and the shared client is connected and responsive.
void OnTerminate(int aPid, int aStatus) override
API_SERVER_PROCESS(std::function< void(int, const wxString &, const wxString &)> aCallback)
std::function< void(int, const wxString &, const wxString &)> m_callback
static void drainStream(wxInputStream *aStream, wxString &aDest)
bool GetItemsCount(const kiapi::common::types::DocumentSpecifier &aDocument, kiapi::common::types::KiCadObjectType aType, int *aCount)
bool send(const google::protobuf::Message &aRequest, kiapi::common::ApiResponse &aResponse)
bool CreateDocument(const wxString &aPath, kiapi::common::types::DocumentType aType, kiapi::common::types::DocumentSpecifier *aDocument=nullptr)
bool GetFootprintByReference(const kiapi::common::types::DocumentSpecifier &aDocument, const wxString &aReference, FOOTPRINT *aFootprint)
Fetch all footprints and deserialize the one whose reference designator matches aReference.
bool OpenDocument(const wxString &aPath, kiapi::common::types::DocumentType aType, kiapi::common::types::DocumentSpecifier *aDocument=nullptr)
bool RunJob(const T &aJobRequest, kiapi::common::types::RunJobResponse *aResponse)
Send an arbitrary job request and receive a RunJobResponse.
KICAD_API_CLIENT m_client
bool CloseDocument(const kiapi::common::types::DocumentSpecifier *aDocument, kiapi::common::ApiStatusCode *aStatus=nullptr)
bool Connect(const wxString &aSocketUrl)
const wxString & LastError() const
bool OpenDocument(const wxString &aPath, kiapi::common::types::DocumentSpecifier *aDocument=nullptr)
bool SendCommand(const T &aMessage, kiapi::common::ApiResponse *aResponse)
bool Ping(kiapi::common::ApiStatusCode *aStatusOut=nullptr)
bool GetFirstFootprint(const kiapi::common::types::DocumentSpecifier &aDocument, FOOTPRINT *aFootprint)
virtual const wxString & GetText() const
Return the string associated with the text object.
const wxChar *const traceApi
Flag to enable debug output related to the IPC API and its plugin system.
@ REFERENCE
Field Reference of part, i.e. "IC21".
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
wxString result
Test unit parsing edge cases and error handling.