1fd4e5da5Sopenharmony_ci# Copyright (c) 2018 Google LLC
2fd4e5da5Sopenharmony_ci#
3fd4e5da5Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
4fd4e5da5Sopenharmony_ci# you may not use this file except in compliance with the License.
5fd4e5da5Sopenharmony_ci# You may obtain a copy of the License at
6fd4e5da5Sopenharmony_ci#
7fd4e5da5Sopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
8fd4e5da5Sopenharmony_ci#
9fd4e5da5Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
10fd4e5da5Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
11fd4e5da5Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12fd4e5da5Sopenharmony_ci# See the License for the specific language governing permissions and
13fd4e5da5Sopenharmony_ci# limitations under the License.
14fd4e5da5Sopenharmony_ci"""Manages and runs tests from the current working directory.
15fd4e5da5Sopenharmony_ci
16fd4e5da5Sopenharmony_ciThis will traverse the current working directory and look for python files that
17fd4e5da5Sopenharmony_cicontain subclasses of SpirvTest.
18fd4e5da5Sopenharmony_ci
19fd4e5da5Sopenharmony_ciIf a class has an @inside_spirv_testsuite decorator, an instance of that
20fd4e5da5Sopenharmony_ciclass will be created and serve as a test case in that testsuite.  The test
21fd4e5da5Sopenharmony_cicase is then run by the following steps:
22fd4e5da5Sopenharmony_ci
23fd4e5da5Sopenharmony_ci  1. A temporary directory will be created.
24fd4e5da5Sopenharmony_ci  2. The spirv_args member variable will be inspected and all placeholders in it
25fd4e5da5Sopenharmony_ci     will be expanded by calling instantiate_for_spirv_args() on placeholders.
26fd4e5da5Sopenharmony_ci     The transformed list elements are then supplied as arguments to the spirv-*
27fd4e5da5Sopenharmony_ci     tool under test.
28fd4e5da5Sopenharmony_ci  3. If the environment member variable exists, its write() method will be
29fd4e5da5Sopenharmony_ci     invoked.
30fd4e5da5Sopenharmony_ci  4. All expected_* member variables will be inspected and all placeholders in
31fd4e5da5Sopenharmony_ci     them will be expanded by calling instantiate_for_expectation() on those
32fd4e5da5Sopenharmony_ci     placeholders. After placeholder expansion, if the expected_* variable is
33fd4e5da5Sopenharmony_ci     a list, its element will be joined together with '' to form a single
34fd4e5da5Sopenharmony_ci     string. These expected_* variables are to be used by the check_*() methods.
35fd4e5da5Sopenharmony_ci  5. The spirv-* tool will be run with the arguments supplied in spirv_args.
36fd4e5da5Sopenharmony_ci  6. All check_*() member methods will be called by supplying a TestStatus as
37fd4e5da5Sopenharmony_ci     argument. Each check_*() method is expected to return a (Success, Message)
38fd4e5da5Sopenharmony_ci     pair where Success is a boolean indicating success and Message is an error
39fd4e5da5Sopenharmony_ci     message.
40fd4e5da5Sopenharmony_ci  7. If any check_*() method fails, the error message is output and the
41fd4e5da5Sopenharmony_ci     current test case fails.
42fd4e5da5Sopenharmony_ci
43fd4e5da5Sopenharmony_ciIf --leave-output was not specified, all temporary files and directories will
44fd4e5da5Sopenharmony_cibe deleted.
45fd4e5da5Sopenharmony_ci"""
46fd4e5da5Sopenharmony_ci
47fd4e5da5Sopenharmony_ciimport argparse
48fd4e5da5Sopenharmony_ciimport fnmatch
49fd4e5da5Sopenharmony_ciimport inspect
50fd4e5da5Sopenharmony_ciimport os
51fd4e5da5Sopenharmony_ciimport shutil
52fd4e5da5Sopenharmony_ciimport subprocess
53fd4e5da5Sopenharmony_ciimport sys
54fd4e5da5Sopenharmony_ciimport tempfile
55fd4e5da5Sopenharmony_cifrom collections import defaultdict
56fd4e5da5Sopenharmony_cifrom placeholder import PlaceHolder
57fd4e5da5Sopenharmony_ci
58fd4e5da5Sopenharmony_ciEXPECTED_BEHAVIOR_PREFIX = 'expected_'
59fd4e5da5Sopenharmony_ciVALIDATE_METHOD_PREFIX = 'check_'
60fd4e5da5Sopenharmony_ci
61fd4e5da5Sopenharmony_ci
62fd4e5da5Sopenharmony_cidef get_all_variables(instance):
63fd4e5da5Sopenharmony_ci  """Returns the names of all the variables in instance."""
64fd4e5da5Sopenharmony_ci  return [v for v in dir(instance) if not callable(getattr(instance, v))]
65fd4e5da5Sopenharmony_ci
66fd4e5da5Sopenharmony_ci
67fd4e5da5Sopenharmony_cidef get_all_methods(instance):
68fd4e5da5Sopenharmony_ci  """Returns the names of all methods in instance."""
69fd4e5da5Sopenharmony_ci  return [m for m in dir(instance) if callable(getattr(instance, m))]
70fd4e5da5Sopenharmony_ci
71fd4e5da5Sopenharmony_ci
72fd4e5da5Sopenharmony_cidef get_all_superclasses(cls):
73fd4e5da5Sopenharmony_ci  """Returns all superclasses of a given class. Omits root 'object' superclass.
74fd4e5da5Sopenharmony_ci
75fd4e5da5Sopenharmony_ci    Returns:
76fd4e5da5Sopenharmony_ci      A list of superclasses of the given class. The order guarantees that
77fd4e5da5Sopenharmony_ci      * A Base class precedes its derived classes, e.g., for "class B(A)", it
78fd4e5da5Sopenharmony_ci        will be [..., A, B, ...].
79fd4e5da5Sopenharmony_ci      * When there are multiple base classes, base classes declared first
80fd4e5da5Sopenharmony_ci        precede those declared later, e.g., for "class C(A, B), it will be
81fd4e5da5Sopenharmony_ci        [..., A, B, C, ...]
82fd4e5da5Sopenharmony_ci    """
83fd4e5da5Sopenharmony_ci  classes = []
84fd4e5da5Sopenharmony_ci  for superclass in cls.__bases__:
85fd4e5da5Sopenharmony_ci    for c in get_all_superclasses(superclass):
86fd4e5da5Sopenharmony_ci      if c is not object and c not in classes:
87fd4e5da5Sopenharmony_ci        classes.append(c)
88fd4e5da5Sopenharmony_ci  for superclass in cls.__bases__:
89fd4e5da5Sopenharmony_ci    if superclass is not object and superclass not in classes:
90fd4e5da5Sopenharmony_ci      classes.append(superclass)
91fd4e5da5Sopenharmony_ci
92fd4e5da5Sopenharmony_ci  return classes
93fd4e5da5Sopenharmony_ci
94fd4e5da5Sopenharmony_ci
95fd4e5da5Sopenharmony_cidef get_all_test_methods(test_class):
96fd4e5da5Sopenharmony_ci  """Gets all validation methods.
97fd4e5da5Sopenharmony_ci
98fd4e5da5Sopenharmony_ci    Returns:
99fd4e5da5Sopenharmony_ci      A list of validation methods. The order guarantees that
100fd4e5da5Sopenharmony_ci      * A method defined in superclass precedes one defined in subclass,
101fd4e5da5Sopenharmony_ci        e.g., for "class A(B)", methods defined in B precedes those defined
102fd4e5da5Sopenharmony_ci        in A.
103fd4e5da5Sopenharmony_ci      * If a subclass has more than one superclass, e.g., "class C(A, B)",
104fd4e5da5Sopenharmony_ci        then methods defined in A precedes those defined in B.
105fd4e5da5Sopenharmony_ci    """
106fd4e5da5Sopenharmony_ci  classes = get_all_superclasses(test_class)
107fd4e5da5Sopenharmony_ci  classes.append(test_class)
108fd4e5da5Sopenharmony_ci  all_tests = [
109fd4e5da5Sopenharmony_ci      m for c in classes for m in get_all_methods(c)
110fd4e5da5Sopenharmony_ci      if m.startswith(VALIDATE_METHOD_PREFIX)
111fd4e5da5Sopenharmony_ci  ]
112fd4e5da5Sopenharmony_ci  unique_tests = []
113fd4e5da5Sopenharmony_ci  for t in all_tests:
114fd4e5da5Sopenharmony_ci    if t not in unique_tests:
115fd4e5da5Sopenharmony_ci      unique_tests.append(t)
116fd4e5da5Sopenharmony_ci  return unique_tests
117fd4e5da5Sopenharmony_ci
118fd4e5da5Sopenharmony_ci
119fd4e5da5Sopenharmony_ciclass SpirvTest:
120fd4e5da5Sopenharmony_ci  """Base class for spirv test cases.
121fd4e5da5Sopenharmony_ci
122fd4e5da5Sopenharmony_ci    Subclasses define test cases' facts (shader source code, spirv command,
123fd4e5da5Sopenharmony_ci    result validation), which will be used by the TestCase class for running
124fd4e5da5Sopenharmony_ci    tests. Subclasses should define spirv_args (specifying spirv_tool command
125fd4e5da5Sopenharmony_ci    arguments), and at least one check_*() method (for result validation) for
126fd4e5da5Sopenharmony_ci    a full-fledged test case. All check_*() methods should take a TestStatus
127fd4e5da5Sopenharmony_ci    parameter and return a (Success, Message) pair, in which Success is a
128fd4e5da5Sopenharmony_ci    boolean indicating success and Message is an error message. The test passes
129fd4e5da5Sopenharmony_ci    iff all check_*() methods returns true.
130fd4e5da5Sopenharmony_ci
131fd4e5da5Sopenharmony_ci    Often, a test case class will delegate the check_* behaviors by inheriting
132fd4e5da5Sopenharmony_ci    from other classes.
133fd4e5da5Sopenharmony_ci    """
134fd4e5da5Sopenharmony_ci
135fd4e5da5Sopenharmony_ci  def name(self):
136fd4e5da5Sopenharmony_ci    return self.__class__.__name__
137fd4e5da5Sopenharmony_ci
138fd4e5da5Sopenharmony_ci
139fd4e5da5Sopenharmony_ciclass TestStatus:
140fd4e5da5Sopenharmony_ci  """A struct for holding run status of a test case."""
141fd4e5da5Sopenharmony_ci
142fd4e5da5Sopenharmony_ci  def __init__(self, test_manager, returncode, stdout, stderr, directory,
143fd4e5da5Sopenharmony_ci               inputs, input_filenames):
144fd4e5da5Sopenharmony_ci    self.test_manager = test_manager
145fd4e5da5Sopenharmony_ci    self.returncode = returncode
146fd4e5da5Sopenharmony_ci    # Some of our MacOS bots still run Python 2, so need to be backwards
147fd4e5da5Sopenharmony_ci    # compatible here.
148fd4e5da5Sopenharmony_ci    if type(stdout) is not str:
149fd4e5da5Sopenharmony_ci      if sys.version_info[0] == 2:
150fd4e5da5Sopenharmony_ci       self.stdout = stdout.decode('utf-8')
151fd4e5da5Sopenharmony_ci      elif sys.version_info[0] == 3:
152fd4e5da5Sopenharmony_ci        self.stdout = str(stdout, encoding='utf-8') if stdout is not None else stdout
153fd4e5da5Sopenharmony_ci      else:
154fd4e5da5Sopenharmony_ci        raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info))
155fd4e5da5Sopenharmony_ci    else:
156fd4e5da5Sopenharmony_ci      self.stdout = stdout
157fd4e5da5Sopenharmony_ci
158fd4e5da5Sopenharmony_ci    if type(stderr) is not str:
159fd4e5da5Sopenharmony_ci      if sys.version_info[0] == 2:
160fd4e5da5Sopenharmony_ci       self.stderr = stderr.decode('utf-8')
161fd4e5da5Sopenharmony_ci      elif sys.version_info[0] == 3:
162fd4e5da5Sopenharmony_ci        self.stderr = str(stderr, encoding='utf-8') if stderr is not None else stderr
163fd4e5da5Sopenharmony_ci      else:
164fd4e5da5Sopenharmony_ci        raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info))
165fd4e5da5Sopenharmony_ci    else:
166fd4e5da5Sopenharmony_ci      self.stderr = stderr
167fd4e5da5Sopenharmony_ci
168fd4e5da5Sopenharmony_ci    # temporary directory where the test runs
169fd4e5da5Sopenharmony_ci    self.directory = directory
170fd4e5da5Sopenharmony_ci    # List of inputs, as PlaceHolder objects.
171fd4e5da5Sopenharmony_ci    self.inputs = inputs
172fd4e5da5Sopenharmony_ci    # the names of input shader files (potentially including paths)
173fd4e5da5Sopenharmony_ci    self.input_filenames = input_filenames
174fd4e5da5Sopenharmony_ci
175fd4e5da5Sopenharmony_ci
176fd4e5da5Sopenharmony_ciclass SpirvTestException(Exception):
177fd4e5da5Sopenharmony_ci  """SpirvTest exception class."""
178fd4e5da5Sopenharmony_ci  pass
179fd4e5da5Sopenharmony_ci
180fd4e5da5Sopenharmony_ci
181fd4e5da5Sopenharmony_cidef inside_spirv_testsuite(testsuite_name):
182fd4e5da5Sopenharmony_ci  """Decorator for subclasses of SpirvTest.
183fd4e5da5Sopenharmony_ci
184fd4e5da5Sopenharmony_ci    This decorator checks that a class meets the requirements (see below)
185fd4e5da5Sopenharmony_ci    for a test case class, and then puts the class in a certain testsuite.
186fd4e5da5Sopenharmony_ci    * The class needs to be a subclass of SpirvTest.
187fd4e5da5Sopenharmony_ci    * The class needs to have spirv_args defined as a list.
188fd4e5da5Sopenharmony_ci    * The class needs to define at least one check_*() methods.
189fd4e5da5Sopenharmony_ci    * All expected_* variables required by check_*() methods can only be
190fd4e5da5Sopenharmony_ci      of bool, str, or list type.
191fd4e5da5Sopenharmony_ci    * Python runtime will throw an exception if the expected_* member
192fd4e5da5Sopenharmony_ci      attributes required by check_*() methods are missing.
193fd4e5da5Sopenharmony_ci    """
194fd4e5da5Sopenharmony_ci
195fd4e5da5Sopenharmony_ci  def actual_decorator(cls):
196fd4e5da5Sopenharmony_ci    if not inspect.isclass(cls):
197fd4e5da5Sopenharmony_ci      raise SpirvTestException('Test case should be a class')
198fd4e5da5Sopenharmony_ci    if not issubclass(cls, SpirvTest):
199fd4e5da5Sopenharmony_ci      raise SpirvTestException(
200fd4e5da5Sopenharmony_ci          'All test cases should be subclasses of SpirvTest')
201fd4e5da5Sopenharmony_ci    if 'spirv_args' not in get_all_variables(cls):
202fd4e5da5Sopenharmony_ci      raise SpirvTestException('No spirv_args found in the test case')
203fd4e5da5Sopenharmony_ci    if not isinstance(cls.spirv_args, list):
204fd4e5da5Sopenharmony_ci      raise SpirvTestException('spirv_args needs to be a list')
205fd4e5da5Sopenharmony_ci    if not any(
206fd4e5da5Sopenharmony_ci        [m.startswith(VALIDATE_METHOD_PREFIX) for m in get_all_methods(cls)]):
207fd4e5da5Sopenharmony_ci      raise SpirvTestException('No check_*() methods found in the test case')
208fd4e5da5Sopenharmony_ci    if not all(
209fd4e5da5Sopenharmony_ci        [isinstance(v, (bool, str, list)) for v in get_all_variables(cls)]):
210fd4e5da5Sopenharmony_ci      raise SpirvTestException(
211fd4e5da5Sopenharmony_ci          'expected_* variables are only allowed to be bool, str, or '
212fd4e5da5Sopenharmony_ci          'list type.')
213fd4e5da5Sopenharmony_ci    cls.parent_testsuite = testsuite_name
214fd4e5da5Sopenharmony_ci    return cls
215fd4e5da5Sopenharmony_ci
216fd4e5da5Sopenharmony_ci  return actual_decorator
217fd4e5da5Sopenharmony_ci
218fd4e5da5Sopenharmony_ci
219fd4e5da5Sopenharmony_ciclass TestManager:
220fd4e5da5Sopenharmony_ci  """Manages and runs a set of tests."""
221fd4e5da5Sopenharmony_ci
222fd4e5da5Sopenharmony_ci  def __init__(self, executable_path, assembler_path, disassembler_path):
223fd4e5da5Sopenharmony_ci    self.executable_path = executable_path
224fd4e5da5Sopenharmony_ci    self.assembler_path = assembler_path
225fd4e5da5Sopenharmony_ci    self.disassembler_path = disassembler_path
226fd4e5da5Sopenharmony_ci    self.num_successes = 0
227fd4e5da5Sopenharmony_ci    self.num_failures = 0
228fd4e5da5Sopenharmony_ci    self.num_tests = 0
229fd4e5da5Sopenharmony_ci    self.leave_output = False
230fd4e5da5Sopenharmony_ci    self.tests = defaultdict(list)
231fd4e5da5Sopenharmony_ci
232fd4e5da5Sopenharmony_ci  def notify_result(self, test_case, success, message):
233fd4e5da5Sopenharmony_ci    """Call this to notify the manager of the results of a test run."""
234fd4e5da5Sopenharmony_ci    self.num_successes += 1 if success else 0
235fd4e5da5Sopenharmony_ci    self.num_failures += 0 if success else 1
236fd4e5da5Sopenharmony_ci    counter_string = str(self.num_successes + self.num_failures) + '/' + str(
237fd4e5da5Sopenharmony_ci        self.num_tests)
238fd4e5da5Sopenharmony_ci    print('%-10s %-40s ' % (counter_string, test_case.test.name()) +
239fd4e5da5Sopenharmony_ci          ('Passed' if success else '-Failed-'))
240fd4e5da5Sopenharmony_ci    if not success:
241fd4e5da5Sopenharmony_ci      print(' '.join(test_case.command))
242fd4e5da5Sopenharmony_ci      print(message)
243fd4e5da5Sopenharmony_ci
244fd4e5da5Sopenharmony_ci  def add_test(self, testsuite, test):
245fd4e5da5Sopenharmony_ci    """Add this to the current list of test cases."""
246fd4e5da5Sopenharmony_ci    self.tests[testsuite].append(TestCase(test, self))
247fd4e5da5Sopenharmony_ci    self.num_tests += 1
248fd4e5da5Sopenharmony_ci
249fd4e5da5Sopenharmony_ci  def run_tests(self):
250fd4e5da5Sopenharmony_ci    for suite in self.tests:
251fd4e5da5Sopenharmony_ci      print('SPIRV tool test suite: "{suite}"'.format(suite=suite))
252fd4e5da5Sopenharmony_ci      for x in self.tests[suite]:
253fd4e5da5Sopenharmony_ci        x.runTest()
254fd4e5da5Sopenharmony_ci
255fd4e5da5Sopenharmony_ci
256fd4e5da5Sopenharmony_ciclass TestCase:
257fd4e5da5Sopenharmony_ci  """A single test case that runs in its own directory."""
258fd4e5da5Sopenharmony_ci
259fd4e5da5Sopenharmony_ci  def __init__(self, test, test_manager):
260fd4e5da5Sopenharmony_ci    self.test = test
261fd4e5da5Sopenharmony_ci    self.test_manager = test_manager
262fd4e5da5Sopenharmony_ci    self.inputs = []  # inputs, as PlaceHolder objects.
263fd4e5da5Sopenharmony_ci    self.file_shaders = []  # filenames of shader files.
264fd4e5da5Sopenharmony_ci    self.stdin_shader = None  # text to be passed to spirv_tool as stdin
265fd4e5da5Sopenharmony_ci
266fd4e5da5Sopenharmony_ci  def setUp(self):
267fd4e5da5Sopenharmony_ci    """Creates environment and instantiates placeholders for the test case."""
268fd4e5da5Sopenharmony_ci
269fd4e5da5Sopenharmony_ci    self.directory = tempfile.mkdtemp(dir=os.getcwd())
270fd4e5da5Sopenharmony_ci    spirv_args = self.test.spirv_args
271fd4e5da5Sopenharmony_ci    # Instantiate placeholders in spirv_args
272fd4e5da5Sopenharmony_ci    self.test.spirv_args = [
273fd4e5da5Sopenharmony_ci        arg.instantiate_for_spirv_args(self)
274fd4e5da5Sopenharmony_ci        if isinstance(arg, PlaceHolder) else arg for arg in self.test.spirv_args
275fd4e5da5Sopenharmony_ci    ]
276fd4e5da5Sopenharmony_ci    # Get all shader files' names
277fd4e5da5Sopenharmony_ci    self.inputs = [arg for arg in spirv_args if isinstance(arg, PlaceHolder)]
278fd4e5da5Sopenharmony_ci    self.file_shaders = [arg.filename for arg in self.inputs]
279fd4e5da5Sopenharmony_ci
280fd4e5da5Sopenharmony_ci    if 'environment' in get_all_variables(self.test):
281fd4e5da5Sopenharmony_ci      self.test.environment.write(self.directory)
282fd4e5da5Sopenharmony_ci
283fd4e5da5Sopenharmony_ci    expectations = [
284fd4e5da5Sopenharmony_ci        v for v in get_all_variables(self.test)
285fd4e5da5Sopenharmony_ci        if v.startswith(EXPECTED_BEHAVIOR_PREFIX)
286fd4e5da5Sopenharmony_ci    ]
287fd4e5da5Sopenharmony_ci    # Instantiate placeholders in expectations
288fd4e5da5Sopenharmony_ci    for expectation_name in expectations:
289fd4e5da5Sopenharmony_ci      expectation = getattr(self.test, expectation_name)
290fd4e5da5Sopenharmony_ci      if isinstance(expectation, list):
291fd4e5da5Sopenharmony_ci        expanded_expections = [
292fd4e5da5Sopenharmony_ci            element.instantiate_for_expectation(self)
293fd4e5da5Sopenharmony_ci            if isinstance(element, PlaceHolder) else element
294fd4e5da5Sopenharmony_ci            for element in expectation
295fd4e5da5Sopenharmony_ci        ]
296fd4e5da5Sopenharmony_ci        setattr(self.test, expectation_name, expanded_expections)
297fd4e5da5Sopenharmony_ci      elif isinstance(expectation, PlaceHolder):
298fd4e5da5Sopenharmony_ci        setattr(self.test, expectation_name,
299fd4e5da5Sopenharmony_ci                expectation.instantiate_for_expectation(self))
300fd4e5da5Sopenharmony_ci
301fd4e5da5Sopenharmony_ci  def tearDown(self):
302fd4e5da5Sopenharmony_ci    """Removes the directory if we were not instructed to do otherwise."""
303fd4e5da5Sopenharmony_ci    if not self.test_manager.leave_output:
304fd4e5da5Sopenharmony_ci      shutil.rmtree(self.directory)
305fd4e5da5Sopenharmony_ci
306fd4e5da5Sopenharmony_ci  def runTest(self):
307fd4e5da5Sopenharmony_ci    """Sets up and runs a test, reports any failures and then cleans up."""
308fd4e5da5Sopenharmony_ci    self.setUp()
309fd4e5da5Sopenharmony_ci    success = False
310fd4e5da5Sopenharmony_ci    message = ''
311fd4e5da5Sopenharmony_ci    try:
312fd4e5da5Sopenharmony_ci      self.command = [self.test_manager.executable_path]
313fd4e5da5Sopenharmony_ci      self.command.extend(self.test.spirv_args)
314fd4e5da5Sopenharmony_ci
315fd4e5da5Sopenharmony_ci      process = subprocess.Popen(
316fd4e5da5Sopenharmony_ci          args=self.command,
317fd4e5da5Sopenharmony_ci          stdin=subprocess.PIPE,
318fd4e5da5Sopenharmony_ci          stdout=subprocess.PIPE,
319fd4e5da5Sopenharmony_ci          stderr=subprocess.PIPE,
320fd4e5da5Sopenharmony_ci          cwd=self.directory)
321fd4e5da5Sopenharmony_ci      output = process.communicate(self.stdin_shader)
322fd4e5da5Sopenharmony_ci      test_status = TestStatus(self.test_manager, process.returncode, output[0],
323fd4e5da5Sopenharmony_ci                               output[1], self.directory, self.inputs,
324fd4e5da5Sopenharmony_ci                               self.file_shaders)
325fd4e5da5Sopenharmony_ci      run_results = [
326fd4e5da5Sopenharmony_ci          getattr(self.test, test_method)(test_status)
327fd4e5da5Sopenharmony_ci          for test_method in get_all_test_methods(self.test.__class__)
328fd4e5da5Sopenharmony_ci      ]
329fd4e5da5Sopenharmony_ci      success, message = zip(*run_results)
330fd4e5da5Sopenharmony_ci      success = all(success)
331fd4e5da5Sopenharmony_ci      message = '\n'.join(message)
332fd4e5da5Sopenharmony_ci    except Exception as e:
333fd4e5da5Sopenharmony_ci      success = False
334fd4e5da5Sopenharmony_ci      message = str(e)
335fd4e5da5Sopenharmony_ci    self.test_manager.notify_result(
336fd4e5da5Sopenharmony_ci        self, success,
337fd4e5da5Sopenharmony_ci        message + '\nSTDOUT:\n%s\nSTDERR:\n%s' % (output[0], output[1]))
338fd4e5da5Sopenharmony_ci    self.tearDown()
339fd4e5da5Sopenharmony_ci
340fd4e5da5Sopenharmony_ci
341fd4e5da5Sopenharmony_cidef main():
342fd4e5da5Sopenharmony_ci  parser = argparse.ArgumentParser()
343fd4e5da5Sopenharmony_ci  parser.add_argument(
344fd4e5da5Sopenharmony_ci      'spirv_tool',
345fd4e5da5Sopenharmony_ci      metavar='path/to/spirv_tool',
346fd4e5da5Sopenharmony_ci      type=str,
347fd4e5da5Sopenharmony_ci      nargs=1,
348fd4e5da5Sopenharmony_ci      help='Path to the spirv-* tool under test')
349fd4e5da5Sopenharmony_ci  parser.add_argument(
350fd4e5da5Sopenharmony_ci      'spirv_as',
351fd4e5da5Sopenharmony_ci      metavar='path/to/spirv-as',
352fd4e5da5Sopenharmony_ci      type=str,
353fd4e5da5Sopenharmony_ci      nargs=1,
354fd4e5da5Sopenharmony_ci      help='Path to spirv-as')
355fd4e5da5Sopenharmony_ci  parser.add_argument(
356fd4e5da5Sopenharmony_ci      'spirv_dis',
357fd4e5da5Sopenharmony_ci      metavar='path/to/spirv-dis',
358fd4e5da5Sopenharmony_ci      type=str,
359fd4e5da5Sopenharmony_ci      nargs=1,
360fd4e5da5Sopenharmony_ci      help='Path to spirv-dis')
361fd4e5da5Sopenharmony_ci  parser.add_argument(
362fd4e5da5Sopenharmony_ci      '--leave-output',
363fd4e5da5Sopenharmony_ci      action='store_const',
364fd4e5da5Sopenharmony_ci      const=1,
365fd4e5da5Sopenharmony_ci      help='Do not clean up temporary directories')
366fd4e5da5Sopenharmony_ci  parser.add_argument(
367fd4e5da5Sopenharmony_ci      '--test-dir', nargs=1, help='Directory to gather the tests from')
368fd4e5da5Sopenharmony_ci  args = parser.parse_args()
369fd4e5da5Sopenharmony_ci  default_path = sys.path
370fd4e5da5Sopenharmony_ci  root_dir = os.getcwd()
371fd4e5da5Sopenharmony_ci  if args.test_dir:
372fd4e5da5Sopenharmony_ci    root_dir = args.test_dir[0]
373fd4e5da5Sopenharmony_ci  manager = TestManager(args.spirv_tool[0], args.spirv_as[0], args.spirv_dis[0])
374fd4e5da5Sopenharmony_ci  if args.leave_output:
375fd4e5da5Sopenharmony_ci    manager.leave_output = True
376fd4e5da5Sopenharmony_ci  for root, _, filenames in os.walk(root_dir):
377fd4e5da5Sopenharmony_ci    for filename in fnmatch.filter(filenames, '*.py'):
378fd4e5da5Sopenharmony_ci      if filename.endswith('nosetest.py'):
379fd4e5da5Sopenharmony_ci        # Skip nose tests, which are for testing functions of
380fd4e5da5Sopenharmony_ci        # the test framework.
381fd4e5da5Sopenharmony_ci        continue
382fd4e5da5Sopenharmony_ci      sys.path = default_path
383fd4e5da5Sopenharmony_ci      sys.path.append(root)
384fd4e5da5Sopenharmony_ci      mod = __import__(os.path.splitext(filename)[0])
385fd4e5da5Sopenharmony_ci      for _, obj, in inspect.getmembers(mod):
386fd4e5da5Sopenharmony_ci        if inspect.isclass(obj) and hasattr(obj, 'parent_testsuite'):
387fd4e5da5Sopenharmony_ci          manager.add_test(obj.parent_testsuite, obj())
388fd4e5da5Sopenharmony_ci  manager.run_tests()
389fd4e5da5Sopenharmony_ci  if manager.num_failures > 0:
390fd4e5da5Sopenharmony_ci    sys.exit(-1)
391fd4e5da5Sopenharmony_ci
392fd4e5da5Sopenharmony_ci
393fd4e5da5Sopenharmony_ciif __name__ == '__main__':
394fd4e5da5Sopenharmony_ci  main()
395