Skip to content

Commit 00ef5a3

Browse files
committed
Merge pull request tobami#188 from tobami/split-views-module
Split views module
2 parents 3d5ec86 + c204d55 commit 00ef5a3

8 files changed

Lines changed: 833 additions & 796 deletions

File tree

codespeed/domain.py

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, unicode_literals
3+
4+
import logging
5+
from datetime import datetime
6+
from django.core.exceptions import ValidationError
7+
8+
from django.conf import settings
9+
from codespeed.models import (Environment, Executable, Revision,
10+
Project, Branch, Benchmark,
11+
Result, Report)
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
def get_default_environment(enviros, data, multi=False):
17+
"""Returns the default environment. Preference level is:
18+
* Present in URL parameters (permalinks)
19+
* Value in settings.py
20+
* First Environment ID
21+
22+
"""
23+
defaultenviros = []
24+
# Use permalink values
25+
if 'env' in data:
26+
for env_value in data['env'].split(","):
27+
for env in enviros:
28+
try:
29+
env_id = int(env_value)
30+
except ValueError:
31+
# Not an int
32+
continue
33+
for env in enviros:
34+
if env_id == env.id:
35+
defaultenviros.append(env)
36+
if not multi:
37+
break
38+
# Use settings.py value
39+
if not defaultenviros and not multi:
40+
if (hasattr(settings, 'DEF_ENVIRONMENT') and
41+
settings.DEF_ENVIRONMENT is not None):
42+
for env in enviros:
43+
if settings.DEF_ENVIRONMENT == env.name:
44+
defaultenviros.append(env)
45+
break
46+
# Last fallback
47+
if not defaultenviros:
48+
defaultenviros = enviros
49+
if multi:
50+
return defaultenviros
51+
else:
52+
return defaultenviros[0]
53+
54+
55+
def getbaselineexecutables():
56+
baseline = [{
57+
'key': "none",
58+
'name': "None",
59+
'executable': "none",
60+
'revision': "none",
61+
}]
62+
executables = Executable.objects.select_related('project')
63+
revs = Revision.objects.exclude(tag="").select_related('branch__project')
64+
maxlen = 22
65+
for rev in revs:
66+
# Add executables that correspond to each tagged revision.
67+
for exe in [e for e in executables if e.project == rev.branch.project]:
68+
exestring = str(exe)
69+
if len(exestring) > maxlen:
70+
exestring = str(exe)[0:maxlen] + "..."
71+
name = exestring + " " + rev.tag
72+
key = str(exe.id) + "+" + str(rev.id)
73+
baseline.append({
74+
'key': key,
75+
'executable': exe,
76+
'revision': rev,
77+
'name': name,
78+
})
79+
# move default to first place
80+
if hasattr(settings, 'DEF_BASELINE') and settings.DEF_BASELINE is not None:
81+
try:
82+
exename = settings.DEF_BASELINE['executable']
83+
commitid = settings.DEF_BASELINE['revision']
84+
for base in baseline:
85+
if base['key'] == "none":
86+
continue
87+
if (base['executable'].name == exename and
88+
base['revision'].commitid == commitid):
89+
baseline.remove(base)
90+
baseline.insert(1, base)
91+
break
92+
except KeyError:
93+
# TODO: write to server logs
94+
# error in settings.DEF_BASELINE
95+
pass
96+
return baseline
97+
98+
99+
def getdefaultexecutable():
100+
default = None
101+
if (hasattr(settings, 'DEF_EXECUTABLE') and
102+
settings.DEF_EXECUTABLE is not None):
103+
try:
104+
default = Executable.objects.get(name=settings.DEF_EXECUTABLE)
105+
except Executable.DoesNotExist:
106+
pass
107+
if default is None:
108+
execquery = Executable.objects.filter(project__track=True)
109+
if len(execquery):
110+
default = execquery[0]
111+
112+
return default
113+
114+
115+
def getcomparisonexes():
116+
all_executables = {}
117+
exekeys = []
118+
baselines = getbaselineexecutables()
119+
for proj in Project.objects.all():
120+
executables = []
121+
executablekeys = []
122+
maxlen = 20
123+
# add all tagged revs for any project
124+
for exe in baselines:
125+
if exe['key'] is not "none" and exe['executable'].project == proj:
126+
executablekeys.append(exe['key'])
127+
executables.append(exe)
128+
129+
# add latest revs of the project
130+
branches = Branch.objects.filter(project=proj)
131+
for branch in branches:
132+
try:
133+
rev = Revision.objects.filter(branch=branch).latest('date')
134+
except Revision.DoesNotExist:
135+
continue
136+
# Now only append when tag == "",
137+
# because we already added tagged revisions
138+
if rev.tag == "":
139+
for exe in Executable.objects.filter(project=proj):
140+
exestring = str(exe)
141+
if len(exestring) > maxlen:
142+
exestring = str(exe)[0:maxlen] + "..."
143+
name = exestring + " latest"
144+
if branch.name != 'default':
145+
name += " in branch '" + branch.name + "'"
146+
key = str(exe.id) + "+L+" + branch.name
147+
executablekeys.append(key)
148+
executables.append({
149+
'key': key,
150+
'executable': exe,
151+
'revision': rev,
152+
'name': name,
153+
})
154+
all_executables[proj] = executables
155+
exekeys += executablekeys
156+
return all_executables, exekeys
157+
158+
159+
def getcommitlogs(rev, startrev, update=False):
160+
logs = []
161+
162+
if rev.branch.project.repo_type == 'S':
163+
from subversion import getlogs, updaterepo
164+
elif rev.branch.project.repo_type == 'M':
165+
from mercurial import getlogs, updaterepo
166+
elif rev.branch.project.repo_type == 'G':
167+
from git import getlogs, updaterepo
168+
elif rev.branch.project.repo_type == 'H':
169+
from github import getlogs, updaterepo
170+
else:
171+
if rev.branch.project.repo_type not in ("N", ""):
172+
logger.warning("Don't know how to retrieve logs from %s project",
173+
rev.branch.project.get_repo_type_display())
174+
return logs
175+
176+
if update:
177+
updaterepo(rev.branch.project)
178+
179+
logs = getlogs(rev, startrev)
180+
181+
# Remove last log because the startrev log shouldn't be shown
182+
if len(logs) > 1 and logs[-1].get('commitid') == startrev.commitid:
183+
logs.pop()
184+
185+
return logs
186+
187+
188+
def saverevisioninfo(rev):
189+
log = getcommitlogs(rev, rev, update=True)
190+
191+
if log:
192+
log = log[0]
193+
rev.author = log['author']
194+
rev.date = log['date']
195+
rev.message = log['message']
196+
197+
198+
def validate_result(item):
199+
"""
200+
Validates that a result dictionary has all needed parameters
201+
202+
It returns a tuple
203+
Environment, False when no errors where found
204+
Errormessage, True when there is an error
205+
"""
206+
mandatory_data = [
207+
'commitid',
208+
'branch',
209+
'project',
210+
'executable',
211+
'benchmark',
212+
'environment',
213+
'result_value',
214+
]
215+
216+
error = True
217+
for key in mandatory_data:
218+
if key not in item:
219+
return 'Key "' + key + '" missing from request', error
220+
elif key in item and item[key] == "":
221+
return 'Value for key "' + key + '" empty in request', error
222+
223+
# Check that the Environment exists
224+
try:
225+
e = Environment.objects.get(name=item['environment'])
226+
error = False
227+
return e, error
228+
except Environment.DoesNotExist:
229+
return "Environment %(environment)s not found" % item, error
230+
231+
232+
def create_report_if_enough_data(rev, exe, e):
233+
"""Triggers Report creation when there are enough results"""
234+
last_revs = Revision.objects.filter(
235+
branch=rev.branch
236+
).order_by('-date')[:2]
237+
if len(last_revs) > 1:
238+
current_results = rev.results.filter(executable=exe, environment=e)
239+
last_results = last_revs[1].results.filter(
240+
executable=exe, environment=e)
241+
# If there is are at least as many results as in the last revision,
242+
# create new report
243+
if len(current_results) >= len(last_results):
244+
logger.debug("create_report_if_enough_data: About to create new report")
245+
report, created = Report.objects.get_or_create(
246+
executable=exe, environment=e, revision=rev
247+
)
248+
report.full_clean()
249+
report.save()
250+
logger.debug("create_report_if_enough_data: Created new report.")
251+
252+
253+
def save_result(data):
254+
res, error = validate_result(data)
255+
if error:
256+
return res, True
257+
else:
258+
assert(isinstance(res, Environment))
259+
env = res
260+
261+
p, created = Project.objects.get_or_create(name=data["project"])
262+
branch, created = Branch.objects.get_or_create(name=data["branch"],
263+
project=p)
264+
b, created = Benchmark.objects.get_or_create(name=data["benchmark"])
265+
266+
if created:
267+
if "description" in data:
268+
b.description = data["description"]
269+
if "units" in data:
270+
b.units = data["units"]
271+
if "units_title" in data:
272+
b.units_title = data["units_title"]
273+
if "lessisbetter" in data:
274+
b.lessisbetter = data["lessisbetter"]
275+
b.full_clean()
276+
b.save()
277+
278+
try:
279+
rev = branch.revisions.get(commitid=data['commitid'])
280+
except Revision.DoesNotExist:
281+
rev_date = data.get("revision_date")
282+
# "None" (as string) can happen when we urlencode the POST data
283+
if not rev_date or rev_date in ["", "None"]:
284+
rev_date = datetime.today()
285+
rev = Revision(branch=branch, project=p, commitid=data['commitid'],
286+
date=rev_date)
287+
try:
288+
rev.full_clean()
289+
except ValidationError as e:
290+
return str(e), True
291+
if p.repo_type not in ("N", ""):
292+
try:
293+
saverevisioninfo(rev)
294+
except RuntimeError as e:
295+
logger.warning("unable to save revision %s info: %s", rev, e,
296+
exc_info=True)
297+
rev.save()
298+
299+
exe, created = Executable.objects.get_or_create(
300+
name=data['executable'],
301+
project=p
302+
)
303+
304+
try:
305+
r = Result.objects.get(
306+
revision=rev, executable=exe, benchmark=b, environment=env)
307+
except Result.DoesNotExist:
308+
r = Result(revision=rev, executable=exe, benchmark=b, environment=env)
309+
310+
r.value = data["result_value"]
311+
if 'result_date' in data:
312+
r.date = data["result_date"]
313+
elif rev.date:
314+
r.date = rev.date
315+
else:
316+
r.date = datetime.now()
317+
318+
r.std_dev = data.get('std_dev')
319+
r.val_min = data.get('min')
320+
r.val_max = data.get('max')
321+
322+
r.full_clean()
323+
r.save()
324+
325+
return (rev, exe, env), False

codespeed/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, unicode_literals
3+
24
import os
35
import json
46

codespeed/tests/test_domain.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# -*- coding: utf-8 -*-
2+
from django.test import TestCase
3+
4+
from codespeed.models import Project, Executable, Branch, Revision
5+
from codespeed.views import getbaselineexecutables
6+
7+
8+
class TestGetBaselineExecutables(TestCase):
9+
"""Test helper functions in codespeed.views"""
10+
11+
def setUp(self):
12+
self.project = Project.objects.create(name='Test')
13+
self.executable = Executable.objects.create(
14+
name='TestExecutable', project=self.project)
15+
self.branch = Branch.objects.create(name='master',
16+
project=self.project)
17+
18+
def test_get_baseline_executables(self):
19+
# No revisions, no baseline
20+
result = getbaselineexecutables()
21+
self.assertEqual(len(result), 1)
22+
self.assertEqual(result[0]['executable'], 'none')
23+
24+
# Check that a tagged revision will be included as baseline
25+
revision1 = Revision.objects.create(commitid='1', tag='0.1',
26+
branch=self.branch)
27+
result = getbaselineexecutables()
28+
self.assertEqual(len(result), 2)
29+
self.assertEqual(result[0]['executable'], 'none')
30+
self.assertEqual(result[1]['executable'], self.executable)
31+
self.assertEqual(result[1]['revision'], revision1)
32+
33+
Revision.objects.create(commitid='2', tag='0.2', branch=self.branch)
34+
result = getbaselineexecutables()
35+
self.assertEqual(len(result), 3)
36+
37+
# An untagged revision will not be available as baseline
38+
Revision.objects.create(commitid='3', branch=self.branch)
39+
result = getbaselineexecutables()
40+
self.assertEqual(len(result), 3)

0 commit comments

Comments
 (0)