Merge lp:~doanac/qa-dashboard/live-status-base into lp:qa-dashboard

Proposed by Andy Doan
Status: Merged
Approved by: Chris Johnston
Approved revision: 690
Merged at revision: 683
Proposed branch: lp:~doanac/qa-dashboard/live-status-base
Merge into: lp:qa-dashboard
Diff against target: 615 lines (+386/-11)
10 files modified
common/templatetags/dashboard_extras.py (+13/-0)
common/utils.py (+4/-2)
qa_dashboard/settings.py (+2/-2)
smokeng/admin.py (+2/-1)
smokeng/management/commands/jenkins_pull_smokeng.py (+6/-1)
smokeng/migrations/0008_auto__add_field_smokeresult_status.py (+124/-0)
smokeng/models.py (+76/-1)
smokeng/tables.py (+10/-3)
smokeng/tests.py (+144/-1)
smokeng/views.py (+5/-0)
To merge this branch: bzr merge lp:~doanac/qa-dashboard/live-status-base
Reviewer Review Type Date Requested Status
Chris Johnston Approve
PS Jenkins bot continuous-integration Approve
Joe Talbott Approve
Review via email: mp+196167@code.launchpad.net

Commit message

This provides the base model and view updates required for live-status of smoke jobs. It adds a new "status" field to results so we can see if they are:

 queued: waiting for jenkins to run the test
 running: jenkins is running the test
 syncing: the test is complete with results, but hasn't made it to the public jenkins (and pulled by our scripts)
 complete: its gone through the pull-script and we have all its job artifacts

Description of the change

This provides the base model and view updates required for live-status of smoke jobs. It adds a new "status" field to results so we can see if they are:

 queued: waiting for jenkins to run the test
 running: jenkins is running the test
 syncing: the test is complete with results, but hasn't made it to the public jenkins (and pulled by our scripts)
 complete: its gone through the pull-script and we have all its job artifacts

To post a comment you must log in.
Revision history for this message
Andy Doan (doanac) wrote :

it might be easier reading commit by commit instead of the full diff.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:689
http://s-jenkins.ubuntu-ci:8080/job/dashboard-ci/258/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/dashboard-ci/258/rebuild

review: Approve (continuous-integration)
Revision history for this message
Chris Johnston (cjohnston) wrote :

I don't really have any issues.. I wouldn't mind http://paste.ubuntu.com/6471885/ being applied though

Revision history for this message
Joe Talbott (joetalbott) wrote :

+1

review: Approve
690. By Andy Doan

add admin filtering as per chris

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:690
http://s-jenkins.ubuntu-ci:8080/job/dashboard-ci/259/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/dashboard-ci/259/rebuild

review: Approve (continuous-integration)
Revision history for this message
Chris Johnston (cjohnston) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'common/templatetags/dashboard_extras.py'
2--- common/templatetags/dashboard_extras.py 2013-10-10 16:11:46 +0000
3+++ common/templatetags/dashboard_extras.py 2013-11-25 18:08:44 +0000
4@@ -22,6 +22,7 @@
5 from common.plugin_helper import ExtensionManager
6
7 from smokeng.config import get_internal_jenkins
8+from smokeng.models import SmokeResult
9
10 register = template.Library()
11
12@@ -216,3 +217,15 @@
13 jenkins = get_internal_jenkins(variant='')
14 jenkins_url = (jenkins + m.group(2) + job + build)
15 return jenkins_url
16+
17+
18+@register.filter
19+def smokeresult_ran_at(smokeresult):
20+ status = smokeresult['status']
21+ if status == SmokeResult.COMPLETE:
22+ return smokeresult['ran_at']
23+ else:
24+ for s in SmokeResult.STATUS_CHOICES:
25+ if s[0] == status:
26+ return s[1]
27+ raise RuntimeError('Unexpected smokeresult status: %d' % status)
28
29=== modified file 'common/utils.py'
30--- common/utils.py 2013-11-05 22:44:55 +0000
31+++ common/utils.py 2013-11-25 18:08:44 +0000
32@@ -221,7 +221,7 @@
33 image.save()
34
35
36-def split_build_number(image):
37+def split_build_number(image, save=True):
38 if ':' in image.build_number:
39 bn_data = image.build_number.split(':')
40 bn_parts = len(bn_data)
41@@ -241,7 +241,9 @@
42 else:
43 image.rootfs_id = image.build_number
44
45- image.save()
46+ if save:
47+ image.save()
48+
49
50 def txstatsd_interval(metric, append_days=False):
51 def dec(func):
52
53=== modified file 'qa_dashboard/settings.py'
54--- qa_dashboard/settings.py 2013-11-05 22:44:55 +0000
55+++ qa_dashboard/settings.py 2013-11-25 18:08:44 +0000
56@@ -190,8 +190,6 @@
57 'south',
58 )
59
60-INSTALLED_APPS = PLUGIN_APPS + LOCAL_APPS + INSTALLED_APPS
61-
62 TEST_RUNNER = "qa_dashboard.local_tests.LocalAppsTestSuiteRunner"
63
64 AUTHENTICATION_BACKENDS = (
65@@ -319,3 +317,5 @@
66 from debug_settings import *
67 except ImportError:
68 pass
69+
70+INSTALLED_APPS = PLUGIN_APPS + LOCAL_APPS + INSTALLED_APPS
71
72=== modified file 'smokeng/admin.py'
73--- smokeng/admin.py 2013-08-16 15:20:39 +0000
74+++ smokeng/admin.py 2013-11-25 18:08:44 +0000
75@@ -36,6 +36,7 @@
76 list_display = (
77 'image',
78 'name',
79+ 'status',
80 'fail_count',
81 'error_count',
82 'pass_count',
83@@ -43,7 +44,7 @@
84 'ran_at',
85 'publish',
86 )
87- list_filter = ['publish', 'name']
88+ list_filter = ['publish', 'status', 'name']
89 search_fields = ['name']
90 date_hierarchy = 'ran_at'
91 ordering = ['image']
92
93=== modified file 'smokeng/management/commands/jenkins_pull_smokeng.py'
94--- smokeng/management/commands/jenkins_pull_smokeng.py 2013-10-22 16:36:32 +0000
95+++ smokeng/management/commands/jenkins_pull_smokeng.py 2013-11-25 18:08:44 +0000
96@@ -13,6 +13,7 @@
97 # You should have received a copy of the GNU Affero General Public License
98 # along with this program. If not, see <http://www.gnu.org/licenses/>.
99
100+import datetime
101 import urllib2
102 import re
103 import logging
104@@ -215,23 +216,27 @@
105 result, new_result = SmokeResult.objects.get_or_create(
106 image=self.image,
107 jenkins_build=self.install_data['jenkins_build'],
108- ran_at=dashboard_data['ran_at'],
109 name=self.install_data['test_name'],
110 defaults=dict(
111+ ran_at=dashboard_data['ran_at'],
112 pass_count=dashboard_data['passes'],
113 fail_count=dashboard_data['failures'],
114 error_count=dashboard_data['errors'],
115 total_count=dashboard_data['total'],
116 pass_rate=pass_rate,
117+ status=SmokeResult.COMPLETE,
118 )
119 )
120
121 if not new_result:
122+ result.ran_at = datetime.datetime.strptime(
123+ dashboard_data['ran_at'], '%Y-%m-%d %H:%M:%S')
124 result.pass_count = dashboard_data['passes']
125 result.fail_count = dashboard_data['failures']
126 result.error_count = dashboard_data['errors']
127 result.total_count = dashboard_data['total']
128 result.pass_rate = pass_rate
129+ result.status = SmokeResult.COMPLETE
130 result.save()
131
132 all_results = SmokeResult.objects.filter(
133
134=== added file 'smokeng/migrations/0008_auto__add_field_smokeresult_status.py'
135--- smokeng/migrations/0008_auto__add_field_smokeresult_status.py 1970-01-01 00:00:00 +0000
136+++ smokeng/migrations/0008_auto__add_field_smokeresult_status.py 2013-11-25 18:08:44 +0000
137@@ -0,0 +1,124 @@
138+# -*- coding: utf-8 -*-
139+import datetime
140+from south.db import db
141+from south.v2 import SchemaMigration
142+from django.db import models
143+
144+
145+class Migration(SchemaMigration):
146+
147+ def forwards(self, orm):
148+ # Adding field 'SmokeResult.status'
149+ db.add_column('smoke_results', 'status',
150+ self.gf('django.db.models.fields.IntegerField')(default=3),
151+ keep_default=False)
152+
153+
154+ def backwards(self, orm):
155+ # Deleting field 'SmokeResult.status'
156+ db.delete_column('smoke_results', 'status')
157+
158+
159+ models = {
160+ u'common.bug': {
161+ 'Meta': {'object_name': 'Bug', 'db_table': "'bugs'"},
162+ 'assignee': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'null': 'True'}),
163+ 'bug_no': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
164+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
165+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
166+ 'importance': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '4096'}),
167+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
168+ 'project': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'null': 'True'}),
169+ 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
170+ 'status': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '4096'}),
171+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'null': 'True'}),
172+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
173+ },
174+ u'common.jenkinsbuild': {
175+ 'Meta': {'unique_together': "(('job', 'build_number'),)", 'object_name': 'JenkinsBuild', 'db_table': "'jenkins_builds'"},
176+ 'bugs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'build_bugs'", 'symmetrical': 'False', 'to': u"orm['common.Bug']"}),
177+ 'build_description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '4096'}),
178+ 'build_number': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
179+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
180+ 'failed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
181+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
182+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
183+ 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['common.JenkinsJob']"}),
184+ 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
185+ 'ran_at': ('django.db.models.fields.DateTimeField', [], {}),
186+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
187+ },
188+ u'common.jenkinsjob': {
189+ 'Meta': {'object_name': 'JenkinsJob', 'db_table': "'jenkins_jobs'"},
190+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
191+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
192+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
193+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '4096'}),
194+ 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
195+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
196+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '4096'})
197+ },
198+ u'smokeng.smokeimage': {
199+ 'Meta': {'object_name': 'SmokeImage', 'db_table': "'smoke_images'"},
200+ 'arch': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
201+ 'build_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '4096'}),
202+ 'build_number': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
203+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
204+ 'customization_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '4096'}),
205+ 'device_specific_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '4096'}),
206+ 'flavor': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
207+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
209+ 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
210+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
211+ 'rootfs_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '4096'}),
212+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
213+ 'variant': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
214+ },
215+ u'smokeng.smokeresult': {
216+ 'Meta': {'object_name': 'SmokeResult', 'db_table': "'smoke_results'"},
217+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
218+ 'error_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
219+ 'fail_count': ('django.db.models.fields.IntegerField', [], {}),
220+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
221+ 'image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['smokeng.SmokeImage']"}),
222+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
223+ 'jenkins_build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['common.JenkinsBuild']"}),
224+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
225+ 'pass_count': ('django.db.models.fields.IntegerField', [], {}),
226+ 'pass_rate': ('django.db.models.fields.FloatField', [], {'default': '0.0'}),
227+ 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
228+ 'ran_at': ('django.db.models.fields.DateTimeField', [], {}),
229+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '3'}),
230+ 'total_count': ('django.db.models.fields.IntegerField', [], {}),
231+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
232+ },
233+ u'smokeng.smokeresultbug': {
234+ 'Meta': {'object_name': 'SmokeResultBug', 'db_table': "'smoke_result_bugs'"},
235+ 'bug': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['common.Bug']"}),
236+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
237+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
238+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
239+ 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
240+ 'result': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['smokeng.SmokeResult']"}),
241+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
242+ },
243+ u'smokeng.smoketestresult': {
244+ 'Meta': {'object_name': 'SmokeTestResult', 'db_table': "'smoke_tests'"},
245+ 'command': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
246+ 'command_type': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
247+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
248+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
250+ 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
251+ 'result': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['smokeng.SmokeResult']"}),
252+ 'returncode': ('django.db.models.fields.IntegerField', [], {}),
253+ 'stderr': ('django.db.models.fields.TextField', [], {'null': 'True'}),
254+ 'stdout': ('django.db.models.fields.TextField', [], {'null': 'True'}),
255+ 'testcase': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
256+ 'testsuite': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
257+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
258+ }
259+ }
260+
261+ complete_apps = ['smokeng']
262\ No newline at end of file
263
264=== modified file 'smokeng/models.py'
265--- smokeng/models.py 2013-10-22 17:54:35 +0000
266+++ smokeng/models.py 2013-11-25 18:08:44 +0000
267@@ -13,6 +13,8 @@
268 # You should have received a copy of the GNU Affero General Public License
269 # along with this program. If not, see <http://www.gnu.org/licenses/>.
270
271+import datetime
272+
273 from django.db import models
274
275 from common.models import (
276@@ -21,6 +23,8 @@
277 JenkinsBuild,
278 )
279
280+from common.utils import split_build_number
281+
282
283 class SmokeImage(DashboardBaseModel):
284 class Meta:
285@@ -41,6 +45,12 @@
286 arch = models.CharField(max_length=4096)
287 variant = models.CharField(max_length=4096)
288
289+ def save(self, **kwargs):
290+ '''ensure we have build_number parts'''
291+ if not self.build_id:
292+ split_build_number(self, False)
293+ return super(SmokeImage, self).save(**kwargs)
294+
295 def __unicode__(self):
296 return "{} {} {} {}".format(
297 self.release,
298@@ -74,6 +84,7 @@
299 'total_count',
300 'name',
301 'ran_at',
302+ 'status',
303 'jenkins_build__job__url',
304 'jenkins_build__build_number',
305 )
306@@ -124,7 +135,8 @@
307 if r['total_count']:
308 r['pass_rate'] = float(r['pass_count']) / r['total_count']
309 r['pass_rate_pct'] = 100 * r['pass_rate']
310- passrates.append(r['pass_rate'])
311+ if r['status'] >= SmokeResult.SYNCING:
312+ passrates.append(r['pass_rate'])
313
314 for b in bugs:
315 if b['result__id'] == r['id']:
316@@ -149,6 +161,19 @@
317 class SmokeResult(DashboardBaseModel):
318 class Meta:
319 db_table = 'smoke_results'
320+
321+ QUEUED = 0
322+ RUNNING = 1
323+ SYNCING = 2
324+ COMPLETE = 3
325+
326+ STATUS_CHOICES = (
327+ (QUEUED, 'Queued'),
328+ (RUNNING, 'Running'),
329+ (SYNCING, 'Syncing'),
330+ (COMPLETE, 'Complete'),
331+ )
332+
333 image = models.ForeignKey(SmokeImage)
334 jenkins_build = models.ForeignKey(JenkinsBuild)
335 name = models.CharField(max_length=4096)
336@@ -157,8 +182,58 @@
337 pass_count = models.IntegerField()
338 total_count = models.IntegerField()
339 ran_at = models.DateTimeField('date run')
340+ status = models.IntegerField(choices=STATUS_CHOICES, default=COMPLETE)
341 pass_rate = models.FloatField(default=0.0)
342
343+ @staticmethod
344+ def create_queued_results(image, build, tests):
345+ ran_at = datetime.datetime.now()
346+ for test in tests:
347+ result, created = SmokeResult.objects.get_or_create(
348+ image=image,
349+ name=test,
350+ defaults={
351+ 'fail_count': 0,
352+ 'error_count': 0,
353+ 'pass_count': 0,
354+ 'total_count': 0,
355+ 'ran_at': ran_at,
356+ 'jenkins_build': build,
357+ 'status': SmokeResult.QUEUED,
358+ },
359+ )
360+ if not created:
361+ result.status = SmokeResult.QUEUED
362+ result.ran_at = ran_at
363+ result.jenkins_build = build
364+ result.save()
365+
366+ @staticmethod
367+ def set_running(image, build, tests):
368+ for test in tests:
369+ result = SmokeResult.objects.get(
370+ image=image,
371+ jenkins_build=build,
372+ name=test,
373+ )
374+ result.ran_at = datetime.datetime.now()
375+ result.status = SmokeResult.RUNNING
376+ result.save()
377+
378+ @staticmethod
379+ def set_syncing(image, build, test, passes, fails, errors):
380+ result = SmokeResult.objects.get(
381+ image=image,
382+ jenkins_build=build,
383+ name=test,
384+ )
385+ result.status = SmokeResult.SYNCING
386+ result.pass_count = passes
387+ result.fail_count = fails
388+ result.error_count = errors
389+ result.total_count = passes + fails + errors
390+ result.save()
391+
392 def __unicode__(self):
393 return "{} - {}".format(
394 self.name,
395
396=== modified file 'smokeng/tables.py'
397--- smokeng/tables.py 2013-09-27 16:55:15 +0000
398+++ smokeng/tables.py 2013-11-25 18:08:44 +0000
399@@ -63,8 +63,12 @@
400 attrs={'td': {'class': 'num'}},
401 )
402 pass_rate = tables.TemplateColumn(
403- '{% load dashboard_extras %}'
404- '{{ record.pass_rate|decimal_to_percent }}',
405+ '''{% load dashboard_extras %}
406+ {% if record.inprogress %}
407+ Running
408+ {% else %}
409+ {{ record.pass_rate|decimal_to_percent }}
410+ {% endif %}''',
411 attrs={'td': {'class': 'num pass_rate_color'}},
412 )
413 td_class_extra = tables.TemplateColumn(
414@@ -127,7 +131,10 @@
415 "name": A('name'),
416 },
417 )
418- ran_at = tables.Column()
419+ ran_at = tables.TemplateColumn(
420+ '{% load dashboard_extras %}'
421+ '{{ record|smokeresult_ran_at }}'
422+ )
423
424
425 class SmokeKPITable(tables.Table):
426
427=== modified file 'smokeng/tests.py'
428--- smokeng/tests.py 2013-11-18 23:04:16 +0000
429+++ smokeng/tests.py 2013-11-25 18:08:44 +0000
430@@ -467,7 +467,7 @@
431 '<li><a href="http://q-jenkins.ubuntu-ci:8080/job/saucy-touch_mir-mako-' +
432 'smoke-notes-app-autopilot/1/artifact/clientlogs/utah.yaml/' +
433 '*view*/">utah.yaml</a></li>'
434- )
435+ )
436 _setUp(self)
437
438 self.artifact = Artifact.objects.create(
439@@ -625,3 +625,146 @@
440 page = self._test_result_detail()
441 self.assertContains(page, self.private_jenkins_build)
442 self.assertContains(page, self.private_jenkins_artifact)
443+
444+
445+class TestSmokeStatus(TestCase):
446+ """ Tests for live status operations """
447+
448+ def setUp(self):
449+ _setUp(self)
450+
451+ # make a basic assumption on what's already in the DB for
452+ # tests below
453+ self.assertEqual(1, SmokeResult.objects.all().count())
454+
455+ self.client = Client()
456+
457+ def _get_results_url(self):
458+ return '/smokeng/%s/%s/%s/%s/%d/' % (
459+ self.image.release,
460+ self.image.variant,
461+ self.image.arch,
462+ self.image.build_number,
463+ self.image.id,
464+ )
465+
466+ def testQueued(self):
467+ tests = ['t1', 't2', 't3']
468+ SmokeResult.create_queued_results(
469+ self.image, self.jenkins_build, tests)
470+
471+ qs = SmokeResult.objects.filter(status=SmokeResult.QUEUED)
472+ self.assertEqual(len(tests), qs.count())
473+
474+ # ensure pass-rates aren't affected
475+ summary = self.image.summary
476+ self.assertEqual(3, summary[0]['total_count'])
477+ self.assertEqual(1.0 / 3.0, summary[0]['pass_rate'])
478+
479+ resp = self.client.get('/smokeng/')
480+ pass_rate = resp.context[-1]['table'].rows[0]['pass_rate']
481+ self.assertEqual(u'Running', pass_rate.strip())
482+
483+ resp = self.client.get(self._get_results_url())
484+ rows = resp.context[-1]['table'].rows
485+ for i in range(len(rows)):
486+ if i < len(rows) - 1:
487+ self.assertEqual('Queued', rows[i]['ran_at'])
488+ else:
489+ self.assertNotEqual('Queued', rows[i]['ran_at'])
490+
491+ def testRunning(self):
492+ tests = ['t1', 't2', 't3']
493+ SmokeResult.create_queued_results(
494+ self.image, self.jenkins_build, tests)
495+
496+ qs = SmokeResult.objects.filter(status=SmokeResult.QUEUED)
497+ self.assertEqual(len(tests), qs.count())
498+ ran_at = qs[0].ran_at
499+
500+ # put the first few into running and make sure the counts add up
501+ SmokeResult.set_running(self.image, self.jenkins_build, tests[:-1])
502+ self.assertEqual(len(tests) + 1, SmokeResult.objects.all().count())
503+ qs = SmokeResult.objects.filter(status=SmokeResult.QUEUED)
504+ self.assertEqual(1, qs.count())
505+ self.assertEqual(ran_at, qs[0].ran_at)
506+ qs = SmokeResult.objects.filter(status=SmokeResult.RUNNING)
507+ self.assertEqual(len(tests) - 1, qs.count())
508+
509+ # put the last result into running and make sure the counts add up
510+ SmokeResult.set_running(self.image, self.jenkins_build, tests[-1:])
511+ self.assertEqual(len(tests) + 1, SmokeResult.objects.all().count())
512+ qs = SmokeResult.objects.filter(status=SmokeResult.QUEUED)
513+ self.assertEqual(0, qs.count())
514+ qs = SmokeResult.objects.filter(status=SmokeResult.RUNNING)
515+ self.assertEqual(len(tests), qs.count())
516+ self.assertGreater(qs[0].ran_at, ran_at)
517+
518+ # ensure pass-rates aren't affected
519+ self.assertEqual(3, self.image.summary[0]['total_count'])
520+ self.assertEqual(1.0 / 3.0, self.image.summary[0]['pass_rate'])
521+
522+ def testSyncing(self):
523+ tests = ['t1', 't2', 't3']
524+ SmokeResult.create_queued_results(
525+ self.image, self.jenkins_build, tests)
526+
527+ qs = SmokeResult.objects.filter(status=SmokeResult.QUEUED)
528+ self.assertEqual(len(tests), qs.count())
529+
530+ # put tests into a few states to ensure it can handle them all
531+ SmokeResult.set_running(self.image, self.jenkins_build, tests[:-1])
532+
533+ # this will have wind up with 4 results:
534+ # the original from _setup with a pass-rate 33%
535+ # t1 = which will be 0, t2 and t3 which will be 33%
536+ i = 0
537+ for t in tests:
538+ SmokeResult.set_syncing(self.image, self.jenkins_build, t, i, i, i)
539+ i += 1
540+ qs = SmokeResult.objects.filter(status=SmokeResult.SYNCING)
541+ self.assertEqual(len(tests), qs.count())
542+ self.assertEqual(12, self.image.summary[0]['total_count'])
543+ self.assertEqual(.25, self.image.summary[0]['pass_rate'])
544+
545+ resp = self.client.get('/smokeng/')
546+ pass_rate = resp.context[-1]['table'].rows[0]['pass_rate']
547+ self.assertEqual(u'25%', pass_rate.strip())
548+
549+ resp = self.client.get(self._get_results_url())
550+ rows = resp.context[-1]['table'].rows
551+ for i in range(len(rows)):
552+ if i < len(rows) - 1:
553+ self.assertEqual('Syncing', rows[i]['ran_at'])
554+ else:
555+ self.assertNotEqual('Syncing', rows[i]['ran_at'])
556+
557+ def testRepeat(self):
558+ '''Ensure we can move a result back to QUEUED properly.'''
559+ tests = ['t1', 't2', 't3']
560+ SmokeResult.create_queued_results(
561+ self.image, self.jenkins_build, tests)
562+
563+ # put tests into a few states to ensure it can handle them all
564+ # the syncing result bumps up the "total count" by 3 to 6
565+ SmokeResult.set_running(self.image, self.jenkins_build, tests[:-1])
566+ SmokeResult.set_syncing(self.image, self.jenkins_build,
567+ tests[-1], 1, 1, 1)
568+
569+ SmokeResult.create_queued_results(
570+ self.image, self.jenkins_build, tests)
571+
572+ # ensure we haven't created new rows:
573+ self.assertEqual(len(tests) + 1, SmokeResult.objects.all().count())
574+
575+ qs = SmokeResult.objects.filter(status=SmokeResult.QUEUED)
576+ self.assertEqual(len(tests), qs.count())
577+
578+ # ensure pass-rates go back properly
579+ summary = self.image.summary
580+ self.assertEqual(6, summary[0]['total_count'])
581+ self.assertEqual(1.0 / 3.0, summary[0]['pass_rate'])
582+
583+ resp = self.client.get('/smokeng/')
584+ pass_rate = resp.context[-1]['table'].rows[0]['pass_rate']
585+ self.assertEqual(u'Running', pass_rate.strip())
586
587=== modified file 'smokeng/views.py'
588--- smokeng/views.py 2013-10-25 15:48:45 +0000
589+++ smokeng/views.py 2013-11-25 18:08:44 +0000
590@@ -179,6 +179,9 @@
591 if 'ran_at' in result:
592 totals[key]['ran_at'] = result['ran_at']
593
594+ if result['status'] < SmokeResult.SYNCING:
595+ totals[key]['inprogress'] = True
596+
597 totals[key]['total_count'] += result['total_count']
598 totals[key]['pass_count'] += result['pass_count']
599 totals[key]['error_count'] += result['error_count']
600@@ -222,6 +225,7 @@
601 'image__release',
602 'name',
603 'ran_at',
604+ 'status',
605 'jenkins_build',
606 ).annotate(
607 pass_count=models.Sum('pass_count'),
608@@ -240,6 +244,7 @@
609 'image__release',
610 'name',
611 'ran_at',
612+ 'status',
613 'jenkins_build',
614 'pass_count',
615 'fail_count',

Subscribers

People subscribed via source and target branches