| # Copyright 2019 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from datetime import date |
| import logging |
| import os |
| import re |
| import shutil |
| import sys |
| import tempfile |
| from typing import Any, Dict, List, Optional |
| import unittest |
| |
| from gpu_tests import color_profile_manager |
| from gpu_tests import common_browser_args as cba |
| from gpu_tests import common_typing as ct |
| from gpu_tests import gpu_helper |
| from gpu_tests import gpu_integration_test |
| from gpu_tests import skia_gold_matching_algorithms as algo |
| |
| from skia_gold_common import skia_gold_properties as sgp |
| from skia_gold_common import skia_gold_session as sgs |
| from skia_gold_common import skia_gold_session_manager as sgsm |
| |
| import gpu_path_util |
| |
| from py_utils import cloud_storage |
| |
| from telemetry.util import image_util |
| |
| TEST_DATA_DIRS = [ |
| gpu_path_util.GPU_DATA_DIR, |
| os.path.join(gpu_path_util.CHROMIUM_SRC_DIR, 'media', 'test', 'data'), |
| ] |
| |
| SKIA_GOLD_CORPUS = 'chrome-gpu' |
| |
| |
| class _ImageParameters(): |
| def __init__(self): |
| # Parameters for cloud storage reference images. |
| self.vendor_id: Optional[int] = None |
| self.device_id: Optional[int] = None |
| self.vendor_string: Optional[str] = None |
| self.device_string: Optional[str] = None |
| self.msaa: bool = False |
| self.model_name: Optional[str] = None |
| self.driver_version: Optional[str] = None |
| self.driver_vendor: Optional[str] = None |
| self.display_server: Optional[str] = None |
| self.skia_graphite_status: Optional[str] = None |
| |
| |
| # This and its subclasses could potentially be switched to using dataclasses, |
| # but due to Python's method resolution order, inheritance breaks if a parent |
| # class has fields with defaults and a child class has fields without defaults. |
| # This might be possible to resolve with kw_only, but that requires at least |
| # Python 3.10. |
| # The third party attrs module supposedly handles this situation |
| # (https://stackoverflow.com/a/53085935), but attempting to do so on version |
| # 21.4.0 fails. |
| class SkiaGoldTestCase(): |
| """Base class for any Gold-enabled test case definition. |
| |
| Only information used within SkiaGoldIntegrationTestBase should be stored |
| here. Additional information should be stored in the appropriate subclass. |
| """ |
| # pylint: disable=too-many-arguments |
| def __init__( |
| self, |
| name: str, |
| gpu_process_disabled: bool = False, |
| grace_period_end: Optional[date] = None, |
| matching_algorithm: Optional[algo.SkiaGoldMatchingAlgorithm] = None, |
| refresh_after_finish: bool = False): |
| """ |
| Args: |
| name: A string containing the name of the test. |
| gpu_process_disabled: Whether the test runs with the GPU process disabled. |
| grace_period_end: An optional datetime.date, before which Gold comparison |
| failures will be ignored. This allows a newly added test to be |
| exempted for a (hopefully) short period after being added. This is so |
| that any slightly different different but valid images that get |
| produced by the CI builders can be triaged without turning the |
| builders red. |
| matching_algorithm: A |
| skia_gold_matching_algoriths.SkiaGoldMatchingAlgorithm that specifies |
| which matching algorithm Skia Gold should use for the test. Defaults |
| to exact matching. |
| refresh_after_finish: Whether to refresh the entire page after the test |
| finishes. |
| """ |
| self.name = name |
| self.gpu_process_disabled = gpu_process_disabled |
| self.grace_period_end = grace_period_end |
| self.matching_algorithm = (matching_algorithm |
| or algo.ExactMatchingAlgorithm()) |
| self.refresh_after_finish = refresh_after_finish |
| |
| #pylint: enable=too-many-arguments |
| |
| |
| class SkiaGoldIntegrationTestBase(gpu_integration_test.GpuIntegrationTest): |
| """Base class for all tests that upload results to Skia Gold.""" |
| _error_image_cloud_storage_bucket = 'chromium-browser-gpu-tests' |
| |
| # This information is class-scoped, so that it can be shared across |
| # invocations of tests; but it's zapped every time the browser is |
| # restarted with different command line arguments. |
| _image_parameters: Optional[_ImageParameters] = None |
| |
| _skia_gold_temp_dir: Optional[str] = None |
| _skia_gold_session_manager: Optional[sgsm.SkiaGoldSessionManager] = None |
| _skia_gold_properties: Optional[sgp.SkiaGoldProperties] = None |
| |
| # Loaded from disk at a later point. |
| _dom_automation_controller_script: Optional[str] = None |
| |
| @classmethod |
| def SetUpProcess(cls) -> None: |
| super(SkiaGoldIntegrationTestBase, cls).SetUpProcess() |
| options = cls.GetOriginalFinderOptions() |
| color_profile_manager.ForceUntilExitSRGB( |
| options.dont_restore_color_profile_after_test) |
| cls.CustomizeBrowserArgs([]) |
| cls.StartBrowser() |
| cls.SetStaticServerDirs(cls._GetStaticServerDirs()) |
| cls._skia_gold_temp_dir = tempfile.mkdtemp() |
| |
| @classmethod |
| def _GetStaticServerDirs(cls) -> List[str]: |
| return TEST_DATA_DIRS |
| |
| @classmethod |
| def _SetClassVariablesFromOptions(cls, options: ct.ParsedCmdArgs) -> None: |
| super()._SetClassVariablesFromOptions(options) |
| if not cls._dom_automation_controller_script: |
| with open( |
| os.path.join(gpu_path_util.GPU_TEST_HARNESS_JAVASCRIPT_DIR, |
| 'websocket_heartbeat.js')) as f: |
| cls._dom_automation_controller_script = f.read() |
| cls._dom_automation_controller_script += '\n' |
| with open( |
| os.path.join(gpu_path_util.GPU_TEST_HARNESS_JAVASCRIPT_DIR, |
| 'dom_automation_controller.js')) as f: |
| cls._dom_automation_controller_script += f.read() |
| |
| @classmethod |
| def GetSkiaGoldProperties(cls) -> sgp.SkiaGoldProperties: |
| if not cls._skia_gold_properties: |
| cls._skia_gold_properties = sgp.SkiaGoldProperties( |
| cls.GetOriginalFinderOptions()) |
| return cls._skia_gold_properties |
| |
| @classmethod |
| def GetSkiaGoldSessionManager(cls) -> sgsm.SkiaGoldSessionManager: |
| if not cls._skia_gold_session_manager: |
| cls._skia_gold_session_manager = sgsm.SkiaGoldSessionManager( |
| cls._skia_gold_temp_dir, cls.GetSkiaGoldProperties()) |
| return cls._skia_gold_session_manager |
| |
| @classmethod |
| def GenerateBrowserArgs(cls, additional_args: List[str]) -> List[str]: |
| """Adds default arguments to |additional_args|. |
| |
| See the parent class' method documentation for additional information. |
| """ |
| default_args = super(SkiaGoldIntegrationTestBase, |
| cls).GenerateBrowserArgs(additional_args) |
| default_args.extend([cba.ENABLE_GPU_BENCHMARKING, cba.TEST_TYPE_GPU]) |
| force_color_profile_arg = [ |
| arg for arg in default_args if arg.startswith('--force-color-profile=') |
| ] |
| if not force_color_profile_arg: |
| default_args.extend([ |
| cba.FORCE_COLOR_PROFILE_SRGB, |
| cba.ENSURE_FORCED_COLOR_PROFILE, |
| ]) |
| return default_args |
| |
| @classmethod |
| def StopBrowser(cls) -> None: |
| super(SkiaGoldIntegrationTestBase, cls).StopBrowser() |
| cls.ResetGpuInfo() |
| |
| @classmethod |
| def TearDownProcess(cls) -> None: |
| super(SkiaGoldIntegrationTestBase, cls).TearDownProcess() |
| shutil.rmtree(cls._skia_gold_temp_dir) |
| cls._skia_gold_session_manager = None |
| |
| @classmethod |
| def AddCommandlineArgs(cls, parser: ct.CmdArgParser) -> None: |
| super(SkiaGoldIntegrationTestBase, cls).AddCommandlineArgs(parser) |
| parser.add_argument('--git-revision', help='Chrome revision being tested.') |
| parser.add_argument( |
| '--test-machine-name', |
| default='', |
| help=('Name of the test machine. Specifying this argument causes this ' |
| 'script to upload failure images and diffs to cloud storage ' |
| 'directly, instead of relying on the ' |
| 'archive_gpu_pixel_test_results.py script.')) |
| parser.add_argument( |
| '--dont-restore-color-profile-after-test', |
| action='store_true', |
| default=False, |
| help=("(Mainly on Mac) don't restore the system's original color " |
| 'profile after the test completes; leave the system using the ' |
| 'sRGB color profile. See http://crbug.com/784456.')) |
| parser.add_argument('--gerrit-issue', |
| default='', |
| help='For Skia Gold integration. Gerrit issue ID.') |
| parser.add_argument( |
| '--gerrit-patchset', |
| default='', |
| help='For Skia Gold integration. Gerrit patch set number.') |
| parser.add_argument('--buildbucket-id', |
| default='', |
| help='For Skia Gold integration. Buildbucket build ID.') |
| parser.add_argument( |
| '--no-skia-gold-failure', |
| action='store_true', |
| default=False, |
| help=('For Skia Gold integration. Always report that the test passed ' |
| 'even if the Skia Gold image comparison reported a failure, but ' |
| 'otherwise perform the same steps as usual.')) |
| # Telemetry is *still* using optparse instead of argparse, so we can't have |
| # these two options in a mutually exclusive group. |
| # TODO(crbug.com/40807291): Use a mutually exclusive group once the |
| # optparse -> argparse migration is complete. |
| parser.add_argument( |
| '--local-pixel-tests', |
| action='store_true', |
| default=None, |
| help=('Specifies to run the test harness in local run mode or not. ' |
| 'When run in local mode, uploading to Gold is disabled and links ' |
| 'to help with local debugging are output. Running in local mode ' |
| 'also implies --no-luci-auth. If both this and ' |
| '--no-local-pixel-tests are left unset, the test harness will ' |
| 'attempt to detect whether it is running on a workstation or not ' |
| 'and set this option accordingly.')) |
| parser.add_argument( |
| '--no-local-pixel-tests', |
| action='store_false', |
| dest='local_pixel_tests', |
| help=('Specifies to run the test harness in non-local (bot) mode. When ' |
| 'run in this mode, data is actually uploaded to Gold and triage ' |
| 'links are generated. If both this and --local-pixel-tests are ' |
| 'left unset, the test harness will attempt to detect whether it ' |
| 'is running on a workstation or not and set this option ' |
| 'accordingly.')) |
| parser.add_argument( |
| '--skia-gold-local-png-write-directory', |
| help=('Specifies a directory to save local image diffs to instead of ' |
| 'the default of a temporary directory. Only has an effect when ' |
| 'running tests locally, not on a bot.')) |
| parser.add_argument( |
| '--no-luci-auth', |
| action='store_true', |
| default=False, |
| help=("Don't use the service account provided by LUCI for " |
| 'authentication for Skia Gold, instead relying on gsutil to be ' |
| 'pre-authenticated. Meant for testing locally instead of on the ' |
| 'bots.')) |
| parser.add_argument( |
| '--service-account', |
| help=('Specifies the service account to use instead of using ' |
| 'LUCI_CONTEXT or whatever is configured in gsutil. Implies ' |
| '--no-luci-auth. Only meant for use in Skylab where the tests ' |
| 'are automated but do not have LUCI_CONTEXT available.')) |
| parser.add_argument( |
| '--bypass-skia-gold-functionality', |
| action='store_true', |
| default=False, |
| help=('Bypass all interaction with Skia Gold, effectively disabling ' |
| 'the image comparison portion of any tests that use Gold. Only ' |
| 'meant to be used in case a Gold outage occurs and cannot be ' |
| 'fixed quickly.')) |
| |
| @classmethod |
| def ResetGpuInfo(cls) -> None: |
| cls._image_parameters = None |
| |
| @classmethod |
| def GetImageParameters(cls, test_case: SkiaGoldTestCase) -> _ImageParameters: |
| if not cls._image_parameters: |
| cls._ComputeGpuInfo(test_case) |
| return cls._image_parameters |
| |
| @classmethod |
| def _ComputeGpuInfo(cls, test_case: SkiaGoldTestCase) -> None: |
| if cls._image_parameters: |
| return |
| browser = cls.browser |
| system_info = browser.GetSystemInfo() |
| if not system_info: |
| raise Exception('System info must be supported by the browser') |
| if not system_info.gpu: |
| raise Exception('GPU information was absent') |
| device = system_info.gpu.devices[0] |
| cls._image_parameters = _ImageParameters() |
| params = cls._image_parameters |
| if device.vendor_id and device.device_id: |
| params.vendor_id = device.vendor_id |
| params.device_id = device.device_id |
| elif device.vendor_string and device.device_string: |
| params.vendor_string = device.vendor_string |
| params.device_string = device.device_string |
| elif test_case.gpu_process_disabled: |
| # Match the vendor and device IDs that the browser advertises |
| # when the software renderer is active. |
| params.vendor_id = 65535 |
| params.device_id = 65535 |
| else: |
| raise Exception('GPU device information was incomplete') |
| # TODO(senorblanco): This should probably be checking |
| # for the presence of the extensions in system_info.gpu_aux_attributes |
| # in order to check for MSAA, rather than sniffing the blocklist. |
| params.msaa = not (('disable_chromium_framebuffer_multisample' in |
| system_info.gpu.driver_bug_workarounds) or |
| ('disable_multisample_render_to_texture' in system_info. |
| gpu.driver_bug_workarounds)) |
| params.model_name = system_info.model_name |
| params.driver_version = device.driver_version |
| params.driver_vendor = device.driver_vendor |
| params.display_server = gpu_helper.GetDisplayServer(browser.browser_type) |
| params.skia_graphite_status = gpu_helper.GetSkiaGraphiteStatus( |
| system_info.gpu) |
| |
| @classmethod |
| def _UploadBitmapToCloudStorage(cls, |
| bucket: str, |
| name: str, |
| bitmap: Any, |
| public: bool = False) -> None: |
| # This sequence of steps works on all platforms to write a temporary |
| # PNG to disk, following the pattern in bitmap_unittest.py. The key to |
| # avoiding PermissionErrors seems to be to not actually try to write to |
| # the temporary file object, but to re-open its name for all operations. |
| temp_file = tempfile.NamedTemporaryFile(suffix='.png').name |
| image_util.WritePngFile(bitmap, temp_file) |
| cloud_storage.Insert(bucket, name, temp_file, publicly_readable=public) |
| |
| # Not used consistently, but potentially useful for debugging issues on the |
| # bots, so kept around for future use. |
| @classmethod |
| def _UploadGoldErrorImageToCloudStorage(cls, image_name: str, |
| screenshot: ct.Screenshot) -> None: |
| revision = cls.GetSkiaGoldProperties().git_revision |
| machine_name = re.sub(r'\W+', '_', |
| cls.GetOriginalFinderOptions().test_machine_name) |
| base_bucket = '%s/gold_failures' % (cls._error_image_cloud_storage_bucket) |
| image_name_with_revision_and_machine = '%s_%s_%s.png' % ( |
| image_name, machine_name, revision) |
| cls._UploadBitmapToCloudStorage( |
| base_bucket, |
| image_name_with_revision_and_machine, |
| screenshot, |
| public=True) |
| |
| @staticmethod |
| def _UrlToImageName(url: str) -> str: |
| image_name = re.sub(r'^(http|https|file)://(/*)', '', url) |
| image_name = re.sub(r'\.\./', '', image_name) |
| image_name = re.sub(r'(\.|/|-)', '_', image_name) |
| return image_name |
| |
| def GetGoldJsonKeys(self, test_case: SkiaGoldTestCase) -> Dict[str, str]: |
| """Get all the JSON metadata that will be passed to goldctl.""" |
| img_params = self.GetImageParameters(test_case) |
| # The frequently changing last part of the ANGLE driver version (revision of |
| # some sort?) messes a bit with inexact matching since each revision will |
| # be treated as a separate trace, so strip it off. |
| _StripAngleRevisionFromDriver(img_params) |
| # When running under the validating decoder, the device string is reported |
| # as the actual device, while under the passthrough decoder it is an ANGLE |
| # string that contains the device and some additional information. This |
| # difference can be annoying when trying to set up forwarding rules for the |
| # public Gold instance, so only use the actual device. |
| _ConvertAngleDeviceStringToActualDevice(img_params) |
| # All values need to be strings, otherwise goldctl fails. |
| gpu_keys = { |
| 'vendor_id': |
| _ToHexOrNone(img_params.vendor_id), |
| 'device_id': |
| _ToHexOrNone(img_params.device_id), |
| 'vendor_string': |
| _ToNonEmptyStrOrNone(img_params.vendor_string), |
| 'device_string': |
| _ToNonEmptyStrOrNone(img_params.device_string), |
| 'msaa': |
| str(img_params.msaa), |
| 'model_name': |
| _ToNonEmptyStrOrNone(img_params.model_name), |
| 'os': |
| _ToNonEmptyStrOrNone(self.browser.platform.GetOSName()), |
| 'os_version': |
| _ToNonEmptyStrOrNone(self.browser.platform.GetOSVersionName()), |
| 'os_version_detail_string': |
| _ToNonEmptyStrOrNone(self.browser.platform.GetOSVersionDetailString()), |
| 'driver_version': |
| _ToNonEmptyStrOrNone(img_params.driver_version), |
| 'driver_vendor': |
| _ToNonEmptyStrOrNone(img_params.driver_vendor), |
| 'display_server': |
| _ToNonEmptyStrOrNone(img_params.display_server), |
| 'combined_hardware_identifier': |
| _GetCombinedHardwareIdentifier(img_params), |
| 'browser_type': |
| _ToNonEmptyStrOrNone(self.browser.browser_type), |
| 'skia_graphite_status': |
| _ToNonEmptyStrOrNone(img_params.skia_graphite_status), |
| } |
| # If we have a grace period active, then the test is potentially flaky. |
| # Include a pair that will cause Gold to ignore any untriaged images, which |
| # will prevent it from automatically commenting on unrelated CLs that happen |
| # to produce a new image. |
| if _GracePeriodActive(test_case): |
| # This is put in the regular keys dict instead of the optional one because |
| # ignore rules do not apply to optional keys. |
| gpu_keys['ignore'] = '1' |
| return gpu_keys |
| |
| # pylint: disable=no-self-use |
| def GetGoldOptionalKeys(self) -> Dict[str, str]: |
| """Get all the optional JSON metadata that will be passed to goldctl. |
| |
| This optional data is unrelated to the configurations that images are |
| produced on, e.g. a comment that will be surfaced in Gold's UI. |
| """ |
| return {} |
| |
| # pylint: enable=no-self-use |
| |
| def _UploadTestResultToSkiaGold(self, image_name: str, |
| screenshot: ct.Screenshot, |
| test_case: SkiaGoldTestCase) -> None: |
| """Compares the given image using Skia Gold and uploads the result. |
| |
| No uploading is done if the test is being run in local run mode. Compares |
| the given screenshot to baselines provided by Gold, raising an Exception if |
| a match is not found. |
| |
| Args: |
| image_name: the name of the image being checked. |
| screenshot: the image being checked as a Telemetry Bitmap. |
| test_case: the GPU SkiaGoldTestCase object for the test. |
| """ |
| # Write screenshot to PNG file on local disk. |
| png_temp_file = tempfile.NamedTemporaryFile( |
| suffix='.png', dir=self._skia_gold_temp_dir).name |
| image_util.WritePngFile(screenshot, png_temp_file) |
| |
| gpu_keys = self.GetGoldJsonKeys(test_case) |
| gold_session = self.GetSkiaGoldSessionManager().GetSkiaGoldSession( |
| gpu_keys, corpus=SKIA_GOLD_CORPUS) |
| gold_properties = self.GetSkiaGoldProperties() |
| use_luci = not (gold_properties.local_pixel_tests |
| or gold_properties.no_luci_auth) |
| optional_keys = self.GetGoldOptionalKeys() |
| |
| status, error = gold_session.RunComparison( |
| name=image_name, |
| png_file=png_temp_file, |
| inexact_matching_args=test_case.matching_algorithm.GetCmdline(), |
| use_luci=use_luci, |
| service_account=gold_properties.service_account, |
| optional_keys=optional_keys) |
| if not status: |
| return |
| |
| status_codes =\ |
| self.GetSkiaGoldSessionManager().GetSessionClass().StatusCodes |
| if status == status_codes.AUTH_FAILURE: |
| logging.error('Gold authentication failed with output %s', error) |
| elif status == status_codes.INIT_FAILURE: |
| logging.error('Gold initialization failed with output %s', error) |
| elif status == status_codes.COMPARISON_FAILURE_REMOTE: |
| public_triage_link, internal_triage_link = gold_session.GetTriageLinks( |
| image_name) |
| if not public_triage_link or not internal_triage_link: |
| logging.error('Failed to get triage links for %s, raw output: %s', |
| image_name, error) |
| logging.error('Reason for no triage links: %s', |
| gold_session.GetTriageLinkOmissionReason(image_name)) |
| elif gold_properties.IsTryjobRun(): |
| self.artifacts.CreateLink('public_triage_link_for_entire_cl', |
| public_triage_link) |
| self.artifacts.CreateLink('internal_triage_link_for_entire_cl', |
| internal_triage_link) |
| else: |
| self.artifacts.CreateLink('public_gold_triage_link', public_triage_link) |
| self.artifacts.CreateLink('internal_gold_triage_link', |
| internal_triage_link) |
| elif status == status_codes.COMPARISON_FAILURE_LOCAL: |
| logging.error('Local comparison failed. Local diff files:') |
| _OutputLocalDiffFiles(gold_session, image_name) |
| elif status == status_codes.LOCAL_DIFF_FAILURE: |
| logging.error( |
| 'Local comparison failed and an error occurred during diff ' |
| 'generation: %s', error) |
| # There might be some files, so try outputting them. |
| logging.error('Local diff files:') |
| _OutputLocalDiffFiles(gold_session, image_name) |
| else: |
| logging.error( |
| 'Given unhandled SkiaGoldSession StatusCode %s with error %s', status, |
| error) |
| if self._ShouldReportGoldFailure(test_case): |
| raise Exception( |
| 'goldctl command returned non-zero exit code, see above for details. ' |
| 'This probably just means that the test produced an image that has ' |
| 'not been triaged as positive.') |
| |
| def _ShouldReportGoldFailure(self, test_case: SkiaGoldTestCase) -> bool: |
| """Determines if a Gold failure should actually be surfaced. |
| |
| Args: |
| test_case: The GPU SkiaGoldTestCase object for the test. |
| |
| Returns: |
| True if the failure should be surfaced, i.e. the test should fail, |
| otherwise False. |
| """ |
| parsed_options = self.GetOriginalFinderOptions() |
| # Don't surface if we're explicitly told not to. |
| if parsed_options.no_skia_gold_failure: |
| return False |
| # Don't surface if the test was recently added and we're still within its |
| # grace period. |
| if _GracePeriodActive(test_case): |
| return False |
| return True |
| |
| @classmethod |
| def GenerateGpuTests(cls, options: ct.ParsedCmdArgs) -> ct.TestGenerator: |
| del options |
| raise NotImplementedError( |
| 'GenerateGpuTests must be overridden in a subclass') |
| |
| def RunActualGpuTest(self, test_path: str, args: ct.TestArgs) -> None: |
| raise NotImplementedError( |
| 'RunActualGpuTest must be overridden in a subclass') |
| |
| |
| def _ToHex(num: str) -> str: |
| return hex(int(num)) |
| |
| |
| def _ToHexOrNone(num: Optional[str]) -> str: |
| return 'None' if num is None else _ToHex(num) |
| |
| |
| def _ToNonEmptyStrOrNone(val: Optional[str]) -> str: |
| return 'None' if val == '' else str(val) |
| |
| |
| def _GracePeriodActive(test_case: SkiaGoldTestCase) -> bool: |
| """Returns whether a grace period is currently active for a test. |
| |
| Args: |
| test_case: The GPU SkiaGoldTestCase object for the test in question. |
| |
| Returns: |
| True if a grace period is defined for |test_case| and has not yet expired. |
| Otherwise, False. |
| """ |
| return (test_case.grace_period_end |
| and date.today() <= test_case.grace_period_end) |
| |
| |
| def _StripAngleRevisionFromDriver(img_params: _ImageParameters) -> None: |
| """Strips the revision off the end of an ANGLE driver version. |
| |
| E.g. 2.1.0.b50541b2d6c4 -> 2.1.0 |
| |
| Modifies the string in place. No-ops if the driver vendor is not ANGLE. |
| |
| Args: |
| img_params: An _ImageParameters instance to modify. |
| """ |
| if 'ANGLE' not in img_params.driver_vendor or not img_params.driver_version: |
| return |
| # Assume that we're never going to have portions of the driver we care about |
| # that are longer than 8 characters. |
| driver_parts = img_params.driver_version.split('.') |
| kept_parts = [] |
| for part in driver_parts: |
| if len(part) > 8: |
| break |
| kept_parts.append(part) |
| img_params.driver_version = '.'.join(kept_parts) |
| |
| |
| def _ConvertAngleDeviceStringToActualDevice( |
| img_params: _ImageParameters) -> None: |
| """Converts an ANGLE device string to only have the actual device. |
| |
| E.g. ANGLE(Qualcomm, Adreno (TM) 640, OpenGL ES ...) -> Adreno (TM) 640 |
| |
| Modifies the string in place. No-ops if the driver string is not ANGLE. |
| |
| Args: |
| img_params: An _ImageParameters instance to modify. |
| """ |
| device_id = gpu_helper.GetANGLEGpuDeviceId(img_params.device_string) |
| if device_id: |
| img_params.device_string = device_id |
| |
| |
| def _GetCombinedHardwareIdentifier(img_params: _ImageParameters) -> str: |
| """Combine all relevant hardware identifiers into a single key. |
| |
| This makes Gold forwarding more precise by allowing us to forward explicit |
| configurations instead of individual components. |
| """ |
| vendor_id = _ToHexOrNone(img_params.vendor_id) |
| device_id = _ToHexOrNone(img_params.device_id) |
| device_string = _ToNonEmptyStrOrNone(img_params.device_string) |
| combined_hw_identifiers = ('vendor_id:{vendor_id}, ' |
| 'device_id:{device_id}, ' |
| 'device_string:{device_string}') |
| combined_hw_identifiers = combined_hw_identifiers.format( |
| vendor_id=vendor_id, device_id=device_id, device_string=device_string) |
| return combined_hw_identifiers |
| |
| |
| def _OutputLocalDiffFiles(gold_session: sgs.SkiaGoldSession, |
| image_name: str) -> None: |
| """Logs the local diff image files from the given SkiaGoldSession. |
| |
| Args: |
| gold_session: A skia_gold_session.SkiaGoldSession instance to pull files |
| from. |
| image_name: A string containing the name of the image/test that was |
| compared. |
| """ |
| given_file = gold_session.GetGivenImageLink(image_name) |
| closest_file = gold_session.GetClosestImageLink(image_name) |
| diff_file = gold_session.GetDiffImageLink(image_name) |
| failure_message = 'Unable to retrieve link' |
| logging.error('Generated image: %s', given_file or failure_message) |
| logging.error('Closest image: %s', closest_file or failure_message) |
| logging.error('Diff image: %s', diff_file or failure_message) |
| |
| |
| def load_tests(loader: unittest.TestLoader, tests: Any, |
| pattern: Any) -> unittest.TestSuite: |
| del loader, tests, pattern # Unused. |
| return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__]) |