add <range-in> spec DSL operator
Add a new spec DSL operator called `range-in` that allowes users of the spec_matcher to match values against numeric ranges. The surrounding brackets determines whether the limit should be inclusive or not. examples: <range-in> [ 10 20 ] : 10 <= x <= 20 <range-in> ( 10 20 ] : 10 < x <= 20 <range-in> [ 10 20 ) : 10 <= x < 20 <range-in> ( 10 20 ) : 10 < x < 20 Closes-Bug: #2052619 Change-Id: I444c01219d02ea7572d4b82117b89b8d3eb75e56 Signed-off-by: Adam Rozman <adam.rozman@est.tech> Co-authored-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
This commit is contained in:
parent
093f20df8d
commit
72c80f6993
@ -27,6 +27,38 @@ def _all_in(x, *y):
|
|||||||
return all(val in x for val in y)
|
return all(val in x for val in y)
|
||||||
|
|
||||||
|
|
||||||
|
def _range_in(x, *y):
|
||||||
|
x = ast.literal_eval(x)
|
||||||
|
if len(y) != 4:
|
||||||
|
raise TypeError("<range-in> operator has to be followed by 2 "
|
||||||
|
"space separated numeric value surrounded by "
|
||||||
|
"brackets \"range_in [ 10 20 ] \"")
|
||||||
|
num_x = float(x)
|
||||||
|
num_y = float(y[1])
|
||||||
|
num_z = float(y[2])
|
||||||
|
if num_y > num_z:
|
||||||
|
raise TypeError("<range-in> operator's first argument has to be "
|
||||||
|
"smaller or equal to the second argument EG"
|
||||||
|
"\"range_in ( 10 20 ] \"")
|
||||||
|
|
||||||
|
if y[0] == '[':
|
||||||
|
lower = num_x >= num_y
|
||||||
|
elif y[0] == '(':
|
||||||
|
lower = num_x > num_y
|
||||||
|
else:
|
||||||
|
raise TypeError("The first element should be an opening bracket "
|
||||||
|
"(\"(\" or \"[\")")
|
||||||
|
|
||||||
|
if y[3] == ']':
|
||||||
|
upper = num_x <= num_z
|
||||||
|
elif y[3] == ')':
|
||||||
|
upper = num_x < num_z
|
||||||
|
else:
|
||||||
|
raise TypeError("The last element should be a closing bracket "
|
||||||
|
"(\")\" or \"]\")")
|
||||||
|
return lower and upper
|
||||||
|
|
||||||
|
|
||||||
op_methods = {
|
op_methods = {
|
||||||
# This one is special/odd,
|
# This one is special/odd,
|
||||||
# TODO(harlowja): fix it so that it's not greater than or
|
# TODO(harlowja): fix it so that it's not greater than or
|
||||||
@ -51,6 +83,7 @@ op_methods = {
|
|||||||
'<all-in>': _all_in,
|
'<all-in>': _all_in,
|
||||||
'<in>': lambda x, y: y in x,
|
'<in>': lambda x, y: y in x,
|
||||||
'<or>': lambda x, *y: any(x == a for a in y),
|
'<or>': lambda x, *y: any(x == a for a in y),
|
||||||
|
'<range-in>': _range_in,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -79,9 +112,12 @@ String operations:
|
|||||||
* ``s>= :`` Greater than or equal
|
* ``s>= :`` Greater than or equal
|
||||||
|
|
||||||
Other operations:
|
Other operations:
|
||||||
* ``<all-in> :`` All items 'in' value
|
* ``<all-in> :`` All items 'in' value
|
||||||
* ``<in> :`` Item 'in' value, like a substring in a string.
|
* ``<in> :`` Item 'in' value, like a substring in a string.
|
||||||
* ``<or> :`` Logical 'or'
|
* ``<or> :`` Logical 'or'
|
||||||
|
* ``<range-in>:`` Range tester with customizable boundary conditions, tests
|
||||||
|
whether value is in the range, boundary condition could be
|
||||||
|
inclusve \'[\' or exclusive \'(\'.
|
||||||
|
|
||||||
If no operator is specified the default is ``s==`` (string equality comparison)
|
If no operator is specified the default is ``s==`` (string equality comparison)
|
||||||
|
|
||||||
@ -91,6 +127,9 @@ Example operations:
|
|||||||
* ``"s== 2.1.0"`` Is the string value equal to ``2.1.0``
|
* ``"s== 2.1.0"`` Is the string value equal to ``2.1.0``
|
||||||
* ``"<in> gcc"`` Is the string ``gcc`` contained in the value string
|
* ``"<in> gcc"`` Is the string ``gcc`` contained in the value string
|
||||||
* ``"<all-in> aes mmx"`` Are both ``aes`` and ``mmx`` in the value
|
* ``"<all-in> aes mmx"`` Are both ``aes`` and ``mmx`` in the value
|
||||||
|
* ``"<range-in> [ 10 20 ]"`` float(value) >= 10 and float(value) <= 20
|
||||||
|
* ``"<range-in> ( 10 20 ]"`` float(value) > 10 and float(value) <= 20
|
||||||
|
* ``"<range-in> ( 10 20 )"`` float(value) > 10 and float(value) < 20
|
||||||
|
|
||||||
:returns: A pyparsing.MatchFirst object. See
|
:returns: A pyparsing.MatchFirst object. See
|
||||||
https://pythonhosted.org/pyparsing/ for details on pyparsing.
|
https://pythonhosted.org/pyparsing/ for details on pyparsing.
|
||||||
@ -113,18 +152,21 @@ Example operations:
|
|||||||
|
|
||||||
all_in_nary_op = pyparsing.Literal("<all-in>")
|
all_in_nary_op = pyparsing.Literal("<all-in>")
|
||||||
or_ = pyparsing.Literal("<or>")
|
or_ = pyparsing.Literal("<or>")
|
||||||
|
range_in_binary_op = pyparsing.Literal("<range-in>")
|
||||||
|
|
||||||
# An atom is anything not an keyword followed by anything but whitespace
|
# An atom is anything not an keyword followed by anything but whitespace
|
||||||
atom = ~(unary_ops | all_in_nary_op | or_) + pyparsing.Regex(r"\S+")
|
atom = ~(unary_ops | all_in_nary_op | or_ | range_in_binary_op) + \
|
||||||
|
pyparsing.Regex(r"\S+")
|
||||||
|
|
||||||
unary = unary_ops + atom
|
unary = unary_ops + atom
|
||||||
|
range_op = range_in_binary_op + atom + atom + atom + atom
|
||||||
nary = all_in_nary_op + pyparsing.OneOrMore(atom)
|
nary = all_in_nary_op + pyparsing.OneOrMore(atom)
|
||||||
disjunction = pyparsing.OneOrMore(or_ + atom)
|
disjunction = pyparsing.OneOrMore(or_ + atom)
|
||||||
|
|
||||||
# Even-numbered tokens will be '<or>', so we drop them
|
# Even-numbered tokens will be '<or>', so we drop them
|
||||||
disjunction.setParseAction(lambda _s, _l, t: ["<or>"] + t[1::2])
|
disjunction.setParseAction(lambda _s, _l, t: ["<or>"] + t[1::2])
|
||||||
|
|
||||||
expr = disjunction | nary | unary | atom
|
expr = disjunction | nary | range_op | unary | atom
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
|
|
||||||
|
@ -435,3 +435,195 @@ class SpecsMatcherTestCase(test_base.BaseTestCase):
|
|||||||
specs_matcher.match,
|
specs_matcher.match,
|
||||||
value="^&*($",
|
value="^&*($",
|
||||||
req='<all-in> aes')
|
req='<all-in> aes')
|
||||||
|
|
||||||
|
def test_specs_fails_not_enough_args_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> [ 10 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_no_brackets_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> 10 20')
|
||||||
|
|
||||||
|
def test_specs_fails_no_opening_bracket_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_no_closing_bracket_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> [ 10 20')
|
||||||
|
|
||||||
|
def test_specs_fails_invalid_brackets_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> { 10 20 }')
|
||||||
|
|
||||||
|
def test_specs_fails_not_opening_brackets_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> ) 10 20 )')
|
||||||
|
|
||||||
|
def test_specs_fails_not_closing_brackets_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> ( 10 20 (')
|
||||||
|
|
||||||
|
def test_specs_fails_reverse_brackets_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> ) 10 20 (')
|
||||||
|
|
||||||
|
def test_specs_fails_too_many_args_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> [ 10 20 30 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_bad_limits_with_op_rangein(self):
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
specs_matcher.match,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> [ 20 10 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_match_beyond_scope_with_op_rangein_le(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> [ 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_match_beyond_scope_with_op_rangein_lt(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="23",
|
||||||
|
req='<range-in> [ 10 20 )')
|
||||||
|
|
||||||
|
def test_specs_fails_match_under_scope_with_op_rangein_ge(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="5",
|
||||||
|
req='<range-in> [ 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_match_under_scope_with_op_rangein_gt(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="5",
|
||||||
|
req='<range-in> ( 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_match_float_beyond_scope_with_op_rangein_le(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="20.3",
|
||||||
|
req='<range-in> [ 10.1 20.2 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_match_float_beyond_scope_with_op_rangein_lt(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="20.3",
|
||||||
|
req='<range-in> [ 10.1 20.2 )')
|
||||||
|
|
||||||
|
def test_specs_fails_match_float_under_scope_with_op_rangein_ge(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="5.0",
|
||||||
|
req='<range-in> [ 5.1 20.2 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_match_float_under_scope_with_op_rangein_gt(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="5.0",
|
||||||
|
req='<range-in> ( 5.1 20.2 ]')
|
||||||
|
|
||||||
|
def test_specs_matches_int_lower_int_range_with_op_rangein_ge(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="10",
|
||||||
|
req='<range-in> [ 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_matchesint_lower_int_range_with_op_rangein_gt(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="10",
|
||||||
|
req='<range-in> ( 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_matches_float_lower_float_range_with_op_rangein_ge(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="10.1",
|
||||||
|
req='<range-in> [ 10.1 20 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_matche_float_lower_float_range_with_op_rangein_gt(
|
||||||
|
self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="10.1",
|
||||||
|
req='<range-in> ( 10.1 20 ]')
|
||||||
|
|
||||||
|
def test_specs_matches_int_with_int_range_with_op_rangein(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="15",
|
||||||
|
req='<range-in> [ 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_matches_float_with_int_limit_with_op_rangein(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="15.5",
|
||||||
|
req='<range-in> [ 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_matches_int_upper_int_range_with_op_rangein(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="20",
|
||||||
|
req='<range-in> [ 10 20 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_matche_int_upper_int_range_with_op_rangein_lt(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="20",
|
||||||
|
req='<range-in> [ 10 20 )')
|
||||||
|
|
||||||
|
def test_specs_matches_float_upper_mixed_range_with_op_rangein(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="20.5",
|
||||||
|
req='<range-in> [ 10 20.5 ]')
|
||||||
|
|
||||||
|
def test_specs_fails_matche_float_upper_mixed_range_with_op_rangein_lt(
|
||||||
|
self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=False,
|
||||||
|
value="20.5",
|
||||||
|
req='<range-in> [ 10 20.5 )')
|
||||||
|
|
||||||
|
def test_specs_matches_float_with_float_limit_with_op_rangein(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="12.5",
|
||||||
|
req='<range-in> [ 10.1 20.1 ]')
|
||||||
|
|
||||||
|
def test_specs_matches_only_one_with_op_rangein(self):
|
||||||
|
self._do_specs_matcher_test(
|
||||||
|
matches=True,
|
||||||
|
value="10.1",
|
||||||
|
req='<range-in> [ 10.1 10.1 ]')
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Introducing a new spec DSL operator called ``<range-in>`` that allows users
|
||||||
|
to match a numeric value against a range of numbers that are delimited with
|
||||||
|
lower and upper limits. The new operator is a binary operator that accepts
|
||||||
|
4 arguments.
|
||||||
|
|
||||||
|
- The first one and the last one are brackets. ``[`` and ``]`` defines
|
||||||
|
inclusive limits while ``(`` and ``)`` defines exclusive limits.
|
||||||
|
|
||||||
|
- The second one is the lower limit while the third one is the upper
|
||||||
|
limit.
|
||||||
|
|
||||||
|
Example: "<range-in> [ 10.4 20 )" will match a value against an range
|
||||||
|
such as the lower limit of the range is 10.4 and the upper limit is 20.
|
||||||
|
Note that 10.4 is included while 20 is excluded.
|
Loading…
Reference in New Issue
Block a user