-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor constraints so that each constraint is it's own class (#23753)
- Loading branch information
Showing
3 changed files
with
225 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
# | ||
# Copyright (c) 2022 Project CHIP Authors | ||
# All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
from abc import ABC, abstractmethod | ||
import chip.yaml.format_converter as Converter | ||
from .variable_storage import VariableStorage | ||
|
||
|
||
class ConstraintValidationError(Exception): | ||
def __init__(self, message): | ||
super().__init__(message) | ||
|
||
|
||
class BaseConstraint(ABC): | ||
'''Constrain Interface''' | ||
|
||
@abstractmethod | ||
def is_met(self, response) -> bool: | ||
pass | ||
|
||
|
||
class _LoadableConstraint(BaseConstraint): | ||
'''Constraints where value might be stored in VariableStorage needing runtime load.''' | ||
|
||
def __init__(self, value, field_type, variable_storage: VariableStorage): | ||
self._variable_storage = variable_storage | ||
# When not none _indirect_value_key is binding a name to the constraint value, and the | ||
# actual value can only be looked-up dynamically, which is why this is a key name. | ||
self._indirect_value_key = None | ||
self._value = None | ||
|
||
if value is None: | ||
# Default values set above is all we need here. | ||
return | ||
|
||
if isinstance(value, str) and self._variable_storage.is_key_saved(value): | ||
self._indirect_value_key = value | ||
else: | ||
self._value = Converter.convert_yaml_type( | ||
value, field_type) | ||
|
||
def get_value(self): | ||
'''Gets the current value of the constraint. | ||
This method accounts for getting the runtime saved value from DUT previous responses. | ||
''' | ||
if self._indirect_value_key: | ||
return self._variable_storage.load(self._indirect_value_key) | ||
return self._value | ||
|
||
|
||
class _ConstraintHasValue(BaseConstraint): | ||
def __init__(self, has_value): | ||
self._has_value = has_value | ||
|
||
def is_met(self, response) -> bool: | ||
raise ConstraintValidationError('HasValue constraint currently not implemented') | ||
|
||
|
||
class _ConstraintType(BaseConstraint): | ||
def __init__(self, type): | ||
self._type = type | ||
|
||
def is_met(self, response) -> bool: | ||
raise ConstraintValidationError('Type constraint currently not implemented') | ||
|
||
|
||
class _ConstraintStartsWith(BaseConstraint): | ||
def __init__(self, starts_with): | ||
self._starts_with = starts_with | ||
|
||
def is_met(self, response) -> bool: | ||
return response.startswith(self._starts_with) | ||
|
||
|
||
class _ConstraintEndsWith(BaseConstraint): | ||
def __init__(self, ends_with): | ||
self._ends_with = ends_with | ||
|
||
def is_met(self, response) -> bool: | ||
return response.endswith(self._ends_with) | ||
|
||
|
||
class _ConstraintIsUpperCase(BaseConstraint): | ||
def __init__(self, is_upper_case): | ||
self._is_upper_case = is_upper_case | ||
|
||
def is_met(self, response) -> bool: | ||
return response.isupper() == self._is_upper_case | ||
|
||
|
||
class _ConstraintIsLowerCase(BaseConstraint): | ||
def __init__(self, is_lower_case): | ||
self._is_lower_case = is_lower_case | ||
|
||
def is_met(self, response) -> bool: | ||
return response.islower() == self._is_lower_case | ||
|
||
|
||
class _ConstraintMinValue(_LoadableConstraint): | ||
def __init__(self, min_value, field_type, variable_storage: VariableStorage): | ||
super().__init__(min_value, field_type, variable_storage) | ||
|
||
def is_met(self, response) -> bool: | ||
min_value = self.get_value() | ||
return response >= min_value | ||
|
||
|
||
class _ConstraintMaxValue(_LoadableConstraint): | ||
def __init__(self, max_value, field_type, variable_storage: VariableStorage): | ||
super().__init__(max_value, field_type, variable_storage) | ||
|
||
def is_met(self, response) -> bool: | ||
max_value = self.get_value() | ||
return response <= max_value | ||
|
||
|
||
class _ConstraintContains(BaseConstraint): | ||
def __init__(self, contains): | ||
self._contains = contains | ||
|
||
def is_met(self, response) -> bool: | ||
return set(self._contains).issubset(response) | ||
|
||
|
||
class _ConstraintExcludes(BaseConstraint): | ||
def __init__(self, excludes): | ||
self._excludes = excludes | ||
|
||
def is_met(self, response) -> bool: | ||
return set(self._excludes).isdisjoint(response) | ||
|
||
|
||
class _ConstraintHasMaskSet(BaseConstraint): | ||
def __init__(self, has_masks_set): | ||
self._has_masks_set = has_masks_set | ||
|
||
def is_met(self, response) -> bool: | ||
return all([(response & mask) == mask for mask in self._has_masks_set]) | ||
|
||
|
||
class _ConstraintHasMaskClear(BaseConstraint): | ||
def __init__(self, has_masks_clear): | ||
self._has_masks_clear = has_masks_clear | ||
|
||
def is_met(self, response) -> bool: | ||
return all([(response & mask) == 0 for mask in self._has_masks_clear]) | ||
|
||
|
||
class _ConstraintNotValue(_LoadableConstraint): | ||
def __init__(self, not_value, field_type, variable_storage: VariableStorage): | ||
super().__init__(not_value, field_type, variable_storage) | ||
|
||
def is_met(self, response) -> bool: | ||
not_value = self.get_value() | ||
return response != not_value | ||
|
||
|
||
def get_constraints(constraints, field_type, | ||
variable_storage: VariableStorage) -> list[BaseConstraint]: | ||
_constraints = [] | ||
if 'hasValue' in constraints: | ||
_constraints.append(_ConstraintHasValue(constraints.get('hasValue'))) | ||
|
||
if 'type' in constraints: | ||
_constraints.append(_ConstraintType(constraints.get('type'))) | ||
|
||
if 'startsWith' in constraints: | ||
_constraints.append(_ConstraintStartsWith(constraints.get('startsWith'))) | ||
|
||
if 'endsWith' in constraints: | ||
_constraints.append(_ConstraintEndsWith(constraints.get('endsWith'))) | ||
|
||
if 'isUpperCase' in constraints: | ||
_constraints.append(_ConstraintIsUpperCase(constraints.get('isUpperCase'))) | ||
|
||
if 'isLowerCase' in constraints: | ||
_constraints.append(_ConstraintIsLowerCase(constraints.get('isLowerCase'))) | ||
|
||
if 'minValue' in constraints: | ||
_constraints.append(_ConstraintMinValue( | ||
constraints.get('minValue'), field_type, variable_storage)) | ||
|
||
if 'maxValue' in constraints: | ||
_constraints.append(_ConstraintMaxValue( | ||
constraints.get('maxValue'), field_type, variable_storage)) | ||
|
||
if 'contains' in constraints: | ||
_constraints.append(_ConstraintContains(constraints.get('contains'))) | ||
|
||
if 'excludes' in constraints: | ||
_constraints.append(_ConstraintExcludes(constraints.get('excludes'))) | ||
|
||
if 'hasMasksSet' in constraints: | ||
_constraints.append(_ConstraintHasMaskSet(constraints.get('hasMasksSet'))) | ||
|
||
if 'hasMasksClear' in constraints: | ||
_constraints.append(_ConstraintHasMaskClear(constraints.get('hasMasksClear'))) | ||
|
||
if 'notValue' in constraints: | ||
_constraints.append(_ConstraintNotValue( | ||
constraints.get('notValue'), field_type, variable_storage)) | ||
|
||
return _constraints |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters