blob: 2cc2e2eec0ad0ac378d698cceda4cce437211611 [file] [log] [blame]
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Constraint checks related to program and project ids.
A note on how some of the checks relate:
- check_design_config_id_segments: Checks that a project's ids fall within the
segment specified in the program config.
- check_design_config_id_segments_overlap: Checks that no id segments overlap.
- check_design_config_ids_unique: Checks that ids within a project are unique.
Together, these constraints enforce uniqueness across a program. If two ids
within a project are the same, this is rejected by
check_design_config_ids_unique. If two ids in different projects are the same,
at least one of them must be out of the specified segment, because no two
segments can overlap.
"""
import itertools
import logging
import pathlib
from checker import constraint_suite
from checker import config_bundle_utils
from chromiumos.config.payload import config_bundle_pb2
class IdConstraintSuite(constraint_suite.ConstraintSuite):
"""Constraint checks related to program and project ids."""
def check_ids_consistent(
self,
program_config: config_bundle_pb2.ConfigBundle,
project_config: config_bundle_pb2.ConfigBundle,
factory_dir: pathlib.Path,
):
"""Checks all project ids are consistent with the program."""
del factory_dir
program_id = config_bundle_utils.get_program(program_config).id.value
for design in project_config.design_list:
self.assertEqual(program_id, design.program_id.value)
def check_design_config_id_segments(
self,
program_config: config_bundle_pb2.ConfigBundle,
project_config: config_bundle_pb2.ConfigBundle,
factory_dir: pathlib.Path,
):
"""Check that all DesignConfigIds fall within their segment."""
del factory_dir
program = config_bundle_utils.get_program(program_config)
segment_map = {
s.design_id.value: s for s in program.design_config_id_segments
}
for design in project_config.design_list:
# It is valid for designs to not have a corresponding segment.
segment = segment_map.get(design.id.value)
if not segment:
logging.warning(
'No DesignConfigIdSegment found for design %s, constraints on ids '
'will not be enforced',
design.id.value,
)
continue
self.assertLess(segment.min_id, segment.max_id)
for config in design.configs:
# DesignConfigIds should have the form "<name>:<id>"
_, id_num = config.id.value.split(':')
id_num = int(id_num)
# Unprovisioned config ids are exempt from the check.
if id_num == 0x7FFFFFFF:
continue
self.assertGreaterEqual(
id_num, segment.min_id,
'DesignConfigId must be >= {}, got {}'.format(
segment.min_id, id_num))
self.assertLessEqual(
id_num, segment.max_id,
'DesignConfigId must be <= {}, got {}'.format(
segment.max_id, id_num))
def check_design_config_id_segments_overlap(
self,
program_config: config_bundle_pb2.ConfigBundle,
project_config: config_bundle_pb2.ConfigBundle,
factory_dir: pathlib.Path,
):
"""Check that no DesignConfigIdSegments overlap."""
del project_config, factory_dir
program = config_bundle_utils.get_program(program_config)
# Get all pairs as permutations, so we can assume that one segment is lower
# than the other.
for seg_a, seg_b in itertools.permutations(
program.design_config_id_segments, 2):
error_message = 'Segments {} and {} overlap'.format(seg_a, seg_b)
# Segment min_ids can never be equal.
self.assertNotEqual(seg_a.min_id, seg_b.min_id, error_message)
# Only check the case where a's min_id is lower than b's min_id; the other
# case will be checked by the opposite permutation.
if seg_a.min_id < seg_b.min_id:
# If a's min_id is lower than b's, a's max id must be lower than b's
# min_id.
self.assertLess(seg_a.max_id, seg_b.min_id, error_message)
def check_design_config_ids_unique(
self,
program_config: config_bundle_pb2.ConfigBundle,
project_config: config_bundle_pb2.ConfigBundle,
factory_dir: pathlib.Path,
):
"""Checks all project DesignConfigIds are unique."""
del program_config, factory_dir
design_config_ids = set()
for design in project_config.design_list:
for config in design.configs:
self.assertNotIn(
config.id.value, design_config_ids,
"Found multiple configs with id '{}'".format(config.id.value))
design_config_ids.add(config.id.value)