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