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 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() | |
OLD | NEW |