| """Tests for config.""" |
| |
| import os |
| import sys |
| import unittest |
| |
| # Allow imports to be rooted in continuous-tests/ folder |
| sys.path.insert( |
| 0, |
| os.path.realpath( |
| os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))) |
| |
| # pylint: disable=wrong-import-position |
| from cq.appengine.genfiles import build_config_pb2 |
| |
| import google.protobuf.text_format |
| |
| _BUILD_RULES_RELPATH = 'configs/{env}/build_rules.protoascii' |
| _BUILD_DETAILS_RELPATH = 'configs/{env}/build_details.protoascii' |
| |
| |
| def GetBuildRules(build_config_env): |
| """Returns the BuildRules proto.""" |
| build_rules = build_config_pb2.BuildRules() |
| build_rules_path = os.path.join( |
| os.path.dirname(__file__), |
| _BUILD_RULES_RELPATH.format(env=build_config_env)) |
| with open(build_rules_path) as f: |
| google.protobuf.text_format.Merge(f.read(), build_rules) |
| return build_rules |
| |
| |
| def GetBuildDetails(build_config_env): |
| """Returns the BuildDetails proto.""" |
| build_details = build_config_pb2.BuildDetails() |
| build_details_path = os.path.join( |
| os.path.dirname(__file__), |
| _BUILD_DETAILS_RELPATH.format(env=build_config_env)) |
| with open(build_details_path) as f: |
| google.protobuf.text_format.Merge(f.read(), build_details) |
| return build_details |
| |
| # Use a wrapping class so the test runner does not mistakenly try to run |
| # tests on ConfigTest, which is not a fully-implemented test class |
| class BaseTestCases: |
| class ConfigTest(unittest.TestCase): |
| """A class to test a build config. |
| |
| Note: The |self.env| property is initialized externally to this class. This |
| class will be setup twice, once with the staging config and once with the |
| prod config. |
| """ |
| env = None |
| |
| @property |
| def build_rules_proto(self): |
| return GetBuildRules(self.env) |
| |
| @property |
| def build_details_proto(self): |
| return GetBuildDetails(self.env) |
| |
| def testParseBuildRules(self): |
| """Tests the build_rules config is a valid proto.""" |
| self.assertTrue(self.build_rules_proto) |
| |
| def testParseBuildDetails(self): |
| """Tests the build_details config is a valid proto.""" |
| self.assertTrue(self.build_details_proto) |
| |
| def testOnlySupportedGobHosts(self): |
| """Tests that only supported gob_hosts are used.""" |
| supported_hosts = { |
| 'caraway-internal', |
| 'chrome-internal', |
| 'eureka-internal', |
| 'eureka-partner', |
| 'eureka-staging-internal', |
| 'libassistant-internal', |
| 'nest-camera-internal', |
| 'nest-internal', |
| 'team', |
| } |
| for rule in self.build_rules_proto.build_rules: |
| self.assertIn(rule.gob_host, supported_hosts) |
| |
| def testEveryBuildRuleHasAMatchingBuildDetail(self): |
| """Test that every build in a build_rule has a matching build_detail.""" |
| |
| referenced_builds = set() |
| for rule in self.build_rules_proto.build_rules: |
| referenced_builds.update(rule.builds) |
| referenced_builds.update(rule.presubmit_builds) |
| for build_set in rule.build_sets: |
| referenced_builds.update( |
| self.build_rules_proto.build_sets[build_set].builds) |
| |
| build_detail_builds = set() |
| for build_name in self.build_details_proto.build_details: |
| build_detail_builds.add(build_name) |
| |
| # Note that it is acceptable for there to be extra build_details, since |
| # that is how a build is enabled for manually scheduling. |
| missing_build_details = referenced_builds - build_detail_builds |
| self.assertFalse(missing_build_details) |
| |
| def testScheduleValid(self): |
| for build, detail in self.build_details_proto.build_details.items(): |
| if detail.schedule.min_branch and detail.schedule.max_branch: |
| self.assertLess( |
| detail.schedule.min_branch, detail.schedule.max_branch, |
| 'Branch range ({} < {}) for {} is invalid'.format( |
| detail.schedule.min_branch, detail.schedule.max_branch, build |
| ) |
| ) |
| if detail.schedule.master_only and detail.schedule.release_only: |
| raise AssertionError( |
| 'Can only set master only or release only for {}'.format(build) |
| ) |
| |
| def testHasRequiredFields(self): |
| for name, detail in self.build_details_proto.build_details.items(): |
| self.assertTrue( |
| detail.HasField('config'), |
| f'{name} must have oneof field config set.') |
| which_config = detail.WhichOneof('config') |
| if which_config == 'device_tests': |
| self.assertGreater( |
| len(detail.device_tests.test_groups), |
| 0, |
| f'{name} must have at least one test_group') |
| self.assertTrue( |
| detail.device_tests.HasField('depends_on'), |
| f'{name} must have a depends_on set') |
| self.assertGreater( |
| len(detail.device_tests.depends_on), |
| 0, |
| f'{name} must have a non-empty depends_on') |
| elif which_config == 'cast_build': |
| self.assertTrue( |
| detail.cast_build.HasField('recipe'), |
| f'{name} must have a recipe set') |
| self.assertGreater( |
| len(detail.cast_build.recipe), |
| 0, |
| f'{name} must have a non-empty recipe') |
| elif which_config == 'jenkins_job': |
| self.assertTrue( |
| detail.jenkins_job.HasField('job_name'), |
| f'{name} must have a job_name set') |
| self.assertGreater( |
| len(detail.jenkins_job.job_name), |
| 0, |
| f'{name} must have a non-empty job_name') |
| |
| |
| class ProdConfigTest(BaseTestCases.ConfigTest): |
| env = "prod" |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |