Index: tools/isolate/strace_inputs.py |
diff --git a/tools/isolate/strace_inputs.py b/tools/isolate/strace_inputs.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..cdb34f4aa35a2e08c2739ff66b13f19b82baf39d |
--- /dev/null |
+++ b/tools/isolate/strace_inputs.py |
@@ -0,0 +1,162 @@ |
+#!/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. |
+ |
+"""Runs strace on a test and processes the logs to extract the dependencies from |
+the source tree. |
+ |
+Automatically extracts directories where all the files are used to make the |
+dependencies list more compact. |
+""" |
+ |
+import os |
+import re |
+import subprocess |
+import sys |
+ |
+ |
+BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
+ROOT_DIR = os.path.dirname(os.path.dirname(BASE_DIR)) |
+ |
+IGNORED = ( |
+ '/dev', |
+ '/etc', |
+ '/home', |
+ '/lib', |
+ '/proc', |
+ '/sys', |
+ '/tmp', |
+ '/usr', |
+ '/var', |
+) |
+ |
+ |
+def gen_trace(cmd, cwd, logname, silent): |
+ """Runs strace on an executable.""" |
+ strace = ['strace', '-f', '-e', 'trace=open', '-o', logname] |
+ stdout = stderr = None |
+ if silent: |
+ stdout = subprocess.PIPE |
+ stderr = subprocess.PIPE |
+ |
+ cmd = [os.path.normpath(os.path.join(cwd, c)) for c in cmd] |
+ p = subprocess.Popen( |
+ strace + cmd, cwd=cwd, stdout=stdout, stderr=stderr) |
+ out, err = p.communicate() |
+ if p.returncode != 0: |
+ print 'Failure: %d' % p.returncode |
+ # pylint: disable=E1103 |
+ print ''.join(out.splitlines(True)[-100:]) |
+ print ''.join(err.splitlines(True)[-100:]) |
+ return p.returncode |
+ |
+ |
+def parse_log(filename, blacklist): |
+ """Processes a strace log and returns the files opened and the files that do |
+ not exist. |
+ |
+ Most of the time, files that do not exist are temporary test files that should |
+ be put in /tmp instead. See http://crbug.com/116251 |
+ |
+ TODO(maruel): Process chdir() calls so relative paths can be processed. |
+ """ |
+ files = set() |
+ non_existent = set() |
+ for line in open(filename): |
+ # 1=pid, 2=filepath, 3=mode, 4=result |
+ m = re.match(r'^(\d+)\s+open\("([^"]+)", ([^\)]+)\)\s+= (.+)$', line) |
+ if not m: |
+ continue |
+ if m.group(4).startswith('-1') or 'O_DIRECTORY' in m.group(3): |
+ # Not present or a directory. |
+ continue |
+ filepath = m.group(2) |
+ if blacklist(filepath): |
+ continue |
+ if not os.path.isfile(filepath): |
+ non_existent.add(filepath) |
+ else: |
+ files.add(filepath) |
+ return files, non_existent |
+ |
+ |
+def relevant_files(files, root): |
+ """Trims the list of files to keep the expected files and unexpected files. |
+ |
+ Unexpected files are files that are not based inside the |root| directory. |
+ """ |
+ expected = [] |
+ unexpected = [] |
+ for f in files: |
+ if f.startswith(root): |
+ expected.append(f[len(root):]) |
+ else: |
+ unexpected.append(f) |
+ return sorted(set(expected)), sorted(set(unexpected)) |
+ |
+ |
+def extract_directories(files, root): |
+ """Detects if all the files in a directory were loaded and if so, replace the |
+ individual files by the directory entry. |
+ """ |
+ directories = set(os.path.dirname(f) for f in files) |
+ files = set(files) |
+ for directory in sorted(directories, reverse=True): |
+ actual = set( |
+ os.path.join(directory, f) for f in |
+ os.listdir(os.path.join(root, directory)) |
+ if not f.endswith(('.svn', '.pyc')) |
+ ) |
+ if not (actual - files): |
+ files -= actual |
+ files.add(directory + '/') |
+ return sorted(files) |
+ |
+ |
+def strace_inputs(unittest, cmd): |
+ """Tries to load the logs if available. If not, strace the test.""" |
+ logname = os.path.join(BASE_DIR, os.path.basename(unittest)) |
+ if not os.path.isfile(logname): |
+ returncode = gen_trace(cmd, ROOT_DIR, logname, True) |
+ if returncode: |
+ return returncode |
+ |
+ def blacklist(f): |
+ """Strips ignored paths.""" |
+ return f.startswith(IGNORED) or f.endswith('.pyc') |
+ |
+ files, non_existent = parse_log(logname, blacklist) |
+ print('Total: %d' % len(files)) |
+ print('Non existent: %d' % len(non_existent)) |
+ for f in non_existent: |
+ print(' %s' % f) |
+ |
+ expected, unexpected = relevant_files(files, ROOT_DIR + '/') |
+ if unexpected: |
+ print('Unexpected: %d' % len(unexpected)) |
+ for f in unexpected: |
+ print(' %s' % f) |
+ |
+ simplified = extract_directories(expected, ROOT_DIR) |
+ print('Interesting: %d reduced to %d' % (len(expected), len(simplified))) |
+ for f in simplified: |
+ print(' %s' % f) |
+ |
+ return 0 |
+ |
+ |
+def main(): |
+ if len(sys.argv) < 3: |
+ print >> sys.stderr, ( |
+ 'Usage: strace_inputs.py [testname] [cmd line...]\n' |
+ '\n' |
+ 'Example:\n' |
+ ' ./strace_inputs.py base_unittests testing/xvfb.py out/Release ' |
+ 'out/Release/base_unittests') |
+ return 1 |
+ return strace_inputs(sys.argv[1], sys.argv[2:]) |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |