コード例 #1
0
ファイル: test_roles.py プロジェクト: ashton/provy
class RoleTest(ProvyTestCase):
    def setUp(self):
        loader = ChoiceLoader([
            FileSystemLoader(os.path.join(PROJECT_ROOT, 'files'))
        ])
        context = {
            'owner': 'foo',
            'registered_loaders': [],
            'loader': loader,
            'cleanup': [],
            'host': 'localhost',
        }
        self.role = Role(prov=None, context=context)
        self.update_data = UpdateData('/tmp/some-file.ext', 'some local md5', 'some remote md5')

    @contextmanager
    def mock_update_data(self):
        with self.mock_role_method('_build_update_data'):
            self.role._build_update_data.return_value = self.update_data
            yield

    @istest
    def checks_if_a_remote_directory_exists(self):
        with self.execute_mock() as execute:
            execute.return_value = '0'
            self.assertTrue(self.role.remote_exists_dir('/some_path'))

            execute.assert_called_with('test -d /some_path; echo $?', stdout=False, sudo=True)

    @istest
    def checks_if_a_remote_directory_doesnt_exist(self):
        with self.execute_mock() as execute:
            execute.return_value = '1'
            self.assertFalse(self.role.remote_exists_dir('/some_path'))

            execute.assert_called_with('test -d /some_path; echo $?', stdout=False, sudo=True)

    @istest
    def doesnt_create_directory_if_it_already_exists(self):
        with self.mock_role_method('remote_exists_dir') as remote_exists_dir, self.execute_mock() as execute:
            remote_exists_dir.return_value = True
            self.role.ensure_dir('/some_path')
            self.assertFalse(execute.called)

    @istest
    def creates_the_directory_if_it_doesnt_exist(self):
        with self.mock_role_method('remote_exists_dir') as remote_exists_dir, self.execute_mock() as execute:
            remote_exists_dir.return_value = False
            self.role.ensure_dir('/some_path')
            execute.assert_called_with('mkdir -p /some_path', stdout=False, sudo=False)

    @istest
    def gets_distro_info_for_debian(self):
        with self.execute_mock() as execute:
            execute.return_value = 'No LSB modules are available.\nDistributor ID:\tDebian\nDescription:\tDebian GNU/Linux 6.0.5 (squeeze)\nRelease:\t6.0.5\nCodename:\tsqueeze'
            distro_info = self.role.get_distro_info()
            execute.assert_called_with('lsb_release -a')
            self.assertEqual(distro_info.distributor_id, 'Debian')
            self.assertEqual(distro_info.description, 'Debian GNU/Linux 6.0.5 (squeeze)')
            self.assertEqual(distro_info.release, '6.0.5')
            self.assertEqual(distro_info.codename, 'squeeze')

    @istest
    def gets_distro_info_for_ubuntu(self):
        with self.execute_mock() as execute:
            execute.return_value = 'No LSB modules are available.\r\nDistributor ID:\tUbuntu\r\nDescription:\tUbuntu 12.04.1 LTS\r\nRelease:\t12.04\r\nCodename:\tprecise'
            distro_info = self.role.get_distro_info()
            execute.assert_called_with('lsb_release -a')
            self.assertEqual(distro_info.distributor_id, 'Ubuntu')
            self.assertEqual(distro_info.description, 'Ubuntu 12.04.1 LTS')
            self.assertEqual(distro_info.release, '12.04')
            self.assertEqual(distro_info.codename, 'precise')

    @istest
    def gets_distro_info_for_centos(self):
        with self.execute_mock() as execute:
            execute.return_value = 'LSB Version:\t:core-4.0-ia32:core-4.0-noarch:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-ia32:printing-4.0-noarch\nDistributor ID:\tCentOS\nDescription:\tCentOS release 5.8 (Final)\nRelease:\t5.8\nCodename:\tFinal'
            distro_info = self.role.get_distro_info()
            execute.assert_called_with('lsb_release -a')
            self.assertEqual(distro_info.lsb_version, ':core-4.0-ia32:core-4.0-noarch:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-ia32:printing-4.0-noarch')
            self.assertEqual(distro_info.distributor_id, 'CentOS')
            self.assertEqual(distro_info.description, 'CentOS release 5.8 (Final)')
            self.assertEqual(distro_info.release, '5.8')
            self.assertEqual(distro_info.codename, 'Final')

    @istest
    def doesnt_hit_the_server_twice_to_get_distro_info(self):
        with self.execute_mock() as execute:
            execute.return_value = 'No LSB modules are available.\r\nDistributor ID:\tUbuntu\r\nDescription:\tUbuntu 12.04.1 LTS\r\nRelease:\t12.04\r\nCodename:\tprecise'

            distro_info1 = self.role.get_distro_info()
            distro_info2 = self.role.get_distro_info()

            execute.assert_called_once_with('lsb_release -a')
            self.assertEqual(distro_info1, distro_info2)

    @istest
    def ignores_line_if_already_exists_in_file(self):
        with self.mock_role_methods("has_line", "put_file", "execute") as mocked:
            has_line, _, execute = mocked
            has_line.return_value = True
            self.role.ensure_line('this line in', '/some/file')
            self.assertFalse(execute.called)

    @istest
    def inserts_line_if_it_doesnt_exist_yet(self):

        LINE_CONTENTS = 'this line in'
        REMOTE_TMP_FILE = "/tmp/foo"
        with self.mock_role_methods("has_line", "put_file", "execute", "create_remote_temp_file") as mocked:
            has_line, put_file, execute, create_remote_temp_file = mocked
            create_remote_temp_file.return_value = REMOTE_TMP_FILE
            has_line.return_value = False
            self.role.ensure_line(LINE_CONTENTS, "/some/file")
            put_file.assert_called_with(FileContentMatcher(self, LINE_CONTENTS), REMOTE_TMP_FILE, False, stdout=False)
            execute.assert_called_with('cat {} >> /some/file && echo >> /some/file'.format(REMOTE_TMP_FILE), stdout=False, sudo=False, user=None)

    @istest
    def inserts_line_if_it_doesnt_exist_yet_with_sudo(self):

        LINE_CONTENTS = 'this line in'
        REMOTE_TMP_FILE = "/tmp/foo"
        with self.mock_role_methods("has_line", "put_file", "execute", "create_remote_temp_file") as mocked:
            has_line, put_file, execute, create_remote_temp_file = mocked
            create_remote_temp_file.return_value = REMOTE_TMP_FILE
            has_line.return_value = False
            self.role.ensure_line(LINE_CONTENTS, "/some/file", sudo=True)
            put_file.assert_called_with(FileContentMatcher(self, LINE_CONTENTS), REMOTE_TMP_FILE, True, stdout=False)
            execute.assert_called_with('cat {} >> /some/file && echo >> /some/file'.format(REMOTE_TMP_FILE), stdout=False, sudo=True, user=None)

    @istest
    def inserts_line_if_it_doesnt_exist_yet_with_user(self):

        LINE_CONTENTS = 'this line in'
        REMOTE_TMP_FILE = "/tmp/foo"
        with self.mock_role_methods("has_line", "put_file", "execute", "create_remote_temp_file") as mocked:
            has_line, put_file, execute, create_remote_temp_file = mocked
            create_remote_temp_file.return_value = REMOTE_TMP_FILE
            has_line.return_value = False
            self.role.ensure_line(LINE_CONTENTS, "/some/file", owner="foo")
            put_file.assert_called_with(FileContentMatcher(self, LINE_CONTENTS), REMOTE_TMP_FILE, True, stdout=False)
            execute.assert_called_with('cat {} >> /some/file && echo >> /some/file'.format(REMOTE_TMP_FILE), stdout=False, sudo=False, user="******")

    @istest
    def registers_a_template_loader(self):
        package_name = 'provy.more.debian.monitoring'

        self.assertNotIn(package_name, self.role.context['registered_loaders'])
        self.role.register_template_loader(package_name)
        self.assertIn(package_name, self.role.context['registered_loaders'])

        choice_loader = self.role.context['loader']
        package_loader = choice_loader.loaders[1]
        self.assertIn('monitoring', package_loader.provider.module_path)

    @istest
    def doesnt_register_a_template_loader_twice(self):
        package_name = 'provy.more.debian.monitoring'

        self.assertNotIn(package_name, self.role.context['registered_loaders'])
        self.role.register_template_loader(package_name)
        self.role.register_template_loader(package_name)
        self.assertIn(package_name, self.role.context['registered_loaders'])

        self.assertEqual(self.role.context['registered_loaders'], ['provy.more.debian.monitoring'])

    @istest
    def appends_role_instance_to_cleanup_list_when_scheduling_cleanup(self):
        self.assertEqual(self.role.context['cleanup'], [])
        self.role.schedule_cleanup()
        self.assertEqual(self.role.context['cleanup'], [self.role])

    @istest
    def doesnt_append_again_if_role_is_already_in_cleanup_list(self):
        same_class_instance = Role(None, {})
        self.role.context['cleanup'] = [same_class_instance]
        self.role.schedule_cleanup()
        self.assertEqual(self.role.context['cleanup'], [same_class_instance])

    @istest
    def appends_role_instance_to_cleanup_list_when_same_class_doesnt_exist_yet(self):
        class DummyRole(Role):
            pass

        same_class_instance = DummyRole(None, {})
        self.role.context['cleanup'] = [same_class_instance]
        self.role.schedule_cleanup()
        self.assertEqual(self.role.context['cleanup'], [same_class_instance, self.role])

    @istest
    def provisions_role(self):
        role_instance = MagicMock()

        def StubRole(prov, context):
            return role_instance

        self.role.provision_role(StubRole)

        role_instance.provision.assert_called_with()

    @istest
    def schedules_cleanup_when_provisioning(self):
        role_instance = MagicMock()

        def StubRole(prov, context):
            return role_instance

        self.role.provision_role(StubRole)

        role_instance.schedule_cleanup.assert_called_with()

    @istest
    def can_call_cleanup_safely(self):
        self.role.cleanup()

    @istest
    def executes_command_with_stdout_and_same_user(self):
        with patch('fabric.api.run') as run:
            self.role.execute('some command', stdout=True)

            run.assert_called_with('some command')

    @istest
    def executes_command_with_stdout_and_sudo(self):
        with patch('fabric.api.sudo') as sudo:
            self.role.execute('some command', stdout=True, sudo=True)

            sudo.assert_called_with('some command', user=None)

    @istest
    def executes_command_with_stdout_and_another_user(self):
        with patch('fabric.api.sudo') as sudo:
            self.role.execute('some command', stdout=True, user='******')

            sudo.assert_called_with('some command', user='******')

    @istest
    def executes_command_without_stdout_but_same_user(self):
        with patch('fabric.api.run') as run, patch('fabric.api.hide') as hide:
            self.role.execute('some command', stdout=False)

            run.assert_called_with('some command')
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def executes_command_without_stdout_but_sudo(self):
        with patch('fabric.api.sudo') as sudo, patch('fabric.api.hide') as hide:
            self.role.execute('some command', stdout=False, sudo=True)

            sudo.assert_called_with('some command', user=None)
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def executes_command_without_stdout_but_another_user(self):
        with patch('fabric.api.sudo') as sudo, patch('fabric.api.hide') as hide:
            self.role.execute('some command', stdout=False, user='******')

            sudo.assert_called_with('some command', user='******')
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def execute_command_check_cd_called_if_cwd_arg(self):
        with patch('fabric.api.run'):
            with patch('fabric.api.cd') as cd:
                self.role.execute("some command", cwd="/some/dir")
        cd.assert_called_once_with("/some/dir")

    @istest
    def execute_command_check_cd_called_if_no_cwd_arg(self):
        with patch('fabric.api.run'):
            with patch('fabric.api.cd') as cd:
                self.role.execute("some command")
        self.assertFalse(cd.called)

    @istest
    def executes_a_local_command_with_stdout_and_same_user(self):
        with patch('fabric.api.local') as local:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=True), 'some result')

            local.assert_called_with('some command', capture=True)

    @istest
    def executes_a_local_command_with_stdout_and_sudo(self):
        with patch('fabric.api.local') as local:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=True, sudo=True), 'some result')

            local.assert_called_with('sudo some command', capture=True)

    @istest
    def executes_a_local_command_with_stdout_and_another_user(self):
        with patch('fabric.api.local') as local:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=True, user='******'), 'some result')

            local.assert_called_with('sudo -u foo some command', capture=True)

    @istest
    def executes_a_local_command_without_stdout_and_another_user(self):
        with patch('fabric.api.local') as local, patch('fabric.api.hide') as hide:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=False, user='******'), 'some result')

            local.assert_called_with('sudo -u foo some command', capture=True)
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def executes_a_python_command(self):
        with self.execute_mock() as execute:
            self.role.execute_python('some command', stdout='is stdout?', sudo='is sudo?')

            execute.assert_called_with('python -c "some command"', stdout='is stdout?', sudo='is sudo?')

    @istest
    def gets_logged_user(self):
        with self.execute_mock() as execute:
            execute.return_value = 'some user'
            user = self.role.get_logged_user()

            self.assertEqual(user, 'some user')
            execute.assert_called_with('whoami', stdout=False)

    @istest
    def verifies_that_remote_file_exists(self):
        with self.execute_mock() as execute:
            execute.return_value = '0'

            self.assertTrue(self.role.remote_exists('/some.path'))
            execute.assert_called_with('test -f /some.path; echo $?', stdout=False, sudo=True)

    @istest
    def verifies_that_remote_file_doesnt_exist(self):
        with self.execute_mock() as execute:
            execute.return_value = '1'

            self.assertFalse(self.role.remote_exists('/some.path'))
            execute.assert_called_with('test -f /some.path; echo $?', stdout=False, sudo=True)

    @istest
    def verifies_that_a_local_file_exists(self):
        file_to_verify = os.path.abspath(__file__)
        self.assertTrue(self.role.local_exists(file_to_verify))

    @istest
    def verifies_that_a_local_file_doesnt_exist(self):
        file_to_verify = '/some/sneaky.file'
        self.assertFalse(self.role.local_exists(file_to_verify))

    @istest
    def creates_a_local_temp_dir(self):
        self.assertTrue(self.role.local_temp_dir().startswith(tempfile.gettempdir()))

    @istest
    def creates_a_remote_temp_dir(self):
        with self.mock_role_method('execute_python') as execute_python:
            execute_python.return_value = '/some/remote/temp/dir'
            directory = self.role.remote_temp_dir()
            self.assertEqual(directory, '/some/remote/temp/dir')
            execute_python.assert_called_with('from tempfile import gettempdir; print gettempdir()', stdout=False)

    @istest
    def changes_the_owner_of_a_path(self):
        with self.execute_mock() as execute:
            self.role.change_path_owner('/some/path', 'foo')

            execute.assert_called_with('chown -R foo /some/path', stdout=False, sudo=True)

    @istest
    def creates_a_directory_when_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = False

            self.role.ensure_dir('/some/dir')

            execute.assert_called_with('mkdir -p /some/dir', stdout=False, sudo=False)

    @istest
    def creates_a_directory_with_sudo_when_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = False

            self.role.ensure_dir('/some/dir', sudo=True)

            execute.assert_called_with('mkdir -p /some/dir', stdout=False, sudo=True)

    @istest
    def creates_a_directory_with_specific_user_when_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir, self.mock_role_method('change_path_owner') as change_path_owner:
            remote_exists_dir.return_value = False

            self.role.ensure_dir('/some/dir', owner='foo')

            execute.assert_called_with('mkdir -p /some/dir', stdout=False, sudo=True)
            change_path_owner.assert_called_with('/some/dir', 'foo')

    @istest
    def gets_object_mode_from_remote_file(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True
            execute.return_value = '755\n'

            self.assertEqual(self.role.get_object_mode('/some/file.ext'), 755)
            execute.assert_called_with('stat -c %a /some/file.ext', stdout=False, sudo=True)

    @istest
    def cannot_get_mode_if_file_doesnt_exist(self):
        with self.execute_mock(), self.mock_role_method('remote_exists') as remote_exists, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists.return_value = False
            remote_exists_dir.return_value = False

            self.assertRaises(IOError, self.role.get_object_mode, '/some/file.ext')

    @istest
    def changes_the_mode_of_a_path_if_its_different(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 644

            self.role.change_path_mode('/some/path', 755)

            execute.assert_called_with('chmod 755 /some/path', stdout=False, sudo=True)

    @istest
    def recursively_changes_the_mode_of_a_path_if_its_different(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 644

            self.role.change_path_mode('/some/path', 755, recursive=True)

            execute.assert_called_with('chmod -R 755 /some/path', stdout=False, sudo=True)

    @istest
    def doesnt_change_path_mode_if_its_the_same(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 755

            self.role.change_path_mode('/some/path', 755)

            self.assertFalse(execute.called)

    @istest
    def recursively_changes_the_mode_of_a_path_even_if_the_mode_of_the_parent_path_is_the_same(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 755

            self.role.change_path_mode('/some/path', 755, recursive=True)

            execute.assert_called_with('chmod -R 755 /some/path', stdout=False, sudo=True)

    @istest
    def gets_the_md5_hash_of_a_local_file(self):
        with self.mock_role_method('execute_local') as execute_local, self.mock_role_method('local_exists') as local_exists:
            local_exists.return_value = True
            execute_local.return_value = 'some-hash\n'

            self.assertEqual(self.role.md5_local('/some/path'), 'some-hash')
            execute_local.assert_called_with('md5sum /some/path | cut -d " " -f 1', stdout=False, sudo=True)

    @istest
    def returns_none_if_local_file_doesnt_exist_for_md5_hash(self):
        with self.mock_role_method('execute_local') as execute_local, self.mock_role_method('local_exists') as local_exists:
            local_exists.return_value = False

            self.assertIsNone(self.role.md5_local('/some/path'))
            self.assertFalse(execute_local.called)

    @istest
    def gets_the_md5_hash_of_a_remote_file(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True
            execute.return_value = 'some-hash\n'

            self.assertEqual(self.role.md5_remote('/some/path'), 'some-hash')
            execute.assert_called_with('md5sum /some/path | cut -d " " -f 1', stdout=False, sudo=True)

    @istest
    def returns_none_if_remote_file_doesnt_exist_for_md5_hash(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = False

            self.assertIsNone(self.role.md5_remote('/some/path'))
            self.assertFalse(execute.called)

    @istest
    def removes_a_directory_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = True

            self.assertTrue(self.role.remove_dir('/some/dir'))
            execute.assert_called_with('rmdir /some/dir', stdout=False, sudo=False)

    @istest
    def doesnt_remove_a_directory_if_it_doesnt_exist(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = False

            self.assertFalse(self.role.remove_dir('/some/dir'))
            self.assertFalse(execute.called)

    @istest
    def removes_a_directory_recursively_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = True

            self.assertTrue(self.role.remove_dir('/some/dir', recursive=True))
            execute.assert_called_with('rm -rf /some/dir', stdout=False, sudo=False)

    @istest
    def removes_a_directory_as_sudo_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = True

            self.assertTrue(self.role.remove_dir('/some/dir', sudo=True))
            execute.assert_called_with('rmdir /some/dir', stdout=False, sudo=True)

    @istest
    def removes_dir_silently_if_requested(self):
        with self.execute_mock():
            with self.mock_role_method('log') as log:
                with self.mock_role_method('remote_exists_dir') as exists:
                    exists.return_value = True
                    self.role.remove_dir('/some/dir', sudo=True, stdout=False)
        self.assertFalse(log.called)

    @istest
    def removes_dir_logging_if_requested(self):
        with self.execute_mock():
            with self.mock_role_method('log') as log:
                with self.mock_role_method('remote_exists_dir') as exists:
                    exists.return_value = True
                    self.role.remove_dir('/some/dir', sudo=True, stdout=True)
        self.assertEqual(len(log.mock_calls), 1)

    @istest
    def puts_file_silently_if_requested(self):
        with patch("fabric.api.put"):
            with self.mock_role_method("_Role__showing_command_output") as showing:
                self.role.put_file("foo", "bar", stdout=False)
        showing.assert_called_once_with(False)

    @istest
    def puts_file_chatting_if_requested(self):
        with patch("fabric.api.put"):
            with self.mock_role_method("_Role__showing_command_output") as showing:
                self.role.put_file("foo", "bar", stdout=True)
        showing.assert_called_once_with(True)

    @istest
    def removes_a_file_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True

            self.assertTrue(self.role.remove_file('/some/file.ext'))
            execute.assert_called_with('rm -f /some/file.ext', stdout=False, sudo=False)

    @istest
    def doesnt_remove_a_file_if_it_doesnt_exist(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = False

            self.assertFalse(self.role.remove_file('/some/dir'))
            self.assertFalse(execute.called)

    @istest
    def removes_a_file_as_sudo_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True

            self.assertTrue(self.role.remove_file('/some/file.ext', sudo=True))
            execute.assert_called_with('rm -f /some/file.ext', stdout=False, sudo=True)

    @istest
    def puts_a_file_in_the_remote_path(self):
        with patch('fabric.api.put') as put:
            self.role.put_file('/from/file', '/to/file')

            put.assert_called_with('/from/file', '/to/file', use_sudo=False)

    @istest
    def puts_a_file_as_sudo_in_the_remote_path(self):
        with patch('fabric.api.put') as put:
            self.role.put_file('/from/file', '/to/file', sudo=True)

            put.assert_called_with('/from/file', '/to/file', use_sudo=True)

    @istest
    def creates_a_remote_symbolic_link_if_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            remote_from_exists = True
            remote_to_exists = False
            sudo = 'is it sudo?'
            remote_exists.side_effect = (remote_from_exists, remote_to_exists)

            self.role.remote_symlink(from_file, to_file, sudo=sudo)

            self.assertEqual(remote_exists.mock_calls, [
                call(from_file),
                call(to_file),
            ])
            execute.assert_called_with('ln -sf %s %s' % (from_file, to_file), sudo=sudo, stdout=False)

    @istest
    def creates_a_remote_symbolic_link_if_it_exists_but_with_different_path(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            another_from_file = '/another/from/file'
            remote_from_exists = True
            remote_to_exists = True
            sudo = 'is it sudo?'
            remote_exists.side_effect = (remote_from_exists, remote_to_exists)
            execute.side_effect = ('-rw-rw-r-- 1 foo foo 4490 Dez 11 22:24 %s -> %s' % (to_file, another_from_file), None)

            self.role.remote_symlink(from_file, to_file, sudo=sudo)

            self.assertEqual(remote_exists.mock_calls, [
                call(from_file),
                call(to_file),
            ])
            self.assertEqual(execute.mock_calls, [
                call('ls -la %s' % to_file, stdout=False, sudo=sudo),
                call('ln -sf %s %s' % (from_file, to_file), sudo=sudo, stdout=False),
            ])

    @istest
    def doesnt_create_symlink_if_file_with_same_name_already_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            remote_from_exists = True
            remote_to_exists = True
            sudo = 'is it sudo?'
            remote_exists.side_effect = (remote_from_exists, remote_to_exists)
            execute.side_effect = ('-rw-rw-r-- 1 foo foo 4490 Dez 11 22:24 %s' % to_file, None)

            self.role.remote_symlink(from_file, to_file, sudo=sudo)

            self.assertEqual(remote_exists.mock_calls, [
                call(from_file),
                call(to_file),
            ])
            self.assertEqual(execute.mock_calls, [
                call('ls -la %s' % to_file, stdout=False, sudo=sudo),
            ])

    @istest
    def doesnt_create_symlink_if_symlink_with_same_name_already_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            remote_from_exists = True
            remote_to_exists = True
            sudo = 'is it sudo?'
            remote_exists.side_effect = (remote_from_exists, remote_to_exists)
            execute.side_effect = ('-rw-rw-r-- 1 foo foo 4490 Dez 11 22:24 %s -> %s' % (to_file, from_file), None)

            self.role.remote_symlink(from_file, to_file, sudo=sudo)

            self.assertEqual(remote_exists.mock_calls, [
                call(from_file),
                call(to_file),
            ])
            self.assertEqual(execute.mock_calls, [
                call('ls -la %s' % to_file, stdout=False, sudo=sudo),
            ])

    @istest
    def raises_exception_if_remote_file_doesnt_exist(self):
        with self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            remote_from_exists = False
            remote_exists.side_effect = (remote_from_exists, )

            self.assertRaises(RuntimeError, self.role.remote_symlink, from_file, to_file)

    @istest
    def renders_a_template_based_on_absolute_path(self):
        template_file = os.path.join(PROJECT_ROOT, 'tests', 'unit', 'fixtures', 'some_template.txt')
        options = {'foo': 'FOO!'}

        content = self.role.render(template_file, options)

        self.assertIn('foo=FOO!', content)

    @istest
    def renders_a_template_based_on_filename(self):
        template_dir = os.path.join(PROJECT_ROOT, 'tests', 'unit', 'fixtures')
        self.role.context['loader'] = FileSystemLoader(template_dir)
        template_file = 'some_template.txt'
        options = {'foo': 'FOO!'}

        content = self.role.render(template_file, options)

        self.assertIn('foo=FOO!', content)

    @istest
    def writes_ascii_content_to_a_temp_file(self):
        content = 'some content'

        temp_file = self.role.write_to_temp_file(content)
        try:
            self.assertEqual(os.path.dirname(temp_file), tempfile.gettempdir())
            self.assertTrue(os.path.isfile(temp_file))

            with open(temp_file) as f:
                saved_content = f.read().strip()
                self.assertEqual(saved_content, content)
        finally:
            os.remove(temp_file)

    @istest
    def writes_utf8_content_to_a_temp_file(self):
        content = u'Tarek Ziadé'

        temp_file = self.role.write_to_temp_file(content)

        try:
            self.assertEqual(os.path.dirname(temp_file), tempfile.gettempdir())
            self.assertTrue(os.path.isfile(temp_file))

            with open(temp_file) as f:
                saved_content = f.read().decode('utf-8').strip()
                self.assertEqual(saved_content, content)
        finally:
            os.remove(temp_file)

    @istest
    def creates_a_new_file_when_remote_doesnt_exist_during_update(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_update_data(), self.mock_role_methods('_force_update_file', 'remote_exists'):
            self.role.remote_exists.return_value = False

            self.assertTrue(self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner))
            self.role._force_update_file.assert_called_with(to_file, sudo, self.update_data.local_temp_path, owner)

    @istest
    def updates_file_with_sudo_when_user_is_passed_but_sudo_not(self):
        to_file = '/etc/foo.conf'
        owner = 'foo'

        with self.mock_update_data(), self.mock_role_methods('put_file', 'change_path_owner', 'remote_exists'):
            self.role.remote_exists.return_value = False

            self.assertTrue(self.role.update_file('some template', to_file, options='some options', owner=owner))
            self.role.put_file.assert_called_with(self.update_data.local_temp_path, to_file, True)

    @istest
    def doesnt_use_sudo_implicitly_if_owner_not_passed(self):
        to_file = '/etc/foo.conf'

        with self.mock_update_data(), self.mock_role_methods('put_file', 'change_path_owner', 'remote_exists'):
            self.role.remote_exists.return_value = False

            self.assertTrue(self.role.update_file('some template', to_file, options='some options'))
            self.role.put_file.assert_called_with(self.update_data.local_temp_path, to_file, False)

    @istest
    def updates_file_when_remote_exists_but_is_different(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_update_data(), self.mock_role_methods('_force_update_file', 'remote_exists'):
            self.role.remote_exists.return_value = True
            self.update_data.from_md5 = 'some local md5'
            self.update_data.to_md5 = 'some remote md5'

            self.assertTrue(self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner))
            self.role._force_update_file.assert_called_with(to_file, sudo, self.update_data.local_temp_path, owner)

    @istest
    def cleans_temp_file_after_updating(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with open(self.update_data.local_temp_path, 'w') as f:
            f.write('foo')

        with self.mock_update_data(), self.mock_role_methods('_force_update_file', 'remote_exists'):
            self.role.remote_exists.return_value = True

            self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner)

            self.assertFalse(os.path.exists(self.update_data.local_temp_path))

    @istest
    def doesnt_update_file_when_content_is_the_same(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_update_data(), self.mock_role_methods('_force_update_file', 'remote_exists'):
            self.role.remote_exists.return_value = True
            self.update_data.from_md5 = 'same md5'
            self.update_data.to_md5 = 'same md5'

            result = self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner)

            self.assertFalse(result)
            self.assertFalse(self.role._force_update_file.called)

    @istest
    def builds_update_data(self):
        from_file = os.path.join(PROJECT_ROOT, 'tests', 'unit', 'fixtures', 'some_template.txt')
        to_file = '/etc/foo.conf'
        options = {'foo': 'FOO!'}
        local_temp_path = '/tmp/template-to-update'
        md5_local = 'some local md5'
        md5_remote = 'some remote md5'

        with self.mock_role_methods('write_to_temp_file', 'md5_local', 'md5_remote'):
            self.role.write_to_temp_file.return_value = local_temp_path
            self.role.md5_local.return_value = md5_local
            self.role.md5_remote.return_value = md5_remote

            update_data = self.role._build_update_data(from_file, options, to_file)

            self.assertEqual(update_data.local_temp_path, local_temp_path)
            self.assertEqual(update_data.from_md5, md5_local)
            self.assertEqual(update_data.to_md5, md5_remote)

    @istest
    def really_updates_file_without_owner(self):
        to_file = '/etc/foo.conf'
        local_temp_path = '/tmp/template-to-update'
        sudo = 'is it sudo?'
        owner = None

        with self.mock_role_method('put_file'):
            self.role._force_update_file(to_file, sudo, local_temp_path, owner)

            self.role.put_file.assert_called_with(local_temp_path, to_file, sudo)

    @istest
    def really_updates_file_with_owner(self):
        to_file = '/etc/foo.conf'
        local_temp_path = '/tmp/template-to-update'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_role_methods('put_file', 'change_path_owner'):
            self.role._force_update_file(to_file, sudo, local_temp_path, owner)

            self.role.put_file.assert_called_with(local_temp_path, to_file, sudo)
            self.role.change_path_owner.assert_called_with(to_file, owner)

    @istest
    def checks_that_content_differs_when_md5_is_different(self):
        self.assertTrue(self.role._contents_differ('some local md5', 'some remote md5'))

    @istest
    def checks_that_content_doesnt_differ_when_md5_is_the_same(self):
        self.assertFalse(self.role._contents_differ('same md5', 'same md5'))

    @istest
    def checks_that_content_doesnt_differ_when_md5_is_the_same_even_with_spaces(self):
        self.assertFalse(self.role._contents_differ('same md5      ', '  same md5'))

    @istest
    def checks_that_content_differs_when_a_md5_is_none(self):
        self.assertTrue(self.role._contents_differ(None, 'some md5'))
        self.assertTrue(self.role._contents_differ('some md5', None))
        self.assertFalse(self.role._contents_differ(None, None))

    @istest
    def reads_a_remote_file(self):
        path = '/some/path'
        sudo = 'is it sudo?'
        content = 'some content'
        with self.mock_role_method('execute_python') as execute_python:
            execute_python.return_value = content

            self.assertEqual(self.role.read_remote_file(path, sudo), content)

            execute_python.assert_called_with("import codecs; print codecs.open('%s', 'r', 'utf-8').read()" % path, stdout=False, sudo=sudo)

    @istest
    def checks_that_a_process_is_running(self):
        process = 'nginx'
        sudo = 'is it sudo?'

        with self.execute_mock() as execute:
            execute.return_value = '0'

            self.assertTrue(self.role.is_process_running(process, sudo=sudo))
            execute.assert_called_with('ps aux | egrep %s | egrep -v egrep > /dev/null;echo $?' % process, stdout=False, sudo=sudo)

    @istest
    def checks_that_a_process_is_not_running(self):
        process = 'nginx'
        sudo = 'is it sudo?'

        with self.execute_mock() as execute:
            execute.return_value = '1'

            self.assertFalse(self.role.is_process_running(process, sudo=sudo))
            execute.assert_called_with('ps aux | egrep %s | egrep -v egrep > /dev/null;echo $?' % process, stdout=False, sudo=sudo)

    @istest
    def checks_that_a_file_has_a_certain_line(self):
        content = """
        some content
        127.0.0.1    localhost
        some other content
        """
        file_path = '/some/path'
        line = '127.0.0.1 localhost'

        with self.mock_role_methods('remote_exists', 'read_remote_file'):
            self.role.remote_exists.return_value = True
            self.role.read_remote_file.return_value = content

            self.assertTrue(self.role.has_line(line, file_path))

            self.role.remote_exists.assert_called_with(file_path)
            self.role.read_remote_file.assert_called_with(file_path)

    @istest
    def checks_that_a_file_has_a_certain_line_metachars(self):
        content = "some content\r\n127.0.0.1    localhost\r\nsome other content"
        file_path = '/some/path'
        line = '127.0.0.1 localhost'

        with self.mock_role_methods('remote_exists', 'read_remote_file'):
            self.role.remote_exists.return_value = True
            self.role.read_remote_file.return_value = content

            self.assertTrue(self.role.has_line(line, file_path))

            self.role.remote_exists.assert_called_with(file_path)
            self.role.read_remote_file.assert_called_with(file_path)

    @istest
    def checks_that_a_file_doesnt_have_a_certain_line(self):
        content = """
        some content
        127.0.0.1    localhost
        some other content
        """
        file_path = '/some/path'
        line = '192.168.0.1 my-gateway'

        with self.mock_role_methods('remote_exists', 'read_remote_file'):
            self.role.remote_exists.return_value = True
            self.role.read_remote_file.return_value = content

            self.assertFalse(self.role.has_line(line, file_path))

            self.role.remote_exists.assert_called_with(file_path)
            self.role.read_remote_file.assert_called_with(file_path)

    @istest
    def checks_that_a_file_doesnt_have_a_certain_line_when_file_doesnt_exist(self):
        file_path = '/some/path'
        line = '192.168.0.1 my-gateway'

        with self.mock_role_methods('remote_exists', 'read_remote_file'):
            self.role.remote_exists.return_value = False

            self.assertFalse(self.role.has_line(line, file_path))

            self.role.remote_exists.assert_called_with(file_path)
            self.assertFalse(self.role.read_remote_file.called)

    @istest
    def uses_role_context_manager(self):
        manager = self.role.using('some role')
        self.assertEqual(manager.role, 'some role')
        self.assertEqual(manager.context, self.role.context)
        self.assertEqual(manager.prov, self.role.prov)

    @istest
    def test_roles_in_context(self):
        dir = {}
        self.role.context["roles_in_context"] = dir
        self.assertIs(self.role.roles_in_context, dir)

    @istest
    def removes_paths_if_in_paths_to_remove(self):

        self.role._paths_to_remove.add("foo")
        with self.mock_role_method("remove_dir") as remove:
            self.role.cleanup()
        remove.assert_called_once_with("foo", True, True)

    @istest
    def assert_paths_to_remove_empty_on_creation(self):
        self.assertEqual(len(self.role._paths_to_remove), 0)

    @istest
    def assert_no_exception_on_error_while_deleting(self):
        self.role._paths_to_remove.add("foo")
        with patch("provy.core.roles.Role.remove_dir", Mock(side_effect=IOError)):
            self.role.cleanup()

    @istest
    def assert_error_logged_on_deleting(self):
        self.role._paths_to_remove.add("foo")
        with patch("provy.core.roles.Role.remove_dir", Mock(side_effect=IOError)):
            with self.mock_role_method("log") as log:
                self.role.cleanup()
        self.assertEqual(len(log.mock_calls), 1)

    @istest
    def test_remote_list_dir(self):
        with self.mock_role_method("execute_python") as execute:
            execute.return_value = "{}"
            self.role.remote_list_directory("/some/path")
        execute.assert_called_once_with('''import os, json; print json.dumps(os.listdir('/some/path'))''', False, True)

    @istest
    def puts_file_when_executing_python_script(self):
        sudo = 'is it sudo?'
        stdout = 'should it stdout?'
        with self.mock_role_methods('execute', 'create_remote_temp_file', 'put_file') as (execute, create_remote_temp_file, put_file):
            create_remote_temp_file.return_value = "/tmp/scriptfoo.py"

            self.role.execute_python_script("script", stdout, sudo)

            put_file.assert_called_once_with(ANY, "/tmp/scriptfoo.py", sudo, False)
            execute.assert_called_once_with(
                'python "{}"'.format("/tmp/scriptfoo.py"),
                stdout,
                sudo
            )

    @istest
    def test_script_converted(self):
        with patch.multiple("provy.core.roles.Role", execute=DEFAULT,
                            create_remote_temp_file=DEFAULT,
                            put_file=DEFAULT) as values:
            values['create_remote_temp_file'].return_value = "/tmp/scriptfoo.py"
            self.role.execute_python_script("script", False, False)

        self.assertTrue(isinstance(values['put_file'].mock_calls[0][1][0], StringIO))

    @istest
    def test_script_file_not_converted(self):
        script = Mock(spec=file)
        with patch.multiple("provy.core.roles.Role", execute=DEFAULT,
                            create_remote_temp_file=DEFAULT,
                            put_file=DEFAULT) as values:
            values['create_remote_temp_file'].return_value = "/tmp/scriptfoo.py"
            self.role.execute_python_script(script, False, False)

        self.assertIs(values['put_file'].mock_calls[0][1][0], script)
コード例 #2
0
ファイル: test_roles.py プロジェクト: rafaelnovello/provy
class RoleTest(ProvyTestCase):
    def setUp(self):
        loader = ChoiceLoader([
            FileSystemLoader(os.path.join(PROJECT_ROOT, 'files'))
        ])
        context = {
            'owner': 'foo',
            'registered_loaders': [],
            'loader': loader,
            'cleanup': [],
            'host': 'localhost',
        }
        self.role = Role(prov=None, context=context)
        self.update_data = UpdateData('/tmp/some-file.ext', 'some local md5', 'some remote md5')

    @contextmanager
    def mock_update_data(self):
        with self.mock_role_method('_build_update_data'):
            self.role._build_update_data.return_value = self.update_data
            yield

    @istest
    def checks_if_a_remote_directory_exists(self):
        with self.execute_mock() as execute:
            execute.return_value = '0'
            self.assertTrue(self.role.remote_exists_dir('/some_path'))

            execute.assert_called_with('test -d /some_path; echo $?', stdout=False, sudo=True)

    @istest
    def checks_if_a_remote_directory_doesnt_exist(self):
        with self.execute_mock() as execute:
            execute.return_value = '1'
            self.assertFalse(self.role.remote_exists_dir('/some_path'))

            execute.assert_called_with('test -d /some_path; echo $?', stdout=False, sudo=True)

    @istest
    def doesnt_create_directory_if_it_already_exists(self):
        with self.mock_role_method('remote_exists_dir') as remote_exists_dir, self.execute_mock() as execute:
            remote_exists_dir.return_value = True
            self.role.ensure_dir('/some_path')
            self.assertFalse(execute.called)

    @istest
    def creates_the_directory_if_it_doesnt_exist(self):
        with self.mock_role_method('remote_exists_dir') as remote_exists_dir, self.execute_mock() as execute:
            remote_exists_dir.return_value = False
            self.role.ensure_dir('/some_path')
            execute.assert_called_with('mkdir -p /some_path', stdout=False, sudo=False)

    @istest
    def gets_distro_info_for_debian(self):
        with self.execute_mock() as execute:
            execute.return_value = 'No LSB modules are available.\nDistributor ID:\tDebian\nDescription:\tDebian GNU/Linux 6.0.5 (squeeze)\nRelease:\t6.0.5\nCodename:\tsqueeze'
            distro_info = self.role.get_distro_info()
            execute.assert_called_with('lsb_release -a')
            self.assertEqual(distro_info.distributor_id, 'Debian')
            self.assertEqual(distro_info.description, 'Debian GNU/Linux 6.0.5 (squeeze)')
            self.assertEqual(distro_info.release, '6.0.5')
            self.assertEqual(distro_info.codename, 'squeeze')

    @istest
    def gets_distro_info_for_ubuntu(self):
        with self.execute_mock() as execute:
            execute.return_value = 'No LSB modules are available.\r\nDistributor ID:\tUbuntu\r\nDescription:\tUbuntu 12.04.1 LTS\r\nRelease:\t12.04\r\nCodename:\tprecise'
            distro_info = self.role.get_distro_info()
            execute.assert_called_with('lsb_release -a')
            self.assertEqual(distro_info.distributor_id, 'Ubuntu')
            self.assertEqual(distro_info.description, 'Ubuntu 12.04.1 LTS')
            self.assertEqual(distro_info.release, '12.04')
            self.assertEqual(distro_info.codename, 'precise')

    @istest
    def gets_distro_info_for_centos(self):
        with self.execute_mock() as execute:
            execute.return_value = 'LSB Version:\t:core-4.0-ia32:core-4.0-noarch:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-ia32:printing-4.0-noarch\nDistributor ID:\tCentOS\nDescription:\tCentOS release 5.8 (Final)\nRelease:\t5.8\nCodename:\tFinal'
            distro_info = self.role.get_distro_info()
            execute.assert_called_with('lsb_release -a')
            self.assertEqual(distro_info.lsb_version, ':core-4.0-ia32:core-4.0-noarch:graphics-4.0-ia32:graphics-4.0-noarch:printing-4.0-ia32:printing-4.0-noarch')
            self.assertEqual(distro_info.distributor_id, 'CentOS')
            self.assertEqual(distro_info.description, 'CentOS release 5.8 (Final)')
            self.assertEqual(distro_info.release, '5.8')
            self.assertEqual(distro_info.codename, 'Final')

    @istest
    def ignores_line_if_already_exists_in_file(self):
        with self.mock_role_method('has_line') as has_line, self.execute_mock() as execute:
            has_line.return_value = True
            self.role.ensure_line('this line in', '/some/file')
            self.assertFalse(execute.called)

    @istest
    def inserts_line_if_it_doesnt_exist_yet(self):
        with self.mock_role_method('has_line') as has_line, self.execute_mock() as execute:
            has_line.return_value = False
            self.role.ensure_line('this line in', '/some/file')
            execute.assert_called_with('echo "this line in" >> /some/file', stdout=False, sudo=False, user=None)

    @istest
    def inserts_line_with_sudo(self):
        with self.mock_role_method('has_line') as has_line, self.execute_mock() as execute:
            has_line.return_value = False
            self.role.ensure_line('this line in', '/some/file', sudo=True)
            execute.assert_called_with('echo "this line in" >> /some/file', stdout=False, sudo=True, user=None)

    @istest
    def inserts_line_with_specific_user(self):
        with self.mock_role_method('has_line') as has_line, self.execute_mock() as execute:
            has_line.return_value = False
            self.role.ensure_line('this line in', '/some/file', owner='foo')
            execute.assert_called_with('echo "this line in" >> /some/file', stdout=False, sudo=False, user='******')

    @istest
    def registers_a_template_loader(self):
        package_name = 'provy.more.debian.monitoring'

        self.assertNotIn(package_name, self.role.context['registered_loaders'])
        self.role.register_template_loader(package_name)
        self.assertIn(package_name, self.role.context['registered_loaders'])

        choice_loader = self.role.context['loader']
        package_loader = choice_loader.loaders[1]
        self.assertIn('monitoring', package_loader.provider.module_path)

    @istest
    def appends_role_instance_to_cleanup_list_when_scheduling_cleanup(self):
        self.assertEqual(self.role.context['cleanup'], [])
        self.role.schedule_cleanup()
        self.assertEqual(self.role.context['cleanup'], [self.role])

    @istest
    def doesnt_append_again_if_role_is_already_in_cleanup_list(self):
        same_class_instance = Role(None, {})
        self.role.context['cleanup'] = [same_class_instance]
        self.role.schedule_cleanup()
        self.assertEqual(self.role.context['cleanup'], [same_class_instance])

    @istest
    def provisions_role(self):
        role_instance = MagicMock()

        def StubRole(prov, context):
            return role_instance

        self.role.provision_role(StubRole)

        role_instance.provision.assert_called_with()

    @istest
    def schedules_cleanup_when_provisioning(self):
        role_instance = MagicMock()

        def StubRole(prov, context):
            return role_instance

        self.role.provision_role(StubRole)

        role_instance.schedule_cleanup.assert_called_with()

    @istest
    def can_call_cleanup_safely(self):
        self.role.cleanup()

    @istest
    def executes_command_with_stdout_and_same_user(self):
        with patch('fabric.api.run') as run:
            self.role.execute('some command', stdout=True)

            run.assert_called_with('some command')

    @istest
    def executes_command_with_stdout_and_sudo(self):
        with patch('fabric.api.sudo') as sudo:
            self.role.execute('some command', stdout=True, sudo=True)

            sudo.assert_called_with('some command', user=None)

    @istest
    def executes_command_with_stdout_and_another_user(self):
        with patch('fabric.api.sudo') as sudo:
            self.role.execute('some command', stdout=True, user='******')

            sudo.assert_called_with('some command', user='******')

    @istest
    def executes_command_without_stdout_but_same_user(self):
        with patch('fabric.api.run') as run, patch('fabric.api.hide') as hide:
            self.role.execute('some command', stdout=False)

            run.assert_called_with('some command')
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def executes_command_without_stdout_but_sudo(self):
        with patch('fabric.api.sudo') as sudo, patch('fabric.api.hide') as hide:
            self.role.execute('some command', stdout=False, sudo=True)

            sudo.assert_called_with('some command', user=None)
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def executes_command_without_stdout_but_another_user(self):
        with patch('fabric.api.sudo') as sudo, patch('fabric.api.hide') as hide:
            self.role.execute('some command', stdout=False, user='******')

            sudo.assert_called_with('some command', user='******')
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def executes_a_local_command_with_stdout_and_same_user(self):
        with patch('fabric.api.local') as local:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=True), 'some result')

            local.assert_called_with('some command', capture=True)

    @istest
    def executes_a_local_command_with_stdout_and_sudo(self):
        with patch('fabric.api.local') as local:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=True, sudo=True), 'some result')

            local.assert_called_with('sudo some command', capture=True)

    @istest
    def executes_a_local_command_with_stdout_and_another_user(self):
        with patch('fabric.api.local') as local:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=True, user='******'), 'some result')

            local.assert_called_with('sudo -u foo some command', capture=True)

    @istest
    def executes_a_local_command_without_stdout_and_another_user(self):
        with patch('fabric.api.local') as local, patch('fabric.api.hide') as hide:
            local.return_value = 'some result'
            self.assertEqual(self.role.execute_local('some command', stdout=False, user='******'), 'some result')

            local.assert_called_with('sudo -u foo some command', capture=True)
            hide.assert_called_with('warnings', 'running', 'stdout', 'stderr')

    @istest
    def executes_a_python_command(self):
        with self.execute_mock() as execute:
            self.role.execute_python('some command', stdout='is stdout?', sudo='is sudo?')

            execute.assert_called_with('python -c "some command"', stdout='is stdout?', sudo='is sudo?')

    @istest
    def gets_logged_user(self):
        with self.execute_mock() as execute:
            execute.return_value = 'some user'
            user = self.role.get_logged_user()

            self.assertEqual(user, 'some user')
            execute.assert_called_with('whoami', stdout=False)

    @istest
    def verifies_that_remote_file_exists(self):
        with self.execute_mock() as execute:
            execute.return_value = '0'

            self.assertTrue(self.role.remote_exists('/some.path'))
            execute.assert_called_with('test -f /some.path; echo $?', stdout=False, sudo=True)

    @istest
    def verifies_that_remote_file_doesnt_exist(self):
        with self.execute_mock() as execute:
            execute.return_value = '1'

            self.assertFalse(self.role.remote_exists('/some.path'))
            execute.assert_called_with('test -f /some.path; echo $?', stdout=False, sudo=True)

    @istest
    def verifies_that_a_local_file_exists(self):
        file_to_verify = os.path.abspath(__file__)
        self.assertTrue(self.role.local_exists(file_to_verify))

    @istest
    def verifies_that_a_local_file_doesnt_exist(self):
        file_to_verify = '/some/sneaky.file'
        self.assertFalse(self.role.local_exists(file_to_verify))

    @istest
    def creates_a_local_temp_dir(self):
        self.assertTrue(self.role.local_temp_dir().startswith('/tmp'))

    @istest
    def creates_a_remote_temp_dir(self):
        with self.mock_role_method('execute_python') as execute_python:
            execute_python.return_value = '/some/remote/temp/dir'
            directory = self.role.remote_temp_dir()
            self.assertEqual(directory, '/some/remote/temp/dir')
            execute_python.assert_called_with('from tempfile import gettempdir; print gettempdir()', stdout=False)

    @istest
    def changes_the_owner_of_a_directory(self):
        with self.execute_mock() as execute:
            self.role.change_dir_owner('/some/dir', 'foo')

            execute.assert_called_with('chown -R foo /some/dir', stdout=False, sudo=True)

    @istest
    def changes_the_owner_of_a_file(self):
        with self.execute_mock() as execute:
            self.role.change_file_owner('/some/file.ext', 'foo')

            execute.assert_called_with('chown -R foo /some/file.ext', stdout=False, sudo=True)

    @istest
    def changes_the_owner_of_a_path(self):
        with self.execute_mock() as execute:
            self.role.change_path_owner('/some/path', 'foo')

            execute.assert_called_with('chown -R foo /some/path', stdout=False, sudo=True)

    @istest
    def creates_a_directory_when_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = False

            self.role.ensure_dir('/some/dir')

            execute.assert_called_with('mkdir -p /some/dir', stdout=False, sudo=False)

    @istest
    def creates_a_directory_with_sudo_when_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = False

            self.role.ensure_dir('/some/dir', sudo=True)

            execute.assert_called_with('mkdir -p /some/dir', stdout=False, sudo=True)

    @istest
    def creates_a_directory_with_specific_user_when_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir, self.mock_role_method('change_path_owner') as change_path_owner:
            remote_exists_dir.return_value = False

            self.role.ensure_dir('/some/dir', owner='foo')

            execute.assert_called_with('mkdir -p /some/dir', stdout=False, sudo=True)
            change_path_owner.assert_called_with('/some/dir', 'foo')

    @istest
    def gets_object_mode_from_remote_file(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True
            execute.return_value = '755\n'

            self.assertEqual(self.role.get_object_mode('/some/file.ext'), 755)
            execute.assert_called_with('stat -c %a /some/file.ext', stdout=False, sudo=True)

    @istest
    def cannot_get_mode_if_file_doesnt_exist(self):
        with self.execute_mock(), self.mock_role_method('remote_exists') as remote_exists, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists.return_value = False
            remote_exists_dir.return_value = False

            self.assertRaises(IOError, self.role.get_object_mode, '/some/file.ext')

    @istest
    def changes_the_mode_of_a_path_if_its_different(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 644

            self.role.change_path_mode('/some/path', 755)

            execute.assert_called_with('chmod 755 /some/path', stdout=False, sudo=True)

    @istest
    def recursively_changes_the_mode_of_a_path_if_its_different(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 644

            self.role.change_path_mode('/some/path', 755, recursive=True)

            execute.assert_called_with('chmod -R 755 /some/path', stdout=False, sudo=True)

    @istest
    def doesnt_change_path_mode_if_its_the_same(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 755

            self.role.change_path_mode('/some/path', 755)

            self.assertFalse(execute.called)

    @istest
    def recursively_changes_the_mode_of_a_path_even_if_the_mode_of_the_parent_path_is_the_same(self):
        with self.execute_mock() as execute, self.mock_role_method('get_object_mode') as get_object_mode:
            get_object_mode.return_value = 755

            self.role.change_path_mode('/some/path', 755, recursive=True)

            execute.assert_called_with('chmod -R 755 /some/path', stdout=False, sudo=True)

    @istest
    def changes_the_mode_of_a_directory(self):
        with self.mock_role_method('change_path_mode') as change_path_mode:
            self.role.change_dir_mode('/some/dir', 755, recursive='is it recursive?')

            change_path_mode.assert_called_with('/some/dir', 755, recursive='is it recursive?')

    @istest
    def changes_the_mode_of_a_file(self):
        with self.mock_role_method('change_path_mode') as change_path_mode:
            self.role.change_file_mode('/some/file.ext', 755)

            change_path_mode.assert_called_with('/some/file.ext', 755)

    @istest
    def gets_the_md5_hash_of_a_local_file(self):
        with self.mock_role_method('execute_local') as execute_local, self.mock_role_method('local_exists') as local_exists:
            local_exists.return_value = True
            execute_local.return_value = 'some-hash\n'

            self.assertEqual(self.role.md5_local('/some/path'), 'some-hash')
            execute_local.assert_called_with('md5sum /some/path | cut -d " " -f 1', stdout=False, sudo=True)

    @istest
    def returns_none_if_local_file_doesnt_exist_for_md5_hash(self):
        with self.mock_role_method('execute_local') as execute_local, self.mock_role_method('local_exists') as local_exists:
            local_exists.return_value = False

            self.assertIsNone(self.role.md5_local('/some/path'))
            self.assertFalse(execute_local.called)

    @istest
    def gets_the_md5_hash_of_a_remote_file(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True
            execute.return_value = 'some-hash\n'

            self.assertEqual(self.role.md5_remote('/some/path'), 'some-hash')
            execute.assert_called_with('md5sum /some/path | cut -d " " -f 1', stdout=False, sudo=True)

    @istest
    def returns_none_if_remote_file_doesnt_exist_for_md5_hash(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = False

            self.assertIsNone(self.role.md5_remote('/some/path'))
            self.assertFalse(execute.called)

    @istest
    def removes_a_directory_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = True

            self.assertTrue(self.role.remove_dir('/some/dir'))
            execute.assert_called_with('rmdir /some/dir', stdout=False, sudo=False)

    @istest
    def doesnt_remove_a_directory_if_it_doesnt_exist(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = False

            self.assertFalse(self.role.remove_dir('/some/dir'))
            self.assertFalse(execute.called)

    @istest
    def removes_a_directory_recursively_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = True

            self.assertTrue(self.role.remove_dir('/some/dir', recursive=True))
            execute.assert_called_with('rm -rf /some/dir', stdout=False, sudo=False)

    @istest
    def removes_a_directory_as_sudo_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists_dir') as remote_exists_dir:
            remote_exists_dir.return_value = True

            self.assertTrue(self.role.remove_dir('/some/dir', sudo=True))
            execute.assert_called_with('rmdir /some/dir', stdout=False, sudo=True)

    @istest
    def removes_a_file_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True

            self.assertTrue(self.role.remove_file('/some/file.ext'))
            execute.assert_called_with('rm -f /some/file.ext', stdout=False, sudo=False)

    @istest
    def doesnt_remove_a_file_if_it_doesnt_exist(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = False

            self.assertFalse(self.role.remove_file('/some/dir'))
            self.assertFalse(execute.called)

    @istest
    def removes_a_file_as_sudo_if_it_exists(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            remote_exists.return_value = True

            self.assertTrue(self.role.remove_file('/some/file.ext', sudo=True))
            execute.assert_called_with('rm -f /some/file.ext', stdout=False, sudo=True)

    @istest
    def puts_a_file_in_the_remote_path(self):
        with patch('fabric.api.put') as put:
            self.role.put_file('/from/file', '/to/file')

            put.assert_called_with('/from/file', '/to/file', use_sudo=False)

    @istest
    def puts_a_file_as_sudo_in_the_remote_path(self):
        with patch('fabric.api.put') as put:
            self.role.put_file('/from/file', '/to/file', sudo=True)

            put.assert_called_with('/from/file', '/to/file', use_sudo=True)

    @istest
    def replaces_a_file(self):
        with self.mock_role_method('put_file') as put_file:
            self.role.replace_file('/from/file', '/to/file')

            put_file.assert_called_with('/from/file', '/to/file')

    @istest
    def creates_a_remote_symbolic_link_if_it_doesnt_exist_yet(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            remote_from_exists = True
            remote_to_exists = False
            sudo = 'is it sudo?'
            remote_exists.side_effect = (remote_from_exists, remote_to_exists)

            self.role.remote_symlink(from_file, to_file, sudo=sudo)

            self.assertEqual(remote_exists.mock_calls, [
                call(from_file),
                call(to_file),
            ])
            execute.assert_called_with('ln -sf %s %s' % (from_file, to_file), sudo=sudo, stdout=False)

    @istest
    def creates_a_remote_symbolic_link_if_it_exists_but_with_different_path(self):
        with self.execute_mock() as execute, self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            another_from_file = '/another/from/file'
            remote_from_exists = True
            remote_to_exists = True
            sudo = 'is it sudo?'
            remote_exists.side_effect = (remote_from_exists, remote_to_exists)
            execute.side_effect = ('-rw-rw-r-- 1 foo foo 4490 Dez 11 22:24 %s -> %s' % (to_file, another_from_file), None)

            self.role.remote_symlink(from_file, to_file, sudo=sudo)

            self.assertEqual(remote_exists.mock_calls, [
                call(from_file),
                call(to_file),
            ])
            self.assertEqual(execute.mock_calls, [
                call('ls -la %s' % to_file, stdout=False, sudo=sudo),
                call('ln -sf %s %s' % (from_file, to_file), sudo=sudo, stdout=False),
            ])

    @istest
    def raises_exception_if_remote_file_doesnt_exist(self):
        with self.mock_role_method('remote_exists') as remote_exists:
            from_file = '/from/file'
            to_file = '/to/file'
            remote_from_exists = False
            remote_exists.side_effect = (remote_from_exists, )

            self.assertRaises(RuntimeError, self.role.remote_symlink, from_file, to_file)

    @istest
    def renders_a_template_based_on_absolute_path(self):
        template_file = os.path.join(PROJECT_ROOT, 'tests', 'unit', 'fixtures', 'some_template.txt')
        options = {'foo': 'FOO!'}

        content = self.role.render(template_file, options)

        self.assertIn('foo=FOO!', content)

    @istest
    def renders_a_template_based_on_filename(self):
        template_dir = os.path.join(PROJECT_ROOT, 'tests', 'unit', 'fixtures')
        self.role.context['loader'] = FileSystemLoader(template_dir)
        template_file = 'some_template.txt'
        options = {'foo': 'FOO!'}

        content = self.role.render(template_file, options)

        self.assertIn('foo=FOO!', content)

    @istest
    def writes_content_to_a_temp_file(self):
        content = 'some content'

        temp_file = self.role.write_to_temp_file(content)

        self.assertRegexpMatches(temp_file, r'%s/.+' % tempfile.gettempdir())

        with open(temp_file) as f:
            saved_content = f.read().strip()
            self.assertEqual(saved_content, content)

    @istest
    def creates_a_new_file_when_remote_doesnt_exist_during_update(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_update_data(), self.mock_role_method('_force_update_file'), self.mock_role_method('remote_exists'):
            self.role.remote_exists.return_value = False

            self.assertTrue(self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner))
            self.role._force_update_file.assert_called_with(to_file, sudo, self.update_data.local_temp_path, owner)

    @istest
    def updates_file_when_remote_exists_but_is_different(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_update_data(), self.mock_role_method('_force_update_file'), self.mock_role_method('remote_exists'):
            self.role.remote_exists.return_value = True
            self.update_data.from_md5 = 'some local md5'
            self.update_data.to_md5 = 'some remote md5'

            self.assertTrue(self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner))
            self.role._force_update_file.assert_called_with(to_file, sudo, self.update_data.local_temp_path, owner)

    @istest
    def cleans_temp_file_after_updating(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with open(self.update_data.local_temp_path, 'w') as f:
            f.write('foo')

        with self.mock_update_data(), self.mock_role_method('_force_update_file'), self.mock_role_method('remote_exists'):
            self.role.remote_exists.return_value = True

            self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner)

            self.assertFalse(os.path.exists(self.update_data.local_temp_path))

    @istest
    def doesnt_update_file_when_content_is_the_same(self):
        to_file = '/etc/foo.conf'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_update_data(), self.mock_role_method('_force_update_file'), self.mock_role_method('remote_exists'):
            self.role.remote_exists.return_value = True
            self.update_data.from_md5 = 'same md5'
            self.update_data.to_md5 = 'same md5'

            self.assertFalse(self.role.update_file('some template', to_file, options='some options', sudo=sudo, owner=owner))
            self.assertFalse(self.role._force_update_file.called)

    @istest
    def builds_update_data(self):
        from_file = os.path.join(PROJECT_ROOT, 'tests', 'unit', 'fixtures', 'some_template.txt')
        to_file = '/etc/foo.conf'
        options = {'foo': 'FOO!'}
        local_temp_path = '/tmp/template-to-update'
        md5_local = 'some local md5'
        md5_remote = 'some remote md5'

        with self.mock_role_method('write_to_temp_file'), self.mock_role_method('md5_local'), self.mock_role_method('md5_remote'):
            self.role.write_to_temp_file.return_value = local_temp_path
            self.role.md5_local.return_value = md5_local
            self.role.md5_remote.return_value = md5_remote

            update_data = self.role._build_update_data(from_file, options, to_file)

            self.assertEqual(update_data.local_temp_path, local_temp_path)
            self.assertEqual(update_data.from_md5, md5_local)
            self.assertEqual(update_data.to_md5, md5_remote)

    @istest
    def really_updates_file_without_owner(self):
        to_file = '/etc/foo.conf'
        local_temp_path = '/tmp/template-to-update'
        sudo = 'is it sudo?'
        owner = None

        with self.mock_role_method('put_file'):
            self.role._force_update_file(to_file, sudo, local_temp_path, owner)

            self.role.put_file.assert_called_with(local_temp_path, to_file, sudo)

    @istest
    def really_updates_file_with_owner(self):
        to_file = '/etc/foo.conf'
        local_temp_path = '/tmp/template-to-update'
        sudo = 'is it sudo?'
        owner = 'foo'

        with self.mock_role_method('put_file'), self.mock_role_method('change_file_owner'):
            self.role._force_update_file(to_file, sudo, local_temp_path, owner)

            self.role.put_file.assert_called_with(local_temp_path, to_file, sudo)
            self.role.change_file_owner.assert_called_with(to_file, owner)

    @istest
    def checks_that_content_differs_when_md5_is_different(self):
        self.assertTrue(self.role._contents_differ('some local md5', 'some remote md5'))

    @istest
    def checks_that_content_doesnt_differ_when_md5_is_the_same(self):
        self.assertFalse(self.role._contents_differ('same md5', 'same md5'))

    @istest
    def checks_that_content_doesnt_differ_when_md5_is_the_same_even_with_spaces(self):
        self.assertFalse(self.role._contents_differ('same md5      ', '  same md5'))

    @istest
    def checks_that_content_differs_when_a_md5_is_none(self):
        self.assertTrue(self.role._contents_differ(None, 'some md5'))
        self.assertTrue(self.role._contents_differ('some md5', None))
        self.assertFalse(self.role._contents_differ(None, None))

    @istest
    def reads_a_remote_file(self):
        path = '/some/path'
        sudo = 'is it sudo?'
        content = 'some content'
        with self.mock_role_method('execute_python') as execute_python:
            execute_python.return_value = content

            self.assertEqual(self.role.read_remote_file(path, sudo), content)

            execute_python.assert_called_with("import codecs; print codecs.open('%s', 'r', 'utf-8').read()" % path, stdout=False, sudo=sudo)

    @istest
    def checks_that_a_process_is_running(self):
        process = 'nginx'
        sudo = 'is it sudo?'

        with self.execute_mock() as execute:
            execute.return_value = '0'

            self.assertTrue(self.role.is_process_running(process, sudo=sudo))
            execute.assert_called_with('ps aux | egrep %s | egrep -v egrep > /dev/null;echo $?' % process, stdout=False, sudo=sudo)

    @istest
    def checks_that_a_process_is_not_running(self):
        process = 'nginx'
        sudo = 'is it sudo?'

        with self.execute_mock() as execute:
            execute.return_value = '1'

            self.assertFalse(self.role.is_process_running(process, sudo=sudo))
            execute.assert_called_with('ps aux | egrep %s | egrep -v egrep > /dev/null;echo $?' % process, stdout=False, sudo=sudo)

    @istest
    def checks_that_a_file_has_a_certain_line(self):
        content = """
        some content
        127.0.0.1    localhost
        some other content
        """
        file_path = '/some/path'
        line = '127.0.0.1 localhost'

        with self.mock_role_method('remote_exists'), self.mock_role_method('read_remote_file'):
            self.role.remote_exists.return_value = True
            self.role.read_remote_file.return_value = content

            self.assertTrue(self.role.has_line(line, file_path))

            self.role.remote_exists.assert_called_with(file_path)
            self.role.read_remote_file.assert_called_with(file_path)

    @istest
    def checks_that_a_file_doesnt_have_a_certain_line(self):
        content = """
        some content
        127.0.0.1    localhost
        some other content
        """
        file_path = '/some/path'
        line = '192.168.0.1 my-gateway'

        with self.mock_role_method('remote_exists'), self.mock_role_method('read_remote_file'):
            self.role.remote_exists.return_value = True
            self.role.read_remote_file.return_value = content

            self.assertFalse(self.role.has_line(line, file_path))

            self.role.remote_exists.assert_called_with(file_path)
            self.role.read_remote_file.assert_called_with(file_path)

    @istest
    def checks_that_a_file_doesnt_have_a_certain_line_when_file_doesnt_exist(self):
        file_path = '/some/path'
        line = '192.168.0.1 my-gateway'

        with self.mock_role_method('remote_exists'), self.mock_role_method('read_remote_file'):
            self.role.remote_exists.return_value = False

            self.assertFalse(self.role.has_line(line, file_path))

            self.role.remote_exists.assert_called_with(file_path)
            self.assertFalse(self.role.read_remote_file.called)

    @istest
    def uses_role_context_manager(self):
        manager = self.role.using('some role')
        self.assertEqual(manager.role, 'some role')
        self.assertEqual(manager.context, self.role.context)
        self.assertEqual(manager.prov, self.role.prov)