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