OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 import logging | |
7 import os | |
8 import unittest | |
9 import sys | |
10 | |
11 BASE_DIR = unicode(os.path.dirname(os.path.abspath(__file__))) | |
12 ROOT_DIR = os.path.dirname(BASE_DIR) | |
13 sys.path.insert(0, ROOT_DIR) | |
14 | |
15 FILE_PATH = unicode(os.path.abspath(__file__)) | |
16 | |
17 import trace_inputs | |
18 | |
19 | |
20 def join_norm(*args): | |
21 """Joins and normalizes path in a single step.""" | |
22 return unicode(os.path.normpath(os.path.join(*args))) | |
23 | |
24 | |
25 class TraceInputs(unittest.TestCase): | |
26 def test_process_quoted_arguments(self): | |
27 test_cases = ( | |
28 ('"foo"', ['foo']), | |
29 ('"foo", "bar"', ['foo', 'bar']), | |
30 ('"foo"..., "bar"', ['foo', 'bar']), | |
31 ('"foo", "bar"...', ['foo', 'bar']), | |
32 ( | |
33 '"/browser_tests", "--type=use,comma"', | |
34 ['/browser_tests', '--type=use,comma'] | |
35 ), | |
36 ( | |
37 '"/browser_tests", "--ignored=\\" --type=renderer \\""', | |
38 ['/browser_tests', '--ignored=" --type=renderer "'] | |
39 ), | |
40 ) | |
41 for actual, expected in test_cases: | |
42 self.assertEquals( | |
43 expected, trace_inputs.strace_process_quoted_arguments(actual)) | |
44 | |
45 def test_process_escaped_arguments(self): | |
46 test_cases = ( | |
47 ('foo\\0', ['foo']), | |
48 ('foo\\001bar\\0', ['foo', 'bar']), | |
49 ('\\"foo\\"\\0', ['"foo"']), | |
50 ) | |
51 for actual, expected in test_cases: | |
52 self.assertEquals( | |
53 expected, | |
54 trace_inputs.Dtrace.Context.process_escaped_arguments(actual)) | |
55 | |
56 def test_variable_abs(self): | |
57 value = trace_inputs.Results.File(None, '/foo/bar', False, False) | |
58 actual = value.replace_variables({'$FOO': '/foo'}) | |
59 self.assertEquals('$FOO/bar', actual.path) | |
60 self.assertEquals('$FOO/bar', actual.full_path) | |
61 self.assertEquals(True, actual.tainted) | |
62 | |
63 def test_variable_rel(self): | |
64 value = trace_inputs.Results.File('/usr', 'foo/bar', False, False) | |
65 actual = value.replace_variables({'$FOO': 'foo'}) | |
66 self.assertEquals('$FOO/bar', actual.path) | |
67 self.assertEquals(os.path.join('/usr', '$FOO/bar'), actual.full_path) | |
68 self.assertEquals(True, actual.tainted) | |
69 | |
70 def test_native_case_end_with_os_path_sep(self): | |
71 # Make sure the trailing os.path.sep is kept. | |
72 path = trace_inputs.get_native_path_case(ROOT_DIR) + os.path.sep | |
73 self.assertEquals(trace_inputs.get_native_path_case(path), path) | |
74 | |
75 def test_native_case_non_existing(self): | |
76 # Make sure it doesn't throw on non-existing files. | |
77 non_existing = 'trace_input_test_this_file_should_not_exist' | |
78 path = os.path.expanduser('~/' + non_existing) | |
79 self.assertFalse(os.path.exists(path)) | |
80 path = trace_inputs.get_native_path_case(ROOT_DIR) + os.path.sep | |
81 self.assertEquals(trace_inputs.get_native_path_case(path), path) | |
82 | |
83 if sys.platform in ('darwin', 'win32'): | |
84 def test_native_case_not_sensitive(self): | |
85 # The home directory is almost guaranteed to have mixed upper/lower case | |
86 # letters on both Windows and OSX. | |
87 # This test also ensures that the output is independent on the input | |
88 # string case. | |
89 path = os.path.expanduser('~') | |
90 self.assertTrue(os.path.isdir(path)) | |
91 # This test assumes the variable is in the native path case on disk, this | |
92 # should be the case. Verify this assumption: | |
93 self.assertEquals(path, trace_inputs.get_native_path_case(path)) | |
94 self.assertEquals( | |
95 trace_inputs.get_native_path_case(path.lower()), | |
96 trace_inputs.get_native_path_case(path.upper())) | |
97 | |
98 def test_native_case_not_sensitive_non_existent(self): | |
99 # This test also ensures that the output is independent on the input | |
100 # string case. | |
101 non_existing = os.path.join( | |
102 'trace_input_test_this_dir_should_not_exist', 'really not', '') | |
103 path = os.path.expanduser(os.path.join('~', non_existing)) | |
104 self.assertFalse(os.path.exists(path)) | |
105 lower = trace_inputs.get_native_path_case(path.lower()) | |
106 upper = trace_inputs.get_native_path_case(path.upper()) | |
107 # Make sure non-existing element is not modified: | |
108 self.assertTrue(lower.endswith(non_existing.lower())) | |
109 self.assertTrue(upper.endswith(non_existing.upper())) | |
110 self.assertEquals(lower[:-len(non_existing)], upper[:-len(non_existing)]) | |
111 | |
112 if sys.platform != 'win32': | |
113 def test_symlink(self): | |
114 # This test will fail if the checkout is in a symlink. | |
115 actual = trace_inputs.split_at_symlink(None, ROOT_DIR) | |
116 expected = (ROOT_DIR, None, None) | |
117 self.assertEquals(expected, actual) | |
118 | |
119 actual = trace_inputs.split_at_symlink( | |
120 None, os.path.join(BASE_DIR, 'trace_inputs')) | |
121 expected = ( | |
122 os.path.join(BASE_DIR, 'trace_inputs'), None, None) | |
123 self.assertEquals(expected, actual) | |
124 | |
125 actual = trace_inputs.split_at_symlink( | |
126 None, os.path.join(BASE_DIR, 'trace_inputs', 'files2')) | |
127 expected = ( | |
128 os.path.join(BASE_DIR, 'trace_inputs'), 'files2', '') | |
129 self.assertEquals(expected, actual) | |
130 | |
131 actual = trace_inputs.split_at_symlink( | |
132 ROOT_DIR, os.path.join('tests', 'trace_inputs', 'files2')) | |
133 expected = ( | |
134 os.path.join('tests', 'trace_inputs'), 'files2', '') | |
135 self.assertEquals(expected, actual) | |
136 actual = trace_inputs.split_at_symlink( | |
137 ROOT_DIR, os.path.join('tests', 'trace_inputs', 'files2', 'bar')) | |
138 expected = ( | |
139 os.path.join('tests', 'trace_inputs'), 'files2', '/bar') | |
140 self.assertEquals(expected, actual) | |
141 | |
142 def test_native_case_symlink_right_case(self): | |
143 actual = trace_inputs.get_native_path_case( | |
144 os.path.join(BASE_DIR, 'trace_inputs')) | |
145 self.assertEquals('trace_inputs', os.path.basename(actual)) | |
146 | |
147 # Make sure the symlink is not resolved. | |
148 actual = trace_inputs.get_native_path_case( | |
149 os.path.join(BASE_DIR, 'trace_inputs', 'files2')) | |
150 self.assertEquals('files2', os.path.basename(actual)) | |
151 | |
152 if sys.platform == 'darwin': | |
153 def test_native_case_symlink_wrong_case(self): | |
154 actual = trace_inputs.get_native_path_case( | |
155 os.path.join(BASE_DIR, 'trace_inputs')) | |
156 self.assertEquals('trace_inputs', os.path.basename(actual)) | |
157 | |
158 # Make sure the symlink is not resolved. | |
159 actual = trace_inputs.get_native_path_case( | |
160 os.path.join(BASE_DIR, 'trace_inputs', 'Files2')) | |
161 self.assertEquals('files2', os.path.basename(actual)) | |
162 | |
163 | |
164 if sys.platform != 'win32': | |
165 class StraceInputs(unittest.TestCase): | |
166 # Represents the root process pid (an arbitrary number). | |
167 _ROOT_PID = 27 | |
168 _CHILD_PID = 14 | |
169 _GRAND_CHILD_PID = 70 | |
170 | |
171 @staticmethod | |
172 def _load_context(lines, initial_cwd): | |
173 context = trace_inputs.Strace.Context(lambda _: False, initial_cwd) | |
174 for line in lines: | |
175 context.on_line(*line) | |
176 return context.to_results().flatten() | |
177 | |
178 def _test_lines(self, lines, initial_cwd, files, command=None): | |
179 filepath = join_norm(initial_cwd, '../out/unittests') | |
180 command = command or ['../out/unittests'] | |
181 expected = { | |
182 'root': { | |
183 'children': [], | |
184 'command': command, | |
185 'executable': filepath, | |
186 'files': files, | |
187 'initial_cwd': initial_cwd, | |
188 'pid': self._ROOT_PID, | |
189 } | |
190 } | |
191 if not files: | |
192 expected['root']['command'] = None | |
193 expected['root']['executable'] = None | |
194 self.assertEquals(expected, self._load_context(lines, initial_cwd)) | |
195 | |
196 def test_execve(self): | |
197 lines = [ | |
198 (self._ROOT_PID, | |
199 'execve("/home/foo_bar_user/out/unittests", ' | |
200 '["/home/foo_bar_user/out/unittests", ' | |
201 '"--gtest_filter=AtExitTest.Basic"], [/* 44 vars */]) = 0'), | |
202 (self._ROOT_PID, | |
203 'open("out/unittests.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 8'), | |
204 ] | |
205 files = [ | |
206 { | |
207 'path': u'/home/foo_bar_user/out/unittests', | |
208 'size': -1, | |
209 }, | |
210 { | |
211 'path': u'/home/foo_bar_user/src/out/unittests.log', | |
212 'size': -1, | |
213 }, | |
214 ] | |
215 command = [ | |
216 '/home/foo_bar_user/out/unittests', '--gtest_filter=AtExitTest.Basic', | |
217 ] | |
218 self._test_lines(lines, '/home/foo_bar_user/src', files, command) | |
219 | |
220 def test_empty(self): | |
221 try: | |
222 self._load_context([], None) | |
223 self.fail() | |
224 except trace_inputs.TracingFailure, e: | |
225 expected = ( | |
226 'Found internal inconsitency in process lifetime detection ' | |
227 'while finding the root process', | |
228 None, | |
229 None, | |
230 None, | |
231 []) | |
232 self.assertEquals(expected, e.args) | |
233 | |
234 def test_chmod(self): | |
235 lines = [ | |
236 (self._ROOT_PID, 'chmod("temp/file", 0100644) = 0'), | |
237 ] | |
238 self._test_lines(lines, '/home/foo_bar_user/src', []) | |
239 | |
240 def test_close(self): | |
241 lines = [ | |
242 (self._ROOT_PID, 'close(7) = 0'), | |
243 ] | |
244 self._test_lines(lines, '/home/foo_bar_user/src', []) | |
245 | |
246 def test_clone(self): | |
247 # Grand-child with relative directory. | |
248 lines = [ | |
249 (self._ROOT_PID, | |
250 'clone(child_stack=0, flags=CLONE_CHILD_CLEARTID' | |
251 '|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f5350f829d0) = %d' % | |
252 self._CHILD_PID), | |
253 (self._CHILD_PID, | |
254 'clone(child_stack=0, flags=CLONE_CHILD_CLEARTID' | |
255 '|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f5350f829d0) = %d' % | |
256 self._GRAND_CHILD_PID), | |
257 (self._GRAND_CHILD_PID, | |
258 'open("%s", O_RDONLY) = 76' % os.path.basename(FILE_PATH)), | |
259 ] | |
260 size = os.stat(FILE_PATH).st_size | |
261 expected = { | |
262 'root': { | |
263 'children': [ | |
264 { | |
265 'children': [ | |
266 { | |
267 'children': [], | |
268 'command': None, | |
269 'executable': None, | |
270 'files': [ | |
271 { | |
272 'path': FILE_PATH, | |
273 'size': size, | |
274 }, | |
275 ], | |
276 'initial_cwd': BASE_DIR, | |
277 'pid': self._GRAND_CHILD_PID, | |
278 }, | |
279 ], | |
280 'command': None, | |
281 'executable': None, | |
282 'files': [], | |
283 'initial_cwd': BASE_DIR, | |
284 'pid': self._CHILD_PID, | |
285 }, | |
286 ], | |
287 'command': None, | |
288 'executable': None, | |
289 'files': [], | |
290 'initial_cwd': BASE_DIR, | |
291 'pid': self._ROOT_PID, | |
292 }, | |
293 } | |
294 self.assertEquals(expected, self._load_context(lines, BASE_DIR)) | |
295 | |
296 def test_clone_chdir(self): | |
297 # Grand-child with relative directory. | |
298 lines = [ | |
299 (self._ROOT_PID, | |
300 'execve("../out/unittests", ' | |
301 '["../out/unittests"...], [/* 44 vars */]) = 0'), | |
302 (self._ROOT_PID, | |
303 'clone(child_stack=0, flags=CLONE_CHILD_CLEARTID' | |
304 '|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f5350f829d0) = %d' % | |
305 self._CHILD_PID), | |
306 (self._CHILD_PID, | |
307 'chdir("/home_foo_bar_user/path1") = 0'), | |
308 (self._CHILD_PID, | |
309 'clone(child_stack=0, flags=CLONE_CHILD_CLEARTID' | |
310 '|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f5350f829d0) = %d' % | |
311 self._GRAND_CHILD_PID), | |
312 (self._GRAND_CHILD_PID, | |
313 'execve("../out/unittests", ' | |
314 '["../out/unittests"...], [/* 44 vars */]) = 0'), | |
315 (self._ROOT_PID, 'chdir("/home_foo_bar_user/path2") = 0'), | |
316 (self._GRAND_CHILD_PID, | |
317 'open("random.txt", O_RDONLY) = 76'), | |
318 ] | |
319 expected = { | |
320 'root': { | |
321 'children': [ | |
322 { | |
323 'children': [ | |
324 { | |
325 'children': [], | |
326 'command': ['../out/unittests'], | |
327 'executable': '/home_foo_bar_user/out/unittests', | |
328 'files': [ | |
329 { | |
330 'path': u'/home_foo_bar_user/out/unittests', | |
331 'size': -1, | |
332 }, | |
333 { | |
334 'path': u'/home_foo_bar_user/path1/random.txt', | |
335 'size': -1, | |
336 }, | |
337 ], | |
338 'initial_cwd': u'/home_foo_bar_user/path1', | |
339 'pid': self._GRAND_CHILD_PID, | |
340 }, | |
341 ], | |
342 # clone does not carry over the command and executable so it is | |
343 # clear if an execve() call was done or not. | |
344 'command': None, | |
345 'executable': None, | |
346 # This is important, since no execve call was done, it didn't | |
347 # touch the executable file. | |
348 'files': [], | |
349 'initial_cwd': unicode(ROOT_DIR), | |
350 'pid': self._CHILD_PID, | |
351 }, | |
352 ], | |
353 'command': ['../out/unittests'], | |
354 'executable': join_norm(ROOT_DIR, '../out/unittests'), | |
355 'files': [ | |
356 { | |
357 'path': join_norm(ROOT_DIR, '../out/unittests'), | |
358 'size': -1, | |
359 }, | |
360 ], | |
361 'initial_cwd': unicode(ROOT_DIR), | |
362 'pid': self._ROOT_PID, | |
363 }, | |
364 } | |
365 self.assertEquals(expected, self._load_context(lines, ROOT_DIR)) | |
366 | |
367 def test_open(self): | |
368 lines = [ | |
369 (self._ROOT_PID, | |
370 'execve("../out/unittests", ' | |
371 '["../out/unittests"...], [/* 44 vars */]) = 0'), | |
372 (self._ROOT_PID, | |
373 'open("out/unittests.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 8'), | |
374 ] | |
375 files = [ | |
376 { | |
377 'path': u'/home/foo_bar_user/out/unittests', | |
378 'size': -1, | |
379 }, | |
380 { | |
381 'path': u'/home/foo_bar_user/src/out/unittests.log', | |
382 'size': -1, | |
383 }, | |
384 ] | |
385 self._test_lines(lines, '/home/foo_bar_user/src', files) | |
386 | |
387 def test_open_resumed(self): | |
388 lines = [ | |
389 (self._ROOT_PID, | |
390 'execve("../out/unittests", ' | |
391 '["../out/unittests"...], [/* 44 vars */]) = 0'), | |
392 (self._ROOT_PID, | |
393 'open("out/unittests.log", O_WRONLY|O_CREAT|O_APPEND ' | |
394 '<unfinished ...>'), | |
395 (self._ROOT_PID, '<... open resumed> ) = 3'), | |
396 ] | |
397 files = [ | |
398 { | |
399 'path': u'/home/foo_bar_user/out/unittests', | |
400 'size': -1, | |
401 }, | |
402 { | |
403 'path': u'/home/foo_bar_user/src/out/unittests.log', | |
404 'size': -1, | |
405 }, | |
406 ] | |
407 self._test_lines(lines, '/home/foo_bar_user/src', files) | |
408 | |
409 def test_rmdir(self): | |
410 lines = [ | |
411 (self._ROOT_PID, 'rmdir("directory/to/delete") = 0'), | |
412 ] | |
413 self._test_lines(lines, '/home/foo_bar_user/src', []) | |
414 | |
415 def test_setxattr(self): | |
416 lines = [ | |
417 (self._ROOT_PID, | |
418 'setxattr("file.exe", "attribute", "value", 0, 0) = 0'), | |
419 ] | |
420 self._test_lines(lines, '/home/foo_bar_user/src', []) | |
421 | |
422 def test_sig_unexpected(self): | |
423 lines = [ | |
424 (self._ROOT_PID, 'exit_group(0) = ?'), | |
425 ] | |
426 self._test_lines(lines, '/home/foo_bar_user/src', []) | |
427 | |
428 def test_stray(self): | |
429 lines = [ | |
430 (self._ROOT_PID, | |
431 'execve("../out/unittests", ' | |
432 '["../out/unittests"...], [/* 44 vars */]) = 0'), | |
433 (self._ROOT_PID, | |
434 ') = ? <unavailable>'), | |
435 ] | |
436 files = [ | |
437 { | |
438 'path': u'/home/foo_bar_user/out/unittests', | |
439 'size': -1, | |
440 }, | |
441 ] | |
442 self._test_lines(lines, '/home/foo_bar_user/src', files) | |
443 | |
444 | |
445 if __name__ == '__main__': | |
446 VERBOSE = '-v' in sys.argv | |
447 logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR) | |
448 unittest.main() | |
OLD | NEW |