import argparse
import fnmatch
import inspect
import os
import shutil
import subprocess
import sys
import tempfile
from collections import defaultdict
from placeholder import PlaceHolder
EXPECTED_BEHAVIOR_PREFIX = 'expected_'
VALIDATE_METHOD_PREFIX = 'check_'
def get_all_variables(instance):
return [v for v in dir(instance) if not callable(getattr(instance, v))]
def get_all_methods(instance):
return [m for m in dir(instance) if callable(getattr(instance, m))]
def get_all_superclasses(cls):
classes = []
for superclass in cls.__bases__:
for c in get_all_superclasses(superclass):
if c is not object and c not in classes:
classes.append(c)
for superclass in cls.__bases__:
if superclass is not object and superclass not in classes:
classes.append(superclass)
return classes
def get_all_test_methods(test_class):
classes = get_all_superclasses(test_class)
classes.append(test_class)
all_tests = [
m for c in classes for m in get_all_methods(c)
if m.startswith(VALIDATE_METHOD_PREFIX)
]
unique_tests = []
for t in all_tests:
if t not in unique_tests:
unique_tests.append(t)
return unique_tests
class SpirvTest:
def name(self):
return self.__class__.__name__
class TestStatus:
def __init__(self, test_manager, returncode, stdout, stderr, directory,
inputs, input_filenames):
self.test_manager = test_manager
self.returncode = returncode
if type(stdout) is not str:
if sys.version_info[0] == 2:
self.stdout = stdout.decode('utf-8')
elif sys.version_info[0] == 3:
self.stdout = str(stdout, encoding='utf-8') if stdout is not None else stdout
else:
raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info))
else:
self.stdout = stdout
if type(stderr) is not str:
if sys.version_info[0] == 2:
self.stderr = stderr.decode('utf-8')
elif sys.version_info[0] == 3:
self.stderr = str(stderr, encoding='utf-8') if stderr is not None else stderr
else:
raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info))
else:
self.stderr = stderr
self.directory = directory
self.inputs = inputs
self.input_filenames = input_filenames
class SpirvTestException(Exception):
pass
def inside_spirv_testsuite(testsuite_name):
def actual_decorator(cls):
if not inspect.isclass(cls):
raise SpirvTestException('Test case should be a class')
if not issubclass(cls, SpirvTest):
raise SpirvTestException(
'All test cases should be subclasses of SpirvTest')
if 'spirv_args' not in get_all_variables(cls):
raise SpirvTestException('No spirv_args found in the test case')
if not isinstance(cls.spirv_args, list):
raise SpirvTestException('spirv_args needs to be a list')
if not any(
[m.startswith(VALIDATE_METHOD_PREFIX) for m in get_all_methods(cls)]):
raise SpirvTestException('No check_*() methods found in the test case')
if not all(
[isinstance(v, (bool, str, list)) for v in get_all_variables(cls)]):
raise SpirvTestException(
'expected_* variables are only allowed to be bool, str, or '
'list type.')
cls.parent_testsuite = testsuite_name
return cls
return actual_decorator
class TestManager:
def __init__(self, executable_path, assembler_path, disassembler_path):
self.executable_path = executable_path
self.assembler_path = assembler_path
self.disassembler_path = disassembler_path
self.num_successes = 0
self.num_failures = 0
self.num_tests = 0
self.leave_output = False
self.tests = defaultdict(list)
def notify_result(self, test_case, success, message):
self.num_successes += 1 if success else 0
self.num_failures += 0 if success else 1
counter_string = str(self.num_successes + self.num_failures) + '/' + str(
self.num_tests)
print('%-10s %-40s ' % (counter_string, test_case.test.name()) +
('Passed' if success else '-Failed-'))
if not success:
print(' '.join(test_case.command))
print(message)
def add_test(self, testsuite, test):
self.tests[testsuite].append(TestCase(test, self))
self.num_tests += 1
def run_tests(self):
for suite in self.tests:
print('SPIRV tool test suite: "{suite}"'.format(suite=suite))
for x in self.tests[suite]:
x.runTest()
class TestCase:
def __init__(self, test, test_manager):
self.test = test
self.test_manager = test_manager
self.inputs = [] self.file_shaders = [] self.stdin_shader = None
def setUp(self):
self.directory = tempfile.mkdtemp(dir=os.getcwd())
spirv_args = self.test.spirv_args
self.test.spirv_args = [
arg.instantiate_for_spirv_args(self)
if isinstance(arg, PlaceHolder) else arg for arg in self.test.spirv_args
]
self.inputs = [arg for arg in spirv_args if isinstance(arg, PlaceHolder)]
self.file_shaders = [arg.filename for arg in self.inputs]
if 'environment' in get_all_variables(self.test):
self.test.environment.write(self.directory)
expectations = [
v for v in get_all_variables(self.test)
if v.startswith(EXPECTED_BEHAVIOR_PREFIX)
]
for expectation_name in expectations:
expectation = getattr(self.test, expectation_name)
if isinstance(expectation, list):
expanded_expections = [
element.instantiate_for_expectation(self)
if isinstance(element, PlaceHolder) else element
for element in expectation
]
setattr(self.test, expectation_name, expanded_expections)
elif isinstance(expectation, PlaceHolder):
setattr(self.test, expectation_name,
expectation.instantiate_for_expectation(self))
def tearDown(self):
if not self.test_manager.leave_output:
shutil.rmtree(self.directory)
def runTest(self):
self.setUp()
success = False
message = ''
try:
self.command = [self.test_manager.executable_path]
self.command.extend(self.test.spirv_args)
process = subprocess.Popen(
args=self.command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.directory)
output = process.communicate(self.stdin_shader)
test_status = TestStatus(self.test_manager, process.returncode, output[0],
output[1], self.directory, self.inputs,
self.file_shaders)
run_results = [
getattr(self.test, test_method)(test_status)
for test_method in get_all_test_methods(self.test.__class__)
]
success, message = zip(*run_results)
success = all(success)
message = '\n'.join(message)
except Exception as e:
success = False
message = str(e)
self.test_manager.notify_result(
self, success,
message + '\nSTDOUT:\n%s\nSTDERR:\n%s' % (output[0], output[1]))
self.tearDown()
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'spirv_tool',
metavar='path/to/spirv_tool',
type=str,
nargs=1,
help='Path to the spirv-* tool under test')
parser.add_argument(
'spirv_as',
metavar='path/to/spirv-as',
type=str,
nargs=1,
help='Path to spirv-as')
parser.add_argument(
'spirv_dis',
metavar='path/to/spirv-dis',
type=str,
nargs=1,
help='Path to spirv-dis')
parser.add_argument(
'--leave-output',
action='store_const',
const=1,
help='Do not clean up temporary directories')
parser.add_argument(
'--test-dir', nargs=1, help='Directory to gather the tests from')
args = parser.parse_args()
default_path = sys.path
root_dir = os.getcwd()
if args.test_dir:
root_dir = args.test_dir[0]
manager = TestManager(args.spirv_tool[0], args.spirv_as[0], args.spirv_dis[0])
if args.leave_output:
manager.leave_output = True
for root, _, filenames in os.walk(root_dir):
for filename in fnmatch.filter(filenames, '*.py'):
if filename.endswith('nosetest.py'):
continue
sys.path = default_path
sys.path.append(root)
mod = __import__(os.path.splitext(filename)[0])
for _, obj, in inspect.getmembers(mod):
if inspect.isclass(obj) and hasattr(obj, 'parent_testsuite'):
manager.add_test(obj.parent_testsuite, obj())
manager.run_tests()
if manager.num_failures > 0:
sys.exit(-1)
if __name__ == '__main__':
main()