File: lock_test.py

package info (click to toggle)
python-certbot 4.0.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,688 kB
  • sloc: python: 21,764; makefile: 182; sh: 108
file content (144 lines) | stat: -rw-r--r-- 5,060 bytes parent folder | download | duplicates (2)
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""Tests for certbot._internal.lock."""
import functools
import multiprocessing
import sys
import unittest
from unittest import mock

import pytest

from certbot import errors
from certbot.compat import os
from certbot.tests import util as test_util

try:
    import fcntl  # pylint: disable=import-error,unused-import
except ImportError:
    POSIX_MODE = False
else:
    POSIX_MODE = True




class LockDirTest(test_util.TempDirTestCase):
    """Tests for certbot._internal.lock.lock_dir."""
    @classmethod
    def _call(cls, *args, **kwargs):
        from certbot._internal.lock import lock_dir
        return lock_dir(*args, **kwargs)

    def test_it(self):
        assert_raises = functools.partial(
            self.assertRaises, errors.LockError, self._call, self.tempdir)
        lock_path = os.path.join(self.tempdir, '.certbot.lock')
        test_util.lock_and_call(assert_raises, lock_path)


class LockFileTest(test_util.TempDirTestCase):
    """Tests for certbot._internal.lock.LockFile."""
    @classmethod
    def _call(cls, *args, **kwargs):
        from certbot._internal.lock import LockFile
        return LockFile(*args, **kwargs)

    def setUp(self):
        super().setUp()
        self.lock_path = os.path.join(self.tempdir, 'test.lock')

    def test_acquire_without_deletion(self):
        # acquire the lock in another process but don't delete the file
        child = multiprocessing.Process(target=self._call,
                                        args=(self.lock_path,))
        child.start()
        child.join()
        assert child.exitcode == 0
        assert os.path.exists(self.lock_path)

        # Test we're still able to properly acquire and release the lock
        self.test_removed()

    def test_contention(self):
        assert_raises = functools.partial(
            self.assertRaises, errors.LockError, self._call, self.lock_path)
        test_util.lock_and_call(assert_raises, self.lock_path)

    def test_locked_repr(self):
        lock_file = self._call(self.lock_path)
        try:
            locked_repr = repr(lock_file)
            self._test_repr_common(lock_file, locked_repr)
            assert 'acquired' in locked_repr
        finally:
            lock_file.release()

    def test_released_repr(self):
        lock_file = self._call(self.lock_path)
        lock_file.release()
        released_repr = repr(lock_file)
        self._test_repr_common(lock_file, released_repr)
        assert 'released' in released_repr

    def _test_repr_common(self, lock_file, lock_repr):
        assert lock_file.__class__.__name__ in lock_repr
        assert self.lock_path in lock_repr

    @test_util.skip_on_windows(
        'Race conditions on lock are specific to the non-blocking file access approach on Linux.')
    def test_race(self):
        should_delete = [True, False]
        # Normally os module should not be imported in certbot codebase except in certbot.compat
        # for the sake of compatibility over Windows and Linux.
        # We make an exception here, since test_race is a test function called only on Linux.
        from os import stat  # pylint: disable=os-module-forbidden

        def delete_and_stat(path):
            """Wrap os.stat and maybe delete the file first."""
            if path == self.lock_path and should_delete.pop(0):
                os.remove(path)
            return stat(path)

        with mock.patch('certbot._internal.lock.filesystem.os.stat') as mock_stat:
            mock_stat.side_effect = delete_and_stat
            self._call(self.lock_path)
        assert len(should_delete) == 0

    def test_removed(self):
        lock_file = self._call(self.lock_path)
        lock_file.release()
        assert not os.path.exists(self.lock_path)

    def test_unexpected_lockf_or_locking_err(self):
        if POSIX_MODE:
            mocked_function = 'certbot._internal.lock.fcntl.lockf'
        else:
            mocked_function = 'certbot._internal.lock.msvcrt.locking'
        msg = 'hi there'
        with mock.patch(mocked_function) as mock_lock:
            mock_lock.side_effect = OSError(msg)
            try:
                self._call(self.lock_path)
            except OSError as err:
                assert msg in str(err)
            else:  # pragma: no cover
                self.fail('IOError not raised')

    def test_unexpected_os_err(self):
        if POSIX_MODE:
            mock_function = 'certbot._internal.lock.filesystem.os.stat'
        else:
            mock_function = 'certbot._internal.lock.msvcrt.locking'
        # The only expected errno are ENOENT and EACCES in lock module.
        msg = 'hi there'
        with mock.patch(mock_function) as mock_os:
            mock_os.side_effect = OSError(msg)
            try:
                self._call(self.lock_path)
            except OSError as err:
                assert msg in str(err)
            else:  # pragma: no cover
                self.fail('OSError not raised')


if __name__ == "__main__":
    sys.exit(pytest.main(sys.argv[1:] + [__file__]))  # pragma: no cover