diff --git a/oslo_utils/specs_matcher.py b/oslo_utils/specs_matcher.py new file mode 100644 index 00000000..01486f2c --- /dev/null +++ b/oslo_utils/specs_matcher.py @@ -0,0 +1,69 @@ +# Copyright (c) 2011 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import operator + +# 1. The following operations are supported: +# =, s==, s!=, s>=, s>, s<=, s<, , , , ==, !=, >=, <= +# 2. Note that is handled in a different way below. +# 3. If the first word in the extra_specs is not one of the operators, +# it is ignored. +op_methods = { + '=': lambda x, y: float(x) >= float(y), + '': lambda x, y: y in x, + '': lambda x, y: all(val in x for val in y), + '==': lambda x, y: float(x) == float(y), + '!=': lambda x, y: float(x) != float(y), + '>=': lambda x, y: float(x) >= float(y), + '<=': lambda x, y: float(x) <= float(y), + 's==': operator.eq, + 's!=': operator.ne, + 's<': operator.lt, + 's<=': operator.le, + 's>': operator.gt, + 's>=': operator.ge +} + + +def match(value, req): + words = req.split() + + op = method = None + if words: + op = words.pop(0) + method = op_methods.get(op) + + if op != '' and not method: + return value == req + + if value is None: + return False + + if op == '': # Ex: v1 v2 v3 + while True: + if words.pop(0) == value: + return True + if not words: + break + words.pop(0) # remove a keyword + if not words: + break + return False + + if words: + if op == '': # requires a list not a string + return method(value, words) + return method(value, words[0]) + return False diff --git a/oslo_utils/tests/test_specs_matcher.py b/oslo_utils/tests/test_specs_matcher.py new file mode 100644 index 00000000..7f99f0ac --- /dev/null +++ b/oslo_utils/tests/test_specs_matcher.py @@ -0,0 +1,236 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslotest import base as test_base + +from oslo_utils import specs_matcher + + +class SpecsMatcherTestCase(test_base.BaseTestCase): + def _do_specs_matcher_test(self, value, req, matches): + assertion = self.assertTrue if matches else self.assertFalse + assertion(specs_matcher.match(value, req)) + + def test_specs_matches_simple(self): + self._do_specs_matcher_test( + value='1', + req='1', + matches=True) + + def test_specs_fails_simple(self): + self._do_specs_matcher_test( + value='', + req='1', + matches=False) + + def test_specs_fails_simple2(self): + self._do_specs_matcher_test( + value='3', + req='1', + matches=False) + + def test_specs_fails_simple3(self): + self._do_specs_matcher_test( + value='222', + req='2', + matches=False) + + def test_specs_fails_with_bogus_ops(self): + self._do_specs_matcher_test( + value='4', + req='> 2', + matches=False) + + def test_specs_matches_with_op_eq(self): + self._do_specs_matcher_test( + value='123', + req='= 123', + matches=True) + + def test_specs_matches_with_op_eq2(self): + self._do_specs_matcher_test( + value='124', + req='= 123', + matches=True) + + def test_specs_fails_with_op_eq(self): + self._do_specs_matcher_test( + value='34', + req='= 234', + matches=False) + + def test_specs_fails_with_op_eq3(self): + self._do_specs_matcher_test( + value='34', + req='=', + matches=False) + + def test_specs_matches_with_op_seq(self): + self._do_specs_matcher_test( + value='123', + req='s== 123', + matches=True) + + def test_specs_fails_with_op_seq(self): + self._do_specs_matcher_test( + value='1234', + req='s== 123', + matches=False) + + def test_specs_matches_with_op_sneq(self): + self._do_specs_matcher_test( + value='1234', + req='s!= 123', + matches=True) + + def test_specs_fails_with_op_sneq(self): + self._do_specs_matcher_test( + value='123', + req='s!= 123', + matches=False) + + def test_specs_fails_with_op_sge(self): + self._do_specs_matcher_test( + value='1000', + req='s>= 234', + matches=False) + + def test_specs_fails_with_op_sle(self): + self._do_specs_matcher_test( + value='1234', + req='s<= 1000', + matches=False) + + def test_specs_fails_with_op_sl(self): + self._do_specs_matcher_test( + value='2', + req='s< 12', + matches=False) + + def test_specs_fails_with_op_sg(self): + self._do_specs_matcher_test( + value='12', + req='s> 2', + matches=False) + + def test_specs_matches_with_op_in(self): + self._do_specs_matcher_test( + value='12311321', + req=' 11', + matches=True) + + def test_specs_matches_with_op_in2(self): + self._do_specs_matcher_test( + value='12311321', + req=' 12311321', + matches=True) + + def test_specs_matches_with_op_in3(self): + self._do_specs_matcher_test( + value='12311321', + req=' 12311321 ', + matches=True) + + def test_specs_fails_with_op_in(self): + self._do_specs_matcher_test( + value='12310321', + req=' 11', + matches=False) + + def test_specs_fails_with_op_in2(self): + self._do_specs_matcher_test( + value='12310321', + req=' 11 ', + matches=False) + + def test_specs_matches_with_op_or(self): + self._do_specs_matcher_test( + value='12', + req=' 11 12', + matches=True) + + def test_specs_matches_with_op_or2(self): + self._do_specs_matcher_test( + value='12', + req=' 11 12 ', + matches=True) + + def test_specs_fails_with_op_or(self): + self._do_specs_matcher_test( + value='13', + req=' 11 12', + matches=False) + + def test_specs_fails_with_op_or2(self): + self._do_specs_matcher_test( + value='13', + req=' 11 12 ', + matches=False) + + def test_specs_matches_with_op_le(self): + self._do_specs_matcher_test( + value='2', + req='<= 10', + matches=True) + + def test_specs_fails_with_op_le(self): + self._do_specs_matcher_test( + value='3', + req='<= 2', + matches=False) + + def test_specs_matches_with_op_ge(self): + self._do_specs_matcher_test( + value='3', + req='>= 1', + matches=True) + + def test_specs_fails_with_op_ge(self): + self._do_specs_matcher_test( + value='2', + req='>= 3', + matches=False) + + def test_specs_matches_all_with_op_allin(self): + values = ['aes', 'mmx', 'aux'] + self._do_specs_matcher_test( + value=str(values), + req=' aes mmx', + matches=True) + + def test_specs_matches_one_with_op_allin(self): + values = ['aes', 'mmx', 'aux'] + self._do_specs_matcher_test( + value=str(values), + req=' mmx', + matches=True) + + def test_specs_fails_with_op_allin(self): + values = ['aes', 'mmx', 'aux'] + self._do_specs_matcher_test( + value=str(values), + req=' txt', + matches=False) + + def test_specs_fails_all_with_op_allin(self): + values = ['aes', 'mmx', 'aux'] + self._do_specs_matcher_test( + value=str(values), + req=' txt 3dnow', + matches=False) + + def test_specs_fails_match_one_with_op_allin(self): + values = ['aes', 'mmx', 'aux'] + self._do_specs_matcher_test( + value=str(values), + req=' txt aes', + matches=False)