Commit a90586e7 authored by Rakib M. Hasan's avatar Rakib M. Hasan Committed by Commit Bot

Reland "blinkpy: Create WPTExpectationsUpdater subclass that handles Android"

import_notifier.py requires WPTExpectationsUpdater.write_test_expectations
to return a dictionary mapping test names to lists of test expectation
strings. This CL makes sure that it returns that dictionary.

The fix for WPT imports can be seen in the diff between patchset 1
and patchset 2.

Test: crrev.com/c/2200009 does a full run of wpt_import.py. Expectations
are created for the imported tests and are written to the file.

Bug: 1050760, 1050754
Change-Id: I138ab91408e52b450e47bf0b38b10158b5677c46
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2199998Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Reviewed-by: default avatarLuke Z <lpz@chromium.org>
Commit-Queue: Rakib Hasan <rmhasan@google.com>
Cr-Commit-Position: refs/heads/master@{#768882}
parent daae0f64
...@@ -114,5 +114,15 @@ ...@@ -114,5 +114,15 @@
"port_name": "win-win10", "port_name": "win-win10",
"specifiers": ["Win10", "Release"], "specifiers": ["Win10", "Release"],
"is_try_builder": true "is_try_builder": true
},
"android-weblayer-pie-arm64-fyi-rel": {
"port_name": "android-android-pie",
"specifiers": ["Android", "android_weblayer", "Release"],
"is_try_builder": true
},
"android-webview-pie-arm64-fyi-rel": {
"port_name": "android-android-pie",
"specifiers": ["Android", "android_webview", "chrome_android", "Release"],
"is_try_builder": true
} }
} }
...@@ -118,18 +118,19 @@ class TestResultsFetcher(object): ...@@ -118,18 +118,19 @@ class TestResultsFetcher(object):
builder_name) + '/results/layout-test-results' builder_name) + '/results/layout-test-results'
@memoized @memoized
def fetch_results(self, build, full=False): def fetch_results(self, build, full=False, step_name=None):
"""Returns a WebTestResults object for results from a given Build. """Returns a WebTestResults object for results from a given Build.
Uses full_results.json if full is True, otherwise failing_results.json. Uses full_results.json if full is True, otherwise failing_results.json.
""" """
if not build.builder_name or not build.build_number: if not build.builder_name or not build.build_number:
_log.debug('Builder name or build number is None') _log.debug('Builder name or build number is None')
return None return None
step_name = step_name or self.get_layout_test_step_name(build)
return self.fetch_web_test_results( return self.fetch_web_test_results(
self.results_url( self.results_url(
build.builder_name, build.builder_name,
build.build_number, build.build_number,
step_name=self.get_layout_test_step_name(build)), full) step_name=step_name), full, step_name)
@memoized @memoized
def get_layout_test_step_name(self, build): def get_layout_test_step_name(self, build):
...@@ -174,7 +175,7 @@ class TestResultsFetcher(object): ...@@ -174,7 +175,7 @@ class TestResultsFetcher(object):
return suites[0] return suites[0]
@memoized @memoized
def fetch_web_test_results(self, results_url, full=False): def fetch_web_test_results(self, results_url, full=False, step_name=None):
"""Returns a WebTestResults object for results fetched from a given URL. """Returns a WebTestResults object for results fetched from a given URL.
Uses full_results.json if full is True, otherwise failing_results.json. Uses full_results.json if full is True, otherwise failing_results.json.
""" """
...@@ -185,7 +186,7 @@ class TestResultsFetcher(object): ...@@ -185,7 +186,7 @@ class TestResultsFetcher(object):
_log.debug('Got 404 response from:\n%s/%s', results_url, _log.debug('Got 404 response from:\n%s/%s', results_url,
base_filename) base_filename)
return None return None
return WebTestResults.results_from_string(results_file) return WebTestResults.results_from_string(results_file, step_name)
def fetch_webdriver_test_results(self, build, master): def fetch_webdriver_test_results(self, build, master):
if not build.builder_name or not build.build_number or not master: if not build.builder_name or not build.build_number or not master:
......
...@@ -26,8 +26,11 @@ ...@@ -26,8 +26,11 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from collections import namedtuple
from blinkpy.common.net.results_fetcher import TestResultsFetcher from blinkpy.common.net.results_fetcher import TestResultsFetcher
BuilderStep = namedtuple('BuilderStep', ['build', 'step_name'])
# TODO(qyearsley): To be consistent with other fake ("mock") classes, this # TODO(qyearsley): To be consistent with other fake ("mock") classes, this
# could be changed so it's not a subclass of TestResultsFetcher. # could be changed so it's not a subclass of TestResultsFetcher.
...@@ -41,12 +44,16 @@ class MockTestResultsFetcher(TestResultsFetcher): ...@@ -41,12 +44,16 @@ class MockTestResultsFetcher(TestResultsFetcher):
self.fetched_webdriver_builds = [] self.fetched_webdriver_builds = []
self._layout_test_step_name = 'blink_web_tests (with patch)' self._layout_test_step_name = 'blink_web_tests (with patch)'
def set_results(self, build, results): def set_results(self, build, results, step_name=None):
self._canned_results[build] = results step_name = step_name or self.get_layout_test_step_name(build)
step = BuilderStep(build=build, step_name=step_name)
self._canned_results[step] = results
def fetch_results(self, build, full=False): def fetch_results(self, build, full=False, step_name=None):
self.fetched_builds.append(build) step_name = step_name or self.get_layout_test_step_name(build)
return self._canned_results.get(build) step = BuilderStep(build=build, step_name=step_name)
self.fetched_builds.append(step)
return self._canned_results.get(step)
def set_webdriver_test_results(self, build, master, results): def set_webdriver_test_results(self, build, master, results):
self._webdriver_results[(build, master)] = results self._webdriver_results[(build, master)] = results
......
...@@ -113,7 +113,7 @@ class WebTestResult(object): ...@@ -113,7 +113,7 @@ class WebTestResult(object):
# This doesn't belong in common.net, but we don't have a better place for it yet. # This doesn't belong in common.net, but we don't have a better place for it yet.
class WebTestResults(object): class WebTestResults(object):
@classmethod @classmethod
def results_from_string(cls, string): def results_from_string(cls, string, step_name=None):
"""Creates a WebTestResults object from a test result JSON string. """Creates a WebTestResults object from a test result JSON string.
Args: Args:
...@@ -128,11 +128,15 @@ class WebTestResults(object): ...@@ -128,11 +128,15 @@ class WebTestResults(object):
if not json_dict: if not json_dict:
return None return None
return cls(json_dict) return cls(json_dict, step_name=step_name)
def __init__(self, parsed_json, chromium_revision=None): def __init__(self, parsed_json, chromium_revision=None, step_name=None):
self._results = parsed_json self._results = parsed_json
self._chromium_revision = chromium_revision self._chromium_revision = chromium_revision
self._step_name = step_name
def step_name(self):
return self._step_name
def run_was_interrupted(self): def run_was_interrupted(self):
return self._results['interrupted'] return self._results['interrupted']
......
...@@ -173,7 +173,8 @@ class RebaselineCL(AbstractParallelRebaselineCommand): ...@@ -173,7 +173,8 @@ class RebaselineCL(AbstractParallelRebaselineCommand):
def selected_try_bots(self): def selected_try_bots(self):
if self._selected_try_bots: if self._selected_try_bots:
return self._selected_try_bots return self._selected_try_bots
return frozenset(self._tool.builders.all_try_builder_names()) return frozenset(self._tool.builders.filter_builders(
is_try=True, exclude_specifiers={'android'}))
def _get_issue_number(self): def _get_issue_number(self):
"""Returns the current CL issue number, or None.""" """Returns the current CL issue number, or None."""
......
# Copyright 2020 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.
"""Updates expectations for Android WPT bots.
Specifically, this class fetches results from try bots for the current CL, then
(1) updates browser specific expectation files for Androids browsers like Weblayer.
We needed an Android flavour of the WPTExpectationsUpdater class because
(1) Android bots don't currently produce output that can be baselined, therefore
we only update expectations in the class below.
(2) For Android we write test expectations to browser specific expectation files,
not the regular web test's TestExpectation and NeverFixTests file.
(3) WPTExpectationsUpdater can only update expectations for the blink_web_tests
and webdriver_test_suite steps. Android bots may run several WPT steps, so
the script needs to be able to update expectations for multiple steps.
"""
import logging
from collections import defaultdict, namedtuple
from blinkpy.common.host import Host
from blinkpy.common.memoized import memoized
from blinkpy.common.system.executive import ScriptError
from blinkpy.w3c.wpt_expectations_updater import WPTExpectationsUpdater
from blinkpy.web_tests.models.test_expectations import TestExpectations
from blinkpy.web_tests.models.typ_types import Expectation
from blinkpy.web_tests.port.android import (
PRODUCTS, PRODUCTS_TO_STEPNAMES, PRODUCTS_TO_BROWSER_TAGS,
PRODUCTS_TO_EXPECTATION_FILE_PATHS)
_log = logging.getLogger(__name__)
AndroidConfig = namedtuple('AndroidConfig', ['port_name', 'browser'])
class AndroidWPTExpectationsUpdater(WPTExpectationsUpdater):
MARKER_COMMENT = '# Add untriaged failures in this block'
UMBRELLA_BUG = 'crbug.com/1050754'
def __init__(self, host, args=None):
super(AndroidWPTExpectationsUpdater, self).__init__(host, args)
expectations_dict = {}
for product in self.options.android_product:
path = PRODUCTS_TO_EXPECTATION_FILE_PATHS[product]
expectations_dict.update(
{path: self.host.filesystem.read_text_file(path)})
self._test_expectations = TestExpectations(
self.port, expectations_dict=expectations_dict)
def _get_web_test_results(self, build):
"""Gets web tests results for Android builders. We need to
bypass the step which gets the step names for the builder
since it does not currently work for Android.
Args:
build: Named tuple containing builder name and number
Returns:
returns: List of web tests results for each web test step
in build.
"""
results_sets = []
build_specifiers = self._get_build_specifiers(build)
for product in self.options.android_product:
if product in build_specifiers:
step_name = PRODUCTS_TO_STEPNAMES[product]
results_sets.append(self.host.results_fetcher.fetch_results(
build, True, '%s (with patch)' % step_name))
return filter(None, results_sets)
def get_builder_configs(self, build, results_set=None):
"""Gets step name from WebTestResults instance and uses
that to create AndroidConfig instances. It also only
returns valid configs for the android products passed
through the --android-product command line argument.
Args:
build: Build object that contains the builder name and
build number.
results_set: WebTestResults instance. If this variable
then it will return a list of android configs for
each product passed through the --android-product
command line argument.
Returns:
List of valid android configs for products passed through
the --android-product command line argument."""
configs = []
if not results_set:
build_specifiers = self._get_build_specifiers(build)
products = build_specifiers & {
s.lower() for s in self.options.android_product}
else:
step_name = results_set.step_name()
step_name = step_name[: step_name.index(' (with patch)')]
product = {s: p for p, s in PRODUCTS_TO_STEPNAMES.items()}[step_name]
products = {product}
for product in products:
browser = PRODUCTS_TO_BROWSER_TAGS[product]
configs.append(
AndroidConfig(port_name=self.port_name(build), browser=browser))
return configs
@memoized
def _get_build_specifiers(self, build):
return {s.lower() for s in
self.host.builders.specifiers_for_builder(build.builder_name)}
def can_rebaseline(self, *_):
"""Return False since we cannot rebaseline tests for
Android at the moment."""
return False
@memoized
def _get_marker_line_number(self, path):
for line in self._test_expectations.get_updated_lines(path):
if line.to_string() == self.MARKER_COMMENT:
return line.lineno
raise ScriptError('Marker comment does not exist in %s' % path)
def write_to_test_expectations(self, test_to_results):
"""Each expectations file is browser specific, and currently only
runs on pie. Therefore we do not need any configuration specifiers
to anotate expectations for certain builds.
Args:
test_to_results: A dictionary that maps test names to another
dictionary which maps a tuple of build configurations and to
a test result.
Returns:
Dictionary mapping test names to lists of expectation strings.
"""
browser_to_exp_path = {
browser: PRODUCTS_TO_EXPECTATION_FILE_PATHS[product]
for product, browser in PRODUCTS_TO_BROWSER_TAGS.items()}
untriaged_exps = defaultdict(dict)
for path in self._test_expectations.expectations_dict:
marker_lineno = self._get_marker_line_number(path)
exp_lines = self._test_expectations.get_updated_lines(path)
for i in range(marker_lineno, len(exp_lines)):
if (not exp_lines[i].to_string().strip() or
exp_lines[i].to_string().startswith('#')):
break
untriaged_exps[path][exp_lines[i].test] = exp_lines[i]
for path, test_exps in untriaged_exps.items():
self._test_expectations.remove_expectations(
path, test_exps.values())
for results_test_name, platform_results in test_to_results.items():
exps_test_name = 'external/wpt/%s' % results_test_name
for configs, test_results in platform_results.items():
for config in configs:
path = browser_to_exp_path[config.browser]
# no system specifiers are necessary because we are
# writing to browser specific expectations files for
# only one Android version.
unexpected_results = {r for r in test_results.actual.split()
if r not in test_results.expected.split()}
if exps_test_name not in untriaged_exps[path]:
untriaged_exps[path][exps_test_name] = Expectation(
test=exps_test_name, reason=self.UMBRELLA_BUG,
results=unexpected_results)
else:
untriaged_exps[path][exps_test_name].add_expectations(
unexpected_results, reason=self.UMBRELLA_BUG)
for path in untriaged_exps:
marker_lineno = self._get_marker_line_number(path)
self._test_expectations.add_expectations(
path,
sorted(untriaged_exps[path].values(), key=lambda e: e.test),
marker_lineno)
self._test_expectations.commit_changes()
# TODO(rmhasan): Return dictionary mapping test names to lists of
# test expectation strings.
return {}
def _is_wpt_test(self, _):
"""On Android we use the wpt executable. The test results do not include
the external/wpt prefix. Therefore we need to override this function for
Android and return True for all tests since we only run WPT on Android.
"""
return True
@memoized
def _get_try_bots(self):
return self.host.builders.filter_builders(
is_try=True, include_specifiers=self.options.android_product)
...@@ -20,14 +20,13 @@ from blinkpy.common.path_finder import PathFinder ...@@ -20,14 +20,13 @@ from blinkpy.common.path_finder import PathFinder
from blinkpy.w3c.common import WPT_GH_URL from blinkpy.w3c.common import WPT_GH_URL
from blinkpy.w3c.directory_owners_extractor import DirectoryOwnersExtractor from blinkpy.w3c.directory_owners_extractor import DirectoryOwnersExtractor
from blinkpy.w3c.monorail import MonorailAPI, MonorailIssue from blinkpy.w3c.monorail import MonorailAPI, MonorailIssue
from blinkpy.w3c.wpt_expectations_updater import UMBRELLA_BUG from blinkpy.w3c.wpt_expectations_updater import WPTExpectationsUpdater
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
GITHUB_COMMIT_PREFIX = WPT_GH_URL + 'commit/' GITHUB_COMMIT_PREFIX = WPT_GH_URL + 'commit/'
SHORT_GERRIT_PREFIX = 'https://crrev.com/c/' SHORT_GERRIT_PREFIX = 'https://crrev.com/c/'
class ImportNotifier(object): class ImportNotifier(object):
def __init__(self, host, chromium_git, local_wpt): def __init__(self, host, chromium_git, local_wpt):
self.host = host self.host = host
...@@ -355,6 +354,6 @@ class TestFailure(object): ...@@ -355,6 +354,6 @@ class TestFailure(object):
assert self.failure_type == self.NEW_EXPECTATION assert self.failure_type == self.NEW_EXPECTATION
# TODO(robertma): Are there saner ways to remove the link to the umbrella bug? # TODO(robertma): Are there saner ways to remove the link to the umbrella bug?
line = self.expectation_line line = self.expectation_line
if line.startswith(UMBRELLA_BUG): if line.startswith(WPTExpectationsUpdater.UMBRELLA_BUG):
line = line[len(UMBRELLA_BUG):].lstrip() line = line[len(WPTExpectationsUpdater.UMBRELLA_BUG):].lstrip()
return line return line
...@@ -12,8 +12,10 @@ from blinkpy.common.system.executive_mock import mock_git_commands ...@@ -12,8 +12,10 @@ from blinkpy.common.system.executive_mock import mock_git_commands
from blinkpy.common.system.filesystem_mock import MockFileSystem from blinkpy.common.system.filesystem_mock import MockFileSystem
from blinkpy.w3c.local_wpt_mock import MockLocalWPT from blinkpy.w3c.local_wpt_mock import MockLocalWPT
from blinkpy.w3c.import_notifier import ImportNotifier, TestFailure from blinkpy.w3c.import_notifier import ImportNotifier, TestFailure
from blinkpy.w3c.wpt_expectations_updater import UMBRELLA_BUG from blinkpy.w3c.wpt_expectations_updater import WPTExpectationsUpdater
UMBRELLA_BUG = WPTExpectationsUpdater.UMBRELLA_BUG
MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
......
...@@ -282,7 +282,8 @@ class TestImporter(object): ...@@ -282,7 +282,8 @@ class TestImporter(object):
def blink_try_bots(self): def blink_try_bots(self):
"""Returns the collection of builders used for updating expectations.""" """Returns the collection of builders used for updating expectations."""
return self.host.builders.all_try_builder_names() return self.host.builders.filter_builders(
is_try=True, exclude_specifiers={'android'})
def parse_args(self, argv): def parse_args(self, argv):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
......
...@@ -42,10 +42,10 @@ class BuilderList(object): ...@@ -42,10 +42,10 @@ class BuilderList(object):
The given dictionary maps builder names to dicts with the keys: The given dictionary maps builder names to dicts with the keys:
"port_name": A fully qualified port name. "port_name": A fully qualified port name.
"specifiers": A two-item list: [version specifier, build type specifier]. "specifiers": A list of specifiers used to describe a builder.
Valid values for the version specifier can be found in The specifiers list will at the very least have a valid
TestExpectationsParser._configuration_tokens_list, and valid port version specifier like "Mac10.15" and and a valid build
values for the build type specifier include "Release" and "Debug". type specifier like "Release".
"is_try_builder": Whether the builder is a trybot. "is_try_builder": Whether the builder is a trybot.
"master": The master name of the builder. It is deprecated, but still required "master": The master name of the builder. It is deprecated, but still required
by test-results.appspot.com API." by test-results.appspot.com API."
...@@ -57,8 +57,11 @@ class BuilderList(object): ...@@ -57,8 +57,11 @@ class BuilderList(object):
""" """
self._builders = builders_dict self._builders = builders_dict
for builder in builders_dict: for builder in builders_dict:
specifiers = {
s.lower() for s in builders_dict[builder].get('specifiers', {})}
assert 'port_name' in builders_dict[builder] assert 'port_name' in builders_dict[builder]
assert len(builders_dict[builder]['specifiers']) == 2 assert ('android' in specifiers or
len(builders_dict[builder]['specifiers']) == 2)
@staticmethod @staticmethod
def load_default_builder_list(filesystem): def load_default_builder_list(filesystem):
...@@ -72,12 +75,29 @@ class BuilderList(object): ...@@ -72,12 +75,29 @@ class BuilderList(object):
return sorted(self._builders) return sorted(self._builders)
def all_try_builder_names(self): def all_try_builder_names(self):
return sorted(b for b in self._builders return self.filter_builders(is_try=True)
if self._builders[b].get('is_try_builder'))
def all_continuous_builder_names(self): def all_continuous_builder_names(self):
return sorted(b for b in self._builders return self.filter_builders(is_try=False)
if not self._builders[b].get('is_try_builder'))
def filter_builders(self, exclude_specifiers=None, include_specifiers=None,
is_try=False):
_lower_specifiers = lambda specifiers: {s.lower() for s in specifiers}
exclude_specifiers = _lower_specifiers(exclude_specifiers or {})
include_specifiers = _lower_specifiers(include_specifiers or {})
builders = []
for b in self._builders:
builder_specifiers = _lower_specifiers(
self._builders[b].get('specifiers', {}))
if self._builders[b].get('is_try_builder', False) != is_try:
continue
if builder_specifiers & exclude_specifiers:
continue
if (include_specifiers and
not include_specifiers & builder_specifiers):
continue
builders.append(b)
return sorted(builders)
def all_port_names(self): def all_port_names(self):
return sorted({b['port_name'] for b in self._builders.values()}) return sorted({b['port_name'] for b in self._builders.values()})
......
...@@ -382,11 +382,13 @@ class TestExpectations(object): ...@@ -382,11 +382,13 @@ class TestExpectations(object):
# test to be whatever the bot thinks they should be. Is this a # test to be whatever the bot thinks they should be. Is this a
# good thing? # good thing?
bot_expectations = self._port.bot_expectations() bot_expectations = self._port.bot_expectations()
raw_expectations = '# results: [ Failure Pass Crash Skip Timeout ]\n' if bot_expectations:
for test, results in bot_expectations.items(): raw_expectations = (
raw_expectations += typ_types.Expectation( '# results: [ Failure Pass Crash Skip Timeout ]\n')
test=test, results=results).to_string() + '\n' for test, results in bot_expectations.items():
self.merge_raw_expectations(raw_expectations) raw_expectations += typ_types.Expectation(
test=test, results=results).to_string() + '\n'
self.merge_raw_expectations(raw_expectations)
def remove_expectations(self, path, exps): def remove_expectations(self, path, exps):
"""This method removes Expectation instances from an expectations file. """This method removes Expectation instances from an expectations file.
......
...@@ -38,6 +38,7 @@ import time ...@@ -38,6 +38,7 @@ import time
from blinkpy.common import exit_codes from blinkpy.common import exit_codes
from blinkpy.common.path_finder import RELATIVE_WEB_TESTS from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT
from blinkpy.common.path_finder import get_blink_dir
from blinkpy.common.path_finder import get_chromium_src_dir from blinkpy.common.path_finder import get_chromium_src_dir
from blinkpy.common.system.executive import ScriptError from blinkpy.common.system.executive import ScriptError
from blinkpy.common.system.profiler import SingleFileOutputProfiler from blinkpy.common.system.profiler import SingleFileOutputProfiler
...@@ -63,6 +64,37 @@ intent = None ...@@ -63,6 +64,37 @@ intent = None
perf_control = None perf_control = None
# pylint: enable=invalid-name # pylint: enable=invalid-name
# product constants used by the wpt runner.
ANDROID_WEBLAYER = 'android_weblayer'
ANDROID_WEBVIEW = 'android_webview'
CHROME_ANDROID = 'chrome_android'
PRODUCTS = [ANDROID_WEBLAYER, ANDROID_WEBVIEW, CHROME_ANDROID]
PRODUCTS_TO_STEPNAMES = {
ANDROID_WEBLAYER: 'weblayer_shell_wpt',
ANDROID_WEBVIEW: 'system_webview_wpt',
CHROME_ANDROID: 'chrome_public_wpt',
}
PRODUCTS_TO_BROWSER_TAGS = {
ANDROID_WEBLAYER: 'weblayer',
ANDROID_WEBVIEW: 'webview',
CHROME_ANDROID: 'chrome',
}
# Android web tests directory, which contains override expectation files
ANDROID_WEB_TESTS_DIR = os.path.join(get_blink_dir(), 'web_tests', 'android')
PRODUCTS_TO_EXPECTATION_FILE_PATHS = {
ANDROID_WEBLAYER: os.path.join(
ANDROID_WEB_TESTS_DIR, 'WeblayerWPTOverrideExpectations'),
ANDROID_WEBVIEW: os.path.join(
ANDROID_WEB_TESTS_DIR, 'WebviewWPTOverrideExpectations'),
CHROME_ANDROID: os.path.join(
ANDROID_WEB_TESTS_DIR, 'ClankWPTOverrideExpectations'),
}
_friendly_browser_names = { _friendly_browser_names = {
'weblayershell': 'weblayer', 'weblayershell': 'weblayer',
'systemwebviewshell': 'webview', 'systemwebviewshell': 'webview',
...@@ -313,6 +345,10 @@ class AndroidPort(base.Port): ...@@ -313,6 +345,10 @@ class AndroidPort(base.Port):
for serial in prepared_devices: for serial in prepared_devices:
self._devices.set_device_prepared(serial) self._devices.set_device_prepared(serial)
def bot_expectations(self):
# TODO(rmhasan) Add bot expectations to WPT metadata.
return {}
def get_platform_tags(self): def get_platform_tags(self):
_sanitize_tag = lambda t: t.replace('_', '-').replace(' ', '-') _sanitize_tag = lambda t: t.replace('_', '-').replace(' ', '-')
return frozenset( return frozenset(
......
...@@ -40,6 +40,7 @@ from blinkpy.common.system.system_host_mock import MockSystemHost ...@@ -40,6 +40,7 @@ from blinkpy.common.system.system_host_mock import MockSystemHost
from blinkpy.web_tests.port import android from blinkpy.web_tests.port import android
from blinkpy.web_tests.port import driver_unittest from blinkpy.web_tests.port import driver_unittest
from blinkpy.web_tests.port import port_testcase from blinkpy.web_tests.port import port_testcase
from blinkpy.web_tests.models.test_expectations import TestExpectations
_DEVIL_ROOT = os.path.join(get_chromium_src_dir(), 'third_party', 'catapult', _DEVIL_ROOT = os.path.join(get_chromium_src_dir(), 'third_party', 'catapult',
'devil') 'devil')
...@@ -121,6 +122,14 @@ class AndroidPortTest(port_testcase.PortTestCase): ...@@ -121,6 +122,14 @@ class AndroidPortTest(port_testcase.PortTestCase):
self.assertEquals(6, port_default.default_child_processes()) self.assertEquals(6, port_default.default_child_processes())
self.assertEquals(1, port_fixed_device.default_child_processes()) self.assertEquals(1, port_fixed_device.default_child_processes())
def test_no_bot_expectations_searched(self):
# We don't support bot expectations at the moment
host = MockSystemHost()
port = android.AndroidPort(host, apk='apks/WebLayerShell.apk')
port.expectations_dict = lambda: {}
test_expectations = TestExpectations(port)
self.assertFalse(test_expectations._expectations)
def test_weblayer_expectation_tags(self): def test_weblayer_expectation_tags(self):
host = MockSystemHost() host = MockSystemHost()
port = android.AndroidPort(host, apk='apks/WebLayerShell.apk') port = android.AndroidPort(host, apk='apks/WebLayerShell.apk')
......
...@@ -5,9 +5,25 @@ ...@@ -5,9 +5,25 @@
import sys import sys
from blinkpy.common import host from blinkpy.common.host import Host
from blinkpy.w3c.wpt_expectations_updater import WPTExpectationsUpdater from blinkpy.w3c.wpt_expectations_updater import WPTExpectationsUpdater
from blinkpy.w3c.android_wpt_expectations_updater import (
AndroidWPTExpectationsUpdater)
if __name__ == "__main__":
updater = WPTExpectationsUpdater(host.Host()) def get_updater(host=None, args=None):
sys.exit(updater.run(sys.argv[1:])) host = host or Host()
if any(arg.startswith('--android-product') for arg in args or []):
return AndroidWPTExpectationsUpdater(host, args)
else:
return WPTExpectationsUpdater(host, args)
def main(args):
host = Host()
updater = get_updater(host, args)
return updater.run()
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment