diff options
-rwxr-xr-x | tools/isolate/data/trace_inputs/touch_only.py | 30 | ||||
-rwxr-xr-x | tools/isolate/trace_inputs.py | 78 | ||||
-rwxr-xr-x | tools/isolate/trace_inputs_smoke_test.py | 35 | ||||
-rwxr-xr-x | tools/isolate/trace_inputs_test.py | 4 |
4 files changed, 114 insertions, 33 deletions
diff --git a/tools/isolate/data/trace_inputs/touch_only.py b/tools/isolate/data/trace_inputs/touch_only.py new file mode 100755 index 0000000..58eba2c --- /dev/null +++ b/tools/isolate/data/trace_inputs/touch_only.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Uses different APIs to touch a file.""" + +import os +import sys + + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def main(): + print 'Only look if a file exists but do not open it.' + assert len(sys.argv) == 2 + path = os.path.join(BASE_DIR, 'test_file.txt') + command = sys.argv[1] + if command == 'access': + return not os.access(path, os.R_OK) + elif command == 'isfile': + return not os.path.isfile(path) + elif command == 'stat': + return not os.stat(path).st_size + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/isolate/trace_inputs.py b/tools/isolate/trace_inputs.py index e04d94a..6878d49 100755 --- a/tools/isolate/trace_inputs.py +++ b/tools/isolate/trace_inputs.py @@ -569,14 +569,18 @@ class Results(object): class _TouchedObject(object): """Something, a file or a directory, that was accessed.""" - def __init__(self, root, path, tainted): - logging.debug('%s(%s, %s)' % (self.__class__.__name__, root, path)) + def __init__(self, root, path, tainted, size, nb_files): + logging.debug( + '%s(%s, %s, %s, %s, %s)' % + (self.__class__.__name__, root, path, tainted, size, nb_files)) self.root = root self.path = path self.tainted = tainted + self.nb_files = nb_files + # Can be used as a cache or a default value, depending on context. + self._size = size # These are cache only. self._real_path = None - self._size = None # Check internal consistency. assert path, path @@ -614,7 +618,10 @@ class Results(object): return self._size def flatten(self): - """Returns a dict representing this object.""" + """Returns a dict representing this object. + + A 'size' of 0 means the file was only touched and not read. + """ return { 'path': self.path, 'size': self.size, @@ -650,25 +657,25 @@ class Results(object): raise NotImplementedError(self.__class__.__name__) class File(_TouchedObject): - """A file that was accessed. + """A file that was accessed. May not be present anymore. If tainted is true, it means it is not a real path anymore as a variable replacement occured. + + If touched_only is True, this means the file was probed for existence, and + it is existent, but was never _opened_. If touched_only is True, the file + must have existed. """ - def __init__(self, root, path, tainted): - """Represents a file accessed. May not be present anymore.""" - super(Results.File, self).__init__(root, path, tainted) - # For compatibility with Directory object interface. - # Shouldn't be used normally, only exists to simplify algorithms. - self.nb_files = 1 + def __init__(self, root, path, tainted, size): + super(Results.File, self).__init__(root, path, tainted, size, 1) def _clone(self, new_root, new_path, tainted): """Clones itself keeping meta-data.""" - out = self.__class__(new_root, new_path, tainted) - # Keep the cache for performance reason. It is also important when the - # file becomes tainted (with a variable instead of the real path) since - # self.path is not an on-disk path anymore so out._size cannot be updated. - out._size = self.size + # Keep the self.size and self._real_path caches for performance reason. It + # is also important when the file becomes tainted (with a variable instead + # of the real path) since self.path is not an on-disk path anymore so + # out._size cannot be updated. + out = self.__class__(new_root, new_path, tainted, self.size) out._real_path = self._real_path return out @@ -677,12 +684,12 @@ class Results(object): def __init__(self, root, path, tainted, size, nb_files): """path='.' is a valid value and must be handled appropriately.""" assert not path.endswith(os.path.sep), path - super(Results.Directory, self).__init__(root, path + os.path.sep, tainted) - self.nb_files = nb_files + super(Results.Directory, self).__init__( + root, path + os.path.sep, tainted, size, nb_files) # In that case, it's not a cache, it's an actual value that is never - # modified. + # modified and represents the total size of the files contained in this + # directory. assert size - self._size = size def flatten(self): out = super(Results.Directory, self).flatten() @@ -705,20 +712,18 @@ class Results(object): Contains references to the files accessed by this process and its children. """ - def __init__( - self, pid, files, executable, command, initial_cwd, children): + def __init__(self, pid, files, executable, command, initial_cwd, children): logging.debug('Process(%s, %d, ...)' % (pid, len(files))) self.pid = pid - self.files = sorted( - (Results.File(None, f, False) for f in files), key=lambda x: x.path) + self.files = sorted(files, key=lambda x: x.path) self.children = children self.executable = executable self.command = command self.initial_cwd = initial_cwd # Check internal consistency. - assert len(set(f.path for f in self.files)) == len(self.files), [ - f.path for f in self.files] + assert len(set(f.path for f in self.files)) == len(self.files), sorted( + f.path for f in self.files) assert isinstance(self.children, list) assert isinstance(self.files, list) @@ -741,20 +746,18 @@ class Results(object): def strip_root(self, root): assert isabs(root) and root.endswith(os.path.sep), root + # Loads the files after since they are constructed as objects. out = self.__class__( self.pid, - [], + filter(None, (f.strip_root(root) for f in self.files)), self.executable, self.command, self.initial_cwd, [c.strip_root(root) for c in self.children]) - # Override the files property. - out.files = filter(None, (f.strip_root(root) for f in self.files)) logging.debug( 'strip_root(%s) %d -> %d' % (root, len(self.files), len(out.files))) return out - def __init__(self, process): self.process = process # Cache. @@ -815,6 +818,7 @@ class ApiBase(object): self.initial_cwd = initial_cwd self.cwd = None self.files = set() + self.only_touched = set() self.executable = None self.command = None @@ -843,10 +847,22 @@ class ApiBase(object): return x return get_native_path_case(x) + # Filters out directories. Some may have passed through. + files = set(map(render_to_string_and_fix_case, self.files)) + only_touched = set( + map(render_to_string_and_fix_case, self.only_touched)) + only_touched -= files + files = [ - f for f in set(map(render_to_string_and_fix_case, self.files)) + Results.File(None, f, False, None) for f in files if not os.path.isdir(f) ] + # Using 0 as size means the file's content is ignored since the file was + # never opened for I/O. + files.extend( + Results.File(None, f, False, 0) for f in only_touched + if not os.path.isdir(f) + ) return Results.Process( self.pid, files, diff --git a/tools/isolate/trace_inputs_smoke_test.py b/tools/isolate/trace_inputs_smoke_test.py index dde74d4..c4fdca7 100755 --- a/tools/isolate/trace_inputs_smoke_test.py +++ b/tools/isolate/trace_inputs_smoke_test.py @@ -559,6 +559,41 @@ class TraceInputsImport(TraceInputsBase): self.assertTrue(actual['root'].pop('pid')) self.assertEquals(expected, actual) + def _touch_expected(self, command): + # Look for file that were touched but not opened, using different APIs. + results = self._execute_trace( + [sys.executable, os.path.join('trace_inputs', 'touch_only.py'), command]) + expected = { + 'root': { + 'children': [], + 'command': [ + self.executable, + os.path.join('trace_inputs', 'touch_only.py'), + command, + ], + 'executable': self.real_executable, + 'files': [ + { + 'path': os.path.join(u'data', 'trace_inputs', 'touch_only.py'), + 'size': self._size('data', 'trace_inputs', 'touch_only.py'), + }, + ], + 'initial_cwd': self.initial_cwd, + }, + } + actual = results.flatten() + self.assertTrue(actual['root'].pop('pid')) + self.assertEquals(expected, actual) + + def test_trace_touch_only_access(self): + self._touch_expected('access') + + def test_trace_touch_only_isfile(self): + self._touch_expected('isfile') + + def test_trace_touch_only_stat(self): + self._touch_expected('stat') + if __name__ == '__main__': VERBOSE = '-v' in sys.argv diff --git a/tools/isolate/trace_inputs_test.py b/tools/isolate/trace_inputs_test.py index 3301b27..d9c8c15 100755 --- a/tools/isolate/trace_inputs_test.py +++ b/tools/isolate/trace_inputs_test.py @@ -51,14 +51,14 @@ class TraceInputs(unittest.TestCase): def test_variable_abs(self): - value = trace_inputs.Results.File(None, '/foo/bar', False) + value = trace_inputs.Results.File(None, '/foo/bar', False, False) actual = value.replace_variables({'$FOO': '/foo'}) self.assertEquals('$FOO/bar', actual.path) self.assertEquals('$FOO/bar', actual.full_path) self.assertEquals(True, actual.tainted) def test_variable_rel(self): - value = trace_inputs.Results.File('/usr', 'foo/bar', False) + value = trace_inputs.Results.File('/usr', 'foo/bar', False, False) actual = value.replace_variables({'$FOO': 'foo'}) self.assertEquals('$FOO/bar', actual.path) self.assertEquals(os.path.join('/usr', '$FOO/bar'), actual.full_path) |