Merge "Restore <all-in> operator"

This commit is contained in:
Jenkins 2016-11-10 16:02:51 +00:00 committed by Gerrit Code Review
commit f86ca3aa22
2 changed files with 105 additions and 8 deletions

View File

@ -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:])

View File

@ -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')