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