• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from __future__ import absolute_import
6from __future__ import division
7from __future__ import print_function
8
9import collections
10import contextlib
11import logging
12import os
13import signal
14import socket
15import sys
16
17import mock
18import pytest
19import subprocess32
20
21from lucifer import leasing
22
23logger = logging.getLogger(__name__)
24
25# 9999-01-01T00:00:00+00:00
26_THE_END = 253370764800
27
28
29def test_obtain_lease(tmpdir):
30    """Test obtain_lease.
31
32    Provides basic test coverage metrics.  The slower subprocess tests
33    provide better functional coverage.
34    """
35    path = _make_lease(tmpdir, 124)
36    with leasing.obtain_lease(path):
37        pass
38    assert not os.path.exists(path)
39
40
41@pytest.mark.slow
42def test_obtain_lease_succesfully_removes_file(tmpdir):
43    """Test obtain_lease cleans up lease file if successful."""
44    path = _make_lease(tmpdir, 124)
45    with _obtain_lease(path) as lease_proc:
46        lease_proc.finish()
47    assert not os.path.exists(path)
48
49
50@pytest.mark.slow
51def test_obtain_lease_with_error_removes_files(tmpdir):
52    """Test obtain_lease removes file if it errors."""
53    path = _make_lease(tmpdir, 124)
54    with _obtain_lease(path) as lease_proc:
55        lease_proc.proc.send_signal(signal.SIGINT)
56        lease_proc.proc.wait()
57    assert not os.path.exists(path)
58
59
60@pytest.mark.slow
61def test_Lease__expired(tmpdir, end_time):
62    """Test get_expired_leases()."""
63    _make_lease(tmpdir, 123)
64    path = _make_lease(tmpdir, 124)
65    with _obtain_lease(path):
66        leases = _leases_dict(str(tmpdir))
67        assert leases[123].expired()
68        assert not leases[124].expired()
69
70
71def test_unlocked_fresh_leases_are_not_expired(tmpdir):
72    """Test get_expired_leases()."""
73    path = _make_lease(tmpdir, 123)
74    os.utime(path, (_THE_END, _THE_END))
75    leases = _leases_dict(str(tmpdir))
76    assert not leases[123].expired()
77
78
79def test_leases_iter_with_sock_files(tmpdir):
80    """Test leases_iter() ignores sock files."""
81    _make_lease(tmpdir, 123)
82    tmpdir.join('124.sock').write('')
83    leases = _leases_dict(str(tmpdir))
84    assert 124 not in leases
85
86
87def test_Job_cleanup(tmpdir):
88    """Test Job.cleanup()."""
89    lease_path = _make_lease(tmpdir, 123)
90    tmpdir.join('123.sock').write('')
91    sock_path = str(tmpdir.join('123.sock'))
92    for job in leasing.leases_iter(str(tmpdir)):
93        logger.debug('Cleaning up %r', job)
94        job.cleanup()
95    assert not os.path.exists(lease_path)
96    assert not os.path.exists(sock_path)
97
98
99def test_Job_cleanup_does_not_raise_on_error(tmpdir):
100    """Test Job.cleanup()."""
101    lease_path = _make_lease(tmpdir, 123)
102    tmpdir.join('123.sock').write('')
103    sock_path = str(tmpdir.join('123.sock'))
104    for job in leasing.leases_iter(str(tmpdir)):
105        os.unlink(lease_path)
106        os.unlink(sock_path)
107        job.cleanup()
108
109
110@pytest.mark.slow
111def test_Job_abort(tmpdir):
112    """Test Job.abort()."""
113    _make_lease(tmpdir, 123)
114    with _abort_socket(tmpdir, 123) as proc:
115        expired = list(leasing.leases_iter(str(tmpdir)))
116        assert len(expired) > 0
117        for job in expired:
118            job.abort()
119        proc.wait()
120        assert proc.returncode == 0
121
122
123@pytest.mark.slow
124def test_Job_abort_with_closed_socket(tmpdir):
125    """Test Job.abort() with closed socket."""
126    _make_lease(tmpdir, 123)
127    with _abort_socket(tmpdir, 123) as proc:
128        proc.terminate()
129        proc.wait()
130        expired = list(leasing.leases_iter(str(tmpdir)))
131        assert len(expired) > 0
132        for job in expired:
133            with pytest.raises(socket.error):
134                job.abort()
135
136
137@pytest.fixture
138def end_time():
139    """Mock out time.time to return a time in the future."""
140    with mock.patch('time.time', return_value=_THE_END) as t:
141        yield t
142
143
144_LeaseProc = collections.namedtuple('_LeaseProc', 'finish proc')
145
146
147@contextlib.contextmanager
148def _obtain_lease(path):
149    """Lock a lease file.
150
151    Yields a _LeaseProc.  finish is a function that can be called to
152    finish the process normally.  proc is a Popen instance.
153
154    This uses a slow subprocess; any test that uses this should be
155    marked slow.
156    """
157    with subprocess32.Popen(
158            [sys.executable, '-um',
159             'lucifer.cmd.test.obtain_lease', path],
160            stdin=subprocess32.PIPE,
161            stdout=subprocess32.PIPE) as proc:
162        # Wait for lock grab.
163        proc.stdout.readline()
164
165        def finish():
166            """Finish lease process normally."""
167            proc.stdin.write('\n')
168            # Wait for lease release.
169            proc.stdout.readline()
170        try:
171            yield _LeaseProc(finish, proc)
172        finally:
173            proc.terminate()
174
175
176@contextlib.contextmanager
177def _abort_socket(tmpdir, job_id):
178    """Open a testing abort socket and listener for a job.
179
180    As a context manager, returns the Popen instance for the listener
181    process when entering.
182
183    This uses a slow subprocess; any test that uses this should be
184    marked slow.
185    """
186    path = os.path.join(str(tmpdir), '%d.sock' % job_id)
187    logger.debug('Making abort socket at %s', path)
188    with subprocess32.Popen(
189            [sys.executable, '-um',
190             'lucifer.cmd.test.abort_socket', path],
191            stdout=subprocess32.PIPE) as proc:
192        # Wait for socket bind.
193        proc.stdout.readline()
194        try:
195            yield proc
196        finally:
197            proc.terminate()
198
199
200def _leases_dict(jobdir):
201    """Convenience method for tests."""
202    return {lease.id: lease for lease
203            in leasing.leases_iter(jobdir)}
204
205
206def _make_lease(tmpdir, job_id):
207    return _make_lease_file(str(tmpdir), job_id)
208
209
210def _make_lease_file(jobdir, job_id):
211    """Make lease file corresponding to a job.
212
213    @param jobdir: job lease file directory
214    @param job_id: Job ID
215    """
216    path = os.path.join(jobdir, str(job_id))
217    with open(path, 'w'):
218        pass
219    return path
220
221
222class _TestError(Exception):
223    """Error for tests."""
224