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

Unified Diff: build/android/pylib/results/flakiness_dashboard/json_results_generator.py

Issue 2392643003: Removes files from //build that we don't need (Closed)
Patch Set: Created 4 years, 2 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: build/android/pylib/results/flakiness_dashboard/json_results_generator.py
diff --git a/build/android/pylib/results/flakiness_dashboard/json_results_generator.py b/build/android/pylib/results/flakiness_dashboard/json_results_generator.py
deleted file mode 100644
index e5c433dac5e348bc523722b07890208d6c81fa6e..0000000000000000000000000000000000000000
--- a/build/android/pylib/results/flakiness_dashboard/json_results_generator.py
+++ /dev/null
@@ -1,697 +0,0 @@
-# Copyright 2014 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.
-
-#
-# Most of this file was ported over from Blink's
-# Tools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py
-# Tools/Scripts/webkitpy/common/net/file_uploader.py
-#
-
-import json
-import logging
-import mimetypes
-import os
-import time
-import urllib2
-
-_log = logging.getLogger(__name__)
-
-_JSON_PREFIX = 'ADD_RESULTS('
-_JSON_SUFFIX = ');'
-
-
-def HasJSONWrapper(string):
- return string.startswith(_JSON_PREFIX) and string.endswith(_JSON_SUFFIX)
-
-
-def StripJSONWrapper(json_content):
- # FIXME: Kill this code once the server returns json instead of jsonp.
- if HasJSONWrapper(json_content):
- return json_content[len(_JSON_PREFIX):len(json_content) - len(_JSON_SUFFIX)]
- return json_content
-
-
-def WriteJSON(json_object, file_path, callback=None):
- # Specify separators in order to get compact encoding.
- json_string = json.dumps(json_object, separators=(',', ':'))
- if callback:
- json_string = callback + '(' + json_string + ');'
- with open(file_path, 'w') as fp:
- fp.write(json_string)
-
-
-def ConvertTrieToFlatPaths(trie, prefix=None):
- """Flattens the trie of paths, prepending a prefix to each."""
- result = {}
- for name, data in trie.iteritems():
- if prefix:
- name = prefix + '/' + name
-
- if len(data) and not 'results' in data:
- result.update(ConvertTrieToFlatPaths(data, name))
- else:
- result[name] = data
-
- return result
-
-
-def AddPathToTrie(path, value, trie):
- """Inserts a single path and value into a directory trie structure."""
- if not '/' in path:
- trie[path] = value
- return
-
- directory, _slash, rest = path.partition('/')
- if not directory in trie:
- trie[directory] = {}
- AddPathToTrie(rest, value, trie[directory])
-
-
-def TestTimingsTrie(individual_test_timings):
- """Breaks a test name into dicts by directory
-
- foo/bar/baz.html: 1ms
- foo/bar/baz1.html: 3ms
-
- becomes
- foo: {
- bar: {
- baz.html: 1,
- baz1.html: 3
- }
- }
- """
- trie = {}
- for test_result in individual_test_timings:
- test = test_result.test_name
-
- AddPathToTrie(test, int(1000 * test_result.test_run_time), trie)
-
- return trie
-
-
-class TestResult(object):
- """A simple class that represents a single test result."""
-
- # Test modifier constants.
- (NONE, FAILS, FLAKY, DISABLED) = range(4)
-
- def __init__(self, test, failed=False, elapsed_time=0):
- self.test_name = test
- self.failed = failed
- self.test_run_time = elapsed_time
-
- test_name = test
- try:
- test_name = test.split('.')[1]
- except IndexError:
- _log.warn('Invalid test name: %s.', test)
-
- if test_name.startswith('FAILS_'):
- self.modifier = self.FAILS
- elif test_name.startswith('FLAKY_'):
- self.modifier = self.FLAKY
- elif test_name.startswith('DISABLED_'):
- self.modifier = self.DISABLED
- else:
- self.modifier = self.NONE
-
- def Fixable(self):
- return self.failed or self.modifier == self.DISABLED
-
-
-class JSONResultsGeneratorBase(object):
- """A JSON results generator for generic tests."""
-
- MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 750
- # Min time (seconds) that will be added to the JSON.
- MIN_TIME = 1
-
- # Note that in non-chromium tests those chars are used to indicate
- # test modifiers (FAILS, FLAKY, etc) but not actual test results.
- PASS_RESULT = 'P'
- SKIP_RESULT = 'X'
- FAIL_RESULT = 'F'
- FLAKY_RESULT = 'L'
- NO_DATA_RESULT = 'N'
-
- MODIFIER_TO_CHAR = {TestResult.NONE: PASS_RESULT,
- TestResult.DISABLED: SKIP_RESULT,
- TestResult.FAILS: FAIL_RESULT,
- TestResult.FLAKY: FLAKY_RESULT}
-
- VERSION = 4
- VERSION_KEY = 'version'
- RESULTS = 'results'
- TIMES = 'times'
- BUILD_NUMBERS = 'buildNumbers'
- TIME = 'secondsSinceEpoch'
- TESTS = 'tests'
-
- FIXABLE_COUNT = 'fixableCount'
- FIXABLE = 'fixableCounts'
- ALL_FIXABLE_COUNT = 'allFixableCount'
-
- RESULTS_FILENAME = 'results.json'
- TIMES_MS_FILENAME = 'times_ms.json'
- INCREMENTAL_RESULTS_FILENAME = 'incremental_results.json'
-
- # line too long pylint: disable=line-too-long
- URL_FOR_TEST_LIST_JSON = (
- 'http://%s/testfile?builder=%s&name=%s&testlistjson=1&testtype=%s&master=%s')
- # pylint: enable=line-too-long
-
- def __init__(self, builder_name, build_name, build_number,
- results_file_base_path, builder_base_url,
- test_results_map, svn_repositories=None,
- test_results_server=None,
- test_type='',
- master_name=''):
- """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 TestResult.
- svn_repositories: A (json_field_name, svn_path) 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.
- """
- 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._test_results = test_results_map.values()
-
- self._svn_repositories = svn_repositories
- if not self._svn_repositories:
- self._svn_repositories = {}
-
- self._test_results_server = test_results_server
- self._test_type = test_type
- self._master_name = master_name
-
- self._archived_results = None
-
- def GenerateJSONOutput(self):
- json_object = self.GetJSON()
- if json_object:
- file_path = (
- os.path.join(
- self._results_directory,
- self.INCREMENTAL_RESULTS_FILENAME))
- WriteJSON(json_object, file_path)
-
- def GenerateTimesMSFile(self):
- times = TestTimingsTrie(self._test_results_map.values())
- file_path = os.path.join(self._results_directory, self.TIMES_MS_FILENAME)
- WriteJSON(times, file_path)
-
- def GetJSON(self):
- """Gets the results for the results.json file."""
- results_json = {}
-
- if not results_json:
- results_json, error = self._GetArchivedJSONResults()
- if error:
- # If there was an error don't write a results.json
- # file at all as it would lose all the information on the
- # bot.
- _log.error('Archive directory is inaccessible. Not '
- 'modifying or clobbering the results.json '
- 'file: ' + str(error))
- return None
-
- builder_name = self._builder_name
- if results_json and builder_name not in results_json:
- _log.debug('Builder name (%s) is not in the results.json file.'
- % builder_name)
-
- self._ConvertJSONToCurrentVersion(results_json)
-
- if builder_name not in results_json:
- results_json[builder_name] = (
- self._CreateResultsForBuilderJSON())
-
- results_for_builder = results_json[builder_name]
-
- if builder_name:
- self._InsertGenericMetaData(results_for_builder)
-
- self._InsertFailureSummaries(results_for_builder)
-
- # Update the all failing tests with result type and time.
- tests = results_for_builder[self.TESTS]
- all_failing_tests = self._GetFailedTestNames()
- all_failing_tests.update(ConvertTrieToFlatPaths(tests))
-
- for test in all_failing_tests:
- self._InsertTestTimeAndResult(test, tests)
-
- return results_json
-
- def SetArchivedResults(self, archived_results):
- self._archived_results = archived_results
-
- def UploadJSONFiles(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:
- _log.error(
- '--test-results-server was set, but --master-name was not. Not '
- 'uploading JSON files.')
- return
-
- _log.info('Uploading JSON files for builder: %s', self._builder_name)
- attrs = [('builder', self._builder_name),
- ('testtype', self._test_type),
- ('master', self._master_name)]
-
- files = [(json_file, os.path.join(self._results_directory, json_file))
- for json_file in json_files]
-
- url = 'http://%s/testfile/upload' % self._test_results_server
- # Set uploading timeout in case appengine server is having problems.
- # 120 seconds are more than enough to upload test results.
- uploader = _FileUploader(url, 120)
- try:
- response = uploader.UploadAsMultipartFormData(files, attrs)
- if response:
- if response.code == 200:
- _log.info('JSON uploaded.')
- else:
- _log.debug(
- "JSON upload failed, %d: '%s'" %
- (response.code, response.read()))
- else:
- _log.error('JSON upload failed; no response returned')
- except Exception, err:
- _log.error('Upload failed: %s' % err)
- return
-
- def _GetTestTiming(self, test_name):
- """Returns test timing data (elapsed time) in second
- for the given test_name."""
- if test_name in self._test_results_map:
- # Floor for now to get time in seconds.
- return int(self._test_results_map[test_name].test_run_time)
- return 0
-
- def _GetFailedTestNames(self):
- """Returns a set of failed test names."""
- return set([r.test_name for r in self._test_results if r.failed])
-
- def _GetModifierChar(self, test_name):
- """Returns a single char (e.g. SKIP_RESULT, FAIL_RESULT,
- PASS_RESULT, NO_DATA_RESULT, etc) that indicates the test modifier
- for the given test_name.
- """
- if test_name not in self._test_results_map:
- return self.__class__.NO_DATA_RESULT
-
- test_result = self._test_results_map[test_name]
- if test_result.modifier in self.MODIFIER_TO_CHAR.keys():
- return self.MODIFIER_TO_CHAR[test_result.modifier]
-
- return self.__class__.PASS_RESULT
-
- def _get_result_char(self, test_name):
- """Returns a single char (e.g. SKIP_RESULT, FAIL_RESULT,
- PASS_RESULT, NO_DATA_RESULT, etc) that indicates the test result
- for the given test_name.
- """
- if test_name not in self._test_results_map:
- return self.__class__.NO_DATA_RESULT
-
- test_result = self._test_results_map[test_name]
- if test_result.modifier == TestResult.DISABLED:
- return self.__class__.SKIP_RESULT
-
- if test_result.failed:
- return self.__class__.FAIL_RESULT
-
- return self.__class__.PASS_RESULT
-
- def _GetSVNRevision(self, in_directory):
- """Returns the svn revision for the given directory.
-
- Args:
- in_directory: The directory where svn is to be run.
- """
- # This is overridden in flakiness_dashboard_results_uploader.py.
- raise NotImplementedError()
-
- def _GetArchivedJSONResults(self):
- """Download JSON file that only contains test
- name list from test-results server. This is for generating incremental
- JSON so the file generated has info for tests that failed before but
- pass or are skipped from current run.
-
- Returns (archived_results, error) tuple where error is None if results
- were successfully read.
- """
- results_json = {}
- old_results = None
- error = None
-
- if not self._test_results_server:
- return {}, None
-
- results_file_url = (self.URL_FOR_TEST_LIST_JSON %
- (urllib2.quote(self._test_results_server),
- urllib2.quote(self._builder_name),
- self.RESULTS_FILENAME,
- urllib2.quote(self._test_type),
- urllib2.quote(self._master_name)))
-
- try:
- # FIXME: We should talk to the network via a Host object.
- results_file = urllib2.urlopen(results_file_url)
- old_results = results_file.read()
- except urllib2.HTTPError, http_error:
- # A non-4xx status code means the bot is hosed for some reason
- # and we can't grab the results.json file off of it.
- if http_error.code < 400 and http_error.code >= 500:
- error = http_error
- except urllib2.URLError, url_error:
- error = url_error
-
- if old_results:
- # Strip the prefix and suffix so we can get the actual JSON object.
- old_results = StripJSONWrapper(old_results)
-
- try:
- results_json = json.loads(old_results)
- except Exception:
- _log.debug('results.json was not valid JSON. Clobbering.')
- # The JSON file is not valid JSON. Just clobber the results.
- results_json = {}
- else:
- _log.debug('Old JSON results do not exist. Starting fresh.')
- results_json = {}
-
- return results_json, error
-
- def _InsertFailureSummaries(self, results_for_builder):
- """Inserts aggregate pass/failure statistics into the JSON.
- This method reads self._test_results and generates
- FIXABLE, FIXABLE_COUNT and ALL_FIXABLE_COUNT entries.
-
- Args:
- results_for_builder: Dictionary containing the test results for a
- single builder.
- """
- # Insert the number of tests that failed or skipped.
- fixable_count = len([r for r in self._test_results if r.Fixable()])
- self._InsertItemIntoRawList(results_for_builder,
- fixable_count, self.FIXABLE_COUNT)
-
- # Create a test modifiers (FAILS, FLAKY etc) summary dictionary.
- entry = {}
- for test_name in self._test_results_map.iterkeys():
- result_char = self._GetModifierChar(test_name)
- entry[result_char] = entry.get(result_char, 0) + 1
-
- # Insert the pass/skip/failure summary dictionary.
- self._InsertItemIntoRawList(results_for_builder, entry,
- self.FIXABLE)
-
- # Insert the number of all the tests that are supposed to pass.
- all_test_count = len(self._test_results)
- self._InsertItemIntoRawList(results_for_builder,
- all_test_count, self.ALL_FIXABLE_COUNT)
-
- def _InsertItemIntoRawList(self, results_for_builder, item, key):
- """Inserts the item into the list with the given key in the results for
- this builder. Creates the list if no such list exists.
-
- Args:
- results_for_builder: Dictionary containing the test results for a
- single builder.
- item: Number or string to insert into the list.
- key: Key in results_for_builder for the list to insert into.
- """
- if key in results_for_builder:
- raw_list = results_for_builder[key]
- else:
- raw_list = []
-
- raw_list.insert(0, item)
- raw_list = raw_list[:self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG]
- results_for_builder[key] = raw_list
-
- def _InsertItemRunLengthEncoded(self, item, encoded_results):
- """Inserts the item into the run-length encoded results.
-
- Args:
- item: String or number to insert.
- encoded_results: run-length encoded results. An array of arrays, e.g.
- [[3,'A'],[1,'Q']] encodes AAAQ.
- """
- if len(encoded_results) and item == encoded_results[0][1]:
- num_results = encoded_results[0][0]
- if num_results <= self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG:
- encoded_results[0][0] = num_results + 1
- else:
- # Use a list instead of a class for the run-length encoding since
- # we want the serialized form to be concise.
- encoded_results.insert(0, [1, item])
-
- def _InsertGenericMetaData(self, results_for_builder):
- """ Inserts generic metadata (such as version number, current time etc)
- into the JSON.
-
- Args:
- results_for_builder: Dictionary containing the test results for
- a single builder.
- """
- self._InsertItemIntoRawList(results_for_builder,
- self._build_number, self.BUILD_NUMBERS)
-
- # Include SVN revisions for the given repositories.
- for (name, path) in self._svn_repositories:
- # Note: for JSON file's backward-compatibility we use 'chrome' rather
- # than 'chromium' here.
- lowercase_name = name.lower()
- if lowercase_name == 'chromium':
- lowercase_name = 'chrome'
- self._InsertItemIntoRawList(results_for_builder,
- self._GetSVNRevision(path),
- lowercase_name + 'Revision')
-
- self._InsertItemIntoRawList(results_for_builder,
- int(time.time()),
- self.TIME)
-
- def _InsertTestTimeAndResult(self, test_name, tests):
- """ Insert a test item with its results to the given tests dictionary.
-
- Args:
- tests: Dictionary containing test result entries.
- """
-
- result = self._get_result_char(test_name)
- test_time = self._GetTestTiming(test_name)
-
- this_test = tests
- for segment in test_name.split('/'):
- if segment not in this_test:
- this_test[segment] = {}
- this_test = this_test[segment]
-
- if not len(this_test):
- self._PopulateResultsAndTimesJSON(this_test)
-
- if self.RESULTS in this_test:
- self._InsertItemRunLengthEncoded(result, this_test[self.RESULTS])
- else:
- this_test[self.RESULTS] = [[1, result]]
-
- if self.TIMES in this_test:
- self._InsertItemRunLengthEncoded(test_time, this_test[self.TIMES])
- else:
- this_test[self.TIMES] = [[1, test_time]]
-
- def _ConvertJSONToCurrentVersion(self, results_json):
- """If the JSON does not match the current version, converts it to the
- current version and adds in the new version number.
- """
- if self.VERSION_KEY in results_json:
- archive_version = results_json[self.VERSION_KEY]
- if archive_version == self.VERSION:
- return
- else:
- archive_version = 3
-
- # version 3->4
- if archive_version == 3:
- for results in results_json.values():
- self._ConvertTestsToTrie(results)
-
- results_json[self.VERSION_KEY] = self.VERSION
-
- def _ConvertTestsToTrie(self, results):
- if not self.TESTS in results:
- return
-
- test_results = results[self.TESTS]
- test_results_trie = {}
- for test in test_results.iterkeys():
- single_test_result = test_results[test]
- AddPathToTrie(test, single_test_result, test_results_trie)
-
- results[self.TESTS] = test_results_trie
-
- def _PopulateResultsAndTimesJSON(self, results_and_times):
- results_and_times[self.RESULTS] = []
- results_and_times[self.TIMES] = []
- return results_and_times
-
- def _CreateResultsForBuilderJSON(self):
- results_for_builder = {}
- results_for_builder[self.TESTS] = {}
- return results_for_builder
-
- def _RemoveItemsOverMaxNumberOfBuilds(self, encoded_list):
- """Removes items from the run-length encoded list after the final
- item that exceeds the max number of builds to track.
-
- Args:
- encoded_results: run-length encoded results. An array of arrays, e.g.
- [[3,'A'],[1,'Q']] encodes AAAQ.
- """
- num_builds = 0
- index = 0
- for result in encoded_list:
- num_builds = num_builds + result[0]
- index = index + 1
- if num_builds > self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG:
- return encoded_list[:index]
- return encoded_list
-
- def _NormalizeResultsJSON(self, test, test_name, tests):
- """ Prune tests where all runs pass or tests that no longer exist and
- truncate all results to maxNumberOfBuilds.
-
- Args:
- test: ResultsAndTimes object for this test.
- test_name: Name of the test.
- tests: The JSON object with all the test results for this builder.
- """
- test[self.RESULTS] = self._RemoveItemsOverMaxNumberOfBuilds(
- test[self.RESULTS])
- test[self.TIMES] = self._RemoveItemsOverMaxNumberOfBuilds(
- test[self.TIMES])
-
- is_all_pass = self._IsResultsAllOfType(test[self.RESULTS],
- self.PASS_RESULT)
- is_all_no_data = self._IsResultsAllOfType(test[self.RESULTS],
- self.NO_DATA_RESULT)
- max_time = max([test_time[1] for test_time in test[self.TIMES]])
-
- # Remove all passes/no-data from the results to reduce noise and
- # filesize. If a test passes every run, but takes > MIN_TIME to run,
- # don't throw away the data.
- if is_all_no_data or (is_all_pass and max_time <= self.MIN_TIME):
- del tests[test_name]
-
- # method could be a function pylint: disable=R0201
- def _IsResultsAllOfType(self, results, result_type):
- """Returns whether all the results are of the given type
- (e.g. all passes)."""
- return len(results) == 1 and results[0][1] == result_type
-
-
-class _FileUploader(object):
-
- def __init__(self, url, timeout_seconds):
- self._url = url
- self._timeout_seconds = timeout_seconds
-
- def UploadAsMultipartFormData(self, files, attrs):
- file_objs = []
- for filename, path in files:
- with file(path, 'rb') as fp:
- file_objs.append(('file', filename, fp.read()))
-
- # FIXME: We should use the same variable names for the formal and actual
- # parameters.
- content_type, data = _EncodeMultipartFormData(attrs, file_objs)
- return self._UploadData(content_type, data)
-
- def _UploadData(self, content_type, data):
- start = time.time()
- end = start + self._timeout_seconds
- while time.time() < end:
- try:
- request = urllib2.Request(self._url, data,
- {'Content-Type': content_type})
- return urllib2.urlopen(request)
- except urllib2.HTTPError as e:
- _log.warn("Received HTTP status %s loading \"%s\". "
- 'Retrying in 10 seconds...' % (e.code, e.filename))
- time.sleep(10)
-
-
-def _GetMIMEType(filename):
- return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
-
-
-# FIXME: Rather than taking tuples, this function should take more
-# structured data.
-def _EncodeMultipartFormData(fields, files):
- """Encode form fields for multipart/form-data.
-
- Args:
- fields: A sequence of (name, value) elements for regular form fields.
- files: A sequence of (name, filename, value) elements for data to be
- uploaded as files.
- Returns:
- (content_type, body) ready for httplib.HTTP instance.
-
- Source:
- http://code.google.com/p/rietveld/source/browse/trunk/upload.py
- """
- BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-'
- CRLF = '\r\n'
- lines = []
-
- for key, value in fields:
- lines.append('--' + BOUNDARY)
- lines.append('Content-Disposition: form-data; name="%s"' % key)
- lines.append('')
- if isinstance(value, unicode):
- value = value.encode('utf-8')
- lines.append(value)
-
- for key, filename, value in files:
- lines.append('--' + BOUNDARY)
- lines.append('Content-Disposition: form-data; name="%s"; '
- 'filename="%s"' % (key, filename))
- lines.append('Content-Type: %s' % _GetMIMEType(filename))
- lines.append('')
- if isinstance(value, unicode):
- value = value.encode('utf-8')
- lines.append(value)
-
- lines.append('--' + BOUNDARY + '--')
- lines.append('')
- body = CRLF.join(lines)
- content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
- return content_type, body

Powered by Google App Engine
This is Rietveld 408576698