# Copyright (c) 2015 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. from unittest import mock from oslotest import base as test_base from oslo_policy import _checks from oslo_policy import _parser from oslo_policy.tests import base class ParseCheckTestCase(test_base.BaseTestCase): def test_false(self): result = _parser._parse_check('!') self.assertIsInstance(result, _checks.FalseCheck) def test_true(self): result = _parser._parse_check('@') self.assertIsInstance(result, _checks.TrueCheck) @mock.patch.object(_parser, 'LOG') def test_bad_rule(self, mock_log): result = _parser._parse_check('foobar') self.assertIsInstance(result, _checks.FalseCheck) mock_log.exception.assert_called_once() @mock.patch.object(_checks, 'registered_checks', {}) @mock.patch.object(_parser, 'LOG') def test_no_handler(self, mock_log): result = _parser._parse_check('no:handler') self.assertIsInstance(result, _checks.FalseCheck) mock_log.error.assert_called() @mock.patch.object(_checks, 'registered_checks', { 'spam': mock.Mock(return_value='spam_check'), None: mock.Mock(return_value='none_check'), }) def test_check(self): result = _parser._parse_check('spam:handler') self.assertEqual('spam_check', result) _checks.registered_checks['spam'].assert_called_once_with('spam', 'handler') self.assertFalse(_checks.registered_checks[None].called) @mock.patch.object(_checks, 'registered_checks', { None: mock.Mock(return_value='none_check'), }) def test_check_default(self): result = _parser._parse_check('spam:handler') self.assertEqual('none_check', result) _checks.registered_checks[None].assert_called_once_with('spam', 'handler') class ParseListRuleTestCase(test_base.BaseTestCase): def test_empty(self): result = _parser._parse_list_rule([]) self.assertIsInstance(result, _checks.TrueCheck) self.assertEqual('@', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_zeroele(self): result = _parser._parse_list_rule([[]]) self.assertIsInstance(result, _checks.FalseCheck) self.assertEqual('!', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_bare(self): result = _parser._parse_list_rule(['rule']) self.assertIsInstance(result, base.FakeCheck) self.assertEqual('rule', result.result) self.assertEqual('rule', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_oneele(self): result = _parser._parse_list_rule([['rule']]) self.assertIsInstance(result, base.FakeCheck) self.assertEqual('rule', result.result) self.assertEqual('rule', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_oneele_multi(self): result = _parser._parse_list_rule([['rule1', 'rule2']]) self.assertIsInstance(result, _checks.AndCheck) self.assertEqual(2, len(result.rules)) for i, value in enumerate(['rule1', 'rule2']): self.assertIsInstance(result.rules[i], base.FakeCheck) self.assertEqual(value, result.rules[i].result) self.assertEqual('(rule1 and rule2)', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_multi_oneele(self): result = _parser._parse_list_rule([['rule1'], ['rule2']]) self.assertIsInstance(result, _checks.OrCheck) self.assertEqual(2, len(result.rules)) for i, value in enumerate(['rule1', 'rule2']): self.assertIsInstance(result.rules[i], base.FakeCheck) self.assertEqual(value, result.rules[i].result) self.assertEqual('(rule1 or rule2)', str(result)) @mock.patch.object(_parser, '_parse_check', base.FakeCheck) def test_multi_multi(self): result = _parser._parse_list_rule([['rule1', 'rule2'], ['rule3', 'rule4']]) self.assertIsInstance(result, _checks.OrCheck) self.assertEqual(2, len(result.rules)) for i, values in enumerate([['rule1', 'rule2'], ['rule3', 'rule4']]): self.assertIsInstance(result.rules[i], _checks.AndCheck) self.assertEqual(2, len(result.rules[i].rules)) for j, value in enumerate(values): self.assertIsInstance(result.rules[i].rules[j], base.FakeCheck) self.assertEqual(value, result.rules[i].rules[j].result) self.assertEqual('((rule1 and rule2) or (rule3 and rule4))', str(result)) class ParseTokenizeTestCase(test_base.BaseTestCase): @mock.patch.object(_parser, '_parse_check', lambda x: x) def test_tokenize(self): exemplar = ("(( ( ((() And)) or ) (check:%(miss)s) not)) " "'a-string' \"another-string\"") expected = [ ('(', '('), ('(', '('), ('(', '('), ('(', '('), ('(', '('), ('(', '('), (')', ')'), ('and', 'And'), (')', ')'), (')', ')'), ('or', 'or'), (')', ')'), ('(', '('), ('check', 'check:%(miss)s'), (')', ')'), ('not', 'not'), (')', ')'), (')', ')'), ('string', 'a-string'), ('string', 'another-string'), ] result = list(_parser._parse_tokenize(exemplar)) self.assertEqual(expected, result) class ParseStateMetaTestCase(test_base.BaseTestCase): def test_reducer(self): @_parser.reducer('a', 'b', 'c') @_parser.reducer('d', 'e', 'f') def spam(): pass self.assertTrue(hasattr(spam, 'reducers')) self.assertEqual([['d', 'e', 'f'], ['a', 'b', 'c']], spam.reducers) def test_parse_state_meta(self): class FakeState(metaclass=_parser.ParseStateMeta): @_parser.reducer('a', 'b', 'c') @_parser.reducer('d', 'e', 'f') def reduce1(self): pass @_parser.reducer('g', 'h', 'i') def reduce2(self): pass self.assertTrue(hasattr(FakeState, 'reducers')) for reduction, reducer in FakeState.reducers: if (reduction == ['a', 'b', 'c'] or reduction == ['d', 'e', 'f']): self.assertEqual('reduce1', reducer) elif reduction == ['g', 'h', 'i']: self.assertEqual('reduce2', reducer) else: self.fail('Unrecognized reducer discovered') class ParseStateTestCase(test_base.BaseTestCase): def test_init(self): state = _parser.ParseState() self.assertEqual([], state.tokens) self.assertEqual([], state.values) @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True) def test_reduce_none(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok2'] state.values = ['val2'] state.reduce() self.assertEqual(['tok2'], state.tokens) self.assertEqual(['val2'], state.values) self.assertFalse(mock_meth.called) @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1', 'tok2'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True) def test_reduce_short(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok1'] state.values = ['val1'] state.reduce() self.assertEqual(['tok1'], state.tokens) self.assertEqual(['val1'], state.values) self.assertFalse(mock_meth.called) @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1', 'tok2'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True, return_value=[('tok3', 'val3')]) def test_reduce_one(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok1', 'tok2'] state.values = ['val1', 'val2'] state.reduce() self.assertEqual(['tok3'], state.tokens) self.assertEqual(['val3'], state.values) mock_meth.assert_called_once_with('val1', 'val2') @mock.patch.object(_parser.ParseState, 'reducers', [ (['tok1', 'tok4'], 'meth2'), (['tok2', 'tok3'], 'meth1'), ]) @mock.patch.object(_parser.ParseState, 'meth1', create=True, return_value=[('tok4', 'val4')]) @mock.patch.object(_parser.ParseState, 'meth2', create=True, return_value=[('tok5', 'val5')]) def test_reduce_two(self, mock_meth2, mock_meth1): state = _parser.ParseState() state.tokens = ['tok1', 'tok2', 'tok3'] state.values = ['val1', 'val2', 'val3'] state.reduce() self.assertEqual(['tok5'], state.tokens) self.assertEqual(['val5'], state.values) mock_meth1.assert_called_once_with('val2', 'val3') mock_meth2.assert_called_once_with('val1', 'val4') @mock.patch.object(_parser.ParseState, 'reducers', [(['tok1', 'tok2'], 'meth')]) @mock.patch.object(_parser.ParseState, 'meth', create=True, return_value=[('tok3', 'val3'), ('tok4', 'val4')]) def test_reduce_multi(self, mock_meth): state = _parser.ParseState() state.tokens = ['tok1', 'tok2'] state.values = ['val1', 'val2'] state.reduce() self.assertEqual(['tok3', 'tok4'], state.tokens) self.assertEqual(['val3', 'val4'], state.values) mock_meth.assert_called_once_with('val1', 'val2') def test_shift(self): state = _parser.ParseState() with mock.patch.object(_parser.ParseState, 'reduce') as mock_reduce: state.shift('token', 'value') self.assertEqual(['token'], state.tokens) self.assertEqual(['value'], state.values) mock_reduce.assert_called_once_with() def test_result_empty(self): state = _parser.ParseState() self.assertRaises(ValueError, lambda: state.result) def test_result_unreduced(self): state = _parser.ParseState() state.tokens = ['tok1', 'tok2'] state.values = ['val1', 'val2'] self.assertRaises(ValueError, lambda: state.result) def test_result(self): state = _parser.ParseState() state.tokens = ['token'] state.values = ['value'] self.assertEqual('value', state.result) def test_wrap_check(self): state = _parser.ParseState() result = state._wrap_check('(', 'the_check', ')') self.assertEqual([('check', 'the_check')], result) @mock.patch.object(_checks, 'AndCheck', lambda x: x) def test_make_and_expr(self): state = _parser.ParseState() result = state._make_and_expr('check1', 'and', 'check2') self.assertEqual([('and_expr', ['check1', 'check2'])], result) def test_extend_and_expr(self): state = _parser.ParseState() mock_expr = mock.Mock() mock_expr.add_check.return_value = 'newcheck' result = state._extend_and_expr(mock_expr, 'and', 'check') self.assertEqual([('and_expr', 'newcheck')], result) mock_expr.add_check.assert_called_once_with('check') @mock.patch.object(_checks, 'OrCheck', lambda x: x) def test_make_or_expr(self): state = _parser.ParseState() result = state._make_or_expr('check1', 'or', 'check2') self.assertEqual([('or_expr', ['check1', 'check2'])], result) def test_extend_or_expr(self): state = _parser.ParseState() mock_expr = mock.Mock() mock_expr.add_check.return_value = 'newcheck' result = state._extend_or_expr(mock_expr, 'or', 'check') self.assertEqual([('or_expr', 'newcheck')], result) mock_expr.add_check.assert_called_once_with('check') @mock.patch.object(_checks, 'NotCheck', lambda x: 'not %s' % x) def test_make_not_expr(self): state = _parser.ParseState() result = state._make_not_expr('not', 'check') self.assertEqual([('check', 'not check')], result) class ParseTextRuleTestCase(test_base.BaseTestCase): def test_empty(self): result = _parser._parse_text_rule('') self.assertIsInstance(result, _checks.TrueCheck) @mock.patch.object(_parser, '_parse_tokenize', return_value=[('tok1', 'val1'), ('tok2', 'val2')]) @mock.patch.object(_parser.ParseState, 'shift') @mock.patch.object(_parser.ParseState, 'result', 'result') def test_shifts(self, mock_shift, mock_parse_tokenize): result = _parser._parse_text_rule('test rule') self.assertEqual('result', result) mock_parse_tokenize.assert_called_once_with('test rule') mock_shift.assert_has_calls( [mock.call('tok1', 'val1'), mock.call('tok2', 'val2')]) @mock.patch.object(_parser, 'LOG', new=mock.Mock()) @mock.patch.object(_parser, '_parse_tokenize', return_value=[]) def test_fail(self, mock_parse_tokenize): result = _parser._parse_text_rule('test rule') self.assertIsInstance(result, _checks.FalseCheck) mock_parse_tokenize.assert_called_once_with('test rule') def test_A_or_B_or_C(self): result = _parser._parse_text_rule('@ or ! or @') self.assertEqual('(@ or ! or @)', str(result)) def test_A_or_B_and_C(self): result = _parser._parse_text_rule('@ or ! and @') self.assertEqual('(@ or (! and @))', str(result)) def test_A_and_B_or_C(self): result = _parser._parse_text_rule('@ and ! or @') self.assertEqual('((@ and !) or @)', str(result)) def test_A_and_B_and_C(self): result = _parser._parse_text_rule('@ and ! and @') self.assertEqual('(@ and ! and @)', str(result)) def test_A_or_B_or_C_or_D(self): result = _parser._parse_text_rule('@ or ! or @ or !') self.assertEqual('(@ or ! or @ or !)', str(result)) def test_A_or_B_or_C_and_D(self): result = _parser._parse_text_rule('@ or ! or @ and !') self.assertEqual('(@ or ! or (@ and !))', str(result)) def test_A_or_B_and_C_or_D(self): result = _parser._parse_text_rule('@ or ! and @ or !') self.assertEqual('(@ or (! and @) or !)', str(result)) def test_A_or_B_and_C_and_D(self): result = _parser._parse_text_rule('@ or ! and @ and !') self.assertEqual('(@ or (! and @ and !))', str(result)) def test_A_and_B_or_C_or_D(self): result = _parser._parse_text_rule('@ and ! or @ or !') self.assertEqual('((@ and !) or @ or !)', str(result)) def test_A_and_B_or_C_and_D(self): result = _parser._parse_text_rule('@ and ! or @ and !') self.assertEqual('((@ and !) or (@ and !))', str(result)) def test_A_and_B_and_C_or_D(self): result = _parser._parse_text_rule('@ and ! and @ or !') self.assertEqual('((@ and ! and @) or !)', str(result)) def test_A_and_B_and_C_and_D(self): result = _parser._parse_text_rule('@ and ! and @ and !') self.assertEqual('(@ and ! and @ and !)', str(result)) def test_A_and_B_or_C_with_not_1(self): result = _parser._parse_text_rule('not @ and ! or @') self.assertEqual('((not @ and !) or @)', str(result)) def test_A_and_B_or_C_with_not_2(self): result = _parser._parse_text_rule('@ and not ! or @') self.assertEqual('((@ and not !) or @)', str(result)) def test_A_and_B_or_C_with_not_3(self): result = _parser._parse_text_rule('@ and ! or not @') self.assertEqual('((@ and !) or not @)', str(result)) def test_A_and_B_or_C_with_group_1(self): for expression in ['( @ ) and ! or @', '@ and ( ! ) or @', '@ and ! or ( @ )', '( @ ) and ! or ( @ )', '@ and ( ! ) or ( @ )', '( @ ) and ( ! ) or ( @ )', '( @ and ! ) or @', '( ( @ ) and ! ) or @', '( @ and ( ! ) ) or @', '( ( @ and ! ) ) or @', '( @ and ! or @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((@ and !) or @)', str(result)) def test_A_and_B_or_C_with_group_2(self): result = _parser._parse_text_rule('@ and ( ! or @ )') self.assertEqual('(@ and (! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_1(self): for expression in ['not ( @ ) and ! or @', 'not @ and ( ! ) or @', 'not @ and ! or ( @ )', '( not @ ) and ! or @', '( not @ and ! ) or @', '( not @ and ! or @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((not @ and !) or @)', str(result)) def test_A_and_B_or_C_with_group_and_not_2(self): result = _parser._parse_text_rule('not @ and ( ! or @ )') self.assertEqual('(not @ and (! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_3(self): result = _parser._parse_text_rule('not ( @ and ! or @ )') self.assertEqual('not ((@ and !) or @)', str(result)) def test_A_and_B_or_C_with_group_and_not_4(self): for expression in ['( @ ) and not ! or @', '@ and ( not ! ) or @', '@ and not ( ! ) or @', '@ and not ! or ( @ )', '( @ and not ! ) or @', '( @ and not ! or @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((@ and not !) or @)', str(result)) def test_A_and_B_or_C_with_group_and_not_5(self): result = _parser._parse_text_rule('@ and ( not ! or @ )') self.assertEqual('(@ and (not ! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_6(self): result = _parser._parse_text_rule('@ and not ( ! or @ )') self.assertEqual('(@ and not (! or @))', str(result)) def test_A_and_B_or_C_with_group_and_not_7(self): for expression in ['( @ ) and ! or not @', '@ and ( ! ) or not @', '@ and ! or not ( @ )', '@ and ! or ( not @ )', '( @ and ! ) or not @', '( @ and ! or not @ )']: result = _parser._parse_text_rule(expression) self.assertEqual('((@ and !) or not @)', str(result)) def test_A_and_B_or_C_with_group_and_not_8(self): result = _parser._parse_text_rule('@ and ( ! or not @ )') self.assertEqual('(@ and (! or not @))', str(result)) class ParseRuleTestCase(test_base.BaseTestCase): @mock.patch.object(_parser, '_parse_text_rule', return_value='text rule') @mock.patch.object(_parser, '_parse_list_rule', return_value='list rule') def test_parse_rule_string(self, mock_parse_list_rule, mock_parse_text_rule): result = _parser.parse_rule('a string') self.assertEqual('text rule', result) self.assertFalse(mock_parse_list_rule.called) mock_parse_text_rule.assert_called_once_with('a string') @mock.patch.object(_parser, '_parse_text_rule', return_value='text rule') @mock.patch.object(_parser, '_parse_list_rule', return_value='list rule') def test_parse_rule_list(self, mock_parse_list_rule, mock_parse_text_rule): result = _parser.parse_rule([['a'], ['list']]) self.assertEqual('list rule', result) self.assertFalse(mock_parse_text_rule.called) mock_parse_list_rule.assert_called_once_with([['a'], ['list']])