xvfbwrapper 0.2.29__tar.gz → 0.2.31__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xvfbwrapper
3
- Version: 0.2.29
3
+ Version: 0.2.31
4
4
  Summary: Manage headless displays with Xvfb (X virtual framebuffer)
5
5
  Author: Corey Goldberg
6
6
  Maintainer: Corey Goldberg
@@ -37,18 +37,19 @@ Dynamic: license-file
37
37
 
38
38
  ## About
39
39
 
40
- `xvfbwrapper` is a Python library for controlling X11 virtual displays with Xvfb.
40
+ `xvfbwrapper` is a Python library for managing X11 virtual displays with
41
+ [Xvfb (X virtual framebuffer)](https://x.org/releases/X11R7.7/doc/man/man1/Xvfb.1.xhtml),
42
+ a virtual X server that runs without a physical display. It provides the
43
+ [`Xvfb`][api-docs] class, a Python interface for configuring and controlling the Xvfb
44
+ program.
45
+
46
+ See the [module API documentation][api-docs] for more information.
41
47
 
42
48
  - Copyright (c) 2012-2026 [Corey Goldberg][github-profile]
43
49
  - Development: [GitHub][github-repo]
44
50
  - Releases: [PyPI][pypi-home]
45
51
  - License: [MIT][mit-license]
46
52
 
47
- The package provides the `xvfvwrapper.Xvfb` class that is used to interact with
48
- an Xvfb server.
49
-
50
- See the [module API documentation][api-docs] for more information.
51
-
52
53
  ----
53
54
 
54
55
  ## Status
@@ -116,7 +117,7 @@ pip install xvfbwrapper
116
117
  - Python 3.10+
117
118
  - X Window System
118
119
  - Xvfb (`sudo apt-get install xvfb`, `yum install xorg-x11-server-Xvfb`, etc)
119
- - Support for locking with `fcntl` system call (non-Windows systems)
120
+ - Support for file locking using `fcntl` (POSIX systems)
120
121
 
121
122
  ----
122
123
 
@@ -244,10 +245,10 @@ finally:
244
245
  xvfb2.stop()
245
246
  ```
246
247
 
247
- #### Usage in testing - headless Selenium WebDriver tests:
248
+ #### Example: headless browser tests:
248
249
 
249
- This is a test using `selenium` and `xvfbwrapper` to run tests
250
- on Chrome with a headless display. (see: [selenium docs][selenium-docs])
250
+ This uses [`selenium`][selenium-docs] and `xvfbwrapper` to run a test
251
+ on Chrome with a headless display.
251
252
 
252
253
  [selenium-docs]: https://www.selenium.dev/selenium/docs/api/py
253
254
 
@@ -6,18 +6,19 @@
6
6
 
7
7
  ## About
8
8
 
9
- `xvfbwrapper` is a Python library for controlling X11 virtual displays with Xvfb.
9
+ `xvfbwrapper` is a Python library for managing X11 virtual displays with
10
+ [Xvfb (X virtual framebuffer)](https://x.org/releases/X11R7.7/doc/man/man1/Xvfb.1.xhtml),
11
+ a virtual X server that runs without a physical display. It provides the
12
+ [`Xvfb`][api-docs] class, a Python interface for configuring and controlling the Xvfb
13
+ program.
14
+
15
+ See the [module API documentation][api-docs] for more information.
10
16
 
11
17
  - Copyright (c) 2012-2026 [Corey Goldberg][github-profile]
12
18
  - Development: [GitHub][github-repo]
13
19
  - Releases: [PyPI][pypi-home]
14
20
  - License: [MIT][mit-license]
15
21
 
16
- The package provides the `xvfvwrapper.Xvfb` class that is used to interact with
17
- an Xvfb server.
18
-
19
- See the [module API documentation][api-docs] for more information.
20
-
21
22
  ----
22
23
 
23
24
  ## Status
@@ -85,7 +86,7 @@ pip install xvfbwrapper
85
86
  - Python 3.10+
86
87
  - X Window System
87
88
  - Xvfb (`sudo apt-get install xvfb`, `yum install xorg-x11-server-Xvfb`, etc)
88
- - Support for locking with `fcntl` system call (non-Windows systems)
89
+ - Support for file locking using `fcntl` (POSIX systems)
89
90
 
90
91
  ----
91
92
 
@@ -213,10 +214,10 @@ finally:
213
214
  xvfb2.stop()
214
215
  ```
215
216
 
216
- #### Usage in testing - headless Selenium WebDriver tests:
217
+ #### Example: headless browser tests:
217
218
 
218
- This is a test using `selenium` and `xvfbwrapper` to run tests
219
- on Chrome with a headless display. (see: [selenium docs][selenium-docs])
219
+ This uses [`selenium`][selenium-docs] and `xvfbwrapper` to run a test
220
+ on Chrome with a headless display.
220
221
 
221
222
  [selenium-docs]: https://www.selenium.dev/selenium/docs/api/py
222
223
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xvfbwrapper"
7
- version = "0.2.29"
7
+ version = "0.2.31"
8
8
  description = "Manage headless displays with Xvfb (X virtual framebuffer)"
9
9
  license = "MIT"
10
10
  license-files = ["LICENSE"]
@@ -216,7 +216,8 @@ class TestXvfb(XvfbCleanTestCase):
216
216
  unwriteable_dir = "/etc"
217
217
  xvfb = Xvfb(tempdir=unwriteable_dir)
218
218
  with self.assertRaisesRegex(
219
- RuntimeError, f"Could not access writable temp directory: {unwriteable_dir}"
219
+ RuntimeError,
220
+ f"Could not access writable temp directory: {unwriteable_dir}",
220
221
  ):
221
222
  xvfb.start()
222
223
  self.assertIsNone(xvfb.proc)
@@ -225,7 +226,8 @@ class TestXvfb(XvfbCleanTestCase):
225
226
  unknown_dir = "/tmp/some_unknown_path"
226
227
  xvfb = Xvfb(tempdir=unknown_dir)
227
228
  with self.assertRaisesRegex(
228
- RuntimeError, f"Could not access writable temp directory: {unknown_dir}"
229
+ RuntimeError,
230
+ f"Could not access writable temp directory: {unknown_dir}",
229
231
  ):
230
232
  xvfb.start()
231
233
  self.assertIsNone(xvfb.proc)
@@ -300,7 +302,7 @@ class TestXvfb(XvfbCleanTestCase):
300
302
  self.assertIsNotNone(xvfb.proc)
301
303
 
302
304
  def test_start_fails_with_unknown_kwargs(self):
303
- xvfb = Xvfb(foo="bar", timeout=5)
305
+ xvfb = Xvfb(foo="bar", timeout=2)
304
306
  expected_cmd_args = [
305
307
  "Xvfb",
306
308
  r":\d",
@@ -311,7 +313,8 @@ class TestXvfb(XvfbCleanTestCase):
311
313
  "bar",
312
314
  ]
313
315
  with self.assertRaisesRegex(
314
- RuntimeError, f"Xvfb display did not open: {expected_cmd_args}"
316
+ RuntimeError,
317
+ f"Xvfb display did not open: {expected_cmd_args}",
315
318
  ):
316
319
  xvfb.start()
317
320
  self.assertIsNone(xvfb.proc)
@@ -14,9 +14,6 @@ envlist =
14
14
  lint
15
15
  type
16
16
  py310
17
- py311
18
- py312
19
- py313
20
17
  py314
21
18
  pypy3
22
19
  skip_missing_interpreters = true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xvfbwrapper
3
- Version: 0.2.29
3
+ Version: 0.2.31
4
4
  Summary: Manage headless displays with Xvfb (X virtual framebuffer)
5
5
  Author: Corey Goldberg
6
6
  Maintainer: Corey Goldberg
@@ -37,18 +37,19 @@ Dynamic: license-file
37
37
 
38
38
  ## About
39
39
 
40
- `xvfbwrapper` is a Python library for controlling X11 virtual displays with Xvfb.
40
+ `xvfbwrapper` is a Python library for managing X11 virtual displays with
41
+ [Xvfb (X virtual framebuffer)](https://x.org/releases/X11R7.7/doc/man/man1/Xvfb.1.xhtml),
42
+ a virtual X server that runs without a physical display. It provides the
43
+ [`Xvfb`][api-docs] class, a Python interface for configuring and controlling the Xvfb
44
+ program.
45
+
46
+ See the [module API documentation][api-docs] for more information.
41
47
 
42
48
  - Copyright (c) 2012-2026 [Corey Goldberg][github-profile]
43
49
  - Development: [GitHub][github-repo]
44
50
  - Releases: [PyPI][pypi-home]
45
51
  - License: [MIT][mit-license]
46
52
 
47
- The package provides the `xvfvwrapper.Xvfb` class that is used to interact with
48
- an Xvfb server.
49
-
50
- See the [module API documentation][api-docs] for more information.
51
-
52
53
  ----
53
54
 
54
55
  ## Status
@@ -116,7 +117,7 @@ pip install xvfbwrapper
116
117
  - Python 3.10+
117
118
  - X Window System
118
119
  - Xvfb (`sudo apt-get install xvfb`, `yum install xorg-x11-server-Xvfb`, etc)
119
- - Support for locking with `fcntl` system call (non-Windows systems)
120
+ - Support for file locking using `fcntl` (POSIX systems)
120
121
 
121
122
  ----
122
123
 
@@ -244,10 +245,10 @@ finally:
244
245
  xvfb2.stop()
245
246
  ```
246
247
 
247
- #### Usage in testing - headless Selenium WebDriver tests:
248
+ #### Example: headless browser tests:
248
249
 
249
- This is a test using `selenium` and `xvfbwrapper` to run tests
250
- on Chrome with a headless display. (see: [selenium docs][selenium-docs])
250
+ This uses [`selenium`][selenium-docs] and `xvfbwrapper` to run a test
251
+ on Chrome with a headless display.
251
252
 
252
253
  [selenium-docs]: https://www.selenium.dev/selenium/docs/api/py
253
254
 
@@ -2,7 +2,14 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
4
 
5
- """Run a headless display inside X virtual framebuffer (Xvfb)."""
5
+ """Manage headless displays with Xvfb (X virtual framebuffer)
6
+
7
+ This module provides a wrapper around Xvfb (X Virtual Framebuffer) for managing
8
+ virtual X displays.
9
+
10
+ - Releases: https://pypi.org/project/xvfbwrapper
11
+ - Development: https://github.com/cgoldberg/xvfbwrapper
12
+ """
6
13
 
7
14
  import os
8
15
  import platform
@@ -25,6 +32,11 @@ except ImportError as e:
25
32
 
26
33
 
27
34
  class Xvfb:
35
+ """Manage an Xvfb (X Virtual Framebuffer) process.
36
+
37
+ Provides an interface for configuring and controlling a virtual X display.
38
+ """
39
+
28
40
  # Maximum value to use for a display. 32-bit maxint is the
29
41
  # highest Xvfb currently supports
30
42
  MAX_DISPLAY: int = 2147483647
@@ -39,46 +51,39 @@ class Xvfb:
39
51
  set_xdg_session_type: bool = False,
40
52
  environ: MutableMapping[str, str] | None = None,
41
53
  extra_args: Sequence[str] | None = None,
42
- timeout: float = 10,
54
+ timeout: float | None = 10,
43
55
  **kwargs: str,
44
56
  ) -> None:
45
57
  self.width: int = width
46
58
  self.height: int = height
47
59
  self.colordepth: int = colordepth
48
60
  self._tempdir: Path | str = tempdir or tempfile.gettempdir()
49
- self._timeout: float = timeout
61
+ self._timeout: float | None = timeout
50
62
  self.new_display: int | None = display
51
63
  self.environ: MutableMapping[str, str] = environ or os.environ
52
-
53
64
  if set_xdg_session_type:
54
65
  os.environ["XDG_SESSION_TYPE"] = "x11"
55
-
66
+ self.environ["XDG_SESSION_TYPE"] = "x11"
56
67
  if not self._xvfb_exists():
57
68
  raise FileNotFoundError(
58
69
  "Could not find Xvfb. Please install it and try again"
59
70
  )
60
-
61
71
  self.xvfb_cmd: list[str] = []
62
-
63
72
  if not extra_args:
64
73
  extra_args = []
65
-
66
74
  self.extra_xvfb_args: list[str] = [
67
75
  "-screen",
68
76
  "0",
69
77
  f"{self.width}x{self.height}x{self.colordepth}",
70
78
  *extra_args,
71
79
  ]
72
-
73
80
  for key, value in kwargs.items():
74
81
  self.extra_xvfb_args += [f"-{key}", value]
75
-
76
82
  self.orig_display_var: str | None
77
83
  if "DISPLAY" in self.environ:
78
84
  self.orig_display_var = self.environ["DISPLAY"]
79
85
  else:
80
86
  self.orig_display_var = None
81
-
82
87
  self.proc: subprocess.Popen[bytes] | None = None
83
88
  self._lock_display_file: TextIO | None = None
84
89
 
@@ -95,6 +100,8 @@ class Xvfb:
95
100
  self.stop()
96
101
 
97
102
  def start(self) -> None:
103
+ """Start Xvfb."""
104
+
98
105
  if not os.access(self._tempdir, os.W_OK):
99
106
  raise RuntimeError(
100
107
  f"Could not access writable temp directory: {self._tempdir}"
@@ -113,10 +120,11 @@ class Xvfb:
113
120
  close_fds=True,
114
121
  env=dict(self.environ),
115
122
  )
116
- start = time.time()
123
+ start = time.monotonic()
117
124
  while not self._local_display_exists(self.new_display):
118
125
  time.sleep(1e-3)
119
- if time.time() - start > self._timeout:
126
+ elapsed = time.monotonic() - start
127
+ if self._timeout is not None and elapsed > self._timeout:
120
128
  self.stop()
121
129
  raise RuntimeError(f"Xvfb display did not open: {self.xvfb_cmd}")
122
130
  ret_code = self.proc.poll()
@@ -127,6 +135,19 @@ class Xvfb:
127
135
  raise RuntimeError(f"Xvfb did not start ({ret_code}): {self.xvfb_cmd}")
128
136
 
129
137
  def stop(self) -> None:
138
+ """Stop Xvfb and clean up its resources.
139
+
140
+ Terminate the process, escalating to kill if it does not exit within the
141
+ timeout. If the process has already exited, termination is treated as
142
+ successful. If `self._timeout` is set, wait up to that duration for termination
143
+ and, if necessary, an additional duration of the same length after killing the
144
+ process. If `self._timeout` is `None`, wait indefinitely for the process to
145
+ exit and be reaped.
146
+
147
+ This is a one-way lifecycle transition: once called, this object is considered
148
+ stopped and cannot be reused, regardless of whether the process exits
149
+ successfully.
150
+ """
130
151
  if self.proc is None:
131
152
  return
132
153
  try:
@@ -134,13 +155,15 @@ class Xvfb:
134
155
  self.environ.pop("DISPLAY", None)
135
156
  else:
136
157
  self._set_display(self.orig_display_var)
137
-
138
- with suppress(OSError):
158
+ with suppress(ProcessLookupError):
139
159
  self.proc.terminate()
160
+ try:
161
+ self.proc.wait(self._timeout)
162
+ except subprocess.TimeoutExpired:
163
+ self.proc.kill()
140
164
  self.proc.wait(self._timeout)
141
-
142
- self.proc = None
143
165
  finally:
166
+ self.proc = None
144
167
  self._cleanup_lock_file()
145
168
 
146
169
  def _xvfb_exists(self) -> bool:
@@ -160,7 +183,6 @@ class Xvfb:
160
183
  """
161
184
  if self._lock_display_file is None:
162
185
  return
163
-
164
186
  self._lock_display_file.close()
165
187
  with suppress(OSError):
166
188
  Path(self._lock_display_file.name).unlink()
File without changes
File without changes
File without changes