From cd6ad8485f4a29d88d7c8cd58f5c95d5d11034ed Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Tue, 29 Jan 2019 14:03:47 +0100 Subject: [PATCH] Allow file assertions to include globs --- .../test_zaza_utilities_file_assertions.py | 63 +++++++++++++++++ zaza/charm_tests/security/tests.py | 33 ++++----- zaza/utilities/file_assertions.py | 68 +++++++++++++++++++ 3 files changed, 143 insertions(+), 21 deletions(-) create mode 100644 unit_tests/utilities/test_zaza_utilities_file_assertions.py create mode 100644 zaza/utilities/file_assertions.py diff --git a/unit_tests/utilities/test_zaza_utilities_file_assertions.py b/unit_tests/utilities/test_zaza_utilities_file_assertions.py new file mode 100644 index 0000000..f0eba1c --- /dev/null +++ b/unit_tests/utilities/test_zaza_utilities_file_assertions.py @@ -0,0 +1,63 @@ +# Copyright 2018 Canonical Ltd. +# +# 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 mock +import unit_tests.utils as ut_utils +import zaza.utilities.file_assertions as file_assertions + + +class TestFileAssertionUtils(ut_utils.BaseTestCase): + def setUp(self): + super(TestFileAssertionUtils, self).setUp() + # Patch all run_on_unit calls + self.patch( + 'zaza.utilities.file_assertions.model.run_on_unit', + new_callable=mock.MagicMock(), + name='run_on_unit' + ) + self._assert = mock.MagicMock() + self._assert.assertEqual = mock.MagicMock() + + def test_path_glob(self): + self.run_on_unit.return_value = { + 'Stdout': 'file-name root root 600' + } + file_details = {'path': '*'} + file_assertions.assert_path_glob( + self._assert, 'test/0', file_details) + self.run_on_unit.assert_called_once_with( + 'test/0', 'stat -c "%n %U %G %a" *') + + def test_single_path(self): + self.run_on_unit.return_value = { + 'Stdout': 'root root 600' + } + file_details = {'path': 'test'} + file_assertions.assert_single_file( + self._assert, 'test/0', file_details) + self.run_on_unit.assert_called_once_with( + 'test/0', 'stat -c "%U %G %a" test') + + def test_error_message_glob(self): + message = file_assertions._error_message( + "Owner", "test/0", "root", "/path/to/something") + self.assertEqual( + message, + "Owner is incorrect for /path/to/something on test/0: root") + + def test_error_message_single(self): + + message = file_assertions._error_message( + "Owner", "test/0", "root") + self.assertEqual(message, "Owner is incorrect on test/0: root") diff --git a/zaza/charm_tests/security/tests.py b/zaza/charm_tests/security/tests.py index 51c685e..2a414a2 100644 --- a/zaza/charm_tests/security/tests.py +++ b/zaza/charm_tests/security/tests.py @@ -20,31 +20,20 @@ import unittest import zaza.model as model import zaza.charm_lifecycle.utils as utils +from zaza.utilities.file_assertions import ( + assert_path_glob, + assert_single_file, +) -def _make_test_function(application, file_details): +def _make_test_function(application, file_details, paths=[]): def test(self): - expected_owner = file_details.get("owner", "root") - expected_group = file_details.get("group", "root") - expected_mode = file_details.get("mode", "600") for unit in model.get_units(application): unit = unit.entity_id - result = model.run_on_unit( - unit, 'stat -c "%U %G %a" {}'.format(file_details['path'])) - ownership = result['Stdout'] - owner, group, mode = ownership.split() - self.assertEqual(expected_owner, - owner, - "Owner is incorrect for {}: {}" - .format(unit, owner)) - self.assertEqual(expected_group, - group, - "Group is incorrect for {}: {}" - .format(unit, group)) - self.assertEqual(expected_mode, - mode, - "Mode is incorrect for {}: {}" - .format(unit, mode)) + if '*' in file_details['path']: + assert_path_glob(self, unit, file_details, paths) + else: + assert_single_file(self, unit, file_details) return test @@ -57,7 +46,9 @@ def _add_tests(): # Lets make sure to only add tests for deployed applications if name in deployed_applications: for file in attributes['files']: - test_func = _make_test_function(name, file) + paths = [file['path'] for file in attributes['files']] + paths = [path for path in paths if path[-1] is not "*"] + test_func = _make_test_function(name, file, paths=paths) setattr( cls, 'test_{}_{}'.format(name, file['path']), diff --git a/zaza/utilities/file_assertions.py b/zaza/utilities/file_assertions.py new file mode 100644 index 0000000..08375c1 --- /dev/null +++ b/zaza/utilities/file_assertions.py @@ -0,0 +1,68 @@ +# Copyright 2018 Canonical Ltd. +# +# 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. + +"""Module of helpers for Zaza file assertions.""" + +import zaza.model as model + + +def assert_path_glob(test_case, unit, file_details, paths=[]): + """Verify all files in a given directory.""" + result = model.run_on_unit( + unit, 'stat -c "%n %U %G %a" {}'.format(file_details['path'])) + files = result['Stdout'] + for file in files.splitlines(): + file, owner, group, mode = file.split() + if file not in paths and file not in ['.', '..']: + verify_file(test_case, + unit, + file_details, + owner, + group, + mode, + path=file) + + +def assert_single_file(test_case, unit, file_details): + """Verify ownership of a single file.""" + result = model.run_on_unit( + unit, 'stat -c "%U %G %a" {}'.format(file_details['path'])) + ownership = result['Stdout'] + owner, group, mode = ownership.split() + verify_file(test_case, unit, file_details, owner, group, mode) + + +def verify_file(test_case, unit, file_details, + actual_owner, actual_group, actual_mode, path=None): + """Assert file has correct permissions.""" + expected_owner = file_details.get("owner", "root") + expected_group = file_details.get("group", "root") + expected_mode = file_details.get("mode", "600") + test_case.assertEqual(expected_owner, + actual_owner, + _error_message("Owner", unit, actual_owner, path)) + test_case.assertEqual(expected_group, + actual_group, + _error_message("Group", unit, actual_group, path)) + test_case.assertEqual(expected_mode, + actual_mode, + _error_message("Mode", unit, actual_mode, path)) + + +def _error_message(thing, unit, value, path=None): + if path: + return "{} is incorrect for {} on {}: {}".format( + thing, path, unit, value) + else: + return "{} is incorrect on {}: {}".format(thing, unit, value)