perf-o-matic should incrementally update JSON responses
[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, starting_revision=100):
531         builds = []
532         results = []
533         for i, value in enumerate(values):
534             build = Build(branch=branch, platform=platform, builder=builder,
535                 buildNumber=i, revision=starting_revision + i, timestamp=timestamps[i] if timestamps else datetime.now())
536             build.put()
537             result = TestResult(name=test_name, build=build, value=value)
538             result.put()
539             builds.append(build)
540             results.append(result)
541         return builds, results
542
543     def test_generate_runs(self):
544         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
545         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
546         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
547         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
548
549         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
550         last_i = 0
551         for i, (build, result) in enumerate(Runs._generate_runs(some_branch, some_platform, some_test)):
552             self.assertEqual(build.buildNumber, i)
553             self.assertEqual(build.revision, 100 + i)
554             self.assertEqual(result.name, 'some-test')
555             self.assertEqual(result.value, results[i].value)
556             last_i = i
557         self.assertTrue(last_i + 1, len(results))
558
559     def test_update_or_insert(self):
560         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
561         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
562         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
563         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
564         self.assertThereIsNoInstanceOf(Runs)
565
566         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
567         self.assertOnlyInstance(runs)
568         self.assertEqual(runs.json_runs, '')
569         self.assertEqual(runs.json_averages, '')
570         self.assertEqual(runs.json_min, None)
571         self.assertEqual(runs.json_max, None)
572         old_memcache_value = memcache.get(Runs._key_name(some_branch.id, some_platform.id, some_test.id))
573         self.assertTrue(old_memcache_value)
574
575         runs.delete()
576         self.assertThereIsNoInstanceOf(Runs)
577
578         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0])
579         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
580         self.assertOnlyInstance(runs)
581         self.assertTrue(runs.json_runs.startswith('[5, [4, 0, 100, null],'))
582         self.assertEqual(json.loads('{' + runs.json_averages + '}'), {"100": 50.0})
583         self.assertEqual(runs.json_min, 50.0)
584         self.assertEqual(runs.json_max, 50.0)
585         self.assertNotEqual(memcache.get(Runs._key_name(some_branch.id, some_platform.id, some_test.id)), old_memcache_value)
586
587     def test_update_incrementally(self):
588         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
589         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
590         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
591         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
592         self.assertThereIsNoInstanceOf(Runs)
593
594         timestamps = [datetime.now(), datetime.now()]
595         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 52.0], timestamps)
596         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
597         self.assertOnlyInstance(runs)
598         self.assertEqual(json.loads('[' + runs.json_runs + ']'),
599             [[5, [4, 0, 100, None], mktime(timestamps[0].timetuple()), 50.0, 0, [], None, None],
600             [7, [6, 1, 101, None], mktime(timestamps[1].timetuple()), 52.0, 0, [], None, None]])
601         self.assertEqual(json.loads('{' + runs.json_averages + '}'), {"100": 50.0, "101": 52.0})
602         self.assertEqual(runs.json_min, 50.0)
603         self.assertEqual(runs.json_max, 52.0)
604
605         timestamps.append(datetime.now())
606         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [48.0],
607             timestamps[2:], starting_revision=102)
608         runs.update_incrementally(builds[0], results[0])
609
610         self.assertOnlyInstance(runs)
611         self.assertEqual(json.loads('[' + runs.json_runs + ']'),
612             [[5, [4, 0, 100, None], mktime(timestamps[0].timetuple()), 50.0, 0, [], None, None],
613             [7, [6, 1, 101, None], mktime(timestamps[1].timetuple()), 52.0, 0, [], None, None],
614             [9, [8, 0, 102, None], mktime(timestamps[2].timetuple()), 48.0, 0, [], None, None]])
615         self.assertEqual(json.loads('{' + runs.json_averages + '}'), {"100": 50.0, "101": 52.0, "102": 48.0})
616         self.assertEqual(runs.json_min, 48.0)
617         self.assertEqual(runs.json_max, 52.0)
618
619     def test_json_by_ids(self):
620         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
621         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
622         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
623         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
624
625         self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0])
626         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
627         runs_json = runs.to_json()
628
629         key_name = Runs._key_name(some_branch.id, some_platform.id, some_test.id)
630         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), runs_json)
631         self.assertEqual(memcache.get(key_name), runs_json)
632
633         memcache.set(key_name, 'blah')
634         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), 'blah')
635
636         memcache.delete(key_name)
637         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), runs_json)
638         self.assertEqual(memcache.get(key_name), runs_json)
639
640     def test_to_json_without_results(self):
641         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
642         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
643         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
644         self.assertOnlyInstance(some_test)
645         self.assertThereIsNoInstanceOf(TestResult)
646         self.assertEqual(json.loads(Runs.update_or_insert(some_branch, some_platform, some_test).to_json()), {
647             'test_runs': [],
648             'averages': {},
649             'min': None,
650             'max': None,
651             'date_range': None,
652             'stat': 'ok'})
653
654     def test_to_json_with_results(self):
655         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
656         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
657         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
658         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
659         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
660
661         value = json.loads(Runs.update_or_insert(some_branch, some_platform, some_test).to_json())
662         self.assertEqualUnorderedList(value.keys(), ['test_runs', 'averages', 'min', 'max', 'date_range', 'stat'])
663         self.assertEqual(value['stat'], 'ok')
664         self.assertEqual(value['min'], 48.0)
665         self.assertEqual(value['max'], 52.0)
666         self.assertEqual(value['date_range'], None)  # date_range is never given
667
668         self.assertEqual(len(value['test_runs']), len(results))
669         for i, run in enumerate(value['test_runs']):
670             result = results[i]
671             self.assertEqual(run[0], result.key().id())
672             self.assertEqual(run[1][1], i)  # Build number
673             self.assertEqual(run[1][2], 100 + i)  # Revision
674             self.assertEqual(run[1][3], None)  # Supplementary revision
675             self.assertEqual(run[3], result.value)
676             self.assertEqual(run[6], some_builder.key().id())
677             self.assertEqual(run[7], None)  # Statistics
678
679     def _assert_entry(self, entry, build, result, value, statistics=None, supplementary_revisions=None):
680         entry = entry[:]
681         entry[2] = None  # timestamp
682         self.assertEqual(entry, [result.key().id(), [build.key().id(), build.buildNumber, build.revision, supplementary_revisions],
683             None,  # timestamp
684             value, 0,  # runNumber
685             [],  # annotations
686             build.builder.key().id(), statistics])
687
688     def test_run_from_build_and_result(self):
689         branch = Branch.create_if_possible('some-branch', 'Some Branch')
690         platform = Platform.create_if_possible('some-platform', 'Some Platform')
691         builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
692         test_name = ' some-test'
693
694         def create_build(build_number, revision):
695             timestamp = datetime.now().replace(microsecond=0)
696             build = Build(branch=branch, platform=platform, builder=builder, buildNumber=build_number,
697                 revision=revision, timestamp=timestamp)
698             build.put()
699             return build
700
701         build = create_build(1, 101)
702         result = TestResult(name=test_name, value=123.0, build=build)
703         result.put()
704         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 123.0)
705
706         build = create_build(2, 102)
707         result = TestResult(name=test_name, value=456.0, valueMedian=789.0, build=build)
708         result.put()
709         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
710
711         result.valueStdev = 7.0
712         result.put()
713         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
714
715         result.valueStdev = None
716         result.valueMin = 123.0
717         result.valueMax = 789.0
718         result.put()
719         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
720
721         result.valueStdev = 8.0
722         result.valueMin = 123.0
723         result.valueMax = 789.0
724         result.put()
725         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0,
726             statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0})
727
728         result.valueMedian = 345.0  # Median is never used by the frontend.
729         result.valueStdev = 8.0
730         result.valueMin = 123.0
731         result.valueMax = 789.0
732         result.put()
733         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0,
734             statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0})
735
736     def test_chart_params_with_value(self):
737         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
738         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
739         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
740         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
741
742         start_time = datetime(2011, 2, 21, 12, 0, 0)
743         end_time = datetime(2011, 2, 28, 12, 0, 0)
744         results = self._create_results(some_branch, some_platform, some_builder, 'some-test',
745             [50.0, 51.0, 52.0, 49.0, 48.0, 51.9, 50.7, 51.1],
746             [start_time + timedelta(day) for day in range(0, 8)])
747
748         # Use int despite of its impreciseness since tests may fail due to rounding errors otherwise.
749         def split_as_int(string):
750             return [int(float(value)) for value in string.split(',')]
751
752         params = Runs.update_or_insert(some_branch, some_platform, some_test).chart_params(7, end_time)
753         self.assertEqual(params['chxl'], '0:|Feb 21|Feb 22|Feb 23|Feb 24|Feb 25|Feb 26|Feb 27|Feb 28')
754         self.assertEqual(split_as_int(params['chxr']), [1, 0, 57, int(52 * 1.1 / 5 + 0.5)])
755         x_min, x_max, y_min, y_max = split_as_int(params['chds'])
756         self.assertEqual(datetime.fromtimestamp(x_min), start_time)
757         self.assertEqual(datetime.fromtimestamp(x_max), end_time)
758         self.assertEqual(y_min, 0)
759         self.assertEqual(y_max, int(52 * 1.1))
760         self.assertEqual(split_as_int(params['chg']), [int(100 / 7), 20, 0, 0])
761
762         params = Runs.update_or_insert(some_branch, some_platform, some_test).chart_params(14, end_time)
763         self.assertEqual(params['chxl'], '0:|Feb 14|Feb 16|Feb 18|Feb 20|Feb 22|Feb 24|Feb 26|Feb 28')
764         self.assertEqual(split_as_int(params['chxr']), [1, 0, 57, int(52 * 1.1 / 5 + 0.5)])
765         x_min, x_max, y_min, y_max = split_as_int(params['chds'])
766         self.assertEqual(datetime.fromtimestamp(x_min), datetime(2011, 2, 14, 12, 0, 0))
767         self.assertEqual(datetime.fromtimestamp(x_max), end_time)
768         self.assertEqual(y_min, 0)
769         self.assertEqual(y_max, int(52 * 1.1))
770         self.assertEqual(split_as_int(params['chg']), [int(100 / 7), 20, 0, 0])
771
772
773 class DashboardImageTests(DataStoreTestsBase):
774     def setUp(self):
775         super(DashboardImageTests, self).setUp()
776         self.testbed.init_memcache_stub()
777
778     def test_create(self):
779         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), None)
780         self.assertThereIsNoInstanceOf(DashboardImage)
781         image = DashboardImage.create(1, 2, 3, 7, 'blah')
782         self.assertOnlyInstance(image)
783         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
784
785     def test_get(self):
786         image = DashboardImage.create(1, 2, 3, 7, 'blah')
787         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
788         memcache.set('dashboard-image:1:2:3:7', 'new value')
789
790         # Check twice to make sure the first call doesn't clear memcache
791         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'new value')
792         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'new value')
793
794         memcache.delete('dashboard-image:1:2:3:7')
795         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), None)
796         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'blah')
797         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
798
799     def test_needs_update(self):
800         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 7))
801         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 30))
802         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 60))
803         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 365))
804
805         image = DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 7), image='blah')
806         image.put()
807         self.assertOnlyInstance(image)
808         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 7))
809
810         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 30), image='blah').put()
811         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 30, datetime.now() + timedelta(0, 10)))
812
813         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 30), image='blah').put()
814         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 30, datetime.now() + timedelta(1)))
815
816         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 90), image='blah').put()
817         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 90, datetime.now() + timedelta(0, 20)))
818
819         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 90), image='blah').put()
820         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 90, datetime.now() + timedelta(1)))
821
822         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 365), image='blah').put()
823         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 365, datetime.now() + timedelta(1)))
824
825         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 365), image='blah').put()
826         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 365, datetime.now() + timedelta(10)))
827
828
829 if __name__ == '__main__':
830     unittest.main()