Merge lp:~ricardokirkner/locolander/celery-tasks into lp:locolander
- celery-tasks
- Merge into trunk
Proposed by
Ricardo Kirkner
Status: | Superseded |
---|---|
Proposed branch: | lp:~ricardokirkner/locolander/celery-tasks |
Merge into: | lp:locolander |
Prerequisite: | lp:~ricardokirkner/locolander/add-author |
Diff against target: |
385 lines (+293/-33) 7 files modified
locolander/locolanderweb/migrations/0003_auto__add_field_mergerequest_author.py (+87/-0) locolander/locolanderweb/models.py (+0/-4) locolander/locolanderweb/tasks.py (+52/-0) locolander/locolanderweb/tests/__init__.py (+5/-4) locolander/locolanderweb/tests/test_base.py (+1/-1) locolander/locolanderweb/tests/test_models.py (+10/-24) locolander/locolanderweb/tests/test_tasks.py (+138/-0) |
To merge this branch: | bzr merge lp:~ricardokirkner/locolander/celery-tasks |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
LocoLanderos | Pending | ||
Review via email: mp+174581@code.launchpad.net |
Commit message
added celery-based tasks
- run_project: processes all outstanding merge requests for a project
- run_request: runs tests and merges a merge requests
- scan_project: finds approved requests for a project
Description of the change
To post a comment you must log in.
- 24. By Ricardo Kirkner
-
merged add-author
- 25. By Ricardo Kirkner
-
fixes per review
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'locolander/locolanderweb/migrations/0003_auto__add_field_mergerequest_author.py' |
2 | --- locolander/locolanderweb/migrations/0003_auto__add_field_mergerequest_author.py 1970-01-01 00:00:00 +0000 |
3 | +++ locolander/locolanderweb/migrations/0003_auto__add_field_mergerequest_author.py 2013-07-14 15:26:27 +0000 |
4 | @@ -0,0 +1,87 @@ |
5 | +# -*- coding: utf-8 -*- |
6 | +from south.db import db |
7 | +from south.v2 import SchemaMigration |
8 | + |
9 | + |
10 | +class Migration(SchemaMigration): |
11 | + |
12 | + def forwards(self, orm): |
13 | + # Adding field 'MergeRequest.author' |
14 | + db.add_column(u'locolanderweb_mergerequest', 'author', |
15 | + self.gf('django.db.models.fields.TextField')(default=''), |
16 | + keep_default=False) |
17 | + |
18 | + |
19 | + def backwards(self, orm): |
20 | + # Deleting field 'MergeRequest.author' |
21 | + db.delete_column(u'locolanderweb_mergerequest', 'author') |
22 | + |
23 | + |
24 | + models = { |
25 | + u'auth.group': { |
26 | + 'Meta': {'object_name': 'Group'}, |
27 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
28 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
29 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
30 | + }, |
31 | + u'auth.permission': { |
32 | + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, |
33 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
34 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), |
35 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
36 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
37 | + }, |
38 | + u'auth.user': { |
39 | + 'Meta': {'object_name': 'User'}, |
40 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
41 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
42 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
43 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
44 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
45 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
46 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
47 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
48 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
49 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
50 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
51 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
52 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
53 | + }, |
54 | + u'contenttypes.contenttype': { |
55 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
56 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
57 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
58 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
59 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
60 | + }, |
61 | + u'locolanderweb.mergerequest': { |
62 | + 'Meta': {'object_name': 'MergeRequest'}, |
63 | + 'author': ('django.db.models.fields.TextField', [], {'default': "''"}), |
64 | + 'commit_message': ('django.db.models.fields.TextField', [], {}), |
65 | + 'date_completed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
66 | + 'date_created': ('django.db.models.fields.DateTimeField', [], {}), |
67 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
68 | + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['locolanderweb.Project']"}), |
69 | + 'reviewers': ('django.db.models.fields.TextField', [], {}), |
70 | + 'source': ('django.db.models.fields.TextField', [], {}), |
71 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '128'}), |
72 | + 'target': ('django.db.models.fields.TextField', [], {}) |
73 | + }, |
74 | + u'locolanderweb.project': { |
75 | + 'Meta': {'object_name': 'Project'}, |
76 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
77 | + 'name': ('django.db.models.fields.TextField', [], {}), |
78 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), |
79 | + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) |
80 | + }, |
81 | + u'locolanderweb.runlog': { |
82 | + 'Meta': {'object_name': 'RunLog'}, |
83 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
84 | + 'merge_request': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['locolanderweb.MergeRequest']"}), |
85 | + 'raw': ('django.db.models.fields.TextField', [], {}), |
86 | + 'return_code': ('django.db.models.fields.IntegerField', [], {}), |
87 | + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) |
88 | + } |
89 | + } |
90 | + |
91 | + complete_apps = ['locolanderweb'] |
92 | |
93 | === modified file 'locolander/locolanderweb/models.py' |
94 | --- locolander/locolanderweb/models.py 2013-07-14 15:26:27 +0000 |
95 | +++ locolander/locolanderweb/models.py 2013-07-14 15:26:27 +0000 |
96 | @@ -39,10 +39,6 @@ |
97 | |
98 | def approved_requests(self): |
99 | """Return approved merge proposals.""" |
100 | - mrs = self.service.approved_requests_for_project_url(self.url) |
101 | - for mr in mrs: |
102 | - MergeRequest.objects.get_or_create(project=self, **mr) |
103 | - |
104 | return MergeRequest.objects.filter(project=self, status=STATUS_PENDING) |
105 | |
106 | |
107 | |
108 | === added file 'locolander/locolanderweb/tasks.py' |
109 | --- locolander/locolanderweb/tasks.py 1970-01-01 00:00:00 +0000 |
110 | +++ locolander/locolanderweb/tasks.py 2013-07-14 15:26:27 +0000 |
111 | @@ -0,0 +1,52 @@ |
112 | +import os.path |
113 | +import subprocess |
114 | + |
115 | +from celery import task |
116 | + |
117 | +from locolanderweb.models import MergeRequest, RunLog |
118 | + |
119 | + |
120 | +@task |
121 | +def run_project(project): |
122 | + for request in project.approved_requests(): |
123 | + run_request(request) |
124 | + |
125 | + |
126 | +@task |
127 | +def run_request(request): |
128 | + project = request.project |
129 | + source = request.source |
130 | + target = request.target |
131 | + author = request.author |
132 | + message = request.commit_message |
133 | + |
134 | + cmd = [ |
135 | + os.path.realpath(os.path.join( |
136 | + os.path.abspath('.'), 'docker/scripts/locolander')), |
137 | + project.name, |
138 | + source, |
139 | + target, |
140 | + message, |
141 | + author, |
142 | + ] |
143 | + try: |
144 | + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
145 | + return_code = 0 |
146 | + error = False |
147 | + except subprocess.CalledProcessError as err: |
148 | + output = err.output |
149 | + return_code = err.returncode |
150 | + error = True |
151 | + |
152 | + RunLog.objects.create(merge_request=request, raw=output, |
153 | + return_code=return_code) |
154 | + |
155 | + # update status on merge request |
156 | + request.complete(error=error) |
157 | + |
158 | + |
159 | +@task |
160 | +def scan_project(project): |
161 | + mrs = project.service.approved_requests_for_project_url(project.url) |
162 | + for mr in mrs: |
163 | + MergeRequest.objects.get_or_create(project=project, **mr) |
164 | |
165 | === modified file 'locolander/locolanderweb/tests/__init__.py' |
166 | --- locolander/locolanderweb/tests/__init__.py 2013-06-21 15:34:22 +0000 |
167 | +++ locolander/locolanderweb/tests/__init__.py 2013-07-14 15:26:27 +0000 |
168 | @@ -1,4 +1,5 @@ |
169 | -from locolanderweb.tests.test_code_style import * |
170 | -from locolanderweb.tests.test_forms import * |
171 | -from locolanderweb.tests.test_models import * |
172 | -from locolanderweb.tests.test_views import * |
173 | +from .test_code_style import * |
174 | +from .test_forms import * |
175 | +from .test_models import * |
176 | +from .test_tasks import * |
177 | +from .test_views import * |
178 | |
179 | === modified file 'locolander/locolanderweb/tests/test_base.py' |
180 | --- locolander/locolanderweb/tests/test_base.py 2013-07-14 15:26:27 +0000 |
181 | +++ locolander/locolanderweb/tests/test_base.py 2013-07-14 15:26:27 +0000 |
182 | @@ -36,7 +36,7 @@ |
183 | |
184 | def make_project(self, name=None, user=None, url=None): |
185 | if name is None: |
186 | - name = self.make_random_string(prefix='project-') |
187 | + name = self.make_random_string(prefix='project') |
188 | if user is None: |
189 | user = self.make_user() |
190 | if url is None: |
191 | |
192 | === modified file 'locolander/locolanderweb/tests/test_models.py' |
193 | --- locolander/locolanderweb/tests/test_models.py 2013-07-14 15:26:27 +0000 |
194 | +++ locolander/locolanderweb/tests/test_models.py 2013-07-14 15:26:27 +0000 |
195 | @@ -15,20 +15,6 @@ |
196 | ) |
197 | from locolanderweb.tests.test_base import BaseTestCase |
198 | |
199 | -MERGE_REQUESTS = [ |
200 | - dict( |
201 | - source='source1', target='target1', reviewers='a,b,c', |
202 | - date_created=datetime(2013, 4, 23), commit_message='something 1', |
203 | - ), |
204 | - dict( |
205 | - source='source2', target='target2', reviewers='pepe', |
206 | - date_created=datetime(2012, 2, 29), commit_message='something else', |
207 | - ), |
208 | - dict( |
209 | - source='source3', target='target3', reviewers='pepe y pepa', |
210 | - date_created=datetime.utcnow(), commit_message='', |
211 | - ), |
212 | -] |
213 | |
214 | INVALID_URLS = ( |
215 | 'locolander', |
216 | @@ -99,16 +85,16 @@ |
217 | self.get_service_from_url.assert_called_once_with(self.obj.url) |
218 | |
219 | def test_approved_requests(self): |
220 | - assert MergeRequest.objects.all().count() == 0 |
221 | - |
222 | - service = self.get_service_from_url.return_value |
223 | - service.approved_requests_for_project_url.return_value = MERGE_REQUESTS |
224 | - |
225 | - result = self.obj.approved_requests() |
226 | - self.assertEqual(MergeRequest.objects.all().count(), len(result)) |
227 | - for mr in MERGE_REQUESTS: |
228 | - self.assertEqual( |
229 | - MergeRequest.objects.filter(**mr).count(), 1) |
230 | + assert MergeRequest.objects.filter(project=self.obj).count() == 0 |
231 | + |
232 | + result = self.obj.approved_requests() |
233 | + self.assertEqual(len(result), 0) |
234 | + |
235 | + # Create an approved merge request |
236 | + self.factory.make_merge_request(project=self.obj) |
237 | + |
238 | + result = self.obj.approved_requests() |
239 | + self.assertEqual(len(result), 1) |
240 | |
241 | |
242 | class MergeRequestTestCase(BaseTestCase): |
243 | |
244 | === added file 'locolander/locolanderweb/tests/test_tasks.py' |
245 | --- locolander/locolanderweb/tests/test_tasks.py 1970-01-01 00:00:00 +0000 |
246 | +++ locolander/locolanderweb/tests/test_tasks.py 2013-07-14 15:26:27 +0000 |
247 | @@ -0,0 +1,138 @@ |
248 | +import subprocess |
249 | +from datetime import datetime |
250 | +from mock import ANY, patch |
251 | + |
252 | +from locolanderweb.models import ( |
253 | + STATUS_ERROR, |
254 | + STATUS_MERGED, |
255 | + MergeRequest, |
256 | + RunLog, |
257 | +) |
258 | +from locolanderweb.tasks import run_project, run_request, scan_project |
259 | +from locolanderweb.tests.test_base import BaseTestCase |
260 | + |
261 | + |
262 | +MERGE_REQUESTS = [ |
263 | + dict( |
264 | + source='source1', target='target1', reviewers='a,b,c', |
265 | + date_created=datetime(2013, 4, 23), commit_message='something 1', |
266 | + ), |
267 | + dict( |
268 | + source='source2', target='target2', reviewers='pepe', |
269 | + date_created=datetime(2012, 2, 29), commit_message='something else', |
270 | + ), |
271 | + dict( |
272 | + source='source3', target='target3', reviewers='pepe y pepa', |
273 | + date_created=datetime.utcnow(), commit_message='', |
274 | + ), |
275 | +] |
276 | + |
277 | + |
278 | +class RunProjectTestCase(BaseTestCase): |
279 | + |
280 | + @patch('locolanderweb.tasks.run_request') |
281 | + def test_run_project(self, mock_run_request): |
282 | + project = self.factory.make_project() |
283 | + merge_request = self.factory.make_merge_request(project=project) |
284 | + |
285 | + run_project(project) |
286 | + mock_run_request.assert_called_once_with(merge_request) |
287 | + |
288 | + @patch('locolanderweb.tasks.run_request') |
289 | + def test_run_project_with_no_approved_requests(self, mock_run_request): |
290 | + project = self.factory.make_project() |
291 | + |
292 | + run_project(project) |
293 | + self.assertFalse(mock_run_request.called) |
294 | + |
295 | + |
296 | +class RunRequestTestCase(BaseTestCase): |
297 | + |
298 | + @patch('locolanderweb.tasks.subprocess.check_output') |
299 | + def test_run_request(self, mock_check_output): |
300 | + mock_check_output.return_value = 'SUCCESS' |
301 | + |
302 | + merge_request = self.factory.make_merge_request() |
303 | + |
304 | + run_request(merge_request) |
305 | + |
306 | + cmd = [ |
307 | + ANY, |
308 | + merge_request.project.name, |
309 | + merge_request.source, |
310 | + merge_request.target, |
311 | + merge_request.commit_message, |
312 | + merge_request.author, |
313 | + ] |
314 | + mock_check_output.assert_called_once_with( |
315 | + cmd, stderr=subprocess.STDOUT) |
316 | + real_cmd = mock_check_output.call_args[0][0][0] |
317 | + self.assertTrue(real_cmd.endswith('docker/scripts/locolander')) |
318 | + |
319 | + self.assertEqual(RunLog.objects.all().count(), 1) |
320 | + run_log = RunLog.objects.get(merge_request=merge_request) |
321 | + self.assertEqual(run_log.raw, 'SUCCESS') |
322 | + self.assertEqual(run_log.return_code, 0) |
323 | + |
324 | + # refresh MR object |
325 | + mr = MergeRequest.objects.get(pk=merge_request.pk) |
326 | + self.assertEqual(mr.status, STATUS_MERGED) |
327 | + |
328 | + @patch('locolanderweb.tasks.subprocess.check_output') |
329 | + def test_run_request_with_error(self, mock_check_output): |
330 | + merge_request = self.factory.make_merge_request() |
331 | + |
332 | + cmd = [ |
333 | + ANY, |
334 | + merge_request.project.name, |
335 | + merge_request.source, |
336 | + merge_request.target, |
337 | + merge_request.commit_message, |
338 | + merge_request.author, |
339 | + ] |
340 | + error = subprocess.CalledProcessError(1, cmd, output='FAILURE') |
341 | + mock_check_output.side_effect = error |
342 | + |
343 | + run_request(merge_request) |
344 | + |
345 | + mock_check_output.assert_called_once_with( |
346 | + cmd, stderr=subprocess.STDOUT) |
347 | + real_cmd = mock_check_output.call_args[0][0][0] |
348 | + self.assertTrue(real_cmd.endswith('docker/scripts/locolander')) |
349 | + |
350 | + self.assertEqual(RunLog.objects.all().count(), 1) |
351 | + run_log = RunLog.objects.get(merge_request=merge_request) |
352 | + self.assertEqual(run_log.raw, 'FAILURE') |
353 | + self.assertEqual(run_log.return_code, 1) |
354 | + |
355 | + # refresh MR object |
356 | + mr = MergeRequest.objects.get(pk=merge_request.pk) |
357 | + self.assertEqual(mr.status, STATUS_ERROR) |
358 | + |
359 | + |
360 | +class ScanProjectTestCase(BaseTestCase): |
361 | + |
362 | + def test_scan_project(self): |
363 | + project = self.factory.make_project() |
364 | + |
365 | + service = self.get_service_from_url.return_value |
366 | + service.approved_requests_for_project_url.return_value = MERGE_REQUESTS |
367 | + |
368 | + scan_project(project) |
369 | + |
370 | + self.assertEqual(MergeRequest.objects.filter(project=project).count(), |
371 | + len(MERGE_REQUESTS)) |
372 | + for mr in MERGE_REQUESTS: |
373 | + self.assertEqual( |
374 | + MergeRequest.objects.filter(**mr).count(), 1) |
375 | + |
376 | + def test_scan_project_with_no_approved_request(self): |
377 | + project = self.factory.make_project() |
378 | + |
379 | + service = self.get_service_from_url.return_value |
380 | + service.approved_requests_for_project_url.return_value = [] |
381 | + |
382 | + scan_project(project) |
383 | + |
384 | + self.assertEqual(MergeRequest.objects.filter(project=project).count(), |
385 | + 0) |