File: test_lock.py

package info (click to toggle)
aiocache 0.12.3-2
  • links: PTS
  • area: main
  • in suites: forky, sid, trixie
  • size: 692 kB
  • sloc: python: 5,044; makefile: 221; sh: 7
file content (112 lines) | stat: -rw-r--r-- 4,231 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import asyncio
from unittest.mock import Mock, patch

import pytest

from aiocache.lock import OptimisticLock, OptimisticLockError, RedLock
from ..utils import KEY_LOCK, Keys


class TestRedLock:
    @pytest.fixture
    def lock(self, mock_base_cache):
        RedLock._EVENTS = {}
        yield RedLock(mock_base_cache, Keys.KEY, 20)

    async def test_acquire(self, mock_base_cache, lock):
        await lock._acquire()
        mock_base_cache._add.assert_called_with(KEY_LOCK, lock._value, ttl=20)
        assert lock._EVENTS[KEY_LOCK].is_set() is False

    async def test_release(self, mock_base_cache, lock):
        mock_base_cache._redlock_release.return_value = True
        await lock._acquire()
        await lock._release()
        mock_base_cache._redlock_release.assert_called_with(KEY_LOCK, lock._value)
        assert KEY_LOCK not in lock._EVENTS

    async def test_release_no_acquire(self, mock_base_cache, lock):
        mock_base_cache._redlock_release.return_value = False
        assert KEY_LOCK not in lock._EVENTS
        await lock._release()
        assert KEY_LOCK not in lock._EVENTS

    async def test_context_manager(self, mock_base_cache, lock):
        async with lock:
            pass
        mock_base_cache._add.assert_called_with(KEY_LOCK, lock._value, ttl=20)
        mock_base_cache._redlock_release.assert_called_with(KEY_LOCK, lock._value)

    async def test_raises_exceptions(self, mock_base_cache, lock):
        mock_base_cache._redlock_release.return_value = True
        with pytest.raises(ValueError):
            async with lock:
                raise ValueError

    async def test_acquire_block_timeouts(self, mock_base_cache, lock):
        await lock._acquire()

        # Mock .wait() to avoid unawaited coroutine warning.
        with patch.object(RedLock._EVENTS[lock.key], "wait", Mock(spec_set=())):
            with patch("asyncio.wait_for", autospec=True, side_effect=asyncio.TimeoutError):
                mock_base_cache._add.side_effect = ValueError
                result = await lock._acquire()
                assert result is None

    async def test_wait_for_release_no_acquire(self, mock_base_cache, lock):
        mock_base_cache._add.side_effect = ValueError
        assert await lock._acquire() is None

    async def test_multiple_locks_lock(self, mock_base_cache, lock):
        lock_1 = RedLock(mock_base_cache, Keys.KEY, 20)
        lock_2 = RedLock(mock_base_cache, Keys.KEY, 20)
        mock_base_cache._add.side_effect = [True, ValueError(), ValueError()]
        await lock._acquire()
        event = lock._EVENTS[KEY_LOCK]

        assert KEY_LOCK in lock._EVENTS
        assert KEY_LOCK in lock_1._EVENTS
        assert KEY_LOCK in lock_2._EVENTS
        assert not event.is_set()

        await asyncio.gather(lock_1._acquire(), lock._release(), lock_2._acquire())

        assert KEY_LOCK not in lock._EVENTS
        assert KEY_LOCK not in lock_1._EVENTS
        assert KEY_LOCK not in lock_2._EVENTS
        assert event.is_set()


class TestOptimisticLock:
    @pytest.fixture
    def lock(self, mock_base_cache):
        yield OptimisticLock(mock_base_cache, Keys.KEY)

    def test_init(self, mock_base_cache, lock):
        assert lock.client == mock_base_cache
        assert lock._token is None
        assert lock.key == Keys.KEY
        assert lock.ns_key == mock_base_cache._build_key(Keys.KEY)

    async def test_aenter_returns_lock(self, lock):
        assert await lock.__aenter__() is lock

    async def test_aexit_not_crashing(self, lock):
        async with lock:
            pass

    async def test_acquire_calls_get(self, lock):
        await lock._acquire()
        lock.client._gets.assert_called_with(Keys.KEY)
        assert lock._token == lock.client._gets.return_value

    async def test_cas_calls_set_with_token(self, lock, mocker):
        m = mocker.spy(lock.client, "set")
        await lock._acquire()
        await lock.cas("value")
        m.assert_called_with(Keys.KEY, "value", _cas_token=lock._token)

    async def test_wrong_token_raises_error(self, mock_base_cache, lock):
        mock_base_cache._set.return_value = 0
        with pytest.raises(OptimisticLockError):
            await lock.cas("value")