diff --git a/osbuild/testutil/atomic.py b/osbuild/testutil/atomic.py new file mode 100644 index 00000000..f48b726b --- /dev/null +++ b/osbuild/testutil/atomic.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +""" +thread/atomic related utilities +""" +import threading + + +class AtomicCounter: + """ A thread-safe counter """ + + def __init__(self, count: int = 0) -> None: + self._count = count + self._lock = threading.Lock() + + def inc(self) -> None: + """ increase the count """ + with self._lock: + self._count += 1 + + def dec(self) -> None: + """ decrease the count """ + with self._lock: + self._count -= 1 + + @property + def count(self) -> int: + """ get the current count """ + with self._lock: + return self._count diff --git a/osbuild/testutil/net.py b/osbuild/testutil/net.py index 02be47b4..d2abb565 100644 --- a/osbuild/testutil/net.py +++ b/osbuild/testutil/net.py @@ -7,6 +7,8 @@ import http.server import socket import threading +from .atomic import AtomicCounter + def _get_free_port(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -23,13 +25,13 @@ class DirHTTPServer(http.server.ThreadingHTTPServer): def __init__(self, *args, directory=None, simulate_failures=0, **kwargs): super().__init__(*args, **kwargs) self.directory = directory - self.simulate_failures = simulate_failures - self.reqs = 0 + self.simulate_failures = AtomicCounter(simulate_failures) + self.reqs = AtomicCounter() def finish_request(self, request, client_address): - self.reqs += 1 # racy on non GIL systems - if self.simulate_failures > 0: - self.simulate_failures -= 1 # racy on non GIL systems + self.reqs.inc() + if self.simulate_failures.count > 0: + self.simulate_failures.dec() SilentHTTPRequestHandler( request, client_address, self, directory="does-not-exists") return diff --git a/sources/test/test_curl_source.py b/sources/test/test_curl_source.py index 626915db..e6bfd061 100644 --- a/sources/test/test_curl_source.py +++ b/sources/test/test_curl_source.py @@ -130,7 +130,7 @@ def test_curl_download_many_with_retry(tmp_path, sources_service): sources_service.cache.mkdir() sources_service.fetch_all(test_sources) # we simulated N failures and we need to fetch K files - assert httpd.reqs == simulate_failures + len(test_sources) + assert httpd.reqs.count == simulate_failures + len(test_sources) # double downloads happend in the expected format for chksum in test_sources: assert (sources_service.cache / chksum).exists() @@ -165,5 +165,5 @@ def test_curl_download_many_retries(tmp_path, sources_service): with pytest.raises(RuntimeError) as exp: sources_service.fetch_all(test_sources) # curl will retry 10 times - assert httpd.reqs == 10 * len(test_sources) + assert httpd.reqs.count == 10 * len(test_sources) assert "curl: error downloading http://localhost:" in str(exp.value) diff --git a/tox.ini b/tox.ini index 505719c5..18f5e3e5 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ setenv = LINTABLES_EXCLUDES = "*.json,*.sh" LINTABLES_EXCLUDES_RE = ".*\.json$,.*\.sh" TYPEABLES = osbuild - TYPEABLES_STRICT = ./osbuild/main_cli.py ./osbuild/util/parsing.py + TYPEABLES_STRICT = ./osbuild/main_cli.py ./osbuild/util/parsing.py ./osbuild/testutil/atomic.py passenv = TEST_CATEGORY