Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(777)

Unified Diff: infra/scripts/legacy/scripts/slave/gtest/json_results_generator.py

Issue 1213433006: Fork runtest.py and everything it needs src-side for easier hacking (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: runisolatedtest.py Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: infra/scripts/legacy/scripts/slave/gtest/json_results_generator.py
diff --git a/infra/scripts/legacy/scripts/slave/gtest/json_results_generator.py b/infra/scripts/legacy/scripts/slave/gtest/json_results_generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf207b0adeab0743299855491c3c4e26ca11ac49
--- /dev/null
+++ b/infra/scripts/legacy/scripts/slave/gtest/json_results_generator.py
@@ -0,0 +1,255 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A utility class to generate JSON results from given test results and upload
+them to the specified results server.
+
+"""
+
+from __future__ import with_statement
+
+import codecs
+import logging
+import os
+import time
+
+import simplejson
+from slave.gtest.test_result import TestResult
+from slave.gtest.test_results_uploader import TestResultsUploader
+
+# A JSON results generator for generic tests.
+
+JSON_PREFIX = 'ADD_RESULTS('
+JSON_SUFFIX = ');'
+
+
+def test_did_pass(test_result):
+ return not test_result.failed and test_result.modifier == TestResult.NONE
+
+
+def add_path_to_trie(path, value, trie):
+ """Inserts a single flat directory path and associated value into a directory
+ trie structure."""
+ if not '/' in path:
+ trie[path] = value
+ return
+
+ # we don't use slash
+ # pylint: disable=W0612
+ directory, slash, rest = path.partition('/')
+ if not directory in trie:
+ trie[directory] = {}
+ add_path_to_trie(rest, value, trie[directory])
+
+
+def generate_test_timings_trie(individual_test_timings):
+ """Breaks a test name into chunks by directory and puts the test time as a
+ value in the lowest part, e.g.
+ foo/bar/baz.html: 1ms
+ foo/bar/baz1.html: 3ms
+
+ becomes
+ foo: {
+ bar: {
+ baz.html: 1,
+ baz1.html: 3
+ }
+ }
+ """
+ trie = {}
+ # Only use the timing of the first try of each test.
+ for test_results in individual_test_timings:
+ test = test_results[0].test_name
+
+ add_path_to_trie(test, int(1000 * test_results[-1].test_run_time), trie)
+
+ return trie
+
+
+class JSONResultsGenerator(object):
+ """A JSON results generator for generic tests."""
+
+ FAIL_LABEL = 'FAIL'
+ PASS_LABEL = 'PASS'
+ FLAKY_LABEL = ' '.join([FAIL_LABEL, PASS_LABEL])
+ SKIP_LABEL = 'SKIP'
+
+ ACTUAL = 'actual'
+ BLINK_REVISION = 'blink_revision'
+ BUILD_NUMBER = 'build_number'
+ BUILDER_NAME = 'builder_name'
+ CHROMIUM_REVISION = 'chromium_revision'
+ EXPECTED = 'expected'
+ FAILURE_SUMMARY = 'num_failures_by_type'
+ SECONDS_SINCE_EPOCH = 'seconds_since_epoch'
+ TEST_TIME = 'time'
+ TESTS = 'tests'
+ VERSION = 'version'
+ VERSION_NUMBER = 3
+
+ RESULTS_FILENAME = 'results.json'
+ TIMES_MS_FILENAME = 'times_ms.json'
+ FULL_RESULTS_FILENAME = 'full_results.json'
+
+ def __init__(self, builder_name, build_name, build_number,
+ results_file_base_path, builder_base_url,
+ test_results_map, svn_revisions=None,
+ test_results_server=None,
+ test_type='',
+ master_name='',
+ file_writer=None):
+ """Modifies the results.json file. Grabs it off the archive directory
+ if it is not found locally.
+
+ Args
+ builder_name: the builder name (e.g. Webkit).
+ build_name: the build name (e.g. webkit-rel).
+ build_number: the build number.
+ results_file_base_path: Absolute path to the directory containing the
+ results json file.
+ builder_base_url: the URL where we have the archived test results.
+ If this is None no archived results will be retrieved.
+ test_results_map: A dictionary that maps test_name to a list of
+ TestResult, one for each time the test was retried.
+ svn_revisions: A (json_field_name, revision) pair for SVN
+ repositories that tests rely on. The SVN revision will be
+ included in the JSON with the given json_field_name.
+ test_results_server: server that hosts test results json.
+ test_type: test type string (e.g. 'layout-tests').
+ master_name: the name of the buildbot master.
+ file_writer: if given the parameter is used to write JSON data to a file.
+ The parameter must be the function that takes two arguments, 'file_path'
+ and 'data' to be written into the file_path.
+ """
+ self._builder_name = builder_name
+ self._build_name = build_name
+ self._build_number = build_number
+ self._builder_base_url = builder_base_url
+ self._results_directory = results_file_base_path
+
+ self._test_results_map = test_results_map
+
+ self._svn_revisions = svn_revisions
+ if not self._svn_revisions:
+ self._svn_revisions = {}
+
+ self._test_results_server = test_results_server
+ self._test_type = test_type
+ self._master_name = master_name
+ self._file_writer = file_writer
+
+ def generate_json_output(self):
+ json = self.get_full_results_json()
+ if json:
+ file_path = os.path.join(self._results_directory,
+ self.FULL_RESULTS_FILENAME)
+ self._write_json(json, file_path)
+
+ def generate_times_ms_file(self):
+ times = generate_test_timings_trie(self._test_results_map.values())
+ file_path = os.path.join(self._results_directory, self.TIMES_MS_FILENAME)
+ self._write_json(times, file_path)
+
+ def get_full_results_json(self):
+ results = {self.VERSION: self.VERSION_NUMBER}
+
+ # Metadata generic to all results.
+ results[self.BUILDER_NAME] = self._builder_name
+ results[self.BUILD_NUMBER] = self._build_number
+ results[self.SECONDS_SINCE_EPOCH] = int(time.time())
+ for name, revision in self._svn_revisions:
+ results[name + '_revision'] = revision
+
+ tests = results.setdefault(self.TESTS, {})
+ for test_name in self._test_results_map.iterkeys():
+ tests[test_name] = self._make_test_data(test_name)
+
+ self._insert_failure_map(results)
+
+ return results
+
+ def _insert_failure_map(self, results):
+ # FAIL, PASS, NOTRUN
+ summary = {self.PASS_LABEL: 0, self.FAIL_LABEL: 0, self.SKIP_LABEL: 0}
+ for test_results in self._test_results_map.itervalues():
+ # Use the result of the first test for aggregate statistics. This may
+ # count as failing a test that passed on retry, but it's a more useful
+ # statistic and it's consistent with our other test harnesses.
+ test_result = test_results[0]
+ if test_did_pass(test_result):
+ summary[self.PASS_LABEL] += 1
+ elif test_result.modifier == TestResult.DISABLED:
+ summary[self.SKIP_LABEL] += 1
+ elif test_result.failed:
+ summary[self.FAIL_LABEL] += 1
+
+ results[self.FAILURE_SUMMARY] = summary
+
+ def _make_test_data(self, test_name):
+ test_data = {}
+ expected, actual = self._get_expected_and_actual_results(test_name)
+ test_data[self.EXPECTED] = expected
+ test_data[self.ACTUAL] = actual
+ # Use the timing of the first try, it's a better representative since it
+ # runs under more load than retries.
+ run_time = int(self._test_results_map[test_name][0].test_run_time)
+ test_data[self.TEST_TIME] = run_time
+
+ return test_data
+
+ def _get_expected_and_actual_results(self, test_name):
+ test_results = self._test_results_map[test_name]
+ # Use the modifier of the first try, they should all be the same.
+ modifier = test_results[0].modifier
+
+ if modifier == TestResult.DISABLED:
+ return (self.SKIP_LABEL, self.SKIP_LABEL)
+
+ actual_list = []
+ for test_result in test_results:
+ label = self.FAIL_LABEL if test_result.failed else self.PASS_LABEL
+ actual_list.append(label)
+ actual = " ".join(actual_list)
+
+ if modifier == TestResult.NONE:
+ return (self.PASS_LABEL, actual)
+ if modifier == TestResult.FLAKY:
+ return (self.FLAKY_LABEL, actual)
+ if modifier == TestResult.FAILS:
+ return (self.FAIL_LABEL, actual)
+
+ def upload_json_files(self, json_files):
+ """Uploads the given json_files to the test_results_server (if the
+ test_results_server is given)."""
+ if not self._test_results_server:
+ return
+
+ if not self._master_name:
+ logging.error('--test-results-server was set, but --master-name was not. '
+ 'Not uploading JSON files.')
+ return
+
+ print 'Uploading JSON files for builder: %s' % self._builder_name
+ attrs = [('builder', self._builder_name),
+ ('testtype', self._test_type),
+ ('master', self._master_name)]
+
+ files = [(f, os.path.join(self._results_directory, f)) for f in json_files]
+
+ uploader = TestResultsUploader(self._test_results_server)
+ # Set uploading timeout in case appengine server is having problem.
+ # 120 seconds are more than enough to upload test results.
+ uploader.upload(attrs, files, 120)
+
+ print 'JSON files uploaded.'
+
+ def _write_json(self, json_object, file_path):
+ # Specify separators in order to get compact encoding.
+ json_data = simplejson.dumps(json_object, separators=(',', ':'))
+ json_string = json_data
+ if self._file_writer:
+ self._file_writer(file_path, json_string)
+ else:
+ with codecs.open(file_path, 'w', 'utf8') as f:
+ f.write(json_string)
« no previous file with comments | « infra/scripts/legacy/scripts/slave/gtest/__init__.py ('k') | infra/scripts/legacy/scripts/slave/gtest/networktransaction.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698