Move more logic from handler classes to model classes and add unit tests
[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 models
31 import unittest
32
33 from datetime import datetime
34 from google.appengine.api import memcache
35 from google.appengine.ext import testbed
36 from time import mktime
37
38
39 class DataStoreTestsBase(unittest.TestCase):
40     def setUp(self):
41         self.testbed = testbed.Testbed()
42         self.testbed.activate()
43         self.testbed.init_datastore_v3_stub()
44
45     def tearDown(self):
46         self.testbed.deactivate()
47
48     def assertThereIsNoInstanceOf(self, model):
49         self.assertEqual(len(model.all().fetch(5)), 0)
50
51     def assertOnlyInstance(self, only_instasnce):
52         self.assertEqual(len(only_instasnce.__class__.all().fetch(5)), 1)
53         self.assertTrue(only_instasnce.__class__.get(only_instasnce.key()))
54
55     def assertEqualUnorderedList(self, list1, list2):
56         self.assertEqual(set(list1), set(list2))
57
58
59 class HelperTests(DataStoreTestsBase):
60     def _assert_there_is_exactly_one_id_holder_and_matches(self, id):
61         id_holders = models.NumericIdHolder.all().fetch(5)
62         self.assertEqual(len(id_holders), 1)
63         self.assertTrue(id_holders[0])
64         self.assertEqual(id_holders[0].key().id(), id)
65
66     def test_create_in_transaction_with_numeric_id_holder(self):
67
68         def execute(id):
69             return models.Branch(id=id, name='some branch', key_name='some-branch').put()
70
71         self.assertThereIsNoInstanceOf(models.Branch)
72         self.assertThereIsNoInstanceOf(models.NumericIdHolder)
73
74         self.assertTrue(models.create_in_transaction_with_numeric_id_holder(execute))
75
76         branches = models.Branch.all().fetch(5)
77         self.assertEqual(len(branches), 1)
78         self.assertEqual(branches[0].name, 'some branch')
79         self.assertEqual(branches[0].key().name(), 'some-branch')
80
81         self._assert_there_is_exactly_one_id_holder_and_matches(branches[0].id)
82
83     def test_failing_in_create_in_transaction_with_numeric_id_holder(self):
84
85         def execute(id):
86             return None
87
88         self.assertThereIsNoInstanceOf(models.Branch)
89         self.assertThereIsNoInstanceOf(models.NumericIdHolder)
90
91         self.assertFalse(models.create_in_transaction_with_numeric_id_holder(execute))
92
93         self.assertThereIsNoInstanceOf(models.Branch)
94         self.assertThereIsNoInstanceOf(models.NumericIdHolder)
95
96     def test_raising_in_create_in_transaction_with_numeric_id_holder(self):
97
98         def execute(id):
99             raise TypeError
100             return None
101
102         self.assertThereIsNoInstanceOf(models.Branch)
103         self.assertThereIsNoInstanceOf(models.NumericIdHolder)
104
105         self.assertRaises(TypeError, models.create_in_transaction_with_numeric_id_holder, (execute))
106
107         self.assertThereIsNoInstanceOf(models.Branch)
108         self.assertThereIsNoInstanceOf(models.NumericIdHolder)
109
110     def test_delete_model_with_numeric_id_holder(self):
111
112         def execute(id):
113             return models.Branch(id=id, name='some branch', key_name='some-branch').put()
114
115         branch = models.Branch.get(models.create_in_transaction_with_numeric_id_holder(execute))
116         self.assertOnlyInstance(branch)
117
118         models.delete_model_with_numeric_id_holder(branch)
119
120         self.assertThereIsNoInstanceOf(models.Branch)
121         self.assertThereIsNoInstanceOf(models.NumericIdHolder)
122
123     def test_model_from_numeric_id(self):
124
125         def execute(id):
126             return models.Branch(id=id, name='some branch', key_name='some-branch').put()
127
128         branch = models.Branch.get(models.create_in_transaction_with_numeric_id_holder(execute))
129
130         self.assertEqual(models.model_from_numeric_id(branch.id, models.Branch).key(), branch.key())
131         self.assertEqual(models.model_from_numeric_id(branch.id + 1, models.Branch), None)
132         models.delete_model_with_numeric_id_holder(branch)
133         self.assertEqual(models.model_from_numeric_id(branch.id, models.Branch), None)
134
135
136 class BranchTests(DataStoreTestsBase):
137     def test_create_if_possible(self):
138         self.assertThereIsNoInstanceOf(models.Branch)
139
140         branch = models.Branch.create_if_possible('some-branch', 'some branch')
141         self.assertTrue(branch)
142         self.assertTrue(branch.key().name(), 'some-branch')
143         self.assertTrue(branch.name, 'some branch')
144         self.assertOnlyInstance(branch)
145
146         self.assertFalse(models.Branch.create_if_possible('some-branch', 'some other branch'))
147         self.assertTrue(branch.name, 'some branch')
148         self.assertOnlyInstance(branch)
149
150
151 class PlatformTests(DataStoreTestsBase):
152     def test_create_if_possible(self):
153         self.assertThereIsNoInstanceOf(models.Platform)
154
155         platform = models.Platform.create_if_possible('some-platform', 'some platform')
156         self.assertTrue(platform)
157         self.assertTrue(platform.key().name(), 'some-platform')
158         self.assertTrue(platform.name, 'some platform')
159         self.assertOnlyInstance(platform)
160
161         self.assertFalse(models.Platform.create_if_possible('some-platform', 'some other platform'))
162         self.assertTrue(platform.name, 'some platform')
163         self.assertOnlyInstance(platform)
164
165
166 class BuilderTests(DataStoreTestsBase):
167     def test_create(self):
168         builder_key = models.Builder.create('some builder', 'some password')
169         self.assertTrue(builder_key)
170         builder = models.Builder.get(builder_key)
171         self.assertEqual(builder.key().name(), 'some builder')
172         self.assertEqual(builder.name, 'some builder')
173         self.assertEqual(builder.password, models.Builder._hashed_password('some password'))
174
175     def test_update_password(self):
176         builder = models.Builder.get(models.Builder.create('some builder', 'some password'))
177         self.assertEqual(builder.password, models.Builder._hashed_password('some password'))
178         builder.update_password('other password')
179         self.assertEqual(builder.password, models.Builder._hashed_password('other password'))
180
181         # Make sure it's saved
182         builder = models.Builder.get(builder.key())
183         self.assertEqual(builder.password, models.Builder._hashed_password('other password'))
184
185     def test_hashed_password(self):
186         self.assertNotEqual(models.Builder._hashed_password('some password'), 'some password')
187         self.assertFalse('some password' in models.Builder._hashed_password('some password'))
188         self.assertEqual(len(models.Builder._hashed_password('some password')), 64)
189
190     def test_authenticate(self):
191         builder = models.Builder.get(models.Builder.create('some builder', 'some password'))
192         self.assertTrue(builder.authenticate('some password'))
193         self.assertFalse(builder.authenticate('bad password'))
194
195
196 def _create_some_builder():
197     branch = models.Branch.create_if_possible('some-branch', 'Some Branch')
198     platform = models.Platform.create_if_possible('some-platform', 'Some Platform')
199     builder_key = models.Builder.create('some-builder', 'Some Builder')
200     return branch, platform, models.Builder.get(builder_key)
201
202
203 class BuildTests(DataStoreTestsBase):
204     def test_get_or_insert_from_log(self):
205         branch, platform, builder = _create_some_builder()
206
207         timestamp = datetime.now().replace(microsecond=0)
208         log = models.ReportLog(timestamp=timestamp, headers='some headers',
209             payload='{"branch": "some-branch", "platform": "some-platform", "builder-name": "some-builder",' +
210                 '"build-number": 123, "webkit-revision": 456, "timestamp": %d}' % int(mktime(timestamp.timetuple())))
211
212         self.assertThereIsNoInstanceOf(models.Build)
213
214         build = models.Build.get_or_insert_from_log(log)
215         self.assertTrue(build)
216         self.assertEqual(build.branch.key(), branch.key())
217         self.assertEqual(build.platform.key(), platform.key())
218         self.assertEqual(build.builder.key(), builder.key())
219         self.assertEqual(build.buildNumber, 123)
220         self.assertEqual(build.revision, 456)
221         self.assertEqual(build.chromiumRevision, None)
222         self.assertEqual(build.timestamp, timestamp)
223
224         self.assertOnlyInstance(build)
225
226
227 class TestModelTests(DataStoreTestsBase):
228     def test_update_or_insert(self):
229         branch = models.Branch.create_if_possible('some-branch', 'Some Branch')
230         platform = models.Platform.create_if_possible('some-platform', 'Some Platform')
231
232         self.assertThereIsNoInstanceOf(models.Test)
233
234         test = models.Test.update_or_insert('some-test', branch, platform)
235         self.assertTrue(test)
236         self.assertEqual(test.branches, [branch.key()])
237         self.assertEqual(test.platforms, [platform.key()])
238         self.assertOnlyInstance(test)
239
240     def test_update_or_insert_to_update(self):
241         branch = models.Branch.create_if_possible('some-branch', 'Some Branch')
242         platform = models.Platform.create_if_possible('some-platform', 'Some Platform')
243         test = models.Test.update_or_insert('some-test', branch, platform)
244         self.assertOnlyInstance(test)
245
246         other_branch = models.Branch.create_if_possible('other-branch', 'Other Branch')
247         other_platform = models.Platform.create_if_possible('other-platform', 'Other Platform')
248         test = models.Test.update_or_insert('some-test', other_branch, other_platform)
249         self.assertOnlyInstance(test)
250         self.assertEqualUnorderedList(test.branches, [branch.key(), other_branch.key()])
251         self.assertEqualUnorderedList(test.platforms, [platform.key(), other_platform.key()])
252
253
254 class TestResultTests(DataStoreTestsBase):
255     def _create_build(self):
256         branch, platform, builder = _create_some_builder()
257         build_key = models.Build(key_name='some-build', branch=branch, platform=platform, builder=builder,
258             buildNumber=1, revision=100, timestamp=datetime.now()).put()
259         return models.Build.get(build_key)
260
261     def test_get_or_insert_value(self):
262         build = self._create_build()
263         self.assertThereIsNoInstanceOf(models.TestResult)
264         result = models.TestResult.get_or_insert_from_parsed_json('some-test', build, 50)
265         self.assertOnlyInstance(result)
266         self.assertEqual(result.name, 'some-test')
267         self.assertEqual(result.build.key(), build.key())
268         self.assertEqual(result.value, 50.0)
269         self.assertEqual(result.valueMedian, None)
270         self.assertEqual(result.valueStdev, None)
271         self.assertEqual(result.valueMin, None)
272         self.assertEqual(result.valueMax, None)
273
274     def test_get_or_insert_stat_value(self):
275         build = self._create_build()
276         self.assertThereIsNoInstanceOf(models.TestResult)
277         result = models.TestResult.get_or_insert_from_parsed_json('some-test', build,
278             {"avg": 40, "median": "40.1", "stdev": 3.25, "min": 30.5, "max": 45})
279         self.assertOnlyInstance(result)
280         self.assertEqual(result.name, 'some-test')
281         self.assertEqual(result.build.key(), build.key())
282         self.assertEqual(result.value, 40.0)
283         self.assertEqual(result.valueMedian, 40.1)
284         self.assertEqual(result.valueStdev, 3.25)
285         self.assertEqual(result.valueMin, 30.5)
286         self.assertEqual(result.valueMax, 45)
287
288     def _create_results(self, test_name, values):
289         branch, platform, builder = _create_some_builder()
290         results = []
291         for i, value in enumerate(values):
292             build = models.Build(branch=branch, platform=platform, builder=builder,
293                 buildNumber=i, revision=100 + i, timestamp=datetime.now())
294             build.put()
295             result = models.TestResult(name=test_name, build=build, value=value)
296             result.put()
297             results.append(result)
298         return branch, platform, results
299
300     def test_generate_runs(self):
301         branch, platform, results = self._create_results('some-test', [50.0, 51.0, 52.0, 49.0, 48.0])
302         last_i = 0
303         for i, (build, result) in enumerate(models.TestResult.generate_runs(branch, platform, "some-test")):
304             self.assertEqual(build.buildNumber, i)
305             self.assertEqual(build.revision, 100 + i)
306             self.assertEqual(result.name, 'some-test')
307             self.assertEqual(result.value, results[i].value)
308             last_i = i
309         self.assertTrue(last_i + 1, len(results))
310
311
312 class ReportLogTests(DataStoreTestsBase):
313     def _create_log_with_payload(self, payload):
314         return models.ReportLog(timestamp=datetime.now(), headers='some headers', payload=payload)
315
316     def test_parsed_payload(self):
317         log = self._create_log_with_payload('')
318         self.assertFalse('_parsed' in log.__dict__)
319         self.assertEqual(log._parsed_payload(), False)
320         self.assertEqual(log._parsed, False)
321
322         log = self._create_log_with_payload('{"key": "value", "another key": 1}')
323         self.assertEqual(log._parsed_payload(), {"key": "value", "another key": 1})
324         self.assertEqual(log._parsed, {"key": "value", "another key": 1})
325
326     def test_get_value(self):
327         log = self._create_log_with_payload('{"string": "value", "integer": 1, "float": 1.1}')
328         self.assertEqual(log.get_value('string'), 'value')
329         self.assertEqual(log.get_value('integer'), 1)
330         self.assertEqual(log.get_value('float'), 1.1)
331         self.assertEqual(log.get_value('bad'), None)
332
333     def test_results(self):
334         log = self._create_log_with_payload('{"results": 123}')
335         self.assertEqual(log.results(), 123)
336
337         log = self._create_log_with_payload('{"key": "value"}')
338         self.assertEqual(log.results(), None)
339
340     def test_builder(self):
341         log = self._create_log_with_payload('{"key": "value"}')
342         self.assertEqual(log.builder(), None)
343
344         builder_name = "Chromium Mac Release (Perf)"
345         log = self._create_log_with_payload('{"builder-name": "%s"}' % builder_name)
346         self.assertEqual(log.builder(), None)
347
348         builder_key = models.Builder.create(builder_name, 'some password')
349         log = self._create_log_with_payload('{"builder-name": "%s"}' % builder_name)
350         self.assertEqual(log.builder().key(), builder_key)
351
352     def test_branch(self):
353         log = self._create_log_with_payload('{"key": "value"}')
354         self.assertEqual(log.branch(), None)
355
356         log = self._create_log_with_payload('{"branch": "some-branch"}')
357         self.assertEqual(log.branch(), None)
358
359         branch = models.Branch.create_if_possible("some-branch", "Some Branch")
360         log = self._create_log_with_payload('{"branch": "some-branch"}')
361         self.assertEqual(log.branch().key(), branch.key())
362
363     def test_platform(self):
364         log = self._create_log_with_payload('{"key": "value"}')
365         self.assertEqual(log.platform(), None)
366
367         log = self._create_log_with_payload('{"platform": "some-platform"}')
368         self.assertEqual(log.platform(), None)
369
370         platform = models.Platform.create_if_possible("some-platform", "Some Platform")
371         log = self._create_log_with_payload('{"platform": "some-platform"}')
372         self.assertEqual(log.platform().key(), platform.key())
373
374     def test_build_number(self):
375         log = self._create_log_with_payload('{"build-number": 123}')
376         self.assertEqual(log.build_number(), 123)
377
378         log = self._create_log_with_payload('{"key": "value"}')
379         self.assertEqual(log.build_number(), None)
380
381     def test_webkit_revision(self):
382         log = self._create_log_with_payload('{"key": "value"}')
383         self.assertEqual(log.webkit_revision(), None)
384
385         log = self._create_log_with_payload('{"webkit-revision": 123}')
386         self.assertEqual(log.webkit_revision(), 123)
387
388     def chromium_revision(self):
389         log = self._create_log_with_payload('{"chromium-revision": 123}')
390         self.assertEqual(log.webkit_revision(), 123)
391
392         log = self._create_log_with_payload('{"key": "value"}')
393         self.assertEqual(log.webkit_revision(), None)
394
395
396 class PersistentCacheTests(DataStoreTestsBase):
397     def setUp(self):
398         self.testbed = testbed.Testbed()
399         self.testbed.activate()
400         self.testbed.init_datastore_v3_stub()
401         self.testbed.init_memcache_stub()
402
403     def _assert_persistent_cache(self, name, value):
404         self.assertEqual(models.PersistentCache.get_by_key_name(name).value, value)
405         self.assertEqual(memcache.get(name), value)
406
407     def test_set(self):
408         self.assertThereIsNoInstanceOf(models.PersistentCache)
409
410         models.PersistentCache.set_cache('some-cache', 'some data')
411         self._assert_persistent_cache('some-cache', 'some data')
412
413         models.PersistentCache.set_cache('some-cache', 'some other data')
414
415         self._assert_persistent_cache('some-cache', 'some other data')
416
417     def test_get(self):
418         self.assertEqual(memcache.get('some-cache'), None)
419         self.assertEqual(models.PersistentCache.get_cache('some-cache'), None)
420
421         models.PersistentCache.set_cache('some-cache', 'some data')
422
423         self.assertEqual(memcache.get('some-cache'), 'some data')
424         self.assertEqual(models.PersistentCache.get_cache('some-cache'), 'some data')
425
426         memcache.delete('some-cache')
427         self.assertEqual(memcache.get('some-cache'), None)
428         self.assertEqual(models.PersistentCache.get_cache('some-cache'), 'some data')
429
430
431 if __name__ == '__main__':
432     unittest.main()