1# Copyright 2020 The ChromiumOS Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Defines ConstraintSuite, which is subclassed to define constraints.""" 5 6import inspect 7import pathlib 8import unittest 9 10from chromiumos.config.payload import config_bundle_pb2 11 12 13class InvalidConstraintSuiteError(Exception): 14 """Exception raised when an invalid ConstraintSuite is defined.""" 15 16 17class ConstraintSuite: 18 """A class whose instances are suites of constraints. 19 20 Constraint authors should subclass ConstraintSuite and add methods starting 21 with "check" for each constraint they want to enforce. Each check method 22 should accept two ConfigBundles, with parameter names "project_config" and 23 "program_config", and a path to the project's "factory" dir, with 24 parameter name "factory_dir". A check method is considered failed iff 25 it raises an Exception. 26 27 Assertion methods similar to those on unittest.TestCase are available, e.g. 28 "assertEqual". See DELEGATED_ASSERTIONS attribute for full list of assertion 29 methods. 30 31 A ConstraintSuite subclass should group related constraints. For example a 32 suite to check form factor constraints could look like: 33 34 class FormFactorConstraintSuite(ConstraintSuite): 35 36 def checkFormFactorDefined( 37 self, program_config, project_config, factory_dir 38 ): 39 ... 40 41 def checkFormFactorAllowedByProgram( 42 self, program_config, project_config, factory_dir 43 ): 44 ... 45 """ 46 47 DELEGATED_ASSERTIONS = [ 48 'assertEqual', 'assertNotEqual', 'assertTrue', 'assertFalse', 'assertIn', 49 'assertNotIn', 'assertGreaterEqual', 'assertLessEqual', 'assertLess' 50 ] 51 52 def __add_delegated_assertions(self): 53 """Adds assertion methods to self. 54 55 Assertion methods just call corresponding method on unittest.TestCase. 56 """ 57 for assertion_name in self.DELEGATED_ASSERTIONS: 58 assertion = getattr(unittest.TestCase(), assertion_name) 59 setattr(self, assertion_name, assertion) 60 61 def __init__(self): 62 super().__init__() 63 64 def is_check(name, value): 65 return name.startswith('check') and inspect.ismethod(value) 66 67 self._checks = [ 68 value for name, value in inspect.getmembers(self) 69 if is_check(name, value) 70 ] 71 72 self.__add_delegated_assertions() 73 74 def run_checks(self, 75 program_config: config_bundle_pb2.ConfigBundle, 76 project_config: config_bundle_pb2.ConfigBundle, 77 factory_dir: pathlib.Path, 78 verbose: int = 0): 79 """Runs all of the checks on an instance. 80 81 Args: 82 program_config: The program's config, to pass to each check. 83 project_config: The project's config, to pass to each check. 84 factory_dir: Path to the project's factory dir, to pass to 85 each check. 86 verbose: Verbosity mode, 0: silent, >= 1: print name of check. 87 """ 88 for method in self._checks: 89 if verbose: 90 # TODO(crbug.com/1051187): Improve logging and failure reporting. 91 print('Running {}.{}'.format(self.__class__.__name__, method.__name__)) 92 method( 93 program_config=program_config, 94 project_config=project_config, 95 factory_dir=factory_dir, 96 ) 97