1 # Copyright 2021 Google LLC.
2 # Copyright 2021 The Khronos Group Inc.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # https://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # Requirements to run the script:
17 # - Python3 (apt-get install -y python3.x)
18 # - GO (apt-get install -y golang-go)
19 # - cmake (version 3.13 or later)
20 # - ninja (apt-get install -y ninja-build)
21 # - git (sudo apt-get install -y git)
23 # GO dependencies needed:
24 # - crypto/openpgp (go get -u golang.org/x/crypto/openpgp...)
32 from argparse import ArgumentParser
33 from shutil import which, copyfile
34 from pathlib import Path
35 from datetime import datetime
37 # Check for correct python version (python3) before doing anything.
38 if sys.version_info.major < 3:
39 raise RuntimeError("Python version needs to be 3 or greater.")
47 help="Path to directory that will be used as root for cloning and file saving.",
48 default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader")
55 help="URL of SwiftShader Git repository.",
56 default="https://swiftshader.googlesource.com/SwiftShader",
63 help="URL of Validation Layers Git repository.",
64 default="https://github.com/KhronosGroup/Vulkan-ValidationLayers.git",
69 metavar="SWS_BUILD_TYPE",
71 help="SwiftShader build type.",
72 choices=["debug", "release"],
80 help="Path to deqp-vk binary.",
87 help="Path to vk-gl-cts source directory.",
92 metavar="VK_GL_CTS_BUILD",
94 help="Path to vk-gl-cts build directory.",
95 default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader" / "vk-gl-cts-build"),
99 "--vk_gl_cts_build_type",
100 metavar="VK_GL_CTS_BUILD_TYPE",
102 help="vk-gl-cts build type.",
103 choices=["debug", "release"],
111 help="Recipes to only run parts of script.",
112 choices=["run-deqp", "check-comparison"],
119 metavar=("NEWER_FILE_PATH", "OLDER_FILE_PATH"),
121 help="Compare two different run results.",
126 metavar="VALIDATION",
128 help="Enable vulkan validation layers.",
129 choices=["true", "false"],
137 help="Filename of the regres results.",
138 default=str("result_" + str(datetime.now().strftime('%m_%d_%Y_%H_%M_%S')) + ".json"),
141 ARGS = AP.parse_args()
143 # Check that we have everything needed to run the script when using recipe run-deqp.
144 if ARGS.recipe == "run-deqp":
145 if which("go") is None:
146 raise RuntimeError("go not found. (apt-get install -y golang-go)")
147 if which("cmake") is None:
148 raise RuntimeError("CMake not found. (version 3.13 or later needed)")
149 if which("ninja") is None:
150 raise RuntimeError("Ninja not found. (apt-get install -y ninja-build)")
151 if which("git") is None:
152 raise RuntimeError("Git not found. (apt-get install -y git)")
153 if ARGS.vk_gl_cts is None:
154 raise RuntimeError("vk-gl-cts source directory must be provided. Use --help for more info.")
156 PARENT_DIR = Path(ARGS.directory).resolve()
158 SWS_SRC_DIR = PARENT_DIR / "SwiftShader"
159 SWS_BUILD_DIR = SWS_SRC_DIR / "build"
160 SWIFTSHADER_URL = ARGS.url
162 LAYERS_PARENT_DIR = Path(ARGS.directory).resolve()
163 LAYERS_SRC_DIR = LAYERS_PARENT_DIR / "Vulkan_Validation_Layers"
164 LAYERS_URL = ARGS.vlayer_url
165 LAYERS_BUILD_DIR = LAYERS_SRC_DIR / "build"
167 LINUX_SWS_ICD_DIR = SWS_BUILD_DIR / "Linux"
168 REGRES_DIR = SWS_SRC_DIR / "tests" / "regres"
169 RESULT_DIR = PARENT_DIR / "regres_results"
170 COMP_RESULTS_DIR = PARENT_DIR / "comparison_results"
172 VK_GL_CTS_ROOT_DIR = Path(ARGS.vk_gl_cts)
173 VK_GL_CTS_BUILD_DIR = Path(ARGS.vk_gl_cts_build)
174 MUSTPASS_LIST = VK_GL_CTS_ROOT_DIR / "external" / "vulkancts" / "mustpass" / "master" / "vk-default.txt"
175 if ARGS.deqp_vk is None:
176 DEQP_VK_BINARY = VK_GL_CTS_BUILD_DIR / "external" / "vulkancts" / "modules" / "vulkan" / "deqp-vk"
178 DEQP_VK_BINARY = str(ARGS.deqp_vk)
183 new_notsupported = []
184 has_been_removed = []
186 compatibility_warning = []
192 def __init__(self, filename):
193 self.filename = filename
194 self.f = open(filename)
195 # Skip the first four lines and check that the file order has not been changed.
198 tmp = tmp + self.f.readline()
199 if "Tests" not in tmp:
200 raise RuntimeError("Skipped four lines, no starting line found. Has the file order changed?")
202 # Reads one test item from the file.
203 def readResult(self):
206 while "}" not in tmp:
207 tmp = tmp + self.f.readline()
209 tmp = tmp[tmp.find("{") : tmp.find("}") + 1]
210 return json.loads(tmp)
214 # Search for a test name. Returns the test data if found and otherwise False.
215 def searchTest(self, test):
216 line = self.f.readline()
218 if line.find(test) != -1:
220 while "}" not in line:
221 line = line + self.f.readline()
223 line = line[line.find("{") : line.find("}") + 1]
224 return json.loads(line)
225 line = self.f.readline()
227 # Run deqp-vk with regres.
228 def runDeqp(deqp_path, testlist_path):
229 deqpVkParam = "--deqp-vk=" + deqp_path
230 validationLayerParam = "--validation=" + ARGS.validation
231 testListParam = "--test-list=" + testlist_path
232 run(["./run_testlist.sh", deqpVkParam, validationLayerParam, testListParam], working_dir=REGRES_DIR)
235 def run(command: str, working_dir: str = Path.cwd()) -> None:
236 """Run command using subprocess.run()"""
237 subprocess.run(command, cwd=working_dir, check=True)
239 # Set VK_ICD_FILENAMES
240 def setVkIcdFilenames():
241 os.environ["VK_ICD_FILENAMES"] = str(LINUX_SWS_ICD_DIR / "vk_swiftshader_icd.json")
242 print(f"VK_ICD_FILENAMES = {os.getenv('VK_ICD_FILENAMES')}")
244 # Choose the category/status to write results to.
245 def writeToStatus(test):
246 if test['Status'] == "PASS":
247 new_pass.append(test['Test'])
248 elif test['Status'] == "FAIL":
249 new_fail.append(test['Test'])
250 elif test['Status'] == "NOT_SUPPORTED" or test['Status'] == "UNSUPPORTED":
251 new_notsupported.append(test['Test'])
252 elif test['Status'] == "CRASH":
253 new_crash.append(test['Test'])
254 elif test['Status'] == "COMPATIBILITY_WARNING":
255 compatibility_warning.append(test['Test'])
256 elif test['Status'] == "QUALITY_WARNING":
257 quality_warning.append(test['Test'])
258 elif test['Status'] == "INTERNAL_ERROR":
259 internal_errors.append(test['Test'])
260 elif test['Status'] == "WAIVER":
261 waivers.append(test['Test'])
263 raise RuntimeError(f"Expected PASS, FAIL, NOT_SUPPORTED, UNSUPPORTED, CRASH, COMPATIBILITY_WARNING, " +
264 f"QUALITY_WARNING, INTERNAL_ERROR or WAIVER as status, " +
265 f"got {test['Status']}. Is there an unhandled status case?")
267 # Compare two result.json files for regression.
268 def compareRuns(new_result, old_result):
269 print(f"Comparing files: {old_result} and {new_result}")
271 r0 = Result(new_result)
272 r1 = Result(old_result)
280 # Old result file has ended, continue with new.
281 if t1 == None and t0 != None:
284 # New result file has ended, continue with old.
285 elif t0 == None and t1 != None:
287 has_been_removed.append(t1['Test'])
288 # Both files have ended, stop iteration.
289 elif t1 == None and t0 == None:
291 # By default advance both files.
296 if t0['Test'] == t1['Test']:
297 # The normal case where both files are in sync. Just check if the status matches.
298 if t0['Status'] != t1['Status']:
299 status_change.append(f"{t0['Test']}, new status: {t0['Status']}, old status: {t1['Status']}")
300 print(f"Status changed: {t0['Test']} {t0['Status']} vs {t1['Status']}")
302 # Create temporary objects for searching through the whole file.
303 tmp0 = Result(r0.filename)
304 tmp1 = Result(r1.filename)
306 # Search the mismatching test cases from the opposite file.
307 s0 = tmp0.searchTest(t1['Test'])
308 s1 = tmp1.searchTest(t0['Test'])
310 # Old test not in new results
312 print(f"Missing old test {t1['Test']} from new file: {r0.filename}\n")
313 has_been_removed.append(t1['Test'])
314 # Don't advance this file since we already read a test case other than the missing one.
317 # New test not in old results
319 print(f"Missing new test {t0['Test']} from old file: {r1.filename}\n")
321 # Don't advance this file since we already read a test case other than the missing one.
325 # This should never happen because the test cases are in alphabetical order.
326 # Print an error and bail out.
327 raise RuntimeError(f"Tests in different locations: {t0['Test']}\n")
329 if not advance0 and not advance1:
330 # An exotic case where both tests are missing from the other file.
340 result_file = str(COMP_RESULTS_DIR / "comparison_results_") + str(datetime.now().strftime('%m_%d_%Y_%H_%M_%S')) + ".txt"
341 print(f"Writing to file {result_file}")
342 COMP_RESULTS_DIR.mkdir(parents=True, exist_ok=True)
344 with open(result_file, "w") as log_file:
345 log_file.write("New passes:\n")
346 for line in new_pass:
347 log_file.write(line + "\n")
350 log_file.write("New fails:\n")
351 for line in new_fail:
352 log_file.write(line + "\n")
355 log_file.write("New crashes:\n")
356 for line in new_crash:
357 log_file.write(line + "\n")
360 log_file.write("New not_supported:\n")
361 for line in new_notsupported:
362 log_file.write(line + "\n")
365 log_file.write("Tests removed:\n")
366 for line in has_been_removed:
367 log_file.write(line + "\n")
370 log_file.write("Status changes:\n")
371 for line in status_change:
372 log_file.write(line + "\n")
375 log_file.write("Compatibility warnings:\n")
376 for line in compatibility_warning:
377 log_file.write(line + "\n")
380 log_file.write("Quality warnings:\n")
381 for line in quality_warning:
382 log_file.write(line + "\n")
385 log_file.write("Internal errors:\n")
386 for line in internal_errors:
387 log_file.write(line + "\n")
390 log_file.write("Waiver:\n")
392 log_file.write(line + "\n")
394 print(f"Comparison done. Results have been written to: {COMP_RESULTS_DIR}")
398 VK_GL_CTS_BUILD_DIR.mkdir(parents=True, exist_ok=True)
400 FETCH_SOURCES = str(VK_GL_CTS_ROOT_DIR / "external" / "fetch_sources.py")
401 run([which("python3"), FETCH_SOURCES], working_dir=VK_GL_CTS_ROOT_DIR)
404 buildType = "-DCMAKE_BUILD_TYPE=" + ARGS.vk_gl_cts_build_type
405 run([which("cmake"), "-GNinja", str(VK_GL_CTS_ROOT_DIR), buildType], working_dir=VK_GL_CTS_BUILD_DIR)
406 run([which("ninja"), "deqp-vk"], working_dir=VK_GL_CTS_BUILD_DIR)
407 print(f"vk-gl-cts built to: {VK_GL_CTS_BUILD_DIR}")
409 # Clone and build SwiftShader and Vulkan validation layers.
410 def cloneSwsAndLayers():
411 # Clone SwiftShader or update if it already exists.
412 if not SWS_SRC_DIR.exists():
413 SWS_SRC_DIR.mkdir(parents=True, exist_ok=True)
414 run([which("git"), "clone", SWIFTSHADER_URL, SWS_SRC_DIR])
416 run([which("git"), "pull", "origin"], working_dir=SWS_SRC_DIR)
422 "-DSWIFTSHADER_BUILD_EGL:BOOL=OFF",
423 "-DSWIFTSHADER_BUILD_GLESv2:BOOL=OFF",
424 "-DSWIFTSHADER_BUILD_TESTS:BOOL=OFF",
425 "-DINSTALL_GTEST=OFF",
426 "-DBUILD_TESTING:BOOL=OFF",
427 "-DENABLE_CTEST:BOOL=OFF",
428 "-DCMAKE_BUILD_TYPE=" + ARGS.sws_build_type],
429 working_dir=SWS_BUILD_DIR)
430 run([which("cmake"), "--build", ".", "--target", "vk_swiftshader"], working_dir=SWS_BUILD_DIR)
432 # Set Vulkan validation layers if flag is set.
433 if ARGS.validation == "true":
434 # Clone Vulkan validation layers or update if they already exist.
435 if not LAYERS_SRC_DIR.exists():
436 LAYERS_SRC_DIR.mkdir(parents=True, exist_ok=True)
437 run([which("git"), "clone", LAYERS_URL, LAYERS_SRC_DIR])
439 run([which("git"), "pull", "origin"], working_dir=LAYERS_SRC_DIR)
441 # Build and set Vulkan validation layers.
442 LAYERS_BUILD_DIR.mkdir(parents=True, exist_ok=True)
443 UPDATE_DEPS = str(LAYERS_SRC_DIR / "scripts" / "update_deps.py")
444 run([which("python3"), UPDATE_DEPS], working_dir=LAYERS_BUILD_DIR)
450 working_dir=LAYERS_BUILD_DIR)
451 run([which("cmake"), "--build", "."], working_dir=LAYERS_BUILD_DIR)
452 LAYERS_PATH = str(LAYERS_BUILD_DIR / "layers")
453 os.environ["VK_LAYER_PATH"] = LAYERS_PATH
454 print(f"Tools cloned and built in: {PARENT_DIR}")
456 # Run cts with regres and move result files accordingly.
460 # Run cts and copy the resulting file to RESULT_DIR.
461 print("Running cts...")
462 runDeqp(str(DEQP_VK_BINARY), str(MUSTPASS_LIST))
463 RESULT_DIR.mkdir(parents=True, exist_ok=True)
464 copyfile(str(REGRES_DIR / "results.json"), str(RESULT_DIR / ARGS.result_output))
465 print("Run completed.")
466 print(f"Result file copied to: {RESULT_DIR}")
469 # Recipe for running cts.
470 if ARGS.recipe == "run-deqp":
472 if ARGS.deqp_vk is None:
476 # Recipe for only comparing the already existing result files.
477 if ARGS.recipe == "check-comparison":
478 if ARGS.files is None:
479 raise RuntimeError("No comparable files provided. Please provide them with flag --files. Use --help for more info.")
480 newFile, oldFile = ARGS.files
481 compareRuns(str(newFile), str(oldFile))