KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_string_utils.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
21#define BOOST_TEST_NO_MAIN
23
24#include <string_utils.h>
25
26
27BOOST_AUTO_TEST_SUITE( StringUtilsTests )
28
29BOOST_AUTO_TEST_CASE( GeneratedNetNamesUseReservedPrefixes )
30{
31 BOOST_CHECK( IsAutoGeneratedNetName( "unconnected-(VD1A-A-Pad2)" ) );
32 BOOST_CHECK( IsAutoGeneratedNetName( "Net-(VD1-Pad2)" ) );
33 BOOST_CHECK( !IsAutoGeneratedNetName( "/Net-(A)/SIG" ) );
34 BOOST_CHECK( !IsAutoGeneratedNetName( "MyNet-(U1-Pad2)" ) );
35 BOOST_CHECK( !IsAutoGeneratedNetName( wxString() ) );
36}
37
38
39// std::string overload: no illegal chars -> returns false and string unchanged
40BOOST_AUTO_TEST_CASE( ReplaceIllegalFileNameChars_StdString_NoChange )
41{
42 std::string name = "valid_filename-123._ok";
43 std::string original = name;
44
45 bool changed = ReplaceIllegalFileNameChars( name );
46
47 BOOST_TEST( changed == false );
48 BOOST_TEST( name == original );
49}
50
51// std::string overload: with illegal chars, default behavior -> percent-hex encoding
52BOOST_AUTO_TEST_CASE( ReplaceIllegalFileNameChars_StdString_PercentEncoding )
53{
54 // Contains several illegal chars: \ / ? * | " < >
55 std::string name = R"(bad\name/with?illegal*chars|"<>.txt)";
56 bool changed = ReplaceIllegalFileNameChars( name ); // aReplaceChar defaults to 0 -> percent-hex
57
58 BOOST_TEST( changed == true );
59
60 // Illegal characters should have been hex-encoded
61 BOOST_TEST( name.find( "%5c" ) != std::string::npos ); // backslash
62 BOOST_TEST( name.find( "%2f" ) != std::string::npos ); // forward slash
63 BOOST_TEST( name.find( "%3f" ) != std::string::npos ); // question mark
64 BOOST_TEST( name.find( "%2a" ) != std::string::npos ); // asterisk
65 BOOST_TEST( name.find( "%7c" ) != std::string::npos ); // vertical bar
66 BOOST_TEST( name.find( "%22" ) != std::string::npos ); // double quote
67 BOOST_TEST( name.find( "%3c" ) != std::string::npos ); // less-than
68 BOOST_TEST( name.find( "%3e" ) != std::string::npos ); // greater-than
69
70 // Non-illegal characters should remain unchanged
71 BOOST_TEST( name.find( ".txt" ) != std::string::npos );
72 BOOST_TEST( name.find( "bad" ) != std::string::npos );
73}
74
75// std::string overload: replacement character provided -> use that character
76BOOST_AUTO_TEST_CASE( ReplaceIllegalFileNameChars_StdString_ReplacementChar )
77{
78 std::string name = R"(a*bad?name|file)";
79 bool changed = ReplaceIllegalFileNameChars( name, '_' ); // replace illegal with underscore
80
81 BOOST_TEST( changed == true );
82 BOOST_TEST( name == "a_bad_name_file" );
83}
84
85// wxString overload: no illegal chars -> returns false and string unchanged
86BOOST_AUTO_TEST_CASE( ReplaceIllegalFileNameChars_WxString_NoChange )
87{
88 wxString name = "valid_filename-123._ok";
89 wxString original = name;
90
91 bool changed = ReplaceIllegalFileNameChars( name );
92
93 BOOST_TEST( changed == false );
94 BOOST_TEST( name == original );
95}
96
97// wxString overload: percent-hex encoding when aReplaceChar == 0
98BOOST_AUTO_TEST_CASE( ReplaceIllegalFileNameChars_WxString_PercentEncoding )
99{
100 wxString name = "bad\\name/with?illegal*chars|\"<>.txt";
101 bool changed = ReplaceIllegalFileNameChars( name ); // default -> percent-hex
102
103 BOOST_TEST( changed == true );
104
105 // Illegal characters should have been hex-encoded
106 BOOST_TEST( name.Contains( "%5c" ) ); // backslash
107 BOOST_TEST( name.Contains( "%2f" ) ); // forward slash
108 BOOST_TEST( name.Contains( "%3f" ) ); // question mark
109 BOOST_TEST( name.Contains( "%2a" ) ); // asterisk
110 BOOST_TEST( name.Contains( "%7c" ) ); // vertical bar
111 BOOST_TEST( name.Contains( "%22" ) ); // double quote
112 BOOST_TEST( name.Contains( "%3c" ) ); // less-than
113 BOOST_TEST( name.Contains( "%3e" ) ); // greater-than
114
115 // Non-illegal characters should remain unchanged
116 BOOST_TEST( name.Contains( ".txt" ) );
117 BOOST_TEST( name.Contains( "bad" ) );
118}
119
120// wxString overload: replacement character provided -> use that character
121BOOST_AUTO_TEST_CASE( ReplaceIllegalFileNameChars_WxString_ReplacementChar )
122{
123 wxString name = "a*bad?name|file";
124 bool changed = ReplaceIllegalFileNameChars( name, '_' );
125
126 BOOST_TEST( changed == true );
127 BOOST_TEST( name == "a_bad_name_file" );
128}
129
130BOOST_AUTO_TEST_CASE( ConvertPathToFileUri_UnixAbsolutePath )
131{
132 // /tmp exists on all Unix CI machines
133 BOOST_TEST( ConvertPathToFileUri( wxS( "/tmp" ), nullptr ) == wxString( "file:///tmp" ) );
134}
135
136BOOST_AUTO_TEST_CASE( ConvertPathToFileUri_AlreadyHasScheme )
137{
138 BOOST_TEST( ConvertPathToFileUri( wxS( "https://example.com" ), nullptr ) == wxString( "https://example.com" ) );
139 BOOST_TEST( ConvertPathToFileUri( wxS( "file:///path" ), nullptr ) == wxString( "file:///path" ) );
140}
141
142BOOST_AUTO_TEST_CASE( ConvertPathToFileUri_Empty )
143{
144 BOOST_TEST( ConvertPathToFileUri( wxEmptyString, nullptr ) == wxString( "" ) );
145}
146
147BOOST_AUTO_TEST_CASE( ConvertPathToFileUri_Tilde )
148{
149 BOOST_TEST( ConvertPathToFileUri( wxS( "~" ), nullptr ) == wxString( "~" ) );
150}
151
152BOOST_AUTO_TEST_CASE( ConvertPathToFileUri_NonexistentPath )
153{
154 BOOST_TEST( ConvertPathToFileUri( wxS( "/nonexistent/file.pdf" ), nullptr )
155 == wxString( "/nonexistent/file.pdf" ) );
156}
157
158BOOST_AUTO_TEST_CASE( ConvertPathToFileUri_PlainText )
159{
160 // Non-path strings should pass through unchanged
161 BOOST_TEST( ConvertPathToFileUri( wxS( "LM358" ), nullptr ) == wxString( "LM358" ) );
162 BOOST_TEST( ConvertPathToFileUri( wxS( "100nF" ), nullptr ) == wxString( "100nF" ) );
163}
164
165
166// Helper: format a vector of pin numbers for diagnostics.
167static wxString JoinPins( const std::vector<wxString>& aPins )
168{
169 wxString out;
170
171 for( const wxString& p : aPins )
172 out << '<' << p << '>';
173
174 return out;
175}
176
177
178// A plain pin number (no brackets) is always a single pin, even when it contains the characters
179// that are structural inside stacked notation. This is the root cause of issue 23782's second
180// bug, where a comma-containing pin number was mistaken for stacked notation.
181BOOST_AUTO_TEST_CASE( StackedPinNotation_CommaInPlainNumberIsSinglePin )
182{
183 bool valid = false;
184
185 std::vector<wxString> expanded = ExpandStackedPinNotation( wxS( "1,foo,bar,buz" ), &valid );
186 BOOST_TEST( valid );
187 BOOST_REQUIRE_EQUAL( expanded.size(), 1u );
188 BOOST_TEST( expanded[0] == wxString( "1,foo,bar,buz" ) );
189
190 BOOST_TEST( CountStackedPinNotation( wxS( "1,foo,bar,buz" ), &valid ) == 1 );
191 BOOST_TEST( valid );
192}
193
194
195// A pin number whose literal text contains a comma must survive a round-trip through stacked
196// notation when escaped, instead of being split into several phantom pins (issue 23782, bug 1).
197BOOST_AUTO_TEST_CASE( StackedPinNotation_EscapedCommaRoundTrips )
198{
199 const wxString raw = wxS( "1,foo" );
200 const wxString escaped = EscapeStackedPinItem( raw );
201
202 BOOST_TEST( escaped == wxString( "1\\,foo" ) );
203
204 const wxString notation = wxS( "[" ) + escaped + wxS( ",2]" );
205
206 bool valid = false;
207 std::vector<wxString> expanded = ExpandStackedPinNotation( notation, &valid );
208
209 BOOST_TEST( valid );
210 BOOST_TEST_INFO( "expanded: " << JoinPins( expanded ) );
211 BOOST_REQUIRE_EQUAL( expanded.size(), 2u );
212 BOOST_TEST( expanded[0] == raw );
213 BOOST_TEST( expanded[1] == wxString( "2" ) );
214
215 BOOST_TEST( CountStackedPinNotation( notation, &valid ) == 2 );
216 BOOST_TEST( valid );
217}
218
219
220// An escaped dash is a literal character, not a range separator.
221BOOST_AUTO_TEST_CASE( StackedPinNotation_EscapedDashIsLiteral )
222{
223 const wxString notation = wxS( "[A\\-B,C]" );
224
225 bool valid = false;
226 std::vector<wxString> expanded = ExpandStackedPinNotation( notation, &valid );
227
228 BOOST_TEST( valid );
229 BOOST_TEST_INFO( "expanded: " << JoinPins( expanded ) );
230 BOOST_REQUIRE_EQUAL( expanded.size(), 2u );
231 BOOST_TEST( expanded[0] == wxString( "A-B" ) );
232 BOOST_TEST( expanded[1] == wxString( "C" ) );
233
234 // An unescaped dash still forms a range.
235 expanded = ExpandStackedPinNotation( wxS( "[1-3]" ), &valid );
236 BOOST_TEST( valid );
237 BOOST_REQUIRE_EQUAL( expanded.size(), 3u );
238 BOOST_TEST( expanded[0] == wxString( "1" ) );
239 BOOST_TEST( expanded[2] == wxString( "3" ) );
240}
241
242
243// Escaping characters that are not special leaves them untouched, and all special characters are
244// escaped exactly once.
245BOOST_AUTO_TEST_CASE( StackedPinNotation_EscapeStackedPinItem )
246{
247 BOOST_TEST( EscapeStackedPinItem( wxS( "A1" ) ) == wxString( "A1" ) );
248 BOOST_TEST( EscapeStackedPinItem( wxS( "a,b" ) ) == wxString( "a\\,b" ) );
249 BOOST_TEST( EscapeStackedPinItem( wxS( "a-b" ) ) == wxString( "a\\-b" ) );
250 BOOST_TEST( EscapeStackedPinItem( wxS( "[x]" ) ) == wxString( "\\[x\\]" ) );
251 BOOST_TEST( EscapeStackedPinItem( wxS( "a\\b" ) ) == wxString( "a\\\\b" ) );
252}
253
254
255// Existing, unescaped notation must continue to behave exactly as before (regression guard).
256BOOST_AUTO_TEST_CASE( StackedPinNotation_LegacyNotationUnchanged )
257{
258 bool valid = false;
259
260 std::vector<wxString> expanded = ExpandStackedPinNotation( wxS( "[6,7,9-11]" ), &valid );
261 BOOST_TEST( valid );
262 BOOST_REQUIRE_EQUAL( expanded.size(), 5u );
263 BOOST_TEST( expanded[0] == wxString( "6" ) );
264 BOOST_TEST( expanded[1] == wxString( "7" ) );
265 BOOST_TEST( expanded[2] == wxString( "9" ) );
266 BOOST_TEST( expanded[3] == wxString( "10" ) );
267 BOOST_TEST( expanded[4] == wxString( "11" ) );
268
269 BOOST_TEST( CountStackedPinNotation( wxS( "[6,7,9-11]" ), &valid ) == 5 );
270 BOOST_TEST( valid );
271}
272
273
274// The display splitter preserves range items and unescapes literal items.
275BOOST_AUTO_TEST_CASE( StackedPinNotation_DisplaySplitPreservesRanges )
276{
277 std::vector<wxString> items = SplitStackedPinDisplayItems( wxS( "1-5,a\\,b,7" ) );
278
279 BOOST_TEST_INFO( "items: " << JoinPins( items ) );
280 BOOST_REQUIRE_EQUAL( items.size(), 3u );
281 BOOST_TEST( items[0] == wxString( "1-5" ) );
282 BOOST_TEST( items[1] == wxString( "a,b" ) );
283 BOOST_TEST( items[2] == wxString( "7" ) );
284}
285
286
287// A backslash that does not precede a structural character is a literal character, so legacy
288// notation that contained an un-escaped backslash keeps it.
289BOOST_AUTO_TEST_CASE( StackedPinNotation_LiteralBackslashPreserved )
290{
291 bool valid = false;
292
293 std::vector<wxString> expanded = ExpandStackedPinNotation( wxS( "[A\\B,C]" ), &valid );
294 BOOST_TEST( valid );
295 BOOST_TEST_INFO( "expanded: " << JoinPins( expanded ) );
296 BOOST_REQUIRE_EQUAL( expanded.size(), 2u );
297 BOOST_TEST( expanded[0] == wxString( "A\\B" ) );
298 BOOST_TEST( expanded[1] == wxString( "C" ) );
299
300 // A backslash before a structural character round-trips through escape/unescape.
301 BOOST_TEST( EscapeStackedPinItem( wxS( "A\\B" ) ) == wxString( "A\\\\B" ) );
302}
303
304
305// An empty pin number is a single, valid pin regardless of whether validity is requested.
306BOOST_AUTO_TEST_CASE( StackedPinNotation_EmptyIsSinglePin )
307{
308 bool valid = false;
309
310 BOOST_TEST( CountStackedPinNotation( wxEmptyString, &valid ) == 1 );
311 BOOST_TEST( valid );
312
313 BOOST_TEST( CountStackedPinNotation( wxEmptyString, nullptr ) == 1 );
314
315 std::vector<wxString> expanded = ExpandStackedPinNotation( wxEmptyString, &valid );
316 BOOST_TEST( valid );
317 BOOST_REQUIRE_EQUAL( expanded.size(), 1u );
318 BOOST_TEST( expanded[0] == wxEmptyString );
319}
320
321
328
329
330BOOST_AUTO_TEST_CASE( StringEscape )
331{
332 // clang-format off
333 const std::vector<STRING_ESCAPE_TEST_CASE> testStrings = {
334 { CTX_NETNAME, wxS( "" ), wxS( "" ) },
335 { CTX_NETNAME, wxS( "abc" ), wxS( "abc" ) },
336 { CTX_NETNAME, wxS( "a/,b" ), wxS( "a{slash},b" ) },
337 { CTX_NETNAME, wxS( "a{b}c" ), wxS( "a{b}c" ) },
338 { CTX_NETNAME, wxS( "a{bc" ), wxS( "a{bc" ) },
339 { CTX_NETNAME, wxS( "abc}" ), wxS( "abc}" ) },
340 };
341 // clang-format on
342
343 for( const auto& testString : testStrings )
344 {
345 BOOST_TEST_INFO( "Testing EscapeString with input: " << testString.Input );
346
347 const wxString actual = EscapeString( testString.Input, testString.Context );
348
349 BOOST_TEST( actual == testString.Expected );
350
351 // Test the round-trip.
352 const wxString unescaped = UnescapeString( actual );
353
354 BOOST_TEST( unescaped == testString.Input );
355 }
356}
357
358
const char * name
std::vector< wxString > ExpandStackedPinNotation(const wxString &aPinName, bool *aValid)
Expand stacked pin notation like [1,2,3], [1-4], [A1-A4], or [AA1-AA3,AB4,CD12-CD14] into individual ...
wxString UnescapeString(const wxString &aSource)
wxString ConvertPathToFileUri(const wxString &aPath, const PROJECT *aProject)
Convert a file path to a file:// URI.
bool IsAutoGeneratedNetName(const wxString &aNetName)
Recognize the reserved prefixes used for generated pin-net names.
std::vector< wxString > SplitStackedPinDisplayItems(const wxString &aInner)
Split the inner part of a stacked notation string (the text between the brackets) into its individual...
bool ReplaceIllegalFileNameChars(std::string &aName, int aReplaceChar)
Checks aName for illegal file name characters.
wxString EscapeStackedPinItem(const wxString &aPinNumber)
Escape the characters that carry structural meaning inside stacked pin notation ('[',...
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
int CountStackedPinNotation(const wxString &aPinName, bool *aValid)
Count the number of pins represented by stacked pin notation.
ESCAPE_CONTEXT
Escape/Unescape routines to safely encode reserved-characters in various contexts.
@ CTX_NETNAME
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
BOOST_TEST_INFO("Two-port Series .op current = "<< iDevice)
int actual
BOOST_AUTO_TEST_CASE(GeneratedNetNamesUseReservedPrefixes)
static wxString JoinPins(const std::vector< wxString > &aPins)