Produce benchmark JSON schema v2

- No more '(Display)' strings in the GPU names, but store devices as
  `{'name': name, 'type': 'CPU/CUDA/OPENCL', 'is_display': bool}`
- Introduces testing with py.test & pipenv. The test suite is far from
  complete, though.
This commit is contained in:
2018-08-28 12:11:01 +02:00
parent 34dd5eec25
commit 4726ee0c9c
12 changed files with 356 additions and 181 deletions

4
.coveragerc Normal file
View File

@@ -0,0 +1,4 @@
[run]
omit =
# omit 3rd party modules
benchmark/foundation/third_party/*

4
.gitignore vendored
View File

@@ -14,3 +14,7 @@ __pycache__
/bundle/blender-benchmark-*.dmg /bundle/blender-benchmark-*.dmg
/bundle/blender-benchmark-*.tar.bz2 /bundle/blender-benchmark-*.tar.bz2
/bundle/blender-benchmark-*.zip /bundle/blender-benchmark-*.zip
.cache
.coverage
.pytest_cache/

14
Pipfile Normal file
View File

@@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
requests = "*"
[dev-packages]
pytest = "*"
pytest-cov = "*"
[requires]
python_version = "3.6"

157
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,157 @@
{
"_meta": {
"hash": {
"sha256": "b4fb2aae21bb351279ef411be59c7870f51bd9790631cfc32456be1a42adbc50"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
],
"version": "==2018.8.24"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"requests": {
"hashes": [
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"index": "pypi",
"version": "==2.19.1"
},
"urllib3": {
"hashes": [
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version < '4' and python_version >= '2.6'",
"version": "==1.23"
}
},
"develop": {
"atomicwrites": {
"hashes": [
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
"sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"
],
"version": "==1.1.5"
},
"attrs": {
"hashes": [
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
"sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
],
"version": "==18.1.0"
},
"coverage": {
"hashes": [
"sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
"sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
"sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95",
"sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
"sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd",
"sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
"sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1",
"sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
"sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
"sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
"sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
"sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
"sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
"sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
"sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
"sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
"sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
"sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
"sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
"sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
"sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
"sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
"sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
"sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
"sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
"sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
"sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
"sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
"sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80"
],
"markers": "python_version != '3.0.*' and python_version < '4' and python_version != '3.2.*' and python_version >= '2.6' and python_version != '3.1.*'",
"version": "==4.5.1"
},
"more-itertools": {
"hashes": [
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
],
"version": "==4.3.0"
},
"pluggy": {
"hashes": [
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
],
"markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*'",
"version": "==0.7.1"
},
"py": {
"hashes": [
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
],
"markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*'",
"version": "==1.5.4"
},
"pytest": {
"hashes": [
"sha256:3459a123ad5532852d36f6f4501dfe1acf4af1dd9541834a164666aa40395b02",
"sha256:96bfd45dbe863b447a3054145cd78a9d7f31475d2bce6111b133c0cc4f305118"
],
"index": "pypi",
"version": "==3.7.2"
},
"pytest-cov": {
"hashes": [
"sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d",
"sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"
],
"index": "pypi",
"version": "==2.5.1"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
}
}
}

View File

@@ -2,7 +2,6 @@
import bpy import bpy
def setDeviceCPU(context, cpref): def setDeviceCPU(context, cpref):
cpref.compute_device_type = 'NONE' cpref.compute_device_type = 'NONE'
return True return True
@@ -123,6 +122,7 @@ def setDeviceGPU(context,
def logComputeDevices(cpref): def logComputeDevices(cpref):
import json
device_type = cpref.compute_device_type device_type = cpref.compute_device_type
if device_type == 'NONE': if device_type == 'NONE':
device_type = 'CPU' device_type = 'CPU'
@@ -131,13 +131,18 @@ def logComputeDevices(cpref):
import _cycles import _cycles
for device in _cycles.available_devices(): for device in _cycles.available_devices():
if device[1] == 'CPU': if device[1] == 'CPU':
print("Using compute device: {}" . format(device[0])) info = {'name': device[0]}
print("Using compute device: {}" . format(json.dumps(info, sort_keys=True)))
else: else:
for device in cpref.devices: for device in cpref.devices:
if device.type != device_type: if device.type != device_type or not device.use:
continue continue
if device.use:
print("Using compute device: {}" . format(device.name)) info = {
'name': device.name.replace(' (Display)', ''),
'is_display': '(Display)' in device.name,
}
print("Using compute device: {}" . format(json.dumps(info, sort_keys=True)))
def logSystemInfo(cpref): def logSystemInfo(cpref):
@@ -148,8 +153,10 @@ def logSystemInfo(cpref):
"name": device.name.replace(" (Display)", ""), "name": device.name.replace(" (Display)", ""),
"type": device.type, "type": device.type,
} }
if device.type != 'CPU':
info_device["is_display"] = '(Display)' in device.name
info_devices.append(info_device) info_devices.append(info_device)
print("Benchmark Devices: {}" . format(json.dumps(info_devices))) print("Benchmark Devices: {}" . format(json.dumps(info_devices, sort_keys=True)))
def main(): def main():

View File

@@ -11,6 +11,8 @@ import tempfile
SCRIPT_PATH = os.path.realpath(__file__) SCRIPT_PATH = os.path.realpath(__file__)
SCRIPT_DIR = os.path.dirname(SCRIPT_PATH) SCRIPT_DIR = os.path.dirname(SCRIPT_PATH)
RESULT_JSON_SCHEMA_VERSION = 2
import foundation import foundation
from foundation import (benchrunner, from foundation import (benchrunner,
buildbot, buildbot,
@@ -286,8 +288,10 @@ def ensureImageOutputDir(results_output_dir):
def getResultJSONString(ctx, results): def getResultJSONString(ctx, results):
import copy
# Convert custom classes to dictionaries for easier JSON dump. # Convert custom classes to dictionaries for easier JSON dump.
json_results = results json_results = copy.deepcopy(results)
json_results['schema_version'] = RESULT_JSON_SCHEMA_VERSION
stats = json_results['stats'] stats = json_results['stats']
for scene in ctx.scenes: for scene in ctx.scenes:
if scene not in stats: if scene not in stats:

View File

@@ -144,7 +144,9 @@ def benchmarkAll(ctx):
return all_stats return all_stats
def benchmarkGetDeviceInfo(ctx): def benchmarkGetDeviceInfo(ctx) -> dict:
import json
command = [ctx.blender, command = [ctx.blender,
"--background", "--background",
"--factory-startup", "--factory-startup",
@@ -173,7 +175,9 @@ def benchmarkGetDeviceInfo(ctx):
if line.startswith("Compute device type:"): if line.startswith("Compute device type:"):
device_type = line.split(':', 1)[1].strip() device_type = line.split(':', 1)[1].strip()
elif line.startswith("Using compute device:"): elif line.startswith("Using compute device:"):
compute_devices.append(line.split(':', 1)[1].strip()) devices_as_json = line.split(':', 1)[1].strip()
devices = json.loads(devices_as_json)
compute_devices.append(devices)
elif line.startswith("CPU threads used:"): elif line.startswith("CPU threads used:"):
num_cpu_threads = int(line.split(':', 1)[1].strip()) num_cpu_threads = int(line.split(':', 1)[1].strip())
return {"device_type": device_type, return {"device_type": device_type,

View File

@@ -2,6 +2,7 @@ import json
import platform import platform
import subprocess import subprocess
import sys import sys
import typing
from datetime import datetime from datetime import datetime
@@ -10,9 +11,12 @@ from .third_party import cpuinfo
from .third_party import cpu_cores from .third_party import cpu_cores
from .third_party.dateutil import parser from .third_party.dateutil import parser
from . import context
def _getBlenderDeviceInfo(ctx: context.Context) -> typing.List[dict]:
prefix = "Benchmark Devices: "
def _getBlenderDeviceInfo(ctx):
PREFIX = "Benchmark Devices: "
command = [ctx.blender, command = [ctx.blender,
"--background", "--background",
"--factory-startup", "--factory-startup",
@@ -28,8 +32,8 @@ def _getBlenderDeviceInfo(ctx):
stdout, stderr = process.communicate() stdout, stderr = process.communicate()
lines = stdout.decode().split("\n") lines = stdout.decode().split("\n")
for line in lines: for line in lines:
if line.startswith(PREFIX): if line.startswith(prefix):
return json.loads(line[len(PREFIX):]) return json.loads(line[len(prefix):])
return [] return []

View File

@@ -1,168 +0,0 @@
#!/usr/bin/env python3
import os
import sys
SCRIPT_PATH = os.path.realpath(__file__)
SCRIPT_DIR = os.path.dirname(SCRIPT_PATH)
import argparse
import foundation
from foundation import (benchrunner,
context,
logger,
system_info,
util)
def configureArgumentParser():
parser = argparse.ArgumentParser(
description="Cycles benchmark helper script.")
parser.add_argument("-b", "--blender",
help="Full file path to Blender's binary " +
"to use for rendering",
default="blender")
parser.add_argument("-d", "--scenes-dir",
help="Directory with scenes",
default="")
parser.add_argument('-s', '--scenes',
nargs='+',
help='Scenes to be rendered',
default=[])
parser.add_argument('-c', '--configure-script',
help="Blender-side configuration script",
default="configure.py")
parser.add_argument('-t', '--device-type',
help="Type of the device to render on",
default="CPU")
parser.add_argument('-n', '--device-name',
help="Device name to render on",
default="")
parser.add_argument('-e', '--device-single',
help="Use single device when multiple matches",
action='store_true',
default=False)
parser.add_argument('-f', '--full-dump',
help="Dump all available in formation",
action='store_true',
default=False)
parser.add_argument('-j', '--json',
help="When in full dump mode, dump JSON",
action='store_true',
default=False)
return parser
def _printFullResult(ctx, results):
print("")
print("=" * 40)
# Print system information.
sys_info = results['system_info']
print("System info:")
print(" System: {} {}" . format(sys_info['system'],
sys_info['bitness']))
if sys_info['system'] == "Linux":
print(" Linux distro: {}, {}" . format(sys_info['dist_name'],
sys_info['dist_version']))
print(" CPU: {}" . format(sys_info['cpu_brand']))
devices = sys_info['devices']
if devices:
print(" Compute devices:")
for device in devices:
print(" - {}: {}" . format(device['type'], device['name']))
# Print Blender version.
blender = results['blender_version']
print("Blender:")
print(" Version: {}" . format(blender['version']))
print(" Hash: {}" . format(blender['build_hash']))
print(" Commit: {} {}" . format(blender['build_commit_date'],
blender['build_commit_time']))
print(" Build: {} {}" . format(blender['build_date'],
blender['build_time']))
# Print scenes status.
print("Nenchmark results:")
stats = results['stats']
for scene in ctx.scenes:
if scene not in stats:
continue
scene_stats = stats[scene]
print(" {}:" . format(scene))
print(" - Engine render time: {}" . format(
util.humanReadableTimeDifference(
scene_stats.total_render_time)))
print(" - Render time without sync: {}" . format(
util.humanReadableTimeDifference(
scene_stats.render_time_no_sync)))
print(" - Total render time: {}" . format(
util.humanReadableTimeDifference(
scene_stats.pipeline_render_time)))
print(" - Peak memory used on device: {}" . format(
util.humanReadableSize(scene_stats.device_peak_memory)))
print(" - Memory used on device during rendering: {}" . format(
util.humanReadableSize(scene_stats.device_memory_usage)))
def _printFullJSONResult(ctx, results):
import json
# Convert custom classes to dictionaries for easier JSON dump.
json_results = results
stats = json_results['stats']
for scene in ctx.scenes:
if scene not in stats:
continue
stats[scene] = stats[scene].asDict()
print(json.dumps(json_results, sort_keys=True, indent=2))
def _printBriefResult(ctx, results):
print("")
print("=" * 40)
stats = results['stats']
for scene in ctx.scenes:
if scene not in stats:
continue
scene_stats = stats[scene]
print("{}: {}" . format(
scene,
util.humanReadableTimeDifference(
scene_stats.pipeline_render_time)))
def main():
parser = configureArgumentParser()
args = parser.parse_args()
logger.init()
logger.HEADER("Cycles Benchmark Suite v{}" . format(foundation.VERSION))
# Configure context.
ctx = context.Context()
ctx.blender = args.blender
ctx.scenes_dir = args.scenes_dir
ctx.configure_script = args.configure_script
ctx.device_type = args.device_type
ctx.device_name = args.device_name
ctx.device_single = args.device_single
if args.scenes:
ctx.scenes = args.scenes
else:
ctx.scenes = ctx.listAllScenes(args.scenes_dir)
logger.INFO("Requested device details:")
benchrunner.benchmarkPrintDeviceInfo(ctx)
# Run benchmark.
all_stats = benchrunner.benchmarkAll(ctx)
# Gather all information together.
result = {
"blender_version": system_info.getBlenderVersion(ctx),
"system_info": system_info.gatherSystemInfo(ctx),
"stats": all_stats if all_stats else {}
}
if args.full_dump:
if args.json:
_printFullJSONResult(ctx, result)
else:
_printFullResult(ctx, result)
else:
_printBriefResult(ctx, result)
if __name__ == "__main__":
main()

View File

@@ -1,2 +1,9 @@
[pycodestyle] [pycodestyle]
max-line-length = 120 max-line-length = 120
[tool:pytest]
addopts = -v
--ignore bundle --ignore .git --ignore .cache --ignore config
--cov benchmark
--cov-report term-missing
python_files = tests.py test_*.py *_tests.py

0
tests/__init__.py Normal file
View File

View File

@@ -0,0 +1,138 @@
import json
import sys
import unittest
from unittest import mock
import typing
from benchmark.foundation import system_info
class FakeCycles:
"""Injected as _cycles module so we can mock its results."""
@staticmethod
def available_devices():
return None
class FakeDevice:
def __init__(self, name: str, type: str, *, use: bool):
self.name = name
self.type = type
self.use = use
def as_cycles(self) -> typing.Tuple[str, str]:
return self.name, self.type
class FakeCpref:
def __init__(self, compute_device_type: str, devices: typing.List[FakeDevice]):
assert compute_device_type in {'NONE', 'CPU', 'CUDA', 'OPENCL'}
self.compute_device_type = compute_device_type
self.devices = devices
class AbstractFakeDevicesTest(unittest.TestCase):
cpu = 'Intel Core i7-4790K CPU @ 4.00GHz'
gpu = 'GeForce GTX 970' # display
gpu2 = 'GeForce GTX 980' # non-display
compute_devices_as_printed = [
{'name': cpu, 'type': 'CPU'},
{'name': gpu, 'type': 'CUDA', 'is_display': True},
{'name': gpu2, 'type': 'CUDA', 'is_display': False},
]
cpref_devs = [
FakeDevice(cpu, 'CPU', use=True),
FakeDevice(f'{gpu} (Display)', 'CUDA', use=True),
FakeDevice(gpu2, 'CUDA', use=False),
]
def setUp(self):
super().setUp()
sys.modules['bpy'] = sys.modules[__name__] # don't fail on 'import bpy'
sys.modules['_cycles'] = FakeCycles # don't fail on 'import _cycles'
# Import so that we can mock functions here.
# noinspection PyUnresolvedReferences
from benchmark import configure
def expected_benchmark_devices_output(self) -> str:
return json.dumps(self.compute_devices_as_printed, sort_keys=True)
class ConfigurePyTest(AbstractFakeDevicesTest):
@mock.patch('_cycles.available_devices')
@mock.patch('benchmark.configure.print')
def test_log_compute_devices_cpu(self, mock_print, mock_available_devices):
from benchmark import configure
mock_available_devices.return_value = [dev.as_cycles() for dev in self.cpref_devs]
cpref = FakeCpref(compute_device_type='NONE', devices=self.cpref_devs)
configure.logComputeDevices(cpref)
expect_json = json.dumps({'name': self.cpu}, sort_keys=True)
mock_print.assert_has_calls([
mock.call('Compute device type: CPU'),
mock.call(f'Using compute device: {expect_json}'),
])
@mock.patch('_cycles.available_devices')
@mock.patch('benchmark.configure.print')
def test_log_compute_devices_gpu(self, mock_print, mock_available_devices):
from benchmark import configure
mock_available_devices.return_value = [dev.as_cycles() for dev in self.cpref_devs]
cpref = FakeCpref(compute_device_type='CUDA', devices=self.cpref_devs)
configure.logComputeDevices(cpref)
expect_json = json.dumps({'name': self.gpu, 'is_display': True}, sort_keys=True)
mock_print.assert_has_calls([
mock.call('Compute device type: CUDA'),
mock.call(f'Using compute device: {expect_json}'),
])
@mock.patch('benchmark.configure.print')
def test_log_system_info(self, mock_print):
from benchmark import configure
cpref = FakeCpref(compute_device_type='CUDA', devices=self.cpref_devs)
configure.logSystemInfo(cpref)
mock_print.assert_has_calls([
mock.call(f'Benchmark Devices: {self.expected_benchmark_devices_output()}'),
])
class BenchRunnerTest(AbstractFakeDevicesTest):
@mock.patch('subprocess.Popen')
def test_get_device_info_gpu(self, mock_popen):
send_json = json.dumps({'name': self.gpu, 'is_display': True}, sort_keys=True)
blender_output = ('\n'.join([
'Nonsense lines',
'Compute device type: GPU',
f'Using compute device: {send_json}',
'CPU threads used: 47',
'More nonsense lines',
])).encode()
mock_process = mock.Mock()
mock_popen.return_value = mock_process
mock_process.communicate.return_value = blender_output, b''
from benchmark.foundation import benchrunner, context
ctx = context.Context()
info = benchrunner.benchmarkGetDeviceInfo(ctx)
expected_info = {
"device_type": 'GPU',
"compute_devices": [{'name': self.gpu, 'is_display': True}],
"num_cpu_threads": 47
}
self.assertEqual(expected_info, info)