Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: tools/isolate/tests/trace_inputs_smoke_test.py

Issue 11045023: Move src/tools/isolate to src/tools/swarm_client as a DEPS. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Use r159961 Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 json
7 import logging
8 import os
9 import shutil
10 import subprocess
11 import sys
12 import tempfile
13 import unittest
14
15 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16 sys.path.insert(0, ROOT_DIR)
17
18 import run_test_cases
19
20 FILENAME = os.path.basename(__file__)
21 REL_DATA = os.path.join(u'tests', 'trace_inputs')
22 VERBOSE = False
23
24
25 class CalledProcessError(subprocess.CalledProcessError):
26 """Makes 2.6 version act like 2.7"""
27 def __init__(self, returncode, cmd, output, cwd):
28 super(CalledProcessError, self).__init__(returncode, cmd)
29 self.output = output
30 self.cwd = cwd
31
32 def __str__(self):
33 return super(CalledProcessError, self).__str__() + (
34 '\n'
35 'cwd=%s\n%s') % (self.cwd, self.output)
36
37
38 class TraceInputsBase(unittest.TestCase):
39 def setUp(self):
40 self.tempdir = tempfile.mkdtemp(prefix='trace_smoke_test')
41 self.log = os.path.join(self.tempdir, 'log')
42 self.trace_inputs_path = os.path.join(ROOT_DIR, 'trace_inputs.py')
43
44 # Wraps up all the differences between OSes here.
45 # - Windows doesn't track initial_cwd.
46 # - OSX replaces /usr/bin/python with /usr/bin/python2.7.
47 self.cwd = os.path.join(ROOT_DIR, u'tests')
48 self.initial_cwd = unicode(self.cwd)
49 self.expected_cwd = unicode(ROOT_DIR)
50 if sys.platform == 'win32':
51 # Not supported on Windows.
52 self.initial_cwd = None
53 self.expected_cwd = None
54
55 # There's 3 kinds of references to python, self.executable,
56 # self.real_executable and self.naked_executable. It depends how python was
57 # started.
58 self.executable = sys.executable
59 if sys.platform == 'darwin':
60 # /usr/bin/python is a thunk executable that decides which version of
61 # python gets executed.
62 suffix = '.'.join(map(str, sys.version_info[0:2]))
63 if os.access(self.executable + suffix, os.X_OK):
64 # So it'll look like /usr/bin/python2.7
65 self.executable += suffix
66
67 import trace_inputs
68 self.real_executable = trace_inputs.get_native_path_case(
69 unicode(self.executable))
70 trace_inputs = None
71
72 # self.naked_executable will only be naked on Windows.
73 self.naked_executable = unicode(sys.executable)
74 if sys.platform == 'win32':
75 self.naked_executable = os.path.basename(sys.executable)
76
77 def tearDown(self):
78 if VERBOSE:
79 print 'Leaking: %s' % self.tempdir
80 else:
81 shutil.rmtree(self.tempdir)
82
83 @staticmethod
84 def get_child_command(from_data):
85 """Returns command to run the child1.py."""
86 cmd = [sys.executable]
87 if from_data:
88 # When the gyp argument is specified, the command is started from --cwd
89 # directory. In this case, 'tests'.
90 cmd.extend([os.path.join('trace_inputs', 'child1.py'), '--child-gyp'])
91 else:
92 # When the gyp argument is not specified, the command is started from
93 # --root-dir directory.
94 cmd.extend([os.path.join(REL_DATA, 'child1.py'), '--child'])
95 return cmd
96
97 @staticmethod
98 def _size(*args):
99 return os.stat(os.path.join(ROOT_DIR, *args)).st_size
100
101
102 class TraceInputs(TraceInputsBase):
103 def _execute(self, mode, command, cwd):
104 cmd = [
105 sys.executable,
106 self.trace_inputs_path,
107 mode,
108 '--log', self.log,
109 ]
110 if VERBOSE:
111 cmd.extend(['-v'] * 3)
112 cmd.extend(command)
113 logging.info('Command: %s' % ' '.join(cmd))
114 p = subprocess.Popen(
115 cmd,
116 stdout=subprocess.PIPE,
117 stderr=subprocess.PIPE,
118 cwd=cwd,
119 universal_newlines=True)
120 out, err = p.communicate()
121 if VERBOSE:
122 print err
123 if p.returncode:
124 raise CalledProcessError(p.returncode, cmd, out + err, cwd)
125 return out or ''
126
127 def _trace(self, from_data):
128 if from_data:
129 cwd = os.path.join(ROOT_DIR, 'tests')
130 else:
131 cwd = ROOT_DIR
132 return self._execute('trace', self.get_child_command(from_data), cwd=cwd)
133
134 def test_trace(self):
135 expected = '\n'.join((
136 'Total: 7',
137 'Non existent: 0',
138 'Interesting: 7 reduced to 6',
139 ' tests/trace_inputs/child1.py'.replace('/', os.path.sep),
140 ' tests/trace_inputs/child2.py'.replace('/', os.path.sep),
141 ' tests/trace_inputs/files1/'.replace('/', os.path.sep),
142 ' tests/trace_inputs/test_file.txt'.replace('/', os.path.sep),
143 (' tests/%s' % FILENAME).replace('/', os.path.sep),
144 ' trace_inputs.py',
145 )) + '\n'
146 trace_expected = '\n'.join((
147 'child from %s' % ROOT_DIR,
148 'child2',
149 )) + '\n'
150 trace_actual = self._trace(False)
151 actual = self._execute(
152 'read',
153 [
154 '--root-dir', ROOT_DIR,
155 '--blacklist', '.+\\.pyc',
156 '--blacklist', '.*\\.svn',
157 '--blacklist', '.*do_not_care\\.txt',
158 ],
159 cwd=ROOT_DIR)
160 self.assertEquals(expected, actual)
161 self.assertEquals(trace_expected, trace_actual)
162
163 def test_trace_json(self):
164 expected = {
165 u'root': {
166 u'children': [
167 {
168 u'children': [],
169 u'command': [u'python', u'child2.py'],
170 u'executable': self.naked_executable,
171 u'files': [
172 {
173 u'path': os.path.join(REL_DATA, 'child2.py'),
174 u'size': self._size(REL_DATA, 'child2.py'),
175 },
176 {
177 u'path': os.path.join(REL_DATA, 'files1', 'bar'),
178 u'size': self._size(REL_DATA, 'files1', 'bar'),
179 },
180 {
181 u'path': os.path.join(REL_DATA, 'files1', 'foo'),
182 u'size': self._size(REL_DATA, 'files1', 'foo'),
183 },
184 {
185 u'path': os.path.join(REL_DATA, 'test_file.txt'),
186 u'size': self._size(REL_DATA, 'test_file.txt'),
187 },
188 ],
189 u'initial_cwd': self.initial_cwd,
190 #u'pid': 123,
191 },
192 ],
193 u'command': [
194 unicode(self.executable),
195 os.path.join(u'trace_inputs', 'child1.py'),
196 u'--child-gyp',
197 ],
198 u'executable': self.real_executable,
199 u'files': [
200 {
201 u'path': os.path.join(REL_DATA, 'child1.py'),
202 u'size': self._size(REL_DATA, 'child1.py'),
203 },
204 {
205 u'path': os.path.join(u'tests', u'trace_inputs_smoke_test.py'),
206 u'size': self._size('tests', 'trace_inputs_smoke_test.py'),
207 },
208 {
209 u'path': u'trace_inputs.py',
210 u'size': self._size('trace_inputs.py'),
211 },
212 ],
213 u'initial_cwd': self.initial_cwd,
214 #u'pid': 123,
215 },
216 }
217 trace_expected = '\n'.join((
218 'child_gyp from %s' % os.path.join(ROOT_DIR, 'tests'),
219 'child2',
220 )) + '\n'
221 trace_actual = self._trace(True)
222 actual_text = self._execute(
223 'read',
224 [
225 '--root-dir', ROOT_DIR,
226 '--blacklist', '.+\\.pyc',
227 '--blacklist', '.*\\.svn',
228 '--blacklist', '.*do_not_care\\.txt',
229 '--json',
230 ],
231 cwd=ROOT_DIR)
232 actual_json = json.loads(actual_text)
233 self.assertEquals(list, actual_json.__class__)
234 self.assertEquals(1, len(actual_json))
235 actual_json = actual_json[0]
236 # Removes the pids.
237 self.assertTrue(actual_json['root'].pop('pid'))
238 self.assertTrue(actual_json['root']['children'][0].pop('pid'))
239 self.assertEquals(expected, actual_json)
240 self.assertEquals(trace_expected, trace_actual)
241
242
243 class TraceInputsImport(TraceInputsBase):
244 def setUp(self):
245 super(TraceInputsImport, self).setUp()
246 import trace_inputs
247 self.trace_inputs = trace_inputs
248
249 def tearDown(self):
250 del self.trace_inputs
251 super(TraceInputsImport, self).tearDown()
252
253 # Similar to TraceInputs test fixture except that it calls the function
254 # directly, so the Results instance can be inspected.
255 # Roughly, make sure the API is stable.
256 def _execute_trace(self, command):
257 # Similar to what trace_test_cases.py does.
258 api = self.trace_inputs.get_api()
259 _, _ = self.trace_inputs.trace(
260 self.log, command, self.cwd, api, True)
261 # TODO(maruel): Check
262 #self.assertEquals(0, returncode)
263 #self.assertEquals('', output)
264 def blacklist(f):
265 return f.endswith(('.pyc', '.svn', 'do_not_care.txt'))
266 return self.trace_inputs.load_trace(self.log, ROOT_DIR, api, blacklist)
267
268 def _gen_dict_wrong_path(self):
269 """Returns the expected flattened Results when child1.py is called with the
270 wrong relative path.
271 """
272 return {
273 'root': {
274 'children': [],
275 'command': [
276 self.executable,
277 os.path.join(REL_DATA, 'child1.py'),
278 '--child',
279 ],
280 'executable': self.real_executable,
281 'files': [],
282 'initial_cwd': self.initial_cwd,
283 },
284 }
285
286 def _gen_dict_full(self):
287 """Returns the expected flattened Results when child1.py is called with
288 --child.
289 """
290 return {
291 'root': {
292 'children': [
293 {
294 'children': [],
295 'command': ['python', 'child2.py'],
296 'executable': self.naked_executable,
297 'files': [
298 {
299 'path': os.path.join(REL_DATA, 'child2.py'),
300 'size': self._size(REL_DATA, 'child2.py'),
301 },
302 {
303 'path': os.path.join(REL_DATA, 'files1', 'bar'),
304 'size': self._size(REL_DATA, 'files1', 'bar'),
305 },
306 {
307 'path': os.path.join(REL_DATA, 'files1', 'foo'),
308 'size': self._size(REL_DATA, 'files1', 'foo'),
309 },
310 {
311 'path': os.path.join(REL_DATA, 'test_file.txt'),
312 'size': self._size(REL_DATA, 'test_file.txt'),
313 },
314 ],
315 'initial_cwd': self.expected_cwd,
316 },
317 ],
318 'command': [
319 self.executable,
320 os.path.join(REL_DATA, 'child1.py'),
321 '--child',
322 ],
323 'executable': self.real_executable,
324 'files': [
325 {
326 'path': os.path.join(REL_DATA, 'child1.py'),
327 'size': self._size(REL_DATA, 'child1.py'),
328 },
329 {
330 u'path': os.path.join(u'tests', u'trace_inputs_smoke_test.py'),
331 'size': self._size('tests', 'trace_inputs_smoke_test.py'),
332 },
333 {
334 'path': u'trace_inputs.py',
335 'size': self._size('trace_inputs.py'),
336 },
337 ],
338 'initial_cwd': self.expected_cwd,
339 },
340 }
341
342 def _gen_dict_full_gyp(self):
343 """Returns the expected flattened results when child1.py is called with
344 --child-gyp.
345 """
346 return {
347 'root': {
348 'children': [
349 {
350 'children': [],
351 'command': ['python', 'child2.py'],
352 'executable': self.naked_executable,
353 'files': [
354 {
355 'path': os.path.join(REL_DATA, 'child2.py'),
356 'size': self._size(REL_DATA, 'child2.py'),
357 },
358 {
359 'path': os.path.join(REL_DATA, 'files1', 'bar'),
360 'size': self._size(REL_DATA, 'files1', 'bar'),
361 },
362 {
363 'path': os.path.join(REL_DATA, 'files1', 'foo'),
364 'size': self._size(REL_DATA, 'files1', 'foo'),
365 },
366 {
367 'path': os.path.join(REL_DATA, 'test_file.txt'),
368 'size': self._size(REL_DATA, 'test_file.txt'),
369 },
370 ],
371 'initial_cwd': self.initial_cwd,
372 },
373 ],
374 'command': [
375 self.executable,
376 os.path.join('trace_inputs', 'child1.py'),
377 '--child-gyp',
378 ],
379 'executable': self.real_executable,
380 'files': [
381 {
382 'path': os.path.join(REL_DATA, 'child1.py'),
383 'size': self._size(REL_DATA, 'child1.py'),
384 },
385 {
386 'path': os.path.join(u'tests', u'trace_inputs_smoke_test.py'),
387 'size': self._size('tests', 'trace_inputs_smoke_test.py'),
388 },
389 {
390 'path': u'trace_inputs.py',
391 'size': self._size('trace_inputs.py'),
392 },
393 ],
394 'initial_cwd': self.initial_cwd,
395 },
396 }
397
398 def test_trace_wrong_path(self):
399 # Deliberately start the trace from the wrong path. Starts it from the
400 # directory 'tests' so 'tests/tests/trace_inputs/child1.py' is not
401 # accessible, so child2.py process is not started.
402 results = self._execute_trace(self.get_child_command(False))
403 expected = self._gen_dict_wrong_path()
404 actual = results.flatten()
405 self.assertTrue(actual['root'].pop('pid'))
406 self.assertEquals(expected, actual)
407
408 def test_trace(self):
409 expected = self._gen_dict_full_gyp()
410 results = self._execute_trace(self.get_child_command(True))
411 actual = results.flatten()
412 self.assertTrue(actual['root'].pop('pid'))
413 self.assertTrue(actual['root']['children'][0].pop('pid'))
414 self.assertEquals(expected, actual)
415 files = [
416 u'tests/trace_inputs/child1.py'.replace('/', os.path.sep),
417 u'tests/trace_inputs/child2.py'.replace('/', os.path.sep),
418 u'tests/trace_inputs/files1/'.replace('/', os.path.sep),
419 u'tests/trace_inputs/test_file.txt'.replace('/', os.path.sep),
420 u'tests/trace_inputs_smoke_test.py'.replace('/', os.path.sep),
421 u'trace_inputs.py',
422 ]
423 def blacklist(f):
424 return f.endswith(('.pyc', 'do_not_care.txt', '.git', '.svn'))
425 simplified = self.trace_inputs.extract_directories(
426 ROOT_DIR, results.files, blacklist)
427 self.assertEquals(files, [f.path for f in simplified])
428
429 def test_trace_multiple(self):
430 # Starts parallel threads and trace parallel child processes simultaneously.
431 # Some are started from 'tests' directory, others from this script's
432 # directory. One trace fails. Verify everything still goes one.
433 parallel = 8
434
435 def trace(tracer, cmd, cwd, tracename):
436 resultcode, output = tracer.trace(
437 cmd, cwd, tracename, True)
438 return (tracename, resultcode, output)
439
440 with run_test_cases.ThreadPool(parallel) as pool:
441 api = self.trace_inputs.get_api()
442 with api.get_tracer(self.log) as tracer:
443 pool.add_task(
444 trace, tracer, self.get_child_command(False), ROOT_DIR, 'trace1')
445 pool.add_task(
446 trace, tracer, self.get_child_command(True), self.cwd, 'trace2')
447 pool.add_task(
448 trace, tracer, self.get_child_command(False), ROOT_DIR, 'trace3')
449 pool.add_task(
450 trace, tracer, self.get_child_command(True), self.cwd, 'trace4')
451 # Have this one fail since it's started from the wrong directory.
452 pool.add_task(
453 trace, tracer, self.get_child_command(False), self.cwd, 'trace5')
454 pool.add_task(
455 trace, tracer, self.get_child_command(True), self.cwd, 'trace6')
456 pool.add_task(
457 trace, tracer, self.get_child_command(False), ROOT_DIR, 'trace7')
458 pool.add_task(
459 trace, tracer, self.get_child_command(True), self.cwd, 'trace8')
460 trace_results = pool.join()
461 def blacklist(f):
462 return f.endswith(('.pyc', 'do_not_care.txt', '.git', '.svn'))
463 actual_results = api.parse_log(self.log, blacklist)
464 self.assertEquals(8, len(trace_results))
465 self.assertEquals(8, len(actual_results))
466
467 # Convert to dict keyed on the trace name, simpler to verify.
468 trace_results = dict((i[0], i[1:]) for i in trace_results)
469 actual_results = dict((x.pop('trace'), x) for x in actual_results)
470 self.assertEquals(sorted(trace_results), sorted(actual_results))
471
472 # It'd be nice to start different kinds of processes.
473 expected_results = [
474 self._gen_dict_full(),
475 self._gen_dict_full_gyp(),
476 self._gen_dict_full(),
477 self._gen_dict_full_gyp(),
478 self._gen_dict_wrong_path(),
479 self._gen_dict_full_gyp(),
480 self._gen_dict_full(),
481 self._gen_dict_full_gyp(),
482 ]
483 self.assertEquals(len(expected_results), len(trace_results))
484
485 # See the comment above about the trace that fails because it's started from
486 # the wrong directory.
487 busted = 4
488 for index, key in enumerate(sorted(actual_results)):
489 self.assertEquals('trace%d' % (index + 1), key)
490 self.assertEquals(2, len(trace_results[key]))
491 # returncode
492 self.assertEquals(0 if index != busted else 2, trace_results[key][0])
493 # output
494 self.assertEquals(actual_results[key]['output'], trace_results[key][1])
495
496 self.assertEquals(['output', 'results'], sorted(actual_results[key]))
497 results = actual_results[key]['results']
498 results = results.strip_root(ROOT_DIR)
499 actual = results.flatten()
500 self.assertTrue(actual['root'].pop('pid'))
501 if index != busted:
502 self.assertTrue(actual['root']['children'][0].pop('pid'))
503 self.assertEquals(expected_results[index], actual)
504
505 if sys.platform != 'win32':
506 def test_trace_symlink(self):
507 expected = {
508 'root': {
509 'children': [],
510 'command': [
511 self.executable,
512 os.path.join('trace_inputs', 'symlink.py'),
513 ],
514 'executable': self.real_executable,
515 'files': [
516 {
517 'path': os.path.join(REL_DATA, 'files2', 'bar'),
518 'size': self._size(REL_DATA, 'files2', 'bar'),
519 },
520 {
521 'path': os.path.join(REL_DATA, 'files2', 'foo'),
522 'size': self._size(REL_DATA, 'files2', 'foo'),
523 },
524 {
525 'path': os.path.join(REL_DATA, 'symlink.py'),
526 'size': self._size(REL_DATA, 'symlink.py'),
527 },
528 ],
529 'initial_cwd': self.initial_cwd,
530 },
531 }
532 cmd = [sys.executable, os.path.join('trace_inputs', 'symlink.py')]
533 results = self._execute_trace(cmd)
534 actual = results.flatten()
535 self.assertTrue(actual['root'].pop('pid'))
536 self.assertEquals(expected, actual)
537 files = [
538 # In particular, the symlink is *not* resolved.
539 u'tests/trace_inputs/files2/'.replace('/', os.path.sep),
540 u'tests/trace_inputs/symlink.py'.replace('/', os.path.sep),
541 ]
542 def blacklist(f):
543 return f.endswith(('.pyc', '.svn', 'do_not_care.txt'))
544 simplified = self.trace_inputs.extract_directories(
545 ROOT_DIR, results.files, blacklist)
546 self.assertEquals(files, [f.path for f in simplified])
547
548 def test_trace_quoted(self):
549 results = self._execute_trace([sys.executable, '-c', 'print("hi")'])
550 expected = {
551 'root': {
552 'children': [],
553 'command': [
554 self.executable,
555 '-c',
556 'print("hi")',
557 ],
558 'executable': self.real_executable,
559 'files': [],
560 'initial_cwd': self.initial_cwd,
561 },
562 }
563 actual = results.flatten()
564 self.assertTrue(actual['root'].pop('pid'))
565 self.assertEquals(expected, actual)
566
567 def _touch_expected(self, command):
568 # Looks for file that were touched but not opened, using different apis.
569 results = self._execute_trace(
570 [sys.executable, os.path.join('trace_inputs', 'touch_only.py'), command])
571 expected = {
572 'root': {
573 'children': [],
574 'command': [
575 self.executable,
576 os.path.join('trace_inputs', 'touch_only.py'),
577 command,
578 ],
579 'executable': self.real_executable,
580 'files': [
581 {
582 'path': os.path.join(REL_DATA, 'test_file.txt'),
583 'size': 0,
584 },
585 {
586 'path': os.path.join(REL_DATA, 'touch_only.py'),
587 'size': self._size(REL_DATA, 'touch_only.py'),
588 },
589 ],
590 'initial_cwd': self.initial_cwd,
591 },
592 }
593 if sys.platform != 'linux2':
594 # TODO(maruel): Remove once properly implemented.
595 expected['root']['files'].pop(0)
596
597 actual = results.flatten()
598 self.assertTrue(actual['root'].pop('pid'))
599 self.assertEquals(expected, actual)
600
601 def test_trace_touch_only_access(self):
602 self._touch_expected('access')
603
604 def test_trace_touch_only_isfile(self):
605 self._touch_expected('isfile')
606
607 def test_trace_touch_only_stat(self):
608 self._touch_expected('stat')
609
610
611 if __name__ == '__main__':
612 VERBOSE = '-v' in sys.argv
613 logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR)
614 unittest.main()
OLDNEW
« no previous file with comments | « tools/isolate/tests/trace_inputs/touch_only.py ('k') | tools/isolate/tests/trace_inputs_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698