blob: 2c2e3c4170c6a528c439e6defb13de4775b56069 [file] [log] [blame]
# Copyright 2021 Google LLC.
# Copyright 2021 The Khronos Group Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Requirements to run the script:
# - Python3 (apt-get install -y python3.x)
# - GO (apt-get install -y golang-go)
# - cmake (version 3.13 or later)
# - ninja (apt-get install -y ninja-build)
# - git (sudo apt-get install -y git)
# GO dependencies needed:
# - crypto/openpgp (go get -u golang.org/x/crypto/openpgp...)
import os
import json
import tempfile
import subprocess
import sys
from argparse import ArgumentParser
from shutil import which, copyfile
from pathlib import Path
from datetime import datetime
# Check for correct python version (python3) before doing anything.
if sys.version_info.major < 3:
raise RuntimeError("Python version needs to be 3 or greater.")
AP = ArgumentParser()
AP.add_argument(
"-d",
"--directory",
metavar="DIRECTORY",
type=str,
help="Path to directory that will be used as root for cloning and file saving.",
default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader")
)
AP.add_argument(
"-u",
"--url",
metavar="URL",
type=str,
help="URL of SwiftShader Git repository.",
default="https://swiftshader.googlesource.com/SwiftShader",
)
AP.add_argument(
"-l",
"--vlayer_url",
metavar="VURL",
type=str,
help="URL of Validation Layers Git repository.",
default="https://github.com/KhronosGroup/Vulkan-ValidationLayers.git",
)
AP.add_argument(
"-b",
"--sws_build_type",
metavar="SWS_BUILD_TYPE",
type=str,
help="SwiftShader build type.",
choices=["debug", "release"],
default="debug",
)
AP.add_argument(
"-q",
"--deqp_vk",
metavar="DEQP_VK",
type=str,
help="Path to deqp-vk binary.",
)
AP.add_argument(
"-v",
"--vk_gl_cts",
metavar="VK_GL_CTS",
type=str,
help="Path to vk-gl-cts source directory.",
)
AP.add_argument(
"-w",
"--vk_gl_cts_build",
metavar="VK_GL_CTS_BUILD",
type=str,
help="Path to vk-gl-cts build directory.",
default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader" / "vk-gl-cts-build"),
)
AP.add_argument(
"-t",
"--vk_gl_cts_build_type",
metavar="VK_GL_CTS_BUILD_TYPE",
type=str,
help="vk-gl-cts build type.",
choices=["debug", "release"],
default="debug",
)
AP.add_argument(
"-r",
"--recipe",
metavar="RECIPE",
type=str,
help="Recipes to only run parts of script.",
choices=["run-deqp", "check-comparison"],
default="run-deqp",
)
AP.add_argument(
"-f",
"--files",
nargs=2,
metavar=("NEWER_FILE_PATH", "OLDER_FILE_PATH"),
type=str,
help="Compare two different run results.",
)
AP.add_argument(
"-a",
"--validation",
metavar="VALIDATION",
type=str,
help="Enable vulkan validation layers.",
choices=["true", "false"],
default="false",
)
AP.add_argument(
"-o",
"--result_output",
metavar="OUTPUT",
type=str,
help="Filename of the regres results.",
default=str("result_" + str(datetime.now().strftime('%m_%d_%Y_%H_%M_%S')) + ".json"),
)
ARGS = AP.parse_args()
# Check that we have everything needed to run the script when using recipe run-deqp.
if ARGS.recipe == "run-deqp":
if which("go") is None:
raise RuntimeError("go not found. (apt-get install -y golang-go)")
if which("cmake") is None:
raise RuntimeError("CMake not found. (version 3.13 or later needed)")
if which("ninja") is None:
raise RuntimeError("Ninja not found. (apt-get install -y ninja-build)")
if which("git") is None:
raise RuntimeError("Git not found. (apt-get install -y git)")
if ARGS.vk_gl_cts is None:
raise RuntimeError("vk-gl-cts source directory must be provided. Use --help for more info.")
PARENT_DIR = Path(ARGS.directory).resolve()
SWS_SRC_DIR = PARENT_DIR / "SwiftShader"
SWS_BUILD_DIR = SWS_SRC_DIR / "build"
SWIFTSHADER_URL = ARGS.url
LAYERS_PARENT_DIR = Path(ARGS.directory).resolve()
LAYERS_SRC_DIR = LAYERS_PARENT_DIR / "Vulkan_Validation_Layers"
LAYERS_URL = ARGS.vlayer_url
LAYERS_BUILD_DIR = LAYERS_SRC_DIR / "build"
LINUX_SWS_ICD_DIR = SWS_BUILD_DIR / "Linux"
REGRES_DIR = SWS_SRC_DIR / "tests" / "regres"
RESULT_DIR = PARENT_DIR / "regres_results"
COMP_RESULTS_DIR = PARENT_DIR / "comparison_results"
VK_GL_CTS_ROOT_DIR = Path(ARGS.vk_gl_cts)
VK_GL_CTS_BUILD_DIR = Path(ARGS.vk_gl_cts_build)
MUSTPASS_LIST = VK_GL_CTS_ROOT_DIR / "external" / "vulkancts" / "mustpass" / "master" / "vk-default.txt"
if ARGS.deqp_vk is None:
DEQP_VK_BINARY = VK_GL_CTS_BUILD_DIR / "external" / "vulkancts" / "modules" / "vulkan" / "deqp-vk"
else:
DEQP_VK_BINARY = str(ARGS.deqp_vk)
new_pass = []
new_fail = []
new_crash = []
new_notsupported = []
has_been_removed = []
status_change = []
compatibility_warning = []
quality_warning = []
internal_errors = []
waivers = []
class Result:
def __init__(self, filename):
self.filename = filename
self.f = open(filename)
# Skip the first four lines and check that the file order has not been changed.
tmp = ""
for i in range(4):
tmp = tmp + self.f.readline()
if "Tests" not in tmp:
raise RuntimeError("Skipped four lines, no starting line found. Has the file order changed?")
# Reads one test item from the file.
def readResult(self):
while True:
tmp = ""
while "}" not in tmp:
tmp = tmp + self.f.readline()
if "Test" in tmp:
tmp = tmp[tmp.find("{") : tmp.find("}") + 1]
return json.loads(tmp)
else:
return None
# Search for a test name. Returns the test data if found and otherwise False.
def searchTest(self, test):
line = self.f.readline()
while line:
if line.find(test) != -1:
# Found the test.
while "}" not in line:
line = line + self.f.readline()
line = line[line.find("{") : line.find("}") + 1]
return json.loads(line)
line = self.f.readline()
# Run deqp-vk with regres.
def runDeqp(deqp_path, testlist_path):
deqpVkParam = "--deqp-vk=" + deqp_path
validationLayerParam = "--validation=" + ARGS.validation
testListParam = "--test-list=" + testlist_path
run(["./run_testlist.sh", deqpVkParam, validationLayerParam, testListParam], working_dir=REGRES_DIR)
# Run commands.
def run(command: str, working_dir: str = Path.cwd()) -> None:
"""Run command using subprocess.run()"""
subprocess.run(command, cwd=working_dir, check=True)
# Set VK_ICD_FILENAMES
def setVkIcdFilenames():
os.environ["VK_ICD_FILENAMES"] = str(LINUX_SWS_ICD_DIR / "vk_swiftshader_icd.json")
print(f"VK_ICD_FILENAMES = {os.getenv('VK_ICD_FILENAMES')}")
# Choose the category/status to write results to.
def writeToStatus(test):
if test['Status'] == "PASS":
new_pass.append(test['Test'])
elif test['Status'] == "FAIL":
new_fail.append(test['Test'])
elif test['Status'] == "NOT_SUPPORTED" or test['Status'] == "UNSUPPORTED":
new_notsupported.append(test['Test'])
elif test['Status'] == "CRASH":
new_crash.append(test['Test'])
elif test['Status'] == "COMPATIBILITY_WARNING":
compatibility_warning.append(test['Test'])
elif test['Status'] == "QUALITY_WARNING":
quality_warning.append(test['Test'])
elif test['Status'] == "INTERNAL_ERROR":
internal_errors.append(test['Test'])
elif test['Status'] == "WAIVER":
waivers.append(test['Test'])
else:
raise RuntimeError(f"Expected PASS, FAIL, NOT_SUPPORTED, UNSUPPORTED, CRASH, COMPATIBILITY_WARNING, " +
f"QUALITY_WARNING, INTERNAL_ERROR or WAIVER as status, " +
f"got {test['Status']}. Is there an unhandled status case?")
# Compare two result.json files for regression.
def compareRuns(new_result, old_result):
print(f"Comparing files: {old_result} and {new_result}")
r0 = Result(new_result)
r1 = Result(old_result)
t0 = r0.readResult()
t1 = r1.readResult()
done = False
while not done:
# Old result file has ended, continue with new.
if t1 == None and t0 != None:
advance1 = False
writeToStatus(t0)
# New result file has ended, continue with old.
elif t0 == None and t1 != None:
advance0 = False
has_been_removed.append(t1['Test'])
# Both files have ended, stop iteration.
elif t1 == None and t0 == None:
done = True
# By default advance both files.
else:
advance0 = True
advance1 = True
if t0['Test'] == t1['Test']:
# The normal case where both files are in sync. Just check if the status matches.
if t0['Status'] != t1['Status']:
status_change.append(f"{t0['Test']}, new status: {t0['Status']}, old status: {t1['Status']}")
print(f"Status changed: {t0['Test']} {t0['Status']} vs {t1['Status']}")
else:
# Create temporary objects for searching through the whole file.
tmp0 = Result(r0.filename)
tmp1 = Result(r1.filename)
# Search the mismatching test cases from the opposite file.
s0 = tmp0.searchTest(t1['Test'])
s1 = tmp1.searchTest(t0['Test'])
# Old test not in new results
if not s0:
print(f"Missing old test {t1['Test']} from new file: {r0.filename}\n")
has_been_removed.append(t1['Test'])
# Don't advance this file since we already read a test case other than the missing one.
advance0 = False
# New test not in old results
if not s1:
print(f"Missing new test {t0['Test']} from old file: {r1.filename}\n")
writeToStatus(t0)
# Don't advance this file since we already read a test case other than the missing one.
advance1 = False
if s0 and s1:
# This should never happen because the test cases are in alphabetical order.
# Print an error and bail out.
raise RuntimeError(f"Tests in different locations: {t0['Test']}\n")
if not advance0 and not advance1:
# An exotic case where both tests are missing from the other file.
# Need to skip both.
advance0 = True
advance1 = True
if advance0:
t0 = r0.readResult()
if advance1:
t1 = r1.readResult()
result_file = str(COMP_RESULTS_DIR / "comparison_results_") + str(datetime.now().strftime('%m_%d_%Y_%H_%M_%S')) + ".txt"
print(f"Writing to file {result_file}")
COMP_RESULTS_DIR.mkdir(parents=True, exist_ok=True)
with open(result_file, "w") as log_file:
log_file.write("New passes:\n")
for line in new_pass:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("New fails:\n")
for line in new_fail:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("New crashes:\n")
for line in new_crash:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("New not_supported:\n")
for line in new_notsupported:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("Tests removed:\n")
for line in has_been_removed:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("Status changes:\n")
for line in status_change:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("Compatibility warnings:\n")
for line in compatibility_warning:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("Quality warnings:\n")
for line in quality_warning:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("Internal errors:\n")
for line in internal_errors:
log_file.write(line + "\n")
log_file.write("\n")
log_file.write("Waiver:\n")
for line in waivers:
log_file.write(line + "\n")
print(f"Comparison done. Results have been written to: {COMP_RESULTS_DIR}")
# Build VK-GL-CTS
def buildCts():
VK_GL_CTS_BUILD_DIR.mkdir(parents=True, exist_ok=True)
FETCH_SOURCES = str(VK_GL_CTS_ROOT_DIR / "external" / "fetch_sources.py")
run([which("python3"), FETCH_SOURCES], working_dir=VK_GL_CTS_ROOT_DIR)
# Build VK-GL-CTS
buildType = "-DCMAKE_BUILD_TYPE=" + ARGS.vk_gl_cts_build_type
run([which("cmake"), "-GNinja", str(VK_GL_CTS_ROOT_DIR), buildType], working_dir=VK_GL_CTS_BUILD_DIR)
run([which("ninja"), "deqp-vk"], working_dir=VK_GL_CTS_BUILD_DIR)
print(f"vk-gl-cts built to: {VK_GL_CTS_BUILD_DIR}")
# Clone and build SwiftShader and Vulkan validation layers.
def cloneSwsAndLayers():
# Clone SwiftShader or update if it already exists.
if not SWS_SRC_DIR.exists():
SWS_SRC_DIR.mkdir(parents=True, exist_ok=True)
run([which("git"), "clone", SWIFTSHADER_URL, SWS_SRC_DIR])
else:
run([which("git"), "pull", "origin"], working_dir=SWS_SRC_DIR)
# Build SwiftShader.
run([which("cmake"),
"-GNinja",
str(SWS_SRC_DIR),
"-DSWIFTSHADER_BUILD_EGL:BOOL=OFF",
"-DSWIFTSHADER_BUILD_GLESv2:BOOL=OFF",
"-DSWIFTSHADER_BUILD_TESTS:BOOL=OFF",
"-DINSTALL_GTEST=OFF",
"-DBUILD_TESTING:BOOL=OFF",
"-DENABLE_CTEST:BOOL=OFF",
"-DCMAKE_BUILD_TYPE=" + ARGS.sws_build_type],
working_dir=SWS_BUILD_DIR)
run([which("cmake"), "--build", ".", "--target", "vk_swiftshader"], working_dir=SWS_BUILD_DIR)
# Set Vulkan validation layers if flag is set.
if ARGS.validation == "true":
# Clone Vulkan validation layers or update if they already exist.
if not LAYERS_SRC_DIR.exists():
LAYERS_SRC_DIR.mkdir(parents=True, exist_ok=True)
run([which("git"), "clone", LAYERS_URL, LAYERS_SRC_DIR])
else:
run([which("git"), "pull", "origin"], working_dir=LAYERS_SRC_DIR)
# Build and set Vulkan validation layers.
LAYERS_BUILD_DIR.mkdir(parents=True, exist_ok=True)
UPDATE_DEPS = str(LAYERS_SRC_DIR / "scripts" / "update_deps.py")
run([which("python3"), UPDATE_DEPS], working_dir=LAYERS_BUILD_DIR)
run([which("cmake"),
"-GNinja",
"-C",
"helper.cmake",
LAYERS_SRC_DIR],
working_dir=LAYERS_BUILD_DIR)
run([which("cmake"), "--build", "."], working_dir=LAYERS_BUILD_DIR)
LAYERS_PATH = str(LAYERS_BUILD_DIR / "layers")
os.environ["VK_LAYER_PATH"] = LAYERS_PATH
print(f"Tools cloned and built in: {PARENT_DIR}")
# Run cts with regres and move result files accordingly.
def runCts():
setVkIcdFilenames()
# Run cts and copy the resulting file to RESULT_DIR.
print("Running cts...")
runDeqp(str(DEQP_VK_BINARY), str(MUSTPASS_LIST))
RESULT_DIR.mkdir(parents=True, exist_ok=True)
copyfile(str(REGRES_DIR / "results.json"), str(RESULT_DIR / ARGS.result_output))
print("Run completed.")
print(f"Result file copied to: {RESULT_DIR}")
exit(0)
# Recipe for running cts.
if ARGS.recipe == "run-deqp":
cloneSwsAndLayers()
if ARGS.deqp_vk is None:
buildCts()
runCts()
# Recipe for only comparing the already existing result files.
if ARGS.recipe == "check-comparison":
if ARGS.files is None:
raise RuntimeError("No comparable files provided. Please provide them with flag --files. Use --help for more info.")
newFile, oldFile = ARGS.files
compareRuns(str(newFile), str(oldFile))