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:
Andres Gomez 2020-12-08 21:44:12 +02:00 committed by Marge Bot
parent 54bdec63ef
commit 050241d81a
16 changed files with 2 additions and 1303 deletions

View File

@ -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/

View File

@ -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"

View File

@ -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"

View File

@ -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.

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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