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 # - GO needs to be installed to use regres. (apt install golang-go)
23 from argparse import ArgumentParser
24 from shutil import which, copyfile, move
25 from pathlib import Path
26 from datetime import datetime
34 help="Path to directory that will be used as root for cloning and file saving.",
35 default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader")
42 help="URL of SwiftShader Git repository.",
43 default="https://swiftshader.googlesource.com/SwiftShader",
50 help="URL of Validation Layers Git repository.",
51 default="https://github.com/KhronosGroup/Vulkan-ValidationLayers.git",
56 metavar="SWS_BUILD_TYPE",
58 help="SwiftShader build type.",
59 choices=["debug", "release"],
67 help="Path to deqp-vk binary.",
74 help="Path to vk-gl-cts source directory.",
79 metavar="VK_GL_CTS_BUILD",
81 help="Path to vk-gl-cts build directory.",
82 default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader" / "vk-gl-cts-build"),
86 "--vk_gl_cts_build_type",
87 metavar="VK_GL_CTS_BUILD_TYPE",
89 help="vk-gl-cts build type.",
90 choices=["debug", "release"],
98 help="Recipes to only run parts of script.",
99 choices=["run-deqp", "check-comparison"],
106 metavar=("NEWER_FILE_PATH", "OLDER_FILE_PATH"),
108 help="Compare two different run results.",
113 metavar="VALIDATION",
115 help="Enable vulkan validation layers.",
116 choices=["true", "false"],
124 help="Filename of the regres results.",
125 default=str("result_" + str(datetime.now().strftime('%m_%d_%Y_%H_%M_%S')) + ".json"),
128 ARGS = AP.parse_args()
130 # Check that we have everything needed to run the script when using recipe run-deqp.
131 if ARGS.recipe == "run-deqp":
132 if which("go") is None:
133 raise RuntimeError("go not found.")
134 if which("cmake") is None:
135 raise RuntimeError("CMake not found.")
136 if which("ninja") is None:
137 raise RuntimeError("Ninja not found.")
138 if which("git") is None:
139 raise RuntimeError("Git not found.")
140 if ARGS.vk_gl_cts is None:
141 raise RuntimeError("vk-gl-cts source directory must be provided. Use --help for more info.")
143 PARENT_DIR = Path(ARGS.directory).resolve()
145 SWS_SRC_DIR = PARENT_DIR / "SwiftShader"
146 SWS_BUILD_DIR = SWS_SRC_DIR / "build"
147 SWIFTSHADER_URL = ARGS.url
149 LAYERS_PARENT_DIR = Path(ARGS.directory).resolve()
150 LAYERS_SRC_DIR = LAYERS_PARENT_DIR / "Vulkan_Validation_Layers"
151 LAYERS_URL = ARGS.vlayer_url
152 LAYERS_BUILD_DIR = LAYERS_SRC_DIR / "build"
154 LINUX_SWS_ICD_DIR = SWS_BUILD_DIR / "Linux"
155 REGRES_DIR = SWS_SRC_DIR / "tests" / "regres"
156 RESULT_DIR = PARENT_DIR / "regres_results"
157 COMP_RESULTS_DIR = PARENT_DIR / "comparison_results"
159 VK_GL_CTS_ROOT_DIR = Path(ARGS.vk_gl_cts)
160 VK_GL_CTS_BUILD_DIR = Path(ARGS.vk_gl_cts_build)
161 MUSTPASS_LIST = VK_GL_CTS_ROOT_DIR / "external" / "vulkancts" / "mustpass" / "master" / "vk-default.txt"
162 if ARGS.deqp_vk is None:
163 DEQP_VK_BINARY = VK_GL_CTS_BUILD_DIR / "external" / "vulkancts" / "modules" / "vulkan" / "deqp-vk"
165 DEQP_VK_BINARY = str(ARGS.deqp_vk)
170 new_notsupported = []
171 has_been_removed = []
173 compatibility_warning = []
179 def __init__(self, filename):
180 self.filename = filename
181 self.f = open(filename)
182 # Skip the first four lines and check that the file order has not been changed.
185 tmp = tmp + self.f.readline()
186 if "Tests" not in tmp:
187 raise RuntimeError("Skipped four lines, no starting line found. Has the file order changed?")
189 # Reads one test item from the file.
190 def readResult(self):
193 while "}" not in tmp:
194 tmp = tmp + self.f.readline()
196 tmp = tmp[tmp.find("{") : tmp.find("}") + 1]
197 return json.loads(tmp)
201 # Search for a test name. Returns the test data if found and otherwise False.
202 def searchTest(self, test):
203 line = self.f.readline()
205 if line.find(test) != -1:
207 while "}" not in line:
208 line = line + self.f.readline()
210 line = line[line.find("{") : line.find("}") + 1]
211 return json.loads(line)
212 line = self.f.readline()
214 # Run deqp-vk with regres.
215 def runDeqp(deqp_path, testlist_path):
216 deqpVkParam = "--deqp-vk=" + deqp_path
217 validationLayerParam = "--validation=" + ARGS.validation
218 testListParam = "--test-list=" + testlist_path
219 run(["./run_testlist.sh", deqpVkParam, validationLayerParam, testListParam], working_dir=REGRES_DIR)
222 def run(command: str, working_dir: str = Path.cwd()) -> None:
223 """Run command using subprocess.run()"""
224 subprocess.run(command, cwd=working_dir, check=True)
226 # Set VK_ICD_FILENAMES
227 def setVkIcdFilenames():
228 os.environ["VK_ICD_FILENAMES"] = str(LINUX_SWS_ICD_DIR / "vk_swiftshader_icd.json")
229 print(f"VK_ICD_FILENAMES = {os.getenv('VK_ICD_FILENAMES')}")
231 # Choose the category/status to write results to.
232 def writeToStatus(test):
233 if test['Status'] == "PASS":
234 new_pass.append(test['Test'])
235 elif test['Status'] == "FAIL":
236 new_fail.append(test['Test'])
237 elif test['Status'] == "NOT_SUPPORTED" or test['Status'] == "UNSUPPORTED":
238 new_notsupported.append(test['Test'])
239 elif test['Status'] == "CRASH":
240 new_crash.append(test['Test'])
241 elif test['Status'] == "COMPATIBILITY_WARNING":
242 compatibility_warning.append(test['Test'])
243 elif test['Status'] == "QUALITY_WARNING":
244 quality_warning.append(test['Test'])
245 elif test['Status'] == "INTERNAL_ERROR":
246 internal_errors.append(test['Test'])
247 elif test['Status'] == "WAIVER":
248 waivers.append(test['Test'])
250 raise RuntimeError(f"Expected PASS, FAIL, NOT_SUPPORTED, UNSUPPORTED, CRASH, COMPATIBILITY_WARNING, " +
251 f"QUALITY_WARNING, INTERNAL_ERROR or WAIVER as status, " +
252 f"got {test['Status']}. Is there an unhandled status case?")
254 # Compare two result.json files for regression.
255 def compareRuns(new_result, old_result):
256 print(f"Comparing files: {old_result} and {new_result}")
258 r0 = Result(new_result)
259 r1 = Result(old_result)
267 # Old result file has ended, continue with new.
268 if t1 == None and t0 != None:
271 # New result file has ended, continue with old.
272 elif t0 == None and t1 != None:
274 has_been_removed.append(t1['Test'])
275 # Both files have ended, stop iteration.
276 elif t1 == None and t0 == None:
278 # By default advance both files.
283 if t0['Test'] == t1['Test']:
284 # The normal case where both files are in sync. Just check if the status matches.
285 if t0['Status'] != t1['Status']:
286 status_change.append(f"{t0['Test']}, new status: {t0['Status']}, old status: {t1['Status']}")
287 print(f"Status changed: {t0['Test']} {t0['Status']} vs {t1['Status']}")
289 # Create temporary objects for searching through the whole file.
290 tmp0 = Result(r0.filename)
291 tmp1 = Result(r1.filename)
293 # Search the mismatching test cases from the opposite file.
294 s0 = tmp0.searchTest(t1['Test'])
295 s1 = tmp1.searchTest(t0['Test'])
297 # Old test not in new results
299 print(f"Missing old test {t1['Test']} from new file: {r0.filename}\n")
300 has_been_removed.append(t1['Test'])
301 # Don't advance this file since we already read a test case other than the missing one.
304 # New test not in old results
306 print(f"Missing new test {t0['Test']} from old file: {r1.filename}\n")
308 # Don't advance this file since we already read a test case other than the missing one.
312 # This should never happen because the test cases are in alphabetical order.
313 # Print an error and bail out.
314 raise RuntimeError(f"Tests in different locations: {t0['Test']}\n")
316 if not advance0 and not advance1:
317 # An exotic case where both tests are missing from the other file.
327 result_file = str(COMP_RESULTS_DIR / "comparison_results_") + str(datetime.now().strftime('%m_%d_%Y_%H_%M_%S')) + ".txt"
328 print(f"Writing to file {result_file}")
329 COMP_RESULTS_DIR.mkdir(parents=True, exist_ok=True)
331 with open(result_file, "w") as log_file:
332 log_file.write("New passes:\n")
333 for line in new_pass:
334 log_file.write(line + "\n")
337 log_file.write("New fails:\n")
338 for line in new_fail:
339 log_file.write(line + "\n")
342 log_file.write("New crashes:\n")
343 for line in new_crash:
344 log_file.write(line + "\n")
347 log_file.write("New not_supported:\n")
348 for line in new_notsupported:
349 log_file.write(line + "\n")
352 log_file.write("Tests removed:\n")
353 for line in has_been_removed:
354 log_file.write(line + "\n")
357 log_file.write("Status changes:\n")
358 for line in status_change:
359 log_file.write(line + "\n")
362 log_file.write("Compatibility warnings:\n")
363 for line in compatibility_warning:
364 log_file.write(line + "\n")
367 log_file.write("Quality warnings:\n")
368 for line in quality_warning:
369 log_file.write(line + "\n")
372 log_file.write("Internal errors:\n")
373 for line in internal_errors:
374 log_file.write(line + "\n")
377 log_file.write("Waiver:\n")
379 log_file.write(line + "\n")
381 print(f"Comparison done. Results have been written to: {COMP_RESULTS_DIR}")
385 VK_GL_CTS_BUILD_DIR.mkdir(parents=True, exist_ok=True)
387 FETCH_SOURCES = str(VK_GL_CTS_ROOT_DIR / "external" / "fetch_sources.py")
388 run([which("python3"), FETCH_SOURCES], working_dir=VK_GL_CTS_ROOT_DIR)
391 buildType = "-DCMAKE_BUILD_TYPE=" + ARGS.vk_gl_cts_build_type
392 run([which("cmake"), "-GNinja", str(VK_GL_CTS_ROOT_DIR), buildType], working_dir=VK_GL_CTS_BUILD_DIR)
393 run([which("ninja")], working_dir=VK_GL_CTS_BUILD_DIR)
394 print(f"vk-gl-cts built to: {VK_GL_CTS_BUILD_DIR}")
396 # Clone and build SwiftShader and Vulkan validation layers.
397 def cloneSwsAndLayers():
398 # Clone SwiftShader or update if it already exists.
399 if not SWS_SRC_DIR.exists():
400 SWS_SRC_DIR.mkdir(parents=True, exist_ok=True)
401 run([which("git"), "clone", SWIFTSHADER_URL, SWS_SRC_DIR])
403 run([which("git"), "pull", "origin"], working_dir=SWS_SRC_DIR)
406 buildType = "-DCMAKE_BUILD_TYPE=" + ARGS.sws_build_type
407 # Set env variables if clang build path is set.
408 if os.getenv("CXX") is None:
409 os.environ["CXX"] = "clang++"
410 if os.getenv("CC") is None:
411 os.environ["CC"] = "clang"
412 run([which("cmake"), "-GNinja", str(SWS_SRC_DIR), buildType], working_dir=SWS_BUILD_DIR)
413 run([which("cmake"), "--build", "."], working_dir=SWS_BUILD_DIR)
415 # Set Vulkan validation layers if flag is set.
416 if ARGS.validation == "true":
417 # Clone Vulkan validation layers or update if they already exist.
418 if not LAYERS_SRC_DIR.exists():
419 LAYERS_SRC_DIR.mkdir(parents=True, exist_ok=True)
420 run([which("git"), "clone", LAYERS_URL, LAYERS_SRC_DIR])
422 run([which("git"), "pull", "origin"], working_dir=LAYERS_SRC_DIR)
424 # Build and set Vulkan validation layers.
425 LAYERS_BUILD_DIR.mkdir(parents=True, exist_ok=True)
426 UPDATE_DEPS = str(LAYERS_SRC_DIR / "scripts" / "update_deps.py")
427 run([which("python3"), UPDATE_DEPS], working_dir=LAYERS_BUILD_DIR)
433 working_dir=LAYERS_BUILD_DIR)
434 run([which("cmake"), "--build", "."], working_dir=LAYERS_BUILD_DIR)
435 LAYERS_PATH = str(LAYERS_BUILD_DIR / "layers")
436 os.environ["VK_LAYER_PATH"] = LAYERS_PATH
437 print(f"Tools cloned and built in: {PARENT_DIR}")
439 # Run cts with regres and move result files accordingly.
443 # Run cts and copy the resulting file to RESULT_DIR.
444 print("Running cts...")
445 runDeqp(str(DEQP_VK_BINARY), str(MUSTPASS_LIST))
446 RESULT_DIR.mkdir(parents=True, exist_ok=True)
447 copyfile(str(REGRES_DIR / "results.json"), str(RESULT_DIR / ARGS.result_output))
448 print("Run completed.")
449 print(f"Result file copied to: {RESULT_DIR}")
452 # Recipe for running cts.
453 if ARGS.recipe == "run-deqp":
455 if ARGS.deqp_vk is None:
459 # Recipe for only comparing the already existing result files.
460 if ARGS.recipe == "check-comparison":
461 if ARGS.files is None:
462 raise RuntimeError("No comparable files provided. Please provide them with flag --files. Use --help for more info.")
463 newFile, oldFile = ARGS.files
464 compareRuns(str(newFile), str(oldFile))