|
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 """Execute files of Python code.""" |
|
5 |
|
6 import marshal |
|
7 import os |
|
8 import struct |
|
9 import sys |
|
10 import types |
|
11 |
|
12 from coverage.backward import BUILTINS |
|
13 from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec |
|
14 from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module |
|
15 from coverage.phystokens import compile_unicode |
|
16 from coverage.python import get_python_source |
|
17 |
|
18 os = isolate_module(os) |
|
19 |
|
20 |
|
21 class DummyLoader(object): |
|
22 """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader. |
|
23 |
|
24 Currently only implements the .fullname attribute |
|
25 """ |
|
26 def __init__(self, fullname, *_args): |
|
27 self.fullname = fullname |
|
28 |
|
29 |
|
30 if importlib_util_find_spec: |
|
31 def find_module(modulename): |
|
32 """Find the module named `modulename`. |
|
33 |
|
34 Returns the file path of the module, and the name of the enclosing |
|
35 package. |
|
36 """ |
|
37 try: |
|
38 spec = importlib_util_find_spec(modulename) |
|
39 except ImportError as err: |
|
40 raise NoSource(str(err)) |
|
41 if not spec: |
|
42 raise NoSource("No module named %r" % (modulename,)) |
|
43 pathname = spec.origin |
|
44 packagename = spec.name |
|
45 if pathname.endswith("__init__.py") and not modulename.endswith("__init__"): |
|
46 mod_main = modulename + ".__main__" |
|
47 spec = importlib_util_find_spec(mod_main) |
|
48 if not spec: |
|
49 raise NoSource( |
|
50 "No module named %s; " |
|
51 "%r is a package and cannot be directly executed" |
|
52 % (mod_main, modulename) |
|
53 ) |
|
54 pathname = spec.origin |
|
55 packagename = spec.name |
|
56 packagename = packagename.rpartition(".")[0] |
|
57 return pathname, packagename |
|
58 else: |
|
59 def find_module(modulename): |
|
60 """Find the module named `modulename`. |
|
61 |
|
62 Returns the file path of the module, and the name of the enclosing |
|
63 package. |
|
64 """ |
|
65 openfile = None |
|
66 glo, loc = globals(), locals() |
|
67 try: |
|
68 # Search for the module - inside its parent package, if any - using |
|
69 # standard import mechanics. |
|
70 if '.' in modulename: |
|
71 packagename, name = modulename.rsplit('.', 1) |
|
72 package = __import__(packagename, glo, loc, ['__path__']) |
|
73 searchpath = package.__path__ |
|
74 else: |
|
75 packagename, name = None, modulename |
|
76 searchpath = None # "top-level search" in imp.find_module() |
|
77 openfile, pathname, _ = imp.find_module(name, searchpath) |
|
78 |
|
79 # Complain if this is a magic non-file module. |
|
80 if openfile is None and pathname is None: |
|
81 raise NoSource( |
|
82 "module does not live in a file: %r" % modulename |
|
83 ) |
|
84 |
|
85 # If `modulename` is actually a package, not a mere module, then we |
|
86 # pretend to be Python 2.7 and try running its __main__.py script. |
|
87 if openfile is None: |
|
88 packagename = modulename |
|
89 name = '__main__' |
|
90 package = __import__(packagename, glo, loc, ['__path__']) |
|
91 searchpath = package.__path__ |
|
92 openfile, pathname, _ = imp.find_module(name, searchpath) |
|
93 except ImportError as err: |
|
94 raise NoSource(str(err)) |
|
95 finally: |
|
96 if openfile: |
|
97 openfile.close() |
|
98 |
|
99 return pathname, packagename |
|
100 |
|
101 |
|
102 def run_python_module(modulename, args): |
|
103 """Run a Python module, as though with ``python -m name args...``. |
|
104 |
|
105 `modulename` is the name of the module, possibly a dot-separated name. |
|
106 `args` is the argument array to present as sys.argv, including the first |
|
107 element naming the module being executed. |
|
108 |
|
109 """ |
|
110 pathname, packagename = find_module(modulename) |
|
111 |
|
112 pathname = os.path.abspath(pathname) |
|
113 args[0] = pathname |
|
114 # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It |
|
115 # used to be an empty string (meaning the current directory). It changed |
|
116 # to be the actual path to the current directory, so that os.chdir wouldn't |
|
117 # affect the outcome. |
|
118 if sys.version_info >= (3, 7, 0, 'beta', 3): |
|
119 path0 = os.getcwd() |
|
120 else: |
|
121 path0 = "" |
|
122 run_python_file(pathname, args, package=packagename, modulename=modulename, path0=path0) |
|
123 |
|
124 |
|
125 def run_python_file(filename, args, package=None, modulename=None, path0=None): |
|
126 """Run a Python file as if it were the main program on the command line. |
|
127 |
|
128 `filename` is the path to the file to execute, it need not be a .py file. |
|
129 `args` is the argument array to present as sys.argv, including the first |
|
130 element naming the file being executed. `package` is the name of the |
|
131 enclosing package, if any. |
|
132 |
|
133 `modulename` is the name of the module the file was run as. |
|
134 |
|
135 `path0` is the value to put into sys.path[0]. If it's None, then this |
|
136 function will decide on a value. |
|
137 |
|
138 """ |
|
139 if modulename is None and sys.version_info >= (3, 3): |
|
140 modulename = '__main__' |
|
141 |
|
142 # Create a module to serve as __main__ |
|
143 old_main_mod = sys.modules['__main__'] |
|
144 main_mod = types.ModuleType('__main__') |
|
145 sys.modules['__main__'] = main_mod |
|
146 main_mod.__file__ = filename |
|
147 if package: |
|
148 main_mod.__package__ = package |
|
149 if modulename: |
|
150 main_mod.__loader__ = DummyLoader(modulename) |
|
151 |
|
152 main_mod.__builtins__ = BUILTINS |
|
153 |
|
154 # Set sys.argv properly. |
|
155 old_argv = sys.argv |
|
156 sys.argv = args |
|
157 |
|
158 if os.path.isdir(filename): |
|
159 # Running a directory means running the __main__.py file in that |
|
160 # directory. |
|
161 my_path0 = filename |
|
162 |
|
163 for ext in [".py", ".pyc", ".pyo"]: |
|
164 try_filename = os.path.join(filename, "__main__" + ext) |
|
165 if os.path.exists(try_filename): |
|
166 filename = try_filename |
|
167 break |
|
168 else: |
|
169 raise NoSource("Can't find '__main__' module in '%s'" % filename) |
|
170 else: |
|
171 my_path0 = os.path.abspath(os.path.dirname(filename)) |
|
172 |
|
173 # Set sys.path correctly. |
|
174 old_path0 = sys.path[0] |
|
175 sys.path[0] = path0 if path0 is not None else my_path0 |
|
176 |
|
177 try: |
|
178 try: |
|
179 # Make a code object somehow. |
|
180 if filename.endswith((".pyc", ".pyo")): |
|
181 code = make_code_from_pyc(filename) |
|
182 else: |
|
183 code = make_code_from_py(filename) |
|
184 except CoverageException: |
|
185 raise |
|
186 except Exception as exc: |
|
187 msg = "Couldn't run {filename!r} as Python code: {exc.__class__.__name__}: {exc}" |
|
188 raise CoverageException(msg.format(filename=filename, exc=exc)) |
|
189 |
|
190 # Execute the code object. |
|
191 try: |
|
192 exec(code, main_mod.__dict__) |
|
193 except SystemExit: |
|
194 # The user called sys.exit(). Just pass it along to the upper |
|
195 # layers, where it will be handled. |
|
196 raise |
|
197 except Exception: |
|
198 # Something went wrong while executing the user code. |
|
199 # Get the exc_info, and pack them into an exception that we can |
|
200 # throw up to the outer loop. We peel one layer off the traceback |
|
201 # so that the coverage.py code doesn't appear in the final printed |
|
202 # traceback. |
|
203 typ, err, tb = sys.exc_info() |
|
204 |
|
205 # PyPy3 weirdness. If I don't access __context__, then somehow it |
|
206 # is non-None when the exception is reported at the upper layer, |
|
207 # and a nested exception is shown to the user. This getattr fixes |
|
208 # it somehow? https://bitbucket.org/pypy/pypy/issue/1903 |
|
209 getattr(err, '__context__', None) |
|
210 |
|
211 # Call the excepthook. |
|
212 try: |
|
213 if hasattr(err, "__traceback__"): |
|
214 err.__traceback__ = err.__traceback__.tb_next |
|
215 sys.excepthook(typ, err, tb.tb_next) |
|
216 except SystemExit: |
|
217 raise |
|
218 except Exception: |
|
219 # Getting the output right in the case of excepthook |
|
220 # shenanigans is kind of involved. |
|
221 sys.stderr.write("Error in sys.excepthook:\n") |
|
222 typ2, err2, tb2 = sys.exc_info() |
|
223 err2.__suppress_context__ = True |
|
224 if hasattr(err2, "__traceback__"): |
|
225 err2.__traceback__ = err2.__traceback__.tb_next |
|
226 sys.__excepthook__(typ2, err2, tb2.tb_next) |
|
227 sys.stderr.write("\nOriginal exception was:\n") |
|
228 raise ExceptionDuringRun(typ, err, tb.tb_next) |
|
229 else: |
|
230 sys.exit(1) |
|
231 |
|
232 finally: |
|
233 # Restore the old __main__, argv, and path. |
|
234 sys.modules['__main__'] = old_main_mod |
|
235 sys.argv = old_argv |
|
236 sys.path[0] = old_path0 |
|
237 |
|
238 |
|
239 def make_code_from_py(filename): |
|
240 """Get source from `filename` and make a code object of it.""" |
|
241 # Open the source file. |
|
242 try: |
|
243 source = get_python_source(filename) |
|
244 except (IOError, NoSource): |
|
245 raise NoSource("No file to run: '%s'" % filename) |
|
246 |
|
247 code = compile_unicode(source, filename, "exec") |
|
248 return code |
|
249 |
|
250 |
|
251 def make_code_from_pyc(filename): |
|
252 """Get a code object from a .pyc file.""" |
|
253 try: |
|
254 fpyc = open(filename, "rb") |
|
255 except IOError: |
|
256 raise NoCode("No file to run: '%s'" % filename) |
|
257 |
|
258 with fpyc: |
|
259 # First four bytes are a version-specific magic number. It has to |
|
260 # match or we won't run the file. |
|
261 magic = fpyc.read(4) |
|
262 if magic != PYC_MAGIC_NUMBER: |
|
263 raise NoCode("Bad magic number in .pyc file") |
|
264 |
|
265 date_based = True |
|
266 if sys.version_info >= (3, 7, 0, 'alpha', 4): |
|
267 flags = struct.unpack('<L', fpyc.read(4))[0] |
|
268 hash_based = flags & 0x01 |
|
269 if hash_based: |
|
270 fpyc.read(8) # Skip the hash. |
|
271 date_based = False |
|
272 if date_based: |
|
273 # Skip the junk in the header that we don't need. |
|
274 fpyc.read(4) # Skip the moddate. |
|
275 if sys.version_info >= (3, 3): |
|
276 # 3.3 added another long to the header (size), skip it. |
|
277 fpyc.read(4) |
|
278 |
|
279 # The rest of the file is the code object we want. |
|
280 code = marshal.load(fpyc) |
|
281 |
|
282 return code |