| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import cStringIO | 6 import cStringIO |
| 7 import hashlib | 7 import hashlib |
| 8 import json | 8 import json |
| 9 import logging | 9 import logging |
| 10 import os | 10 import os |
| 11 import re | 11 import re |
| 12 import shutil | 12 import shutil |
| 13 import subprocess | 13 import subprocess |
| 14 import sys | 14 import sys |
| 15 import tempfile | 15 import tempfile |
| 16 import unittest | 16 import unittest |
| 17 | 17 |
| 18 import isolate |
| 19 |
| 18 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) | 20 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 19 VERBOSE = False | 21 VERBOSE = False |
| 20 FILENAME = os.path.basename(__file__) | 22 |
| 21 | 23 |
| 22 | 24 |
| 23 class CalledProcessError(subprocess.CalledProcessError): | 25 class CalledProcessError(subprocess.CalledProcessError): |
| 24 """Makes 2.6 version act like 2.7""" | 26 """Makes 2.6 version act like 2.7""" |
| 25 def __init__(self, returncode, cmd, output, cwd): | 27 def __init__(self, returncode, cmd, output, cwd): |
| 26 super(CalledProcessError, self).__init__(returncode, cmd) | 28 super(CalledProcessError, self).__init__(returncode, cmd) |
| 27 self.output = output | 29 self.output = output |
| 28 self.cwd = cwd | 30 self.cwd = cwd |
| 29 | 31 |
| 30 def __str__(self): | 32 def __str__(self): |
| 31 return super(CalledProcessError, self).__str__() + ( | 33 return super(CalledProcessError, self).__str__() + ( |
| 32 '\n' | 34 '\n' |
| 33 'cwd=%s\n%s') % (self.cwd, self.output) | 35 'cwd=%s\n%s') % (self.cwd, self.output) |
| 34 | 36 |
| 35 | 37 |
| 36 class Isolate(unittest.TestCase): | 38 class Isolate(unittest.TestCase): |
| 37 def setUp(self): | 39 def setUp(self): |
| 38 # The reason is that FILENAME --ok is run in a temporary directory | |
| 39 # without access to isolate.py | |
| 40 import isolate | |
| 41 self.isolate = isolate | |
| 42 self.tempdir = tempfile.mkdtemp() | 40 self.tempdir = tempfile.mkdtemp() |
| 43 self.result = os.path.join(self.tempdir, 'result') | 41 self.result = os.path.join(self.tempdir, 'result') |
| 42 self.child = os.path.join('data', 'isolate', 'child.py') |
| 44 if VERBOSE: | 43 if VERBOSE: |
| 45 print | 44 print |
| 45 self.files = [ |
| 46 self.child, |
| 47 os.path.join('data', 'isolate', 'files1', 'test_file1.txt'), |
| 48 os.path.join('data', 'isolate', 'files1', 'test_file2.txt'), |
| 49 ] |
| 46 | 50 |
| 47 def tearDown(self): | 51 def tearDown(self): |
| 48 shutil.rmtree(self.tempdir) | 52 shutil.rmtree(self.tempdir) |
| 49 | 53 |
| 50 def _expected_tree(self, files): | 54 def _expected_tree(self, files): |
| 51 self.assertEquals(sorted(files), sorted(os.listdir(self.tempdir))) | 55 self.assertEquals(sorted(files), sorted(os.listdir(self.tempdir))) |
| 52 | 56 |
| 53 def _expected_result(self, with_hash, files, args, read_only): | 57 def _expected_result(self, with_hash, files, args, read_only): |
| 54 if sys.platform == 'win32': | 58 if sys.platform == 'win32': |
| 55 mode = lambda _: 420 | 59 mode = lambda _: 420 |
| 56 else: | 60 else: |
| 57 # 4 modes are supported, 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r) | 61 # 4 modes are supported, 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r) |
| 58 min_mode = 0444 | 62 min_mode = 0444 |
| 59 if not read_only: | 63 if not read_only: |
| 60 min_mode |= 0200 | 64 min_mode |= 0200 |
| 61 def mode(filename): | 65 def mode(filename): |
| 62 return (min_mode | 0111) if filename.endswith('.py') else min_mode | 66 return (min_mode | 0111) if filename.endswith('.py') else min_mode |
| 67 |
| 68 if not isinstance(files, dict): |
| 69 # Update files to dict. |
| 70 files = dict((unicode(f), {u'mode': mode(f)}) for f in files) |
| 71 # Add size and timestamp. |
| 72 files = files.copy() |
| 73 for k, v in files.iteritems(): |
| 74 if v: |
| 75 filestats = os.stat(k) |
| 76 v[u'size'] = filestats.st_size |
| 77 # Used the skip recalculating the hash. Use the most recent update |
| 78 # time. |
| 79 v[u'timestamp'] = int(round( |
| 80 max(filestats.st_mtime, filestats.st_ctime))) |
| 81 |
| 63 expected = { | 82 expected = { |
| 64 u'command': | 83 u'files': files, |
| 65 [unicode(sys.executable)] + | 84 u'relative_cwd': u'data/isolate', |
| 66 [unicode(x) for x in args], | 85 u'read_only': None, |
| 67 u'files': dict((unicode(f), {u'mode': mode(f)}) for f in files), | |
| 68 u'relative_cwd': u'.', | |
| 69 u'read_only': False, | |
| 70 } | 86 } |
| 87 if args: |
| 88 expected[u'command'] = [u'python'] + [unicode(x) for x in args] |
| 89 else: |
| 90 expected[u'command'] = [] |
| 71 if with_hash: | 91 if with_hash: |
| 72 for filename in expected[u'files']: | 92 for filename in expected[u'files']: |
| 73 # Calculate our hash. | 93 # Calculate our hash. |
| 74 h = hashlib.sha1() | 94 h = hashlib.sha1() |
| 75 h.update(open(os.path.join(ROOT_DIR, filename), 'rb').read()) | 95 h.update(open(os.path.join(ROOT_DIR, filename), 'rb').read()) |
| 76 expected[u'files'][filename][u'sha-1'] = h.hexdigest() | 96 expected[u'files'][filename][u'sha-1'] = unicode(h.hexdigest()) |
| 77 | 97 |
| 78 actual = json.load(open(self.result, 'rb')) | 98 actual = json.load(open(self.result, 'rb')) |
| 79 self.assertEquals(expected, actual) | 99 self.assertEquals(expected, actual) |
| 80 return expected | 100 return expected |
| 81 | 101 |
| 82 def _execute(self, args, need_output=False): | 102 def _execute(self, filename, args, need_output=False): |
| 83 cmd = [ | 103 cmd = [ |
| 84 sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), | 104 sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), |
| 85 '--root', ROOT_DIR, | 105 '--variable', 'DEPTH=%s' % ROOT_DIR, |
| 86 '--result', self.result, | 106 '--result', self.result, |
| 87 ] | 107 os.path.join(ROOT_DIR, 'data', 'isolate', filename), |
| 108 ] + args |
| 109 env = os.environ.copy() |
| 110 if 'ISOLATE_DEBUG' in env: |
| 111 del env['ISOLATE_DEBUG'] |
| 88 if need_output or not VERBOSE: | 112 if need_output or not VERBOSE: |
| 89 stdout = subprocess.PIPE | 113 stdout = subprocess.PIPE |
| 90 stderr = subprocess.STDOUT | 114 stderr = subprocess.STDOUT |
| 91 else: | 115 else: |
| 92 cmd.extend(['-v'] * 3) | 116 cmd.extend(['-v'] * 3) |
| 93 stdout = None | 117 stdout = None |
| 94 stderr = None | 118 stderr = None |
| 95 cwd = ROOT_DIR | 119 cwd = ROOT_DIR |
| 96 p = subprocess.Popen( | 120 p = subprocess.Popen( |
| 97 cmd + args, | 121 cmd + args, |
| 98 stdout=stdout, | 122 stdout=stdout, |
| 99 stderr=stderr, | 123 stderr=stderr, |
| 100 cwd=cwd, | 124 cwd=cwd, |
| 125 env=env, |
| 101 universal_newlines=True) | 126 universal_newlines=True) |
| 102 out = p.communicate()[0] | 127 out = p.communicate()[0] |
| 103 if p.returncode: | 128 if p.returncode: |
| 104 raise CalledProcessError(p.returncode, cmd, out, cwd) | 129 raise CalledProcessError(p.returncode, cmd, out, cwd) |
| 105 return out | 130 return out |
| 106 | 131 |
| 107 def test_help_modes(self): | 132 def test_help_modes(self): |
| 108 # Check coherency in the help and implemented modes. | 133 # Check coherency in the help and implemented modes. |
| 109 p = subprocess.Popen( | 134 p = subprocess.Popen( |
| 110 [sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), '--help'], | 135 [sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), '--help'], |
| 111 stdout=subprocess.PIPE, | 136 stdout=subprocess.PIPE, |
| 112 stderr=subprocess.STDOUT, | 137 stderr=subprocess.STDOUT, |
| 113 cwd=ROOT_DIR) | 138 cwd=ROOT_DIR) |
| 114 out = p.communicate()[0].splitlines() | 139 out = p.communicate()[0].splitlines() |
| 115 self.assertEquals(0, p.returncode) | 140 self.assertEquals(0, p.returncode) |
| 116 out = out[out.index('') + 1:] | 141 out = out[out.index('') + 1:] |
| 117 out = out[:out.index('')] | 142 out = out[:out.index('')] |
| 118 modes = [re.match(r'^ (\w+) .+', l) for l in out] | 143 modes = [re.match(r'^ (\w+) .+', l) for l in out] |
| 119 modes = tuple(m.group(1) for m in modes if m) | 144 modes = tuple(m.group(1) for m in modes if m) |
| 120 # Keep the list hard coded. | 145 # Keep the list hard coded. |
| 121 expected = ('check', 'hashtable', 'remap', 'run', 'trace') | 146 expected = ('check', 'hashtable', 'remap', 'run', 'trace') |
| 122 self.assertEquals(expected, modes) | 147 self.assertEquals(expected, modes) |
| 123 self.assertEquals(expected, modes) | 148 self.assertEquals(expected, modes) |
| 124 for mode in modes: | 149 for mode in modes: |
| 125 self.assertTrue(hasattr(self, 'test_%s' % mode), mode) | 150 self.assertTrue(hasattr(self, 'test_%s' % mode), mode) |
| 126 self._expected_tree([]) | 151 self._expected_tree([]) |
| 127 | 152 |
| 128 def test_check(self): | 153 def test_check(self): |
| 129 cmd = [ | 154 self._execute('fail.isolate', ['--mode', 'check']) |
| 130 '--mode', 'check', | |
| 131 FILENAME, | |
| 132 ] | |
| 133 self._execute(cmd) | |
| 134 self._expected_tree(['result']) | 155 self._expected_tree(['result']) |
| 135 self._expected_result( | 156 self._expected_result( |
| 136 False, | 157 False, dict((f, {}) for f in self.files), ['child.py', '--fail'], False) |
| 137 [FILENAME], | |
| 138 [os.path.join('.', FILENAME)], | |
| 139 False) | |
| 140 | 158 |
| 141 def test_check_non_existant(self): | 159 def test_check_no_run(self): |
| 142 cmd = [ | 160 self._execute('no_run.isolate', ['--mode', 'check']) |
| 143 '--mode', 'check', | 161 self._expected_tree(['result']) |
| 144 'NonExistentFile', | 162 self._expected_result( |
| 145 ] | 163 False, dict((f, {}) for f in self.files), None, False) |
| 164 |
| 165 def test_check_non_existent(self): |
| 146 try: | 166 try: |
| 147 self._execute(cmd) | 167 self._execute('non_existent.isolate', ['--mode', 'check']) |
| 148 self.fail() | 168 self.fail() |
| 149 except subprocess.CalledProcessError: | 169 except subprocess.CalledProcessError: |
| 150 pass | 170 pass |
| 151 self._expected_tree([]) | 171 self._expected_tree([]) |
| 152 | 172 |
| 153 def test_check_directory_no_slash(self): | 173 def test_check_directory_no_slash(self): |
| 154 cmd = [ | |
| 155 '--mode', 'check', | |
| 156 # Trailing slash missing. | |
| 157 os.path.join('data', 'isolate'), | |
| 158 ] | |
| 159 try: | 174 try: |
| 160 self._execute(cmd) | 175 self._execute('missing_trailing_slash.isolate', ['--mode', 'check']) |
| 161 self.fail() | 176 self.fail() |
| 162 except subprocess.CalledProcessError: | 177 except subprocess.CalledProcessError: |
| 163 pass | 178 pass |
| 164 self._expected_tree([]) | 179 self._expected_tree([]) |
| 165 | 180 |
| 166 def test_check_abs_path(self): | |
| 167 cmd = [ | |
| 168 '--mode', 'check', | |
| 169 FILENAME, | |
| 170 '--', | |
| 171 os.path.join(ROOT_DIR, FILENAME), | |
| 172 ] | |
| 173 self._execute(cmd) | |
| 174 self._expected_tree(['result']) | |
| 175 self._expected_result( | |
| 176 False, [FILENAME], [FILENAME], False) | |
| 177 | |
| 178 def test_hashtable(self): | 181 def test_hashtable(self): |
| 179 cmd = [ | 182 cmd = [ |
| 180 '--mode', 'hashtable', | 183 '--mode', 'hashtable', |
| 181 '--outdir', self.tempdir, | 184 '--outdir', self.tempdir, |
| 182 FILENAME, | |
| 183 os.path.join('data', 'isolate') + os.path.sep, | |
| 184 ] | 185 ] |
| 185 self._execute(cmd) | 186 self._execute('no_run.isolate', cmd) |
| 186 files = [ | 187 data = self._expected_result(True, self.files, None, False) |
| 187 FILENAME, | |
| 188 os.path.join('data', 'isolate', 'test_file1.txt'), | |
| 189 os.path.join('data', 'isolate', 'test_file2.txt'), | |
| 190 ] | |
| 191 data = self._expected_result( | |
| 192 True, files, [os.path.join('.', FILENAME)], False) | |
| 193 self._expected_tree( | 188 self._expected_tree( |
| 194 [f['sha-1'] for f in data['files'].itervalues()] + ['result']) | 189 [f['sha-1'] for f in data['files'].itervalues()] + ['result']) |
| 195 | 190 |
| 196 def test_remap(self): | 191 def test_remap(self): |
| 197 cmd = [ | 192 cmd = [ |
| 198 '--mode', 'remap', | 193 '--mode', 'remap', |
| 199 '--outdir', self.tempdir, | 194 '--outdir', self.tempdir, |
| 200 FILENAME, | |
| 201 ] | 195 ] |
| 202 self._execute(cmd) | 196 self._execute('no_run.isolate', cmd) |
| 203 self._expected_tree([FILENAME, 'result']) | 197 self._expected_tree(['data', 'result']) |
| 204 self._expected_result( | 198 self._expected_result( |
| 205 False, | 199 False, |
| 206 [FILENAME], | 200 self.files, |
| 207 [os.path.join('.', FILENAME)], | 201 None, |
| 208 False) | 202 False) |
| 209 | 203 |
| 210 def test_run(self): | 204 def test_run(self): |
| 211 cmd = [ | 205 self._execute('ok.isolate', ['--mode', 'run']) |
| 212 '--mode', 'run', | |
| 213 FILENAME, | |
| 214 '--', | |
| 215 sys.executable, FILENAME, '--ok', | |
| 216 ] | |
| 217 self._execute(cmd) | |
| 218 self._expected_tree(['result']) | 206 self._expected_tree(['result']) |
| 219 # cmd[0] is not generated from infiles[0] so it's not using a relative path. | 207 # cmd[0] is not generated from infiles[0] so it's not using a relative path. |
| 220 self._expected_result( | 208 self._expected_result( |
| 221 False, [FILENAME], [FILENAME, '--ok'], False) | 209 False, self.files, ['child.py', '--ok'], False) |
| 222 | 210 |
| 223 def test_run_fail(self): | 211 def test_run_fail(self): |
| 224 cmd = [ | |
| 225 '--mode', 'run', | |
| 226 FILENAME, | |
| 227 '--', | |
| 228 sys.executable, FILENAME, '--fail', | |
| 229 ] | |
| 230 try: | 212 try: |
| 231 self._execute(cmd) | 213 self._execute('fail.isolate', ['--mode', 'run']) |
| 232 self.fail() | 214 self.fail() |
| 233 except subprocess.CalledProcessError: | 215 except subprocess.CalledProcessError: |
| 234 pass | 216 pass |
| 235 self._expected_tree([]) | 217 self._expected_tree(['result']) |
| 236 | 218 |
| 237 def test_trace(self): | 219 def test_trace(self): |
| 238 cmd = [ | 220 out = self._execute('ok.isolate', ['--mode', 'trace'], True) |
| 239 '--mode', 'trace', | 221 self._expected_tree(['result', 'result.log']) |
| 240 FILENAME, | |
| 241 '--', | |
| 242 sys.executable, os.path.join(ROOT_DIR, FILENAME), '--ok', | |
| 243 ] | |
| 244 out = self._execute(cmd, True) | |
| 245 expected_tree = ['result', 'result.log'] | |
| 246 if sys.platform == 'win32': | |
| 247 expected_tree.append('result.log.etl') | |
| 248 self._expected_tree(expected_tree) | |
| 249 # The 'result.log' log is OS-specific so we can't read it but we can read | 222 # The 'result.log' log is OS-specific so we can't read it but we can read |
| 250 # the gyp result. | 223 # the gyp result. |
| 251 # cmd[0] is not generated from infiles[0] so it's not using a relative path. | 224 # cmd[0] is not generated from infiles[0] so it's not using a relative path. |
| 252 self._expected_result( | 225 self._expected_result( |
| 253 False, [FILENAME], [FILENAME, '--ok'], False) | 226 False, self.files, ['child.py', '--ok'], False) |
| 254 | 227 |
| 255 expected_value = { | 228 expected_value = { |
| 256 'conditions': [ | 229 'conditions': [ |
| 257 ['OS=="%s"' % self.isolate.trace_inputs.get_flavor(), { | 230 ['OS=="%s"' % isolate.trace_inputs.get_flavor(), { |
| 258 'variables': { | 231 'variables': { |
| 259 'isolate_files': [ | 232 isolate.trace_inputs.KEY_TRACKED: [ |
| 260 '<(DEPTH)/%s' % FILENAME, | 233 'child.py', |
| 234 ], |
| 235 isolate.trace_inputs.KEY_UNTRACKED: [ |
| 236 'files1/', |
| 261 ], | 237 ], |
| 262 }, | 238 }, |
| 263 }], | 239 }], |
| 264 ], | 240 ], |
| 265 } | 241 } |
| 266 expected_buffer = cStringIO.StringIO() | 242 expected_buffer = cStringIO.StringIO() |
| 267 self.isolate.trace_inputs.pretty_print(expected_value, expected_buffer) | 243 isolate.trace_inputs.pretty_print(expected_value, expected_buffer) |
| 268 self.assertEquals(expected_buffer.getvalue(), out) | 244 self.assertEquals(expected_buffer.getvalue(), out) |
| 269 | 245 |
| 270 | 246 |
| 271 def main(): | |
| 272 global VERBOSE | |
| 273 VERBOSE = '-v' in sys.argv | |
| 274 level = logging.DEBUG if VERBOSE else logging.ERROR | |
| 275 logging.basicConfig(level=level) | |
| 276 if len(sys.argv) == 1: | |
| 277 unittest.main() | |
| 278 if sys.argv[1] == '--ok': | |
| 279 return 0 | |
| 280 if sys.argv[1] == '--fail': | |
| 281 return 1 | |
| 282 | |
| 283 unittest.main() | |
| 284 | |
| 285 | 247 |
| 286 if __name__ == '__main__': | 248 if __name__ == '__main__': |
| 287 sys.exit(main()) | 249 VERBOSE = '-v' in sys.argv |
| 250 logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR) |
| 251 unittest.main() |
| OLD | NEW |