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

Side by Side Diff: tools/telemetry/third_party/coverage/tests/test_farm.py

Issue 1366913004: Add coverage Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 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
OLDNEW
(Empty)
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3
4 """Run tests in the farm sub-directory. Designed for nose."""
5
6 import difflib
7 import filecmp
8 import fnmatch
9 import glob
10 import os
11 import re
12 import shutil
13 import sys
14 import unittest
15
16 from nose.plugins.skip import SkipTest
17
18 from coverage.test_helpers import ModuleAwareMixin, SysPathAwareMixin, change_di r, saved_sys_path
19 from tests.helpers import run_command
20 from tests.backtest import execfile # pylint: disable=redefined-builtin
21
22 from coverage.debug import _TEST_NAME_FILE
23
24
25 def test_farm(clean_only=False):
26 """A test-generating function for nose to find and run."""
27 for fname in glob.glob("tests/farm/*/*.py"):
28 case = FarmTestCase(fname, clean_only)
29 yield (case,)
30
31
32 # "rU" was deprecated in 3.4
33 READ_MODE = "rU" if sys.version_info < (3, 4) else "r"
34
35
36 class FarmTestCase(ModuleAwareMixin, SysPathAwareMixin, unittest.TestCase):
37 """A test case from the farm tree.
38
39 Tests are short Python script files, often called run.py:
40
41 copy("src", "out")
42 run('''
43 coverage run white.py
44 coverage annotate white.py
45 ''', rundir="out")
46 compare("out", "gold", "*,cover")
47 clean("out")
48
49 Verbs (copy, run, compare, clean) are methods in this class. FarmTestCase
50 has options to allow various uses of the test cases (normal execution,
51 cleaning-only, or run and leave the results for debugging).
52
53 This class is a unittest.TestCase so that we can use behavior-modifying
54 mixins, but it's only useful as a nose test function. Yes, this is
55 confusing.
56
57 """
58
59 # We don't want test runners finding this and instantiating it themselves.
60 __test__ = False
61
62 def __init__(self, runpy, clean_only=False, dont_clean=False):
63 """Create a test case from a run.py file.
64
65 `clean_only` means that only the clean() action is executed.
66 `dont_clean` means that the clean() action is not executed.
67
68 """
69 super(FarmTestCase, self).__init__()
70
71 self.description = runpy
72 self.dir, self.runpy = os.path.split(runpy)
73 self.clean_only = clean_only
74 self.dont_clean = dont_clean
75 self.ok = True
76
77 def setUp(self):
78 """Test set up, run by nose before __call__."""
79 super(FarmTestCase, self).setUp()
80 # Modules should be importable from the current directory.
81 sys.path.insert(0, '')
82
83 def tearDown(self):
84 """Test tear down, run by nose after __call__."""
85 # Make sure the test is cleaned up, unless we never want to, or if the
86 # test failed.
87 if not self.dont_clean and self.ok: # pragma: part covered
88 self.clean_only = True
89 self()
90
91 super(FarmTestCase, self).tearDown()
92
93 # This object will be run by nose via the __call__ method, and nose
94 # doesn't do cleanups in that case. Do them now.
95 self.doCleanups()
96
97 def runTest(self):
98 """Here to make unittest.TestCase happy, but will never be invoked."""
99 raise Exception("runTest isn't used in this class!")
100
101 def __call__(self):
102 """Execute the test from the run.py file."""
103 if _TEST_NAME_FILE: # pragma: debugging
104 with open(_TEST_NAME_FILE, "w") as f:
105 f.write(self.description.replace("/", "_"))
106
107 # Prepare a dictionary of globals for the run.py files to use.
108 fns = """
109 copy run runfunc clean skip
110 compare contains contains_any doesnt_contain
111 """.split()
112 if self.clean_only:
113 glo = dict((fn, noop) for fn in fns)
114 glo['clean'] = clean
115 else:
116 glo = dict((fn, globals()[fn]) for fn in fns)
117 if self.dont_clean: # pragma: not covered
118 glo['clean'] = noop
119
120 with change_dir(self.dir):
121 try:
122 execfile(self.runpy, glo)
123 except Exception:
124 self.ok = False
125 raise
126
127 def run_fully(self): # pragma: not covered
128 """Run as a full test case, with setUp and tearDown."""
129 self.setUp()
130 try:
131 self()
132 finally:
133 self.tearDown()
134
135
136 # Functions usable inside farm run.py files
137
138 def noop(*args_unused, **kwargs_unused):
139 """A no-op function to stub out run, copy, etc, when only cleaning."""
140 pass
141
142
143 def copy(src, dst):
144 """Copy a directory."""
145 if os.path.exists(dst):
146 shutil.rmtree(dst)
147 shutil.copytree(src, dst)
148
149
150 def run(cmds, rundir="src", outfile=None):
151 """Run a list of commands.
152
153 `cmds` is a string, commands separated by newlines.
154 `rundir` is the directory in which to run the commands.
155 `outfile` is a file name to redirect stdout to.
156
157 """
158 with change_dir(rundir):
159 if outfile:
160 fout = open(outfile, "a+")
161 try:
162 for cmd in cmds.split("\n"):
163 cmd = cmd.strip()
164 if not cmd:
165 continue
166 retcode, output = run_command(cmd)
167 print(output.rstrip())
168 if outfile:
169 fout.write(output)
170 if retcode:
171 raise Exception("command exited abnormally")
172 finally:
173 if outfile:
174 fout.close()
175
176
177 def runfunc(fn, rundir="src", addtopath=None):
178 """Run a function.
179
180 `fn` is a callable.
181 `rundir` is the directory in which to run the function.
182
183 """
184 with change_dir(rundir):
185 with saved_sys_path():
186 if addtopath is not None:
187 sys.path.insert(0, addtopath)
188 fn()
189
190
191 def compare(
192 dir1, dir2, file_pattern=None, size_within=0,
193 left_extra=False, right_extra=False, scrubs=None
194 ):
195 """Compare files matching `file_pattern` in `dir1` and `dir2`.
196
197 `dir2` is interpreted as a prefix, with Python version numbers appended
198 to find the actual directory to compare with. "foo" will compare
199 against "foo_v241", "foo_v24", "foo_v2", or "foo", depending on which
200 directory is found first.
201
202 `size_within` is a percentage delta for the file sizes. If non-zero,
203 then the file contents are not compared (since they are expected to
204 often be different), but the file sizes must be within this amount.
205 For example, size_within=10 means that the two files' sizes must be
206 within 10 percent of each other to compare equal.
207
208 `left_extra` true means the left directory can have extra files in it
209 without triggering an assertion. `right_extra` means the right
210 directory can.
211
212 `scrubs` is a list of pairs, regexes to find and literal strings to
213 replace them with to scrub the files of unimportant differences.
214
215 An assertion will be raised if the directories fail one of their
216 matches.
217
218 """
219 # Search for a dir2 with a version suffix.
220 version_suff = ''.join(map(str, sys.version_info[:3]))
221 while version_suff:
222 trydir = dir2 + '_v' + version_suff
223 if os.path.exists(trydir):
224 dir2 = trydir
225 break
226 version_suff = version_suff[:-1]
227
228 assert os.path.exists(dir1), "Left directory missing: %s" % dir1
229 assert os.path.exists(dir2), "Right directory missing: %s" % dir2
230
231 dc = filecmp.dircmp(dir1, dir2)
232 diff_files = fnmatch_list(dc.diff_files, file_pattern)
233 left_only = fnmatch_list(dc.left_only, file_pattern)
234 right_only = fnmatch_list(dc.right_only, file_pattern)
235 show_diff = True
236
237 if size_within:
238 # The files were already compared, use the diff_files list as a
239 # guide for size comparison.
240 wrong_size = []
241 for f in diff_files:
242 with open(os.path.join(dir1, f), "rb") as fobj:
243 left = fobj.read()
244 with open(os.path.join(dir2, f), "rb") as fobj:
245 right = fobj.read()
246 size_l, size_r = len(left), len(right)
247 big, little = max(size_l, size_r), min(size_l, size_r)
248 if (big - little) / float(little) > size_within/100.0:
249 # print "%d %d" % (big, little)
250 # print "Left: ---\n%s\n-----\n%s" % (left, right)
251 wrong_size.append("%s (%s,%s)" % (f, size_l, size_r))
252 if wrong_size:
253 print("File sizes differ between %s and %s: %s" % (
254 dir1, dir2, ", ".join(wrong_size)
255 ))
256
257 # We'll show the diff iff the files differed enough in size.
258 show_diff = bool(wrong_size)
259
260 if show_diff:
261 # filecmp only compares in binary mode, but we want text mode. So
262 # look through the list of different files, and compare them
263 # ourselves.
264 text_diff = []
265 for f in diff_files:
266 with open(os.path.join(dir1, f), READ_MODE) as fobj:
267 left = fobj.read()
268 with open(os.path.join(dir2, f), READ_MODE) as fobj:
269 right = fobj.read()
270 if scrubs:
271 left = scrub(left, scrubs)
272 right = scrub(right, scrubs)
273 if left != right:
274 text_diff.append(f)
275 left = left.splitlines()
276 right = right.splitlines()
277 print("\n".join(difflib.Differ().compare(left, right)))
278 assert not text_diff, "Files differ: %s" % text_diff
279
280 if not left_extra:
281 assert not left_only, "Files in %s only: %s" % (dir1, left_only)
282 if not right_extra:
283 assert not right_only, "Files in %s only: %s" % (dir2, right_only)
284
285
286 def contains(filename, *strlist):
287 """Check that the file contains all of a list of strings.
288
289 An assert will be raised if one of the arguments in `strlist` is
290 missing in `filename`.
291
292 """
293 with open(filename, "r") as fobj:
294 text = fobj.read()
295 for s in strlist:
296 assert s in text, "Missing content in %s: %r" % (filename, s)
297
298
299 def contains_any(filename, *strlist):
300 """Check that the file contains at least one of a list of strings.
301
302 An assert will be raised if none of the arguments in `strlist` is in
303 `filename`.
304
305 """
306 with open(filename, "r") as fobj:
307 text = fobj.read()
308 for s in strlist:
309 if s in text:
310 return
311 assert False, "Missing content in %s: %r [1 of %d]" % (filename, strlist[0], len(strlist),)
312
313
314 def doesnt_contain(filename, *strlist):
315 """Check that the file contains none of a list of strings.
316
317 An assert will be raised if any of the strings in `strlist` appears in
318 `filename`.
319
320 """
321 with open(filename, "r") as fobj:
322 text = fobj.read()
323 for s in strlist:
324 assert s not in text, "Forbidden content in %s: %r" % (filename, s)
325
326
327 def clean(cleandir):
328 """Clean `cleandir` by removing it and all its children completely."""
329 # rmtree gives mysterious failures on Win7, so retry a "few" times.
330 # I've seen it take over 100 tries, so, 1000! This is probably the
331 # most unpleasant hack I've written in a long time...
332 tries = 1000
333 while tries: # pragma: part covered
334 if os.path.exists(cleandir):
335 try:
336 shutil.rmtree(cleandir)
337 except OSError: # pragma: not covered
338 if tries == 1:
339 raise
340 else:
341 tries -= 1
342 continue
343 break
344
345
346 def skip(msg=None):
347 """Skip the current test."""
348 raise SkipTest(msg)
349
350
351 # Helpers
352
353 def fnmatch_list(files, file_pattern):
354 """Filter the list of `files` to only those that match `file_pattern`.
355
356 If `file_pattern` is None, then return the entire list of files.
357
358 Returns a list of the filtered files.
359
360 """
361 if file_pattern:
362 files = [f for f in files if fnmatch.fnmatch(f, file_pattern)]
363 return files
364
365
366 def scrub(strdata, scrubs):
367 """Scrub uninteresting data from the payload in `strdata`.
368
369 `scrubs` is a list of (find, replace) pairs of regexes that are used on
370 `strdata`. A string is returned.
371
372 """
373 for rgx_find, rgx_replace in scrubs:
374 strdata = re.sub(rgx_find, re.escape(rgx_replace), strdata)
375 return strdata
376
377
378 def main(): # pragma: not covered
379 """Command-line access to test_farm.
380
381 Commands:
382
383 run testcase ... - Run specific test case(s)
384 out testcase ... - Run test cases, but don't clean up, leaving output.
385 clean - Clean all the output for all tests.
386
387 """
388 try:
389 op = sys.argv[1]
390 except IndexError:
391 op = 'help'
392
393 if op == 'run':
394 # Run the test for real.
395 for test_case in sys.argv[2:]:
396 case = FarmTestCase(test_case)
397 case.run_fully()
398 elif op == 'out':
399 # Run the test, but don't clean up, so we can examine the output.
400 for test_case in sys.argv[2:]:
401 case = FarmTestCase(test_case, dont_clean=True)
402 case.run_fully()
403 elif op == 'clean':
404 # Run all the tests, but just clean.
405 for test in test_farm(clean_only=True):
406 test[0].run_fully()
407 else:
408 print(main.__doc__)
409
410 # So that we can run just one farm run.py at a time.
411 if __name__ == '__main__':
412 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698