1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
|
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""Tests for coverage.execfile"""
import compileall
import json
import os
import os.path
import pathlib
import py_compile
import re
import sys
import pytest
from coverage import env
from coverage.exceptions import NoCode, NoSource, _ExceptionDuringRun
from coverage.execfile import run_python_file, run_python_module
from coverage.files import python_reported_file
from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin
TRY_EXECFILE = os.path.join(TESTS_DIR, "modules/process_test/try_execfile.py")
class RunFileTest(CoverageTest):
"""Test cases for `run_python_file`."""
@pytest.fixture(autouse=True)
def clean_up(self):
"""These tests all run in-process. Clean up global changes."""
yield
sys.excepthook = sys.__excepthook__
def test_run_python_file(self):
run_python_file([TRY_EXECFILE, "arg1", "arg2"])
mod_globs = json.loads(self.stdout())
# The file should think it is __main__
assert mod_globs['__name__'] == "__main__"
# It should seem to come from a file named try_execfile.py
dunder_file = os.path.basename(mod_globs['__file__'])
assert dunder_file == "try_execfile.py"
# It should have its correct module data.
assert mod_globs['__doc__'].splitlines()[0] == "Test file for run_python_file."
assert mod_globs['DATA'] == "xyzzy"
assert mod_globs['FN_VAL'] == "my_fn('fooey')"
# It must be self-importable as __main__.
assert mod_globs['__main__.DATA'] == "xyzzy"
# Argv should have the proper values.
assert mod_globs['argv0'] == TRY_EXECFILE
assert mod_globs['argv1-n'] == ["arg1", "arg2"]
# __builtins__ should have the right values, like open().
assert mod_globs['__builtins__.has_open'] is True
def test_no_extra_file(self):
# Make sure that running a file doesn't create an extra compiled file.
self.make_file("xxx", """\
desc = "a non-.py file!"
""")
assert os.listdir(".") == ["xxx"]
run_python_file(["xxx"])
assert os.listdir(".") == ["xxx"]
def test_universal_newlines(self):
# Make sure we can read any sort of line ending.
pylines = """# try newlines|print('Hello, world!')|""".split('|')
for nl in ('\n', '\r\n', '\r'):
with open('nl.py', 'wb') as fpy:
fpy.write(nl.join(pylines).encode('utf-8'))
run_python_file(['nl.py'])
assert self.stdout() == "Hello, world!\n"*3
def test_missing_final_newline(self):
# Make sure we can deal with a Python file with no final newline.
self.make_file("abrupt.py", """\
if 1:
a = 1
print(f"a is {a!r}")
#""")
with open("abrupt.py") as f:
abrupt = f.read()
assert abrupt[-1] == '#'
run_python_file(["abrupt.py"])
assert self.stdout() == "a is 1\n"
def test_no_such_file(self):
path = python_reported_file('xyzzy.py')
msg = re.escape(f"No file to run: '{path}'")
with pytest.raises(NoSource, match=msg):
run_python_file(["xyzzy.py"])
def test_directory_with_main(self):
self.make_file("with_main/__main__.py", """\
print("I am __main__")
""")
run_python_file(["with_main"])
assert self.stdout() == "I am __main__\n"
def test_directory_without_main(self):
self.make_file("without_main/__init__.py", "")
with pytest.raises(NoSource, match="Can't find '__main__' module in 'without_main'"):
run_python_file(["without_main"])
def test_code_throws(self):
self.make_file("throw.py", """\
class MyException(Exception):
pass
def f1():
print("about to raise..")
raise MyException("hey!")
def f2():
f1()
f2()
""")
with pytest.raises(SystemExit) as exc_info:
run_python_file(["throw.py"])
assert exc_info.value.args == (1,)
assert self.stdout() == "about to raise..\n"
assert self.stderr() == ""
def test_code_exits(self):
self.make_file("exit.py", """\
import sys
def f1():
print("about to exit..")
sys.exit(17)
def f2():
f1()
f2()
""")
with pytest.raises(SystemExit) as exc_info:
run_python_file(["exit.py"])
assert exc_info.value.args == (17,)
assert self.stdout() == "about to exit..\n"
assert self.stderr() == ""
def test_excepthook_exit(self):
self.make_file("excepthook_exit.py", """\
import sys
def excepthook(*args):
print('in excepthook')
sys.exit(0)
sys.excepthook = excepthook
raise RuntimeError('Error Outside')
""")
with pytest.raises(SystemExit):
run_python_file(["excepthook_exit.py"])
cov_out = self.stdout()
assert cov_out == "in excepthook\n"
def test_excepthook_throw(self):
self.make_file("excepthook_throw.py", """\
import sys
def excepthook(*args):
# Write this message to stderr so that we don't have to deal
# with interleaved stdout/stderr comparisons in the assertions
# in the test.
sys.stderr.write('in excepthook\\n')
raise RuntimeError('Error Inside')
sys.excepthook = excepthook
raise RuntimeError('Error Outside')
""")
with pytest.raises(_ExceptionDuringRun) as exc_info:
run_python_file(["excepthook_throw.py"])
# The _ExceptionDuringRun exception has the RuntimeError as its argument.
assert exc_info.value.args[1].args[0] == "Error Outside"
stderr = self.stderr()
assert "in excepthook\n" in stderr
assert "Error in sys.excepthook:\n" in stderr
assert "RuntimeError: Error Inside" in stderr
class RunPycFileTest(CoverageTest):
"""Test cases for `run_python_file`."""
def make_pyc(self, **kwargs):
"""Create a .pyc file, and return the path to it."""
if env.JYTHON:
pytest.skip("Can't make .pyc files on Jython")
self.make_file("compiled.py", """\
def doit():
print("I am here!")
doit()
""")
compileall.compile_dir(".", quiet=True, **kwargs)
os.remove("compiled.py")
# Find the .pyc file!
return str(next(pathlib.Path(".").rglob("compiled*.pyc")))
def test_running_pyc(self):
pycfile = self.make_pyc()
run_python_file([pycfile])
assert self.stdout() == "I am here!\n"
def test_running_pyo(self):
pycfile = self.make_pyc()
pyofile = re.sub(r"[.]pyc$", ".pyo", pycfile)
assert pycfile != pyofile
os.rename(pycfile, pyofile)
run_python_file([pyofile])
assert self.stdout() == "I am here!\n"
def test_running_pyc_from_wrong_python(self):
pycfile = self.make_pyc()
# Jam Python 2.1 magic number into the .pyc file.
with open(pycfile, "r+b") as fpyc:
fpyc.seek(0)
fpyc.write(bytes([0x2a, 0xeb, 0x0d, 0x0a]))
with pytest.raises(NoCode, match="Bad magic number in .pyc file"):
run_python_file([pycfile])
# In some environments, the pycfile persists and pollutes another test.
os.remove(pycfile)
def test_running_hashed_pyc(self):
pycfile = self.make_pyc(invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH)
run_python_file([pycfile])
assert self.stdout() == "I am here!\n"
def test_no_such_pyc_file(self):
path = python_reported_file('xyzzy.pyc')
msg = re.escape(f"No file to run: '{path}'")
with pytest.raises(NoCode, match=msg):
run_python_file(["xyzzy.pyc"])
def test_running_py_from_binary(self):
# Use make_file to get the bookkeeping. Ideally, it would
# be able to write binary files.
bf = self.make_file("binary")
with open(bf, "wb") as f:
f.write(b'\x7fELF\x02\x01\x01\x00\x00\x00')
path = python_reported_file('binary')
msg = (
re.escape(f"Couldn't run '{path}' as Python code: ") +
r"(TypeError|ValueError): source code string cannot contain null bytes"
)
with pytest.raises(Exception, match=msg):
run_python_file([bf])
class RunModuleTest(UsingModulesMixin, CoverageTest):
"""Test run_python_module."""
run_in_temp_dir = False
def test_runmod1(self):
run_python_module(["runmod1", "hello"])
out, err = self.stdouterr()
assert out == "runmod1: passed hello\n"
assert err == ""
def test_runmod2(self):
run_python_module(["pkg1.runmod2", "hello"])
out, err = self.stdouterr()
assert out == "pkg1.__init__: pkg1\nrunmod2: passed hello\n"
assert err == ""
def test_runmod3(self):
run_python_module(["pkg1.sub.runmod3", "hello"])
out, err = self.stdouterr()
assert out == "pkg1.__init__: pkg1\nrunmod3: passed hello\n"
assert err == ""
def test_pkg1_main(self):
run_python_module(["pkg1", "hello"])
out, err = self.stdouterr()
assert out == "pkg1.__init__: pkg1\npkg1.__main__: passed hello\n"
assert err == ""
def test_pkg1_sub_main(self):
run_python_module(["pkg1.sub", "hello"])
out, err = self.stdouterr()
assert out == "pkg1.__init__: pkg1\npkg1.sub.__main__: passed hello\n"
assert err == ""
def test_pkg1_init(self):
run_python_module(["pkg1.__init__", "wut?"])
out, err = self.stdouterr()
assert out == "pkg1.__init__: pkg1\npkg1.__init__: __main__\n"
assert err == ""
def test_no_such_module(self):
with pytest.raises(NoSource, match="No module named '?i_dont_exist'?"):
run_python_module(["i_dont_exist"])
with pytest.raises(NoSource, match="No module named '?i'?"):
run_python_module(["i.dont_exist"])
with pytest.raises(NoSource, match="No module named '?i'?"):
run_python_module(["i.dont.exist"])
def test_no_main(self):
with pytest.raises(NoSource):
run_python_module(["pkg2", "hi"])
|