summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-11-10 16:02:51 +0000
committerGerrit Code Review <review@openstack.org>2016-11-10 16:02:51 +0000
commitf86ca3aa224e66ed82e012a3ae41076e1c522ec1 (patch)
treeae1f041fc5b169e9818d5debde0f75efffcdf973
parent805381e16da79b9eb704433c18471464468116f4 (diff)
parentfdc96a7aa80a6c2f24bdbb17858813e7c5dd3c5e (diff)
downloadoslo-utils-f86ca3aa224e66ed82e012a3ae41076e1c522ec1.tar.gz
Merge "Restore <all-in> operator"
-rw-r--r--oslo_utils/specs_matcher.py30
-rw-r--r--oslo_utils/tests/test_specs_matcher.py83
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')