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 along
17 * with this program. If not, see <http://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>
25#include <picosha2.h>
28#include <sch_io/sch_io.h>
29#include <sch_io/sch_io_mgr.h>
31
32#include <wx/filefn.h>
33#include <wx/filename.h>
34#include <wx/stdpaths.h>
35
36
37namespace
38{
39std::string symbolPayload( const char* aName )
40{
41 return wxString::Format(
42 wxS( "(kicad_symbol_lib (version 20220914) (generator kicad_symbol_editor)\n"
43 " (symbol \"%s\" (in_bom yes) (on_board yes)\n"
44 " (property \"Reference\" \"R\" (at 0 0 0)\n"
45 " (effects (font (size 1.27 1.27)))\n"
46 " )\n"
47 " (property \"Value\" \"%s\" (at 0 0 0)\n"
48 " (effects (font (size 1.27 1.27)))\n"
49 " )\n"
50 " (property \"Footprint\" \"\" (at 0 0 0)\n"
51 " (effects (font (size 1.27 1.27)) hide)\n"
52 " )\n"
53 " (property \"Datasheet\" \"\" (at 0 0 0)\n"
54 " (effects (font (size 1.27 1.27)) hide)\n"
55 " )\n"
56 " (symbol \"%s_0_1\"\n"
57 " (rectangle (start -1.27 -1.27) (end 1.27 1.27)\n"
58 " (stroke (width 0) (type default))\n"
59 " (fill (type background))\n"
60 " )\n"
61 " )\n"
62 " (symbol \"%s_1_1\"\n"
63 " (pin passive line (at -3.81 0 0) (length 2.54)\n"
64 " (name \"PIN\" (effects (font (size 1.27 1.27))))\n"
65 " (number \"1\" (effects (font (size 1.27 1.27))))\n"
66 " )\n"
67 " )\n"
68 " )\n"
69 ")\n" ),
70 wxString::FromUTF8( aName ), wxString::FromUTF8( aName ),
71 wxString::FromUTF8( aName ), wxString::FromUTF8( aName ) )
72 .ToStdString();
73}
74
75
76wxString sha256Hex( const std::string& aPayload )
77{
78 std::string hashHex;
79 picosha2::hash256_hex_string( aPayload.begin(), aPayload.end(), hashHex );
80 return wxString::FromUTF8( hashHex.c_str() );
81}
82
83
84wxString tempDir()
85{
86 wxString path = wxFileName::CreateTempFileName( wxS( "remote-symbol-import" ) );
87 wxRemoveFile( path );
88 wxMkdir( path );
89 return path;
90}
91
92
94{
96 metadata.provider_name = wxString( "Acme" );
97 metadata.provider_version = wxString( "1.0.0" );
98 metadata.api_base_url = wxString( "https://provider.example.test/api" );
99 metadata.max_download_bytes = 4096;
100 metadata.parts_v1 = true;
101 metadata.direct_downloads_v1 = true;
102 return metadata;
103}
104
105REMOTE_SYMBOL_IMPORT_CONTEXT importContext()
106{
108 context.symbol_name = wxString( "R" );
109 context.library_name = wxString( "Device" );
110 return context;
111}
112
113
115{
117 manifest.part_id = wxString( "acme-res-10k" );
118 manifest.display_name = wxString( "RC0603FR-0710KL" );
119
121 const std::string symbolBlob = symbolPayload( "R" );
122 symbol.asset_type = wxString( "symbol" );
123 symbol.name = wxString( "acme-res-10k.kicad_sym" );
124 symbol.content_type = wxString( "application/x-kicad-symbol" );
125 symbol.size_bytes = static_cast<long long>( symbolBlob.size() );
126 symbol.sha256 = sha256Hex( symbolBlob );
127 symbol.download_url = wxString( "https://provider.example.test/downloads/acme-res-10k.kicad_sym" );
128 symbol.required = true;
129 symbol.target_library = wxString( "Device" );
130 symbol.target_name = wxString( "R" );
131
133 footprint.asset_type = wxString( "footprint" );
134 footprint.name = wxString( "R_0603.pretty" );
135 footprint.content_type = wxString( "application/x-kicad-footprint" );
136 footprint.size_bytes = 44;
137 footprint.sha256 = wxString( "8d8090740282c9ec23541a148af0ae57543e0da581e00e714e066dc4a1adefb0" );
138 footprint.download_url = wxString( "https://provider.example.test/downloads/R_0603.pretty" );
139 footprint.required = false;
140 footprint.target_library = wxString( "Resistor_SMD" );
141 footprint.target_name = wxString( "R_0603_1608Metric" );
142
143 manifest.assets = { symbol, footprint };
144 return manifest;
145}
146} // namespace
147
148
149BOOST_AUTO_TEST_SUITE( RemoteSymbolImportTests )
150
151BOOST_AUTO_TEST_CASE( ImportWritesDownloadedAssets )
152{
153 const wxString outputDir = tempDir();
155 BOOST_REQUIRE( settings );
156 settings->m_RemoteSymbol.destination_dir = outputDir;
157 settings->m_RemoteSymbol.library_prefix = wxString( "testremote" );
158 settings->m_RemoteSymbol.add_to_global_table = true;
159
161 [&]( const wxString& aUrl, REMOTE_SYMBOL_FETCH_RESPONSE& aResponse, wxString& aError )
162 {
163 wxUnusedVar( aError );
164 aResponse.status_code = 200;
165
166 if( aUrl.EndsWith( wxString( "acme-res-10k.kicad_sym" ) ) )
167 {
168 aResponse.content_type = wxString( "application/x-kicad-symbol" );
169 const std::string payload = symbolPayload( "R" );
170 aResponse.payload.assign( payload.begin(), payload.end() );
171 return true;
172 }
173
174 aResponse.content_type = wxString( "application/x-kicad-footprint" );
175 const std::string payload = "(module \"R_0603_1608Metric\" (layer \"F.Cu\"))\n";
176 aResponse.payload.assign( payload.begin(), payload.end() );
177 return true;
178 } );
179
180 REMOTE_SYMBOL_IMPORT_JOB job( nullptr, &downloader );
181 wxString error;
182
183 BOOST_REQUIRE( job.Import( provider(), importContext(), manifest(), false, error ) );
184
185 wxFileName symbolPath( outputDir, wxString() );
186 symbolPath.AppendDir( wxString( "symbols" ) );
187 symbolPath.SetFullName( wxString( "testremote_device.kicad_sym" ) );
188 BOOST_CHECK( symbolPath.FileExists() );
189
190 wxFileName footprintPath( outputDir, wxString() );
191 footprintPath.AppendDir( wxString( "footprints" ) );
192 footprintPath.AppendDir( wxString( "testremote_resistor_smd.pretty" ) );
193 footprintPath.SetFullName( wxString( "R_0603_1608Metric.kicad_mod" ) );
194 BOOST_CHECK( footprintPath.FileExists() );
195
196 // The downloaded symbol's Footprint field must point to the local LIB_ID of the
197 // bundled footprint so that subsequent placement / CvPcb sees the link.
198 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
199 BOOST_REQUIRE( plugin );
200
201 LIB_SYMBOL* loaded = plugin->LoadSymbol( symbolPath.GetFullPath(), wxString( "R" ) );
202 BOOST_REQUIRE( loaded );
203 BOOST_CHECK_EQUAL( loaded->GetFootprintProp().ToStdString(),
204 std::string( "testremote_resistor_smd:R_0603_1608Metric" ) );
205}
206
207BOOST_AUTO_TEST_CASE( ImportRejectsSymbolPayloadThatDoesNotContainExpectedName )
208{
209 const wxString outputDir = tempDir();
211 BOOST_REQUIRE( settings );
212 settings->m_RemoteSymbol.destination_dir = outputDir;
213 settings->m_RemoteSymbol.library_prefix = wxString( "testremote" );
214 settings->m_RemoteSymbol.add_to_global_table = true;
215
217 [&]( const wxString& aUrl, REMOTE_SYMBOL_FETCH_RESPONSE& aResponse, wxString& aError )
218 {
219 wxUnusedVar( aError );
220 aResponse.status_code = 200;
221
222 if( aUrl.EndsWith( wxString( "acme-res-10k.kicad_sym" ) ) )
223 {
224 aResponse.content_type = wxString( "application/x-kicad-symbol" );
225 const std::string payload = symbolPayload( "WrongName" );
226 aResponse.payload.assign( payload.begin(), payload.end() );
227 return true;
228 }
229
230 aResponse.content_type = wxString( "application/x-kicad-footprint" );
231 const std::string payload = "(module \"R_0603_1608Metric\" (layer \"F.Cu\"))\n";
232 aResponse.payload.assign( payload.begin(), payload.end() );
233 return true;
234 } );
235
236 REMOTE_SYMBOL_IMPORT_JOB job( nullptr, &downloader );
237 wxString error;
238
239 BOOST_CHECK( !job.Import( provider(), importContext(), manifest(), false, error ) );
240 BOOST_CHECK( !error.IsEmpty() );
241}
242
243BOOST_AUTO_TEST_CASE( ImportLinksFirstFootprintAndAddsAlternatesAsFilters )
244{
245 const wxString outputDir = tempDir();
247 BOOST_REQUIRE( settings );
248 settings->m_RemoteSymbol.destination_dir = outputDir;
249 settings->m_RemoteSymbol.library_prefix = wxString( "testremote" );
250 settings->m_RemoteSymbol.add_to_global_table = true;
251
252 REMOTE_PROVIDER_PART_MANIFEST multiManifest;
253 multiManifest.part_id = wxString( "acme-res-10k" );
254
255 REMOTE_PROVIDER_PART_ASSET symbolAsset;
256 const std::string symbolBlob = symbolPayload( "R" );
257 symbolAsset.asset_type = wxString( "symbol" );
258 symbolAsset.name = wxString( "acme-res-10k.kicad_sym" );
259 symbolAsset.content_type = wxString( "application/x-kicad-symbol" );
260 symbolAsset.size_bytes = static_cast<long long>( symbolBlob.size() );
261 symbolAsset.sha256 = sha256Hex( symbolBlob );
262 symbolAsset.download_url = wxString( "https://provider.example.test/downloads/acme-res-10k.kicad_sym" );
263 symbolAsset.target_library = wxString( "Device" );
264 symbolAsset.target_name = wxString( "R" );
265
266 const std::string fpBlob0603 = "(module \"R_0603_1608Metric\" (layer \"F.Cu\"))\n";
267 const std::string fpBlob0805 = "(module \"R_0805_2012Metric\" (layer \"F.Cu\"))\n";
268
270 fpPrimary.asset_type = wxString( "footprint" );
271 fpPrimary.name = wxString( "R_0603.pretty" );
272 fpPrimary.content_type = wxString( "application/x-kicad-footprint" );
273 fpPrimary.size_bytes = static_cast<long long>( fpBlob0603.size() );
274 fpPrimary.sha256 = sha256Hex( fpBlob0603 );
275 fpPrimary.download_url = wxString( "https://provider.example.test/downloads/R_0603.kicad_mod" );
276 fpPrimary.target_library = wxString( "Resistor_SMD" );
277 fpPrimary.target_name = wxString( "R_0603_1608Metric" );
278
280 fpAlt.asset_type = wxString( "footprint" );
281 fpAlt.name = wxString( "R_0805.pretty" );
282 fpAlt.content_type = wxString( "application/x-kicad-footprint" );
283 fpAlt.size_bytes = static_cast<long long>( fpBlob0805.size() );
284 fpAlt.sha256 = sha256Hex( fpBlob0805 );
285 fpAlt.download_url = wxString( "https://provider.example.test/downloads/R_0805.kicad_mod" );
286 fpAlt.target_library = wxString( "Resistor_SMD" );
287 fpAlt.target_name = wxString( "R_0805_2012Metric" );
288
289 // Intentionally list the symbol BEFORE the footprints to verify the import job
290 // reorders processing so the symbol's footprint field can resolve.
291 multiManifest.assets = { symbolAsset, fpPrimary, fpAlt };
292
294 [&]( const wxString& aUrl, REMOTE_SYMBOL_FETCH_RESPONSE& aResponse, wxString& aError )
295 {
296 wxUnusedVar( aError );
297 aResponse.status_code = 200;
298
299 if( aUrl.EndsWith( wxString( "acme-res-10k.kicad_sym" ) ) )
300 {
301 aResponse.content_type = wxString( "application/x-kicad-symbol" );
302 aResponse.payload.assign( symbolBlob.begin(), symbolBlob.end() );
303 return true;
304 }
305
306 if( aUrl.EndsWith( wxString( "R_0805.kicad_mod" ) ) )
307 {
308 aResponse.content_type = wxString( "application/x-kicad-footprint" );
309 aResponse.payload.assign( fpBlob0805.begin(), fpBlob0805.end() );
310 return true;
311 }
312
313 aResponse.content_type = wxString( "application/x-kicad-footprint" );
314 aResponse.payload.assign( fpBlob0603.begin(), fpBlob0603.end() );
315 return true;
316 } );
317
318 REMOTE_SYMBOL_IMPORT_JOB job( nullptr, &downloader );
319 wxString error;
320 BOOST_REQUIRE( job.Import( provider(), importContext(), multiManifest, false, error ) );
321
322 wxFileName symbolPath( outputDir, wxString() );
323 symbolPath.AppendDir( wxString( "symbols" ) );
324 symbolPath.SetFullName( wxString( "testremote_device.kicad_sym" ) );
325 BOOST_REQUIRE( symbolPath.FileExists() );
326
327 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
328 BOOST_REQUIRE( plugin );
329
330 LIB_SYMBOL* loaded = plugin->LoadSymbol( symbolPath.GetFullPath(), wxString( "R" ) );
331 BOOST_REQUIRE( loaded );
332
333 BOOST_CHECK_EQUAL( loaded->GetFootprintProp().ToStdString(),
334 std::string( "testremote_resistor_smd:R_0603_1608Metric" ) );
335
336 const wxArrayString filters = loaded->GetFPFilters();
337 BOOST_CHECK_EQUAL( filters.GetCount(), 1u );
338 if( filters.GetCount() == 1 )
339 BOOST_CHECK_EQUAL( filters[0].ToStdString(), std::string( "R_0805_2012Metric" ) );
340}
341
342
REMOTE_PROVIDER_SETTINGS m_RemoteSymbol
Define a library symbol object.
Definition lib_symbol.h:83
wxArrayString GetFPFilters() const
Definition lib_symbol.h:211
wxString GetFootprintProp() const
Definition lib_symbol.h:388
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
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")