|  | #!/usr/bin/env python | 
|  | # Copyright 2020 The Tint Authors. | 
|  | # | 
|  | # 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 | 
|  | # | 
|  | #     http://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. | 
|  |  | 
|  | # Test runner for executing a test of tests with Tint. The runner will | 
|  | # find all .wgsl files in the given folder and attempt to convert them | 
|  | # to each of the backend formats. If the file contains a '.fail.' in the | 
|  | # name then the runner will expect the file to fail conversion. | 
|  |  | 
|  | import base64 | 
|  | import copy | 
|  | import difflib | 
|  | import optparse | 
|  | import os | 
|  | import platform | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  | import tempfile | 
|  |  | 
|  |  | 
|  | """ | 
|  | A single test case to be executed. Stores the path to the test file | 
|  | and the result of executing the test. | 
|  | """ | 
|  |  | 
|  |  | 
|  | class TestCase: | 
|  | def __init__(self, input_path, parse_only): | 
|  | self.input_path = input_path | 
|  | self.parse_only = parse_only | 
|  | self.results = {} | 
|  |  | 
|  | def IsExpectedFail(self): | 
|  | fail_re = re.compile('^.+[\.]fail[\.]wgsl') | 
|  | return fail_re.match(self.GetInputPath()) | 
|  |  | 
|  | def IsParseOnly(self): | 
|  | return self.parse_only | 
|  |  | 
|  | def GetInputPath(self): | 
|  | return self.input_path | 
|  |  | 
|  | def GetResult(self, fmt): | 
|  | return self.results[fmt] | 
|  |  | 
|  | def GetReason(self): | 
|  | with open(self.GetInputPath()) as test: | 
|  | first_line = test.readline() | 
|  | if (first_line.startswith("# v-")): | 
|  | reason = first_line[2:8] | 
|  | else: | 
|  | reason = '' | 
|  | return reason | 
|  |  | 
|  |  | 
|  | """ | 
|  | The test runner, will execute a series of test cases and record the | 
|  | results. | 
|  | """ | 
|  |  | 
|  |  | 
|  | class TestRunner: | 
|  | def RunTest(self, tc): | 
|  | """Runs a single test.""" | 
|  | print("Testing {}".format(tc.GetInputPath())) | 
|  |  | 
|  | cmd = [self.options.test_prog_path] | 
|  | if tc.IsParseOnly(): | 
|  | cmd += ['--parse-only'] | 
|  |  | 
|  | languages = ["wgsl", "spvasm", "msl", "hlsl"] | 
|  | try: | 
|  | for lang in languages: | 
|  | lang_cmd = copy.copy(cmd) | 
|  | lang_cmd += ['--format', lang] | 
|  | lang_cmd += [tc.GetInputPath()] | 
|  | err = subprocess.check_output(lang_cmd, | 
|  | stderr=subprocess.STDOUT) | 
|  |  | 
|  | except Exception as e: | 
|  | failure_reason = "{}".format("".join(map(chr, | 
|  | bytearray(e.output)))) | 
|  | if tc.IsExpectedFail(): | 
|  | right_reason = tc.GetReason() | 
|  | if (right_reason in failure_reason): | 
|  | return False, "" | 
|  | else: | 
|  | return False, right_reason | 
|  |  | 
|  | if not tc.IsExpectedFail(): | 
|  | print(failure_reason) | 
|  | print(e) | 
|  | return False, "" | 
|  |  | 
|  | return True, "" | 
|  |  | 
|  | def RunTests(self): | 
|  | """Runs a set of test cases""" | 
|  | for tc in self.test_cases: | 
|  | result, reason = self.RunTest(tc) | 
|  | """evaluate final result based on result, tc.IsExpectedFail() and reason""" | 
|  | if not result: | 
|  | # result == false, expected true, reason:don't care | 
|  | if not tc.IsExpectedFail(): | 
|  | print("Expected: " + tc.GetInputPath() + | 
|  | " to pass but failed.") | 
|  | self.failures.append(tc.GetInputPath()) | 
|  | # result == false, expected false, reason: wrong | 
|  | else: | 
|  | if reason.startswith("v-"): | 
|  | print("Failed for a wrong reason: " + | 
|  | tc.GetInputPath() + | 
|  | " expected with error code: " + reason) | 
|  | self.failures_wrong_reason.append(tc.GetInputPath()) | 
|  | # result == true, expected false, reason:don't care | 
|  | elif tc.IsExpectedFail(): | 
|  | print("Expected: " + tc.GetInputPath() + | 
|  | " to fail but passed.") | 
|  | self.failures.append(tc.GetInputPath()) | 
|  |  | 
|  | def GetUnexpectedFailures(self): | 
|  | for failure in self.failures + self.failures_wrong_reason: | 
|  | if failure not in self.known_failures: | 
|  | self.unexpected_failures.append(failure) | 
|  | return | 
|  |  | 
|  | def SummarizeResults(self): | 
|  | """Prints a summarization of the test results to STDOUT""" | 
|  | if len(self.unexpected_failures): | 
|  | self.unexpected_failures.sort() | 
|  | print('\nSummary of unexpected failures:') | 
|  | for unexpected_fail in self.unexpected_failures: | 
|  | print(unexpected_fail) | 
|  |  | 
|  | for f in self.known_failures: | 
|  | if f not in self.failures_wrong_reason + self.failures: | 
|  | self.unexpected_successes.append(f) | 
|  |  | 
|  | if len(self.unexpected_successes): | 
|  | print('\nSummary of unexpected successes:') | 
|  | for s in self.unexpected_successes: | 
|  | print(s) | 
|  |  | 
|  | print('') | 
|  | print('Test cases executed: {}'.format(len(self.test_cases))) | 
|  | print('  Successes:  {}'.format( | 
|  | (len(self.test_cases) - len(self.failures) - | 
|  | len(self.failures_wrong_reason)))) | 
|  | print('  Failures:   {}'.format( | 
|  | len(self.failures) + len(self.failures_wrong_reason))) | 
|  | print('  Unexpected Failures:  {}'.format(len( | 
|  | self.unexpected_failures))) | 
|  | print('  Unexpected Successes:  {}'.format( | 
|  | len(self.unexpected_successes))) | 
|  | print('') | 
|  |  | 
|  | def Run(self): | 
|  | """Executes the test runner.""" | 
|  | base_path = os.path.abspath( | 
|  | os.path.join(os.path.dirname(__file__), '..')) | 
|  |  | 
|  | usage = 'usage: %prog [options] (file)' | 
|  | parser = optparse.OptionParser(usage=usage) | 
|  | parser.add_option('--build-dir', | 
|  | default=os.path.join(base_path, 'out', 'Debug'), | 
|  | help='path to build directory') | 
|  | parser.add_option('--test-dir', | 
|  | default=os.path.join(os.path.dirname(__file__), '..', | 
|  | 'third_party', 'gpuweb-cts', | 
|  | 'src', 'webgpu', 'shader', | 
|  | 'validation', 'wgsl'), | 
|  | help='path to directory containing test files') | 
|  | parser.add_option( | 
|  | '--known-failures-file', | 
|  | default=os.path.join(base_path, 'tools', 'known_tint_failures'), | 
|  | help='path to directory containing the known failures file') | 
|  | parser.add_option( | 
|  | '--test-prog-path', | 
|  | default=None, | 
|  | help='path to program to test (default build-dir/tint)') | 
|  | parser.add_option('--parse-only', | 
|  | action="store_true", | 
|  | default=False, | 
|  | help='only parse test cases; do not compile') | 
|  |  | 
|  | self.options, self.args = parser.parse_args() | 
|  |  | 
|  | if self.options.test_prog_path == None: | 
|  | test_prog = os.path.abspath( | 
|  | os.path.join(self.options.build_dir, 'tint')) | 
|  | if not os.path.isfile(test_prog): | 
|  | print("Cannot find test program {}".format(test_prog)) | 
|  | return 1 | 
|  |  | 
|  | self.options.test_prog_path = test_prog | 
|  |  | 
|  | if not os.path.isfile(self.options.test_prog_path): | 
|  | print("Cannot find test program '{}'".format( | 
|  | self.options.test_prog_path)) | 
|  | return 1 | 
|  |  | 
|  | input_file_re = re.compile('^.+[\.]wgsl') | 
|  | self.test_cases = [] | 
|  |  | 
|  | if self.args: | 
|  | for filename in self.args: | 
|  | input_path = os.path.join(self.options.test_dir, filename) | 
|  | if not os.path.isfile(input_path): | 
|  | print("Cannot find test file '{}'".format(filename)) | 
|  | return 1 | 
|  |  | 
|  | self.test_cases.append( | 
|  | TestCase(input_path, self.options.parse_only)) | 
|  |  | 
|  | else: | 
|  | for file_dir, _, filename_list in os.walk(self.options.test_dir): | 
|  | for input_filename in filename_list: | 
|  | if input_file_re.match(input_filename): | 
|  | input_path = os.path.join(file_dir, input_filename) | 
|  | if os.path.isfile(input_path): | 
|  | self.test_cases.append( | 
|  | TestCase(input_path, self.options.parse_only)) | 
|  | known_failure_file = self.options.known_failures_file | 
|  | self.known_failures = [] | 
|  | with open(known_failure_file, 'r') as f: | 
|  | for failure_filename in f.read().splitlines(): | 
|  | self.known_failures.append( | 
|  | os.path.join(self.options.test_dir, failure_filename)) | 
|  |  | 
|  | self.failures = [] | 
|  | self.failures_wrong_reason = [] | 
|  | self.unexpected_failures = [] | 
|  | self.unexpected_successes = [] | 
|  |  | 
|  | self.RunTests() | 
|  | self.GetUnexpectedFailures() | 
|  | self.SummarizeResults() | 
|  |  | 
|  | return not len(self.unexpected_failures + self.unexpected_successes) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | runner = TestRunner() | 
|  | return runner.Run() | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |