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
|
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
import py
import os
from debugpy.common import json
from tests.patterns import some
class Target(object):
"""Describes Python code that gets run by a Runner."""
def __init__(self, filename, args=()):
if filename is not None and not isinstance(filename, py.path.local):
filename = py.path.local(filename)
self.filename = filename
self.args = args
if self.filename is None:
self.code = None
else:
with open(self.filename.strpath, "rb") as f:
self.code = f.read().decode("utf-8")
def configure(self, session):
"""Configures the session to execute this target.
This should only modify session.config, but gets access to the entire session
to retrieve information about it.
"""
raise NotImplementedError
def cli(self, env):
"""Provides the command line arguments, suitable for passing to python or
python -m debugpy, to execute this target.
Returns command line arguments as a list, e.g. ["-m", "module"].
If any environment variables are needed to properly interpret the command
line - e.g. PYTHONPATH - the implementation should send them in env.
"""
raise NotImplementedError
@property
def argslist(self):
args = self.args
if isinstance(args, str):
return [args]
else:
return list(args)
@property
def co_filename(self):
"""co_filename of code objects created at runtime from the source that this
Target describes, assuming no path mapping.
"""
assert (
self.filename is not None
), "co_filename requires Target created from filename"
return self.filename.strpath
@property
def source(self):
"""DAP "source" JSON for this Target."""
return some.dap.source(py.path.local(self.co_filename))
@property
def lines(self):
"""Same as self.filename.lines, if it is valid - e.g. for @pyfile objects."""
assert (
self.filename is not None
), "lines() requires Target created from filename"
return self.filename.lines
class Program(Target):
"""
A Python script, executed directly.
By default executes a program through its absolute path with:
python /full/path/to/foo.py
If made relative through make_relative(cwd), the cwd is set and the
launch is made relative to it.
i.e.:
Given a path such as /full/path/to/foo.py
if make_relative('/full/path') is called,
the cwd becomes `/full/path` and the launch is made as:
python to/foo.py
"""
pytest_id = "program"
def __init__(self, filename, args=()):
super().__init__(filename, args)
self._cwd = ""
def make_relative(self, cwd):
if not cwd.endswith(("/", "\\")):
cwd += os.path.sep
assert self.filename.strpath.lower().startswith(cwd.lower())
self._cwd = cwd
def _get_relative_program(self):
assert self._cwd
relative_filename = self.filename.strpath[len(self._cwd) :]
assert not relative_filename.startswith(("/", "\\"))
return relative_filename
def __repr__(self):
if self._cwd:
return f"program (relative) {json.repr(self._cwd)} / {json.repr(self._get_relative_program())}"
else:
return f"program {json.repr(self.filename.strpath)}"
def configure(self, session):
if self._cwd:
session.config["cwd"] = self._cwd
session.config["program"] = self._get_relative_program()
else:
session.config["program"] = self.filename.strpath
session.config["args"] = self.args
def cli(self, env):
if self._cwd:
cli = [self._get_relative_program()]
else:
cli = [self.filename.strpath]
cli += self.argslist
return cli
class Module(Target):
"""A Python module, executed by name: python -m foo.bar
If created from a filename, the module name is the name of the file, and the
Target will automatically add a PYTHONPATH entry.
"""
pytest_id = "module"
def __init__(self, filename=None, name=None, args=()):
assert (filename is None) ^ (name is None)
super().__init__(filename, args)
self.name = name if name is not None else self.filename.purebasename
def __repr__(self):
return f"module {self.name}"
def configure(self, session):
session.config["module"] = self.name
session.config["args"] = self.args
def cli(self, env):
if self.filename is not None:
env.prepend_to("PYTHONPATH", self.filename.dirname)
return ["-m", self.name] + self.argslist
class Code(Target):
"""A snippet of Python code: python -c "print('foo')"
If created from a filename, the code is the contents of the file.
"""
pytest_id = "code"
def __init__(self, filename=None, code=None, args=()):
assert (filename is None) ^ (code is None)
super().__init__(filename, args)
if code is not None:
self.code = code
def __repr__(self):
lines = self.code.split("\n")
return f"code: {json.repr(lines)}"
def configure(self, session):
session.config["code"] = self.code
session.config["args"] = self.args
def cli(self, env):
return ["-c", self.code] + self.argslist
@property
def co_filename(self):
return "<string>"
@property
def source(self):
"""DAP "source" JSON for this Target."""
return some.dap.source("<string>")
all_named = [Program, Module]
"""All targets that produce uniquely named code objects at runtime, and thus can
have breakpoints set in them.
"""
all_unnamed = [Code]
"""All targets that produce unnamed code objects at runtime, and thus cannot have
breakpoints set in them.
"""
all = all_named + all_unnamed
|