| 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 |