diff options
author | Andres Gomez <agomez@igalia.com> | 2020-10-16 16:08:32 +0300 |
---|---|---|
committer | Andres Gomez <agomez@igalia.com> | 2020-11-02 22:22:33 +0200 |
commit | 1c9d03dcb310ad3f310ecf4a8a54eb9c15c678f9 (patch) | |
tree | c032705b0474339cefbfe3cf72bc5054c06c5ad3 | |
parent | 536b568794406dcf72f8c06542d2e903b867155d (diff) |
unittests: add tests for framework/replay
v2:
- Added new dependencies to the CI build.
v3:
- Updated build tag in .gitlab-ci.yml.
v4:
- Use OSError instead of PIL.UnidentifiedImageError for backwards
compatibility in image_checksum test.
v5:
- Do not use yoda style comparisons (Dylan).
- Use newly added compare result enumeration.
- Deal with AttributeError instead of TypeError from recent changes
in query_traces_yaml.
v6:
- Use textwrap.dedent to enhance readability.
Signed-off-by: Andres Gomez <agomez@igalia.com>
Reviewed-by: Dylan Baker <dylan@pnwbakers.com>
Part-of: <https://gitlab.freedesktop.org/mesa/piglit/-/merge_requests/353>
-rw-r--r-- | .gitlab-ci.yml | 2 | ||||
-rw-r--r-- | .gitlab-ci/debian-install.sh | 4 | ||||
-rw-r--r-- | tox.ini | 7 | ||||
-rw-r--r-- | unittests/framework/replay/backends/test_apitrace.py | 500 | ||||
-rw-r--r-- | unittests/framework/replay/backends/test_gfxreconstruct.py | 374 | ||||
-rw-r--r-- | unittests/framework/replay/backends/test_package.py | 93 | ||||
-rw-r--r-- | unittests/framework/replay/backends/test_renderdoc.py | 196 | ||||
-rw-r--r-- | unittests/framework/replay/test_compare_replay.py | 313 | ||||
-rw-r--r-- | unittests/framework/replay/test_download_utils.py | 110 | ||||
-rw-r--r-- | unittests/framework/replay/test_image_checksum.py | 41 | ||||
-rw-r--r-- | unittests/framework/replay/test_options.py | 62 | ||||
-rw-r--r-- | unittests/framework/replay/test_query_traces_yaml.py | 202 |
12 files changed, 1900 insertions, 4 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4d383ca7e..4a1403a4f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ # repository's registry will be used there as well. variables: UPSTREAM_REPO: mesa/piglit - DEBIAN_TAG: "2020-04-21" + DEBIAN_TAG: "2020-10-16" DEBIAN_VERSION: buster-slim DEBIAN_IMAGE: "$CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG" WINDOWS_TAG: "2020-08-18" diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index 7412b7674..cc52710bc 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -48,13 +48,17 @@ apt-get install -y \ python3-mako \ python3-mock \ python3-numpy \ + python3-pil \ python3-pip \ python3-psutil \ python3-pytest \ python3-pytest-mock \ python3-pytest-timeout \ + python3-requests \ + python3-requests-mock \ python3-setuptools \ python3-wheel \ + python3-yaml \ tox \ waffle-utils @@ -22,14 +22,15 @@ deps = {accel,noaccel,generator,streams}: pytest==3.2.5 functional: pytest>=3.9 pytest-mock==1.11.2 + {accel,noaccel}: requests-mock pytest-pythonpath pytest-raises pytest-timeout==1.2.1 {accel,noaccel,streams}: jsonschema streams: jsonstreams>=0.4.1 - functional: pyyaml - functional: requests - functional: Pillow + {accel,noaccel,functional}: pyyaml + {accel,noaccel,functional}: requests + {accel,noaccel,functional}: Pillow commands = {accel,noaccel}: py.test -rw unittests/framework unittests/suites [] generator: py.test -rw unittests/generators [] diff --git a/unittests/framework/replay/backends/test_apitrace.py b/unittests/framework/replay/backends/test_apitrace.py new file mode 100644 index 000000000..aa9ea6ddb --- /dev/null +++ b/unittests/framework/replay/backends/test_apitrace.py @@ -0,0 +1,500 @@ +# coding=utf-8 +# +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's apitrace backend.""" + +import pytest + +import os +import subprocess + +from os import path + +from framework import core +from framework import exceptions +from framework.replay import backends +from framework.replay.options import OPTIONS + + +@pytest.yield_fixture +def config(mocker): + conf = mocker.patch('framework.core.PIGLIT_CONFIG', + new_callable=core.PiglitConfig) + conf.add_section('replay') + yield conf + +class TestAPITraceBackend(object): + """Tests for the APITraceBackend class.""" + + def mock_apitrace_subprocess_run(self, cmd, stdout, env=None): + get_last_call_args = ['dump', '--calls=frame'] + replay_retrace_args = ['--headless'] + if cmd[1:-1] == get_last_call_args: + # GL get_last_call + ret = subprocess.CompletedProcess(cmd, 0) + if cmd[-1] == self.gl_last_frame_fails_trace_path: + ret.stdout = b'\n' + else: + ret.stdout = bytearray( + str(int(self.gl_trace_last_call) - 50) + + ' glXSwapBuffers(dpy = 0x56060e921f80, ' + 'drawable = 31457282)\n' + '\n' + + self.gl_trace_last_call + + ' glXSwapBuffers(dpy = 0x56060e921f80, ' + 'drawable = 31457282)\n', + 'utf-8') + + return ret + elif cmd[1:2] == replay_retrace_args: + # GL replay + ret = subprocess.CompletedProcess(cmd, 0) + if cmd[-1] == self.gl_trace_path: + calls = cmd[2].split('=')[1] + prefix = cmd[3].split('=')[1] + ret.stdout = b'' + for call in calls.split(','): + if call != self.gl_trace_wrong_call: + dump = prefix + call.zfill(10) + '.png' + with open(dump, 'w') as f: + f.write("content") + ret.stdout += bytearray('Wrote ' + dump + '\n', + 'utf-8') + else: + ret.stdout = b'\n' + if cmd[-1] == self.gl_replay_crashes_trace_path: + ret.returncode = 1 + + return ret + elif cmd[2:-1] == get_last_call_args: + # DXGI get_last_call + ret = subprocess.CompletedProcess(cmd, 0) + if cmd[-1] == self.dxgi_last_frame_fails_trace_path: + ret.stdout = b'\n' + else: + ret.stdout = bytearray( + str(int(self.dxgi_trace_last_call) - 50) + + ' IDXGISwapChain4::Present(this = 0x3de3b0, ' + 'SyncInterval = 1, Flags = 0x0) = S_OK\n' + '\n' + + self.dxgi_trace_last_call + + ' IDXGISwapChain4::Present(this = 0x3de3b0, ' + 'SyncInterval = 1, Flags = 0x0) = S_OK\n', + 'utf-8') + return ret + elif cmd[2:3] == replay_retrace_args: + # DXGI replay + ret = subprocess.CompletedProcess(cmd, 0) + if cmd[-1] == self.dxgi_trace_path: + calls = cmd[3].split('=')[1] + prefix = cmd[4].split('=')[1] + ret.stdout = b'' + for call in calls.split(','): + dump = prefix + call.zfill(10) + '.png' + if call != self.dxgi_trace_wrong_call: + with open(dump, 'w') as f: + f.write("content") + ret.stdout += bytearray('Wrote ' + dump + '\n', + 'utf-8') + else: + ret.stdout = b'\n' + if cmd[-1] == self.dxgi_replay_crashes_trace_path: + ret.returncode = 1 + + return ret + else: + raise exceptions.PiglitFatalError( + 'Non treated cmd: {}'.format(cmd)) + + @pytest.fixture(autouse=True) + def setup(self, mocker, tmpdir): + """Setup for TestAPITraceBackend. + + This set ups the basic environment for testing. + """ + + OPTIONS.device_name = 'test-device' + self.apitrace = 'apitrace' + self.eglretrace = 'eglretrace' + self.wine = 'wine' + self.d3dretrace = 'd3dretrace' + self.gl_trace_path = tmpdir.mkdir( + 'db-path').join('glxgears/glxgears-2.trace').strpath + self.gl_last_frame_fails_trace_path = tmpdir.join( + 'db-path', + 'last-frame/fails.trace').strpath + self.gl_replay_crashes_trace_path = tmpdir.join( + 'db-path', + 'replay/fails.trace').strpath + self.gl_trace_calls = '1211,1384' + self.gl_trace_last_call = '1413' + self.gl_trace_wrong_call = '1414' + self.output_dir = tmpdir.mkdir('results').strpath + self.dxgi_trace_path = tmpdir.join( + 'db-path', + 'Wicked-Engine/Tests:Cloth_Physics_Test.trace-dxgi').strpath + self.dxgi_last_frame_fails_trace_path = tmpdir.join( + 'db-path', + 'last-frame/fails.trace-dxgi').strpath + self.dxgi_replay_crashes_trace_path = tmpdir.join( + 'db-path', + 'replay/fails.trace-dxgi').strpath + self.dxgi_trace_calls = '235747,257964' + self.dxgi_trace_last_call = '273345' + self.dxgi_trace_wrong_call = '273346' + self.results_partial_path = path.join('trace', OPTIONS.device_name) + self.m_apitrace_subprocess_run = mocker.patch( + 'framework.replay.backends.apitrace.subprocess.run', + side_effect=self.mock_apitrace_subprocess_run) + self.tmpdir = tmpdir + self.mocker = mocker + mocker.patch.dict('os.environ') + self.env = os.environ + self.env.clear() + + @pytest.mark.raises(exception=exceptions.PiglitFatalError) + def test_init_unsupported_trace(self): + """Tests for the init method. + + Should raise an exception in case of creating with an unsupported trace + format. + + """ + test = backends.apitrace.APITraceBackend('unsupported_trace.gfxr') + + @pytest.mark.parametrize('option, apitrace, eglretrace', [ + (0, '/env/apitrace', '/env/eglretrace'), + (1, '/config/apitrace', '/config/eglretrace'), + (2, 'apitrace', 'eglretrace'), + ]) + def test_dump_gl_options(self, option, apitrace, eglretrace, config): + """Tests for the dump method: basic with options. + + Check basic GL dumps with different configurations. No specific output + directory is provided and leaving to the method to figure out the last + call from the trace file itself. + + """ + self.apitrace = apitrace + self.eglretrace = eglretrace + if option == 0: + self.env['PIGLIT_REPLAY_APITRACE_BINARY'] = self.apitrace + self.env['PIGLIT_REPLAY_EGLRETRACE_BINARY'] = self.eglretrace + elif option == 1: + config.set('replay', 'apitrace_bin', self.apitrace) + config.set('replay', 'eglretrace_bin', self.eglretrace) + calls = self.gl_trace_last_call + trace_path = self.gl_trace_path + test = backends.apitrace.APITraceBackend(trace_path) + assert test.dump() + snapshot_prefix = trace_path + '-' + m_calls = [self.mocker.call( + [self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.eglretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_gl_output(self): + """Tests for the dump method: explicit output directory. + + Check a basic GL dump, specifying the output and leaving for the method + to figure out the last call from the trace file itself. + + """ + calls = self.gl_trace_last_call + trace_path = self.gl_trace_path + test = backends.apitrace.APITraceBackend(trace_path, + output_dir=self.output_dir) + assert test.dump() + snapshot_prefix = path.join(self.output_dir, + path.basename(trace_path) + '-') + m_calls = [self.mocker.call( + [self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.eglretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_gl_calls(self): + """Tests for the dump method: explicit valid calls. + + Check a basic GL dump, specifying valid calls to dump. No specific + output directory is provided. + + """ + calls = self.gl_trace_calls + trace_path = self.gl_trace_path + test = backends.apitrace.APITraceBackend(trace_path, + calls=calls.split(',')) + assert test.dump() + snapshot_prefix = trace_path + '-' + self.m_apitrace_subprocess_run.assert_called_once_with( + [self.eglretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_gl_wrong_call(self): + """Tests for the dump method: explicit invalid call. + + Check a basic GL dump, specifying an invalid call to dump. No specific + output directory is provided. + + """ + calls = self.gl_trace_wrong_call + trace_path = self.gl_trace_path + test = backends.apitrace.APITraceBackend(trace_path, + calls=calls.split(',')) + assert test.dump() + snapshot_prefix = trace_path + '-' + self.m_apitrace_subprocess_run.assert_called_once_with( + [self.eglretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_gl_last_frame_fails(self): + """Tests for the dump method: the call to figure out the last frame fails. + + Check a basic GL dump. The call to figure out the last frame fails. + + """ + calls = '-1' + trace_path = self.gl_last_frame_fails_trace_path + test = backends.apitrace.APITraceBackend(trace_path) + assert test.dump() + snapshot_prefix = trace_path + '-' + m_calls = [self.mocker.call( + [self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.eglretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_gl_replay_crashes(self): + """Tests for the dump method: the replay call crashes. + + Check a basic GL dump. The replay call crashes. + + """ + calls = self.gl_trace_last_call + trace_path = self.gl_replay_crashes_trace_path + test = backends.apitrace.APITraceBackend(trace_path) + assert not test.dump() + snapshot_prefix = trace_path + '-' + m_calls = [self.mocker.call( + [self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.eglretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call.zfill(10) + '.png') + + @pytest.mark.parametrize('option, wine, apitrace, d3dretrace', [ + (0, '/env/wine', '/env/wine/apitrace', '/env/wine/d3dretrace'), + (1, '/config/wine', '/config/wine/apitrace', '/config/wine/d3dretrace'), + (2, 'wine', 'apitrace', 'd3dretrace'), + ]) + def test_dump_dxgi_options(self, option, wine, apitrace, d3dretrace, config): + """Tests for the dump method: basic with options. + + Check basic DXGI dumps with different configurations. No specific output + directory is provided and leaving to the method to figure out the last + call from the trace file itself. + + """ + self.wine = wine + self.apitrace = apitrace + self.d3dretrace = d3dretrace + if option == 0: + self.env['PIGLIT_REPLAY_WINE_BINARY'] = self.wine + self.env['PIGLIT_REPLAY_WINE_APITRACE_BINARY'] = self.apitrace + self.env['PIGLIT_REPLAY_WINE_D3DRETRACE_BINARY'] = self.d3dretrace + elif option == 1: + config.set('replay', 'wine_bin', self.wine) + config.set('replay', 'wine_apitrace_bin', self.apitrace) + config.set('replay', 'wine_d3dretrace_bin', self.d3dretrace) + calls = self.dxgi_trace_last_call + trace_path = self.dxgi_trace_path + test = backends.apitrace.APITraceBackend(trace_path) + assert test.dump() + snapshot_prefix = trace_path + '-' + m_calls = [self.mocker.call( + [self.wine, self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.wine, self.d3dretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_dxgi_output(self): + """Tests for the dump method: explicit output directory. + + Check a basic DXGI dump, specifying the output and leaving for the method + to figure out the last call from the trace file itself. + + """ + calls = self.dxgi_trace_last_call + trace_path = self.dxgi_trace_path + test = backends.apitrace.APITraceBackend(trace_path, + output_dir=self.output_dir) + assert test.dump() + snapshot_prefix = path.join(self.output_dir, + path.basename(trace_path) + '-') + m_calls = [self.mocker.call( + [self.wine, self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.wine, self.d3dretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_dxgi_calls(self): + """Tests for the dump method: explicit valid calls. + + Check a basic DXGI dump, specifying valid calls to dump. No specific + output directory is provided. + + """ + calls = self.dxgi_trace_calls + trace_path = self.dxgi_trace_path + test = backends.apitrace.APITraceBackend(trace_path, + calls=calls.split(',')) + assert test.dump() + snapshot_prefix = trace_path + '-' + self.m_apitrace_subprocess_run.assert_called_once_with( + [self.wine, self.d3dretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_dxgi_wrong_call(self): + """Tests for the dump method: explicit invalid call. + + Check a basic DXGI dump, specifying an invalid call to dump. No + specific output directory is provided. + + """ + calls = self.dxgi_trace_wrong_call + trace_path = self.dxgi_trace_path + test = backends.apitrace.APITraceBackend(trace_path, + calls=calls.split(',')) + assert test.dump() + snapshot_prefix = trace_path + '-' + self.m_apitrace_subprocess_run.assert_called_once_with( + [self.wine, self.d3dretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_dxgi_last_frame_fails(self): + """Tests for the dump method: the call to figure out the last frame fails. + + Check a basic DXGI dump. The call to figure out the last frame fails. + + """ + calls = '-1' + trace_path = self.dxgi_last_frame_fails_trace_path + test = backends.apitrace.APITraceBackend(trace_path) + assert test.dump() + snapshot_prefix = trace_path + '-' + m_calls = [self.mocker.call( + [self.wine, self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.wine, self.d3dretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call.zfill(10) + '.png') + + def test_dump_dxgi_replay_crashes(self): + """Tests for the dump method: the replay call crashes. + + Check a basic DXGI dump. The replay call crashes. + + """ + calls = self.dxgi_trace_last_call + trace_path = self.dxgi_replay_crashes_trace_path + test = backends.apitrace.APITraceBackend(trace_path) + assert not test.dump() + snapshot_prefix = trace_path + '-' + m_calls = [self.mocker.call( + [self.wine, self.apitrace, 'dump', '--calls=frame', trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.wine, self.d3dretrace, '--headless', + '--snapshot=' + calls, + '--snapshot-prefix=' + snapshot_prefix, trace_path], + env=None, stdout=subprocess.PIPE)] + assert self.m_apitrace_subprocess_run.call_count == 2 + self.m_apitrace_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call.zfill(10) + '.png') diff --git a/unittests/framework/replay/backends/test_gfxreconstruct.py b/unittests/framework/replay/backends/test_gfxreconstruct.py new file mode 100644 index 000000000..ae9b92dae --- /dev/null +++ b/unittests/framework/replay/backends/test_gfxreconstruct.py @@ -0,0 +1,374 @@ +# coding=utf-8 +# +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's gfxreconstruct backend.""" + +import pytest + +import os +import subprocess +import textwrap + +from os import path + +from framework import core +from framework import exceptions +from framework.replay import backends +from framework.replay.options import OPTIONS + + +@pytest.yield_fixture +def config(mocker): + conf = mocker.patch('framework.core.PIGLIT_CONFIG', + new_callable=core.PiglitConfig) + conf.add_section('replay') + yield conf + +class TestGFXReconstructBackend(object): + """Tests for the GFXReconstructBackend class.""" + + def mock_gfxreconstruct_subprocess_run(self, cmd, stdout, env=None): + if cmd[0].endswith(self.gfxrecon_info): + # VK get_last_call + ret = subprocess.CompletedProcess(cmd, 0) + if cmd[-1] == self.vk_last_frame_fails_trace_path: + ret.stdout = b'' + else: + ret.stdout = bytearray('Total frames: ' + + str(int(self.vk_trace_last_call) + 1) + + textwrap.dedent(''' + Application info: + Application name: vkcube + Application version: 0 + Engine name: vkcube + Engine version: 0 + Target API version: 4194304 (1.0.0) + + Physical device info: + Device name: TESTING DEVICE + Device ID: 0xabcd + Vendor ID: 0xefgh + Driver version: 83890275 (0x5001063) + API version: 4202627 (1.2.131) + + Device memory allocation info: + Total allocations: 5 + Min allocation size: 1216 + Max allocation size: 540680 + + Pipeline info: + Total graphics pipelines: 1 + Total compute pipelines: 0 + '''), + 'utf-8') + + return ret + elif cmd[0].endswith(self.gfxrecon_replay): + # VK replay + ret = subprocess.CompletedProcess(cmd, 0) + if cmd[-1] != self.vk_replay_crashes_trace_path: + calls = env['VK_SCREENSHOT_FRAMES'] + prefix = env['VK_SCREENSHOT_DIR'] + ret.stdout = b'' + if env['VK_INSTANCE_LAYERS'] == 'VK_LAYER_LUNARG_screenshot': + for call in calls.split(','): + if (call != self.vk_trace_wrong_call and + call != '-1'): + from PIL import Image + dump = path.join(prefix, call + '.ppm') + rgba = 'ff00ffff' + color = [int(rgba[0:2], 16), int(rgba[2:4], 16), + int(rgba[4:6], 16), int(rgba[6:8], 16)] + Image.frombytes('RGBA', (32, 32), + bytes(color * 32 * 32)).save(dump) + ret.stdout += bytearray('Screen Capture file ' + 'is: ' + dump + '\n', + 'utf-8') + ret.stdout += bytearray('35.650065 fps, 0.280504 seconds, ' + '10 frames, 1 loop, framerange 1-10\n', + 'utf-8') + else: + ret.stdout = b'\n' + ret.returncode = 1 + + return ret + else: + raise exceptions.PiglitFatalError( + 'Non treated cmd: {}'.format(cmd)) + + @pytest.fixture(autouse=True) + def setup(self, mocker, tmpdir): + """Setup for TestGFXReconstructBackend. + + This set ups the basic environment for testing. + """ + + OPTIONS.device_name = 'test-device' + self.gfxrecon_info = 'gfxrecon-info' + self.gfxrecon_replay = 'gfxrecon-replay' + self.gfxrecon_replay_extra = '' + self.vk_trace_path = tmpdir.mkdir( + 'db-path').join('KhronosGroup-Vulkan-Tools/vkcube.gfxr').strpath + self.vk_last_frame_fails_trace_path = tmpdir.join( + 'db-path', + 'last-frame/fails.gfxr').strpath + self.vk_replay_crashes_trace_path = tmpdir.join( + 'db-path', + 'replay/fails.gfxr').strpath + self.vk_trace_calls = '2,5' + self.vk_trace_last_call = '9' + self.vk_trace_wrong_call = '10' + self.output_dir = tmpdir.mkdir('results').strpath + self.results_partial_path = path.join('trace', OPTIONS.device_name) + self.m_gfxreconstruct_subprocess_run = mocker.patch( + 'framework.replay.backends.gfxreconstruct.subprocess.run', + side_effect=self.mock_gfxreconstruct_subprocess_run) + self.tmpdir = tmpdir + self.mocker = mocker + mocker.patch.dict('os.environ') + self.env = os.environ + self.env.clear() + + @pytest.mark.raises(exception=exceptions.PiglitFatalError) + def test_init_unsupported_trace(self): + """Tests for the init method. + + Should raise an exception in case of creating with an unsupported trace + format. + + """ + test = backends.gfxreconstruct.GFXReconstructBackend( + 'unsupported_trace.trace') + + @pytest.mark.parametrize('option, gfxrecon_info, gfxrecon_replay', [ + (0, '/env/gfxrecon-info', '/env/gfxrecon-replay'), + (1, '/config/gfxrecon-info', '/config/gfxrecon-replay'), + (2, 'gfxrecon-info', 'gfxrecon-replay'), + ]) + def test_dump_vk_options(self, + option, gfxrecon_info, gfxrecon_replay, config): + """Tests for the dump method: basic with options. + + Check basic VK dumps with different configurations. No specific output + directory is provided and leaving to the method to figure out the last + call from the trace file itself. + + """ + self.gfxrecon_info = gfxrecon_info + self.gfxrecon_replay = gfxrecon_replay + if option == 0: + self.env['PIGLIT_REPLAY_GFXRECON_INFO_BINARY'] = self.gfxrecon_info + self.env[ + 'PIGLIT_REPLAY_GFXRECON_REPLAY_BINARY'] = self.gfxrecon_replay + elif option == 1: + config.set('replay', 'gfxrecon-info_bin', self.gfxrecon_info) + config.set('replay', 'gfxrecon-replay_bin', self.gfxrecon_replay) + calls = self.vk_trace_last_call + trace_path = self.vk_trace_path + test = backends.gfxreconstruct.GFXReconstructBackend(trace_path) + assert test.dump() + snapshot_prefix = trace_path + '-' + env = os.environ.copy() + env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot' + env['VK_SCREENSHOT_FRAMES'] = calls + env['VK_SCREENSHOT_DIR'] = path.dirname(trace_path) + m_calls = [self.mocker.call( + [self.gfxrecon_info, trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.gfxrecon_replay, trace_path], + env=env, stdout=subprocess.PIPE)] + assert self.m_gfxreconstruct_subprocess_run.call_count == 2 + self.m_gfxreconstruct_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call + '.png') + + @pytest.mark.parametrize('option, gfxrecon_replay_extra', [ + (0, '-m remap'), + (1, '-m rebind'), + ]) + def test_dump_vk_replay_extra_args(self, + option, gfxrecon_replay_extra, config): + """Tests for the dump method: basic with replay extra args. + + Check basic VK dumps with different replay extra args. No specific + output directory and other options are provided and it is left to the + method to figure out the last call from the trace file itself. + + """ + if option == 0: + self.env[ + 'PIGLIT_REPLAY_GFXRECON_REPLAY_EXTRA_ARGS' + ] = gfxrecon_replay_extra + elif option == 1: + config.set('replay', 'gfxrecon-replay_extra_args', + gfxrecon_replay_extra) + calls = self.vk_trace_last_call + trace_path = self.vk_trace_path + test = backends.gfxreconstruct.GFXReconstructBackend(trace_path) + assert test.dump() + snapshot_prefix = trace_path + '-' + env = os.environ.copy() + env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot' + env['VK_SCREENSHOT_FRAMES'] = calls + env['VK_SCREENSHOT_DIR'] = path.dirname(trace_path) + m_calls = [self.mocker.call( + [self.gfxrecon_info, trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.gfxrecon_replay] + + gfxrecon_replay_extra.split() + [trace_path], + env=env, stdout=subprocess.PIPE)] + assert self.m_gfxreconstruct_subprocess_run.call_count == 2 + self.m_gfxreconstruct_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call + '.png') + + def test_dump_vk_output(self): + """Tests for the dump method: explicit output directory. + + Check a basic VK dump, specifying the output and leaving for the method + to figure out the last call from the trace file itself. + + """ + calls = self.vk_trace_last_call + trace_path = self.vk_trace_path + test = backends.gfxreconstruct.GFXReconstructBackend( + trace_path, output_dir=self.output_dir) + assert test.dump() + snapshot_prefix = path.join(self.output_dir, + path.basename(trace_path) + '-') + env = os.environ.copy() + env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot' + env['VK_SCREENSHOT_FRAMES'] = calls + env['VK_SCREENSHOT_DIR'] = self.output_dir + m_calls = [self.mocker.call( + [self.gfxrecon_info, trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.gfxrecon_replay, trace_path], + env=env, stdout=subprocess.PIPE)] + assert self.m_gfxreconstruct_subprocess_run.call_count == 2 + self.m_gfxreconstruct_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call + '.png') + + def test_dump_vk_calls(self): + """Tests for the dump method: explicit valid calls. + + Check a basic VK dump, specifying valid calls to dump. No specific + output directory is provided. + + """ + calls = self.vk_trace_calls + trace_path = self.vk_trace_path + test = backends.gfxreconstruct.GFXReconstructBackend( + trace_path, calls=calls.split(',')) + assert test.dump() + snapshot_prefix = trace_path + '-' + env = os.environ.copy() + env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot' + env['VK_SCREENSHOT_FRAMES'] = calls + env['VK_SCREENSHOT_DIR'] = path.dirname(trace_path) + self.m_gfxreconstruct_subprocess_run.assert_called_once_with( + [self.gfxrecon_replay, trace_path], + env=env, stdout=subprocess.PIPE) + for call in calls.split(','): + assert path.exists(snapshot_prefix + call + '.png') + + def test_dump_vk_wrong_call(self): + """Tests for the dump method: explicit invalid call. + + Check a basic VK dump, specifying an invalid call to dump. No specific + output directory is provided. + + """ + calls = self.vk_trace_wrong_call + trace_path = self.vk_trace_path + test = backends.gfxreconstruct.GFXReconstructBackend( + trace_path, calls=calls.split(',')) + assert not test.dump() + snapshot_prefix = trace_path + '-' + env = os.environ.copy() + env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot' + env['VK_SCREENSHOT_FRAMES'] = calls + env['VK_SCREENSHOT_DIR'] = path.dirname(trace_path) + self.m_gfxreconstruct_subprocess_run.assert_called_once_with( + [self.gfxrecon_replay, trace_path], + env=env, stdout=subprocess.PIPE) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call + '.png') + + def test_dump_vk_last_frame_fails(self): + """Tests for the dump method: the call to figure out the last frame fails. + + Check a basic VK dump. The call to figure out the last frame fails. + + """ + calls = '-1' + trace_path = self.vk_last_frame_fails_trace_path + test = backends.gfxreconstruct.GFXReconstructBackend(trace_path) + assert not test.dump() + snapshot_prefix = trace_path + '-' + env = os.environ.copy() + env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot' + env['VK_SCREENSHOT_FRAMES'] = calls + env['VK_SCREENSHOT_DIR'] = path.dirname(trace_path) + m_calls = [self.mocker.call( + [self.gfxrecon_info, trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.gfxrecon_replay, trace_path], + env=env, stdout=subprocess.PIPE)] + assert self.m_gfxreconstruct_subprocess_run.call_count == 2 + self.m_gfxreconstruct_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call + '.png') + + def test_dump_vk_replay_crashes(self): + """Tests for the dump method: the replay call crashes. + + Check a basic VK dump. The replay call crashes. + + """ + calls = self.vk_trace_last_call + trace_path = self.vk_replay_crashes_trace_path + test = backends.gfxreconstruct.GFXReconstructBackend(trace_path) + assert not test.dump() + snapshot_prefix = trace_path + '-' + env = os.environ.copy() + env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot' + env['VK_SCREENSHOT_FRAMES'] = calls + env['VK_SCREENSHOT_DIR'] = path.dirname(trace_path) + m_calls = [self.mocker.call( + [self.gfxrecon_info, trace_path], + stdout=subprocess.PIPE), + self.mocker.call( + [self.gfxrecon_replay, trace_path], + env=env, stdout=subprocess.PIPE)] + assert self.m_gfxreconstruct_subprocess_run.call_count == 2 + self.m_gfxreconstruct_subprocess_run.assert_has_calls(m_calls) + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call + '.png') diff --git a/unittests/framework/replay/backends/test_package.py b/unittests/framework/replay/backends/test_package.py new file mode 100644 index 000000000..8f8e487d5 --- /dev/null +++ b/unittests/framework/replay/backends/test_package.py @@ -0,0 +1,93 @@ +# coding=utf-8 +# +# Copyright (c) 2014, 2016 Intel Corporation +# Copyright © 2019-2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + +""" Tests for replayer's backend package """ + +import pytest + +from framework.replay import backends + +# pylint: disable=no-self-use + + + +# Helpers + + +class TestBackend(): + """A dummy test backend""" + + def __init__(self, trace_path, output_dir=None, calls=[]): + self._trace_path = trace_path + + def dump(self): + return [self._trace_path] + +# Prevent pytest from trying to collect TestBackend as tests: +TestBackend.__test__ = False + +@pytest.yield_fixture +def mock_backend(mocker, backend): + """Add an extra backend for testing.""" + mocker.patch.dict( + backends.DUMPBACKENDS, + {'test_backend': backends.register.Registry( + extensions=['.test_backend'], + backend=backend, + )}) + yield + + +# Tests + + +class TestDump(object): + """Tests for the dump function.""" + + @pytest.mark.parametrize("backend", [ + (TestBackend), + ]) + def test_basic(self, mock_backend): # pylint: disable=unused-argument + """backends.dump: works as expected.""" + p = 'foo.test_backend' + test = backends.dump(p) + assert [p] == test + + @pytest.mark.raises(exception=backends.DumpBackendError) + def test_unknown(self): + """backends.dump(): An error is raised if no backend is registered for an + extension. + """ + backends.dump('foo.test_extension') + + @pytest.mark.raises(exception=backends.DumpBackendNotImplementedError) + @pytest.mark.parametrize("backend", [ + (None), + ]) + def test_notimplemented(self, mock_backend): # pylint: disable=unused-argument + """backends.dump(): An error is raised if a dumper isn't properly + implmented. + """ + backends.dump('foo.test_backend') diff --git a/unittests/framework/replay/backends/test_renderdoc.py b/unittests/framework/replay/backends/test_renderdoc.py new file mode 100644 index 000000000..f78bb944a --- /dev/null +++ b/unittests/framework/replay/backends/test_renderdoc.py @@ -0,0 +1,196 @@ +# coding=utf-8 +# +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's renderdoc backend.""" + +import pytest + +import os +import subprocess + +from os import path + +from framework import core +from framework import exceptions +from framework.replay import backends +from framework.replay.options import OPTIONS + + +@pytest.yield_fixture +def config(mocker): + conf = mocker.patch('framework.core.PIGLIT_CONFIG', + new_callable=core.PiglitConfig) + conf.add_section('replay') + yield conf + +class TestRenderDocBackend(object): + """Tests for the RenderDocBackend class.""" + + def mock_renderdoc_subprocess_run(self, cmd, stdout, env=None): + ret = subprocess.CompletedProcess(cmd, 0) + if len(cmd) > 3: + calls = cmd[3:] + else: + calls = [self.gl_trace_last_call] + if (cmd[1] == self.gl_trace_path and + calls != [self.gl_trace_wrong_call]): + prefix = path.join(cmd[2], path.basename(cmd[1])) + ret.stdout = b'' + for call in calls: + if call != self.gl_trace_wrong_call: + dump = prefix + '-' + call + '.png' + with open(dump, 'w') as f: + f.write("content") + if call == self.gl_trace_last_call: + call_text = 'End of Capture' + else: + call_text = 'glDrawArrays(4)' + ret.stdout += bytearray('Saving image at eventId ' + call + + ': ' + call_text + ' to ' + dump + + '\n', 'utf-8') + else: + ret.stdout = b'' + ret.returncode = 1 + + return ret + + @pytest.fixture(autouse=True) + def setup(self, mocker, tmpdir): + """Setup for TestRenderDocBackend. + + This set ups the basic environment for testing. + """ + + OPTIONS.device_name = 'test-device' + self.renderdoc = 'renderdoc/renderdoc_dump_images.py' + self.gl_trace_path = tmpdir.mkdir( + 'db-path').join('glmark2/desktop.rdc').strpath + self.gl_replay_crashes_trace_path = tmpdir.join( + 'db-path', + 'replay/fails.rdc').strpath + self.gl_trace_calls = '47,332' + self.gl_trace_last_call = '340' + self.gl_trace_wrong_call = '333' + self.output_dir = tmpdir.mkdir('results').strpath + self.results_partial_path = path.join('trace', OPTIONS.device_name) + self.m_renderdoc_subprocess_run = mocker.patch( + 'framework.replay.backends.abstract.subprocess.run', + side_effect=self.mock_renderdoc_subprocess_run) + self.tmpdir = tmpdir + self.mocker = mocker + + @pytest.mark.raises(exception=exceptions.PiglitFatalError) + def test_init_unsupported_trace(self): + """Tests for the init method. + + Should raise an exception in case of creating with an unsupported trace + format. + + """ + test = backends.renderdoc.RenderDocBackend('unsupported_trace.gfxr') + + def test_dump_gl_options(self): + """Tests for the dump method: basic with options. + + Check basic GL dumps with different configurations. No specific output + directory is provided and leaving to the method to figure out the last + call from the trace file itself. + + """ + calls = self.gl_trace_last_call + trace_path = self.gl_trace_path + test = backends.renderdoc.RenderDocBackend(trace_path) + assert test.dump() + snapshot_prefix = trace_path + '-' + self.m_renderdoc_subprocess_run.assert_called_once() + for call in calls.split(','): + assert path.exists(snapshot_prefix + call + '.png') + + def test_dump_gl_output(self): + """Tests for the dump method: explicit output directory. + + Check a basic GL dump, specifying the output and leaving for the method + to figure out the last call from the trace file itself. + + """ + calls = self.gl_trace_last_call + trace_path = self.gl_trace_path + test = backends.renderdoc.RenderDocBackend(trace_path, + output_dir=self.output_dir) + assert test.dump() + snapshot_prefix = path.join(self.output_dir, + path.basename(trace_path) + '-') + self.m_renderdoc_subprocess_run.assert_called_once() + for call in calls.split(','): + assert path.exists(snapshot_prefix + call + '.png') + + def test_dump_gl_calls(self): + """Tests for the dump method: explicit valid calls. + + Check a basic GL dump, specifying valid calls to dump. No specific + output directory is provided. + + """ + calls = self.gl_trace_calls + trace_path = self.gl_trace_path + test = backends.renderdoc.RenderDocBackend(trace_path, + calls=calls.split(',')) + assert test.dump() + snapshot_prefix = trace_path + '-' + self.m_renderdoc_subprocess_run.assert_called_once() + for call in calls.split(','): + assert path.exists(snapshot_prefix + call + '.png') + + def test_dump_gl_wrong_call(self): + """Tests for the dump method: explicit invalid call. + + Check a basic GL dump, specifying an invalid call to dump. No specific + output directory is provided. + + """ + calls = self.gl_trace_wrong_call + trace_path = self.gl_trace_path + test = backends.renderdoc.RenderDocBackend(trace_path, + calls=calls.split(',')) + assert not test.dump() + snapshot_prefix = trace_path + '-' + self.m_renderdoc_subprocess_run.assert_called_once() + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call + '.png') + + def test_dump_gl_replay_crashes(self): + """Tests for the dump method: the replay call crashes. + + Check a basic GL dump. The replay call crashes. + + """ + calls = self.gl_trace_last_call + trace_path = self.gl_replay_crashes_trace_path + test = backends.renderdoc.RenderDocBackend(trace_path) + assert not test.dump() + snapshot_prefix = trace_path + '-' + self.m_renderdoc_subprocess_run.assert_called_once() + for call in calls.split(','): + assert not path.exists(snapshot_prefix + call + '.png') diff --git a/unittests/framework/replay/test_compare_replay.py b/unittests/framework/replay/test_compare_replay.py new file mode 100644 index 000000000..47142ebba --- /dev/null +++ b/unittests/framework/replay/test_compare_replay.py @@ -0,0 +1,313 @@ +# coding=utf-8 +# +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's compare_replay module.""" + +import pytest + +import contextlib +import io +import os + +from os import path + +from framework import exceptions +from framework.replay import backends +from framework.replay import compare_replay +from framework.replay.options import OPTIONS + + +class TestCompareReplay(object): + """Tests for compare_replay methods.""" + + @staticmethod + def _create_dump(trace_path, results_path, calls): + p = path.join(results_path, + path.basename(trace_path) + '-' + str(calls) + '.png') + os.makedirs(path.dirname(p), exist_ok=True) + with open(p, 'w') as f: + f.write('content') + + @staticmethod + def mock_backends_dump(trace_path, results_path, calls): + if trace_path.endswith('KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr'): + TestCompareReplay._create_dump(trace_path, results_path, 99) + + return True + elif trace_path.endswith('pathfinder/demo.trace'): + TestCompareReplay._create_dump(trace_path, results_path, 78) + + return True + elif trace_path.endswith('Wicked-Engine/Tests:Cloth_Physics_Test.trace-dxgi'): + return False + elif trace_path.endswith('unimplemented/backend.vktrace'): + raise backends.DumpBackendNotImplementedError( + 'DumpBackend for "vktrace" is not implemented') + elif trace_path.endswith('unexisting/back.end'): + raise backends.DumpBackendError( + 'No module supports file extensions "end"') + else: + raise exceptions.PiglitFatalError( + 'Non treated trace path: {}'.format(trace_path)) + + @staticmethod + def mock_hexdigest_from_image(image_file): + if image_file.endswith( + 'KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr-99.png'): + return '917cbbf4f09dd62ea26d247a1c70c16e' + elif image_file.endswith( + 'pathfinder/demo.trace-78.png'): + return 'e624d76c70cc3c532f4f54439e13659a' + else: + raise exceptions.PiglitFatalError( + 'Non treated image file: {}'.format(image_file)) + + @staticmethod + def mock_qty_load_yaml(yaml_file): + if yaml_file == 'empty.yml': + return {} + elif yaml_file == 'no-device.yml': + return {"traces": + [{"path": "glmark2/desktop-blur-radius=5:effect=blur:passes=1:separable=true:windows=4.rdc", + "expectations": [{"device": "gl-vmware-llvmpipe", + "checksum": "8867f3a41f180626d0d4b7661ff5c0f4"}]},]} + elif yaml_file == 'one-trace.yml': + return {"traces": + [{"path": "KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr", + "expectations": [{"device": OPTIONS.device_name, + "checksum": "917cbbf4f09dd62ea26d247a1c70c16e"}]},]} + elif yaml_file == 'two-traces.yml': + return {"traces": + [{"path": "pathfinder/demo.trace", + "expectations": [{"device": OPTIONS.device_name, + "checksum": "e624d76c70cc3c532f4f54439e13659a"}]}, + {"path": "KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr", + "expectations": [{"device": OPTIONS.device_name, + "checksum": "917cbbf4f09dd62ea26d247a1c70c16e"}]},]} + else: + raise exceptions.PiglitFatalError( + 'Non treated YAML file: {}'.format(yaml_file)) + + @pytest.fixture(autouse=True) + def setup(self, mocker, tmpdir): + """Setup for TestCompareReplay. + + This create the basic environment for testing. + """ + + OPTIONS.device_name = 'test-device' + OPTIONS.db_path = tmpdir.mkdir('db-path').strpath + OPTIONS.results_path = tmpdir.mkdir('results').strpath + self.trace_path = 'KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr' + self.exp_checksum = '917cbbf4f09dd62ea26d247a1c70c16e' + self.results_partial_path = path.join('results/trace', + OPTIONS.device_name) + self.m_qty_load_yaml = mocker.patch( + 'framework.replay.compare_replay.qty.load_yaml', + side_effect=TestCompareReplay.mock_qty_load_yaml) + self.m_ensure_file = mocker.patch( + 'framework.replay.compare_replay.ensure_file', + return_value=None) + self.m_backends_dump = mocker.patch( + 'framework.replay.compare_replay.backends.dump', + side_effect=TestCompareReplay.mock_backends_dump) + self.m_hexdigest_from_image = mocker.patch( + 'framework.replay.compare_replay.hexdigest_from_image', + side_effect=TestCompareReplay.mock_hexdigest_from_image) + self.tmpdir = tmpdir + + def test_from_yaml_empty(self): + """compare_replay.from_yaml: compare using an empty YAML file""" + + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.from_yaml('empty.yml') + is compare_replay.Result.MATCH) + self.m_qty_load_yaml.assert_called_once() + s = f.getvalue() + assert s == '' + + def test_from_yaml_no_device(self): + """compare_replay.from_yaml: compare using a YAML without expectations for the used device""" + + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.from_yaml('no-device.yml') + is compare_replay.Result.MATCH) + self.m_qty_load_yaml.assert_called_once() + s = f.getvalue() + assert s == '' + + def test_from_yaml_one_trace(self): + """compare_replay.from_yaml: compare using a YAML with just one expectation for the used device""" + + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.from_yaml('one-trace.yml') + is compare_replay.Result.MATCH) + self.m_qty_load_yaml.assert_called_once() + self.m_ensure_file.assert_called_once() + self.m_backends_dump.assert_called_once() + self.m_hexdigest_from_image.assert_called_once() + assert not self.tmpdir.join(self.results_partial_path, + self.trace_path + '-99.png').check() + assert not self.tmpdir.join(self.results_partial_path, + path.dirname(self.trace_path), + self.exp_checksum + '.png').check() + s = f.getvalue() + assert s == ('[check_image]\n' + ' actual: ' + self.exp_checksum + '\n' + ' expected: ' + self.exp_checksum + '\n' + '[check_image] Images match for:\n' + ' ' + self.trace_path + '\n' + '\n') + + def test_from_yaml_two_traces(self): + """compare_replay.from_yaml: compare using a YAML with more than one expectation for the used device""" + + second_trace_path = 'pathfinder/demo.trace' + second_exp_checksum = 'e624d76c70cc3c532f4f54439e13659a' + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.from_yaml('two-traces.yml') + is compare_replay.Result.MATCH) + self.m_qty_load_yaml.assert_called_once() + assert self.m_ensure_file.call_count == 2 + assert self.m_backends_dump.call_count == 2 + assert self.m_hexdigest_from_image.call_count == 2 + assert not self.tmpdir.join(self.results_partial_path, + second_trace_path + '-78.png').check() + assert not self.tmpdir.join(self.results_partial_path, + path.dirname(second_trace_path), + second_exp_checksum + '.png').check() + assert not self.tmpdir.join(self.results_partial_path, + self.trace_path + '-99.png').check() + assert not self.tmpdir.join(self.results_partial_path, + path.dirname(self.trace_path), + self.exp_checksum + '.png').check() + s = f.getvalue() + assert s == ('[check_image]\n' + ' actual: ' + second_exp_checksum + '\n' + ' expected: ' + second_exp_checksum + '\n' + '[check_image] Images match for:\n' + ' ' + second_trace_path + '\n' + '\n' + '[check_image]\n' + ' actual: ' + self.exp_checksum + '\n' + ' expected: ' + self.exp_checksum + '\n' + '[check_image] Images match for:\n' + ' ' + self.trace_path + '\n' + '\n') + + def test_trace_success(self): + """compare_replay.trace: compare a trace successfully""" + + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.trace(self.trace_path, self.exp_checksum) + is compare_replay.Result.MATCH) + self.m_qty_load_yaml.assert_not_called() + self.m_ensure_file.assert_called_once() + self.m_backends_dump.assert_called_once() + self.m_hexdigest_from_image.assert_called_once() + assert not self.tmpdir.join(self.results_partial_path, + self.trace_path + '-99.png').check() + assert not self.tmpdir.join(self.results_partial_path, + path.dirname(self.trace_path), + self.exp_checksum + '.png').check() + s = f.getvalue() + assert s.endswith('PIGLIT: {"result": "pass"}\n') + + def test_trace_success_keep_image(self): + """compare_replay.trace: compare a trace successfully and set the option to keep the dumped image""" + + OPTIONS.keep_image = True + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.trace(self.trace_path, self.exp_checksum) + is compare_replay.Result.MATCH) + self.m_qty_load_yaml.assert_not_called() + self.m_ensure_file.assert_called_once() + self.m_backends_dump.assert_called_once() + self.m_hexdigest_from_image.assert_called_once() + assert not self.tmpdir.join(self.results_partial_path, + self.trace_path + '-99.png').check() + assert self.tmpdir.join(self.results_partial_path, + path.dirname(self.trace_path), + self.exp_checksum + '.png').check() + s = f.getvalue() + assert s.endswith('PIGLIT: {"result": "pass"}\n') + + def test_trace_fail(self): + """compare_replay.trace: fail comparing a trace""" + + wrong_checksum = '917cbbf4f09dd62ea26d247a1c70c16f' + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.trace(self.trace_path, wrong_checksum) + is compare_replay.Result.DIFFER) + self.m_qty_load_yaml.assert_not_called() + self.m_ensure_file.assert_called_once() + self.m_backends_dump.assert_called_once() + self.m_hexdigest_from_image.assert_called_once() + assert not self.tmpdir.join(self.results_partial_path, + self.trace_path + '-99.png').check() + assert self.tmpdir.join(self.results_partial_path, + path.dirname(self.trace_path), + self.exp_checksum + '.png').check() + s = f.getvalue() + assert s.endswith('PIGLIT: ' + '{"images": [{' + '"image_desc": "' + self.trace_path + '", ' + '"image_ref": "' + wrong_checksum + '.png", ' + '"image_render": "' + + self.tmpdir.join(self.results_partial_path, + path.dirname(self.trace_path), + self.exp_checksum + + '.png').strpath + + '"}], "result": "fail"}\n') + + @pytest.mark.parametrize('trace_path', [ + ('Wicked-Engine/Tests:Cloth_Physics_Test.trace-dxgi'), + ('unimplemented/backend.vktrace'), + ('unexisting/back.end'), + ]) + def test_trace_dump_crash(self, trace_path): + """compare_replay.trace: dump crashes or fails comparing a trace""" + + third_exp_checksum = '6b6d27df609b8d086cc3335e6d103581' + f = io.StringIO() + with contextlib.redirect_stdout(f): + assert (compare_replay.trace(trace_path, third_exp_checksum) + is compare_replay.Result.FAILURE) + self.m_qty_load_yaml.assert_not_called() + self.m_ensure_file.assert_called_once() + self.m_backends_dump.assert_called_once() + self.m_hexdigest_from_image.assert_not_called() + assert not self.tmpdir.join(self.results_partial_path, + path.dirname(trace_path), + third_exp_checksum + '.png').check() + s = f.getvalue() + assert s.endswith('PIGLIT: {"result": "crash"}\n') diff --git a/unittests/framework/replay/test_download_utils.py b/unittests/framework/replay/test_download_utils.py new file mode 100644 index 000000000..6170ac0db --- /dev/null +++ b/unittests/framework/replay/test_download_utils.py @@ -0,0 +1,110 @@ +# coding=utf-8 +# +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's download_utils module.""" + +import pytest + +import os +import requests +import requests_mock + +from os import path + +from framework import exceptions +from framework.replay import download_utils +from framework.replay.options import OPTIONS + + +class TestDownloadUtils(object): + """Tests for download_utils methods.""" + + @pytest.fixture(autouse=True) + def setup(self, requests_mock, tmpdir): + self.url = 'https://unittest.piglit.org/' + self.trace_path = 'KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr' + self.full_url = self.url + self.trace_path + self.trace_file = tmpdir.join(self.trace_path) + OPTIONS.set_download_url(self.url) + OPTIONS.download['force'] = False + OPTIONS.db_path = tmpdir.strpath + requests_mock.get(self.full_url, text='remote') + + @staticmethod + def check_same_file(path_local, expected_content, expected_mtime=None): + assert path_local.read() == expected_content + if expected_mtime is not None: + m = path_local.mtime() + assert m == expected_mtime + + def test_ensure_file_exists(self): + """download_utils.ensure_file: Check an existing file doesn't get overwritten""" + + os.makedirs(path.dirname(self.trace_file), exist_ok=True) + self.trace_file.write("local") + m = self.trace_file.mtime() + download_utils.ensure_file(self.trace_path) + TestDownloadUtils.check_same_file(self.trace_file, "local", m) + + def test_ensure_file_not_exists(self): + """download_utils.ensure_file: Check a non existing file gets downloaded""" + + assert not self.trace_file.check() + download_utils.ensure_file(self.trace_path) + TestDownloadUtils.check_same_file(self.trace_file, "remote") + + def test_ensure_file_exists_force_download(self): + """download_utils.ensure_file: Check an existing file gets overwritten when forced""" + + OPTIONS.download['force'] = True + os.makedirs(path.dirname(self.trace_file), exist_ok=True) + self.trace_file.write("local") + m = self.trace_file.mtime() + download_utils.ensure_file(self.trace_path) + TestDownloadUtils.check_same_file(self.trace_file, "remote") + + @pytest.mark.raises(exception=exceptions.PiglitFatalError) + def test_ensure_file_not_exists_no_url(self): + """download_utils.ensure_file: Check an exception raises when not passing an URL for a non existing file""" + + OPTIONS.set_download_url("") + assert not self.trace_file.check() + download_utils.ensure_file(self.trace_path) + + @pytest.mark.raises(exception=requests.exceptions.HTTPError) + def test_ensure_file_not_exists_404(self, requests_mock): + """download_utils.ensure_file: Check an exception raises when an URL returns a 404""" + + requests_mock.get(self.full_url, text='Not Found', status_code=404) + assert not self.trace_file.check() + download_utils.ensure_file(self.trace_path) + + @pytest.mark.raises(exception=requests.exceptions.ConnectTimeout) + def test_ensure_file_not_exists_timeout(self, requests_mock): + """download_utils.ensure_file: Check an exception raises when an URL returns a Connect Timeout""" + + requests_mock.get(self.full_url, exc=requests.exceptions.ConnectTimeout) + assert not self.trace_file.check() + download_utils.ensure_file(self.trace_path) diff --git a/unittests/framework/replay/test_image_checksum.py b/unittests/framework/replay/test_image_checksum.py new file mode 100644 index 000000000..2c9f2edbb --- /dev/null +++ b/unittests/framework/replay/test_image_checksum.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's image_checksum module.""" + +import pytest + +import PIL + +from framework.replay import image_checksum + + +@pytest.mark.raises(exception=OSError) +def test_hexdigest_from_image_UnidentifiedImageError(tmpdir): + """image_checksum.hexdigest_from_image: Check an exception raises when a non image is passed""" + + f = tmpdir.join("image.png") + f.write("content") + image_checksum.hexdigest_from_image(f.strpath) diff --git a/unittests/framework/replay/test_options.py b/unittests/framework/replay/test_options.py new file mode 100644 index 000000000..1102e29a2 --- /dev/null +++ b/unittests/framework/replay/test_options.py @@ -0,0 +1,62 @@ +# coding=utf-8 +# +# Copyright (c) 2015-2016, 2019 Intel Corporation +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's options module.""" + +import pytest + +from urllib.parse import urlparse + +from framework.replay import options + +# pylint: disable=protected-access +# pylint: disable=invalid-name +# pylint: disable=no-self-use + + +def test_options_clear(): + """options.Options.clear(): resests options values to init state.""" + baseline = options._Options() + + test = options._Options() + test.device_name = "dummy-device" + test.keep_image = True + test.clear() + + assert list(iter(baseline)) == list(iter(test)) + + +@pytest.mark.parametrize("url, expected", [ + ("this://is.a.valid/url", urlparse("this://is.a.valid/url")), + ("this://is.not.a[/valid/url", None), +]) +def test_options_set_download_url(url, expected): + """options.Options.set_download_url(): safely sets the parsed download url.""" + o = options._Options() + + o.set_download_url(url) + + assert o.download["url"] == expected diff --git a/unittests/framework/replay/test_query_traces_yaml.py b/unittests/framework/replay/test_query_traces_yaml.py new file mode 100644 index 000000000..e131f4721 --- /dev/null +++ b/unittests/framework/replay/test_query_traces_yaml.py @@ -0,0 +1,202 @@ +# coding=utf-8 +# +# Copyright © 2020 Valve Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# SPDX-License-Identifier: MIT + + +"""Tests for replayer's query_traces_yaml module.""" + +import pytest + +from framework import exceptions + +from framework.replay import query_traces_yaml as qty + + +YAML_DATA = [["", {}], + [""" +- First +- Second +""", + ["First", "Second"]], + [""" +a: 1 +b: + c: 2 +""", + {"a": 1, "b": {"c": 2}}], +] + +TRACES_DATA = {"traces": + [{"path": "glmark2/desktop-blur-radius=5:effect=blur:passes=1:separable=true:windows=4.rdc", + "expectations": [{"device": "gl-vmware-llvmpipe", + "checksum": "8867f3a41f180626d0d4b7661ff5c0f4"}]}, + {"path": "glxgears/glxgears-2.trace", + "expectations": [{"device": "gl-vmware-llvmpipe", + "checksum": "f8eba0fec6e3e0af9cb09844bc73bdc7"}, + {"device": "gl-virgl", + "checksum": "f8eba0fec6e3e0af9cb09844bc73bdc7"}]}, + {"path": "pathfinder/demo.trace", + "expectations": [{"device": "gl-vmware-llvmpipe", + "checksum": "e624d76c70cc3c532f4f54439e13659a"}]}, + {"path": "KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr", + "expectations": [{"device": "vk-amd-polaris10", + "checksum": "917cbbf4f09dd62ea26d247a1c70c16e"}]},]} + +@pytest.mark.raises(exception=exceptions.PiglitFatalError) +def test_load_yaml_PiglitFatalError(): + """query_traces_yaml.load_yaml: Raise PiglitFatalError on invalid YAML""" + y = qty.load_yaml("*** this is not YAML ***") + + +def test_load_yaml_basic(): + """query_traces_yaml.load_yaml: Load some basic YAML documents""" + + for i in YAML_DATA: + y = qty.load_yaml(i[0]) + + assert i[1] == y + + +@pytest.mark.raises(exception=TypeError) +@pytest.mark.parametrize("trace", [ + ([]), + ({"expectations": "one"}), + ({"expectations": ["one", "another"]}), +]) +def test_trace_devices_TypeError(trace): + """query_traces_yaml.trace_devicess: Raise TypeError on invalid trace""" + d = qty.trace_devices(trace) + + +@pytest.mark.parametrize("trace, expected", [ + ({}, []), + ({"one": 1}, []), + ({"expectations": [{"one": 1}]}, []), + ({"expectations": [{"device": "gl-vmware-lvmpipe"}]}, + ["gl-vmware-lvmpipe"]), + ({"expectations": [{"device": "gl-vmware-lvmpipe"}, + {"device": "vk-intel-anv", "checksum": "a checksum"}]}, + ["gl-vmware-lvmpipe", "vk-intel-anv"]), +]) +def test_trace_devices_basic(trace, expected): + """query_traces_yaml.trace_devicess: Get devices from some basic traces""" + assert expected == qty.trace_devices(trace) + + +@pytest.mark.raises(exception=TypeError) +@pytest.mark.parametrize("trace, device", [ + ([], None), + ({"expectations": "one"}, None), + ({"expectations": ["one", "another"]}, None), +]) +def test_trace_checksum_TypeError(trace, device): + """query_traces_yaml.trace_checksum: Raise TypeError on invalid trace""" + c = qty.trace_checksum(trace, device) + + +@pytest.mark.parametrize("trace, device, expected", [ + ({}, None, ''), + ({"one": 1}, None, ''), + ({"expectations": [{"one": 1}]}, None, ''), + ({"expectations": [{"device": "gl-vmware-lvmpipe"}]}, "gl-vmware-lvmpipe", ''), + ({"expectations": [{"device": "gl-vmware-lvmpipe", "checksum": "a checksum"}]}, + "gl-vmware-lvmpipe", "a checksum"), + ({"expectations": [{"device": "gl-vmware-lvmpipe", "checksum": "a checksum"}, + {"device": "vk-intel-anv", "checksum": "another checksum"}]}, + "vk-intel-anv", "another checksum"), + ({"expectations": [{"device": "gl-vmware-lvmpipe", "checksum": "a checksum"}, + {"device": "vk-intel-anv", "checksum": "another checksum"}, + {"device": "vk-intel-anv", "checksum": "yet another checksum"}]}, + "vk-intel-anv", "another checksum"), +]) +def test_trace_checksum_basic(trace, device, expected): + """query_traces_yaml.trace_checksum: Get checksum from some basic traces""" + assert expected == qty.trace_checksum(trace, device) + + +@pytest.mark.raises(exception=TypeError) +@pytest.mark.parametrize("yaml", [ + (8), + ({"traces-db": "one"}), +]) +def test_download_url_TypeError(yaml): + """query_traces_yaml.download_url: Raise TypeError on invalid YAML""" + u = qty.download_url(yaml) + + +@pytest.mark.parametrize("yaml, expected", [ + ({}, None), + ({"traces-db": {"one": 1}}, None), + ({"traces-db": {"download-url": "an url"}}, "an url"), + ({"traces-db": {"one": 1, "download-url": "an url"}}, "an url"), +]) +def test_download_url_basic(yaml, expected): + """query_yamls_yaml.download_url: Get download url from some basic YAML with a trace-db entry""" + assert expected == qty.download_url(yaml) + + +@pytest.mark.raises(exception=AttributeError) +@pytest.mark.parametrize("yaml, ext, device, checksum", [ + (8, ".trace", "gl-vmware-llvmpipe", True), + (TRACES_DATA, {}, None, False), +]) +def test_traces_AttributeError(yaml, ext, device, checksum): + """query_traces_yaml.traces: Raise AttributeError on invalid parameters""" + t = list(qty.traces(yaml, ext, device, checksum)) + + +@pytest.mark.raises(exception=TypeError) +@pytest.mark.parametrize("yaml, ext, device, checksum", [ + ({"traces": "one"}, ".trace", "gl-vmware-llvmpipe", True), + ({"traces": ["one"]}, ".trace", "gl-vmware-llvmpipe", True), + ({"traces": [{"path": 8}]}, ".trace", "gl-vmware-llvmpipe", True), +]) +def test_traces_TypeError(yaml, ext, device, checksum): + """query_traces_yaml.traces: Raise TypeError on invalid YAML""" + t = list(qty.traces(yaml, ext, device, checksum)) + + +@pytest.mark.parametrize("yaml, ext, device, checksum, expected", [ + ({}, ".trace", "gl-vmware-llvmpipe", True, []), + ({"traces": [{}]}, ".trace", "gl-vmware-llvmpipe", True, []), + (TRACES_DATA, "", "gl-virgl", False, []), + (TRACES_DATA, ".rdc", "gl-virgl", False, []), + (TRACES_DATA, ".trace", "gl-virgl", False, + [{"path": "glxgears/glxgears-2.trace"}]), + (TRACES_DATA, ".rdc,.trace", "gl-vmware-llvmpipe", False, + [{"path": "glmark2/desktop-blur-radius=5:effect=blur:passes=1:separable=true:windows=4.rdc"}, + {"path": "glxgears/glxgears-2.trace"}, + {"path": "pathfinder/demo.trace"}]), + (TRACES_DATA, ".trace", None, False, + [{"path": "glxgears/glxgears-2.trace"}, + {"path": "pathfinder/demo.trace"}]), + (TRACES_DATA, ".gfxr", "vk-amd-polaris10", True, + [{"checksum": "917cbbf4f09dd62ea26d247a1c70c16e", + "path": "KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr"}]), + (TRACES_DATA, ".gfxr", None, True, + [{"checksum": "", + "path": "KhronosGroup-Vulkan-Tools/amd/polaris10/vkcube.gfxr"}]), +]) +def test_traces_basic(yaml, ext, device, checksum, expected): + """query_traces_yaml.traces: Get traces lists from some basic yamls""" + assert expected == list(qty.traces(yaml, ext, device, checksum)) |