mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-28 06:41:43 +00:00
feat: threadsafe services
In the most cases it was enough to just add lock. In case of worker trigger, since there is atomic operation on timer, it was also required to add queue (coz python doesn't have atomics)
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.distributed import WorkersCache
|
||||
from ahriman.core.distributed import WorkerTrigger, WorkersCache
|
||||
from ahriman.core.distributed.distributed_system import DistributedSystem
|
||||
|
||||
|
||||
@ -21,6 +21,22 @@ def distributed_system(configuration: Configuration) -> DistributedSystem:
|
||||
return DistributedSystem(repository_id, configuration)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def worker_trigger(configuration: Configuration) -> WorkerTrigger:
|
||||
"""
|
||||
worker trigger fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
Returns:
|
||||
WorkerTrigger: worker trigger test instance
|
||||
"""
|
||||
configuration.set_option("status", "address", "http://localhost:8081")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
return WorkerTrigger(repository_id, configuration)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def workers_cache(configuration: Configuration) -> WorkersCache:
|
||||
"""
|
||||
|
@ -1,52 +1,66 @@
|
||||
from threading import Timer
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.distributed import WorkerTrigger
|
||||
|
||||
|
||||
def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_create_timer(worker_trigger: WorkerTrigger) -> None:
|
||||
"""
|
||||
must create a timer and put it to queue
|
||||
"""
|
||||
worker_trigger.create_timer()
|
||||
|
||||
timer = worker_trigger._timers.popleft()
|
||||
assert timer.function == worker_trigger.ping
|
||||
timer.cancel()
|
||||
|
||||
|
||||
def test_on_start(worker_trigger: WorkerTrigger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must register itself as worker
|
||||
"""
|
||||
configuration.set_option("status", "address", "http://localhost:8081")
|
||||
run_mock = mocker.patch("threading.Timer.start")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
|
||||
WorkerTrigger(repository_id, configuration).on_start()
|
||||
run_mock = mocker.patch("ahriman.core.distributed.WorkerTrigger.create_timer")
|
||||
worker_trigger.on_start()
|
||||
run_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_on_stop(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_on_stop(worker_trigger: WorkerTrigger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must unregister itself as worker
|
||||
"""
|
||||
configuration.set_option("status", "address", "http://localhost:8081")
|
||||
run_mock = mocker.patch("threading.Timer.cancel")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
worker_trigger._timers.append(Timer(1, print)) # doesn't matter
|
||||
|
||||
WorkerTrigger(repository_id, configuration).on_stop()
|
||||
worker_trigger.on_stop()
|
||||
run_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_on_stop_empty_timer(configuration: Configuration) -> None:
|
||||
def test_on_stop_empty_timer(worker_trigger: WorkerTrigger) -> None:
|
||||
"""
|
||||
must do not fail if no timer was started
|
||||
"""
|
||||
configuration.set_option("status", "address", "http://localhost:8081")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
|
||||
WorkerTrigger(repository_id, configuration).on_stop()
|
||||
worker_trigger.on_stop()
|
||||
|
||||
|
||||
def test_ping(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_ping(worker_trigger: WorkerTrigger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly process timer action
|
||||
"""
|
||||
configuration.set_option("status", "address", "http://localhost:8081")
|
||||
run_mock = mocker.patch("ahriman.core.distributed.WorkerTrigger.register")
|
||||
timer_mock = mocker.patch("threading.Timer.start")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
worker_trigger._timers.append(Timer(1, print)) # doesn't matter
|
||||
|
||||
WorkerTrigger(repository_id, configuration).ping()
|
||||
worker_trigger.ping()
|
||||
run_mock.assert_called_once_with()
|
||||
timer_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_ping_empty_queue(worker_trigger: WorkerTrigger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must do nothing in case of empty queue
|
||||
"""
|
||||
run_mock = mocker.patch("ahriman.core.distributed.WorkerTrigger.register")
|
||||
worker_trigger.ping()
|
||||
run_mock.assert_not_called()
|
||||
|
@ -21,7 +21,7 @@ def test_load(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture)
|
||||
|
||||
watcher.load()
|
||||
cache_mock.assert_called_once_with(watcher.repository_id)
|
||||
package, status = watcher.known[package_ahriman.base]
|
||||
package, status = watcher._known[package_ahriman.base]
|
||||
assert package == package_ahriman
|
||||
assert status.status == BuildStatusEnum.Unknown
|
||||
|
||||
@ -32,10 +32,10 @@ def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFi
|
||||
"""
|
||||
status = BuildStatus(BuildStatusEnum.Success)
|
||||
mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=[(package_ahriman, status)])
|
||||
watcher.known = {package_ahriman.base: (package_ahriman, status)}
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, status)}
|
||||
|
||||
watcher.load()
|
||||
_, status = watcher.known[package_ahriman.base]
|
||||
_, status = watcher._known[package_ahriman.base]
|
||||
assert status.status == BuildStatusEnum.Success
|
||||
|
||||
|
||||
@ -43,11 +43,21 @@ def test_logs_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixt
|
||||
"""
|
||||
must return package logs
|
||||
"""
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get")
|
||||
|
||||
watcher.logs_get(package_ahriman.base, 1, 2)
|
||||
logs_mock.assert_called_once_with(package_ahriman.base, 1, 2, watcher.repository_id)
|
||||
|
||||
|
||||
def test_logs_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise UnknownPackageError on logs in case of unknown package
|
||||
"""
|
||||
with pytest.raises(UnknownPackageError):
|
||||
watcher.logs_get(package_ahriman.base)
|
||||
|
||||
|
||||
def test_logs_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove package logs
|
||||
@ -94,7 +104,7 @@ def test_package_changes_get(watcher: Watcher, package_ahriman: Package, mocker:
|
||||
must return package changes
|
||||
"""
|
||||
get_mock = mocker.patch("ahriman.core.database.SQLite.changes_get", return_value=Changes("sha"))
|
||||
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
|
||||
assert watcher.package_changes_get(package_ahriman.base) == Changes("sha")
|
||||
get_mock.assert_called_once_with(package_ahriman.base, watcher.repository_id)
|
||||
@ -102,7 +112,7 @@ def test_package_changes_get(watcher: Watcher, package_ahriman: Package, mocker:
|
||||
|
||||
def test_package_changes_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise UnknownPackageError in case of unknown package
|
||||
must raise UnknownPackageError on changes in case of unknown package
|
||||
"""
|
||||
with pytest.raises(UnknownPackageError):
|
||||
watcher.package_changes_get(package_ahriman.base)
|
||||
@ -112,7 +122,7 @@ def test_package_get(watcher: Watcher, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return package status
|
||||
"""
|
||||
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
package, status = watcher.package_get(package_ahriman.base)
|
||||
assert package == package_ahriman
|
||||
assert status.status == BuildStatusEnum.Unknown
|
||||
@ -132,10 +142,10 @@ def test_package_remove(watcher: Watcher, package_ahriman: Package, mocker: Mock
|
||||
"""
|
||||
cache_mock = mocker.patch("ahriman.core.database.SQLite.package_remove")
|
||||
logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove")
|
||||
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
|
||||
watcher.package_remove(package_ahriman.base)
|
||||
assert not watcher.known
|
||||
assert not watcher._known
|
||||
cache_mock.assert_called_once_with(package_ahriman.base, watcher.repository_id)
|
||||
logs_mock.assert_called_once_with(package_ahriman.base, None)
|
||||
|
||||
@ -158,7 +168,7 @@ def test_package_update(watcher: Watcher, package_ahriman: Package, mocker: Mock
|
||||
|
||||
watcher.package_update(package_ahriman.base, BuildStatusEnum.Unknown, package_ahriman)
|
||||
cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int), watcher.repository_id)
|
||||
package, status = watcher.known[package_ahriman.base]
|
||||
package, status = watcher._known[package_ahriman.base]
|
||||
assert package == package_ahriman
|
||||
assert status.status == BuildStatusEnum.Unknown
|
||||
|
||||
@ -168,11 +178,11 @@ def test_package_update_ping(watcher: Watcher, package_ahriman: Package, mocker:
|
||||
must update package status only for known package
|
||||
"""
|
||||
cache_mock = mocker.patch("ahriman.core.database.SQLite.package_update")
|
||||
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
|
||||
watcher.package_update(package_ahriman.base, BuildStatusEnum.Success, None)
|
||||
cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int), watcher.repository_id)
|
||||
package, status = watcher.known[package_ahriman.base]
|
||||
package, status = watcher._known[package_ahriman.base]
|
||||
assert package == package_ahriman
|
||||
assert status.status == BuildStatusEnum.Success
|
||||
|
||||
@ -189,6 +199,7 @@ def test_patches_get(watcher: Watcher, package_ahriman: Package, mocker: MockerF
|
||||
"""
|
||||
must return patches for the package
|
||||
"""
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_list")
|
||||
|
||||
watcher.patches_get(package_ahriman.base, None)
|
||||
@ -201,6 +212,14 @@ def test_patches_get(watcher: Watcher, package_ahriman: Package, mocker: MockerF
|
||||
])
|
||||
|
||||
|
||||
def test_patches_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise UnknownPackageError on patches in case of unknown package
|
||||
"""
|
||||
with pytest.raises(UnknownPackageError):
|
||||
watcher.patches_get(package_ahriman.base, None)
|
||||
|
||||
|
||||
def test_patches_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove patches for the package
|
||||
|
Reference in New Issue
Block a user