Diff
Modified: trunk/ChangeLog (109056 => 109057)
--- trunk/ChangeLog 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/ChangeLog 2012-02-28 02:14:37 UTC (rev 109057)
@@ -1,3 +1,46 @@
+2012-02-27 Ryosuke Niwa <rn...@webkit.org>
+
+ Extract the logic to merge tests from MergeTestsHandler and add unit tests
+ https://bugs.webkit.org/show_bug.cgi?id=79602
+
+ Reviewed by Hajime Morita.
+
+ Extracted Test.merge and TestResult.replace_to_change_test_name out of MergeTestsHandler,
+ and moved MergeTestsHandler into admin_handlers.py where it belongs.
+
+ Added new backend "model-manipulator" to execute tasks to merge tests.
+
+ Also revive the inadvertently removed manual submission form on the admin page.
+
+ * Websites/webkit-perf.appspot.com/admin_handlers.py:
+ (AdminDashboardHandler.get_tests):
+ (MergeTestsHandler):
+ (MergeTestsHandler.post):
+ * Websites/webkit-perf.appspot.com/app.yaml:
+ * Websites/webkit-perf.appspot.com/backends.yaml: Added.
+ * Websites/webkit-perf.appspot.com/css/admin.css:
+ * Websites/webkit-perf.appspot.com/js/admin.js:
+ * Websites/webkit-perf.appspot.com/main.py:
+ * Websites/webkit-perf.appspot.com/merge_tests_handler.py: Removed.
+ * Websites/webkit-perf.appspot.com/models.py:
+ (Test):
+ (Test.merge):
+ (TestResult.replace_to_change_test_name):
+ * Websites/webkit-perf.appspot.com/models_unittest.py:
+ (DataStoreTestsBase.assertOnlyInstance):
+ (DataStoreTestsBase):
+ (DataStoreTestsBase.assertOnlyInstances):
+ (DataStoreTestsBase.assertEqualUnorderedModelList):
+ (DataStoreTestsBase.assertEqualUnorderedList):
+ (_create_build):
+ (TestModelTests.test_merge):
+ (TestResultTests):
+ (TestResultTests.test_get_or_insert_value):
+ (TestResultTests.test_get_or_insert_stat_value):
+ (TestResultTests.test_replace_to_change_test_name):
+ (TestResultTests.test_replace_to_change_test_name_with_stat_value):
+ (TestResultTests.test_replace_to_change_test_name_overrides_conflicting_result):
+
2012-02-27 ChangSeok Oh <shivami...@gmail.com>
[EFL] Support mutation observers
Modified: trunk/Websites/webkit-perf.appspot.com/admin_handlers.py (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/admin_handlers.py 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/admin_handlers.py 2012-02-28 02:14:37 UTC (rev 109057)
@@ -30,10 +30,14 @@
import webapp2
import json
+from google.appengine.api import taskqueue
from google.appengine.api import users
from google.appengine.ext.db import GqlQuery
from google.appengine.ext.webapp import template
+from controller import schedule_runs_update
+from controller import schedule_dashboard_update
+from controller import schedule_manifest_update
from models import Branch
from models import Builder
from models import Platform
@@ -81,3 +85,42 @@
def get_tests(self):
self.response.headers['Content-Type'] = 'application/json'
self.response.out.write(json.dumps([test.name for test in Test.all().fetch(limit=200)]))
+
+
+class MergeTestsHandler(webapp2.RequestHandler):
+ def post(self, task):
+ self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
+
+ if task != 'run':
+ try:
+ payload = json.loads(self.request.body)
+ merge = payload.get('merge', '')
+ into = payload.get('into', '')
+ except:
+ self.response.out.write("Failed to parse the payload: %s" % self.request.body)
+ return
+
+ if merge == into or not Test.get_by_key_name(merge) or not Test.get_by_key_name(into):
+ self.response.out.write('Invalid test names')
+ return
+
+ taskqueue.add(url='', params={'merge': merge, 'into': into}, target='model-manipulator')
+ self.response.out.write('OK')
+ return
+
+ merge = Test.get_by_key_name(self.request.get('merge'))
+ into = Test.get_by_key_name(self.request.get('into'))
+
+ branches_and_platforms_to_update = into.merge(merge)
+ if branches_and_platforms_to_update == None:
+ # FIXME: This message is invisible. Need to store this somewhere and let the admin page pull it.
+ self.response.out.write('Cannot merge %s into %s. There are conflicting results.' % (merge.name, into.name))
+ return
+
+ for branch_id, platform_id in branches_and_platforms_to_update:
+ schedule_runs_update(into.id, branch_id, platform_id)
+
+ schedule_dashboard_update()
+ schedule_manifest_update()
+
+ self.response.out.write('OK')
Modified: trunk/Websites/webkit-perf.appspot.com/app.yaml (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/app.yaml 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/app.yaml 2012-02-28 02:14:37 UTC (rev 109057)
@@ -1,5 +1,5 @@
application: webkit-perf
-version: 14
+version: 15
runtime: python27
api_version: 1
threadsafe: false
@@ -9,12 +9,6 @@
static_files: favicon.ico
upload: favicon\.ico
-- url: /admin/(.+\.html)
- static_files: static/\1
- upload: static
- secure: always
- login: admin
-
- url: /
static_files: index.html
upload: index.html
Added: trunk/Websites/webkit-perf.appspot.com/backends.yaml (0 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/backends.yaml (rev 0)
+++ trunk/Websites/webkit-perf.appspot.com/backends.yaml 2012-02-28 02:14:37 UTC (rev 109057)
@@ -0,0 +1,3 @@
+backends:
+- model-manipulator
+ options: dynamic
Modified: trunk/Websites/webkit-perf.appspot.com/css/admin.css (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/css/admin.css 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/css/admin.css 2012-02-28 02:14:37 UTC (rev 109057)
@@ -4,10 +4,13 @@
overflow: auto;
}
+#summary, #manual-submission {
+ font-size: 14px;
+ line-height: 36px;
+}
+
#summary {
margin-top: 49px;
- font-size: 14px;
- line-height: 36px;
display: table;
border-collapse: collapse;
}
@@ -18,7 +21,6 @@
border: 1px solid lightgrey;
border-collapse: collapse;
min-height: 300px;
- max-width: 400px;
}
h2 {
@@ -82,6 +84,15 @@
width: 150px;
}
+#manual-submission form {
+ padding: 0;
+}
+#manual-submission textarea {
+ font-family: monospace;
+ font-size: 14px;
+ border: none;
+}
+
#footer {
margin: 10px;
}
Modified: trunk/Websites/webkit-perf.appspot.com/js/admin.js (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/js/admin.js 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/js/admin.js 2012-02-28 02:14:37 UTC (rev 109057)
@@ -59,9 +59,15 @@
$('form').bind('submit', function (event) {
event.preventDefault();
- var contents = {}
- for (var i = 0; i < this.elements.length; i++)
- contents[this.elements[i].name] = this.elements[i].value;
+ var payload;
+ if (this.payload)
+ payload = this.payload.value;
+ else {
+ var contents = {};
+ for (var i = 0; i < this.elements.length; i++)
+ contents[this.elements[i].name] = this.elements[i].value;
+ payload = JSON.stringify(contents);
+ }
var xhr = new XMLHttpRequest;
xhr._onreadystatechange_ = function () {
@@ -71,10 +77,24 @@
error('HTTP status: ' + xhr.status);
else if (xhr.responseText != 'OK')
error(xhr.responseText);
-
}
xhr.open(this.method, this.action, true);
- xhr.send(JSON.stringify(contents));
+ xhr.send(payload);
$(this).trigger('reload');
});
+
+$('#manual-submission textarea').val(JSON.stringify({
+ 'branch': 'webkit-trunk',
+ 'platform': 'chromium-mac',
+ 'builder-name': 'Chromium Mac Release (Perf)',
+ 'build-number': '123',
+ 'timestamp': parseInt(Date.now() / 1000),
+ 'webkit-revision': 104856,
+ 'chromium-revision': 123059,
+ 'results':
+ {
+ 'webkit_style_test': {'avg': 100, 'median': 102, 'stdev': 5, 'min': 90, 'max': 110},
+ 'some_test': 54,
+ },
+}, null, ' '));
Modified: trunk/Websites/webkit-perf.appspot.com/main.py (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/main.py 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/main.py 2012-02-28 02:14:37 UTC (rev 109057)
@@ -20,8 +20,9 @@
import json
+from admin_handlers import AdminDashboardHandler
from admin_handlers import IsAdminHandler
-from admin_handlers import AdminDashboardHandler
+from admin_handlers import MergeTestsHandler
from controller import CachedDashboardHandler
from controller import CachedManifestHandler
from controller import CachedRunsHandler
@@ -33,11 +34,10 @@
from report_handler import AdminReportHandler
from report_process_handler import ReportProcessHandler
from report_logs_handler import ReportLogsHandler
-from merge_tests_handler import MergeTestsHandler
routes = [
('/admin/report/?', AdminReportHandler),
- ('/admin/merge-tests/?', MergeTestsHandler),
+ (r'/admin/merge-tests(?:/(.*))?', MergeTestsHandler),
('/admin/report-logs/?', ReportLogsHandler),
('/admin/create/(.*)', CreateHandler),
(r'/admin/([A-Za-z\-]*)', AdminDashboardHandler),
Deleted: trunk/Websites/webkit-perf.appspot.com/merge_tests_handler.py (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/merge_tests_handler.py 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/merge_tests_handler.py 2012-02-28 02:14:37 UTC (rev 109057)
@@ -1,78 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) 2012 Google Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import webapp2
-from google.appengine.ext.webapp import template
-
-import json
-import os
-
-from controller import schedule_runs_update
-from controller import schedule_dashboard_update
-from controller import schedule_manifest_update
-from models import Test
-from models import TestResult
-from models import delete_model_with_numeric_id_holder
-
-
-class MergeTestsHandler(webapp2.RequestHandler):
- def post(self):
- self.response.headers['Content-Type'] = 'text/plain; charset=utf-8';
-
- try:
- payload = json.loads(self.request.body)
- merge = payload.get('merge', '')
- into = payload.get('into', '')
- except:
- self.response.out.write("Failed to parse the payload: %s" % self.request.body)
- return
-
- merge = Test.get_by_key_name(merge)
- into = Test.get_by_key_name(into)
- if not merge or not into:
- self.response.out.write('Invalid test names')
- return
-
- merged_results = TestResult.all()
- merged_results.filter('name =', merge.name)
- branches_and_platforms_to_update = set()
- for result in merged_results:
- branches_and_platforms_to_update.add((result.build.branch.id, result.build.platform.id))
- result.name = into.name
- result.put()
-
- for branch_id, platform_id in branches_and_platforms_to_update:
- schedule_runs_update(into.id, branch_id, platform_id)
-
- schedule_dashboard_update()
- schedule_manifest_update()
-
- delete_model_with_numeric_id_holder(merge)
-
- self.response.out.write('OK')
Modified: trunk/Websites/webkit-perf.appspot.com/models.py (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/models.py 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/models.py 2012-02-28 02:14:37 UTC (rev 109057)
@@ -142,6 +142,8 @@
class Test(db.Model):
id = db.IntegerProperty(required=True)
name = db.StringProperty(required=True)
+ # FIXME: Storing branches and platforms separately is flawed since a test maybe available on
+ # one platform but only on some branch and vice versa.
branches = db.ListProperty(db.Key)
platforms = db.ListProperty(db.Key)
@@ -170,7 +172,27 @@
return create_in_transaction_with_numeric_id_holder(execute) or existing_test[0]
+ def merge(self, other):
+ assert self.key() != other.key()
+ merged_results = TestResult.all()
+ merged_results.filter('name =', other.name)
+
+ # FIXME: We should be doing this check in a transaction but only ancestor queries are allowed
+ for result in merged_results:
+ if TestResult.get_by_key_name(TestResult.key_name(result.build, self.name)):
+ return None
+
+ branches_and_platforms_to_update = set()
+ for result in merged_results:
+ branches_and_platforms_to_update.add((result.build.branch.id, result.build.platform.id))
+ result.replace_to_change_test_name(self.name)
+
+ delete_model_with_numeric_id_holder(other)
+
+ return branches_and_platforms_to_update
+
+
class TestResult(db.Model):
name = db.StringProperty(required=True)
build = db.ReferenceProperty(Build, required=True)
@@ -201,7 +223,14 @@
valueMedian=_float_or_none(result, 'median'), valueStdev=_float_or_none(result, 'stdev'),
valueMin=_float_or_none(result, 'min'), valueMax=_float_or_none(result, 'max'))
+ def replace_to_change_test_name(self, new_name):
+ clone = TestResult(key_name=TestResult.key_name(self.build, new_name), name=new_name, build=self.build,
+ value=self.value, valueMedian=self.valueMedian, valueStdev=self.valueMin, valueMin=self.valueMin, valueMax=self.valueMax)
+ clone.put()
+ self.delete()
+ return clone
+
class ReportLog(db.Model):
timestamp = db.DateTimeProperty(required=True)
headers = db.TextProperty()
Modified: trunk/Websites/webkit-perf.appspot.com/models_unittest.py (109056 => 109057)
--- trunk/Websites/webkit-perf.appspot.com/models_unittest.py 2012-02-28 02:05:13 UTC (rev 109056)
+++ trunk/Websites/webkit-perf.appspot.com/models_unittest.py 2012-02-28 02:14:37 UTC (rev 109057)
@@ -49,11 +49,18 @@
self.assertEqual(len(model.all().fetch(5)), 0)
def assertOnlyInstance(self, only_instasnce):
- self.assertEqual(len(only_instasnce.__class__.all().fetch(5)), 1)
- self.assertTrue(only_instasnce.__class__.get(only_instasnce.key()))
+ self.assertOnlyInstances([only_instasnce])
+ def assertOnlyInstances(self, expected_instances):
+ actual_instances = expected_instances[0].__class__.all().fetch(len(expected_instances) + 1)
+ self.assertEqualUnorderedModelList(actual_instances, expected_instances)
+
+ def assertEqualUnorderedModelList(self, list1, list2):
+ self.assertEqualUnorderedList([item.key() for item in list1], [item.key() for item in list1])
+
def assertEqualUnorderedList(self, list1, list2):
self.assertEqual(set(list1), set(list2))
+ self.assertEqual(len(list1), len(list2))
class HelperTests(DataStoreTestsBase):
@@ -200,6 +207,12 @@
return branch, platform, models.Builder.get(builder_key)
+def _create_build(branch, platform, builder, key_name='some-build'):
+ build_key = models.Build(key_name=key_name, branch=branch, platform=platform, builder=builder,
+ buildNumber=1, revision=100, timestamp=datetime.now()).put()
+ return models.Build.get(build_key)
+
+
class BuildTests(DataStoreTestsBase):
def test_get_or_insert_from_log(self):
branch, platform, builder = _create_some_builder()
@@ -254,16 +267,42 @@
self.assertEqualUnorderedList(test.branches, [branch.key(), other_branch.key()])
self.assertEqualUnorderedList(test.platforms, [platform.key(), other_platform.key()])
+ def test_merge(self):
+ branch, platform, builder = _create_some_builder()
+ some_build = _create_build(branch, platform, builder)
+ some_result = models.TestResult.get_or_insert_from_parsed_json('some-test', some_build, 50)
+ some_test = models.Test.update_or_insert('some-test', branch, platform)
+ other_build = _create_build(branch, platform, builder, 'other-build')
+ other_result = models.TestResult.get_or_insert_from_parsed_json('other-test', other_build, 30)
+ other_test = models.Test.update_or_insert('other-test', branch, platform)
+
+ self.assertOnlyInstances([some_result, other_result])
+ self.assertNotEqual(some_result.key(), other_result.key())
+ self.assertOnlyInstances([some_test, other_test])
+
+ self.assertRaises(AssertionError, some_test.merge, (some_test))
+ self.assertOnlyInstances([some_test, other_test])
+
+ some_test.merge(other_test)
+ results_for_some_test = models.TestResult.all()
+ results_for_some_test.filter('name =', 'some-test')
+ results_for_some_test = results_for_some_test.fetch(5)
+ self.assertEqual(len(results_for_some_test), 2)
+
+ self.assertEqual(results_for_some_test[0].name, 'some-test')
+ self.assertEqual(results_for_some_test[1].name, 'some-test')
+
+ if results_for_some_test[0].value == 50:
+ self.assertEqual(results_for_some_test[1].value, 30)
+ else:
+ self.assertEqual(results_for_some_test[1].value, 50)
+
+
class TestResultTests(DataStoreTestsBase):
- def _create_build(self):
+ def test_get_or_insert_value(self):
branch, platform, builder = _create_some_builder()
- build_key = models.Build(key_name='some-build', branch=branch, platform=platform, builder=builder,
- buildNumber=1, revision=100, timestamp=datetime.now()).put()
- return models.Build.get(build_key)
-
- def test_get_or_insert_value(self):
- build = self._create_build()
+ build = _create_build(branch, platform, builder)
self.assertThereIsNoInstanceOf(models.TestResult)
result = models.TestResult.get_or_insert_from_parsed_json('some-test', build, 50)
self.assertOnlyInstance(result)
@@ -276,7 +315,8 @@
self.assertEqual(result.valueMax, None)
def test_get_or_insert_stat_value(self):
- build = self._create_build()
+ branch, platform, builder = _create_some_builder()
+ build = _create_build(branch, platform, builder)
self.assertThereIsNoInstanceOf(models.TestResult)
result = models.TestResult.get_or_insert_from_parsed_json('some-test', build,
{"avg": 40, "median": "40.1", "stdev": 3.25, "min": 30.5, "max": 45})
@@ -289,7 +329,65 @@
self.assertEqual(result.valueMin, 30.5)
self.assertEqual(result.valueMax, 45)
+ def test_replace_to_change_test_name(self):
+ branch, platform, builder = _create_some_builder()
+ build = _create_build(branch, platform, builder)
+ self.assertThereIsNoInstanceOf(models.TestResult)
+ result = models.TestResult.get_or_insert_from_parsed_json('some-test', build, 50)
+ self.assertOnlyInstance(result)
+ self.assertEqual(result.name, 'some-test')
+ new_result = result.replace_to_change_test_name('other-test')
+ self.assertNotEqual(result, new_result)
+ self.assertOnlyInstance(new_result)
+
+ self.assertEqual(new_result.name, 'other-test')
+ self.assertEqual(new_result.build.key(), result.build.key())
+ self.assertEqual(new_result.value, result.value)
+ self.assertEqual(new_result.valueMedian, None)
+ self.assertEqual(new_result.valueStdev, None)
+ self.assertEqual(new_result.valueMin, None)
+ self.assertEqual(new_result.valueMax, None)
+
+ def test_replace_to_change_test_name_with_stat_value(self):
+ branch, platform, builder = _create_some_builder()
+ build = _create_build(branch, platform, builder)
+ self.assertThereIsNoInstanceOf(models.TestResult)
+ result = models.TestResult.get_or_insert_from_parsed_json('some-test', build,
+ {"avg": 40, "median": "40.1", "stdev": 3.25, "min": 30.5, "max": 45})
+ self.assertOnlyInstance(result)
+ self.assertEqual(result.name, 'some-test')
+
+ new_result = result.replace_to_change_test_name('other-test')
+ self.assertNotEqual(result, new_result)
+ self.assertOnlyInstance(new_result)
+
+ self.assertEqual(new_result.name, 'other-test')
+ self.assertEqual(new_result.build.key(), result.build.key())
+ self.assertEqual(new_result.value, result.value)
+ self.assertEqual(result.value, 40.0)
+ self.assertEqual(result.valueMedian, 40.1)
+ self.assertEqual(result.valueStdev, 3.25)
+ self.assertEqual(result.valueMin, 30.5)
+ self.assertEqual(result.valueMax, 45)
+
+ def test_replace_to_change_test_name_overrides_conflicting_result(self):
+ branch, platform, builder = _create_some_builder()
+ build = _create_build(branch, platform, builder)
+ self.assertThereIsNoInstanceOf(models.TestResult)
+ result = models.TestResult.get_or_insert_from_parsed_json('some-test', build, 20)
+ self.assertOnlyInstance(result)
+
+ conflicting_result = models.TestResult.get_or_insert_from_parsed_json('other-test', build, 10)
+
+ new_result = result.replace_to_change_test_name('other-test')
+ self.assertNotEqual(result, new_result)
+ self.assertOnlyInstance(new_result)
+
+ self.assertEqual(new_result.name, 'other-test')
+ self.assertEqual(models.TestResult.get(conflicting_result.key()).value, 20)
+
+
class ReportLogTests(DataStoreTestsBase):
def _create_log_with_payload(self, payload):
return models.ReportLog(timestamp=datetime.now(), headers='some headers', payload=payload)