eric6/DebugClients/Python/coverage/execfile.py

changeset 7427
362cd1b6f81a
parent 6942
2602857055c5
equal deleted inserted replaced
7426:dc171b1d8261 7427:362cd1b6f81a
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 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 2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3 3
4 """Execute files of Python code.""" 4 """Execute files of Python code."""
5 5
6 import inspect
6 import marshal 7 import marshal
7 import os 8 import os
8 import struct 9 import struct
9 import sys 10 import sys
10 import types 11 import types
11 12
13 from coverage import env
12 from coverage.backward import BUILTINS 14 from coverage.backward import BUILTINS
13 from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec 15 from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec
16 from coverage.files import canonical_filename, python_reported_file
14 from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module 17 from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module
15 from coverage.phystokens import compile_unicode 18 from coverage.phystokens import compile_unicode
16 from coverage.python import get_python_source 19 from coverage.python import get_python_source
17 20
18 os = isolate_module(os) 21 os = isolate_module(os)
29 32
30 if importlib_util_find_spec: 33 if importlib_util_find_spec:
31 def find_module(modulename): 34 def find_module(modulename):
32 """Find the module named `modulename`. 35 """Find the module named `modulename`.
33 36
34 Returns the file path of the module, and the name of the enclosing 37 Returns the file path of the module, the name of the enclosing
35 package. 38 package, and the spec.
36 """ 39 """
37 try: 40 try:
38 spec = importlib_util_find_spec(modulename) 41 spec = importlib_util_find_spec(modulename)
39 except ImportError as err: 42 except ImportError as err:
40 raise NoSource(str(err)) 43 raise NoSource(str(err))
41 if not spec: 44 if not spec:
42 raise NoSource("No module named %r" % (modulename,)) 45 raise NoSource("No module named %r" % (modulename,))
43 pathname = spec.origin 46 pathname = spec.origin
44 packagename = spec.name 47 packagename = spec.name
45 if pathname.endswith("__init__.py") and not modulename.endswith("__init__"): 48 if spec.submodule_search_locations:
46 mod_main = modulename + ".__main__" 49 mod_main = modulename + ".__main__"
47 spec = importlib_util_find_spec(mod_main) 50 spec = importlib_util_find_spec(mod_main)
48 if not spec: 51 if not spec:
49 raise NoSource( 52 raise NoSource(
50 "No module named %s; " 53 "No module named %s; "
52 % (mod_main, modulename) 55 % (mod_main, modulename)
53 ) 56 )
54 pathname = spec.origin 57 pathname = spec.origin
55 packagename = spec.name 58 packagename = spec.name
56 packagename = packagename.rpartition(".")[0] 59 packagename = packagename.rpartition(".")[0]
57 return pathname, packagename 60 return pathname, packagename, spec
58 else: 61 else:
59 def find_module(modulename): 62 def find_module(modulename):
60 """Find the module named `modulename`. 63 """Find the module named `modulename`.
61 64
62 Returns the file path of the module, and the name of the enclosing 65 Returns the file path of the module, the name of the enclosing
63 package. 66 package, and None (where a spec would have been).
64 """ 67 """
65 openfile = None 68 openfile = None
66 glo, loc = globals(), locals() 69 glo, loc = globals(), locals()
67 try: 70 try:
68 # Search for the module - inside its parent package, if any - using 71 # Search for the module - inside its parent package, if any - using
94 raise NoSource(str(err)) 97 raise NoSource(str(err))
95 finally: 98 finally:
96 if openfile: 99 if openfile:
97 openfile.close() 100 openfile.close()
98 101
99 return pathname, packagename 102 return pathname, packagename, None
100 103
101 104
102 def run_python_module(modulename, args): 105 class PyRunner(object):
103 """Run a Python module, as though with ``python -m name args...``. 106 """Multi-stage execution of Python code.
104 107
105 `modulename` is the name of the module, possibly a dot-separated name. 108 This is meant to emulate real Python execution as closely as possible.
106 `args` is the argument array to present as sys.argv, including the first
107 element naming the module being executed.
108 109
109 """ 110 """
110 pathname, packagename = find_module(modulename) 111 def __init__(self, args, as_module=False):
111 112 self.args = args
112 pathname = os.path.abspath(pathname) 113 self.as_module = as_module
113 args[0] = pathname 114
114 # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It 115 self.arg0 = args[0]
115 # used to be an empty string (meaning the current directory). It changed 116 self.package = self.modulename = self.pathname = self.loader = self.spec = None
116 # to be the actual path to the current directory, so that os.chdir wouldn't 117
117 # affect the outcome. 118 def prepare(self):
118 if sys.version_info >= (3, 7, 0, 'beta', 3): 119 """Set sys.path properly.
119 path0 = os.getcwd() 120
120 else: 121 This needs to happen before any importing, and without importing anything.
121 path0 = "" 122 """
122 run_python_file(pathname, args, package=packagename, modulename=modulename, path0=path0) 123 if self.as_module:
123 124 if env.PYBEHAVIOR.actual_syspath0_dash_m:
124 125 path0 = os.getcwd()
125 def run_python_file(filename, args, package=None, modulename=None, path0=None): 126 else:
126 """Run a Python file as if it were the main program on the command line. 127 path0 = ""
127 128 elif os.path.isdir(self.arg0):
128 `filename` is the path to the file to execute, it need not be a .py file. 129 # Running a directory means running the __main__.py file in that
129 `args` is the argument array to present as sys.argv, including the first 130 # directory.
130 element naming the file being executed. `package` is the name of the 131 path0 = self.arg0
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: 132 else:
169 raise NoSource("Can't find '__main__' module in '%s'" % filename) 133 path0 = os.path.abspath(os.path.dirname(self.arg0))
170 else: 134
171 my_path0 = os.path.abspath(os.path.dirname(filename)) 135 if os.path.isdir(sys.path[0]):
172 136 # sys.path fakery. If we are being run as a command, then sys.path[0]
173 # Set sys.path correctly. 137 # is the directory of the "coverage" script. If this is so, replace
174 old_path0 = sys.path[0] 138 # sys.path[0] with the directory of the file we're running, or the
175 sys.path[0] = path0 if path0 is not None else my_path0 139 # current directory when running modules. If it isn't so, then we
176 140 # don't know what's going on, and just leave it alone.
177 try: 141 top_file = inspect.stack()[-1][0].f_code.co_filename
142 sys_path_0_abs = os.path.abspath(sys.path[0])
143 top_file_dir_abs = os.path.abspath(os.path.dirname(top_file))
144 sys_path_0_abs = canonical_filename(sys_path_0_abs)
145 top_file_dir_abs = canonical_filename(top_file_dir_abs)
146 if sys_path_0_abs != top_file_dir_abs:
147 path0 = None
148
149 else:
150 # sys.path[0] is a file. Is the next entry the directory containing
151 # that file?
152 if sys.path[1] == os.path.dirname(sys.path[0]):
153 # Can it be right to always remove that?
154 del sys.path[1]
155
156 if path0 is not None:
157 sys.path[0] = python_reported_file(path0)
158
159 def _prepare2(self):
160 """Do more preparation to run Python code.
161
162 Includes finding the module to run and adjusting sys.argv[0].
163 This method is allowed to import code.
164
165 """
166 if self.as_module:
167 self.modulename = self.arg0
168 pathname, self.package, self.spec = find_module(self.modulename)
169 if self.spec is not None:
170 self.modulename = self.spec.name
171 self.loader = DummyLoader(self.modulename)
172 self.pathname = os.path.abspath(pathname)
173 self.args[0] = self.arg0 = self.pathname
174 elif os.path.isdir(self.arg0):
175 # Running a directory means running the __main__.py file in that
176 # directory.
177 for ext in [".py", ".pyc", ".pyo"]:
178 try_filename = os.path.join(self.arg0, "__main__" + ext)
179 if os.path.exists(try_filename):
180 self.arg0 = try_filename
181 break
182 else:
183 raise NoSource("Can't find '__main__' module in '%s'" % self.arg0)
184
185 if env.PY2:
186 self.arg0 = os.path.abspath(self.arg0)
187
188 # Make a spec. I don't know if this is the right way to do it.
189 try:
190 import importlib.machinery
191 except ImportError:
192 pass
193 else:
194 try_filename = python_reported_file(try_filename)
195 self.spec = importlib.machinery.ModuleSpec("__main__", None, origin=try_filename)
196 self.spec.has_location = True
197 self.package = ""
198 self.loader = DummyLoader("__main__")
199 else:
200 if env.PY3:
201 self.loader = DummyLoader("__main__")
202
203 self.arg0 = python_reported_file(self.arg0)
204
205 def run(self):
206 """Run the Python code!"""
207
208 self._prepare2()
209
210 # Create a module to serve as __main__
211 main_mod = types.ModuleType('__main__')
212
213 from_pyc = self.arg0.endswith((".pyc", ".pyo"))
214 main_mod.__file__ = self.arg0
215 if from_pyc:
216 main_mod.__file__ = main_mod.__file__[:-1]
217 if self.package is not None:
218 main_mod.__package__ = self.package
219 main_mod.__loader__ = self.loader
220 if self.spec is not None:
221 main_mod.__spec__ = self.spec
222
223 main_mod.__builtins__ = BUILTINS
224
225 sys.modules['__main__'] = main_mod
226
227 # Set sys.argv properly.
228 sys.argv = self.args
229
178 try: 230 try:
179 # Make a code object somehow. 231 # Make a code object somehow.
180 if filename.endswith((".pyc", ".pyo")): 232 if from_pyc:
181 code = make_code_from_pyc(filename) 233 code = make_code_from_pyc(self.arg0)
182 else: 234 else:
183 code = make_code_from_py(filename) 235 code = make_code_from_py(self.arg0)
184 except CoverageException: 236 except CoverageException:
185 raise 237 raise
186 except Exception as exc: 238 except Exception as exc:
187 msg = "Couldn't run {filename!r} as Python code: {exc.__class__.__name__}: {exc}" 239 msg = "Couldn't run '{filename}' as Python code: {exc.__class__.__name__}: {exc}"
188 raise CoverageException(msg.format(filename=filename, exc=exc)) 240 raise CoverageException(msg.format(filename=self.arg0, exc=exc))
189 241
190 # Execute the code object. 242 # Execute the code object.
243 # Return to the original directory in case the test code exits in
244 # a non-existent directory.
245 cwd = os.getcwd()
191 try: 246 try:
192 exec(code, main_mod.__dict__) 247 exec(code, main_mod.__dict__)
193 except SystemExit: 248 except SystemExit: # pylint: disable=try-except-raise
194 # The user called sys.exit(). Just pass it along to the upper 249 # The user called sys.exit(). Just pass it along to the upper
195 # layers, where it will be handled. 250 # layers, where it will be handled.
196 raise 251 raise
197 except Exception: 252 except Exception:
198 # Something went wrong while executing the user code. 253 # Something went wrong while executing the user code.
211 # Call the excepthook. 266 # Call the excepthook.
212 try: 267 try:
213 if hasattr(err, "__traceback__"): 268 if hasattr(err, "__traceback__"):
214 err.__traceback__ = err.__traceback__.tb_next 269 err.__traceback__ = err.__traceback__.tb_next
215 sys.excepthook(typ, err, tb.tb_next) 270 sys.excepthook(typ, err, tb.tb_next)
216 except SystemExit: 271 except SystemExit: # pylint: disable=try-except-raise
217 raise 272 raise
218 except Exception: 273 except Exception:
219 # Getting the output right in the case of excepthook 274 # Getting the output right in the case of excepthook
220 # shenanigans is kind of involved. 275 # shenanigans is kind of involved.
221 sys.stderr.write("Error in sys.excepthook:\n") 276 sys.stderr.write("Error in sys.excepthook:\n")
226 sys.__excepthook__(typ2, err2, tb2.tb_next) 281 sys.__excepthook__(typ2, err2, tb2.tb_next)
227 sys.stderr.write("\nOriginal exception was:\n") 282 sys.stderr.write("\nOriginal exception was:\n")
228 raise ExceptionDuringRun(typ, err, tb.tb_next) 283 raise ExceptionDuringRun(typ, err, tb.tb_next)
229 else: 284 else:
230 sys.exit(1) 285 sys.exit(1)
231 286 finally:
232 finally: 287 os.chdir(cwd)
233 # Restore the old __main__, argv, and path. 288
234 sys.modules['__main__'] = old_main_mod 289
235 sys.argv = old_argv 290 def run_python_module(args):
236 sys.path[0] = old_path0 291 """Run a Python module, as though with ``python -m name args...``.
292
293 `args` is the argument array to present as sys.argv, including the first
294 element naming the module being executed.
295
296 This is a helper for tests, to encapsulate how to use PyRunner.
297
298 """
299 runner = PyRunner(args, as_module=True)
300 runner.prepare()
301 runner.run()
302
303
304 def run_python_file(args):
305 """Run a Python file as if it were the main program on the command line.
306
307 `args` is the argument array to present as sys.argv, including the first
308 element naming the file being executed. `package` is the name of the
309 enclosing package, if any.
310
311 This is a helper for tests, to encapsulate how to use PyRunner.
312
313 """
314 runner = PyRunner(args, as_module=False)
315 runner.prepare()
316 runner.run()
237 317
238 318
239 def make_code_from_py(filename): 319 def make_code_from_py(filename):
240 """Get source from `filename` and make a code object of it.""" 320 """Get source from `filename` and make a code object of it."""
241 # Open the source file. 321 # Open the source file.
258 with fpyc: 338 with fpyc:
259 # First four bytes are a version-specific magic number. It has to 339 # First four bytes are a version-specific magic number. It has to
260 # match or we won't run the file. 340 # match or we won't run the file.
261 magic = fpyc.read(4) 341 magic = fpyc.read(4)
262 if magic != PYC_MAGIC_NUMBER: 342 if magic != PYC_MAGIC_NUMBER:
263 raise NoCode("Bad magic number in .pyc file") 343 raise NoCode("Bad magic number in .pyc file: {} != {}".format(magic, PYC_MAGIC_NUMBER))
264 344
265 date_based = True 345 date_based = True
266 if sys.version_info >= (3, 7, 0, 'alpha', 4): 346 if env.PYBEHAVIOR.hashed_pyc_pep552:
267 flags = struct.unpack('<L', fpyc.read(4))[0] 347 flags = struct.unpack('<L', fpyc.read(4))[0]
268 hash_based = flags & 0x01 348 hash_based = flags & 0x01
269 if hash_based: 349 if hash_based:
270 fpyc.read(8) # Skip the hash. 350 fpyc.read(8) # Skip the hash.
271 date_based = False 351 date_based = False
272 if date_based: 352 if date_based:
273 # Skip the junk in the header that we don't need. 353 # Skip the junk in the header that we don't need.
274 fpyc.read(4) # Skip the moddate. 354 fpyc.read(4) # Skip the moddate.
275 if sys.version_info >= (3, 3): 355 if env.PYBEHAVIOR.size_in_pyc:
276 # 3.3 added another long to the header (size), skip it. 356 # 3.3 added another long to the header (size), skip it.
277 fpyc.read(4) 357 fpyc.read(4)
278 358
279 # The rest of the file is the code object we want. 359 # The rest of the file is the code object we want.
280 code = marshal.load(fpyc) 360 code = marshal.load(fpyc)

eric ide

mercurial