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