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. |
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. |