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 |