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

Side by Side Diff: tools/telemetry/third_party/coverage/tests/coveragetest.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 """Base test case class for coverage.py testing."""
5
6 import datetime
7 import glob
8 import os
9 import random
10 import re
11 import shlex
12 import shutil
13 import sys
14
15 import coverage
16 from coverage.backunittest import TestCase
17 from coverage.backward import StringIO, import_local_file, string_class
18 from coverage.cmdline import CoverageScript
19 from coverage.debug import _TEST_NAME_FILE, DebugControl
20 from coverage.test_helpers import (
21 EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin,
22 )
23
24 from nose.plugins.skip import SkipTest
25
26 from tests.helpers import run_command
27
28
29 # Status returns for the command line.
30 OK, ERR = 0, 1
31
32
33 class CoverageTest(
34 EnvironmentAwareMixin,
35 StdStreamCapturingMixin,
36 TempDirMixin,
37 TestCase
38 ):
39 """A base class for coverage.py test cases."""
40
41 # Standard unittest setting: show me diffs even if they are very long.
42 maxDiff = None
43
44 # Tell newer unittest implementations to print long helpful messages.
45 longMessage = True
46
47 def setUp(self):
48 super(CoverageTest, self).setUp()
49
50 if _TEST_NAME_FILE: # pragma: debugging
51 with open(_TEST_NAME_FILE, "w") as f:
52 f.write("%s_%s" % (
53 self.__class__.__name__, self._testMethodName,
54 ))
55
56 def skip(self, reason):
57 """Skip this test, and give a reason."""
58 self.class_behavior().skipped += 1
59 raise SkipTest(reason)
60
61 def clean_local_file_imports(self):
62 """Clean up the results of calls to `import_local_file`.
63
64 Use this if you need to `import_local_file` the same file twice in
65 one test.
66
67 """
68 # So that we can re-import files, clean them out first.
69 self.cleanup_modules()
70 # Also have to clean out the .pyc file, since the timestamp
71 # resolution is only one second, a changed file might not be
72 # picked up.
73 for pyc in glob.glob('*.pyc'):
74 os.remove(pyc)
75 if os.path.exists("__pycache__"):
76 shutil.rmtree("__pycache__")
77
78 def import_local_file(self, modname):
79 """Import a local file as a module.
80
81 Opens a file in the current directory named `modname`.py, imports it
82 as `modname`, and returns the module object.
83
84 """
85 return import_local_file(modname)
86
87 def start_import_stop(self, cov, modname):
88 """Start coverage, import a file, then stop coverage.
89
90 `cov` is started and stopped, with an `import_local_file` of
91 `modname` in the middle.
92
93 The imported module is returned.
94
95 """
96 cov.start()
97 try: # pragma: nested
98 # Import the Python file, executing it.
99 mod = self.import_local_file(modname)
100 finally: # pragma: nested
101 # Stop coverage.py.
102 cov.stop()
103 return mod
104
105 def get_module_name(self):
106 """Return the module name to use for this test run."""
107 return 'coverage_test_' + str(random.random())[2:]
108
109 # Map chars to numbers for arcz_to_arcs
110 _arcz_map = {'.': -1}
111 _arcz_map.update(dict((c, ord(c) - ord('0')) for c in '123456789'))
112 _arcz_map.update(dict(
113 (c, 10 + ord(c) - ord('A')) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
114 ))
115
116 def arcz_to_arcs(self, arcz):
117 """Convert a compact textual representation of arcs to a list of pairs.
118
119 The text has space-separated pairs of letters. Period is -1, 1-9 are
120 1-9, A-Z are 10 through 36. The resulting list is sorted regardless of
121 the order of the input pairs.
122
123 ".1 12 2." --> [(-1,1), (1,2), (2,-1)]
124
125 Minus signs can be included in the pairs:
126
127 "-11, 12, 2-5" --> [(-1,1), (1,2), (2,-5)]
128
129 """
130 arcs = []
131 for pair in arcz.split():
132 asgn = bsgn = 1
133 if len(pair) == 2:
134 a, b = pair
135 else:
136 assert len(pair) == 3
137 if pair[0] == '-':
138 _, a, b = pair
139 asgn = -1
140 else:
141 assert pair[1] == '-'
142 a, _, b = pair
143 bsgn = -1
144 arcs.append((asgn * self._arcz_map[a], bsgn * self._arcz_map[b]))
145 return sorted(arcs)
146
147 def assert_equal_args(self, a1, a2, msg=None):
148 """Assert that the arc lists `a1` and `a2` are equal."""
149 # Make them into multi-line strings so we can see what's going wrong.
150 s1 = "\n".join(repr(a) for a in a1) + "\n"
151 s2 = "\n".join(repr(a) for a in a2) + "\n"
152 self.assertMultiLineEqual(s1, s2, msg)
153
154 def check_coverage(
155 self, text, lines=None, missing="", report="",
156 excludes=None, partials="",
157 arcz=None, arcz_missing=None, arcz_unpredicted=None,
158 arcs=None, arcs_missing=None, arcs_unpredicted=None,
159 ):
160 """Check the coverage measurement of `text`.
161
162 The source `text` is run and measured. `lines` are the line numbers
163 that are executable, or a list of possible line numbers, any of which
164 could match. `missing` are the lines not executed, `excludes` are
165 regexes to match against for excluding lines, and `report` is the text
166 of the measurement report.
167
168 For arc measurement, `arcz` is a string that can be decoded into arcs
169 in the code (see `arcz_to_arcs` for the encoding scheme),
170 `arcz_missing` are the arcs that are not executed, and
171 `arcs_unpredicted` are the arcs executed in the code, but not deducible
172 from the code.
173
174 Returns the Coverage object, in case you want to poke at it some more.
175
176 """
177 # We write the code into a file so that we can import it.
178 # Coverage.py wants to deal with things as modules with file names.
179 modname = self.get_module_name()
180
181 self.make_file(modname + ".py", text)
182
183 if arcs is None and arcz is not None:
184 arcs = self.arcz_to_arcs(arcz)
185 if arcs_missing is None and arcz_missing is not None:
186 arcs_missing = self.arcz_to_arcs(arcz_missing)
187 if arcs_unpredicted is None and arcz_unpredicted is not None:
188 arcs_unpredicted = self.arcz_to_arcs(arcz_unpredicted)
189 branch = any(x is not None for x in [arcs, arcs_missing, arcs_unpredicte d])
190
191 # Start up coverage.py.
192 cov = coverage.Coverage(branch=branch)
193 cov.erase()
194 for exc in excludes or []:
195 cov.exclude(exc)
196 for par in partials or []:
197 cov.exclude(par, which='partial')
198
199 mod = self.start_import_stop(cov, modname)
200
201 # Clean up our side effects
202 del sys.modules[modname]
203
204 # Get the analysis results, and check that they are right.
205 analysis = cov._analyze(mod)
206 statements = sorted(analysis.statements)
207 if lines is not None:
208 if isinstance(lines[0], int):
209 # lines is just a list of numbers, it must match the statements
210 # found in the code.
211 self.assertEqual(statements, lines)
212 else:
213 # lines is a list of possible line number lists, one of them
214 # must match.
215 for line_list in lines:
216 if statements == line_list:
217 break
218 else:
219 self.fail("None of the lines choices matched %r" % statement s)
220
221 missing_formatted = analysis.missing_formatted()
222 if isinstance(missing, string_class):
223 self.assertEqual(missing_formatted, missing)
224 else:
225 for missing_list in missing:
226 if missing_formatted == missing_list:
227 break
228 else:
229 self.fail("None of the missing choices matched %r" % missing _formatted)
230
231 if arcs is not None:
232 self.assert_equal_args(analysis.arc_possibilities(), arcs, "Possible arcs differ")
233
234 if arcs_missing is not None:
235 self.assert_equal_args(
236 analysis.arcs_missing(), arcs_missing,
237 "Missing arcs differ"
238 )
239
240 if arcs_unpredicted is not None:
241 self.assert_equal_args(
242 analysis.arcs_unpredicted(), arcs_unpredicted,
243 "Unpredicted arcs differ"
244 )
245
246 if report:
247 frep = StringIO()
248 cov.report(mod, file=frep)
249 rep = " ".join(frep.getvalue().split("\n")[2].split()[1:])
250 self.assertEqual(report, rep)
251
252 return cov
253
254 def nice_file(self, *fparts):
255 """Canonicalize the file name composed of the parts in `fparts`."""
256 fname = os.path.join(*fparts)
257 return os.path.normcase(os.path.abspath(os.path.realpath(fname)))
258
259 def assert_same_files(self, flist1, flist2):
260 """Assert that `flist1` and `flist2` are the same set of file names."""
261 flist1_nice = [self.nice_file(f) for f in flist1]
262 flist2_nice = [self.nice_file(f) for f in flist2]
263 self.assertCountEqual(flist1_nice, flist2_nice)
264
265 def assert_exists(self, fname):
266 """Assert that `fname` is a file that exists."""
267 msg = "File %r should exist" % fname
268 self.assertTrue(os.path.exists(fname), msg)
269
270 def assert_doesnt_exist(self, fname):
271 """Assert that `fname` is a file that doesn't exist."""
272 msg = "File %r shouldn't exist" % fname
273 self.assertTrue(not os.path.exists(fname), msg)
274
275 def assert_starts_with(self, s, prefix, msg=None):
276 """Assert that `s` starts with `prefix`."""
277 if not s.startswith(prefix):
278 self.fail(msg or ("%r doesn't start with %r" % (s, prefix)))
279
280 def assert_recent_datetime(self, dt, seconds=10, msg=None):
281 """Assert that `dt` marks a time at most `seconds` seconds ago."""
282 age = datetime.datetime.now() - dt
283 # Python2.6 doesn't have total_seconds :(
284 self.assertEqual(age.days, 0, msg)
285 self.assertGreaterEqual(age.seconds, 0, msg)
286 self.assertLessEqual(age.seconds, seconds, msg)
287
288 def command_line(self, args, ret=OK, _covpkg=None):
289 """Run `args` through the command line.
290
291 Use this when you want to run the full coverage machinery, but in the
292 current process. Exceptions may be thrown from deep in the code.
293 Asserts that `ret` is returned by `CoverageScript.command_line`.
294
295 Compare with `run_command`.
296
297 Returns None.
298
299 """
300 script = CoverageScript(_covpkg=_covpkg)
301 ret_actual = script.command_line(shlex.split(args))
302 self.assertEqual(ret_actual, ret)
303
304 def run_command(self, cmd):
305 """Run the command-line `cmd` in a sub-process, and print its output.
306
307 Use this when you need to test the process behavior of coverage.
308
309 Compare with `command_line`.
310
311 Returns the process' stdout text.
312
313 """
314 # Running Python sub-processes can be tricky. Use the real name of our
315 # own executable. So "python foo.py" might get executed as
316 # "python3.3 foo.py". This is important because Python 3.x doesn't
317 # install as "python", so you might get a Python 2 executable instead
318 # if you don't use the executable's basename.
319 if cmd.startswith("python "):
320 cmd = os.path.basename(sys.executable) + cmd[6:]
321
322 _, output = self.run_command_status(cmd)
323 return output
324
325 def run_command_status(self, cmd):
326 """Run the command-line `cmd` in a sub-process, and print its output.
327
328 Use this when you need to test the process behavior of coverage.
329
330 Compare with `command_line`.
331
332 Returns a pair: the process' exit status and stdout text.
333
334 """
335 # Add our test modules directory to PYTHONPATH. I'm sure there's too
336 # much path munging here, but...
337 here = os.path.dirname(self.nice_file(coverage.__file__, ".."))
338 testmods = self.nice_file(here, 'tests/modules')
339 zipfile = self.nice_file(here, 'tests/zipmods.zip')
340 pypath = os.getenv('PYTHONPATH', '')
341 if pypath:
342 pypath += os.pathsep
343 pypath += testmods + os.pathsep + zipfile
344 self.set_environ('PYTHONPATH', pypath)
345
346 status, output = run_command(cmd)
347 print(output)
348 return status, output
349
350 def report_from_command(self, cmd):
351 """Return the report from the `cmd`, with some convenience added."""
352 report = self.run_command(cmd).replace('\\', '/')
353 self.assertNotIn("error", report.lower())
354 return report
355
356 def report_lines(self, report):
357 """Return the lines of the report, as a list."""
358 lines = report.split('\n')
359 self.assertEqual(lines[-1], "")
360 return lines[:-1]
361
362 def line_count(self, report):
363 """How many lines are in `report`?"""
364 return len(self.report_lines(report))
365
366 def squeezed_lines(self, report):
367 """Return a list of the lines in report, with the spaces squeezed."""
368 lines = self.report_lines(report)
369 return [re.sub(r"\s+", " ", l.strip()) for l in lines]
370
371 def last_line_squeezed(self, report):
372 """Return the last line of `report` with the spaces squeezed down."""
373 return self.squeezed_lines(report)[-1]
374
375
376 class DebugControlString(DebugControl):
377 """A `DebugControl` that writes to a StringIO, for testing."""
378 def __init__(self, options):
379 super(DebugControlString, self).__init__(options, StringIO())
380
381 def get_output(self):
382 """Get the output text from the `DebugControl`."""
383 return self.output.getvalue()
OLDNEW
« no previous file with comments | « tools/telemetry/third_party/coverage/tests/backtest.py ('k') | tools/telemetry/third_party/coverage/tests/covmodzip1.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698