• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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