diff --git a/tests/unit/test_hardware.py b/tests/unit/test_hardware.py index 98a544a237..341be10db4 100644 --- a/tests/unit/test_hardware.py +++ b/tests/unit/test_hardware.py @@ -67,6 +67,26 @@ def test_constraint_name_pattern(value: str, expected: tuple[Any, Any]) -> None: assert match.groups() == expected +_size_constraint_pattern_input = [ + ({'name': 'num_with_default', 'raw_value': '10', 'default_unit': 'GiB'}, + 'num_with_default: == 10 gibibyte'), + ({'name': 'num_without_default', 'raw_value': '1024'}, 'num_without_default: == 1024 byte'), + ({'name': 'num_with_unit', 'raw_value': '10 GiB', 'default_unit': 'MiB'}, + 'num_with_unit: == 10 GiB'), + ] + + +@pytest.mark.parametrize( + ('value', 'expected'), + _size_constraint_pattern_input, + ) +def test_constraint_default_unit(value: dict, expected: tuple[Any, Any]) -> None: + constraint_out = tmt.hardware.SizeConstraint.from_specification(**value) + + assert constraint_out is not None + assert str(constraint_out) == expected + + _constraint_components_pattern_input = [ ('memory 10 GiB', ('memory', None, None, None, '10 GiB')), ('cpu.processors != 4 ', ('cpu', None, 'processors', '!=', '4')), diff --git a/tmt/hardware.py b/tmt/hardware.py index 43fae1edd6..deb40f8748 100644 --- a/tmt/hardware.py +++ b/tmt/hardware.py @@ -493,7 +493,7 @@ class Constraint(BaseConstraint, Generic[ConstraintValueT]): raw_value: str # If set, it is a raw unit specified by the constraint. - unit: Optional[str] = None + default_unit: Optional[str] = None # If set, it is a "bigger" constraint, to which this constraint logically # belongs as one of its aspects. @@ -507,7 +507,8 @@ def _from_specification( as_quantity: bool = True, as_cast: Optional[Callable[[str], ConstraintValueT]] = None, original_constraint: Optional['Constraint[Any]'] = None, - allowed_operators: Optional[list[Operator]] = None + allowed_operators: Optional[list[Operator]] = None, + default_unit: Optional[Any] = "bytes" ) -> T: """ Parse raw constraint specification into our internal representation. @@ -520,6 +521,7 @@ def _from_specification( :param original_constraint: when specified, new constraint logically belongs to ``original_constraint``, possibly representing one of its aspects. :param allowed_operators: if specified, only operators on this list are accepted. + :param default_unit: if raw_value contains no unit, this unit will be appended. :raises ParseError: when parsing fails, or the operator is now allowed. :returns: a :py:class:`Constraint` representing the given specification. """ @@ -560,7 +562,7 @@ def _from_specification( if not isinstance( value, pint.Quantity): # type: ignore[reportUnnecessaryIsInstance,unused-ignore] - value = pint.Quantity(value) + value = pint.Quantity(value, default_unit) elif as_cast is not None: value = as_cast(raw_value) @@ -672,14 +674,16 @@ def from_specification( name: str, raw_value: str, original_constraint: Optional['Constraint[Any]'] = None, - allowed_operators: Optional[list[Operator]] = None + allowed_operators: Optional[list[Operator]] = None, + default_unit: Optional[Any] = 'bytes' ) -> T: return cls._from_specification( name, raw_value, as_quantity=True, original_constraint=original_constraint, - allowed_operators=allowed_operators + allowed_operators=allowed_operators, + default_unit=default_unit )