KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_remote_symbol_import.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,
12 * but 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 <boost/test/unit_test.hpp>
21
22#include <eeschema_settings.h>
23#include <io/io_mgr.h>
24#include <lib_symbol.h>
27#include <pgm_base.h>
28#include <picosha2.h>
29#include <project.h>
30#include <project_sch.h>
34#include <sch_io/sch_io.h>
35#include <sch_io/sch_io_mgr.h>
37
39
40#include <wx/ffile.h>
41#include <wx/filefn.h>
42#include <wx/filename.h>
43#include <wx/stdpaths.h>
44
45
46namespace
47{
48// The served payload is the shared qa/data library with its symbol renamed, so a download
49// under a different name still yields a parseable .kicad_sym.
50std::string symbolPayload( const char* aName )
51{
52 wxString libPath = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() )
53 + wxS( "remote_symbol_lib.kicad_sym" );
54
55 wxFFile file( libPath, wxS( "rb" ) );
56 BOOST_REQUIRE_MESSAGE( file.IsOpened(), "Could not open " << libPath );
57
58 wxString body;
59 BOOST_REQUIRE( file.ReadAll( &body ) );
60
61 body.Replace( wxS( "TestResistor" ), wxString::FromUTF8( aName ) );
62
63 return std::string( body.ToUTF8() );
64}
65
66
67wxString sha256Hex( const std::string& aPayload )
68{
69 std::string hashHex;
70 picosha2::hash256_hex_string( aPayload.begin(), aPayload.end(), hashHex );
71 return wxString::FromUTF8( hashHex.c_str() );
72}
73
74
75wxString tempDir()
76{
77 wxString path = wxFileName::CreateTempFileName( wxS( "remote-symbol-import" ) );
78 wxRemoveFile( path );
79 wxMkdir( path );
80 return path;
81}
82
83
85{
87 metadata.provider_name = wxString( "Acme" );
88 metadata.provider_version = wxString( "1.0.0" );
89 metadata.api_base_url = wxString( "https://provider.example.test/api" );
90 metadata.max_download_bytes = 4096;
91 metadata.parts_v1 = true;
92 metadata.direct_downloads_v1 = true;
93 return metadata;
94}
95
96REMOTE_SYMBOL_IMPORT_CONTEXT importContext()
97{
99 context.symbol_name = wxString( "R" );
100 context.library_name = wxString( "Device" );
101 return context;
102}
103
104
106{
108 manifest.part_id = wxString( "acme-res-10k" );
109 manifest.display_name = wxString( "RC0603FR-0710KL" );
110
112 const std::string symbolBlob = symbolPayload( "R" );
113 symbol.asset_type = wxString( "symbol" );
114 symbol.name = wxString( "acme-res-10k.kicad_sym" );
115 symbol.content_type = wxString( "application/x-kicad-symbol" );
116 symbol.size_bytes = static_cast<long long>( symbolBlob.size() );
117 symbol.sha256 = sha256Hex( symbolBlob );
118 symbol.download_url = wxString( "https://provider.example.test/downloads/acme-res-10k.kicad_sym" );
119 symbol.required = true;
120 symbol.target_library = wxString( "Device" );
121 symbol.target_name = wxString( "R" );
122
124 footprint.asset_type = wxString( "footprint" );
125 footprint.name = wxString( "R_0603.pretty" );
126 footprint.content_type = wxString( "application/x-kicad-footprint" );
127 footprint.size_bytes = 44;
128 footprint.sha256 = wxString( "8d8090740282c9ec23541a148af0ae57543e0da581e00e714e066dc4a1adefb0" );
129 footprint.download_url = wxString( "https://provider.example.test/downloads/R_0603.pretty" );
130 footprint.required = false;
131 footprint.target_library = wxString( "Resistor_SMD" );
132 footprint.target_name = wxString( "R_0603_1608Metric" );
133
134 manifest.assets = { symbol, footprint };
135 return manifest;
136}
137} // namespace
138
139
140BOOST_AUTO_TEST_SUITE( RemoteSymbolImportTests )
141
142BOOST_AUTO_TEST_CASE( ImportWritesDownloadedAssets )
143{
144 const wxString outputDir = tempDir();
146 BOOST_REQUIRE( settings );
147 settings->m_RemoteSymbol.destination_dir = outputDir;
148 settings->m_RemoteSymbol.library_prefix = wxString( "testremote" );
149 settings->m_RemoteSymbol.add_to_global_table = true;
150
152 [&]( const wxString& aUrl, REMOTE_SYMBOL_FETCH_RESPONSE& aResponse, wxString& aError )
153 {
154 wxUnusedVar( aError );
155 aResponse.status_code = 200;
156
157 if( aUrl.EndsWith( wxString( "acme-res-10k.kicad_sym" ) ) )
158 {
159 aResponse.content_type = wxString( "application/x-kicad-symbol" );
160 const std::string payload = symbolPayload( "R" );
161 aResponse.payload.assign( payload.begin(), payload.end() );
162 return true;
163 }
164
165 aResponse.content_type = wxString( "application/x-kicad-footprint" );
166 const std::string payload = "(module \"R_0603_1608Metric\" (layer \"F.Cu\"))\n";
167 aResponse.payload.assign( payload.begin(), payload.end() );
168 return true;
169 } );
170
171 REMOTE_SYMBOL_IMPORT_JOB job( nullptr, &downloader );
172 wxString error;
173
174 BOOST_REQUIRE( job.Import( provider(), importContext(), manifest(), false, error ) );
175
176 wxFileName symbolPath( outputDir, wxString() );
177 symbolPath.AppendDir( wxString( "symbols" ) );
178 symbolPath.SetFullName( wxString( "testremote_device.kicad_sym" ) );
179 BOOST_CHECK( symbolPath.FileExists() );
180
181 wxFileName footprintPath( outputDir, wxString() );
182 footprintPath.AppendDir( wxString( "footprints" ) );
183 footprintPath.AppendDir( wxString( "testremote_resistor_smd.pretty" ) );
184 footprintPath.SetFullName( wxString( "R_0603_1608Metric.kicad_mod" ) );
185 BOOST_CHECK( footprintPath.FileExists() );
186
187 // The downloaded symbol's Footprint field must point to the local LIB_ID of the
188 // bundled footprint so that subsequent placement / CvPcb sees the link.
189 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
190 BOOST_REQUIRE( plugin );
191
192 LIB_SYMBOL* loaded = plugin->LoadSymbol( symbolPath.GetFullPath(), wxString( "R" ) );
193 BOOST_REQUIRE( loaded );
194 BOOST_CHECK_EQUAL( loaded->GetFootprintProp().ToStdString(),
195 std::string( "testremote_resistor_smd:R_0603_1608Metric" ) );
196}
197
198BOOST_AUTO_TEST_CASE( ImportRejectsSymbolPayloadThatDoesNotContainExpectedName )
199{
200 const wxString outputDir = tempDir();
202 BOOST_REQUIRE( settings );
203 settings->m_RemoteSymbol.destination_dir = outputDir;
204 settings->m_RemoteSymbol.library_prefix = wxString( "testremote" );
205 settings->m_RemoteSymbol.add_to_global_table = true;
206
208 [&]( const wxString& aUrl, REMOTE_SYMBOL_FETCH_RESPONSE& aResponse, wxString& aError )
209 {
210 wxUnusedVar( aError );
211 aResponse.status_code = 200;
212
213 if( aUrl.EndsWith( wxString( "acme-res-10k.kicad_sym" ) ) )
214 {
215 aResponse.content_type = wxString( "application/x-kicad-symbol" );
216 const std::string payload = symbolPayload( "WrongName" );
217 aResponse.payload.assign( payload.begin(), payload.end() );
218 return true;
219 }
220
221 aResponse.content_type = wxString( "application/x-kicad-footprint" );
222 const std::string payload = "(module \"R_0603_1608Metric\" (layer \"F.Cu\"))\n";
223 aResponse.payload.assign( payload.begin(), payload.end() );
224 return true;
225 } );
226
227 REMOTE_SYMBOL_IMPORT_JOB job( nullptr, &downloader );
228 wxString error;
229
230 BOOST_CHECK( !job.Import( provider(), importContext(), manifest(), false, error ) );
231 BOOST_CHECK( !error.IsEmpty() );
232}
233
234BOOST_AUTO_TEST_CASE( ImportLinksFirstFootprintAndAddsAlternatesAsFilters )
235{
236 const wxString outputDir = tempDir();
238 BOOST_REQUIRE( settings );
239 settings->m_RemoteSymbol.destination_dir = outputDir;
240 settings->m_RemoteSymbol.library_prefix = wxString( "testremote" );
241 settings->m_RemoteSymbol.add_to_global_table = true;
242
243 REMOTE_PROVIDER_PART_MANIFEST multiManifest;
244 multiManifest.part_id = wxString( "acme-res-10k" );
245
246 REMOTE_PROVIDER_PART_ASSET symbolAsset;
247 const std::string symbolBlob = symbolPayload( "R" );
248 symbolAsset.asset_type = wxString( "symbol" );
249 symbolAsset.name = wxString( "acme-res-10k.kicad_sym" );
250 symbolAsset.content_type = wxString( "application/x-kicad-symbol" );
251 symbolAsset.size_bytes = static_cast<long long>( symbolBlob.size() );
252 symbolAsset.sha256 = sha256Hex( symbolBlob );
253 symbolAsset.download_url = wxString( "https://provider.example.test/downloads/acme-res-10k.kicad_sym" );
254 symbolAsset.target_library = wxString( "Device" );
255 symbolAsset.target_name = wxString( "R" );
256
257 const std::string fpBlob0603 = "(module \"R_0603_1608Metric\" (layer \"F.Cu\"))\n";
258 const std::string fpBlob0805 = "(module \"R_0805_2012Metric\" (layer \"F.Cu\"))\n";
259
261 fpPrimary.asset_type = wxString( "footprint" );
262 fpPrimary.name = wxString( "R_0603.pretty" );
263 fpPrimary.content_type = wxString( "application/x-kicad-footprint" );
264 fpPrimary.size_bytes = static_cast<long long>( fpBlob0603.size() );
265 fpPrimary.sha256 = sha256Hex( fpBlob0603 );
266 fpPrimary.download_url = wxString( "https://provider.example.test/downloads/R_0603.kicad_mod" );
267 fpPrimary.target_library = wxString( "Resistor_SMD" );
268 fpPrimary.target_name = wxString( "R_0603_1608Metric" );
269
271 fpAlt.asset_type = wxString( "footprint" );
272 fpAlt.name = wxString( "R_0805.pretty" );
273 fpAlt.content_type = wxString( "application/x-kicad-footprint" );
274 fpAlt.size_bytes = static_cast<long long>( fpBlob0805.size() );
275 fpAlt.sha256 = sha256Hex( fpBlob0805 );
276 fpAlt.download_url = wxString( "https://provider.example.test/downloads/R_0805.kicad_mod" );
277 fpAlt.target_library = wxString( "Resistor_SMD" );
278 fpAlt.target_name = wxString( "R_0805_2012Metric" );
279
280 // Intentionally list the symbol BEFORE the footprints to verify the import job
281 // reorders processing so the symbol's footprint field can resolve.
282 multiManifest.assets = { symbolAsset, fpPrimary, fpAlt };
283
285 [&]( const wxString& aUrl, REMOTE_SYMBOL_FETCH_RESPONSE& aResponse, wxString& aError )
286 {
287 wxUnusedVar( aError );
288 aResponse.status_code = 200;
289
290 if( aUrl.EndsWith( wxString( "acme-res-10k.kicad_sym" ) ) )
291 {
292 aResponse.content_type = wxString( "application/x-kicad-symbol" );
293 aResponse.payload.assign( symbolBlob.begin(), symbolBlob.end() );
294 return true;
295 }
296
297 if( aUrl.EndsWith( wxString( "R_0805.kicad_mod" ) ) )
298 {
299 aResponse.content_type = wxString( "application/x-kicad-footprint" );
300 aResponse.payload.assign( fpBlob0805.begin(), fpBlob0805.end() );
301 return true;
302 }
303
304 aResponse.content_type = wxString( "application/x-kicad-footprint" );
305 aResponse.payload.assign( fpBlob0603.begin(), fpBlob0603.end() );
306 return true;
307 } );
308
309 REMOTE_SYMBOL_IMPORT_JOB job( nullptr, &downloader );
310 wxString error;
311 BOOST_REQUIRE( job.Import( provider(), importContext(), multiManifest, false, error ) );
312
313 wxFileName symbolPath( outputDir, wxString() );
314 symbolPath.AppendDir( wxString( "symbols" ) );
315 symbolPath.SetFullName( wxString( "testremote_device.kicad_sym" ) );
316 BOOST_REQUIRE( symbolPath.FileExists() );
317
318 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
319 BOOST_REQUIRE( plugin );
320
321 LIB_SYMBOL* loaded = plugin->LoadSymbol( symbolPath.GetFullPath(), wxString( "R" ) );
322 BOOST_REQUIRE( loaded );
323
324 BOOST_CHECK_EQUAL( loaded->GetFootprintProp().ToStdString(),
325 std::string( "testremote_resistor_smd:R_0603_1608Metric" ) );
326
327 const wxArrayString filters = loaded->GetFPFilters();
328 BOOST_CHECK_EQUAL( filters.GetCount(), 1u );
329 if( filters.GetCount() == 1 )
330 BOOST_CHECK_EQUAL( filters[0].ToStdString(), std::string( "R_0805_2012Metric" ) );
331}
332
333// The first save into a remote library must create its file rather than fail to load it
334BOOST_AUTO_TEST_CASE( SaveCreatesMissingSymbolLibrary )
335{
336 const wxString outputDir = tempDir();
337 wxFileName projectFile( outputDir, wxS( "remote" ), wxS( "kicad_pro" ) );
338
339 Pgm().GetSettingsManager().LoadProject( projectFile.GetFullPath() );
341 Pgm().GetLibraryManager().LoadProjectTables( project.GetProjectDirectory() );
342
344 BOOST_REQUIRE( adapter );
345
346 const std::string payload = symbolPayload( "R" );
347 wxString error;
348
349 std::unique_ptr<LIB_SYMBOL> symbol = LoadRemoteSymbolFromPayload(
350 std::vector<uint8_t>( payload.begin(), payload.end() ), wxS( "R" ), error );
351 BOOST_REQUIRE( symbol );
352
353 const wxString nickname = wxS( "testremote_device" );
354 wxFileName libFile( outputDir, nickname, wxS( "kicad_sym" ) );
355
356 BOOST_CHECK_MESSAGE( SaveRemoteSymbolToLibrary( *adapter, libFile, nickname, false, std::move( symbol ), error ),
357 error );
358}
359
360
REMOTE_PROVIDER_SETTINGS m_RemoteSymbol
void LoadProjectTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the project library tables in the given list, or all tables if no list is given
Define a library symbol object.
Definition lib_symbol.h:119
wxArrayString GetFPFilters() const
Definition lib_symbol.h:247
wxString GetFootprintProp() const
Definition lib_symbol.h:492
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:125
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
Container for project specific data.
Definition project.h:63
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
static std::string ToStdString(const wxString &aStr)
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
std::unique_ptr< LIB_SYMBOL > LoadRemoteSymbolFromPayload(const std::vector< uint8_t > &aPayload, const wxString &aLibItemName, wxString &aError)
Deserialize a remote-downloaded symbol payload into a LIB_SYMBOL.
bool SaveRemoteSymbolToLibrary(SYMBOL_LIBRARY_ADAPTER &aAdapter, const wxFileName &aLibraryFile, const wxString &aNickname, bool aGlobalTable, std::unique_ptr< LIB_SYMBOL > aSymbol, wxString &aError)
Save a downloaded symbol into the remote symbol library aNickname backed by aLibraryFile.
T * GetAppSettings(const char *aFilename)
std::vector< REMOTE_PROVIDER_PART_ASSET > assets
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string path
BOOST_AUTO_TEST_CASE(ImportWritesDownloadedAssets)
BOOST_CHECK_EQUAL(result, "25.4")