xvfbwrapper 0.2.32__tar.gz → 0.2.34__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.32
3
+ Version: 0.2.34
4
4
  Summary: Manage headless displays with Xvfb (X virtual framebuffer)
5
5
  Author: Corey Goldberg
6
6
  Maintainer: Corey Goldberg
@@ -39,9 +39,8 @@ Dynamic: license-file
39
39
 
40
40
  `xvfbwrapper` is a Python library for managing X11 virtual displays with
41
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.
42
+ a virtual X server that runs without a physical display. It provides the `Xvfb` class,
43
+ a Python interface for configuring and controlling the Xvfb program.
45
44
 
46
45
  See the [module API documentation][api-docs] for more information.
47
46
 
@@ -64,13 +63,21 @@ See the [module API documentation][api-docs] for more information.
64
63
  </td>
65
64
  </tr>
66
65
  <tr>
67
- <td>Build/Tests (CI)</td>
66
+ <td>Tests (CI)</td>
68
67
  <td>
69
68
  <a href="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/test.yml">
70
69
  <img src="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/test.yml/badge.svg">
71
70
  </a>
72
71
  </td>
73
72
  </tr>
73
+ <tr>
74
+ <td>API Docs (CI)</td>
75
+ <td>
76
+ <a href="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/docs.yml">
77
+ <img src="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/docs.yml/badge.svg">
78
+ </a>
79
+ </td>
80
+ </tr>
74
81
  <tr>
75
82
  <td>Supported Python Versions</td>
76
83
  <td>
@@ -218,12 +225,10 @@ xvfb.start() # Xvfb will be called with the `ttyxx -nocursor +extension RANDR`
218
225
 
219
226
  #### Multithreaded execution:
220
227
 
221
- To run several Xvfb displays at the same time, you can use the `environ`
222
- keyword when starting the `Xvfb` instances. This provides isolation between
223
- processes or threads. Be sure to use the environment dictionary you initialize
224
- `Xvfb` with in your subsequent calls. Also, if you wish to inherit your current
225
- environment, you must use the copy method of `os.environ` and not simply
226
- assign a new variable to `os.environ`:
228
+ To run several Xvfb displays at the same time, you can use the `environ` keyword
229
+ when starting the `Xvfb` instances. This provides isolation between processes or
230
+ threads. If you wish to inherit your current environment, you must use the copy
231
+ method of `os.environ` and not simply assign a new variable to `os.environ`:
227
232
 
228
233
  ```python
229
234
  import os
@@ -286,8 +291,6 @@ if __name__ == "__main__":
286
291
 
287
292
  ## Development
288
293
 
289
- - Git: [github.com/cgoldberg/xvfbwrapper.git][github-repo]
290
-
291
294
  - Create a virtual env and install required testing packages:
292
295
 
293
296
  ```
@@ -8,9 +8,8 @@
8
8
 
9
9
  `xvfbwrapper` is a Python library for managing X11 virtual displays with
10
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.
11
+ a virtual X server that runs without a physical display. It provides the `Xvfb` class,
12
+ a Python interface for configuring and controlling the Xvfb program.
14
13
 
15
14
  See the [module API documentation][api-docs] for more information.
16
15
 
@@ -33,13 +32,21 @@ See the [module API documentation][api-docs] for more information.
33
32
  </td>
34
33
  </tr>
35
34
  <tr>
36
- <td>Build/Tests (CI)</td>
35
+ <td>Tests (CI)</td>
37
36
  <td>
38
37
  <a href="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/test.yml">
39
38
  <img src="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/test.yml/badge.svg">
40
39
  </a>
41
40
  </td>
42
41
  </tr>
42
+ <tr>
43
+ <td>API Docs (CI)</td>
44
+ <td>
45
+ <a href="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/docs.yml">
46
+ <img src="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/docs.yml/badge.svg">
47
+ </a>
48
+ </td>
49
+ </tr>
43
50
  <tr>
44
51
  <td>Supported Python Versions</td>
45
52
  <td>
@@ -187,12 +194,10 @@ xvfb.start() # Xvfb will be called with the `ttyxx -nocursor +extension RANDR`
187
194
 
188
195
  #### Multithreaded execution:
189
196
 
190
- To run several Xvfb displays at the same time, you can use the `environ`
191
- keyword when starting the `Xvfb` instances. This provides isolation between
192
- processes or threads. Be sure to use the environment dictionary you initialize
193
- `Xvfb` with in your subsequent calls. Also, if you wish to inherit your current
194
- environment, you must use the copy method of `os.environ` and not simply
195
- assign a new variable to `os.environ`:
197
+ To run several Xvfb displays at the same time, you can use the `environ` keyword
198
+ when starting the `Xvfb` instances. This provides isolation between processes or
199
+ threads. If you wish to inherit your current environment, you must use the copy
200
+ method of `os.environ` and not simply assign a new variable to `os.environ`:
196
201
 
197
202
  ```python
198
203
  import os
@@ -255,8 +260,6 @@ if __name__ == "__main__":
255
260
 
256
261
  ## Development
257
262
 
258
- - Git: [github.com/cgoldberg/xvfbwrapper.git][github-repo]
259
-
260
263
  - Create a virtual env and install required testing packages:
261
264
 
262
265
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xvfbwrapper"
7
- version = "0.2.32"
7
+ version = "0.2.34"
8
8
  description = "Manage headless displays with Xvfb (X virtual framebuffer)"
9
9
  license = "MIT"
10
10
  license-files = ["LICENSE"]
@@ -40,6 +40,9 @@ download = "https://pypi.org/project/xvfbwrapper"
40
40
  dev = [
41
41
  "tox",
42
42
  ]
43
+ docs = [
44
+ "pdoc",
45
+ ]
43
46
  lint = [
44
47
  "ruff",
45
48
  ]
@@ -60,6 +63,7 @@ py-modules = ["xvfbwrapper"]
60
63
 
61
64
  [tool.pytest]
62
65
  addopts = ["-v"]
66
+ filterwarnings = ["default"]
63
67
 
64
68
  [tool.ruff]
65
69
  line-length = 88
@@ -5,7 +5,7 @@
5
5
  """Tests for xvfbwrapper."""
6
6
 
7
7
  import os
8
- import sys
8
+ import re
9
9
  import tempfile
10
10
  import unittest
11
11
  from contextlib import suppress
@@ -167,17 +167,6 @@ class TestXvfb(XvfbCleanTestCase):
167
167
  self.assertEqual(orig_display, os.environ["DISPLAY"])
168
168
  self.assertIsNone(xvfb.proc)
169
169
 
170
- def test_start_multiple_times(self):
171
- xvfb = Xvfb()
172
- xvfb.start()
173
- self.addCleanup(xvfb.stop)
174
- pid1 = xvfb.proc.pid
175
- xvfb.stop()
176
- xvfb.start()
177
- pid2 = xvfb.proc.pid
178
- self.assertIsNotNone(xvfb.proc)
179
- self.assertNotEqual(pid1, pid2)
180
-
181
170
  def test_stop_if_not_running_doesnt_raise_error(self):
182
171
  xvfb = Xvfb()
183
172
  xvfb.stop()
@@ -216,8 +205,7 @@ class TestXvfb(XvfbCleanTestCase):
216
205
  unwriteable_dir = "/etc"
217
206
  xvfb = Xvfb(tempdir=unwriteable_dir)
218
207
  with self.assertRaisesRegex(
219
- RuntimeError,
220
- f"Could not access writable temp directory: {unwriteable_dir}",
208
+ RuntimeError, f"Could not access writable temp directory: {unwriteable_dir}"
221
209
  ):
222
210
  xvfb.start()
223
211
  self.assertIsNone(xvfb.proc)
@@ -226,8 +214,7 @@ class TestXvfb(XvfbCleanTestCase):
226
214
  unknown_dir = "/tmp/some_unknown_path"
227
215
  xvfb = Xvfb(tempdir=unknown_dir)
228
216
  with self.assertRaisesRegex(
229
- RuntimeError,
230
- f"Could not access writable temp directory: {unknown_dir}",
217
+ RuntimeError, f"Could not access writable temp directory: {unknown_dir}"
231
218
  ):
232
219
  xvfb.start()
233
220
  self.assertIsNone(xvfb.proc)
@@ -268,8 +255,7 @@ class TestXvfb(XvfbCleanTestCase):
268
255
  self.assertEqual(xvfb.new_display, display_num)
269
256
  self.assertIsNotNone(xvfb.proc)
270
257
  with self.assertRaisesRegex(
271
- RuntimeError,
272
- f"Could not lock display :{display_num}",
258
+ RuntimeError, f"Could not lock display: {display_num}"
273
259
  ):
274
260
  xvfb2.start()
275
261
 
@@ -313,12 +299,32 @@ class TestXvfb(XvfbCleanTestCase):
313
299
  "bar",
314
300
  ]
315
301
  with self.assertRaisesRegex(
316
- RuntimeError,
317
- f"Xvfb display did not open: {expected_cmd_args}",
302
+ RuntimeError, f"Xvfb display did not open: {expected_cmd_args}"
318
303
  ):
319
304
  xvfb.start()
320
305
  self.assertIsNone(xvfb.proc)
321
306
 
307
+ def test_start_already_running(self):
308
+ xvfb = Xvfb()
309
+ self.addCleanup(xvfb.stop)
310
+ xvfb.start()
311
+ with self.assertRaisesRegex(
312
+ RuntimeError, re.escape(f"Xvfb is already running (PID: {xvfb.proc.pid})")
313
+ ):
314
+ xvfb.start()
315
+ self.assertIsNotNone(xvfb.proc)
316
+
317
+ def test_multiple_lifecycles(self):
318
+ xvfb = Xvfb()
319
+ self.addCleanup(xvfb.stop)
320
+ xvfb.start()
321
+ pid1 = xvfb.proc.pid
322
+ xvfb.stop()
323
+ xvfb.start()
324
+ pid2 = xvfb.proc.pid
325
+ self.assertIsNotNone(xvfb.proc)
326
+ self.assertNotEqual(pid1, pid2)
327
+
322
328
  def test_get_next_unused_display_does_not_reuse_lock(self):
323
329
  xvfb = Xvfb()
324
330
  xvfb2 = Xvfb()
@@ -330,21 +336,10 @@ class TestXvfb(XvfbCleanTestCase):
330
336
  with patch("xvfbwrapper.randint", side_effect=side_effect) as mockrandint:
331
337
  self.assertEqual(xvfb._get_next_unused_display(), 11)
332
338
  self.assertEqual(mockrandint.call_count, 1)
333
- if sys.implementation.name == "cpython":
334
- # ResourceWarning is only raised on CPython because
335
- # of an implementation detail in it's garbage collector.
336
- # This does not occur on other Python implementations
337
- # (like PyPy).
338
- with self.assertWarns(ResourceWarning):
339
- self.assertEqual(xvfb2._get_next_unused_display(), 22)
340
- self.assertEqual(mockrandint.call_count, 3)
341
- self.assertEqual(xvfb3._get_next_unused_display(), 33)
342
- self.assertEqual(mockrandint.call_count, 10)
343
- else:
344
- self.assertEqual(xvfb2._get_next_unused_display(), 22)
345
- self.assertEqual(mockrandint.call_count, 3)
346
- self.assertEqual(xvfb3._get_next_unused_display(), 33)
347
- self.assertEqual(mockrandint.call_count, 10)
339
+ self.assertEqual(xvfb2._get_next_unused_display(), 22)
340
+ self.assertEqual(mockrandint.call_count, 3)
341
+ self.assertEqual(xvfb3._get_next_unused_display(), 33)
342
+ self.assertEqual(mockrandint.call_count, 10)
348
343
 
349
344
  def test_environ_keyword_isolates_environment_modification(self):
350
345
  # Check that start and stop methods modified the environ dict if
@@ -368,10 +363,7 @@ class TestXvfb(XvfbCleanTestCase):
368
363
  self.addCleanup(xvfb.stop)
369
364
  xvfb.start()
370
365
  proc = psutil.Process(xvfb.proc.pid)
371
- self.assertEqual(
372
- proc.environ().get("XVFBWRAPPER_TEST_MARKER"),
373
- marker_value,
374
- )
366
+ self.assertEqual(proc.environ().get("XVFBWRAPPER_TEST_MARKER"), marker_value)
375
367
 
376
368
  def test_start_failure_without_initial_display_env(self):
377
369
  # Provide a custom env *without* DISPLAY so orig_display_var == None
@@ -392,8 +384,7 @@ class TestXvfb(XvfbCleanTestCase):
392
384
  with (
393
385
  patch.object(xvfb, "_local_display_exists", return_value=False),
394
386
  self.assertRaisesRegex(
395
- RuntimeError,
396
- f"Xvfb display did not open: {expected_cmd_args}",
387
+ RuntimeError, f"Xvfb display did not open: {expected_cmd_args}"
397
388
  ),
398
389
  ):
399
390
  xvfb.start()
@@ -57,3 +57,22 @@ dependency_groups =
57
57
  skip_install = true
58
58
  commands =
59
59
  mypy --install-types --non-interactive {posargs:.}
60
+
61
+
62
+ [testenv:docs]
63
+ description = build docs
64
+ platform = .*
65
+ allowlist_externals =
66
+ echo
67
+ dependency_groups =
68
+ docs
69
+ commands =
70
+ echo "Cleaning {tox_root}/build/"
71
+ {envpython} -c "import shutil; shutil.rmtree('build', ignore_errors=True)"
72
+ echo "Building docs..."
73
+ {envpython} -m pdoc xvfbwrapper \
74
+ --output-dir build/docs \
75
+ --template-directory docs/pdoc \
76
+ --no-search \
77
+ --docformat markdown
78
+ echo "Docs successfully built: {tox_root}/build/docs/index.html"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xvfbwrapper
3
- Version: 0.2.32
3
+ Version: 0.2.34
4
4
  Summary: Manage headless displays with Xvfb (X virtual framebuffer)
5
5
  Author: Corey Goldberg
6
6
  Maintainer: Corey Goldberg
@@ -39,9 +39,8 @@ Dynamic: license-file
39
39
 
40
40
  `xvfbwrapper` is a Python library for managing X11 virtual displays with
41
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.
42
+ a virtual X server that runs without a physical display. It provides the `Xvfb` class,
43
+ a Python interface for configuring and controlling the Xvfb program.
45
44
 
46
45
  See the [module API documentation][api-docs] for more information.
47
46
 
@@ -64,13 +63,21 @@ See the [module API documentation][api-docs] for more information.
64
63
  </td>
65
64
  </tr>
66
65
  <tr>
67
- <td>Build/Tests (CI)</td>
66
+ <td>Tests (CI)</td>
68
67
  <td>
69
68
  <a href="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/test.yml">
70
69
  <img src="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/test.yml/badge.svg">
71
70
  </a>
72
71
  </td>
73
72
  </tr>
73
+ <tr>
74
+ <td>API Docs (CI)</td>
75
+ <td>
76
+ <a href="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/docs.yml">
77
+ <img src="https://github.com/cgoldberg/xvfbwrapper/actions/workflows/docs.yml/badge.svg">
78
+ </a>
79
+ </td>
80
+ </tr>
74
81
  <tr>
75
82
  <td>Supported Python Versions</td>
76
83
  <td>
@@ -218,12 +225,10 @@ xvfb.start() # Xvfb will be called with the `ttyxx -nocursor +extension RANDR`
218
225
 
219
226
  #### Multithreaded execution:
220
227
 
221
- To run several Xvfb displays at the same time, you can use the `environ`
222
- keyword when starting the `Xvfb` instances. This provides isolation between
223
- processes or threads. Be sure to use the environment dictionary you initialize
224
- `Xvfb` with in your subsequent calls. Also, if you wish to inherit your current
225
- environment, you must use the copy method of `os.environ` and not simply
226
- assign a new variable to `os.environ`:
228
+ To run several Xvfb displays at the same time, you can use the `environ` keyword
229
+ when starting the `Xvfb` instances. This provides isolation between processes or
230
+ threads. If you wish to inherit your current environment, you must use the copy
231
+ method of `os.environ` and not simply assign a new variable to `os.environ`:
227
232
 
228
233
  ```python
229
234
  import os
@@ -286,8 +291,6 @@ if __name__ == "__main__":
286
291
 
287
292
  ## Development
288
293
 
289
- - Git: [github.com/cgoldberg/xvfbwrapper.git][github-repo]
290
-
291
294
  - Create a virtual env and install required testing packages:
292
295
 
293
296
  ```
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) 2012-2026 Corey Goldberg
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
-
5
4
  """Manage headless displays with Xvfb (X virtual framebuffer)
6
5
 
7
6
  This module provides a wrapper around Xvfb (X Virtual Framebuffer) for managing
@@ -102,13 +101,17 @@ class Xvfb:
102
101
  def start(self) -> None:
103
102
  """Start Xvfb."""
104
103
 
104
+ if self.proc is not None:
105
+ raise RuntimeError(
106
+ f"Xvfb is already running (PID: {self.proc.pid})",
107
+ )
105
108
  if not os.access(self._tempdir, os.W_OK):
106
109
  raise RuntimeError(
107
110
  f"Could not access writable temp directory: {self._tempdir}"
108
111
  )
109
112
  if self.new_display is not None:
110
113
  if not self._get_lock_for_display(self.new_display):
111
- raise RuntimeError(f"Could not lock display :{self.new_display}")
114
+ raise RuntimeError(f"Could not lock display: {self.new_display}")
112
115
  else:
113
116
  self.new_display = self._get_next_unused_display()
114
117
  display_var = f":{self.new_display}"
@@ -192,16 +195,17 @@ class Xvfb:
192
195
  """
193
196
  tempfile_path = Path(self._tempdir, f".X{display}-lock")
194
197
  try:
195
- self._lock_display_file = tempfile_path.open("w")
198
+ lock_file = tempfile_path.open("w")
196
199
  except PermissionError:
197
200
  return False
201
+ try:
202
+ fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
203
+ except BlockingIOError:
204
+ lock_file.close()
205
+ return False
198
206
  else:
199
- try:
200
- fcntl.flock(self._lock_display_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
201
- except BlockingIOError:
202
- return False
203
- else:
204
- return True
207
+ self._lock_display_file = lock_file
208
+ return True
205
209
 
206
210
  def _get_next_unused_display(self) -> int:
207
211
  """Randomly choose a display number and try to acquire a lock for it.
File without changes
File without changes
File without changes