diff options
author | Jenkins <jenkins@review.openstack.org> | 2016-11-10 16:02:51 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2016-11-10 16:02:51 +0000 |
commit | f86ca3aa224e66ed82e012a3ae41076e1c522ec1 (patch) | |
tree | ae1f041fc5b169e9818d5debde0f75efffcdf973 | |
parent | 805381e16da79b9eb704433c18471464468116f4 (diff) | |
parent | fdc96a7aa80a6c2f24bdbb17858813e7c5dd3c5e (diff) | |
download | oslo-utils-f86ca3aa224e66ed82e012a3ae41076e1c522ec1.tar.gz |
Merge "Restore <all-in> operator"
-rw-r--r-- | oslo_utils/specs_matcher.py | 30 | ||||
-rw-r--r-- | oslo_utils/tests/test_specs_matcher.py | 83 |
2 files changed, 105 insertions, 8 deletions
diff --git a/oslo_utils/specs_matcher.py b/oslo_utils/specs_matcher.py index 5e4177a..d654db5 100644 --- a/oslo_utils/specs_matcher.py +++ b/oslo_utils/specs_matcher.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ast import operator import pyparsing @@ -20,6 +21,15 @@ from pyparsing import Literal from pyparsing import OneOrMore from pyparsing import Regex + +def _all_in(x, *y): + x = ast.literal_eval(x) + if not isinstance(x, list): + raise TypeError("<all-in> must compare with a list literal" + " string, EG \"%s\"" % (['aes', 'mmx'],)) + return all(val in x for val in y) + + op_methods = { # This one is special/odd, # TODO(harlowja): fix it so that it's not greater than or @@ -38,6 +48,7 @@ op_methods = { 's==': operator.eq, 's>': operator.gt, 's>=': operator.ge, + '<all-in>': _all_in, '<in>': lambda x, y: y in x, '<or>': lambda x, *y: any(x == a for a in y), } @@ -61,18 +72,20 @@ def make_grammar(): # Order matters here (so that '>' doesn't match before '>=') Literal("s>=") | Literal("s>")) + all_in_nary_op = Literal("<all-in>") or_ = Literal("<or>") # An atom is anything not an keyword followed by anything but whitespace - atom = ~(unary_ops | or_) + Regex(r"\S+") + atom = ~(unary_ops | all_in_nary_op | or_) + Regex(r"\S+") unary = unary_ops + atom + nary = all_in_nary_op + OneOrMore(atom) disjunction = OneOrMore(or_ + atom) # Even-numbered tokens will be '<or>', so we drop them disjunction.setParseAction(lambda _s, _l, t: ["<or>"] + t[1::2]) - expr = disjunction | unary | atom + expr = disjunction | nary | unary | atom return expr @@ -80,10 +93,11 @@ def match(cmp_value, spec): """Match a given value to a given spec DSL.""" expr = make_grammar() try: - ast = expr.parseString(spec) + tree = expr.parseString(spec) except pyparsing.ParseException: - ast = [spec] - if len(ast) == 1: - return ast[0] == cmp_value - op = op_methods[ast[0]] - return op(cmp_value, *ast[1:]) + tree = [spec] + if len(tree) == 1: + return tree[0] == cmp_value + + op = op_methods[tree[0]] + return op(cmp_value, *tree[1:]) diff --git a/oslo_utils/tests/test_specs_matcher.py b/oslo_utils/tests/test_specs_matcher.py index 30c919b..1d18d45 100644 --- a/oslo_utils/tests/test_specs_matcher.py +++ b/oslo_utils/tests/test_specs_matcher.py @@ -337,3 +337,86 @@ class SpecsMatcherTestCase(test_base.BaseTestCase): value='3.0', req='== 3.1', matches=False) + + def test_specs_matches_all_with_op_allin(self): + self._do_specs_matcher_test( + value=str(['aes', 'mmx', 'aux']), + req='<all-in> aes mmx', + matches=True) + + def test_specs_matches_one_with_op_allin(self): + self._do_specs_matcher_test( + value=str(['aes', 'mmx', 'aux']), + req='<all-in> mmx', + matches=True) + + def test_specs_fails_with_op_allin(self): + self._do_specs_matcher_test( + value=str(['aes', 'mmx', 'aux']), + req='<all-in> txt', + matches=False) + + def test_specs_fails_all_with_op_allin(self): + self._do_specs_matcher_test( + value=str(['aes', 'mmx', 'aux']), + req='<all-in> txt 3dnow', + matches=False) + + def test_specs_fails_match_one_with_op_allin(self): + self._do_specs_matcher_test( + value=str(['aes', 'mmx', 'aux']), + req='<all-in> txt aes', + matches=False) + + def test_specs_fails_match_substr_single(self): + self._do_specs_matcher_test( + value=str(['X_X']), + req='<all-in> _', + matches=False) + + def test_specs_fails_match_substr(self): + self._do_specs_matcher_test( + value=str(['X___X']), + req='<all-in> ___', + matches=False) + + def test_specs_fails_match_substr_reversed(self): + self._do_specs_matcher_test( + value=str(['aes', 'mmx', 'aux']), + req='<all-in> XaesX', + matches=False) + + def test_specs_fails_onechar_with_op_allin(self): + self.assertRaises( + TypeError, + specs_matcher.match, + value=str(['aes', 'mmx', 'aux']), + req='<all-in> e') + + def test_specs_errors_list_with_op_allin(self): + self.assertRaises( + TypeError, + specs_matcher.match, + value=['aes', 'mmx', 'aux'], + req='<all-in> aes') + + def test_specs_errors_str_with_op_allin(self): + self.assertRaises( + TypeError, + specs_matcher.match, + value='aes', + req='<all-in> aes') + + def test_specs_errors_dict_literal_with_op_allin(self): + self.assertRaises( + TypeError, + specs_matcher.match, + value=str({'aes': 1}), + req='<all-in> aes') + + def test_specs_errors_bad_literal_with_op_allin(self): + self.assertRaises( + TypeError, + specs_matcher.match, + value="^&*($", + req='<all-in> aes') |