1 | #!/usr/bin/env python2
|
2 | # coding=utf8
|
3 | # Copyright 2016 Andy Chu. All rights reserved.
|
4 | # Licensed under the Apache License, Version 2.0 (the "License");
|
5 | # you may not use this file except in compliance with the License.
|
6 | # You may obtain a copy of the License at
|
7 | #
|
8 | # http://www.apache.org/licenses/LICENSE-2.0
|
9 | from __future__ import print_function
|
10 | """
|
11 | libc_test.py: Tests for libc.py
|
12 | """
|
13 | import unittest
|
14 | import sys
|
15 |
|
16 | import libc # module under test
|
17 |
|
18 | # guard some tests that fail on Darwin
|
19 | IS_DARWIN = sys.platform == 'darwin'
|
20 |
|
21 | class LibcTest(unittest.TestCase):
|
22 |
|
23 | def testFnmatch(self):
|
24 |
|
25 | cases = [
|
26 | # (pattern, string, result)
|
27 |
|
28 | ('', '', 1), # no pattern is valid
|
29 | ('a', 'a', 1),
|
30 | ('?', 'a', 1),
|
31 |
|
32 | # Test escaping of glob operator chars
|
33 | ('\\?', '-', 0),
|
34 | ('\\?', '?', 1),
|
35 |
|
36 | ('\\*', '-', 0),
|
37 | ('\\*', '*', 1),
|
38 |
|
39 | ('\\[', '-', 0),
|
40 | ('\\[', '[', 1),
|
41 |
|
42 | ('\\!', '-', 0),
|
43 | ('\\!', '!', 1),
|
44 |
|
45 | # What if we also escape extended glob chars?
|
46 | # Extra escaping is OK, so we should ALWAYS escape them.
|
47 | ('\\(', '(', 1),
|
48 | ('\\(', 'x', 0),
|
49 | ('\\(', '\\', 0),
|
50 | ('\\(', '\\(', 0),
|
51 |
|
52 | ('\\|', '|', 1),
|
53 | ('\\|', 'x', 0),
|
54 |
|
55 | ('\\\\', '\\', 1),
|
56 | ('\\\\', 'x', 0),
|
57 | ('\\\\', '\\extra', 0),
|
58 |
|
59 | ('\\f', '\\', 0), # no match
|
60 |
|
61 | # Hm this is weird, c is not a special character
|
62 | ('\\c', 'c', 1),
|
63 | ('\\c', '\\c', 0),
|
64 | ('\\\\c', '\\c', 1), # the proper way to match
|
65 |
|
66 | ('c:\\foo', 'c:\\foo', 0),
|
67 | ('c:\\foo', 'c:foo', 1),
|
68 |
|
69 | ('strange]one', 'strange]one', 1),
|
70 |
|
71 | # What is another error? Invalid escape is OK?
|
72 | None if IS_DARWIN else ('\\', '\\', 0), # no pattern is valid
|
73 |
|
74 | ('[[:alpha:]]', 'a', 1),
|
75 | ('[^[:alpha:]]', 'a', 0), # negate
|
76 | ('[[:alpha:]]', 'aa', 0), # exact match fails
|
77 |
|
78 | # Combining char class and a literal character
|
79 | ('[[:alpha:]7]', '7', 1),
|
80 | ('[[:alpha:]][[:alpha:]]', 'az', 1),
|
81 |
|
82 | ('[a]', 'a', 1),
|
83 | # Hm [] is treated as a constant string, not an empty char class.
|
84 | # Should we change LooksLikeGlob?
|
85 | ('[]', '', 0),
|
86 |
|
87 | ('[a-z]', 'a', 1),
|
88 | ('[a-z]', '-', 0),
|
89 |
|
90 | # THIS IS INCONSISTENT WITH REGEX!
|
91 | # Somehow in regexes (at least ERE) GNU libc treats [a\-z] as [a-z].
|
92 | # See below.
|
93 | ('[a\-z]', '-', 1),
|
94 | ('[a\-z]', 'b', 0),
|
95 |
|
96 | # Need double backslash in character class
|
97 | ('[\\\\]', '\\', 1),
|
98 |
|
99 | # Can you escape ] with \? Yes in fnmatch
|
100 | ('[\\]]', '\\', 0),
|
101 | ('[\\]]', ']', 1),
|
102 |
|
103 |
|
104 | None if IS_DARWIN else ('[]', 'a', 0),
|
105 | None if IS_DARWIN else ('[]', '[]', 1),
|
106 |
|
107 | ('?.c', 'a.c', 1),
|
108 | ('?.c', 'aa.c', 0),
|
109 | # mu character
|
110 | ('?.c', '\xce\xbc.c', 1),
|
111 | ]
|
112 |
|
113 | for pat, s, expected in filter(None, cases):
|
114 | actual = libc.fnmatch(pat, s)
|
115 | self.assertEqual(
|
116 | expected, actual, '%r %r -> got %d' % (pat, s, actual))
|
117 |
|
118 | def testFnmatchExtglob(self):
|
119 | # NOTE: We always use FNM_EXTMATCH when available
|
120 |
|
121 | # With GNU extension.
|
122 | cases = [
|
123 | # One of these
|
124 | ('--@(help|verbose)', '--verbose', 1),
|
125 | ('--@(help|verbose)', '--foo', 0),
|
126 |
|
127 | ('--*(help|verbose)', '--verbose', 1),
|
128 | ('--*(help|verbose)', '--', 1),
|
129 | ('--*(help|verbose)', '--helpverbose', 1), # Not what we want
|
130 |
|
131 | ('--+(help|verbose)', '--verbose', 1),
|
132 | ('--+(help|verbose)', '--', 0),
|
133 | ('--+(help|verbose)', '--helpverbose', 1), # Not what we want
|
134 |
|
135 | ('--?(help|verbose)', '--verbose', 1),
|
136 | ('--?(help|verbose)', '--helpverbose', 0),
|
137 |
|
138 | # Neither of these
|
139 | ('--!(help|verbose)', '--verbose', 0),
|
140 |
|
141 | # escaping *
|
142 | ('@(ab\*)', 'ab*', 1),
|
143 | ('@(ab\*)', 'abc', 0),
|
144 | # escaping ?
|
145 | ('@(ab\?)', 'ab?', 1),
|
146 | ('@(ab\?)', 'abc', 0),
|
147 |
|
148 | # escaping []
|
149 | ('@(ab\[\])', 'ab[]', 1),
|
150 | ('@(ab\[\])', 'abcd', 0),
|
151 |
|
152 | # escaping :
|
153 | ('@(ab\:)', 'ab:', 1),
|
154 | ('@(ab\:)', 'abc', 0),
|
155 |
|
156 | # escaping a is no-op
|
157 | (r'@(\ab)', 'ab', 1),
|
158 | (r'@(\ab)', r'\ab', 0),
|
159 |
|
160 | #('@(ab\|)', 'ab|', 1), # GNU libc bug? THIS SHOULD WORK
|
161 |
|
162 | # There's no way to escape | in extended glob??? wtf.
|
163 | #('@(ab\|)', 'ab', 1),
|
164 | #('@(ab\|)', 'ab\\', 1),
|
165 | #('@(ab\|)', 'ab\\|', 1),
|
166 | ]
|
167 | for pat, s, expected in cases:
|
168 | actual = libc.fnmatch(pat, s)
|
169 | self.assertEqual(expected, actual,
|
170 | "Matching %s against %s: got %s but expected %s" %
|
171 | (pat, s, actual, expected))
|
172 |
|
173 | def testGlob(self):
|
174 | print(libc.glob('*.py'))
|
175 |
|
176 | # This will not match anything!
|
177 | print(libc.glob('\\'))
|
178 | # This one will match a file named \
|
179 | print(libc.glob('\\\\'))
|
180 | print(libc.glob('[[:punct:]]'))
|
181 |
|
182 | def testRegexMatchError(self):
|
183 | # See core/util_test.py for more tests
|
184 | try:
|
185 | libc.regex_search(r'*', 0, 'abcd', 0)
|
186 | except ValueError as e:
|
187 | print(e)
|
188 | else:
|
189 | self.fail('Expected ValueError')
|
190 |
|
191 | def testRegexFirstGroupMatch(self):
|
192 | s='oXooXoooXoX'
|
193 | self.assertEqual(
|
194 | (1, 3),
|
195 | libc.regex_first_group_match('(X.)', s, 0))
|
196 |
|
197 | # Match from position 3
|
198 | self.assertEqual(
|
199 | (4, 6),
|
200 | libc.regex_first_group_match('(X.)', s, 3))
|
201 |
|
202 | # Match from position 3
|
203 | self.assertEqual(
|
204 | (8, 10),
|
205 | libc.regex_first_group_match('(X.)', s, 6))
|
206 |
|
207 | # Syntax Error
|
208 | self.assertRaises(
|
209 | RuntimeError, libc.regex_first_group_match, r'*', 'abcd', 0)
|
210 |
|
211 | def testRegexFirstGroupMatchError(self):
|
212 | # Helping to debug issue #291
|
213 | s = ''
|
214 | if 0:
|
215 | # Invalid regex syntax
|
216 | libc.regex_first_group_match("(['+-'])", s, 6)
|
217 |
|
218 | def testSpecialCharsInCharClass(self):
|
219 | CASES = [
|
220 | ("([a-z]+)", '123abc123', (3, 6)),
|
221 |
|
222 | # Uh what the heck, \- means the same thing as -? It's just ignored. At
|
223 | # least in GNU libc.
|
224 |
|
225 | # https://stackoverflow.com/questions/28495913/how-do-you-escape-a-hyphen-as-character-range-in-a-posix-regex
|
226 | # The <hyphen> character shall be treated as itself if it occurs first (after an initial '^', if any) or last in the list, or as an ending range point in a range expression
|
227 |
|
228 | ("([a\-z]+)", '123abc123', (3, 6)),
|
229 |
|
230 | # This is an inverted range. TODO: Need to fix the error message.
|
231 | #("([a\-.]+)", '123abc123', None),
|
232 |
|
233 | ("([\\\\]+)", 'a\\b', (1, 2)),
|
234 |
|
235 | # Can you escape ] with \? Yes in fnmatch, but NO here!!!
|
236 | ('([\\]])', '\\', None),
|
237 | ('([\\]])', ']', None),
|
238 |
|
239 | # Weird parsing!!!
|
240 | ('([\\]])', '\\]', (0, 2)),
|
241 |
|
242 | ]
|
243 |
|
244 | for pat, s, expected in CASES:
|
245 | result = libc.regex_first_group_match(pat, s, 0)
|
246 | self.assertEqual(expected, result,
|
247 | "FAILED: pat %r s %r result %s" % (pat, s, result))
|
248 |
|
249 | def testRealpathFailOnNonexistentDirectory(self):
|
250 | # This behaviour is actually inconsistent with GNU readlink,
|
251 | # but matches behaviour of busybox readlink
|
252 | # (https://github.com/jgunthorpe/busybox)
|
253 | self.assertEqual(None, libc.realpath('_tmp/nonexistent'))
|
254 |
|
255 | # Consistent with GNU
|
256 | self.assertEqual(None, libc.realpath('_tmp/nonexistent/supernonexistent'))
|
257 |
|
258 | def testPrintTime(self):
|
259 | print('', file=sys.stderr)
|
260 | libc.print_time(0.1, 0.2, 0.3)
|
261 | print('', file=sys.stderr)
|
262 |
|
263 | def testGethostname(self):
|
264 | print(libc.gethostname())
|
265 |
|
266 | def testGetTerminalWidth(self):
|
267 | try:
|
268 | width = libc.get_terminal_width()
|
269 | except IOError as e:
|
270 | print('error getting terminal width: %s' % e)
|
271 | else:
|
272 | print('width % d' % width)
|
273 |
|
274 | def testWcsWidth(self):
|
275 | if not IS_DARWIN:
|
276 | self.assertEqual(1, libc.wcswidth("▶️"))
|
277 | self.assertEqual(28, libc.wcswidth("(osh) ~/.../unchanged/oil ▶️ "))
|
278 |
|
279 | mu = u"\u03bc".encode('utf-8')
|
280 | print(repr(mu))
|
281 | print(mu)
|
282 | print(len(mu))
|
283 | self.assertEqual(1, libc.wcswidth(mu))
|
284 |
|
285 | self.assertEqual(2, libc.wcswidth("→ "))
|
286 |
|
287 | # mbstowcs fails on invalid utf-8
|
288 | try:
|
289 | # first byte of mu
|
290 | libc.wcswidth("\xce")
|
291 | except UnicodeError as e:
|
292 | self.assertEqual('mbstowcs() 1', e.message)
|
293 | else:
|
294 | self.fail('Expected failure')
|
295 |
|
296 | # wcswidth fails on unprintable character
|
297 | try:
|
298 | libc.wcswidth("\x01")
|
299 | except UnicodeError as e:
|
300 | self.assertEqual('wcswidth()', e.message)
|
301 | else:
|
302 | self.fail('Expected failure')
|
303 |
|
304 | self.assertRaises(UnicodeError, libc.wcswidth, "\xfe")
|
305 |
|
306 |
|
307 | if __name__ == '__main__':
|
308 | # To simulate the OVM_MAIN patch in pythonrun.c
|
309 | libc.cpython_reset_locale()
|
310 | unittest.main()
|