#!/usr/bin/env vpython3
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import unittest
from unittest import mock
import dataclasses # Built-in, but pylint gives an ordering false positive.
from gpu_tests import common_typing as ct
from gpu_tests import gpu_helper
from telemetry.internal.platform import gpu_info
# pylint: disable=too-many-arguments
def CreateGpuDeviceDict(vendor_id: Optional[int] = None,
device_id: Optional[int] = None,
sub_sys_id: Optional[int] = None,
revision: Optional[int] = None,
vendor_string: Optional[str] = None,
device_string: Optional[str] = None,
driver_vendor: Optional[str] = None,
driver_version: Optional[str] = None
) -> Dict[str, Union[str, int]]:
return {
'vendor_id':
vendor_id or 0,
'device_id':
device_id or 0,
'sub_sys_id':
sub_sys_id or 0,
'revision':
revision or 0,
'vendor_string':
'vendor_string' if vendor_string is None else vendor_string,
'device_string':
'device_string' if device_string is None else device_string,
'driver_vendor':
'driver_vendor' if driver_vendor is None else driver_vendor,
'driver_version':
'driver_version' if driver_version is None else driver_version,
}
# pylint: enable=too-many-arguments
@dataclasses.dataclass
class TagHelperTestCase():
"""Struct-like class for defining a tag helper test case."""
expected_result: Any
device_dict: Dict[str, Union[str, int]] = ct.EmptyDict()
aux_attributes: Dict[str, Any] = ct.EmptyDict()
feature_status: Dict[str, str] = ct.EmptyDict()
extra_browser_args: List[str] = ct.EmptyList()
class TagHelpersUnittest(unittest.TestCase):
def runTagHelperTestWithIndex(
self, tc: TagHelperTestCase,
test_method: Callable[[Optional[gpu_info.GPUInfo], int], Any]) -> None:
"""Helper method for running a single tag helper test case w/ index."""
info = gpu_info.GPUInfo([CreateGpuDeviceDict(**tc.device_dict)],
tc.aux_attributes, tc.feature_status, None)
self.assertEqual(test_method(info, 0), tc.expected_result)
def runTagHelperTest(self, tc: TagHelperTestCase,
test_method: Callable[[Optional[gpu_info.GPUInfo]], Any]
) -> None:
"""Helper method for running a single tag helper test case w/o index."""
info = gpu_info.GPUInfo([CreateGpuDeviceDict(**tc.device_dict)],
tc.aux_attributes, tc.feature_status, None)
self.assertEqual(test_method(info), tc.expected_result)
def testGetGpuVendorString(self) -> None:
"""Tests all code paths for the GetGpuVendorString() method."""
cases = [
# Explicit ID -> AMD.
TagHelperTestCase(
'amd', {
'vendor_id': 0x1002,
'device_string': 'ANGLE (ANGLE gpu, 1, 2)',
'vendor_string': 'Vendor_gpu 1 2'
}),
# Explicit ID -> Intel.
TagHelperTestCase(
'intel', {
'vendor_id': 0x8086,
'device_string': 'ANGLE (ANGLE gpu, 1, 2)',
'vendor_string': 'Vendor_gpu 1 2'
}),
# Explicit ID -> NVIDIA.
TagHelperTestCase(
'nvidia', {
'vendor_id': 0x10DE,
'device_string': 'ANGLE (ANGLE gpu, 1, 2)',
'vendor_string': 'Vendor_gpu 1 2'
}),
# ANGLE vendor string.
TagHelperTestCase(
'angle gpu', {
'device_string': 'ANGLE (ANGLE gpu, 1, 2)',
'vendor_string': 'Vendor_gpu 1 2'
}),
# Vendor string.
TagHelperTestCase('vendor_gpu', {'vendor_string': 'Vendor_gpu 1 2'}),
# Defined info but unknown.
TagHelperTestCase('unknown_gpu', {'vendor_string': ''}),
]
for tc in cases:
self.runTagHelperTestWithIndex(tc, gpu_helper.GetGpuVendorString)
# Undefined info.
self.assertEqual(gpu_helper.GetGpuVendorString(None, 0), 'unknown_gpu')
def testGetGpuDeviceId(self) -> None:
"""Tests all code paths for the GetGpuDeviceId() method."""
cases = [
# Explicit device.
TagHelperTestCase(0xFFFF, {
'device_id': 0xFFFF,
'device_string': 'ANGLE (Vendor, Device, Driver)'
}),
# ANGLE device string.
TagHelperTestCase('Device',
{'device_string': 'ANGLE (Vendor, Device, Driver)'}),
# Device string.
TagHelperTestCase('Some device', {'device_string': 'Some device'}),
]
for tc in cases:
self.runTagHelperTestWithIndex(tc, gpu_helper.GetGpuDeviceId)
# Undefined info.
self.assertEqual(gpu_helper.GetGpuDeviceId(None, 0), 0)
def testIntelMasks(self) -> None:
"""Tests the masking methods for determining Intel generation."""
# Sample of real IDs taken from
# https://dgpu-docs.intel.com/devices/hardware-table.html
# Note that 12th gen is referred to as "Xe" or "XeHPG.
gen_9_ids = {0x1923, 0x3184, 0x3EA4, 0x591C, 0x5A85, 0x9BC8}
# 0x4F and 0xA7-prefixed samples missing since none were listed.
gen_12_ids = {0x4C8A, 0x9A40, 0x4905, 0x4680, 0x5690}
for pci_id in gen_9_ids:
self.assertTrue(gpu_helper.IsIntelGen9(pci_id))
self.assertFalse(gpu_helper.IsIntelGen12(pci_id))
for pci_id in gen_12_ids:
self.assertTrue(gpu_helper.IsIntelGen12(pci_id))
self.assertFalse(gpu_helper.IsIntelGen9(pci_id))
def testGetGpuDriverVendor(self) -> None:
"""Tests all code paths for the GetGpuDriverVendor() method."""
# Explicit vendor.
self.runTagHelperTest(
TagHelperTestCase('vendor', {'driver_vendor': 'vendor'}),
gpu_helper.GetGpuDriverVendor)
# Undefined info.
self.assertEqual(gpu_helper.GetGpuDriverVendor(None), None)
def testGetGpuDriverVersion(self) -> None:
"""Tests all code paths for the GetGpuDriverVersion() method."""
# Explicit version.
self.runTagHelperTest(
TagHelperTestCase('Some version', {'driver_version': 'Some version'}),
gpu_helper.GetGpuDriverVersion)
# Undefined info.
self.assertEqual(gpu_helper.GetGpuDriverVersion(None), None)
def testGetANGLERenderer(self) -> None:
"""Tests all code paths for the GetANGLERenderer() method."""
cases = [
# No aux attributes.
TagHelperTestCase('angle-disabled'),
# Non-ANGLE renderer.
TagHelperTestCase('angle-disabled',
aux_attributes={'gl_renderer': 'renderer'}),
# D3D11.
TagHelperTestCase('angle-d3d11',
aux_attributes={'gl_renderer': 'ANGLE Direct3D11'}),
# D3D9.
TagHelperTestCase('angle-d3d9',
aux_attributes={'gl_renderer': 'ANGLE Direct3D9'}),
# OpenGL ES.
TagHelperTestCase('angle-opengles',
aux_attributes={'gl_renderer': 'ANGLE OpenGL ES'}),
# OpenGL.
TagHelperTestCase('angle-opengl',
aux_attributes={'gl_renderer': 'ANGLE OpenGL'}),
# Metal.
TagHelperTestCase('angle-metal',
aux_attributes={'gl_renderer': 'ANGLE Metal'}),
# SwiftShader, explicitly test that it's chosen over Vulkan.
TagHelperTestCase(
'angle-swiftshader',
aux_attributes={'gl_renderer': 'ANGLE Vulkan SwiftShader'}),
# Vulkan.
TagHelperTestCase('angle-vulkan',
aux_attributes={'gl_renderer': 'ANGLE Vulkan'}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetANGLERenderer)
# Undefined info.
self.assertEqual(gpu_helper.GetANGLERenderer(None), 'angle-disabled')
def testGetCommandDecoder(self) -> None:
"""Tests all code paths for the GetcommandDecoder() method."""
cases = [
# No aux attributes.
TagHelperTestCase('no_passthrough'),
# Validating.
TagHelperTestCase('no_passthrough',
aux_attributes={'passthrough_cmd_decoder': False}),
# Passthrough.
TagHelperTestCase('passthrough',
aux_attributes={'passthrough_cmd_decoder': True}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetCommandDecoder)
# Undefined info.
self.assertEqual(gpu_helper.GetCommandDecoder(None), 'no_passthrough')
def testGetSkiaGraphiteStatus(self) -> None:
"""Tests all the code paths for the GetSkiaGraphiteStatus() method."""
cases = [
# No feature status.
TagHelperTestCase('graphite-disabled'),
# Feature status off.
TagHelperTestCase('graphite-disabled',
feature_status={'skia_graphite': 'disabled'}),
# Feature status on.
TagHelperTestCase('graphite-enabled',
feature_status={'skia_graphite': 'enabled_on'}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetSkiaGraphiteStatus)
# Undefined info.
self.assertEqual(gpu_helper.GetSkiaGraphiteStatus(None),
'graphite-disabled')
def testGetSkiaRenderer(self) -> None:
"""Tests all code paths for the GetSkiaRenderer() method."""
cases = [
# No feature status.
TagHelperTestCase('renderer-software'),
# No GPU Compositing.
TagHelperTestCase('renderer-software',
feature_status={'gpu_compositing': 'disabled'}),
# No renderer.
TagHelperTestCase('renderer-software',
feature_status={'gpu_compositing': 'enabled'}),
# Vulkan Skia Renderer.
TagHelperTestCase('renderer-skia-vulkan',
feature_status={
'gpu_compositing': 'enabled',
'vulkan': 'enabled_on',
'opengl': 'enabled_on'
}),
# GL Skia Renderer.
TagHelperTestCase('renderer-skia-gl',
feature_status={
'gpu_compositing': 'enabled',
'vulkan': 'enabled_off',
'opengl': 'enabled_on'
}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetSkiaRenderer)
# Undefined info.
self.assertEqual(gpu_helper.GetSkiaRenderer(None), 'renderer-software')
def testGetDisplayServer(self) -> None:
"""Tests all code paths for the GetDisplayServer() method."""
with mock.patch('gpu_tests.util.host_information.IsLinux',
return_value=True):
# Remote platforms.
for browser_type in gpu_helper.REMOTE_BROWSER_TYPES:
self.assertEqual(gpu_helper.GetDisplayServer(browser_type), None)
# X.
with mock.patch.dict('os.environ', {}, clear=True):
self.assertEqual(gpu_helper.GetDisplayServer(''), 'display-server-x')
# Wayland.
with mock.patch.dict('os.environ', {'WAYLAND_DISPLAY': '1'}, clear=True):
self.assertEqual(gpu_helper.GetDisplayServer(''),
'display-server-wayland')
with mock.patch('gpu_tests.util.host_information.IsLinux',
return_value=False):
self.assertEqual(gpu_helper.GetDisplayServer(''), None)
def testGetOOPCanvasStatus(self) -> None:
"""Tests all the code paths for the GetOOPCanvasStatus() method."""
cases = [
# No feature status.
TagHelperTestCase('no-oop-c'),
# Feature status off.
TagHelperTestCase(
'no-oop-c',
feature_status={'canvas_oop_rasterization': 'enabled_off'}),
# Feature status on.
TagHelperTestCase(
'oop-c', feature_status={'canvas_oop_rasterization': 'enabled_on'}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetOOPCanvasStatus)
# Undefined info.
self.assertEqual(gpu_helper.GetOOPCanvasStatus(None), 'no-oop-c')
def testGetAsanStatus(self) -> None:
"""Tests all code paths for the GetAsanStatus() method."""
cases = [
# No aux attributes.
TagHelperTestCase('no-asan'),
# Built without ASan.
TagHelperTestCase('no-asan', aux_attributes={'is_asan': False}),
# Built with ASan.
TagHelperTestCase('asan', aux_attributes={'is_asan': True}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetAsanStatus)
# Undefined info.
self.assertEqual(gpu_helper.GetAsanStatus(None), 'no-asan')
def testGetTargetCpuStatus(self) -> None:
"""Tests all code paths for the GetTargetCpuStatus() method."""
cases = [
# No aux attributes.
TagHelperTestCase('target-cpu-unknown'),
# Target CPU specified.
TagHelperTestCase('target-cpu-32',
aux_attributes={'target_cpu_bits': 32}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetTargetCpuStatus)
# Undefined info.
self.assertEqual(gpu_helper.GetTargetCpuStatus(None), 'target-cpu-unknown')
def testGetClangCoverage(self) -> None:
"""Tests all code paths for the GetClangCoverage() method."""
cases = [
# No aux attributes.
TagHelperTestCase('no-clang-coverage'),
# Built without Clang coverage.
TagHelperTestCase('no-clang-coverage',
aux_attributes={'is_clang_coverage': False}),
# Built with Clang coverage.
TagHelperTestCase('clang-coverage',
aux_attributes={'is_clang_coverage': True}),
]
for tc in cases:
self.runTagHelperTest(tc, gpu_helper.GetClangCoverage)
# Undefined info.
self.assertEqual(gpu_helper.GetClangCoverage(None), 'no-clang-coverage')
class ReplaceTagsUnittest(unittest.TestCase):
def testSubstringReplacement(self) -> None:
tags = ['some_tag', 'some-nvidia-corporation', 'another_tag']
self.assertEqual(gpu_helper.ReplaceTags(tags),
['some_tag', 'some-nvidia', 'another_tag'])
def testRegexReplacement(self) -> None:
tags = [
'some_tag',
'google-Vulkan-1.3.0-(SwiftShader-Device-(LLVM-10.0.0)-(0x0000C0DE))',
'another_tag'
]
self.assertEqual(gpu_helper.ReplaceTags(tags),
['some_tag', 'google-vulkan', 'another_tag'])
class EvaluateVersionComparisonUnittest(unittest.TestCase):
def testWindowsIntelUncomparableVersions(self) -> None:
"""Tests Windows Intel comparison when versions cannot be compared."""
non_ne_operations = ('eq', 'ge', 'gt', 'le', 'lt')
# Versions should only be comparable with 4 elements.
self.assertTrue(
gpu_helper.EvaluateVersionComparison('1.2.3', 'ne', '1.2.3', 'win',
'intel'))
for op in non_ne_operations:
self.assertFalse(
gpu_helper.EvaluateVersionComparison('1.2.3', op, '1.2.3', 'win',
'intel'))
self.assertTrue(
gpu_helper.EvaluateVersionComparison('1.2.3.4.5', 'ne', '1.2.3.4.5',
'win', 'intel'))
for op in non_ne_operations:
self.assertFalse(
gpu_helper.EvaluateVersionComparison('1.2.3.4.5', op, '1.2.3.4.5',
'win', 'intel'))
def testWindowsIntelOnlyLastTwoPartsUsed(self) -> None:
"""Tests that only the last two version parts are used on Windows Intel."""
self.assertTrue(
gpu_helper.EvaluateVersionComparison('1.2.3.4', 'eq', '2.3.3.4', 'win',
'intel'))
self.assertFalse(
gpu_helper.EvaluateVersionComparison('1.2.3.4', 'eq', '2.3.5.4', 'win',
'intel'))
def testInvalidOperation(self) -> None:
"""Tests that an error is raised when using an invalid operation."""
with self.assertRaisesRegex(Exception, 'Invalid operation: foo'):
gpu_helper.EvaluateVersionComparison('1.2.3.4', 'foo', '2.3.4.5')
def testEqual(self) -> None:
"""Tests that equality operations work as expected."""
eq_operations = ('eq', 'ge', 'le')
for op in eq_operations:
# Purely numerical.
self.assertTrue(
gpu_helper.EvaluateVersionComparison('1.2.3.4', op, '1.2.3.4'))
# Numerical + suffix.
self.assertTrue(
gpu_helper.EvaluateVersionComparison('1a.2b.3c.4a', op,
'1a.2b.3c.4a'))
# Mismatched length implies 0.
self.assertTrue(gpu_helper.EvaluateVersionComparison(
'1.2.0.0', op, '1.2'))
self.assertTrue(gpu_helper.EvaluateVersionComparison(
'1.2', op, '1.2.0.0'))
# Failure to parse gets skipped unless in the reference version.
self.assertTrue(
gpu_helper.EvaluateVersionComparison('1.2..4', op, '1.2.3.4'))
with self.assertRaises(AssertionError):
gpu_helper.EvaluateVersionComparison('1.2.3.4', op, '1.2..4')
def testNotEqual(self) -> None:
"""Tests that the not equal operation works as expected."""
true_cases = (
# Purely numerical.
('1.2.3.4', '1.2.3'),
('1.2.3', '1.2.3.4'),
# Same suffix, different numerical.
('2a.2b.3c.4d', '1a.2b.3c.4d'),
('1a.3b.3c.4d', '1a.2b.3c.4d'),
('1a.2b.4c.4d', '1a.2b.3c.4d'),
('1a.2b.3c.5d', '1a.2b.3c.4d'),
# Same numerical, different suffix.
('1b.2b.3c.4d', '1a.2b.3c.4d'),
('1a.2c.3c.4d', '1a.2b.3c.4d'),
('1a.2b.3d.4d', '1a.2b.3c.4d'),
('1a.2b.3c.4e', '1a.2b.3c.4d'),
)
false_cases = (
# Numerical.
('1.2.3.4', '1.2.3.4'),
# Numerical + suffix.
('1a.2b.3c.4d', '1a.2b.3c.4d'),
)
for left, right in true_cases:
self.assertTrue(gpu_helper.EvaluateVersionComparison(left, 'ne', right))
self.assertTrue(gpu_helper.EvaluateVersionComparison(right, 'ne', left))
for left, right in false_cases:
self.assertFalse(gpu_helper.EvaluateVersionComparison(left, 'ne', right))
self.assertFalse(gpu_helper.EvaluateVersionComparison(right, 'ne', left))
def testGreater(self) -> None:
"""Tests that greater operations work as expected."""
gt_operations = ('gt', 'ge')
for op in gt_operations:
for left, right in GetGreaterTestCases():
self.assertTrue(gpu_helper.EvaluateVersionComparison(left, op, right))
self.assertFalse(gpu_helper.EvaluateVersionComparison(right, op, left))
def testLess(self) -> None:
"""Tests that less operations work as expected."""
lt_operations = ('lt', 'le')
for op in lt_operations:
# Less than test cases are simply the inverse of greater than test cases,
# so just reverse the order.
for right, left in GetGreaterTestCases():
self.assertTrue(gpu_helper.EvaluateVersionComparison(left, op, right))
self.assertFalse(gpu_helper.EvaluateVersionComparison(right, op, left))
def GetGreaterTestCases() -> Tuple[Tuple[str, str], ...]:
return (
# Purely numerical.
('2.2.3.4', '1.2.3.4'),
('1.3.3.4', '1.2.3.4'),
('1.2.4.4', '1.2.3.4'),
('1.2.3.5', '1.2.3.4'),
# Same suffix, different numerical.
('2a.2b.3c.4d', '1a.2b.3c.4d'),
('1a.3b.3c.4d', '1a.2b.3c.4d'),
('1a.2b.4c.4d', '1a.2b.3c.4d'),
('1a.2b.3c.5d', '1a.2b.3c.4d'),
# Same numerical, different suffix.
('1b.2b.3c.4d', '1a.2b.3c.4d'),
('1a.2c.3c.4d', '1a.2b.3c.4d'),
('1a.2b.3d.4d', '1a.2b.3c.4d'),
('1a.2b.3c.4e', '1a.2b.3c.4d'),
)
if __name__ == '__main__':
unittest.main(verbosity=2)