mirror of
https://gitee.com/openharmony/third_party_mesa3d
synced 2024-11-23 23:41:13 +00:00
ci: remove all tracie remains
Signed-off-by: Andres Gomez <agomez@igalia.com> Reviewed-by: Tomeu Vizoso <tomeu.vizoso@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/6388>
This commit is contained in:
parent
54bdec63ef
commit
050241d81a
@ -27,9 +27,6 @@ cp -Rp .gitlab-ci/bare-metal install/
|
||||
cp -Rp .gitlab-ci/deqp* install/
|
||||
cp -Rp .gitlab-ci/piglit install/
|
||||
cp -Rp .gitlab-ci/traces*.yml install/
|
||||
cp -Rp .gitlab-ci/tracie install/
|
||||
cp -Rp .gitlab-ci/tracie-runner-gl.sh install/
|
||||
cp -Rp .gitlab-ci/tracie-runner-vk.sh install/
|
||||
cp -Rp .gitlab-ci/fossils.yml install/
|
||||
cp -Rp .gitlab-ci/fossils install/
|
||||
cp -Rp .gitlab-ci/fossilize-runner.sh install/
|
||||
|
@ -1,57 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
INSTALL="$(pwd)/install"
|
||||
|
||||
# Set up the driver environment.
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$(pwd)/install/lib/"
|
||||
|
||||
# Set environment for renderdoc libraries.
|
||||
export PYTHONPATH="$PYTHONPATH:/renderdoc/build/lib"
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/renderdoc/build/lib"
|
||||
|
||||
# Set environment for the waffle library.
|
||||
export LD_LIBRARY_PATH="/waffle/build/lib:$LD_LIBRARY_PATH"
|
||||
|
||||
# Set environment for apitrace executable.
|
||||
export PATH="/apitrace/build:$PATH"
|
||||
|
||||
# Set environment for wflinfo executable.
|
||||
export PATH="/waffle/build/bin:$PATH"
|
||||
|
||||
# Use the surfaceless EGL platform.
|
||||
export EGL_PLATFORM="surfaceless"
|
||||
export DISPLAY=
|
||||
export WAFFLE_PLATFORM="surfaceless_egl"
|
||||
|
||||
# Our rootfs may not have "less", which apitrace uses during apitrace dump
|
||||
export PAGER=cat
|
||||
|
||||
RESULTS=`pwd`/results
|
||||
mkdir -p $RESULTS
|
||||
|
||||
# Perform a self-test to ensure tracie is working properly.
|
||||
if [ -z "$TRACIE_NO_UNIT_TESTS" ]; then
|
||||
python3 -m pytest -v --pyargs $INSTALL/tracie/tests/test.py
|
||||
fi
|
||||
|
||||
if [ "$GALLIUM_DRIVER" = "virpipe" ]; then
|
||||
# tracie is to use virpipe, and virgl_test_server llvmpipe
|
||||
export GALLIUM_DRIVER="$GALLIUM_DRIVER"
|
||||
|
||||
GALLIUM_DRIVER=llvmpipe \
|
||||
GALLIVM_PERF="nopt,no_filter_hacks" \
|
||||
VTEST_USE_EGL_SURFACELESS=1 \
|
||||
VTEST_USE_GLES=1 \
|
||||
virgl_test_server >$RESULTS/vtest-log.txt 2>&1 &
|
||||
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# Sanity check to ensure that our environment is sufficient to make our tests
|
||||
# run against the Mesa built by CI, rather than any installed distro version.
|
||||
MESA_VERSION=$(cat "$INSTALL/VERSION" | sed 's/\./\\./g')
|
||||
wflinfo --platform surfaceless_egl --api gles2 | grep "Mesa $MESA_VERSION\(\s\|$\)"
|
||||
|
||||
python3 "$INSTALL/tracie/tracie.py" --file "$INSTALL/traces-$DRIVER_NAME.yml" --device-name "$DEVICE_NAME"
|
@ -1,37 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
INSTALL="$(pwd)/install"
|
||||
|
||||
# Set the Vulkan driver to use.
|
||||
export VK_ICD_FILENAMES="$(pwd)/install/share/vulkan/icd.d/${VK_DRIVER}_icd.x86_64.json"
|
||||
|
||||
# Set environment for VulkanTools' VK_LAYER_LUNARG_screenshot layer.
|
||||
export VK_LAYER_PATH="$VK_LAYER_PATH:/VulkanTools/build/etc/vulkan/explicit_layer.d"
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/VulkanTools/build/lib"
|
||||
|
||||
# Set environment for Wine
|
||||
export WINEDEBUG="-all"
|
||||
export WINEPREFIX="/dxvk-wine64"
|
||||
export WINEESYNC=1
|
||||
|
||||
# Set environment for DXVK
|
||||
export DXVK_LOG_LEVEL="none"
|
||||
export DXVK_STATE_CACHE=0
|
||||
|
||||
# Perform a self-test to ensure tracie is working properly.
|
||||
python3 -m pytest -v --pyargs $INSTALL/tracie/tests/test.py
|
||||
|
||||
# Sanity check to ensure that our environment is sufficient to make our tests
|
||||
# run against the Mesa built by CI, rather than any installed distro version.
|
||||
MESA_VERSION=$(cat "$INSTALL/VERSION" | sed 's/\./\\./g')
|
||||
vulkaninfo | grep "Mesa $MESA_VERSION\(\s\|$\)"
|
||||
|
||||
# Run gfxreconstruct traces against the host's running X server (xvfb
|
||||
# doesn't have DRI3 support).
|
||||
# Set the DISPLAY env variable in each gitlab-runner's configuration
|
||||
# file:
|
||||
# https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section
|
||||
PATH="/gfxreconstruct/build/bin:$PATH" \
|
||||
python3 "$INSTALL/tracie/tracie.py" --file "$INSTALL/traces-$DRIVER_NAME.yml" --device-name "$DEVICE_NAME"
|
@ -1,159 +0,0 @@
|
||||
Tracie - Mesa Traces Continuous Integration System
|
||||
==================================================
|
||||
|
||||
Home of the Mesa trace testing effort.
|
||||
|
||||
### Traces definition file
|
||||
|
||||
The trace definition file contains information about the traces to run along
|
||||
with their expected image checksums on each device, and optionally from where to
|
||||
download them. An example:
|
||||
|
||||
```yaml
|
||||
traces-db:
|
||||
download-url: https://minio-packet.freedesktop.org/mesa-tracie-public/
|
||||
|
||||
traces:
|
||||
- path: glmark2/jellyfish.rdc
|
||||
expectations:
|
||||
- device: gl-intel-0x3185
|
||||
checksum: 58359ea4caf6ad44c6b65526881bbd17
|
||||
- device: gl-vmware-llvmpipe
|
||||
checksum: d82267c25a0decdad7b563c56bb81106
|
||||
- path: supertuxkart/supertuxkart-antediluvian-abyss.rdc
|
||||
expectations:
|
||||
- device: gl-intel-0x3185
|
||||
checksum: ff827f7eb069afd87cc305a422cba939
|
||||
```
|
||||
|
||||
The `traces-db` entry can be absent, in which case it is assumed that
|
||||
the traces can be found in the `CWD/traces-db` directory.
|
||||
|
||||
Traces that don't have an expectation for the current device are skipped
|
||||
during trace replay.
|
||||
|
||||
Adding a new trace to the list involves commiting the trace to the git repo and
|
||||
adding an entry to the `traces` list. The reference checksums can be calculated
|
||||
with the [image_checksum.py](.gitlab-ci/tracie/image_checksum.py) script.
|
||||
Alternatively, an arbitrary checksum can be used, and during replay (see below)
|
||||
the scripts will report the mismatch and expected checksum.
|
||||
|
||||
### Trace-db download urls
|
||||
|
||||
The trace-db:download-url property contains an HTTPS url from which traces can
|
||||
be downloaded, by appending traces:path properties to it.
|
||||
|
||||
### Enabling trace testing on a new device
|
||||
|
||||
To enable trace testing on a new device:
|
||||
|
||||
1. Create a new job in .gitlab-ci.yml. The job will need to be tagged
|
||||
to run on runners with the appropriate hardware.
|
||||
|
||||
1. If you mean to test GL traces, use the `.traces-test-gl`
|
||||
template jobs as a base, and make sure you set a unique value for the
|
||||
`DEVICE_NAME` variable and the name of the Mesa driver as `DRIVER_NAME`:
|
||||
|
||||
```yaml
|
||||
my-hardware-gl-traces:
|
||||
extends: .traces-test-gl
|
||||
variables:
|
||||
DEVICE_NAME: "gl-myhardware"
|
||||
DRIVER_NAME: "mydriver"
|
||||
```
|
||||
|
||||
2. If you mean to test Vulkan traces, use the `.traces-test-vk`
|
||||
template jobs as a base, set the `VK_DRIVER` variable, and make
|
||||
sure you set a unique value for the `DEVICE_NAME` variable:
|
||||
|
||||
```yaml
|
||||
my-hardware-vk-traces:
|
||||
extends: .traces-test-vk
|
||||
variables:
|
||||
VK_DRIVER: "radeon"
|
||||
DEVICE_NAME: "vk-myhardware"
|
||||
DRIVER_NAME: "radv"
|
||||
```
|
||||
|
||||
2. Update the .gitlab-ci/traces-$DRIVER_NAME.yml file with expectations for
|
||||
the new device. Ensure that the device name used in the expectations
|
||||
matches the one set in the job. For more information, and tips about how to
|
||||
calculate the checksums, see the section describing the trace definition
|
||||
files.
|
||||
|
||||
### Trace files
|
||||
|
||||
Tracie supports renderdoc (.rdc), apitrace (.trace) and gfxreconstruct
|
||||
(.gfxr) files. Trace files need to have the correct extension so that
|
||||
tracie can detect them properly.
|
||||
|
||||
The trace files that are contained in public traces-db repositories must be
|
||||
legally redistributable. This is typically true for FOSS games and
|
||||
applications. Traces for proprietary games and application are typically not
|
||||
redistributable, unless specific redistribution rights have been granted by the
|
||||
publisher.
|
||||
|
||||
Trace files in a given repository are expected to be immutable once committed
|
||||
for the first time, so any changes need to be accompanied by a change in the
|
||||
file name (eg. by appending a _v2 suffix to the file).
|
||||
|
||||
### Replaying traces
|
||||
|
||||
Mesa traces CI uses a set of scripts to replay traces and check the output
|
||||
against reference checksums.
|
||||
|
||||
The high level script [tracie.py](.gitlab-ci/tracie/tracie.py) accepts
|
||||
a traces definition file and the name of the device to be tested:
|
||||
|
||||
tracie.py --file .gitlab-ci/traces-llvmpipe.yml --device-name gl-vmware-llvmpipe
|
||||
|
||||
tracie.py copies the produced artifacts to the `$CI_PROJECT_DIR/result`
|
||||
directory. By default, created images from traces are only stored in case of a
|
||||
checksum mismatch. The `TRACIE_STORE_IMAGES` CI/environment variable can be set
|
||||
to `1` to force storing images, e.g., to get a complete set of reference
|
||||
images.
|
||||
|
||||
At a lower level the
|
||||
[dump_trace_images.py](.gitlab-ci/tracie/dump_trace_images.py) script is
|
||||
called, which replays a trace, dumping a set of images in the process. By
|
||||
default only the image corresponding to the last frame of the trace is dumped,
|
||||
but this can be changed with the `--calls` parameter. The dumped images are
|
||||
stored in a subdirectory `test/<device-name>` next to the trace file itself,
|
||||
with names of the form `tracefilename-callnum.png`. The full log of any
|
||||
commands used while dumping the images is also saved in a file in the
|
||||
'test/<device-name>' subdirectory, named after the trace name with '.log'
|
||||
appended.
|
||||
|
||||
Examples:
|
||||
|
||||
python3 dump_traces_images.py --device-name=gl-vmware-llvmpipe mytrace.trace
|
||||
python3 dump_traces_images.py --device-name=gl-vmware-llvmpipe --calls=2075,3300 mytrace.trace
|
||||
|
||||
### Running the replay scripts locally
|
||||
|
||||
It's often useful, especially during development, to be able to run the scripts
|
||||
locally.
|
||||
|
||||
Depending on the target 3D API, the scripts require a recent version
|
||||
of apitrace being in the path, and also the renderdoc python module
|
||||
being available, for GL traces.
|
||||
|
||||
To ensure python3 can find the renderdoc python module you need to set
|
||||
`PYTHONPATH` to point to the location of `renderdoc.so` (binary python modules)
|
||||
and `LD_LIBRARY_PATH` to point to the location of `librenderdoc.so`. In the
|
||||
renderdoc build tree, both of these are in `renderdoc/<builddir>/lib`. Note
|
||||
that renderdoc doesn't install the `renderdoc.so` python module.
|
||||
|
||||
In the case of Vulkan traces, the scripts need a recent version of
|
||||
gfxrecon-replay being in the path, and also the
|
||||
`VK_LAYER_LUNARG_screenshot` Vulkan layer from LunarG's VulkanTools.
|
||||
|
||||
To ensure that this layer can be found when running the trace you need
|
||||
to set `VK_LAYER_PATH` to point to the location of
|
||||
`VkLayer_screenshot.json` and `LD_LIBRARY_PATH` to point to the
|
||||
location of `libVkLayer_screenshot.so`.
|
||||
|
||||
In the case of DXGI traces, the scripts require Wine, a recent version
|
||||
of DXVK installed in the default `WINEPREFIX`, and a recent binary
|
||||
version of apitrace for Windows which should be reachable through
|
||||
Windows' `PATH` environment variable.
|
@ -1,176 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Copyright (c) 2019 Collabora Ltd
|
||||
# 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
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from traceutil import trace_type_from_filename, TraceType
|
||||
|
||||
def log(severity, msg, end='\n'):
|
||||
print("[dump_trace_images] %s: %s" % (severity, msg), flush=True, end=end)
|
||||
|
||||
def log_result(msg):
|
||||
print(msg, flush=True)
|
||||
|
||||
def run_logged_command(cmd, env, log_path):
|
||||
ret = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
|
||||
logoutput = ("[dump_trace_images] Running: %s\n" % " ".join(cmd)).encode() + \
|
||||
ret.stdout
|
||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with log_path.open(mode='wb') as log:
|
||||
log.write(logoutput)
|
||||
if ret.returncode:
|
||||
raise RuntimeError(
|
||||
logoutput.decode(errors='replace') +
|
||||
"[dump_traces_images] Process failed with error code: %d" % ret.returncode)
|
||||
|
||||
def get_last_apitrace_frame_call(cmd_wrapper, trace_path):
|
||||
cmd = cmd_wrapper + ["apitrace", "dump", "--calls=frame", str(trace_path)]
|
||||
ret = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
for l in reversed(ret.stdout.decode(errors='replace').splitlines()):
|
||||
s = l.split(None, 1)
|
||||
if len(s) >= 1 and s[0].isnumeric():
|
||||
return int(s[0])
|
||||
return -1
|
||||
|
||||
def get_last_gfxreconstruct_frame_call(trace_path):
|
||||
cmd = ["gfxrecon-info", str(trace_path)]
|
||||
ret = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
lines = ret.stdout.decode(errors='replace').splitlines()
|
||||
if len(lines) >= 1:
|
||||
c = lines[0].split(": ", 1)
|
||||
if len(c) >= 2 and c[1].isnumeric():
|
||||
return int(c[1])
|
||||
return -1
|
||||
|
||||
def dump_with_apitrace(retrace_cmd, trace_path, calls, device_name):
|
||||
outputdir = str(trace_path.parent / "test" / device_name)
|
||||
os.makedirs(outputdir, exist_ok=True)
|
||||
outputprefix = str(Path(outputdir) / trace_path.name) + "-"
|
||||
if len(calls) == 0:
|
||||
calls = [str(get_last_apitrace_frame_call(retrace_cmd[:-1], trace_path))]
|
||||
cmd = retrace_cmd + ["--headless",
|
||||
"--snapshot=" + ','.join(calls),
|
||||
"--snapshot-prefix=" + outputprefix, str(trace_path)]
|
||||
log_path = Path(outputdir) / (trace_path.name + ".log")
|
||||
run_logged_command(cmd, None, log_path)
|
||||
|
||||
def dump_with_renderdoc(trace_path, calls, device_name):
|
||||
outputdir = str(trace_path.parent / "test" / device_name)
|
||||
script_path = Path(os.path.dirname(os.path.abspath(__file__)))
|
||||
cmd = [str(script_path / "renderdoc_dump_images.py"), str(trace_path), outputdir]
|
||||
cmd.extend(calls)
|
||||
log_path = Path(outputdir) / (trace_path.name + ".log")
|
||||
run_logged_command(cmd, None, log_path)
|
||||
|
||||
def dump_with_gfxreconstruct(trace_path, calls, device_name):
|
||||
from PIL import Image
|
||||
outputdir_path = trace_path.parent / "test" / device_name
|
||||
outputdir_path.mkdir(parents=True, exist_ok=True)
|
||||
outputprefix = str(outputdir_path / trace_path.name) + "-"
|
||||
if len(calls) == 0:
|
||||
# FIXME: The VK_LAYER_LUNARG_screenshot numbers the calls from
|
||||
# 0 to (total-num-calls - 1) while gfxreconstruct does it from
|
||||
# 1 to total-num-calls:
|
||||
# https://github.com/LunarG/gfxreconstruct/issues/284
|
||||
calls = [str(get_last_gfxreconstruct_frame_call(trace_path) - 1)]
|
||||
cmd = ["gfxrecon-replay", str(trace_path)]
|
||||
log_path = outputdir_path / (trace_path.name + ".log")
|
||||
env = os.environ.copy()
|
||||
env["VK_INSTANCE_LAYERS"] = "VK_LAYER_LUNARG_screenshot"
|
||||
env["VK_SCREENSHOT_FRAMES"] = ",".join(calls)
|
||||
env["VK_SCREENSHOT_DIR"] = str(outputdir_path)
|
||||
run_logged_command(cmd, env, log_path)
|
||||
for c in calls:
|
||||
ppm = str(outputdir_path / c) + ".ppm"
|
||||
outputfile = outputprefix + c + ".png"
|
||||
with log_path.open(mode='w') as log:
|
||||
log.write("Writing: %s to %s" % (ppm, outputfile))
|
||||
Image.open(ppm).save(outputfile)
|
||||
os.remove(ppm)
|
||||
|
||||
def dump_with_testtrace(trace_path, calls, device_name):
|
||||
from PIL import Image
|
||||
outputdir_path = trace_path.parent / "test" / device_name
|
||||
outputdir_path.mkdir(parents=True, exist_ok=True)
|
||||
with trace_path.open() as f:
|
||||
rgba = f.read()
|
||||
color = [int(rgba[0:2], 16), int(rgba[2:4], 16),
|
||||
int(rgba[4:6], 16), int(rgba[6:8], 16)]
|
||||
if len(calls) == 0: calls = ["0"]
|
||||
for c in calls:
|
||||
outputfile = str(outputdir_path / trace_path.name) + "-" + c + ".png"
|
||||
log_path = outputdir_path / (trace_path.name + ".log")
|
||||
with log_path.open(mode='w') as log:
|
||||
log.write("Writing RGBA: %s to %s" % (rgba, outputfile))
|
||||
Image.frombytes('RGBA', (32, 32), bytes(color * 32 * 32)).save(outputfile)
|
||||
|
||||
def dump_from_trace(trace_path, calls, device_name):
|
||||
log("Info", "Dumping trace %s" % trace_path, end='... ')
|
||||
trace_type = trace_type_from_filename(trace_path.name)
|
||||
try:
|
||||
if trace_type == TraceType.APITRACE:
|
||||
dump_with_apitrace(["eglretrace"], trace_path, calls, device_name)
|
||||
elif trace_type == TraceType.APITRACE_DXGI:
|
||||
dump_with_apitrace(["wine", "d3dretrace"], trace_path, calls, device_name)
|
||||
elif trace_type == TraceType.RENDERDOC:
|
||||
dump_with_renderdoc(trace_path, calls, device_name)
|
||||
elif trace_type == TraceType.GFXRECONSTRUCT:
|
||||
dump_with_gfxreconstruct(trace_path, calls, device_name)
|
||||
elif trace_type == TraceType.TESTTRACE:
|
||||
dump_with_testtrace(trace_path, calls, device_name)
|
||||
else:
|
||||
raise RuntimeError("Unknown tracefile extension")
|
||||
log_result("OK")
|
||||
return True
|
||||
except Exception as e:
|
||||
log_result("ERROR")
|
||||
log("Debug", "=== Failure log start ===")
|
||||
print(e)
|
||||
log("Debug", "=== Failure log end ===")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('tracepath', help="trace to dump")
|
||||
parser.add_argument('--device-name', required=True,
|
||||
help="the name of the graphics device used to produce images")
|
||||
parser.add_argument('--calls', required=False,
|
||||
help="the call numbers from the trace to dump (default: last frame)")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.calls is not None:
|
||||
args.calls = args.calls.split(",")
|
||||
else:
|
||||
args.calls = []
|
||||
|
||||
success = dump_from_trace(Path(args.tracepath), args.calls, args.device_name)
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2019 Collabora Ltd
|
||||
#
|
||||
# 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
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from PIL import Image
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('imagefile', help='image file to calculate checksum for')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
md5 = hashlib.md5(Image.open(args.imagefile).tobytes())
|
||||
print(md5.hexdigest())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,108 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Copyright (c) 2019 Collabora Ltd
|
||||
# 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
|
||||
|
||||
import argparse
|
||||
import yaml
|
||||
from traceutil import all_trace_type_names, trace_type_from_name
|
||||
from traceutil import trace_type_from_filename
|
||||
|
||||
def trace_devices(trace):
|
||||
return [e['device'] for e in trace['expectations']]
|
||||
|
||||
def cmd_traces_db_gitlab_project_url(args):
|
||||
with open(args.file, 'r') as f:
|
||||
y = yaml.safe_load(f)
|
||||
print(y['traces-db']['gitlab-project-url'])
|
||||
|
||||
def cmd_traces_db_commit(args):
|
||||
with open(args.file, 'r') as f:
|
||||
y = yaml.safe_load(f)
|
||||
print(y['traces-db']['commit'])
|
||||
|
||||
def cmd_traces(args):
|
||||
with open(args.file, 'r') as f:
|
||||
y = yaml.safe_load(f)
|
||||
|
||||
traces = y['traces']
|
||||
traces = filter(lambda t: trace_type_from_filename(t['path']) in args.trace_types,
|
||||
traces)
|
||||
if args.device_name:
|
||||
traces = filter(lambda t: args.device_name in trace_devices(t), traces)
|
||||
|
||||
traces = list(traces)
|
||||
|
||||
if len(traces) == 0:
|
||||
return
|
||||
|
||||
print('\n'.join((t['path'] for t in traces)))
|
||||
|
||||
def cmd_checksum(args):
|
||||
with open(args.file, 'r') as f:
|
||||
y = yaml.safe_load(f)
|
||||
|
||||
traces = y['traces']
|
||||
trace = next(t for t in traces if t['path'] == args.trace_path)
|
||||
expectation = next(e for e in trace['expectations'] if e['device'] == args.device_name)
|
||||
|
||||
print(expectation['checksum'])
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--file', required=True,
|
||||
help='the name of the yaml file')
|
||||
|
||||
subparsers = parser.add_subparsers(help='sub-command help')
|
||||
|
||||
parser_traces_db_gitlab_project_url = subparsers.add_parser('traces_db_gitlab_project_url')
|
||||
parser_traces_db_gitlab_project_url.set_defaults(func=cmd_traces_db_gitlab_project_url)
|
||||
|
||||
parser_traces_db_commit = subparsers.add_parser('traces_db_commit')
|
||||
parser_traces_db_commit.set_defaults(func=cmd_traces_db_commit)
|
||||
|
||||
parser_traces = subparsers.add_parser('traces')
|
||||
parser_traces.add_argument('--device-name', required=False,
|
||||
help="the name of the graphics device used to "
|
||||
"produce images")
|
||||
parser_traces.add_argument('--trace-types', required=False,
|
||||
default=",".join(all_trace_type_names()),
|
||||
help="the types of traces to look for in recursive "
|
||||
"dir walks " "(by default all types)")
|
||||
parser_traces.set_defaults(func=cmd_traces)
|
||||
|
||||
parser_checksum = subparsers.add_parser('checksum')
|
||||
parser_checksum.add_argument('--device-name', required=True,
|
||||
help="the name of the graphics device used to "
|
||||
"produce images")
|
||||
parser_checksum.add_argument('trace_path')
|
||||
parser_checksum.set_defaults(func=cmd_checksum)
|
||||
|
||||
args = parser.parse_args()
|
||||
if hasattr(args, 'trace_types'):
|
||||
args.trace_types = [trace_type_from_name(t) for t in args.trace_types.split(",")]
|
||||
|
||||
args.func(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,125 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2019 Collabora Ltd
|
||||
#
|
||||
# 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
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
def cleanup(dirpath):
|
||||
shutil.rmtree(dirpath)
|
||||
|
||||
dirpath = tempfile.mkdtemp()
|
||||
atexit.register(cleanup, dirpath)
|
||||
RENDERDOC_DEBUG_FILE = dirpath + "/renderdoc.log"
|
||||
|
||||
# Needs to be in the environment before importing the module
|
||||
os.environ['RENDERDOC_DEBUG_LOG_FILE'] = RENDERDOC_DEBUG_FILE
|
||||
import renderdoc as rd
|
||||
|
||||
def findDrawWithEventId(controller, eventId):
|
||||
for d in controller.GetDrawcalls():
|
||||
if d.eventId == eventId:
|
||||
return d
|
||||
|
||||
return None
|
||||
|
||||
def dumpImage(controller, eventId, outputDir, tracefile):
|
||||
draw = findDrawWithEventId(controller, eventId)
|
||||
if draw is None:
|
||||
raise RuntimeError("Couldn't find draw call with eventId " + str(eventId))
|
||||
|
||||
controller.SetFrameEvent(draw.eventId, True)
|
||||
|
||||
texsave = rd.TextureSave()
|
||||
|
||||
# Select the first color output
|
||||
texsave.resourceId = draw.outputs[0]
|
||||
|
||||
if texsave.resourceId == rd.ResourceId.Null():
|
||||
return
|
||||
|
||||
filepath = Path(outputDir)
|
||||
filepath.mkdir(parents = True, exist_ok = True)
|
||||
filepath = filepath / (tracefile + "-" + str(int(draw.eventId)) + ".png")
|
||||
|
||||
print("Saving image at eventId %d: %s to %s" % (draw.eventId, draw.name, filepath))
|
||||
|
||||
# Most formats can only display a single image per file, so we select the
|
||||
# first mip and first slice
|
||||
texsave.mip = 0
|
||||
texsave.slice.sliceIndex = 0
|
||||
|
||||
# For formats with an alpha channel, preserve it
|
||||
texsave.alpha = rd.AlphaMapping.Preserve
|
||||
texsave.destType = rd.FileType.PNG
|
||||
controller.SaveTexture(texsave, str(filepath))
|
||||
|
||||
def loadCapture(filename):
|
||||
cap = rd.OpenCaptureFile()
|
||||
|
||||
status = cap.OpenFile(filename, '', None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError("Couldn't open file: " + str(status))
|
||||
if not cap.LocalReplaySupport():
|
||||
raise RuntimeError("Capture cannot be replayed")
|
||||
|
||||
status, controller = cap.OpenCapture(rd.ReplayOptions(), None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
if os.path.exists(RENDERDOC_DEBUG_FILE):
|
||||
print(open(RENDERDOC_DEBUG_FILE, "r").read())
|
||||
raise RuntimeError("Couldn't initialise replay: " + str(status))
|
||||
|
||||
if os.path.exists(RENDERDOC_DEBUG_FILE):
|
||||
open(RENDERDOC_DEBUG_FILE, "w").write("")
|
||||
|
||||
return (cap, controller)
|
||||
|
||||
def renderdoc_dump_images(filename, eventIds, outputDir):
|
||||
rd.InitialiseReplay(rd.GlobalEnvironment(), [])
|
||||
cap, controller = loadCapture(filename);
|
||||
|
||||
tracefile = Path(filename).name
|
||||
|
||||
if len(eventIds) == 0:
|
||||
eventIds.append(controller.GetDrawcalls()[-1].eventId)
|
||||
|
||||
for eventId in eventIds:
|
||||
dumpImage(controller, eventId, outputDir, tracefile)
|
||||
|
||||
cap.Shutdown()
|
||||
|
||||
rd.ShutdownReplay()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
raise RuntimeError("Usage: renderdoc_dump_images.py <trace> <outputdir> [<draw-id>...]")
|
||||
|
||||
eventIds = [int(e) for e in sys.argv[3:]]
|
||||
|
||||
renderdoc_dump_images(sys.argv[1], eventIds, sys.argv[2])
|
@ -1 +0,0 @@
|
||||
ff00ffff
|
@ -1 +0,0 @@
|
||||
80800080
|
@ -1,253 +0,0 @@
|
||||
import logging
|
||||
import pytest
|
||||
import re
|
||||
import shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from os import environ, chdir
|
||||
from os.path import dirname, exists, realpath
|
||||
|
||||
import tracie
|
||||
|
||||
|
||||
RESULTS_YAML = "results/results.yml"
|
||||
JUNIT_XML = "results/junit.xml"
|
||||
TRACE_LOG_TEST1 = "results/trace1/test/gl-test-device/magenta.testtrace.log"
|
||||
TRACE_LOG_TEST2 = "results/trace2/test/vk-test-device/olive.testtrace.log"
|
||||
TRACE_PNG_TEST1 = "results/trace1/test/gl-test-device/magenta.testtrace-0.png"
|
||||
TRACE_PNG_TEST2 = "results/trace2/test/vk-test-device/olive.testtrace-0.png"
|
||||
TRACIE_DIR = dirname(realpath(__file__)) + "/.."
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def write_to(content, filename):
|
||||
with open(filename, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def read_from(filename):
|
||||
with open(filename) as f:
|
||||
content = f.read()
|
||||
return content
|
||||
|
||||
|
||||
def run_tracie():
|
||||
'''
|
||||
Run tests for the .testtrace types, using the "gl-test-device" and
|
||||
"vk-test-device" device names.
|
||||
'''
|
||||
result = tracie.main(["--device-name", "gl-test-device",
|
||||
"--file", "./tests/traces.yml"])
|
||||
if not result:
|
||||
return False
|
||||
result = tracie.main(["--device-name", "vk-test-device",
|
||||
"--file", "./tests/traces.yml"])
|
||||
return result
|
||||
|
||||
|
||||
def prepare_for_run(tmp_path):
|
||||
'''
|
||||
Copy all the tracie scripts to the test dir for the unit tests.
|
||||
This avoids polluting the normal working dir with test result artifacts.
|
||||
'''
|
||||
test_dir = str(tmp_path) + "/run"
|
||||
shutil.copytree(TRACIE_DIR, test_dir)
|
||||
# Change the working dir to the test_dir
|
||||
chdir(test_dir)
|
||||
# Set the traces-db
|
||||
shutil.move("./tests/test-data", "./traces-db")
|
||||
# Disable trace storing
|
||||
environ["TRACIE_STORE_IMAGES"] = "0"
|
||||
environ["TRACIE_UPLOAD_TO_MINIO"] = "0"
|
||||
environ["CI_PROJECT_PATH"] = "test-project"
|
||||
environ["CI_PIPELINE_ID"] = "667"
|
||||
environ["CI_JOB_ID"] = "42"
|
||||
|
||||
def cleanup(tmp_path):
|
||||
'''
|
||||
Performs the clean up of the test dir.
|
||||
'''
|
||||
if exists(tmp_path):
|
||||
shutil.rmtree(tmp_path)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def run_test(tmp_path):
|
||||
'''
|
||||
Wraps the execution of each test as follows:
|
||||
|
||||
prepare_for_run()
|
||||
test()
|
||||
cleanup()
|
||||
'''
|
||||
logger.debug("Working dir: %s", tmp_path)
|
||||
prepare_for_run(tmp_path)
|
||||
yield
|
||||
cleanup(tmp_path)
|
||||
|
||||
|
||||
def check_results_yaml_content(filename, expectations):
|
||||
'''
|
||||
Checks the content of the filename with the list of expectations
|
||||
passed as parameter.
|
||||
|
||||
Arguments:
|
||||
filename (str): The path of the file to check
|
||||
expectations (list): A list with the content to find in the file
|
||||
|
||||
Returns:
|
||||
bool: The return value. True if the content of the filename satisfies
|
||||
the expectations, False otherwise.
|
||||
'''
|
||||
content = read_from(filename)
|
||||
for e in expectations:
|
||||
ocurrencies = re.findall(e, content)
|
||||
if not len(ocurrencies):
|
||||
logger.error("Expectation not found in %s: %s", filename, e)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def test_tracie_succeeds_if_all_images_match():
|
||||
assert run_tracie()
|
||||
expectations = [
|
||||
"actual: 5efda83854befe0155ff8517a58d5b51",
|
||||
"expected: 5efda83854befe0155ff8517a58d5b51",
|
||||
]
|
||||
assert check_results_yaml_content(RESULTS_YAML, expectations)
|
||||
|
||||
|
||||
def test_tracie_fails_on_image_mismatch():
|
||||
filename = "./tests/traces.yml"
|
||||
content = read_from(filename)
|
||||
content = content.replace("5efda83854befe0155ff8517a58d5b51",
|
||||
"8e0a801367e1714463475a824dab363b")
|
||||
write_to(content, filename)
|
||||
assert not run_tracie()
|
||||
expectations = [
|
||||
"actual: 5efda83854befe0155ff8517a58d5b51",
|
||||
"expected: 8e0a801367e1714463475a824dab363b",
|
||||
"trace2/test/vk-test-device/olive.testtrace-0.png"
|
||||
]
|
||||
assert check_results_yaml_content(RESULTS_YAML, expectations)
|
||||
|
||||
|
||||
def test_tracie_traces_with_and_without_checksum():
|
||||
filename = "./tests/traces.yml"
|
||||
content = read_from(filename)
|
||||
content += ''' - path: trace1/red.testtrace
|
||||
expectations:
|
||||
- device: bla
|
||||
checksum: 000000000000000'''
|
||||
write_to(content, filename)
|
||||
|
||||
# red.testtrace should be skipped, since it doesn't
|
||||
# have any checksums for our device
|
||||
filename = "./traces-db/trace1/red.testtrace"
|
||||
content = "ff0000ff"
|
||||
write_to(content, filename)
|
||||
assert run_tracie()
|
||||
|
||||
|
||||
def test_tracie_only_traces_without_checksum():
|
||||
filename = "./tests/traces.yml"
|
||||
content = '''traces:
|
||||
- path: trace1/red.testtrace
|
||||
expectations:
|
||||
- device: bla
|
||||
checksum: 000000000000000'''
|
||||
write_to(content, filename)
|
||||
|
||||
# red.testtrace should be skipped, since it doesn't
|
||||
# have any checksums for our device
|
||||
filename = "./traces-db/trace1/red.testtrace"
|
||||
content = "ff0000ff"
|
||||
write_to(content, filename)
|
||||
assert run_tracie()
|
||||
|
||||
|
||||
def test_tracie_with_no_traces():
|
||||
filename = "./tests/traces.yml"
|
||||
content = 'traces:'
|
||||
write_to(content, filename)
|
||||
assert run_tracie()
|
||||
expectations = [
|
||||
"{}",
|
||||
]
|
||||
assert check_results_yaml_content(RESULTS_YAML, expectations)
|
||||
|
||||
|
||||
def test_tracie_fails_on_dump_image_error():
|
||||
# "invalid" should fail to parse as rgba and
|
||||
# cause an error
|
||||
filename = "./traces-db/trace1/magenta.testtrace"
|
||||
write_to("invalid\n", filename)
|
||||
run_tracie()
|
||||
expectations = [
|
||||
"actual: error",
|
||||
"expected: 8e0a801367e1714463475a824dab363b",
|
||||
"trace1/magenta.testtrace",
|
||||
]
|
||||
assert check_results_yaml_content(RESULTS_YAML, expectations)
|
||||
|
||||
|
||||
def test_tracie_stores_only_logs_on_checksum_match():
|
||||
assert run_tracie()
|
||||
assert exists(TRACE_LOG_TEST1)
|
||||
assert exists(TRACE_LOG_TEST2)
|
||||
assert not exists(TRACE_PNG_TEST1)
|
||||
assert not exists(TRACE_PNG_TEST2)
|
||||
|
||||
|
||||
def test_tracie_stores_images_on_checksum_mismatch():
|
||||
filename = "./tests/traces.yml"
|
||||
content = read_from(filename)
|
||||
content = content.replace("5efda83854befe0155ff8517a58d5b51",
|
||||
"8e0a801367e1714463475a824dab363b")
|
||||
write_to(content, filename)
|
||||
assert not run_tracie()
|
||||
assert not exists(TRACE_PNG_TEST1)
|
||||
assert exists(TRACE_PNG_TEST2)
|
||||
|
||||
|
||||
def test_tracie_stores_images_on_request():
|
||||
environ["TRACIE_STORE_IMAGES"] = "1"
|
||||
assert run_tracie()
|
||||
assert exists(TRACE_PNG_TEST1)
|
||||
assert exists(TRACE_PNG_TEST2)
|
||||
|
||||
def test_tracie_writes_junit_xml():
|
||||
assert run_tracie()
|
||||
junit_xml = ET.parse(JUNIT_XML)
|
||||
assert junit_xml.getroot().tag == 'testsuites'
|
||||
testsuites = junit_xml.findall("./testsuite")
|
||||
testcases_gl = junit_xml.findall("./testsuite[@name='traces.yml:gl-test-device']/testcase")
|
||||
testcases_vk = junit_xml.findall("./testsuite[@name='traces.yml:vk-test-device']/testcase")
|
||||
|
||||
assert len(testsuites) == 2
|
||||
assert len(testcases_gl) == 1
|
||||
assert len(testcases_vk) == 1
|
||||
assert testcases_gl[0].get("name") == "trace1/magenta.testtrace"
|
||||
assert testcases_gl[0].get("classname") == "traces.yml:gl-test-device"
|
||||
assert testcases_vk[0].get("name") == "trace2/olive.testtrace"
|
||||
assert testcases_vk[0].get("classname") == "traces.yml:vk-test-device"
|
||||
|
||||
def test_tracie_writes_dashboard_url_in_junit_xml_failure_tag():
|
||||
filename = "./tests/traces.yml"
|
||||
content = read_from(filename)
|
||||
content = content.replace("5efda83854befe0155ff8517a58d5b51",
|
||||
"8e0a801367e1714463475a824dab363b")
|
||||
write_to(content, filename)
|
||||
|
||||
assert not run_tracie()
|
||||
|
||||
junit_xml = ET.parse(JUNIT_XML)
|
||||
failures_gl = junit_xml.findall("./testsuite[@name='traces.yml:gl-test-device']/testcase/failure")
|
||||
failures_vk = junit_xml.findall("./testsuite[@name='traces.yml:vk-test-device']/testcase/failure")
|
||||
|
||||
assert len(failures_gl) == 0
|
||||
assert len(failures_vk) == 1
|
||||
dashboard_url = "https://tracie.freedesktop.org/dashboard/imagediff/test-project/42/trace2/olive.testtrace"
|
||||
assert dashboard_url in failures_vk[0].text
|
@ -1,9 +0,0 @@
|
||||
traces:
|
||||
- path: trace1/magenta.testtrace
|
||||
expectations:
|
||||
- device: gl-test-device
|
||||
checksum: 8e0a801367e1714463475a824dab363b
|
||||
- path: trace2/olive.testtrace
|
||||
expectations:
|
||||
- device: vk-test-device
|
||||
checksum: 5efda83854befe0155ff8517a58d5b51
|
@ -1,63 +0,0 @@
|
||||
# Copyright (c) 2019 Collabora Ltd
|
||||
# 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
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from enum import Enum, auto
|
||||
|
||||
class TraceType(Enum):
|
||||
UNKNOWN = auto()
|
||||
APITRACE = auto()
|
||||
APITRACE_DXGI = auto()
|
||||
RENDERDOC = auto()
|
||||
GFXRECONSTRUCT = auto()
|
||||
TESTTRACE = auto()
|
||||
|
||||
_trace_type_info_map = {
|
||||
TraceType.APITRACE : ("apitrace", ".trace"),
|
||||
TraceType.APITRACE_DXGI : ("apitrace-dxgi", ".trace-dxgi"),
|
||||
TraceType.RENDERDOC : ("renderdoc", ".rdc"),
|
||||
TraceType.GFXRECONSTRUCT : ("gfxreconstruct", ".gfxr"),
|
||||
TraceType.TESTTRACE : ("testtrace", ".testtrace")
|
||||
}
|
||||
|
||||
def all_trace_type_names():
|
||||
s = []
|
||||
for t,(name, ext) in _trace_type_info_map.items():
|
||||
if t != TraceType.UNKNOWN:
|
||||
s.append(name)
|
||||
return s
|
||||
|
||||
def trace_type_from_name(tt_name):
|
||||
for t,(name, ext) in _trace_type_info_map.items():
|
||||
if tt_name == name:
|
||||
return t
|
||||
|
||||
return TraceType.UNKNOWN
|
||||
|
||||
def trace_type_from_filename(trace_file):
|
||||
for t,(name, ext) in _trace_type_info_map.items():
|
||||
if trace_file.endswith(ext):
|
||||
return t
|
||||
|
||||
return TraceType.UNKNOWN
|
@ -1,270 +0,0 @@
|
||||
import argparse
|
||||
import base64
|
||||
import datetime
|
||||
import enum
|
||||
import glob
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import yaml
|
||||
import shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from email.utils import formatdate
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
from urllib import parse
|
||||
|
||||
import dump_trace_images
|
||||
|
||||
TRACES_DB_PATH = "./traces-db/"
|
||||
RESULTS_PATH = "./results/"
|
||||
MINIO_HOST = "minio-packet.freedesktop.org"
|
||||
DASHBOARD_URL = "https://tracie.freedesktop.org/dashboard"
|
||||
|
||||
minio_credentials = None
|
||||
|
||||
def replay(trace_path, device_name):
|
||||
success = dump_trace_images.dump_from_trace(trace_path, [], device_name)
|
||||
|
||||
if not success:
|
||||
print("[check_image] Trace %s couldn't be replayed. See above logs for more information." % (str(trace_path)))
|
||||
return None, None, None
|
||||
else:
|
||||
base_path = trace_path.parent
|
||||
file_name = trace_path.name
|
||||
files = glob.glob(str(base_path / "test" / device_name / (file_name + "-*" + ".png")))
|
||||
assert(files)
|
||||
image_file = files[0]
|
||||
files = glob.glob(str(base_path / "test" / device_name / (file_name + ".log")))
|
||||
assert(files)
|
||||
log_file = files[0]
|
||||
return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file
|
||||
|
||||
def gitlab_ensure_trace(project_url, trace):
|
||||
trace_path = TRACES_DB_PATH + trace['path']
|
||||
if project_url is None:
|
||||
if not os.path.exists(trace_path):
|
||||
print("{} missing".format(trace_path))
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
os.makedirs(os.path.dirname(trace_path), exist_ok=True)
|
||||
|
||||
if os.path.exists(trace_path):
|
||||
return
|
||||
|
||||
print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
|
||||
download_time = time.time()
|
||||
r = requests.get(project_url + trace['path'])
|
||||
open(trace_path, "wb").write(r.content)
|
||||
print("took %ds." % (time.time() - download_time), flush=True)
|
||||
|
||||
def sign_with_hmac(key, message):
|
||||
key = key.encode("UTF-8")
|
||||
message = message.encode("UTF-8")
|
||||
|
||||
signature = hmac.new(key, message, hashlib.sha1).digest()
|
||||
|
||||
return base64.encodebytes(signature).strip().decode()
|
||||
|
||||
def ensure_minio_credentials():
|
||||
global minio_credentials
|
||||
|
||||
if minio_credentials is None:
|
||||
minio_credentials = {}
|
||||
|
||||
params = {'Action': 'AssumeRoleWithWebIdentity',
|
||||
'Version': '2011-06-15',
|
||||
'RoleArn': 'arn:aws:iam::123456789012:role/FederatedWebIdentityRole',
|
||||
'RoleSessionName': '%s:%s' % (os.environ['CI_PROJECT_PATH'], os.environ['CI_JOB_ID']),
|
||||
'DurationSeconds': 900,
|
||||
'WebIdentityToken': os.environ['CI_JOB_JWT']}
|
||||
r = requests.post('https://%s' % (MINIO_HOST), params=params)
|
||||
if r.status_code >= 400:
|
||||
print(r.text)
|
||||
r.raise_for_status()
|
||||
|
||||
root = ET.fromstring(r.text)
|
||||
for attr in root.iter():
|
||||
if attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}AccessKeyId':
|
||||
minio_credentials['AccessKeyId'] = attr.text
|
||||
elif attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}SecretAccessKey':
|
||||
minio_credentials['SecretAccessKey'] = attr.text
|
||||
elif attr.tag == '{https://sts.amazonaws.com/doc/2011-06-15/}SessionToken':
|
||||
minio_credentials['SessionToken'] = attr.text
|
||||
|
||||
def upload_to_minio(file_name, resource, content_type):
|
||||
ensure_minio_credentials()
|
||||
|
||||
minio_key = minio_credentials['AccessKeyId']
|
||||
minio_secret = minio_credentials['SecretAccessKey']
|
||||
minio_token = minio_credentials['SessionToken']
|
||||
|
||||
date = formatdate(timeval=None, localtime=False, usegmt=True)
|
||||
url = 'https://%s%s' % (MINIO_HOST, resource)
|
||||
to_sign = "PUT\n\n%s\n%s\nx-amz-security-token:%s\n%s" % (content_type, date, minio_token, resource)
|
||||
signature = sign_with_hmac(minio_secret, to_sign)
|
||||
|
||||
with open(file_name, 'rb') as data:
|
||||
headers = {'Host': MINIO_HOST,
|
||||
'Date': date,
|
||||
'Content-Type': content_type,
|
||||
'Authorization': 'AWS %s:%s' % (minio_key, signature),
|
||||
'x-amz-security-token': minio_token}
|
||||
print("Uploading artifact to %s" % url);
|
||||
r = requests.put(url, headers=headers, data=data)
|
||||
if r.status_code >= 400:
|
||||
print(r.text)
|
||||
r.raise_for_status()
|
||||
|
||||
def upload_artifact(file_name, key, content_type):
|
||||
resource = '/artifacts/%s/%s/%s/%s' % (os.environ['CI_PROJECT_PATH'],
|
||||
os.environ['CI_PIPELINE_ID'],
|
||||
os.environ['CI_JOB_ID'],
|
||||
key)
|
||||
upload_to_minio(file_name, resource, content_type)
|
||||
|
||||
def ensure_reference_image(file_name, checksum):
|
||||
resource = '/mesa-tracie-results/%s/%s.png' % (os.environ['CI_PROJECT_PATH'], checksum)
|
||||
url = 'https://%s%s' % (MINIO_HOST, resource)
|
||||
r = requests.head(url, allow_redirects=True)
|
||||
if r.status_code == 200:
|
||||
return
|
||||
upload_to_minio(file_name, resource, 'image/png')
|
||||
|
||||
def image_diff_url(trace_path):
|
||||
return "%s/imagediff/%s/%s/%s" % (DASHBOARD_URL,
|
||||
os.environ.get('CI_PROJECT_PATH'),
|
||||
os.environ.get('CI_JOB_ID'),
|
||||
trace_path)
|
||||
|
||||
def gitlab_check_trace(project_url, device_name, trace, expectation):
|
||||
gitlab_ensure_trace(project_url, trace)
|
||||
|
||||
result = {}
|
||||
result[trace['path']] = {}
|
||||
result[trace['path']]['expected'] = expectation['checksum']
|
||||
|
||||
trace_path = Path(TRACES_DB_PATH + trace['path'])
|
||||
checksum, image_file, log_file = replay(trace_path, device_name)
|
||||
if checksum is None:
|
||||
result[trace['path']]['actual'] = 'error'
|
||||
return False, result
|
||||
elif checksum == expectation['checksum']:
|
||||
print("[check_image] Images match for %s" % (trace['path']))
|
||||
ok = True
|
||||
else:
|
||||
print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
|
||||
(trace['path'], expectation['checksum'], checksum))
|
||||
print("[check_image] For more information see "
|
||||
"https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
|
||||
print("[check_image] %s" % image_diff_url(trace['path']))
|
||||
ok = False
|
||||
|
||||
trace_dir = os.path.split(trace['path'])[0]
|
||||
dir_in_results = os.path.join(trace_dir, "test", device_name)
|
||||
results_path = os.path.join(RESULTS_PATH, dir_in_results)
|
||||
os.makedirs(results_path, exist_ok=True)
|
||||
shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
|
||||
if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
|
||||
if ok:
|
||||
if os.environ['CI_PROJECT_PATH'] == 'mesa/mesa':
|
||||
ensure_reference_image(image_file, checksum)
|
||||
else:
|
||||
upload_artifact(image_file, 'traces/%s.png' % checksum, 'image/png')
|
||||
if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
|
||||
image_name = os.path.split(image_file)[1]
|
||||
shutil.move(image_file, os.path.join(results_path, image_name))
|
||||
result[trace['path']]['image'] = os.path.join(dir_in_results, image_name)
|
||||
|
||||
result[trace['path']]['actual'] = checksum
|
||||
|
||||
return ok, result
|
||||
|
||||
def write_junit_xml(junit_xml_path, traces_filename, device_name, results):
|
||||
tests = len(results)
|
||||
failures = sum(1 for r in results.values() if r["actual"] != r["expected"])
|
||||
|
||||
try:
|
||||
testsuites = ET.parse(junit_xml_path).getroot()
|
||||
except:
|
||||
test_name = os.environ.get('CI_PROJECT_PATH') + "/" + \
|
||||
os.environ.get('CI_PIPELINE_ID') + "/" + \
|
||||
os.environ.get('CI_JOB_ID')
|
||||
testsuites = ET.Element('testsuites', name=test_name)
|
||||
|
||||
testsuites.set('tests', str(int(testsuites.get('tests', 0)) + tests))
|
||||
testsuites.set('failures', str(int(testsuites.get('failures', 0)) + failures))
|
||||
|
||||
testsuite_name = os.path.basename(traces_filename) + ":" + device_name
|
||||
|
||||
testsuite = ET.SubElement(testsuites, 'testsuite',
|
||||
name=testsuite_name,
|
||||
tests=str(tests), failures=str(failures))
|
||||
|
||||
for (path, result) in results.items():
|
||||
testcase = ET.SubElement(testsuite, 'testcase', name=path,
|
||||
classname=testsuite_name)
|
||||
if result["actual"] != result["expected"]:
|
||||
failure = ET.SubElement(testcase, 'failure')
|
||||
failure.text = \
|
||||
("Images differ (expected: %s, actual: %s).\n" + \
|
||||
"To view the image differences visit:\n%s") % \
|
||||
(result["expected"], result["actual"], image_diff_url(path))
|
||||
|
||||
ET.ElementTree(testsuites).write(junit_xml_path)
|
||||
|
||||
def run(filename, device_name):
|
||||
|
||||
with open(filename, 'r') as f:
|
||||
y = yaml.safe_load(f)
|
||||
|
||||
if "traces-db" in y:
|
||||
project_url = y["traces-db"]["download-url"]
|
||||
else:
|
||||
project_url = None
|
||||
|
||||
traces = y['traces'] or []
|
||||
all_ok = True
|
||||
results = {}
|
||||
for trace in traces:
|
||||
for expectation in trace['expectations']:
|
||||
if expectation['device'] == device_name:
|
||||
ok, result = gitlab_check_trace(project_url,
|
||||
device_name, trace,
|
||||
expectation)
|
||||
all_ok = all_ok and ok
|
||||
results.update(result)
|
||||
|
||||
os.makedirs(RESULTS_PATH, exist_ok=True)
|
||||
with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f:
|
||||
yaml.safe_dump(results, f, default_flow_style=False)
|
||||
|
||||
junit_xml_path = os.path.join(RESULTS_PATH, "junit.xml")
|
||||
write_junit_xml(junit_xml_path, filename, device_name, results)
|
||||
|
||||
if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
|
||||
upload_artifact(os.path.join(RESULTS_PATH, 'results.yml'), 'traces/results.yml', 'text/yaml')
|
||||
upload_artifact(junit_xml_path, 'traces/junit.xml', 'text/xml')
|
||||
|
||||
return all_ok
|
||||
|
||||
def main(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--file', required=True,
|
||||
help='the name of the traces.yml file listing traces and their checksums for each device')
|
||||
parser.add_argument('--device-name', required=True,
|
||||
help="the name of the graphics device used to replay traces")
|
||||
|
||||
args = parser.parse_args(args)
|
||||
return run(args.file, args.device_name)
|
||||
|
||||
if __name__ == "__main__":
|
||||
all_ok = main(sys.argv[1:])
|
||||
sys.exit(0 if all_ok else 1)
|
@ -42,8 +42,8 @@ at /tftp in the container.
|
||||
|
||||
Since we're going the TFTP route, we also use NFS root. This avoids
|
||||
packing the rootfs and sending it to the board as a ramdisk, which
|
||||
means we can support larger rootfses (for piglit or tracie testing),
|
||||
at the cost of needing more storage on the runner.
|
||||
means we can support larger rootfses (for piglit testing), at the cost
|
||||
of needing more storage on the runner.
|
||||
|
||||
Telling the board about where its TFTP and NFS should come from is
|
||||
done using dnsmasq on the runner host. For example, this snippet in
|
||||
|
Loading…
Reference in New Issue
Block a user