• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 Google LLC.
2# Copyright 2021 The Khronos Group Inc.
3#
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
7#
8#     https://www.apache.org/licenses/LICENSE-2.0
9#
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.
15
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)
22
23# GO dependencies needed:
24# - crypto/openpgp (go get -u golang.org/x/crypto/openpgp...)
25
26import os
27import json
28import tempfile
29import subprocess
30import sys
31
32from argparse import ArgumentParser
33from shutil import which, copyfile
34from pathlib import Path
35from datetime import datetime
36
37# Check for correct python version (python3) before doing anything.
38if sys.version_info.major < 3:
39        raise RuntimeError("Python version needs to be 3 or greater.")
40
41AP = ArgumentParser()
42AP.add_argument(
43    "-d",
44    "--directory",
45    metavar="DIRECTORY",
46    type=str,
47    help="Path to directory that will be used as root for cloning and file saving.",
48    default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader")
49)
50AP.add_argument(
51    "-u",
52    "--url",
53    metavar="URL",
54    type=str,
55    help="URL of SwiftShader Git repository.",
56    default="https://swiftshader.googlesource.com/SwiftShader",
57)
58AP.add_argument(
59    "-l",
60    "--vlayer_url",
61    metavar="VURL",
62    type=str,
63    help="URL of Validation Layers Git repository.",
64    default="https://github.com/KhronosGroup/Vulkan-ValidationLayers.git",
65)
66AP.add_argument(
67    "-b",
68    "--sws_build_type",
69    metavar="SWS_BUILD_TYPE",
70    type=str,
71    help="SwiftShader build type.",
72    choices=["debug", "release"],
73    default="debug",
74)
75AP.add_argument(
76    "-q",
77    "--deqp_vk",
78    metavar="DEQP_VK",
79    type=str,
80    help="Path to deqp-vk binary.",
81)
82AP.add_argument(
83    "-v",
84    "--vk_gl_cts",
85    metavar="VK_GL_CTS",
86    type=str,
87    help="Path to vk-gl-cts source directory.",
88)
89AP.add_argument(
90    "-w",
91    "--vk_gl_cts_build",
92    metavar="VK_GL_CTS_BUILD",
93    type=str,
94    help="Path to vk-gl-cts build directory.",
95    default=str(Path(tempfile.gettempdir()) / "deqp-swiftshader" / "vk-gl-cts-build"),
96)
97AP.add_argument(
98    "-t",
99    "--vk_gl_cts_build_type",
100    metavar="VK_GL_CTS_BUILD_TYPE",
101    type=str,
102    help="vk-gl-cts build type.",
103    choices=["debug", "release"],
104    default="debug",
105)
106AP.add_argument(
107    "-r",
108    "--recipe",
109    metavar="RECIPE",
110    type=str,
111    help="Recipes to only run parts of script.",
112    choices=["run-deqp", "check-comparison"],
113    default="run-deqp",
114)
115AP.add_argument(
116    "-f",
117    "--files",
118    nargs=2,
119    metavar=("NEWER_FILE_PATH", "OLDER_FILE_PATH"),
120    type=str,
121    help="Compare two different run results.",
122)
123AP.add_argument(
124    "-a",
125    "--validation",
126    metavar="VALIDATION",
127    type=str,
128    help="Enable vulkan validation layers.",
129    choices=["true", "false"],
130    default="false",
131)
132AP.add_argument(
133    "-o",
134    "--result_output",
135    metavar="OUTPUT",
136    type=str,
137    help="Filename of the regres results.",
138    default=str("result_" + str(datetime.now().strftime('%m_%d_%Y_%H_%M_%S')) + ".json"),
139)
140
141ARGS = AP.parse_args()
142
143# Check that we have everything needed to run the script when using recipe run-deqp.
144if 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.")
155
156PARENT_DIR = Path(ARGS.directory).resolve()
157
158SWS_SRC_DIR = PARENT_DIR / "SwiftShader"
159SWS_BUILD_DIR = SWS_SRC_DIR / "build"
160SWIFTSHADER_URL = ARGS.url
161
162LAYERS_PARENT_DIR = Path(ARGS.directory).resolve()
163LAYERS_SRC_DIR = LAYERS_PARENT_DIR / "Vulkan_Validation_Layers"
164LAYERS_URL = ARGS.vlayer_url
165LAYERS_BUILD_DIR = LAYERS_SRC_DIR / "build"
166
167LINUX_SWS_ICD_DIR = SWS_BUILD_DIR / "Linux"
168REGRES_DIR = SWS_SRC_DIR / "tests" / "regres"
169RESULT_DIR = PARENT_DIR / "regres_results"
170COMP_RESULTS_DIR = PARENT_DIR / "comparison_results"
171
172VK_GL_CTS_ROOT_DIR = Path(ARGS.vk_gl_cts)
173VK_GL_CTS_BUILD_DIR = Path(ARGS.vk_gl_cts_build)
174MUSTPASS_LIST = VK_GL_CTS_ROOT_DIR / "external" / "vulkancts" / "mustpass" / "master" / "vk-default.txt"
175if ARGS.deqp_vk is None:
176    DEQP_VK_BINARY = VK_GL_CTS_BUILD_DIR / "external" / "vulkancts" / "modules" / "vulkan" / "deqp-vk"
177else:
178    DEQP_VK_BINARY = str(ARGS.deqp_vk)
179
180new_pass = []
181new_fail = []
182new_crash = []
183new_notsupported = []
184has_been_removed = []
185status_change = []
186compatibility_warning = []
187quality_warning = []
188internal_errors = []
189waivers = []
190
191class Result:
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.
196        tmp = ""
197        for i in range(4):
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?")
201
202    # Reads one test item from the file.
203    def readResult(self):
204        while True:
205            tmp = ""
206            while "}" not in tmp:
207                tmp = tmp + self.f.readline()
208            if "Test" in tmp:
209                tmp = tmp[tmp.find("{") : tmp.find("}") + 1]
210                return json.loads(tmp)
211            else:
212                return None
213
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()
217        while line:
218            if line.find(test) != -1:
219                # Found the test.
220                while "}" not in line:
221                    line = line + self.f.readline()
222
223                line = line[line.find("{") : line.find("}") + 1]
224                return json.loads(line)
225            line = self.f.readline()
226
227# Run deqp-vk with regres.
228def 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)
233
234# Run commands.
235def run(command: str, working_dir: str = Path.cwd()) -> None:
236    """Run command using subprocess.run()"""
237    subprocess.run(command, cwd=working_dir, check=True)
238
239# Set VK_ICD_FILENAMES
240def 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')}")
243
244# Choose the category/status to write results to.
245def 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'])
262    else:
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?")
266
267# Compare two result.json files for regression.
268def compareRuns(new_result, old_result):
269    print(f"Comparing files: {old_result} and {new_result}")
270
271    r0 = Result(new_result)
272    r1 = Result(old_result)
273
274    t0 = r0.readResult()
275    t1 = r1.readResult()
276
277    done = False
278
279    while not done:
280        # Old result file has ended, continue with new.
281        if t1 == None and t0 != None:
282            advance1 = False
283            writeToStatus(t0)
284        # New result file has ended, continue with old.
285        elif t0 == None and t1 != None:
286            advance0 = False
287            has_been_removed.append(t1['Test'])
288        # Both files have ended, stop iteration.
289        elif t1 == None and t0 == None:
290            done = True
291        # By default advance both files.
292        else:
293            advance0 = True
294            advance1 = True
295
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']}")
301            else:
302                # Create temporary objects for searching through the whole file.
303                tmp0 = Result(r0.filename)
304                tmp1 = Result(r1.filename)
305
306                # Search the mismatching test cases from the opposite file.
307                s0 = tmp0.searchTest(t1['Test'])
308                s1 = tmp1.searchTest(t0['Test'])
309
310                # Old test not in new results
311                if not s0:
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.
315                    advance0 = False
316
317                # New test not in old results
318                if not s1:
319                    print(f"Missing new test {t0['Test']} from old file: {r1.filename}\n")
320                    writeToStatus(t0)
321                    # Don't advance this file since we already read a test case other than the missing one.
322                    advance1 = False
323
324                if s0 and s1:
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")
328
329            if not advance0 and not advance1:
330                # An exotic case where both tests are missing from the other file.
331                # Need to skip both.
332                advance0 = True
333                advance1 = True
334
335        if advance0:
336            t0 = r0.readResult()
337        if advance1:
338            t1 = r1.readResult()
339
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)
343
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")
348        log_file.write("\n")
349
350        log_file.write("New fails:\n")
351        for line in new_fail:
352            log_file.write(line + "\n")
353        log_file.write("\n")
354
355        log_file.write("New crashes:\n")
356        for line in new_crash:
357            log_file.write(line + "\n")
358        log_file.write("\n")
359
360        log_file.write("New not_supported:\n")
361        for line in new_notsupported:
362            log_file.write(line + "\n")
363        log_file.write("\n")
364
365        log_file.write("Tests removed:\n")
366        for line in has_been_removed:
367            log_file.write(line + "\n")
368        log_file.write("\n")
369
370        log_file.write("Status changes:\n")
371        for line in status_change:
372            log_file.write(line + "\n")
373        log_file.write("\n")
374
375        log_file.write("Compatibility warnings:\n")
376        for line in compatibility_warning:
377            log_file.write(line + "\n")
378        log_file.write("\n")
379
380        log_file.write("Quality warnings:\n")
381        for line in quality_warning:
382            log_file.write(line + "\n")
383        log_file.write("\n")
384
385        log_file.write("Internal errors:\n")
386        for line in internal_errors:
387            log_file.write(line + "\n")
388        log_file.write("\n")
389
390        log_file.write("Waiver:\n")
391        for line in waivers:
392            log_file.write(line + "\n")
393
394    print(f"Comparison done. Results have been written to: {COMP_RESULTS_DIR}")
395
396# Build VK-GL-CTS
397def buildCts():
398    VK_GL_CTS_BUILD_DIR.mkdir(parents=True, exist_ok=True)
399
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)
402
403    # Build VK-GL-CTS
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}")
408
409# Clone and build SwiftShader and Vulkan validation layers.
410def 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])
415    else:
416        run([which("git"), "pull", "origin"], working_dir=SWS_SRC_DIR)
417
418    # Build SwiftShader.
419    run([which("cmake"),
420            "-GNinja",
421            str(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)
431
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])
438        else:
439            run([which("git"), "pull", "origin"], working_dir=LAYERS_SRC_DIR)
440
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)
445        run([which("cmake"),
446                "-GNinja",
447                "-C",
448                "helper.cmake",
449                LAYERS_SRC_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}")
455
456# Run cts with regres and move result files accordingly.
457def runCts():
458    setVkIcdFilenames()
459
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}")
467    exit(0)
468
469# Recipe for running cts.
470if ARGS.recipe == "run-deqp":
471    cloneSwsAndLayers()
472    if ARGS.deqp_vk is None:
473        buildCts()
474    runCts()
475
476# Recipe for only comparing the already existing result files.
477if 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))
482