diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2009-03-13 21:23:00 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2009-03-13 21:23:00 -0400 |
commit | 8e9454c81ae484f6fdc9af33403a71c35dda193e (patch) | |
tree | 0af3938008ab21221d912284be82d7a3df3ebf55 /test/test_coverage.py | |
parent | eb8bd102097e9d177ec7c56e3948bcf265004119 (diff) | |
download | python-coveragepy-git-8e9454c81ae484f6fdc9af33403a71c35dda193e.tar.gz |
Move the tests into the test directory.
--HG--
rename : coverage_coverage.py => test/coverage_coverage.py
rename : test_coverage.py => test/test_coverage.py
Diffstat (limited to 'test/test_coverage.py')
-rw-r--r-- | test/test_coverage.py | 1979 |
1 files changed, 1979 insertions, 0 deletions
diff --git a/test/test_coverage.py b/test/test_coverage.py new file mode 100644 index 00000000..4c2f8b6e --- /dev/null +++ b/test/test_coverage.py @@ -0,0 +1,1979 @@ +# test coverage.py +# Copyright 2004-2009, Ned Batchelder +# http://nedbatchelder.com/code/modules/coverage.html + +# Change this 0 to 1 to get diagnostic output during testing. +showstdout = 0 + +import unittest +import imp, os, pprint, random, sys, tempfile +from cStringIO import StringIO + +import path # from http://www.jorendorff.com/articles/python/path/ + +import coverage + +CovExc = coverage.CoverageException + +from textwrap import dedent + + +coverage.use_cache(0) + + +class CoverageTest(unittest.TestCase): + def setUp(self): + # Create a temporary directory. + self.noise = str(random.random())[2:] + self.temproot = path.path(tempfile.gettempdir()) / 'test_coverage' + self.tempdir = self.temproot / self.noise + self.tempdir.makedirs() + self.olddir = os.getcwd() + os.chdir(self.tempdir) + # Keep a counter to make every call to checkCoverage unique. + self.n = 0 + + # Capture stdout, so we can use print statements in the tests and not + # pollute the test output. + self.oldstdout = sys.stdout + self.capturedstdout = StringIO() + if not showstdout: + sys.stdout = self.capturedstdout + coverage.begin_recursive() + + def tearDown(self): + coverage.end_recursive() + sys.stdout = self.oldstdout + # Get rid of the temporary directory. + os.chdir(self.olddir) + self.temproot.rmtree() + + def getStdout(self): + return self.capturedstdout.getvalue() + + def makeFile(self, modname, text): + """ Create a temp file with modname as the module name, and text as the + contents. + """ + text = dedent(text) + + # Create the python file. + f = open(modname + '.py', 'w') + f.write(text) + f.close() + + def importModule(self, modname): + """ Import the module named modname, and return the module object. + """ + modfile = modname + '.py' + f = open(modfile, 'r') + + for suff in imp.get_suffixes(): + if suff[0] == '.py': + break + try: + mod = imp.load_module(modname, f, modfile, suff) + finally: + f.close() + return mod + + def getModuleName(self): + # We append self.n because otherwise two calls in one test will use the + # same filename and whether the test works or not depends on the + # timestamps in the .pyc file, so it becomes random whether the second + # call will use the compiled version of the first call's code or not! + modname = 'coverage_test_' + self.noise + str(self.n) + self.n += 1 + return modname + + def checkCoverage(self, text, lines, missing="", excludes=[], report=""): + self.checkEverything(text=text, lines=lines, missing=missing, excludes=excludes, report=report) + + def checkEverything(self, text=None, file=None, lines=None, missing=None, + excludes=[], report="", annfile=None): + assert text or file + assert not (text and file) + + # We write the code into a file so that we can import it. + # coverage.py wants to deal with things as modules with file names. + modname = self.getModuleName() + + if text: + self.makeFile(modname, text) + elif file: + p = path.path(self.olddir) / file + p.copyfile(modname + '.py') + + # Start up coverage.py + coverage.erase() + for exc in excludes: + coverage.exclude(exc) + coverage.start() + + # Import the python file, executing it. + mod = self.importModule(modname) + + # Stop coverage.py + coverage.stop() + + # Clean up our side effects + del sys.modules[modname] + + # Get the analysis results, and check that they are right. + _, clines, _, cmissing = coverage.analysis(mod) + if lines is not None: + if type(lines[0]) == type(1): + self.assertEqual(clines, lines) + else: + for line_list in lines: + if clines == line_list: + break + else: + self.fail("None of the lines choices matched %r" % clines) + if missing is not None: + if type(missing) == type(""): + self.assertEqual(cmissing, missing) + else: + for missing_list in missing: + if cmissing == missing_list: + break + else: + self.fail("None of the missing choices matched %r" % cmissing) + + if report: + frep = StringIO() + coverage.report(mod, file=frep) + rep = " ".join(frep.getvalue().split("\n")[2].split()[1:]) + self.assertEqual(report, rep) + + if annfile: + # Run annotate. + coverage.annotate([modname+'.py']) + expect = (path.path(self.olddir) / annfile).text() + actual = path.path(modname + '.py,cover').text() + # Write the actual results into a file for comparison. + out = path.path(self.olddir) / (annfile + "_actual_%s%s" % (sys.version_info[:2])) + # Check if the results are right + if expect == actual: + # They are right: delete the old test results if they are still + # around. + if out.exists(): + out.remove() + else: + # The results are wrong: write them out so we can diff them to + # see what happened. + out.write_text(actual) + self.fail("Annotation is incorrect: %s" % out) + + def assertRaisesMsg(self, excClass, msg, callableObj, *args, **kwargs): + """ Just like unittest.TestCase.assertRaises, + but checks that the message is right too. + """ + try: + callableObj(*args, **kwargs) + except excClass, exc: + excMsg = str(exc) + if not msg: + # No message provided: it passes. + return #pragma: no cover + elif excMsg == msg: + # Message provided, and we got the right message: it passes. + return + else: #pragma: no cover + # Message provided, and it didn't match: fail! + raise self.failureException("Right exception, wrong message: got '%s' expected '%s'" % (excMsg, msg)) + # No need to catch other exceptions: They'll fail the test all by themselves! + else: #pragma: no cover + if hasattr(excClass,'__name__'): + excName = excClass.__name__ + else: + excName = str(excClass) + raise self.failureException("Expected to raise %s, didn't get an exception at all" % excName) + + def nice_file(self, *fparts): + return os.path.normcase(os.path.abspath(os.path.realpath(os.path.join(*fparts)))) + + def run_command(self, cmd): + """ Run the command-line `cmd`, print its output. + """ + # Add our test modules directory to PYTHONPATH. I'm sure there's too + # much path munging here, but... + here = os.path.dirname(self.nice_file(coverage.__file__, "..")) + testmods = self.nice_file(here, 'test/modules') + zipfile = self.nice_file(here, 'test/zipmods.zip') + pypath = os.environ['PYTHONPATH'] + if pypath: + pypath += os.pathsep + pypath += testmods + os.pathsep + zipfile + os.environ['PYTHONPATH'] = pypath + + stdin, stdouterr = os.popen4(cmd) + output = stdouterr.read() + if showstdout: + print output + return output + + +class BasicCoverageTests(CoverageTest): + def testSimple(self): + self.checkCoverage("""\ + a = 1 + b = 2 + + c = 4 + # Nothing here + d = 6 + """, + [1,2,4,6], report="4 4 100%") + + def testIndentationWackiness(self): + # Partial final lines are OK. + self.checkCoverage("""\ + import sys + if not sys.path: + a = 1 + """, + [1,2,3], "3") + + def testMultilineInitializer(self): + self.checkCoverage("""\ + d = { + 'foo': 1+2, + 'bar': (lambda x: x+1)(1), + 'baz': str(1), + } + + e = { 'foo': 1, 'bar': 2 } + """, + [1,7], "") + + def testListComprehension(self): + self.checkCoverage("""\ + l = [ + 2*i for i in range(10) + if i > 5 + ] + assert l == [12, 14, 16, 18] + """, + [1,5], "") + + +class SimpleStatementTests(CoverageTest): + def testExpression(self): + self.checkCoverage("""\ + 1 + 2 + 1 + \\ + 2 + """, + [1,2], "") + + def testAssert(self): + self.checkCoverage("""\ + assert (1 + 2) + assert (1 + + 2) + assert (1 + 2), 'the universe is broken' + assert (1 + + 2), \\ + 'something is amiss' + """, + [1,2,4,5], "") + + def testAssignment(self): + # Simple variable assignment + self.checkCoverage("""\ + a = (1 + 2) + b = (1 + + 2) + c = \\ + 1 + """, + [1,2,4], "") + + def testAssignTuple(self): + self.checkCoverage("""\ + a = 1 + a,b,c = 7,8,9 + assert a == 7 and b == 8 and c == 9 + """, + [1,2,3], "") + + def testAttributeAssignment(self): + # Attribute assignment + self.checkCoverage("""\ + class obj: pass + o = obj() + o.foo = (1 + 2) + o.foo = (1 + + 2) + o.foo = \\ + 1 + """, + [1,2,3,4,6], "") + + def testListofAttributeAssignment(self): + self.checkCoverage("""\ + class obj: pass + o = obj() + o.a, o.b = (1 + 2), 3 + o.a, o.b = (1 + + 2), (3 + + 4) + o.a, o.b = \\ + 1, \\ + 2 + """, + [1,2,3,4,7], "") + + def testAugmentedAssignment(self): + self.checkCoverage("""\ + a = 1 + a += 1 + a += (1 + + 2) + a += \\ + 1 + """, + [1,2,3,5], "") + + def testTripleStringStuff(self): + self.checkCoverage("""\ + a = ''' + a multiline + string. + ''' + b = ''' + long expression + ''' + ''' + on many + lines. + ''' + c = len(''' + long expression + ''' + + ''' + on many + lines. + ''') + """, + [1,5,11], "") + + def testPass(self): + # pass is tricky: if it's the only statement in a block, then it is + # "executed". But if it is not the only statement, then it is not. + self.checkCoverage("""\ + if 1==1: + pass + """, + [1,2], "") + self.checkCoverage("""\ + def foo(): + pass + foo() + """, + [1,2,3], "") + self.checkCoverage("""\ + def foo(): + "doc" + pass + foo() + """, + ([1,3,4], [1,4]), "") + self.checkCoverage("""\ + class Foo: + def foo(self): + pass + Foo().foo() + """, + [1,2,3,4], "") + self.checkCoverage("""\ + class Foo: + def foo(self): + "Huh?" + pass + Foo().foo() + """, + ([1,2,4,5], [1,2,5]), "") + + def testDel(self): + self.checkCoverage("""\ + d = { 'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1 } + del d['a'] + del d[ + 'b' + ] + del d['c'], \\ + d['d'], \\ + d['e'] + assert(len(d.keys()) == 0) + """, + [1,2,3,6,9], "") + + def testPrint(self): + self.checkCoverage("""\ + print "hello, world!" + print ("hey: %d" % + 17) + print "goodbye" + print "hello, world!", + print ("hey: %d" % + 17), + print "goodbye", + """, + [1,2,4,5,6,8], "") + + def testRaise(self): + self.checkCoverage("""\ + try: + raise Exception( + "hello %d" % + 17) + except: + pass + """, + [1,2,5,6], "") + + def testReturn(self): + self.checkCoverage("""\ + def fn(): + a = 1 + return a + + x = fn() + assert(x == 1) + """, + [1,2,3,5,6], "") + self.checkCoverage("""\ + def fn(): + a = 1 + return ( + a + + 1) + + x = fn() + assert(x == 2) + """, + [1,2,3,7,8], "") + self.checkCoverage("""\ + def fn(): + a = 1 + return (a, + a + 1, + a + 2) + + x,y,z = fn() + assert x == 1 and y == 2 and z == 3 + """, + [1,2,3,7,8], "") + + def testYield(self): + self.checkCoverage("""\ + from __future__ import generators + def gen(): + yield 1 + yield (2+ + 3+ + 4) + yield 1, \\ + 2 + a,b,c = gen() + assert a == 1 and b == 9 and c == (1,2) + """, + [1,2,3,4,7,9,10], "") + + def testBreak(self): + self.checkCoverage("""\ + for x in range(10): + print "Hello" + break + print "Not here" + """, + [1,2,3,4], "4") + + def testContinue(self): + self.checkCoverage("""\ + for x in range(10): + print "Hello" + continue + print "Not here" + """, + [1,2,3,4], "4") + + if 0: + # Peephole optimization of jumps to jumps can mean that some statements + # never hit the line tracer. The behavior is different in different + # versions of Python, so don't run this test: + def testStrangeUnexecutedContinue(self): + self.checkCoverage("""\ + a = b = c = 0 + for n in range(100): + if n % 2: + if n % 4: + a += 1 + continue # <-- This line may not be hit. + else: + b += 1 + c += 1 + assert a == 50 and b == 50 and c == 50 + + a = b = c = 0 + for n in range(100): + if n % 2: + if n % 3: + a += 1 + continue # <-- This line is always hit. + else: + b += 1 + c += 1 + assert a == 33 and b == 50 and c == 50 + """, + [1,2,3,4,5,6,8,9,10, 12,13,14,15,16,17,19,20,21], "") + + def testImport(self): + self.checkCoverage("""\ + import string + from sys import path + a = 1 + """, + [1,2,3], "") + self.checkCoverage("""\ + import string + if 1 == 2: + from sys import path + a = 1 + """, + [1,2,3,4], "3") + self.checkCoverage("""\ + import string, \\ + os, \\ + re + from sys import path, \\ + stdout + a = 1 + """, + [1,4,6], "") + self.checkCoverage("""\ + import sys, sys as s + assert s.path == sys.path + """, + [1,2], "") + self.checkCoverage("""\ + import sys, \\ + sys as s + assert s.path == sys.path + """, + [1,3], "") + self.checkCoverage("""\ + from sys import path, \\ + path as p + assert p == path + """, + [1,3], "") + self.checkCoverage("""\ + from sys import \\ + * + assert len(path) > 0 + """, + [1,3], "") + + def testGlobal(self): + self.checkCoverage("""\ + g = h = i = 1 + def fn(): + global g + global h, \\ + i + g = h = i = 2 + fn() + assert g == 2 and h == 2 and i == 2 + """, + [1,2,6,7,8], "") + self.checkCoverage("""\ + g = h = i = 1 + def fn(): + global g; g = 2 + fn() + assert g == 2 and h == 1 and i == 1 + """, + [1,2,3,4,5], "") + + def testExec(self): + self.checkCoverage("""\ + a = b = c = 1 + exec "a = 2" + exec ("b = " + + "c = " + + "2") + assert a == 2 and b == 2 and c == 2 + """, + [1,2,3,6], "") + self.checkCoverage("""\ + vars = {'a': 1, 'b': 1, 'c': 1} + exec "a = 2" in vars + exec ("b = " + + "c = " + + "2") in vars + assert vars['a'] == 2 and vars['b'] == 2 and vars['c'] == 2 + """, + [1,2,3,6], "") + self.checkCoverage("""\ + globs = {} + locs = {'a': 1, 'b': 1, 'c': 1} + exec "a = 2" in globs, locs + exec ("b = " + + "c = " + + "2") in globs, locs + assert locs['a'] == 2 and locs['b'] == 2 and locs['c'] == 2 + """, + [1,2,3,4,7], "") + + def testExtraDocString(self): + self.checkCoverage("""\ + a = 1 + "An extra docstring, should be a comment." + b = 3 + assert (a,b) == (1,3) + """, + [1,3,4], "") + self.checkCoverage("""\ + a = 1 + "An extra docstring, should be a comment." + b = 3 + 123 # A number for some reason: ignored + 1+1 # An expression: executed. + c = 6 + assert (a,b,c) == (1,3,6) + """, + ([1,3,5,6,7], [1,3,4,5,6,7]), "") + + +class CompoundStatementTests(CoverageTest): + def testStatementList(self): + self.checkCoverage("""\ + a = 1; + b = 2; c = 3 + d = 4; e = 5; + + assert (a,b,c,d,e) == (1,2,3,4,5) + """, + [1,2,3,5], "") + + def testIf(self): + self.checkCoverage("""\ + a = 1 + if a == 1: + x = 3 + assert x == 3 + if (a == + 1): + x = 7 + assert x == 7 + """, + [1,2,3,4,5,7,8], "") + self.checkCoverage("""\ + a = 1 + if a == 1: + x = 3 + else: + y = 5 + assert x == 3 + """, + [1,2,3,5,6], "5") + self.checkCoverage("""\ + a = 1 + if a != 1: + x = 3 + else: + y = 5 + assert y == 5 + """, + [1,2,3,5,6], "3") + self.checkCoverage("""\ + a = 1; b = 2 + if a == 1: + if b == 2: + x = 4 + else: + y = 6 + else: + z = 8 + assert x == 4 + """, + [1,2,3,4,6,8,9], "6-8") + + def testElif(self): + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a == 1: + x = 3 + elif b == 2: + y = 5 + else: + z = 7 + assert x == 3 + """, + [1,2,3,4,5,7,8], "4-7", report="7 4 57% 4-7") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a != 1: + x = 3 + elif b == 2: + y = 5 + else: + z = 7 + assert y == 5 + """, + [1,2,3,4,5,7,8], "3, 7", report="7 5 71% 3, 7") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a != 1: + x = 3 + elif b != 2: + y = 5 + else: + z = 7 + assert z == 7 + """, + [1,2,3,4,5,7,8], "3, 5", report="7 5 71% 3, 5") + + def testElifNoElse(self): + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a == 1: + x = 3 + elif b == 2: + y = 5 + assert x == 3 + """, + [1,2,3,4,5,6], "4-5", report="6 4 66% 4-5") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a != 1: + x = 3 + elif b == 2: + y = 5 + assert y == 5 + """, + [1,2,3,4,5,6], "3", report="6 5 83% 3") + + def testElifBizarre(self): + self.checkCoverage("""\ + def f(self): + if self==1: + x = 3 + elif self.m('fred'): + x = 5 + elif (g==1) and (b==2): + x = 7 + elif self.m('fred')==True: + x = 9 + elif ((g==1) and (b==2))==True: + x = 11 + else: + x = 13 + """, + [1,2,3,4,5,6,7,8,9,10,11,13], "2-13") + + def testSplitIf(self): + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if \\ + a == 1: + x = 3 + elif \\ + b == 2: + y = 5 + else: + z = 7 + assert x == 3 + """, + [1,2,4,5,7,9,10], "5-9") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if \\ + a != 1: + x = 3 + elif \\ + b == 2: + y = 5 + else: + z = 7 + assert y == 5 + """, + [1,2,4,5,7,9,10], "4, 9") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if \\ + a != 1: + x = 3 + elif \\ + b != 2: + y = 5 + else: + z = 7 + assert z == 7 + """, + [1,2,4,5,7,9,10], "4, 7") + + def testPathologicalSplitIf(self): + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if ( + a == 1 + ): + x = 3 + elif ( + b == 2 + ): + y = 5 + else: + z = 7 + assert x == 3 + """, + [1,2,5,6,9,11,12], "6-11") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if ( + a != 1 + ): + x = 3 + elif ( + b == 2 + ): + y = 5 + else: + z = 7 + assert y == 5 + """, + [1,2,5,6,9,11,12], "5, 11") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if ( + a != 1 + ): + x = 3 + elif ( + b != 2 + ): + y = 5 + else: + z = 7 + assert z == 7 + """, + [1,2,5,6,9,11,12], "5, 9") + + def testAbsurdSplitIf(self): + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a == 1 \\ + : + x = 3 + elif b == 2 \\ + : + y = 5 + else: + z = 7 + assert x == 3 + """, + [1,2,4,5,7,9,10], "5-9") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a != 1 \\ + : + x = 3 + elif b == 2 \\ + : + y = 5 + else: + z = 7 + assert y == 5 + """, + [1,2,4,5,7,9,10], "4, 9") + self.checkCoverage("""\ + a = 1; b = 2; c = 3; + if a != 1 \\ + : + x = 3 + elif b != 2 \\ + : + y = 5 + else: + z = 7 + assert z == 7 + """, + [1,2,4,5,7,9,10], "4, 7") + + def testWhile(self): + self.checkCoverage("""\ + a = 3; b = 0 + while a: + b += 1 + a -= 1 + assert a == 0 and b == 3 + """, + [1,2,3,4,5], "") + self.checkCoverage("""\ + a = 3; b = 0 + while a: + b += 1 + break + b = 99 + assert a == 3 and b == 1 + """, + [1,2,3,4,5,6], "5") + + def testWhileElse(self): + # Take the else branch. + self.checkCoverage("""\ + a = 3; b = 0 + while a: + b += 1 + a -= 1 + else: + b = 99 + assert a == 0 and b == 99 + """, + [1,2,3,4,6,7], "") + # Don't take the else branch. + self.checkCoverage("""\ + a = 3; b = 0 + while a: + b += 1 + a -= 1 + break + b = 123 + else: + b = 99 + assert a == 2 and b == 1 + """, + [1,2,3,4,5,6,8,9], "6-8") + + def testSplitWhile(self): + self.checkCoverage("""\ + a = 3; b = 0 + while \\ + a: + b += 1 + a -= 1 + assert a == 0 and b == 3 + """, + [1,2,4,5,6], "") + self.checkCoverage("""\ + a = 3; b = 0 + while ( + a + ): + b += 1 + a -= 1 + assert a == 0 and b == 3 + """, + [1,2,5,6,7], "") + + def testFor(self): + self.checkCoverage("""\ + a = 0 + for i in [1,2,3,4,5]: + a += i + assert a == 15 + """, + [1,2,3,4], "") + self.checkCoverage("""\ + a = 0 + for i in [1, + 2,3,4, + 5]: + a += i + assert a == 15 + """, + [1,2,5,6], "") + self.checkCoverage("""\ + a = 0 + for i in [1,2,3,4,5]: + a += i + break + a = 99 + assert a == 1 + """, + [1,2,3,4,5,6], "5") + + def testForElse(self): + self.checkCoverage("""\ + a = 0 + for i in range(5): + a += i+1 + else: + a = 99 + assert a == 99 + """, + [1,2,3,5,6], "") + self.checkCoverage("""\ + a = 0 + for i in range(5): + a += i+1 + break + a = 99 + else: + a = 123 + assert a == 1 + """, + [1,2,3,4,5,7,8], "5-7") + + def testSplitFor(self): + self.checkCoverage("""\ + a = 0 + for \\ + i in [1,2,3,4,5]: + a += i + assert a == 15 + """, + [1,2,4,5], "") + self.checkCoverage("""\ + a = 0 + for \\ + i in [1, + 2,3,4, + 5]: + a += i + assert a == 15 + """, + [1,2,6,7], "") + + def testTryExcept(self): + self.checkCoverage("""\ + a = 0 + try: + a = 1 + except: + a = 99 + assert a == 1 + """, + [1,2,3,4,5,6], "4-5") + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except: + a = 99 + assert a == 99 + """, + [1,2,3,4,5,6,7], "") + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except ImportError: + a = 99 + except: + a = 123 + assert a == 123 + """, + [1,2,3,4,5,6,7,8,9], "6") + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise IOError("foo") + except ImportError: + a = 99 + except IOError: + a = 17 + except: + a = 123 + assert a == 17 + """, + [1,2,3,4,5,6,7,8,9,10,11], "6, 9-10") + self.checkCoverage("""\ + a = 0 + try: + a = 1 + except: + a = 99 + else: + a = 123 + assert a == 123 + """, + [1,2,3,4,5,7,8], "4-5") + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except: + a = 99 + else: + a = 123 + assert a == 99 + """, + [1,2,3,4,5,6,8,9], "8") + + def testTryFinally(self): + self.checkCoverage("""\ + a = 0 + try: + a = 1 + finally: + a = 99 + assert a == 99 + """, + [1,2,3,5,6], "") + self.checkCoverage("""\ + a = 0; b = 0 + try: + a = 1 + try: + raise Exception("foo") + finally: + b = 123 + except: + a = 99 + assert a == 99 and b == 123 + """, + [1,2,3,4,5,7,8,9,10], "") + + def testFunctionDef(self): + self.checkCoverage("""\ + a = 99 + def foo(): + ''' docstring + ''' + return 1 + + a = foo() + assert a == 1 + """, + [1,2,5,7,8], "") + self.checkCoverage("""\ + def foo( + a, + b + ): + ''' docstring + ''' + return a+b + + x = foo(17, 23) + assert x == 40 + """, + [1,7,9,10], "") + self.checkCoverage("""\ + def foo( + a = (lambda x: x*2)(10), + b = ( + lambda x: + x+1 + )(1) + ): + ''' docstring + ''' + return a+b + + x = foo() + assert x == 22 + """, + [1,10,12,13], "") + + def testClassDef(self): + self.checkCoverage("""\ + # A comment. + class theClass: + ''' the docstring. + Don't be fooled. + ''' + def __init__(self): + ''' Another docstring. ''' + self.a = 1 + + def foo(self): + return self.a + + x = theClass().foo() + assert x == 1 + """, + [2,6,8,10,11,13,14], "") + + +class ExcludeTests(CoverageTest): + def testSimple(self): + self.checkCoverage("""\ + a = 1; b = 2 + + if 0: + a = 4 # -cc + """, + [1,3], "", ['-cc']) + + def testTwoExcludes(self): + self.checkCoverage("""\ + a = 1; b = 2 + + if a == 99: + a = 4 # -cc + b = 5 + c = 6 # -xx + assert a == 1 and b == 2 + """, + [1,3,5,7], "5", ['-cc', '-xx']) + + def testExcludingIfSuite(self): + self.checkCoverage("""\ + a = 1; b = 2 + + if 0: + a = 4 + b = 5 + c = 6 + assert a == 1 and b == 2 + """, + [1,7], "", ['if 0:']) + + def testExcludingIfButNotElseSuite(self): + self.checkCoverage("""\ + a = 1; b = 2 + + if 0: + a = 4 + b = 5 + c = 6 + else: + a = 8 + b = 9 + assert a == 8 and b == 9 + """, + [1,8,9,10], "", ['if 0:']) + + def testExcludingElseSuite(self): + self.checkCoverage("""\ + a = 1; b = 2 + + if 1==1: + a = 4 + b = 5 + c = 6 + else: #pragma: NO COVER + a = 8 + b = 9 + assert a == 4 and b == 5 and c == 6 + """, + [1,3,4,5,6,10], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 1; b = 2 + + if 1==1: + a = 4 + b = 5 + c = 6 + + # Lots of comments to confuse the else handler. + # more. + + else: #pragma: NO COVER + + # Comments here too. + + a = 8 + b = 9 + assert a == 4 and b == 5 and c == 6 + """, + [1,3,4,5,6,17], "", ['#pragma: NO COVER']) + + def testExcludingElifSuites(self): + self.checkCoverage("""\ + a = 1; b = 2 + + if 1==1: + a = 4 + b = 5 + c = 6 + elif 1==0: #pragma: NO COVER + a = 8 + b = 9 + else: + a = 11 + b = 12 + assert a == 4 and b == 5 and c == 6 + """, + [1,3,4,5,6,11,12,13], "11-12", ['#pragma: NO COVER']) + + def testExcludingOnelineIf(self): + self.checkCoverage("""\ + def foo(): + a = 2 + if 0: x = 3 # no cover + b = 4 + + foo() + """, + [1,2,4,6], "", ["no cover"]) + + def testExcludingAColonNotASuite(self): + self.checkCoverage("""\ + def foo(): + l = range(10) + print l[:3] # no cover + b = 4 + + foo() + """, + [1,2,4,6], "", ["no cover"]) + + def testExcludingForSuite(self): + self.checkCoverage("""\ + a = 0 + for i in [1,2,3,4,5]: #pragma: NO COVER + a += i + assert a == 15 + """, + [1,4], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + for i in [1, + 2,3,4, + 5]: #pragma: NO COVER + a += i + assert a == 15 + """, + [1,6], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + for i in [1,2,3,4,5 + ]: #pragma: NO COVER + a += i + break + a = 99 + assert a == 1 + """, + [1,7], "", ['#pragma: NO COVER']) + + def testExcludingForElse(self): + self.checkCoverage("""\ + a = 0 + for i in range(5): + a += i+1 + break + a = 99 + else: #pragma: NO COVER + a = 123 + assert a == 1 + """, + [1,2,3,4,5,8], "5", ['#pragma: NO COVER']) + + def testExcludingWhile(self): + self.checkCoverage("""\ + a = 3; b = 0 + while a*b: #pragma: NO COVER + b += 1 + break + b = 99 + assert a == 3 and b == 0 + """, + [1,6], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 3; b = 0 + while ( + a*b + ): #pragma: NO COVER + b += 1 + break + b = 99 + assert a == 3 and b == 0 + """, + [1,8], "", ['#pragma: NO COVER']) + + def testExcludingWhileElse(self): + self.checkCoverage("""\ + a = 3; b = 0 + while a: + b += 1 + break + b = 99 + else: #pragma: NO COVER + b = 123 + assert a == 3 and b == 1 + """, + [1,2,3,4,5,8], "5", ['#pragma: NO COVER']) + + def testExcludingTryExcept(self): + self.checkCoverage("""\ + a = 0 + try: + a = 1 + except: #pragma: NO COVER + a = 99 + assert a == 1 + """, + [1,2,3,6], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except: + a = 99 + assert a == 99 + """, + [1,2,3,4,5,6,7], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except ImportError: #pragma: NO COVER + a = 99 + except: + a = 123 + assert a == 123 + """, + [1,2,3,4,7,8,9], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + try: + a = 1 + except: #pragma: NO COVER + a = 99 + else: + a = 123 + assert a == 123 + """, + [1,2,3,7,8], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except: + a = 99 + else: #pragma: NO COVER + a = 123 + assert a == 99 + """, + [1,2,3,4,5,6,9], "", ['#pragma: NO COVER']) + + def testExcludingTryExceptPass(self): + self.checkCoverage("""\ + a = 0 + try: + a = 1 + except: #pragma: NO COVER + x = 2 + assert a == 1 + """, + [1,2,3,6], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except ImportError: #pragma: NO COVER + x = 2 + except: + a = 123 + assert a == 123 + """, + [1,2,3,4,7,8,9], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + try: + a = 1 + except: #pragma: NO COVER + x = 2 + else: + a = 123 + assert a == 123 + """, + [1,2,3,7,8], "", ['#pragma: NO COVER']) + self.checkCoverage("""\ + a = 0 + try: + a = 1 + raise Exception("foo") + except: + a = 99 + else: #pragma: NO COVER + x = 2 + assert a == 99 + """, + [1,2,3,4,5,6,9], "", ['#pragma: NO COVER']) + + def testExcludingIfPass(self): + # From a comment on the coverage page by Michael McNeil Forbes: + self.checkCoverage("""\ + def f(): + if False: # pragma: no cover + pass # This line still reported as missing + if False: # pragma: no cover + x = 1 # Now it is skipped. + + f() + """, + [1,7], "", ["no cover"]) + + def testExcludingFunction(self): + self.checkCoverage("""\ + def fn(foo): #pragma: NO COVER + a = 1 + b = 2 + c = 3 + + x = 1 + assert x == 1 + """, + [6,7], "", ['#pragma: NO COVER']) + + def testExcludingMethod(self): + self.checkCoverage("""\ + class Fooey: + def __init__(self): + self.a = 1 + + def foo(self): #pragma: NO COVER + return self.a + + x = Fooey() + assert x.a == 1 + """, + [1,2,3,8,9], "", ['#pragma: NO COVER']) + + def testExcludingClass(self): + self.checkCoverage("""\ + class Fooey: #pragma: NO COVER + def __init__(self): + self.a = 1 + + def foo(self): + return self.a + + x = 1 + assert x == 1 + """, + [8,9], "", ['#pragma: NO COVER']) + + +if sys.hexversion >= 0x020300f0: + # threading support was new in 2.3, only test there. + class ThreadingTests(CoverageTest): + def testThreading(self): + self.checkCoverage("""\ + import time, threading + + def fromMainThread(): + return "called from main thread" + + def fromOtherThread(): + return "called from other thread" + + def neverCalled(): + return "no one calls me" + + threading.Thread(target=fromOtherThread).start() + fromMainThread() + time.sleep(1) + """, + [1,3,4,6,7,9,10,12,13,14], "10") + + +if sys.hexversion >= 0x020400f0: + class Py24Tests(CoverageTest): + def testFunctionDecorators(self): + self.checkCoverage("""\ + def require_int(func): + def wrapper(arg): + assert isinstance(arg, int) + return func(arg) + + return wrapper + + @require_int + def p1(arg): + return arg*2 + + assert p1(10) == 20 + """, + [1,2,3,4,6,8,10,12], "") + + def testFunctionDecoratorsWithArgs(self): + self.checkCoverage("""\ + def boost_by(extra): + def decorator(func): + def wrapper(arg): + return extra*func(arg) + return wrapper + return decorator + + @boost_by(10) + def boosted(arg): + return arg*2 + + assert boosted(10) == 200 + """, + [1,2,3,4,5,6,8,10,12], "") + + def testDoubleFunctionDecorators(self): + self.checkCoverage("""\ + def require_int(func): + def wrapper(arg): + assert isinstance(arg, int) + return func(arg) + return wrapper + + def boost_by(extra): + def decorator(func): + def wrapper(arg): + return extra*func(arg) + return wrapper + return decorator + + @require_int + @boost_by(10) + def boosted1(arg): + return arg*2 + + assert boosted1(10) == 200 + + @boost_by(10) + @require_int + def boosted2(arg): + return arg*2 + + assert boosted2(10) == 200 + """, + ([1,2,3,4,5,7,8,9,10,11,12,14,15,17,19,21,22,24,26], + [1,2,3,4,5,7,8,9,10,11,12,14, 17,19,21, 24,26]), "") + + +if sys.hexversion >= 0x020500f0: + class Py25Tests(CoverageTest): + def testWithStatement(self): + self.checkCoverage("""\ + from __future__ import with_statement + + class Managed: + def __enter__(self): + print "enter" + + def __exit__(self, type, value, tb): + print "exit", type + + m = Managed() + with m: + print "block1a" + print "block1b" + + try: + with m: + print "block2" + raise Exception("Boo!") + except: + print "caught" + """, + [1,3,4,5,7,8,10,11,12,13,15,16,17,18,19,20], "") + + def testTryExceptFinally(self): + self.checkCoverage("""\ + a = 0; b = 0 + try: + a = 1 + except: + a = 99 + finally: + b = 2 + assert a == 1 and b == 2 + """, + [1,2,3,4,5,7,8], "4-5") + self.checkCoverage("""\ + a = 0; b = 0 + try: + a = 1 + raise Exception("foo") + except: + a = 99 + finally: + b = 2 + assert a == 99 and b == 2 + """, + [1,2,3,4,5,6,8,9], "") + self.checkCoverage("""\ + a = 0; b = 0 + try: + a = 1 + raise Exception("foo") + except ImportError: + a = 99 + except: + a = 123 + finally: + b = 2 + assert a == 123 and b == 2 + """, + [1,2,3,4,5,6,7,8,10,11], "6") + self.checkCoverage("""\ + a = 0; b = 0 + try: + a = 1 + raise IOError("foo") + except ImportError: + a = 99 + except IOError: + a = 17 + except: + a = 123 + finally: + b = 2 + assert a == 17 and b == 2 + """, + [1,2,3,4,5,6,7,8,9,10,12,13], "6, 9-10") + self.checkCoverage("""\ + a = 0; b = 0 + try: + a = 1 + except: + a = 99 + else: + a = 123 + finally: + b = 2 + assert a == 123 and b == 2 + """, + [1,2,3,4,5,7,9,10], "4-5") + self.checkCoverage("""\ + a = 0; b = 0 + try: + a = 1 + raise Exception("foo") + except: + a = 99 + else: + a = 123 + finally: + b = 2 + assert a == 99 and b == 2 + """, + [1,2,3,4,5,6,8,10,11], "8") + + +class ModuleTests(CoverageTest): + def testNotSingleton(self): + """ You *can* create another coverage object. + """ + coverage.coverage() + coverage.coverage() + + +class ApiTests(CoverageTest): + def testSimple(self): + coverage.erase() + + self.makeFile("mycode", """\ + a = 1 + b = 2 + if b == 3: + c = 4 + d = 5 + """) + + # Import the python file, executing it. + coverage.start() + self.importModule("mycode") + coverage.stop() + + filename, statements, missing, readablemissing = coverage.analysis("mycode.py") + self.assertEqual(statements, [1,2,3,4,5]) + self.assertEqual(missing, [4]) + self.assertEqual(readablemissing, "4") + + def doReportWork(self, modname): + coverage.erase() + + self.makeFile(modname, """\ + a = 1 + b = 2 + if b == 3: + c = 4 + d = 5 + e = 6 + f = 7 + """) + + # Import the python file, executing it. + coverage.start() + self.importModule(modname) + coverage.stop() + coverage.analysis(modname + ".py") + + def testReport(self): + self.doReportWork("mycode2") + coverage.report(["mycode2.py"]) + self.assertEqual(self.getStdout(), dedent("""\ + Name Stmts Exec Cover Missing + --------------------------------------- + mycode2 7 4 57% 4-6 + """)) + + def testReportFile(self): + self.doReportWork("mycode3") + fout = StringIO() + coverage.report(["mycode3.py"], file=fout) + self.assertEqual(self.getStdout(), "") + self.assertEqual(fout.getvalue(), dedent("""\ + Name Stmts Exec Cover Missing + --------------------------------------- + mycode3 7 4 57% 4-6 + """)) + + +class AnnotationTests(CoverageTest): + def testWhite(self): + self.checkEverything(file='test/white.py', annfile='test/white.py,cover') + + +class CmdLineTests(CoverageTest): + def help_fn(self, error=None): + raise Exception(error or "__doc__") + + def command_line(self, argv): + return coverage.CoverageScript().command_line(argv, self.help_fn) + + def testHelp(self): + self.assertRaisesMsg(Exception, "__doc__", self.command_line, ['-h']) + self.assertRaisesMsg(Exception, "__doc__", self.command_line, ['--help']) + + def testUnknownOption(self): + self.assertRaisesMsg(Exception, "option -z not recognized", self.command_line, ['-z']) + + def testBadActionCombinations(self): + self.assertRaisesMsg(Exception, "You can't specify the 'erase' and 'annotate' options at the same time.", self.command_line, ['-e', '-a']) + self.assertRaisesMsg(Exception, "You can't specify the 'erase' and 'report' options at the same time.", self.command_line, ['-e', '-r']) + self.assertRaisesMsg(Exception, "You can't specify the 'erase' and 'combine' options at the same time.", self.command_line, ['-e', '-c']) + self.assertRaisesMsg(Exception, "You can't specify the 'execute' and 'annotate' options at the same time.", self.command_line, ['-x', '-a']) + self.assertRaisesMsg(Exception, "You can't specify the 'execute' and 'report' options at the same time.", self.command_line, ['-x', '-r']) + self.assertRaisesMsg(Exception, "You can't specify the 'execute' and 'combine' options at the same time.", self.command_line, ['-x', '-c']) + + def testNeedAction(self): + self.assertRaisesMsg(Exception, "You must specify at least one of -e, -x, -c, -r, or -a.", self.command_line, ['-p']) + + def testArglessActions(self): + self.assertRaisesMsg(Exception, "Unexpected arguments: foo bar", self.command_line, ['-e', 'foo', 'bar']) + self.assertRaisesMsg(Exception, "Unexpected arguments: baz quux", self.command_line, ['-c', 'baz', 'quux']) + + +class ProcessTests(CoverageTest): + def testSaveOnExit(self): + self.makeFile("mycode", """\ + a = 1 + b = 2 + if b == 3: + c = 4 + d = 5 + """) + + self.assert_(not os.path.exists(".coverage")) + self.run_command("coverage -x mycode.py") + self.assert_(os.path.exists(".coverage")) + + def testEnvironment(self): + # Checks that we can import modules from the test directory at all! + self.makeFile("mycode", """\ + import covmod1 + import covmodzip1 + a = 1 + print 'done' + """) + + self.assert_(not os.path.exists(".coverage")) + out = self.run_command("coverage -x mycode.py") + self.assert_(os.path.exists(".coverage")) + self.assertEqual(out, 'done\n') + + def testReport(self): + self.makeFile("mycode", """\ + import covmod1 + import covmodzip1 + a = 1 + print 'done' + """) + + out = self.run_command("coverage -x mycode.py") + self.assertEqual(out, 'done\n') + report1 = self.run_command("coverage -r").replace('\\', '/') + + # Name Stmts Exec Cover + # ----------------------------------------------------------------------- + # c:/ned/coverage/trunk/coverage/__init__ 616 3 0% + # c:/ned/coverage/trunk/test/modules/covmod1 2 2 100% + # c:/ned/coverage/trunk/test/zipmods.zip/covmodzip1 2 2 100% + # c:/python25/lib/atexit 33 5 15% + # c:/python25/lib/ntpath 250 12 4% + # c:/python25/lib/threading 562 1 0% + # mycode 4 4 100% + # ----------------------------------------------------------------------- + # TOTAL 1467 27 1% + + self.assert_("/coverage/" in report1) + self.assert_("/test/modules/covmod1 " in report1) + self.assert_("/test/zipmods.zip/covmodzip1 " in report1) + self.assert_("mycode " in report1) + + for l in report1.split('\n'): + if '/test/modules/covmod1' in l: + # Save a module prefix for the omit test later. + prefix = l.split('/test/')[0] + '/test/' + break + + # Try reporting just one module + report2 = self.run_command("coverage -r mycode.py").replace('\\', '/') + self.assert_("/coverage/" not in report2) + self.assert_("/test/modules/covmod1 " not in report2) + self.assert_("/test/zipmods.zip/covmodzip1 " not in report2) + self.assert_("mycode " in report2) + + # Try reporting while omitting some modules + report3 = self.run_command("coverage -r -o %s" % prefix).replace('\\', '/') + self.assert_("/coverage/" in report3) + self.assert_("/test/modules/covmod1 " not in report3) + self.assert_("/test/zipmods.zip/covmodzip1 " not in report3) + self.assert_("mycode " in report3) + + def testCombineParallelData(self): + self.makeFile("b_or_c", """\ + import sys + a = 1 + if sys.argv[1] == 'b': + b = 1 + else: + c = 1 + d = 1 + print 'done' + """) + + out = self.run_command("coverage -x -p b_or_c.py b") + self.assertEqual(out, 'done\n') + self.assert_(not os.path.exists(".coverage")) + + out = self.run_command("coverage -x -p b_or_c.py c") + self.assertEqual(out, 'done\n') + self.assert_(not os.path.exists(".coverage")) + + # After two -p runs, there should be two .coverage.machine.123 files. + self.assertEqual(len([f for f in os.listdir('.') if f.startswith('.coverage.')]), 2) + + # Combine the parallel coverage data files into .coverage . + self.run_command("coverage -c") + self.assert_(os.path.exists(".coverage")) + + # Read the coverage file and see that b_or_c.py has all 7 lines executed. + data = coverage.CoverageData() + data.read_file(".coverage") + self.assertEqual(data.summary()['b_or_c.py'], 7) + + +if __name__ == '__main__': + print "Testing under Python version: %s" % sys.version + unittest.main() + + +# TODO: split "and" conditions across lines, and detect not running lines. +# (Can't be done: trace function doesn't get called for each line +# in an expression!) +# TODO: Generator comprehensions? +# TODO: Constant if tests ("if 1:"). Py 2.4 doesn't execute them. |