Turn Runs class into a proper model to implement incremental JSON update
[WebKit-https.git] / Websites / webkit-perf.appspot.com / models_unittest.py
1 #!/usr/bin/env python
2 # Copyright (C) 2012 Google Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import json
31 import models
32 import unittest
33
34 from datetime import datetime
35 from datetime import timedelta
36 from google.appengine.api import memcache
37 from google.appengine.ext import testbed
38 from time import mktime
39
40 from models import NumericIdHolder
41 from models import Branch
42 from models import Platform
43 from models import Builder
44 from models import Build
45 from models import Test
46 from models import TestResult
47 from models import ReportLog
48 from models import PersistentCache
49 from models import Runs
50 from models import DashboardImage
51 from models import create_in_transaction_with_numeric_id_holder
52 from models import delete_model_with_numeric_id_holder
53 from models import model_from_numeric_id
54
55
56 class DataStoreTestsBase(unittest.TestCase):
57     def setUp(self):
58         self.testbed = testbed.Testbed()
59         self.testbed.activate()
60         self.testbed.init_datastore_v3_stub()
61
62     def tearDown(self):
63         self.testbed.deactivate()
64
65     def assertThereIsNoInstanceOf(self, model):
66         self.assertEqual(len(model.all().fetch(5)), 0)
67
68     def assertOnlyInstance(self, only_instasnce):
69         self.assertOnlyInstances([only_instasnce])
70
71     def assertOnlyInstances(self, expected_instances):
72         actual_instances = expected_instances[0].__class__.all().fetch(len(expected_instances) + 1)
73         self.assertEqualUnorderedModelList(actual_instances, expected_instances)
74
75     def assertEqualUnorderedModelList(self, list1, list2):
76         self.assertEqualUnorderedList([item.key() for item in list1], [item.key() for item in list1])
77
78     def assertEqualUnorderedList(self, list1, list2):
79         self.assertEqual(set(list1), set(list2))
80         self.assertEqual(len(list1), len(list2))
81
82
83 class HelperTests(DataStoreTestsBase):
84     def _assert_there_is_exactly_one_id_holder_and_matches(self, id):
85         id_holders = NumericIdHolder.all().fetch(5)
86         self.assertEqual(len(id_holders), 1)
87         self.assertTrue(id_holders[0])
88         self.assertEqual(id_holders[0].key().id(), id)
89
90     def test_create_in_transaction_with_numeric_id_holder(self):
91
92         def execute(id):
93             return Branch(id=id, name='some branch', key_name='some-branch').put()
94
95         self.assertThereIsNoInstanceOf(Branch)
96         self.assertThereIsNoInstanceOf(NumericIdHolder)
97
98         self.assertTrue(create_in_transaction_with_numeric_id_holder(execute))
99
100         branches = Branch.all().fetch(5)
101         self.assertEqual(len(branches), 1)
102         self.assertEqual(branches[0].name, 'some branch')
103         self.assertEqual(branches[0].key().name(), 'some-branch')
104
105         self._assert_there_is_exactly_one_id_holder_and_matches(branches[0].id)
106
107     def test_failing_in_create_in_transaction_with_numeric_id_holder(self):
108
109         def execute(id):
110             return None
111
112         self.assertThereIsNoInstanceOf(Branch)
113         self.assertThereIsNoInstanceOf(NumericIdHolder)
114
115         self.assertFalse(create_in_transaction_with_numeric_id_holder(execute))
116
117         self.assertThereIsNoInstanceOf(Branch)
118         self.assertThereIsNoInstanceOf(NumericIdHolder)
119
120     def test_raising_in_create_in_transaction_with_numeric_id_holder(self):
121
122         def execute(id):
123             raise TypeError
124             return None
125
126         self.assertThereIsNoInstanceOf(Branch)
127         self.assertThereIsNoInstanceOf(NumericIdHolder)
128
129         self.assertRaises(TypeError, create_in_transaction_with_numeric_id_holder, (execute))
130
131         self.assertThereIsNoInstanceOf(Branch)
132         self.assertThereIsNoInstanceOf(NumericIdHolder)
133
134     def test_delete_model_with_numeric_id_holder(self):
135
136         def execute(id):
137             return Branch(id=id, name='some branch', key_name='some-branch').put()
138
139         branch = Branch.get(create_in_transaction_with_numeric_id_holder(execute))
140         self.assertOnlyInstance(branch)
141
142         delete_model_with_numeric_id_holder(branch)
143
144         self.assertThereIsNoInstanceOf(Branch)
145         self.assertThereIsNoInstanceOf(NumericIdHolder)
146
147     def test_model_from_numeric_id(self):
148
149         def execute(id):
150             return Branch(id=id, name='some branch', key_name='some-branch').put()
151
152         branch = Branch.get(create_in_transaction_with_numeric_id_holder(execute))
153
154         self.assertEqual(model_from_numeric_id(branch.id, Branch).key(), branch.key())
155         self.assertEqual(model_from_numeric_id(branch.id + 1, Branch), None)
156         delete_model_with_numeric_id_holder(branch)
157         self.assertEqual(model_from_numeric_id(branch.id, Branch), None)
158
159
160 class BranchTests(DataStoreTestsBase):
161     def test_create_if_possible(self):
162         self.assertThereIsNoInstanceOf(Branch)
163
164         branch = Branch.create_if_possible('some-branch', 'some branch')
165         self.assertTrue(branch)
166         self.assertTrue(branch.key().name(), 'some-branch')
167         self.assertTrue(branch.name, 'some branch')
168         self.assertOnlyInstance(branch)
169
170         self.assertFalse(Branch.create_if_possible('some-branch', 'some other branch'))
171         self.assertTrue(branch.name, 'some branch')
172         self.assertOnlyInstance(branch)
173
174
175 class PlatformTests(DataStoreTestsBase):
176     def test_create_if_possible(self):
177         self.assertThereIsNoInstanceOf(Platform)
178
179         platform = Platform.create_if_possible('some-platform', 'some platform')
180         self.assertTrue(platform)
181         self.assertTrue(platform.key().name(), 'some-platform')
182         self.assertTrue(platform.name, 'some platform')
183         self.assertOnlyInstance(platform)
184
185         self.assertFalse(Platform.create_if_possible('some-platform', 'some other platform'))
186         self.assertTrue(platform.name, 'some platform')
187         self.assertOnlyInstance(platform)
188
189
190 class BuilderTests(DataStoreTestsBase):
191     def test_create(self):
192         builder_key = Builder.create('some builder', 'some password')
193         self.assertTrue(builder_key)
194         builder = Builder.get(builder_key)
195         self.assertEqual(builder.key().name(), 'some builder')
196         self.assertEqual(builder.name, 'some builder')
197         self.assertEqual(builder.password, Builder._hashed_password('some password'))
198
199     def test_update_password(self):
200         builder = Builder.get(Builder.create('some builder', 'some password'))
201         self.assertEqual(builder.password, Builder._hashed_password('some password'))
202         builder.update_password('other password')
203         self.assertEqual(builder.password, Builder._hashed_password('other password'))
204
205         # Make sure it's saved
206         builder = Builder.get(builder.key())
207         self.assertEqual(builder.password, Builder._hashed_password('other password'))
208
209     def test_hashed_password(self):
210         self.assertNotEqual(Builder._hashed_password('some password'), 'some password')
211         self.assertFalse('some password' in Builder._hashed_password('some password'))
212         self.assertEqual(len(Builder._hashed_password('some password')), 64)
213
214     def test_authenticate(self):
215         builder = Builder.get(Builder.create('some builder', 'some password'))
216         self.assertTrue(builder.authenticate('some password'))
217         self.assertFalse(builder.authenticate('bad password'))
218
219
220 def _create_some_builder():
221     branch = Branch.create_if_possible('some-branch', 'Some Branch')
222     platform = Platform.create_if_possible('some-platform', 'Some Platform')
223     builder_key = Builder.create('some-builder', 'Some Builder')
224     return branch, platform, Builder.get(builder_key)
225
226
227 def _create_build(branch, platform, builder, key_name='some-build'):
228     build_key = Build(key_name=key_name, branch=branch, platform=platform, builder=builder,
229         buildNumber=1, revision=100, timestamp=datetime.now()).put()
230     return Build.get(build_key)
231
232
233 class BuildTests(DataStoreTestsBase):
234     def test_get_or_insert_from_log(self):
235         branch, platform, builder = _create_some_builder()
236
237         timestamp = datetime.now().replace(microsecond=0)
238         log = ReportLog(timestamp=timestamp, headers='some headers',
239             payload='{"branch": "some-branch", "platform": "some-platform", "builder-name": "some-builder",' +
240                 '"build-number": 123, "webkit-revision": 456, "timestamp": %d}' % int(mktime(timestamp.timetuple())))
241
242         self.assertThereIsNoInstanceOf(Build)
243
244         build = Build.get_or_insert_from_log(log)
245         self.assertTrue(build)
246         self.assertEqual(build.branch.key(), branch.key())
247         self.assertEqual(build.platform.key(), platform.key())
248         self.assertEqual(build.builder.key(), builder.key())
249         self.assertEqual(build.buildNumber, 123)
250         self.assertEqual(build.revision, 456)
251         self.assertEqual(build.chromiumRevision, None)
252         self.assertEqual(build.timestamp, timestamp)
253
254         self.assertOnlyInstance(build)
255
256
257 class TestModelTests(DataStoreTestsBase):
258     def test_update_or_insert(self):
259         branch = Branch.create_if_possible('some-branch', 'Some Branch')
260         platform = Platform.create_if_possible('some-platform', 'Some Platform')
261
262         self.assertThereIsNoInstanceOf(Test)
263
264         test = Test.update_or_insert('some-test', branch, platform)
265         self.assertTrue(test)
266         self.assertEqual(test.branches, [branch.key()])
267         self.assertEqual(test.platforms, [platform.key()])
268         self.assertOnlyInstance(test)
269
270     def test_update_or_insert_to_update(self):
271         branch = Branch.create_if_possible('some-branch', 'Some Branch')
272         platform = Platform.create_if_possible('some-platform', 'Some Platform')
273         test = Test.update_or_insert('some-test', branch, platform)
274         self.assertOnlyInstance(test)
275
276         other_branch = Branch.create_if_possible('other-branch', 'Other Branch')
277         other_platform = Platform.create_if_possible('other-platform', 'Other Platform')
278         test = Test.update_or_insert('some-test', other_branch, other_platform)
279         self.assertOnlyInstance(test)
280         self.assertEqualUnorderedList(test.branches, [branch.key(), other_branch.key()])
281         self.assertEqualUnorderedList(test.platforms, [platform.key(), other_platform.key()])
282
283         test = Test.get(test.key())
284         self.assertEqualUnorderedList(test.branches, [branch.key(), other_branch.key()])
285         self.assertEqualUnorderedList(test.platforms, [platform.key(), other_platform.key()])
286
287     def test_merge(self):
288         branch, platform, builder = _create_some_builder()
289         some_build = _create_build(branch, platform, builder)
290         some_result = TestResult.get_or_insert_from_parsed_json('some-test', some_build, 50)
291         some_test = Test.update_or_insert('some-test', branch, platform)
292
293         other_build = _create_build(branch, platform, builder, 'other-build')
294         other_result = TestResult.get_or_insert_from_parsed_json('other-test', other_build, 30)
295         other_test = Test.update_or_insert('other-test', branch, platform)
296
297         self.assertOnlyInstances([some_result, other_result])
298         self.assertNotEqual(some_result.key(), other_result.key())
299         self.assertOnlyInstances([some_test, other_test])
300
301         self.assertRaises(AssertionError, some_test.merge, (some_test))
302         self.assertOnlyInstances([some_test, other_test])
303
304         some_test.merge(other_test)
305         results_for_some_test = TestResult.all()
306         results_for_some_test.filter('name =', 'some-test')
307         results_for_some_test = results_for_some_test.fetch(5)
308         self.assertEqual(len(results_for_some_test), 2)
309
310         self.assertEqual(results_for_some_test[0].name, 'some-test')
311         self.assertEqual(results_for_some_test[1].name, 'some-test')
312
313         if results_for_some_test[0].value == 50:
314             self.assertEqual(results_for_some_test[1].value, 30)
315         else:
316             self.assertEqual(results_for_some_test[1].value, 50)
317
318
319 class TestResultTests(DataStoreTestsBase):
320     def test_get_or_insert_value(self):
321         branch, platform, builder = _create_some_builder()
322         build = _create_build(branch, platform, builder)
323         self.assertThereIsNoInstanceOf(TestResult)
324         result = TestResult.get_or_insert_from_parsed_json('some-test', build, 50)
325         self.assertOnlyInstance(result)
326         self.assertEqual(result.name, 'some-test')
327         self.assertEqual(result.build.key(), build.key())
328         self.assertEqual(result.value, 50.0)
329         self.assertEqual(result.valueMedian, None)
330         self.assertEqual(result.valueStdev, None)
331         self.assertEqual(result.valueMin, None)
332         self.assertEqual(result.valueMax, None)
333
334     def test_get_or_insert_stat_value(self):
335         branch, platform, builder = _create_some_builder()
336         build = _create_build(branch, platform, builder)
337         self.assertThereIsNoInstanceOf(TestResult)
338         result = TestResult.get_or_insert_from_parsed_json('some-test', build,
339             {"avg": 40, "median": "40.1", "stdev": 3.25, "min": 30.5, "max": 45})
340         self.assertOnlyInstance(result)
341         self.assertEqual(result.name, 'some-test')
342         self.assertEqual(result.build.key(), build.key())
343         self.assertEqual(result.value, 40.0)
344         self.assertEqual(result.valueMedian, 40.1)
345         self.assertEqual(result.valueStdev, 3.25)
346         self.assertEqual(result.valueMin, 30.5)
347         self.assertEqual(result.valueMax, 45)
348
349     def test_replace_to_change_test_name(self):
350         branch, platform, builder = _create_some_builder()
351         build = _create_build(branch, platform, builder)
352         self.assertThereIsNoInstanceOf(TestResult)
353         result = TestResult.get_or_insert_from_parsed_json('some-test', build, 50)
354         self.assertOnlyInstance(result)
355         self.assertEqual(result.name, 'some-test')
356
357         new_result = result.replace_to_change_test_name('other-test')
358         self.assertNotEqual(result, new_result)
359         self.assertOnlyInstance(new_result)
360
361         self.assertEqual(new_result.name, 'other-test')
362         self.assertEqual(new_result.build.key(), result.build.key())
363         self.assertEqual(new_result.value, result.value)
364         self.assertEqual(new_result.valueMedian, None)
365         self.assertEqual(new_result.valueStdev, None)
366         self.assertEqual(new_result.valueMin, None)
367         self.assertEqual(new_result.valueMax, None)
368
369     def test_replace_to_change_test_name_with_stat_value(self):
370         branch, platform, builder = _create_some_builder()
371         build = _create_build(branch, platform, builder)
372         self.assertThereIsNoInstanceOf(TestResult)
373         result = TestResult.get_or_insert_from_parsed_json('some-test', build,
374             {"avg": 40, "median": "40.1", "stdev": 3.25, "min": 30.5, "max": 45})
375         self.assertOnlyInstance(result)
376         self.assertEqual(result.name, 'some-test')
377
378         new_result = result.replace_to_change_test_name('other-test')
379         self.assertNotEqual(result, new_result)
380         self.assertOnlyInstance(new_result)
381
382         self.assertEqual(new_result.name, 'other-test')
383         self.assertEqual(new_result.build.key(), result.build.key())
384         self.assertEqual(new_result.value, result.value)
385         self.assertEqual(result.value, 40.0)
386         self.assertEqual(result.valueMedian, 40.1)
387         self.assertEqual(result.valueStdev, 3.25)
388         self.assertEqual(result.valueMin, 30.5)
389         self.assertEqual(result.valueMax, 45)
390
391     def test_replace_to_change_test_name_overrides_conflicting_result(self):
392         branch, platform, builder = _create_some_builder()
393         build = _create_build(branch, platform, builder)
394         self.assertThereIsNoInstanceOf(TestResult)
395         result = TestResult.get_or_insert_from_parsed_json('some-test', build, 20)
396         self.assertOnlyInstance(result)
397
398         conflicting_result = TestResult.get_or_insert_from_parsed_json('other-test', build, 10)
399
400         new_result = result.replace_to_change_test_name('other-test')
401         self.assertNotEqual(result, new_result)
402         self.assertOnlyInstance(new_result)
403
404         self.assertEqual(new_result.name, 'other-test')
405         self.assertEqual(TestResult.get(conflicting_result.key()).value, 20)
406
407
408 class ReportLogTests(DataStoreTestsBase):
409     def _create_log_with_payload(self, payload):
410         return ReportLog(timestamp=datetime.now(), headers='some headers', payload=payload)
411
412     def test_parsed_payload(self):
413         log = self._create_log_with_payload('')
414         self.assertFalse('_parsed' in log.__dict__)
415         self.assertEqual(log._parsed_payload(), False)
416         self.assertEqual(log._parsed, False)
417
418         log = self._create_log_with_payload('{"key": "value", "another key": 1}')
419         self.assertEqual(log._parsed_payload(), {"key": "value", "another key": 1})
420         self.assertEqual(log._parsed, {"key": "value", "another key": 1})
421
422     def test_get_value(self):
423         log = self._create_log_with_payload('{"string": "value", "integer": 1, "float": 1.1}')
424         self.assertEqual(log.get_value('string'), 'value')
425         self.assertEqual(log.get_value('integer'), 1)
426         self.assertEqual(log.get_value('float'), 1.1)
427         self.assertEqual(log.get_value('bad'), None)
428
429     def test_results(self):
430         log = self._create_log_with_payload('{"results": 123}')
431         self.assertEqual(log.results(), 123)
432
433         log = self._create_log_with_payload('{"key": "value"}')
434         self.assertEqual(log.results(), None)
435
436     def test_builder(self):
437         log = self._create_log_with_payload('{"key": "value"}')
438         self.assertEqual(log.builder(), None)
439
440         builder_name = "Chromium Mac Release (Perf)"
441         log = self._create_log_with_payload('{"builder-name": "%s"}' % builder_name)
442         self.assertEqual(log.builder(), None)
443
444         builder_key = Builder.create(builder_name, 'some password')
445         log = self._create_log_with_payload('{"builder-name": "%s"}' % builder_name)
446         self.assertEqual(log.builder().key(), builder_key)
447
448     def test_branch(self):
449         log = self._create_log_with_payload('{"key": "value"}')
450         self.assertEqual(log.branch(), None)
451
452         log = self._create_log_with_payload('{"branch": "some-branch"}')
453         self.assertEqual(log.branch(), None)
454
455         branch = Branch.create_if_possible("some-branch", "Some Branch")
456         log = self._create_log_with_payload('{"branch": "some-branch"}')
457         self.assertEqual(log.branch().key(), branch.key())
458
459     def test_platform(self):
460         log = self._create_log_with_payload('{"key": "value"}')
461         self.assertEqual(log.platform(), None)
462
463         log = self._create_log_with_payload('{"platform": "some-platform"}')
464         self.assertEqual(log.platform(), None)
465
466         platform = Platform.create_if_possible("some-platform", "Some Platform")
467         log = self._create_log_with_payload('{"platform": "some-platform"}')
468         self.assertEqual(log.platform().key(), platform.key())
469
470     def test_build_number(self):
471         log = self._create_log_with_payload('{"build-number": 123}')
472         self.assertEqual(log.build_number(), 123)
473
474         log = self._create_log_with_payload('{"key": "value"}')
475         self.assertEqual(log.build_number(), None)
476
477     def test_webkit_revision(self):
478         log = self._create_log_with_payload('{"key": "value"}')
479         self.assertEqual(log.webkit_revision(), None)
480
481         log = self._create_log_with_payload('{"webkit-revision": 123}')
482         self.assertEqual(log.webkit_revision(), 123)
483
484     def chromium_revision(self):
485         log = self._create_log_with_payload('{"chromium-revision": 123}')
486         self.assertEqual(log.webkit_revision(), 123)
487
488         log = self._create_log_with_payload('{"key": "value"}')
489         self.assertEqual(log.webkit_revision(), None)
490
491
492 class PersistentCacheTests(DataStoreTestsBase):
493     def setUp(self):
494         super(PersistentCacheTests, self).setUp()
495         self.testbed.init_memcache_stub()
496
497     def _assert_persistent_cache(self, name, value):
498         self.assertEqual(PersistentCache.get_by_key_name(name).value, value)
499         self.assertEqual(memcache.get(name), value)
500
501     def test_set_cache(self):
502         self.assertThereIsNoInstanceOf(PersistentCache)
503
504         PersistentCache.set_cache('some-cache', 'some data')
505         self._assert_persistent_cache('some-cache', 'some data')
506
507         PersistentCache.set_cache('some-cache', 'some other data')
508
509         self._assert_persistent_cache('some-cache', 'some other data')
510
511     def test_get_cache(self):
512         self.assertEqual(memcache.get('some-cache'), None)
513         self.assertEqual(PersistentCache.get_cache('some-cache'), None)
514
515         PersistentCache.set_cache('some-cache', 'some data')
516
517         self.assertEqual(memcache.get('some-cache'), 'some data')
518         self.assertEqual(PersistentCache.get_cache('some-cache'), 'some data')
519
520         memcache.delete('some-cache')
521         self.assertEqual(memcache.get('some-cache'), None)
522         self.assertEqual(PersistentCache.get_cache('some-cache'), 'some data')
523
524
525 class RunsTest(DataStoreTestsBase):
526     def setUp(self):
527         super(RunsTest, self).setUp()
528         self.testbed.init_memcache_stub()
529
530     def _create_results(self, branch, platform, builder, test_name, values, timestamps=None):
531         results = []
532         for i, value in enumerate(values):
533             build = Build(branch=branch, platform=platform, builder=builder,
534                 buildNumber=i, revision=100 + i, timestamp=timestamps[i] if timestamps else datetime.now())
535             build.put()
536             result = TestResult(name=test_name, build=build, value=value)
537             result.put()
538             results.append(result)
539         return results
540
541     def test_generate_runs(self):
542         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
543         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
544         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
545         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
546
547         results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
548         last_i = 0
549         for i, (build, result) in enumerate(Runs._generate_runs(some_branch, some_platform, some_test)):
550             self.assertEqual(build.buildNumber, i)
551             self.assertEqual(build.revision, 100 + i)
552             self.assertEqual(result.name, 'some-test')
553             self.assertEqual(result.value, results[i].value)
554             last_i = i
555         self.assertTrue(last_i + 1, len(results))
556
557     def test_update_or_insert(self):
558         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
559         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
560         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
561         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
562         self.assertThereIsNoInstanceOf(Runs)
563
564         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
565         self.assertOnlyInstance(runs)
566         self.assertEqual(runs.json_runs, '')
567         self.assertEqual(runs.json_averages, '')
568         self.assertEqual(runs.json_min, None)
569         self.assertEqual(runs.json_max, None)
570         old_memcache_value = memcache.get(Runs._key_name(some_branch.id, some_platform.id, some_test.id))
571         self.assertTrue(old_memcache_value)
572
573         runs.delete()
574         self.assertThereIsNoInstanceOf(Runs)
575
576         results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0])
577         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
578         self.assertOnlyInstance(runs)
579         self.assertTrue(runs.json_runs.startswith('[5, [4, 0, 100, null],'))
580         self.assertEqual(runs.json_averages, '"100": 50.0')
581         self.assertEqual(runs.json_min, 50.0)
582         self.assertEqual(runs.json_max, 50.0)
583         self.assertNotEqual(memcache.get(Runs._key_name(some_branch.id, some_platform.id, some_test.id)), old_memcache_value)
584
585     def test_json_by_ids(self):
586         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
587         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
588         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
589         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
590
591         self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0])
592         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
593         runs_json = runs.to_json()
594
595         key_name = Runs._key_name(some_branch.id, some_platform.id, some_test.id)
596         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), runs_json)
597         self.assertEqual(memcache.get(key_name), runs_json)
598
599         memcache.set(key_name, 'blah')
600         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), 'blah')
601
602         memcache.delete(key_name)
603         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), runs_json)
604         self.assertEqual(memcache.get(key_name), runs_json)
605
606     def test_to_json_without_results(self):
607         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
608         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
609         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
610         self.assertOnlyInstance(some_test)
611         self.assertThereIsNoInstanceOf(TestResult)
612         self.assertEqual(json.loads(Runs.update_or_insert(some_branch, some_platform, some_test).to_json()), {
613             'test_runs': [],
614             'averages': {},
615             'min': None,
616             'max': None,
617             'date_range': None,
618             'stat': 'ok'})
619
620     def test_to_json_with_results(self):
621         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
622         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
623         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
624         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
625         results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
626
627         value = json.loads(Runs.update_or_insert(some_branch, some_platform, some_test).to_json())
628         self.assertEqualUnorderedList(value.keys(), ['test_runs', 'averages', 'min', 'max', 'date_range', 'stat'])
629         self.assertEqual(value['stat'], 'ok')
630         self.assertEqual(value['min'], 48.0)
631         self.assertEqual(value['max'], 52.0)
632         self.assertEqual(value['date_range'], None)  # date_range is never given
633
634         self.assertEqual(len(value['test_runs']), len(results))
635         for i, run in enumerate(value['test_runs']):
636             result = results[i]
637             self.assertEqual(run[0], result.key().id())
638             self.assertEqual(run[1][1], i)  # Build number
639             self.assertEqual(run[1][2], 100 + i)  # Revision
640             self.assertEqual(run[1][3], None)  # Supplementary revision
641             self.assertEqual(run[3], result.value)
642             self.assertEqual(run[6], some_builder.key().id())
643             self.assertEqual(run[7], None)  # Statistics
644
645     def _assert_entry(self, entry, build, result, value, statistics=None, supplementary_revisions=None):
646         entry = entry[:]
647         entry[2] = None  # timestamp
648         self.assertEqual(entry, [result.key().id(), [build.key().id(), build.buildNumber, build.revision, supplementary_revisions],
649             None,  # timestamp
650             value, 0,  # runNumber
651             [],  # annotations
652             build.builder.key().id(), statistics])
653
654     def test_run_from_build_and_result(self):
655         branch = Branch.create_if_possible('some-branch', 'Some Branch')
656         platform = Platform.create_if_possible('some-platform', 'Some Platform')
657         builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
658         test_name = ' some-test'
659
660         def create_build(build_number, revision):
661             timestamp = datetime.now().replace(microsecond=0)
662             build = Build(branch=branch, platform=platform, builder=builder, buildNumber=build_number,
663                 revision=revision, timestamp=timestamp)
664             build.put()
665             return build
666
667         build = create_build(1, 101)
668         result = TestResult(name=test_name, value=123.0, build=build)
669         result.put()
670         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 123.0)
671
672         build = create_build(2, 102)
673         result = TestResult(name=test_name, value=456.0, valueMedian=789.0, build=build)
674         result.put()
675         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
676
677         result.valueStdev = 7.0
678         result.put()
679         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
680
681         result.valueStdev = None
682         result.valueMin = 123.0
683         result.valueMax = 789.0
684         result.put()
685         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
686
687         result.valueStdev = 8.0
688         result.valueMin = 123.0
689         result.valueMax = 789.0
690         result.put()
691         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0,
692             statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0})
693
694         result.valueMedian = 345.0  # Median is never used by the frontend.
695         result.valueStdev = 8.0
696         result.valueMin = 123.0
697         result.valueMax = 789.0
698         result.put()
699         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0,
700             statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0})
701
702     def test_chart_params_with_value(self):
703         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
704         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
705         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
706         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
707
708         start_time = datetime(2011, 2, 21, 12, 0, 0)
709         end_time = datetime(2011, 2, 28, 12, 0, 0)
710         results = self._create_results(some_branch, some_platform, some_builder, 'some-test',
711             [50.0, 51.0, 52.0, 49.0, 48.0, 51.9, 50.7, 51.1],
712             [start_time + timedelta(day) for day in range(0, 8)])
713
714         # Use int despite of its impreciseness since tests may fail due to rounding errors otherwise.
715         def split_as_int(string):
716             return [int(float(value)) for value in string.split(',')]
717
718         params = Runs.update_or_insert(some_branch, some_platform, some_test).chart_params(7, end_time)
719         self.assertEqual(params['chxl'], '0:|Feb 21|Feb 22|Feb 23|Feb 24|Feb 25|Feb 26|Feb 27|Feb 28')
720         self.assertEqual(split_as_int(params['chxr']), [1, 0, 57, int(52 * 1.1 / 5 + 0.5)])
721         x_min, x_max, y_min, y_max = split_as_int(params['chds'])
722         self.assertEqual(datetime.fromtimestamp(x_min), start_time)
723         self.assertEqual(datetime.fromtimestamp(x_max), end_time)
724         self.assertEqual(y_min, 0)
725         self.assertEqual(y_max, int(52 * 1.1))
726         self.assertEqual(split_as_int(params['chg']), [int(100 / 7), 20, 0, 0])
727
728         params = Runs.update_or_insert(some_branch, some_platform, some_test).chart_params(14, end_time)
729         self.assertEqual(params['chxl'], '0:|Feb 14|Feb 16|Feb 18|Feb 20|Feb 22|Feb 24|Feb 26|Feb 28')
730         self.assertEqual(split_as_int(params['chxr']), [1, 0, 57, int(52 * 1.1 / 5 + 0.5)])
731         x_min, x_max, y_min, y_max = split_as_int(params['chds'])
732         self.assertEqual(datetime.fromtimestamp(x_min), datetime(2011, 2, 14, 12, 0, 0))
733         self.assertEqual(datetime.fromtimestamp(x_max), end_time)
734         self.assertEqual(y_min, 0)
735         self.assertEqual(y_max, int(52 * 1.1))
736         self.assertEqual(split_as_int(params['chg']), [int(100 / 7), 20, 0, 0])
737
738
739 class DashboardImageTests(DataStoreTestsBase):
740     def setUp(self):
741         super(DashboardImageTests, self).setUp()
742         self.testbed.init_memcache_stub()
743
744     def test_create(self):
745         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), None)
746         self.assertThereIsNoInstanceOf(DashboardImage)
747         image = DashboardImage.create(1, 2, 3, 7, 'blah')
748         self.assertOnlyInstance(image)
749         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
750
751     def test_get(self):
752         image = DashboardImage.create(1, 2, 3, 7, 'blah')
753         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
754         memcache.set('dashboard-image:1:2:3:7', 'new value')
755
756         # Check twice to make sure the first call doesn't clear memcache
757         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'new value')
758         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'new value')
759
760         memcache.delete('dashboard-image:1:2:3:7')
761         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), None)
762         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'blah')
763         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
764
765     def test_needs_update(self):
766         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 7))
767         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 30))
768         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 60))
769         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 365))
770
771         image = DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 7), image='blah')
772         image.put()
773         self.assertOnlyInstance(image)
774         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 7))
775
776         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 30), image='blah').put()
777         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 30, datetime.now() + timedelta(0, 10)))
778
779         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 30), image='blah').put()
780         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 30, datetime.now() + timedelta(1)))
781
782         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 90), image='blah').put()
783         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 90, datetime.now() + timedelta(0, 20)))
784
785         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 90), image='blah').put()
786         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 90, datetime.now() + timedelta(1)))
787
788         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 365), image='blah').put()
789         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 365, datetime.now() + timedelta(1)))
790
791         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 365), image='blah').put()
792         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 365, datetime.now() + timedelta(10)))
793
794
795 if __name__ == '__main__':
796     unittest.main()