| Index: build/android/pylib/utils/json_results_generator.py
|
| diff --git a/build/android/pylib/utils/json_results_generator.py b/build/android/pylib/utils/json_results_generator.py
|
| deleted file mode 100644
|
| index e5c433dac5e348bc523722b07890208d6c81fa6e..0000000000000000000000000000000000000000
|
| --- a/build/android/pylib/utils/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
|
|
|