~psivaa/uci-engine/lander-jenkins-with-proxy

« back to all changes in this revision

Viewing changes to ppa-assigner/ppa_assigner/tests.py

  • Committer: Tarmac
  • Author(s): Andy Doan
  • Date: 2013-12-06 23:25:38 UTC
  • mfrom: (6.1.22 ppa-assigner)
  • Revision ID: tarmac-20131206232538-auo3c8yhml7wbnee
[r=Francis Ginther] adds first pass of ppa-assigner

more changes to come following design updates.  from Andy Doan

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Ubuntu CI Services
 
2
# Copyright 2013 Canonical Ltd.
 
3
 
 
4
# This program is free software: you can redistribute it and/or modify it
 
5
# under the terms of the GNU Affero General Public License version 3, as
 
6
# published by the Free Software Foundation.
 
7
 
 
8
# This program is distributed in the hope that it will be useful, but
 
9
# WITHOUT ANY WARRANTY; without even the implied warranties of
 
10
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
11
# PURPOSE.  See the GNU Affero General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU Affero General Public License
 
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 
 
16
import contextlib
 
17
import datetime
 
18
import json
 
19
import threading
 
20
import time
 
21
import urllib2
 
22
 
 
23
import mock
 
24
 
 
25
from django.conf import settings
 
26
from django.db import connection, transaction
 
27
from django.db.utils import ConnectionHandler, DEFAULT_DB_ALIAS
 
28
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
 
29
 
 
30
from ci_utils.tastypie.test import TastypieTestCase
 
31
from ppa_assigner.models import PPA, LaunchpadError
 
32
 
 
33
 
 
34
class TestModel(TestCase):
 
35
 
 
36
    def setUp(self):
 
37
        super(TestModel, self).setUp()
 
38
        for n in range(5):
 
39
            PPA.objects.create(name='ci_pool-%s' % str(n).zfill(3))
 
40
 
 
41
    @mock.patch('ppa_assigner.models.PPA.clean_artifacts')
 
42
    def testReserveNoClean(self, clean):
 
43
        p = PPA.reserve('testlockname')
 
44
        self.assertIsNotNone(p)
 
45
        self.assertFalse(clean.called)
 
46
        self.assertEqual('testlockname', p.lockname)
 
47
 
 
48
        reserved = PPA.objects.filter(reserved=True).count()
 
49
        self.assertEqual(1, reserved)
 
50
 
 
51
    def testReserveTimestamp(self):
 
52
        p = PPA.reserve()
 
53
        delta = datetime.datetime.now() - p.timestamp
 
54
        # the lock should have happened in less than a second
 
55
        self.assertLess(delta.seconds, 1)
 
56
 
 
57
    @mock.patch('ppa_assigner.models.PPA.clean_artifacts')
 
58
    def testReserveClean(self, clean):
 
59
        p = PPA.reserve('testlockname', True)
 
60
        self.assertIsNotNone(p)
 
61
        self.assertTrue(clean.called)
 
62
        self.assertEqual('testlockname', p.lockname)
 
63
 
 
64
        reserved = PPA.objects.filter(reserved=True).count()
 
65
        self.assertEqual(1, reserved)
 
66
 
 
67
    @mock.patch('ppa_assigner.models.PPA.clean_artifacts')
 
68
    def testReserveCleanFail(self, clean):
 
69
        '''ensure ppa isn't locked if clean operation fails.'''
 
70
        clean.side_effect = RuntimeError
 
71
        locked_before = PPA.objects.filter(reserved=True).count()
 
72
        with self.assertRaises(LaunchpadError):
 
73
            PPA.reserve(clean=True)
 
74
 
 
75
        locked_after = PPA.objects.filter(reserved=True).count()
 
76
        self.assertEqual(locked_before, locked_after)
 
77
 
 
78
    def testReserveNoFree(self):
 
79
        PPA.objects.update(reserved=True)
 
80
        with self.assertRaises(PPA.DoesNotExist):
 
81
            PPA.reserve()
 
82
 
 
83
    def testCleanOperation(self):
 
84
        # TODO more logic for this
 
85
        PPA.reserve(clean=True)
 
86
 
 
87
 
 
88
class TestConcurrency(TransactionTestCase):
 
89
    # inspired by, but heavily modified:
 
90
    # https://github.com/django/django/blob/master/tests/select_for_update/
 
91
 
 
92
    def setUp(self):
 
93
        super(TestConcurrency, self).setUp()
 
94
        transaction.enter_transaction_management()
 
95
        for n in range(5):
 
96
            PPA.objects.create(name='ci_pool-%s' % str(n).zfill(3))
 
97
        transaction.commit()
 
98
        self.addCleanup(transaction.abort)
 
99
 
 
100
    @contextlib.contextmanager
 
101
    def blocking_transaction(self):
 
102
        # We need another database connection to test that one connection
 
103
        # issuing a SELECT ... FOR UPDATE will block.
 
104
        con = ConnectionHandler(settings.DATABASES)[DEFAULT_DB_ALIAS]
 
105
        con.enter_transaction_management()
 
106
        cursor = con.cursor()
 
107
        sql = 'SELECT * FROM %(db_table)s %(for_update)s;' % {
 
108
            'db_table': PPA._meta.db_table,
 
109
            'for_update': con.ops.for_update_sql(),
 
110
        }
 
111
        cursor.execute(sql, ())
 
112
        cursor.fetchone()
 
113
        try:
 
114
            yield
 
115
        finally:
 
116
            con.rollback()
 
117
            con.close()
 
118
 
 
119
    def _reserve(self, params):
 
120
        params['signal'].set()
 
121
        start = time.time()
 
122
        PPA.reserve()
 
123
        params['time'] = time.time() - start
 
124
        # connections are per-thread, so close this
 
125
        connection.close()
 
126
 
 
127
    @skipUnlessDBFeature('has_select_for_update')
 
128
    def testReserveConcurrency(self):
 
129
        params = {'signal': threading.Event()}
 
130
        t = threading.Thread(target=self._reserve, args=(params,))
 
131
        locktime = 2
 
132
        with self.blocking_transaction():
 
133
            t.start()
 
134
            # wait for thread to let us know its running
 
135
            params['signal'].wait()
 
136
            time.sleep(locktime)
 
137
        t.join()
 
138
        # it won't be exact but it should be pretty close
 
139
        self.assertLess(abs(locktime - params['time']), 0.05)
 
140
 
 
141
 
 
142
class TestApi(TastypieTestCase):
 
143
 
 
144
    def setUp(self):
 
145
        super(TestApi, self).setUp()
 
146
        for n in range(5):
 
147
            PPA.objects.create(name='ci_pool-%s' % str(n).zfill(3))
 
148
 
 
149
        TastypieTestCase.setUp(self, '/api/v1')
 
150
 
 
151
    def testGetAll(self):
 
152
        obj = self.get('ppa/')
 
153
        self.assertEqual(PPA.objects.all().count(), len(obj['objects']))
 
154
 
 
155
    def testGetQuery(self):
 
156
        p = PPA.reserve()
 
157
        obj = self.get('ppa/', {'reserved': False})
 
158
        count = PPA.objects.filter(reserved=False).count()
 
159
        self.assertEqual(count, len(obj['objects']))
 
160
 
 
161
        obj = self.getResource('ppa/', {'reserved': True})
 
162
        self.assertEqual(p.name, obj['name'])
 
163
 
 
164
        p.lockname = 'foo'
 
165
        p.save()
 
166
        obj = self.getResource('ppa/', {'reserved': True})
 
167
        self.assertEqual(p.lockname, obj['lockname'])
 
168
 
 
169
    def testReserveNoClean(self):
 
170
        params = {'lockname': 'foo'}
 
171
        obj = self.createResource('ppa/', params)
 
172
        self.assertEqual(obj['lockname'], params['lockname'])
 
173
        self.assertTrue(obj['reserved'])
 
174
 
 
175
    @mock.patch('ppa_assigner.models.PPA.clean_artifacts')
 
176
    def testReserveClean(self, clean):
 
177
        obj = self.createResource('ppa/', {'lockname': 'foo', 'clean': True})
 
178
        self.assertTrue(obj['reserved'])
 
179
        self.assertTrue(clean.called)
 
180
 
 
181
    def testReserveNoFree(self):
 
182
        PPA.objects.update(reserved=True)
 
183
        params = {'lockname': 'foo'}
 
184
        with self.assertRaises(PPA.DoesNotExist):
 
185
            self.post('ppa/', params)
 
186
 
 
187
    def testFree(self):
 
188
        p = PPA.reserve()
 
189
        self.patch('ppa/%d/' % p.id, {'reserved': False})
 
190
        p = PPA.objects.get(pk=p.id)
 
191
        self.assertFalse(p.reserved)
 
192
 
 
193
    @mock.patch('urllib2.urlopen')
 
194
    def testPopulateFail(self, urlopen):
 
195
        '''ensure a lp error is handled correctly.'''
 
196
        def side_effect(arg):
 
197
            raise urllib2.URLError('foo')
 
198
        urlopen.side_effect = side_effect
 
199
        resp = self.client.patch(
 
200
            self._resource('ppa/'),
 
201
            data={'populate': True},
 
202
            authentication=self.auth)
 
203
        self.assertEqual(500, resp.status_code)
 
204
        self.assertEqual('<urlopen error foo>', resp.content)
 
205
 
 
206
    @mock.patch('urllib2.urlopen')
 
207
    def testPopulateMerge(self, urlopen):
 
208
        data = {
 
209
            'total_size': 2,
 
210
            'entries': [
 
211
                {'name': 'ppa1'},
 
212
                {'name': 'ppa2'},
 
213
            ]
 
214
        }
 
215
 
 
216
        resp = mock.Mock()
 
217
        resp.read.return_value = json.dumps(data)
 
218
        urlopen.return_value = resp
 
219
        user = settings.LAUNCHPAD_PPA_USER
 
220
        self.patch('ppa/', {'populate': True})
 
221
        expected = ['ppa:%s/%s' % (user, x['name']) for x in data['entries']]
 
222
        found = [x.name for x in PPA.objects.all().order_by('name')]
 
223
        self.assertListEqual(expected, found)