Skip to content

Commit 6dac644

Browse files
authored
Group internal resource lists and reorganize xprocess into a package (#85)
* group resource lists into single dict * add XProcessResources class * update test * add destructor and force_clean_up * turn xprocess into package * remove __version__ * add changelog
1 parent dfd4b3c commit 6dac644

File tree

6 files changed

+91
-42
lines changed

6 files changed

+91
-42
lines changed

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
0.19.0 (UNRELEASED)
2+
-------------------
3+
4+
- reorganize internals. ``pytest-xprocess`` is now a package and all resources
5+
used by running processes are kept as instances of :class:``XProcessResources``.
6+
7+
18
0.18.1 (2021-07-27)
29
-------------------
310

setup.cfg

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,23 @@ classifiers=
2323
Topic :: Utilities
2424

2525
[options]
26+
packages = find:
2627
setup_requires=
2728
setuptools_scm
28-
py_modules=
29-
pytest_xprocess
30-
xprocess
3129
python_requires = >= 3.5
3230

31+
[options.packages.find]
32+
exclude = docs, tests
33+
3334
[options.entry_points]
3435
pytest11 =
35-
xprocess = pytest_xprocess
36+
xprocess = xprocess.pytest_xprocess
3637

3738
[coverage:run]
3839
branch = true
3940
include =
40-
xprocess.py
41-
pytest_xprocess.py
41+
xprocess/xprocess.py
42+
xprocess/pytest_xprocess.py
4243

4344
[flake8]
4445
# B = bugbear

tests/test_process_initialization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class Starter(ProcessStarter):
107107
xprocess.ensure(proc_name, Starter)
108108

109109
info = xprocess.getinfo(proc_name)
110-
proc = xprocess._popen_instances[-1]
110+
proc = xprocess.resources[-1].popen
111111

112112
if sys.version_info < (3, 7):
113113
text_mode = proc.universal_newlines

xprocess/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from .xprocess import ProcessStarter
2+
from .xprocess import XProcess
3+
from .xprocess import XProcessInfo
4+
from .xprocess import XProcessResources
5+
6+
__all__ = [
7+
"ProcessStarter",
8+
"XProcess",
9+
"XProcessResources",
10+
"XProcessInfo",
11+
]

pytest_xprocess.py renamed to xprocess/pytest_xprocess.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,6 @@ def pytest_runtest_makereport(item, call):
6060

6161

6262
def pytest_unconfigure(config):
63-
try:
64-
xprocess = config._xprocess
65-
except AttributeError:
66-
# xprocess fixture was not used
67-
pass
68-
else:
69-
xprocess._clean_up_resources()
70-
7163
print(
7264
"pytest-xprocess reminder::Be sure to terminate the started process by running "
7365
"'pytest --xkill' if you have not explicitly done so in your fixture with "
@@ -89,7 +81,7 @@ def pytest_configure(self, config):
8981
self.config = config
9082

9183
def info_objects(self):
92-
return self.config._xprocess._info_objects
84+
return [xrsc.info for xrsc in self.config._xprocess.resources]
9385

9486
def interruption_clean_up(self):
9587
try:
@@ -100,7 +92,7 @@ def interruption_clean_up(self):
10092
for info, terminate_on_interrupt in self.info_objects():
10193
if terminate_on_interrupt:
10294
info.terminate()
103-
xprocess._clean_up_resources()
95+
xprocess._force_clean_up()
10496

10597
def pytest_keyboard_interrupt(self, excinfo):
10698
self.interruption_clean_up()

xprocess.py renamed to xprocess/xprocess.py

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,47 @@ def isrunning(self, ignore_zombies=True):
120120
)
121121

122122

123+
class XProcessResources:
124+
"""Resources used by a running process.
125+
Each time XProcess.ensure is called a single XProcessResources
126+
instance will be created and all resources used by the started
127+
process will be held by it. Namely: file handle, XProcessInfo
128+
and Popen instance.
129+
"""
130+
131+
def __init__(self, timeout):
132+
self.timeout = timeout
133+
# handle to the process logfile
134+
self.fhandle = None
135+
# XProcessInfo holding information on XProcess instance
136+
self.info = None
137+
# Each XProcess will have a related Popen instance
138+
# used for process management through python's
139+
# subprocess API
140+
self.popen = None
141+
142+
def __del__(self):
143+
self.release()
144+
145+
def __repr__(self):
146+
return "<XProcessResources {}, {}, {}>".format(
147+
self.fhandle, self.info, self.popen
148+
)
149+
150+
def release(self):
151+
# file handles should always be closed
152+
# in order to avoid ResourceWarnings
153+
self.fhandle.close()
154+
155+
# We should wait on procs exit status if
156+
# termination signal has been issued
157+
try:
158+
if self.info[0]._termination_signal:
159+
self.popen.wait(self.timeout)
160+
except TypeError:
161+
pass
162+
163+
123164
class XProcess:
124165
"""Main xprocess class. Represents a running process instance for which
125166
a set of actions is offered, such as process startup, command line actions
@@ -130,11 +171,9 @@ def __init__(self, config, rootdir, log=None, proc_wait_timeout=60):
130171
self.rootdir = rootdir
131172
self.proc_wait_timeout = proc_wait_timeout
132173

133-
# these will be used to keep all necessary
134-
# references for proper cleanup before exiting
135-
self._info_objects = []
136-
self._file_handles = []
137-
self._popen_instances = []
174+
# used to keep all necessary references
175+
# for proper cleanup before exiting
176+
self.resources = []
138177

139178
class Log:
140179
def debug(self, msg, *args):
@@ -175,6 +214,8 @@ def ensure(self, name, preparefunc, restart=False):
175214

176215
from subprocess import Popen, STDOUT
177216

217+
xresource = XProcessResources(self.proc_wait_timeout)
218+
178219
info = self.getinfo(name)
179220
if not restart and not info.isrunning():
180221
restart = True
@@ -213,22 +254,29 @@ def ensure(self, name, preparefunc, restart=False):
213254

214255
# keep references of all popen
215256
# and info objects for cleanup
216-
self._info_objects.append((info, starter.terminate_on_interrupt))
217-
self._popen_instances.append(Popen(args, **popen_kwargs, **kwargs))
257+
xresource.info = (info, starter.terminate_on_interrupt)
258+
xresource.popen = Popen(args, **popen_kwargs, **kwargs)
218259

219-
info.pid = pid = self._popen_instances[-1].pid
260+
info.pid = pid = xresource.popen.pid
220261
info.pidpath.write(str(pid))
221262
self.log.debug("process %r started pid=%s", name, pid)
222263
stdout.close()
223264

224265
# keep track of all file handles so we can
225266
# cleanup later during teardown phase
226-
self._file_handles.append(info.logpath.open())
267+
xresource.fhandle = info.logpath.open()
268+
269+
self.resources.append(xresource)
270+
print(
271+
"self.resources at end of ensure function: ",
272+
self.resources,
273+
file=sys.stderr,
274+
)
227275

228276
if not restart:
229-
self._file_handles[-1].seek(0, 2)
277+
xresource.fhandle.seek(0, 2)
230278
else:
231-
if not starter.wait(self._file_handles[-1]):
279+
if not starter.wait(xresource.fhandle):
232280
raise RuntimeError(
233281
"Could not start process {}, the specified "
234282
"log pattern was not found within {} lines.".format(
@@ -238,7 +286,7 @@ def ensure(self, name, preparefunc, restart=False):
238286
self.log.debug("%s process startup detected", name)
239287

240288
pytest_extlogfiles = self.config.__dict__.setdefault("_extlogfiles", {})
241-
pytest_extlogfiles[name] = self._file_handles[-1]
289+
pytest_extlogfiles[name] = xresource.fhandle
242290
self.getinfo(name)
243291

244292
return info.pid, info.logpath
@@ -267,19 +315,9 @@ def _xshow(self, tw):
267315
tw.line(tmpl.format(**locals()))
268316
return 0
269317

270-
def _clean_up_resources(self):
271-
# file handles should always be closed
272-
# in order to avoid ResourceWarnings
273-
for f in self._file_handles:
274-
f.close()
275-
# XProcessInfo objects and Popen objects have
276-
# a one to one relation, so we should wait on
277-
# procs exit status if termination signal has
278-
# been isued for that particular XProcessInfo
279-
# Object (subprocess requirement)
280-
for (info, _), proc in zip(self._info_objects, self._popen_instances):
281-
if info._termination_signal:
282-
proc.wait(self.proc_wait_timeout)
318+
def _force_clean_up(self):
319+
for xresource in self.resources:
320+
xresource.release()
283321

284322

285323
class ProcessStarter(ABC):

0 commit comments

Comments
 (0)