OILS / pyext / libc_test.py View on Github | oils.pub

314 lines, 176 significant
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
9from __future__ import print_function
10"""
11libc_test.py: Tests for libc.py
12"""
13import unittest
14import sys
15
16import libc # module under test
17
18# guard some tests that fail on Darwin
19IS_DARWIN = sys.platform == 'darwin'
20
21class 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
311if __name__ == '__main__':
312 # To simulate the OVM_MAIN patch in pythonrun.c
313 libc.cpython_reset_locale()
314 unittest.main()