230598670d3ef99c6d101fc12bfa1f1780df710a
[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.assertEqual(test.unit, None)
269         self.assertOnlyInstance(test)
270
271     def test_update_or_insert_with_unit(self):
272         branch = Branch.create_if_possible('some-branch', 'Some Branch')
273         platform = Platform.create_if_possible('some-platform', 'Some Platform')
274         test = Test.update_or_insert('some-test', branch, platform, 'runs/s')
275         self.assertOnlyInstance(test)
276         self.assertEqualUnorderedList(test.unit, 'runs/s')
277
278     def test_update_or_insert_to_update(self):
279         branch = Branch.create_if_possible('some-branch', 'Some Branch')
280         platform = Platform.create_if_possible('some-platform', 'Some Platform')
281         test = Test.update_or_insert('some-test', branch, platform)
282         self.assertOnlyInstance(test)
283
284         other_branch = Branch.create_if_possible('other-branch', 'Other Branch')
285         other_platform = Platform.create_if_possible('other-platform', 'Other Platform')
286         test = Test.update_or_insert('some-test', other_branch, other_platform, 'ms')
287         self.assertOnlyInstance(test)
288         self.assertEqualUnorderedList(test.branches, [branch.key(), other_branch.key()])
289         self.assertEqualUnorderedList(test.platforms, [platform.key(), other_platform.key()])
290         self.assertEqualUnorderedList(test.unit, 'ms')
291
292     def test_merge(self):
293         branch, platform, builder = _create_some_builder()
294         some_build = _create_build(branch, platform, builder)
295         some_result = TestResult.get_or_insert_from_parsed_json('some-test', some_build, 50)
296         some_test = Test.update_or_insert('some-test', branch, platform)
297
298         other_build = _create_build(branch, platform, builder, 'other-build')
299         other_result = TestResult.get_or_insert_from_parsed_json('other-test', other_build, 30)
300         other_test = Test.update_or_insert('other-test', branch, platform)
301
302         self.assertOnlyInstances([some_result, other_result])
303         self.assertNotEqual(some_result.key(), other_result.key())
304         self.assertOnlyInstances([some_test, other_test])
305
306         self.assertRaises(AssertionError, some_test.merge, (some_test))
307         self.assertOnlyInstances([some_test, other_test])
308
309         some_test.merge(other_test)
310         results_for_some_test = TestResult.all()
311         results_for_some_test.filter('name =', 'some-test')
312         results_for_some_test = results_for_some_test.fetch(5)
313         self.assertEqual(len(results_for_some_test), 2)
314
315         self.assertEqual(results_for_some_test[0].name, 'some-test')
316         self.assertEqual(results_for_some_test[1].name, 'some-test')
317
318         if results_for_some_test[0].value == 50:
319             self.assertEqual(results_for_some_test[1].value, 30)
320         else:
321             self.assertEqual(results_for_some_test[1].value, 50)
322
323
324 class TestResultTests(DataStoreTestsBase):
325     def test_get_or_insert_value(self):
326         branch, platform, builder = _create_some_builder()
327         build = _create_build(branch, platform, builder)
328         self.assertThereIsNoInstanceOf(TestResult)
329         result = TestResult.get_or_insert_from_parsed_json('some-test', build, 50)
330         self.assertOnlyInstance(result)
331         self.assertEqual(result.name, 'some-test')
332         self.assertEqual(result.build.key(), build.key())
333         self.assertEqual(result.value, 50.0)
334         self.assertEqual(result.valueMedian, None)
335         self.assertEqual(result.valueStdev, None)
336         self.assertEqual(result.valueMin, None)
337         self.assertEqual(result.valueMax, None)
338
339     def test_get_or_insert_stat_value(self):
340         branch, platform, builder = _create_some_builder()
341         build = _create_build(branch, platform, builder)
342         self.assertThereIsNoInstanceOf(TestResult)
343         result = TestResult.get_or_insert_from_parsed_json('some-test', build,
344             {"avg": 40, "median": "40.1", "stdev": 3.25, "min": 30.5, "max": 45})
345         self.assertOnlyInstance(result)
346         self.assertEqual(result.name, 'some-test')
347         self.assertEqual(result.build.key(), build.key())
348         self.assertEqual(result.value, 40.0)
349         self.assertEqual(result.valueMedian, 40.1)
350         self.assertEqual(result.valueStdev, 3.25)
351         self.assertEqual(result.valueMin, 30.5)
352         self.assertEqual(result.valueMax, 45)
353
354     def test_replace_to_change_test_name(self):
355         branch, platform, builder = _create_some_builder()
356         build = _create_build(branch, platform, builder)
357         self.assertThereIsNoInstanceOf(TestResult)
358         result = TestResult.get_or_insert_from_parsed_json('some-test', build, 50)
359         self.assertOnlyInstance(result)
360         self.assertEqual(result.name, 'some-test')
361
362         new_result = result.replace_to_change_test_name('other-test')
363         self.assertNotEqual(result, new_result)
364         self.assertOnlyInstance(new_result)
365
366         self.assertEqual(new_result.name, 'other-test')
367         self.assertEqual(new_result.build.key(), result.build.key())
368         self.assertEqual(new_result.value, result.value)
369         self.assertEqual(new_result.valueMedian, None)
370         self.assertEqual(new_result.valueStdev, None)
371         self.assertEqual(new_result.valueMin, None)
372         self.assertEqual(new_result.valueMax, None)
373
374     def test_replace_to_change_test_name_with_stat_value(self):
375         branch, platform, builder = _create_some_builder()
376         build = _create_build(branch, platform, builder)
377         self.assertThereIsNoInstanceOf(TestResult)
378         result = TestResult.get_or_insert_from_parsed_json('some-test', build,
379             {"avg": 40, "median": "40.1", "stdev": 3.25, "min": 30.5, "max": 45})
380         self.assertOnlyInstance(result)
381         self.assertEqual(result.name, 'some-test')
382
383         new_result = result.replace_to_change_test_name('other-test')
384         self.assertNotEqual(result, new_result)
385         self.assertOnlyInstance(new_result)
386
387         self.assertEqual(new_result.name, 'other-test')
388         self.assertEqual(new_result.build.key(), result.build.key())
389         self.assertEqual(new_result.value, result.value)
390         self.assertEqual(result.value, 40.0)
391         self.assertEqual(result.valueMedian, 40.1)
392         self.assertEqual(result.valueStdev, 3.25)
393         self.assertEqual(result.valueMin, 30.5)
394         self.assertEqual(result.valueMax, 45)
395
396     def test_replace_to_change_test_name_overrides_conflicting_result(self):
397         branch, platform, builder = _create_some_builder()
398         build = _create_build(branch, platform, builder)
399         self.assertThereIsNoInstanceOf(TestResult)
400         result = TestResult.get_or_insert_from_parsed_json('some-test', build, 20)
401         self.assertOnlyInstance(result)
402
403         conflicting_result = TestResult.get_or_insert_from_parsed_json('other-test', build, 10)
404
405         new_result = result.replace_to_change_test_name('other-test')
406         self.assertNotEqual(result, new_result)
407         self.assertOnlyInstance(new_result)
408
409         self.assertEqual(new_result.name, 'other-test')
410         self.assertEqual(TestResult.get(conflicting_result.key()).value, 20)
411
412
413 class ReportLogTests(DataStoreTestsBase):
414     def _create_log_with_payload(self, payload):
415         return ReportLog(timestamp=datetime.now(), headers='some headers', payload=payload)
416
417     def test_parsed_payload(self):
418         log = self._create_log_with_payload('')
419         self.assertFalse('_parsed' in log.__dict__)
420         self.assertEqual(log._parsed_payload(), False)
421         self.assertEqual(log._parsed, False)
422
423         log = self._create_log_with_payload('{"key": "value", "another key": 1}')
424         self.assertEqual(log._parsed_payload(), {"key": "value", "another key": 1})
425         self.assertEqual(log._parsed, {"key": "value", "another key": 1})
426
427     def test_get_value(self):
428         log = self._create_log_with_payload('{"string": "value", "integer": 1, "float": 1.1}')
429         self.assertEqual(log.get_value('string'), 'value')
430         self.assertEqual(log.get_value('integer'), 1)
431         self.assertEqual(log.get_value('float'), 1.1)
432         self.assertEqual(log.get_value('bad'), None)
433
434     def test_results(self):
435         log = self._create_log_with_payload('{"results": 123}')
436         self.assertEqual(log.results(), 123)
437
438         log = self._create_log_with_payload('{"key": "value"}')
439         self.assertEqual(log.results(), None)
440
441     def test_results_are_well_formed(self):
442
443         def assert_results_are_well_formed(json, expected):
444             self.assertEqual(self._create_log_with_payload(json).results_are_well_formed(), expected)
445
446         assert_results_are_well_formed('{"results": 123}', False)
447         assert_results_are_well_formed('{"results": {"test": 123}}', True)
448         assert_results_are_well_formed('{"results": {"test": 123, "other-test": 456}}', True)
449         assert_results_are_well_formed('{"results": {"test": 123, "other-test": 456, "bad-test": "hi"}}', False)
450         assert_results_are_well_formed('{"results": {"test": {"avg": 456}}}', True)
451         assert_results_are_well_formed('{"results": {"test": {"avg": 456, "median": "hello"}}}', False)
452         assert_results_are_well_formed('{"results": {"test": {"avg": 456, "median": 789}}}', True)
453         assert_results_are_well_formed('{"results": {"test": {"avg": 456, "unit": "bytes"}}}', True)
454
455     def test_builder(self):
456         log = self._create_log_with_payload('{"key": "value"}')
457         self.assertEqual(log.builder(), None)
458
459         builder_name = "Chromium Mac Release (Perf)"
460         log = self._create_log_with_payload('{"builder-name": "%s"}' % builder_name)
461         self.assertEqual(log.builder(), None)
462
463         builder_key = Builder.create(builder_name, 'some password')
464         log = self._create_log_with_payload('{"builder-name": "%s"}' % builder_name)
465         self.assertEqual(log.builder().key(), builder_key)
466
467     def test_branch(self):
468         log = self._create_log_with_payload('{"key": "value"}')
469         self.assertEqual(log.branch(), None)
470
471         log = self._create_log_with_payload('{"branch": "some-branch"}')
472         self.assertEqual(log.branch(), None)
473
474         branch = Branch.create_if_possible("some-branch", "Some Branch")
475         log = self._create_log_with_payload('{"branch": "some-branch"}')
476         self.assertEqual(log.branch().key(), branch.key())
477
478     def test_platform(self):
479         log = self._create_log_with_payload('{"key": "value"}')
480         self.assertEqual(log.platform(), None)
481
482         log = self._create_log_with_payload('{"platform": "some-platform"}')
483         self.assertEqual(log.platform(), None)
484
485         platform = Platform.create_if_possible("some-platform", "Some Platform")
486         log = self._create_log_with_payload('{"platform": "some-platform"}')
487         self.assertEqual(log.platform().key(), platform.key())
488
489     def test_build_number(self):
490         log = self._create_log_with_payload('{"build-number": 123}')
491         self.assertEqual(log.build_number(), 123)
492
493         log = self._create_log_with_payload('{"key": "value"}')
494         self.assertEqual(log.build_number(), None)
495
496     def test_webkit_revision(self):
497         log = self._create_log_with_payload('{"key": "value"}')
498         self.assertEqual(log.webkit_revision(), None)
499
500         log = self._create_log_with_payload('{"webkit-revision": 123}')
501         self.assertEqual(log.webkit_revision(), 123)
502
503     def chromium_revision(self):
504         log = self._create_log_with_payload('{"chromium-revision": 123}')
505         self.assertEqual(log.webkit_revision(), 123)
506
507         log = self._create_log_with_payload('{"key": "value"}')
508         self.assertEqual(log.webkit_revision(), None)
509
510
511 class PersistentCacheTests(DataStoreTestsBase):
512     def setUp(self):
513         super(PersistentCacheTests, self).setUp()
514         self.testbed.init_memcache_stub()
515
516     def _assert_persistent_cache(self, name, value):
517         self.assertEqual(PersistentCache.get_by_key_name(name).value, value)
518         self.assertEqual(memcache.get(name), value)
519
520     def test_set_cache(self):
521         self.assertThereIsNoInstanceOf(PersistentCache)
522
523         PersistentCache.set_cache('some-cache', 'some data')
524         self._assert_persistent_cache('some-cache', 'some data')
525
526         PersistentCache.set_cache('some-cache', 'some other data')
527
528         self._assert_persistent_cache('some-cache', 'some other data')
529
530     def test_get_cache(self):
531         self.assertEqual(memcache.get('some-cache'), None)
532         self.assertEqual(PersistentCache.get_cache('some-cache'), None)
533
534         PersistentCache.set_cache('some-cache', 'some data')
535
536         self.assertEqual(memcache.get('some-cache'), 'some data')
537         self.assertEqual(PersistentCache.get_cache('some-cache'), 'some data')
538
539         memcache.delete('some-cache')
540         self.assertEqual(memcache.get('some-cache'), None)
541         self.assertEqual(PersistentCache.get_cache('some-cache'), 'some data')
542
543
544 class RunsTest(DataStoreTestsBase):
545     def setUp(self):
546         super(RunsTest, self).setUp()
547         self.testbed.init_memcache_stub()
548
549     def _create_results(self, branch, platform, builder, test_name, values, timestamps=None, starting_revision=100):
550         builds = []
551         results = []
552         for i, value in enumerate(values):
553             build = Build(branch=branch, platform=platform, builder=builder,
554                 buildNumber=i, revision=starting_revision + i, timestamp=timestamps[i] if timestamps else datetime.now())
555             build.put()
556             result = TestResult(name=test_name, build=build, value=value)
557             result.put()
558             builds.append(build)
559             results.append(result)
560         return builds, results
561
562     def test_generate_runs(self):
563         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
564         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
565         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
566         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
567
568         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
569         last_i = 0
570         for i, (build, result) in enumerate(Runs._generate_runs(some_branch, some_platform, some_test)):
571             self.assertEqual(build.buildNumber, i)
572             self.assertEqual(build.revision, 100 + i)
573             self.assertEqual(result.name, 'some-test')
574             self.assertEqual(result.value, results[i].value)
575             last_i = i
576         self.assertTrue(last_i + 1, len(results))
577
578     def test_update_or_insert(self):
579         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
580         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
581         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
582         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
583         self.assertThereIsNoInstanceOf(Runs)
584
585         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
586         self.assertOnlyInstance(runs)
587         self.assertEqual(runs.json_runs, '')
588         self.assertEqual(runs.json_averages, '')
589         self.assertEqual(runs.json_min, None)
590         self.assertEqual(runs.json_max, None)
591         old_memcache_value = memcache.get(Runs._key_name(some_branch.id, some_platform.id, some_test.id))
592         self.assertTrue(old_memcache_value)
593
594         runs.delete()
595         self.assertThereIsNoInstanceOf(Runs)
596
597         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0])
598         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
599         self.assertOnlyInstance(runs)
600         self.assertTrue(runs.json_runs.startswith('[5, [4, 0, 100, null],'))
601         self.assertEqual(json.loads('{' + runs.json_averages + '}'), {"100": 50.0})
602         self.assertEqual(runs.json_min, 50.0)
603         self.assertEqual(runs.json_max, 50.0)
604         self.assertNotEqual(memcache.get(Runs._key_name(some_branch.id, some_platform.id, some_test.id)), old_memcache_value)
605
606     def test_update_incrementally(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_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
610         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
611         self.assertThereIsNoInstanceOf(Runs)
612
613         timestamps = [datetime.now(), datetime.now()]
614         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 52.0], timestamps)
615         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
616         self.assertOnlyInstance(runs)
617         self.assertEqual(json.loads('[' + runs.json_runs + ']'),
618             [[5, [4, 0, 100, None], mktime(timestamps[0].timetuple()), 50.0, 0, [], None, None],
619             [7, [6, 1, 101, None], mktime(timestamps[1].timetuple()), 52.0, 0, [], None, None]])
620         self.assertEqual(json.loads('{' + runs.json_averages + '}'), {"100": 50.0, "101": 52.0})
621         self.assertEqual(runs.json_min, 50.0)
622         self.assertEqual(runs.json_max, 52.0)
623
624         timestamps.append(datetime.now())
625         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [48.0],
626             timestamps[2:], starting_revision=102)
627         runs.update_incrementally(builds[0], results[0])
628
629         self.assertOnlyInstance(runs)
630         self.assertEqual(json.loads('[' + runs.json_runs + ']'),
631             [[5, [4, 0, 100, None], mktime(timestamps[0].timetuple()), 50.0, 0, [], None, None],
632             [7, [6, 1, 101, None], mktime(timestamps[1].timetuple()), 52.0, 0, [], None, None],
633             [9, [8, 0, 102, None], mktime(timestamps[2].timetuple()), 48.0, 0, [], None, None]])
634         self.assertEqual(json.loads('{' + runs.json_averages + '}'), {"100": 50.0, "101": 52.0, "102": 48.0})
635         self.assertEqual(runs.json_min, 48.0)
636         self.assertEqual(runs.json_max, 52.0)
637
638     def test_json_by_ids(self):
639         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
640         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
641         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
642         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
643
644         self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0])
645         runs = Runs.update_or_insert(some_branch, some_platform, some_test)
646         runs_json = runs.to_json()
647
648         key_name = Runs._key_name(some_branch.id, some_platform.id, some_test.id)
649         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), runs_json)
650         self.assertEqual(memcache.get(key_name), runs_json)
651
652         memcache.set(key_name, 'blah')
653         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), 'blah')
654
655         memcache.delete(key_name)
656         self.assertEqual(Runs.json_by_ids(some_branch.id, some_platform.id, some_test.id), runs_json)
657         self.assertEqual(memcache.get(key_name), runs_json)
658
659     def test_to_json_without_results(self):
660         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
661         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
662         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
663         self.assertOnlyInstance(some_test)
664         self.assertThereIsNoInstanceOf(TestResult)
665         self.assertEqual(json.loads(Runs.update_or_insert(some_branch, some_platform, some_test).to_json()), {
666             'test_runs': [],
667             'averages': {},
668             'min': None,
669             'max': None,
670             'unit': None,
671             'date_range': None,
672             'stat': 'ok'})
673
674     def test_to_json_with_results(self):
675         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
676         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
677         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
678         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
679         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
680
681         value = json.loads(Runs.update_or_insert(some_branch, some_platform, some_test).to_json())
682         self.assertEqualUnorderedList(value.keys(), ['test_runs', 'averages', 'min', 'max', 'unit', 'date_range', 'stat'])
683         self.assertEqual(value['stat'], 'ok')
684         self.assertEqual(value['min'], 48.0)
685         self.assertEqual(value['max'], 52.0)
686         self.assertEqual(value['unit'], None)
687         self.assertEqual(value['date_range'], None)  # date_range is never given
688
689         self.assertEqual(len(value['test_runs']), len(results))
690         for i, run in enumerate(value['test_runs']):
691             result = results[i]
692             self.assertEqual(run[0], result.key().id())
693             self.assertEqual(run[1][1], i)  # Build number
694             self.assertEqual(run[1][2], 100 + i)  # Revision
695             self.assertEqual(run[1][3], None)  # Supplementary revision
696             self.assertEqual(run[3], result.value)
697             self.assertEqual(run[6], some_builder.key().id())
698             self.assertEqual(run[7], None)  # Statistics
699
700     def test_to_json_with_unit(self):
701         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
702         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
703         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
704         some_test = Test.update_or_insert('some-test', some_branch, some_platform, 'runs/s')
705         builds, results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
706
707         value = json.loads(Runs.update_or_insert(some_branch, some_platform, some_test).to_json())
708         self.assertEqual(value['unit'], 'runs/s')
709
710     def _assert_entry(self, entry, build, result, value, statistics=None, supplementary_revisions=None):
711         entry = entry[:]
712         entry[2] = None  # timestamp
713         self.assertEqual(entry, [result.key().id(), [build.key().id(), build.buildNumber, build.revision, supplementary_revisions],
714             None,  # timestamp
715             value, 0,  # runNumber
716             [],  # annotations
717             build.builder.key().id(), statistics])
718
719     def test_run_from_build_and_result(self):
720         branch = Branch.create_if_possible('some-branch', 'Some Branch')
721         platform = Platform.create_if_possible('some-platform', 'Some Platform')
722         builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
723         test_name = ' some-test'
724
725         def create_build(build_number, revision):
726             timestamp = datetime.now().replace(microsecond=0)
727             build = Build(branch=branch, platform=platform, builder=builder, buildNumber=build_number,
728                 revision=revision, timestamp=timestamp)
729             build.put()
730             return build
731
732         build = create_build(1, 101)
733         result = TestResult(name=test_name, value=123.0, build=build)
734         result.put()
735         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 123.0)
736
737         build = create_build(2, 102)
738         result = TestResult(name=test_name, value=456.0, valueMedian=789.0, build=build)
739         result.put()
740         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
741
742         result.valueStdev = 7.0
743         result.put()
744         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
745
746         result.valueStdev = None
747         result.valueMin = 123.0
748         result.valueMax = 789.0
749         result.put()
750         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0)
751
752         result.valueStdev = 8.0
753         result.valueMin = 123.0
754         result.valueMax = 789.0
755         result.put()
756         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0,
757             statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0})
758
759         result.valueMedian = 345.0  # Median is never used by the frontend.
760         result.valueStdev = 8.0
761         result.valueMin = 123.0
762         result.valueMax = 789.0
763         result.put()
764         self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0,
765             statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0})
766
767     def test_chart_params_with_value(self):
768         some_branch = Branch.create_if_possible('some-branch', 'Some Branch')
769         some_platform = Platform.create_if_possible('some-platform', 'Some Platform')
770         some_builder = Builder.get(Builder.create('some-builder', 'Some Builder'))
771         some_test = Test.update_or_insert('some-test', some_branch, some_platform)
772
773         start_time = datetime(2011, 2, 21, 12, 0, 0)
774         end_time = datetime(2011, 2, 28, 12, 0, 0)
775         results = self._create_results(some_branch, some_platform, some_builder, 'some-test',
776             [50.0, 51.0, 52.0, 49.0, 48.0, 51.9, 50.7, 51.1],
777             [start_time + timedelta(day) for day in range(0, 8)])
778
779         # Use int despite of its impreciseness since tests may fail due to rounding errors otherwise.
780         def split_as_int(string):
781             return [int(float(value)) for value in string.split(',')]
782
783         params = Runs.update_or_insert(some_branch, some_platform, some_test).chart_params(7)
784         self.assertEqual(params['chxl'], '0:|Feb 21|Feb 22|Feb 23|Feb 24|Feb 25|Feb 26|Feb 27|Feb 28')
785         self.assertEqual(split_as_int(params['chxr']), [1, 0, 57, int(52 * 1.1 / 5 + 0.5)])
786         x_min, x_max, y_min, y_max = split_as_int(params['chds'])
787         self.assertEqual(datetime.fromtimestamp(x_min), start_time)
788         self.assertEqual(datetime.fromtimestamp(x_max), end_time)
789         self.assertEqual(y_min, 0)
790         self.assertEqual(y_max, int(52 * 1.1))
791         self.assertEqual(split_as_int(params['chg']), [int(100 / 7), 20, 0, 0])
792
793         params = Runs.update_or_insert(some_branch, some_platform, some_test).chart_params(14)
794         self.assertEqual(params['chxl'], '0:|Feb 14|Feb 16|Feb 18|Feb 20|Feb 22|Feb 24|Feb 26|Feb 28')
795         self.assertEqual(split_as_int(params['chxr']), [1, 0, 57, int(52 * 1.1 / 5 + 0.5)])
796         x_min, x_max, y_min, y_max = split_as_int(params['chds'])
797         self.assertEqual(datetime.fromtimestamp(x_min), datetime(2011, 2, 14, 12, 0, 0))
798         self.assertEqual(datetime.fromtimestamp(x_max), end_time)
799         self.assertEqual(y_min, 0)
800         self.assertEqual(y_max, int(52 * 1.1))
801         self.assertEqual(split_as_int(params['chg']), [int(100 / 7), 20, 0, 0])
802
803
804 class DashboardImageTests(DataStoreTestsBase):
805     def setUp(self):
806         super(DashboardImageTests, self).setUp()
807         self.testbed.init_memcache_stub()
808
809     def test_create(self):
810         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), None)
811         self.assertThereIsNoInstanceOf(DashboardImage)
812         image = DashboardImage.create(1, 2, 3, 7, 'blah')
813         self.assertOnlyInstance(image)
814         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
815
816     def test_get(self):
817         image = DashboardImage.create(1, 2, 3, 7, 'blah')
818         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
819         memcache.set('dashboard-image:1:2:3:7', 'new value')
820
821         # Check twice to make sure the first call doesn't clear memcache
822         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'new value')
823         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'new value')
824
825         memcache.delete('dashboard-image:1:2:3:7')
826         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), None)
827         self.assertEqual(DashboardImage.get_image(1, 2, 3, 7), 'blah')
828         self.assertEqual(memcache.get('dashboard-image:1:2:3:7'), 'blah')
829
830     def test_needs_update(self):
831         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 7))
832         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 30))
833         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 60))
834         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 365))
835
836         image = DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 7), image='blah')
837         image.put()
838         self.assertOnlyInstance(image)
839         self.assertTrue(DashboardImage.needs_update(1, 2, 3, 7))
840
841         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 30), image='blah').put()
842         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 30, datetime.now() + timedelta(0, 10)))
843
844         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 30), image='blah').put()
845         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 30, datetime.now() + timedelta(1)))
846
847         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 90), image='blah').put()
848         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 90, datetime.now() + timedelta(0, 20)))
849
850         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 90), image='blah').put()
851         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 90, datetime.now() + timedelta(1)))
852
853         DashboardImage(key_name=DashboardImage.key_name(1, 2, 3, 365), image='blah').put()
854         self.assertFalse(DashboardImage.needs_update(1, 2, 3, 365, datetime.now() + timedelta(1)))
855
856         DashboardImage(key_name=DashboardImage.key_name(1, 2, 4, 365), image='blah').put()
857         self.assertTrue(DashboardImage.needs_update(1, 2, 4, 365, datetime.now() + timedelta(10)))
858
859
860 if __name__ == '__main__':
861     unittest.main()