OLD | NEW |
(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 """Tests for coverage.py's code parsing.""" |
| 5 |
| 6 import textwrap |
| 7 |
| 8 from tests.coveragetest import CoverageTest |
| 9 |
| 10 from coverage import env |
| 11 from coverage.misc import NotPython |
| 12 from coverage.parser import PythonParser |
| 13 |
| 14 |
| 15 class PythonParserTest(CoverageTest): |
| 16 """Tests for coverage.py's Python code parsing.""" |
| 17 |
| 18 run_in_temp_dir = False |
| 19 |
| 20 def parse_source(self, text): |
| 21 """Parse `text` as source, and return the `PythonParser` used.""" |
| 22 if env.PY2: |
| 23 text = text.decode("ascii") |
| 24 text = textwrap.dedent(text) |
| 25 parser = PythonParser(text=text, exclude="nocover") |
| 26 parser.parse_source() |
| 27 return parser |
| 28 |
| 29 def test_exit_counts(self): |
| 30 parser = self.parse_source("""\ |
| 31 # check some basic branch counting |
| 32 class Foo: |
| 33 def foo(self, a): |
| 34 if a: |
| 35 return 5 |
| 36 else: |
| 37 return 7 |
| 38 |
| 39 class Bar: |
| 40 pass |
| 41 """) |
| 42 self.assertEqual(parser.exit_counts(), { |
| 43 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1 |
| 44 }) |
| 45 |
| 46 def test_generator_exit_counts(self): |
| 47 # https://bitbucket.org/ned/coveragepy/issue/324/yield-in-loop-confuses-
branch-coverage |
| 48 parser = self.parse_source("""\ |
| 49 def gen(input): |
| 50 for n in inp: |
| 51 yield (i * 2 for i in range(n)) |
| 52 |
| 53 list(gen([1,2,3])) |
| 54 """) |
| 55 self.assertEqual(parser.exit_counts(), { |
| 56 1:1, # def -> list |
| 57 2:2, # for -> yield; for -> exit |
| 58 3:2, # yield -> for; genexp exit |
| 59 5:1, # list -> exit |
| 60 }) |
| 61 |
| 62 def test_try_except(self): |
| 63 parser = self.parse_source("""\ |
| 64 try: |
| 65 a = 2 |
| 66 except ValueError: |
| 67 a = 4 |
| 68 except ZeroDivideError: |
| 69 a = 6 |
| 70 except: |
| 71 a = 8 |
| 72 b = 9 |
| 73 """) |
| 74 self.assertEqual(parser.exit_counts(), { |
| 75 1: 1, 2:1, 3:1, 4:1, 5:1, 6:1, 7:1, 8:1, 9:1 |
| 76 }) |
| 77 |
| 78 def test_excluded_classes(self): |
| 79 parser = self.parse_source("""\ |
| 80 class Foo: |
| 81 def __init__(self): |
| 82 pass |
| 83 |
| 84 if 0: # nocover |
| 85 class Bar: |
| 86 pass |
| 87 """) |
| 88 self.assertEqual(parser.exit_counts(), { |
| 89 1:0, 2:1, 3:1 |
| 90 }) |
| 91 |
| 92 def test_missing_branch_to_excluded_code(self): |
| 93 parser = self.parse_source("""\ |
| 94 if fooey: |
| 95 a = 2 |
| 96 else: # nocover |
| 97 a = 4 |
| 98 b = 5 |
| 99 """) |
| 100 self.assertEqual(parser.exit_counts(), { 1:1, 2:1, 5:1 }) |
| 101 parser = self.parse_source("""\ |
| 102 def foo(): |
| 103 if fooey: |
| 104 a = 3 |
| 105 else: |
| 106 a = 5 |
| 107 b = 6 |
| 108 """) |
| 109 self.assertEqual(parser.exit_counts(), { 1:1, 2:2, 3:1, 5:1, 6:1 }) |
| 110 parser = self.parse_source("""\ |
| 111 def foo(): |
| 112 if fooey: |
| 113 a = 3 |
| 114 else: # nocover |
| 115 a = 5 |
| 116 b = 6 |
| 117 """) |
| 118 self.assertEqual(parser.exit_counts(), { 1:1, 2:1, 3:1, 6:1 }) |
| 119 |
| 120 def test_indentation_error(self): |
| 121 msg = ( |
| 122 "Couldn't parse '<code>' as Python source: " |
| 123 "'unindent does not match any outer indentation level' at line 3" |
| 124 ) |
| 125 with self.assertRaisesRegex(NotPython, msg): |
| 126 _ = self.parse_source("""\ |
| 127 0 spaces |
| 128 2 |
| 129 1 |
| 130 """) |
| 131 |
| 132 def test_token_error(self): |
| 133 msg = "Couldn't parse '<code>' as Python source: 'EOF in multi-line stri
ng' at line 1" |
| 134 with self.assertRaisesRegex(NotPython, msg): |
| 135 _ = self.parse_source("""\ |
| 136 ''' |
| 137 """) |
| 138 |
| 139 |
| 140 class ParserFileTest(CoverageTest): |
| 141 """Tests for coverage.py's code parsing from files.""" |
| 142 |
| 143 def parse_file(self, filename): |
| 144 """Parse `text` as source, and return the `PythonParser` used.""" |
| 145 # pylint: disable=attribute-defined-outside-init |
| 146 parser = PythonParser(filename=filename, exclude="nocover") |
| 147 self.statements, self.excluded = parser.parse_source() |
| 148 return parser |
| 149 |
| 150 def test_line_endings(self): |
| 151 text = """\ |
| 152 # check some basic branch counting |
| 153 class Foo: |
| 154 def foo(self, a): |
| 155 if a: |
| 156 return 5 |
| 157 else: |
| 158 return 7 |
| 159 |
| 160 class Bar: |
| 161 pass |
| 162 """ |
| 163 counts = { 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1 } |
| 164 name_endings = (("unix", "\n"), ("dos", "\r\n"), ("mac", "\r")) |
| 165 for fname, newline in name_endings: |
| 166 fname = fname + ".py" |
| 167 self.make_file(fname, text, newline=newline) |
| 168 parser = self.parse_file(fname) |
| 169 self.assertEqual( |
| 170 parser.exit_counts(), |
| 171 counts, |
| 172 "Wrong for %r" % fname |
| 173 ) |
| 174 |
| 175 def test_encoding(self): |
| 176 self.make_file("encoded.py", """\ |
| 177 coverage = "\xe7\xf6v\xear\xe3g\xe9" |
| 178 """) |
| 179 parser = self.parse_file("encoded.py") |
| 180 self.assertEqual(parser.exit_counts(), {1: 1}) |
| 181 |
| 182 def test_missing_line_ending(self): |
| 183 # Test that the set of statements is the same even if a final |
| 184 # multi-line statement has no final newline. |
| 185 # https://bitbucket.org/ned/coveragepy/issue/293 |
| 186 |
| 187 self.make_file("normal.py", """\ |
| 188 out, err = subprocess.Popen( |
| 189 [sys.executable, '-c', 'pass'], |
| 190 stdout=subprocess.PIPE, |
| 191 stderr=subprocess.PIPE).communicate() |
| 192 """) |
| 193 |
| 194 self.parse_file("normal.py") |
| 195 self.assertEqual(self.statements, set([1])) |
| 196 |
| 197 self.make_file("abrupt.py", """\ |
| 198 out, err = subprocess.Popen( |
| 199 [sys.executable, '-c', 'pass'], |
| 200 stdout=subprocess.PIPE, |
| 201 stderr=subprocess.PIPE).communicate()""") # no final newline. |
| 202 |
| 203 # Double-check that some test helper wasn't being helpful. |
| 204 with open("abrupt.py") as f: |
| 205 self.assertEqual(f.read()[-1], ")") |
| 206 |
| 207 self.parse_file("abrupt.py") |
| 208 self.assertEqual(self.statements, set([1])) |
OLD | NEW |