# -*- coding: utf-8 -*-
'''test for pillar git_pillar.py


  :codeauthor: :email:`Georges Racinet (gracinet@anybox.fr)`

Based on joint work with Paul Tonelli about hg_pillar integration.

'''

# Import python libs
from __future__ import absolute_import

import os
import tempfile
import shutil
import subprocess
import yaml
import stat

# Import Salt Testing libs
from tests.integration import AdaptedConfigurationTestCaseMixin
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.paths import TMP
from tests.support.unit import TestCase, skipIf
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch

COMMIT_USER_NAME = 'test_user'
COMMIT_USER_EMAIL = 'someone@git.test'
# file contents
PILLAR_CONTENT = {'gna': 'hello'}
FILE_DATA = {
             'top.sls': {'base': {'*': ['user']}},
             'user.sls': PILLAR_CONTENT
             }

# Import Salt Libs
import salt.utils
from salt.pillar import Pillar
import salt.pillar.git_pillar as git_pillar


@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(not git_pillar.HAS_GITPYTHON, 'no GitPython')
class GitPillarTestCase(TestCase, AdaptedConfigurationTestCaseMixin, LoaderModuleMockMixin):
    'test git_pillar pillar'
    maxDiff = None

    def setup_loader_modules(self):
        self.tmpdir = tempfile.mkdtemp(dir=TMP)
        cachedir = os.path.join(self.tmpdir, 'cachedir')
        os.makedirs(os.path.join(cachedir, 'pillar_gitfs'))
        self.repo_path = self._create_repo()
        return {
            git_pillar: {
                '__opts__': {
                    'cachedir': cachedir,
                    'pillar_roots': {},
                    'hash_type': 'sha256',
                    'file_roots': {},
                    'state_top': 'top.sls',
                    'extension_modules': '',
                    'renderer': 'yaml_jinja',
                    'renderer_blacklist': [],
                    'renderer_whitelist': [],
                    'pillar_opts': False
                }
            }
        }

    def setUp(self):
        super(GitPillarTestCase, self).setUp()
        git_pillar._update('master', 'file://{0}'.format(self.repo_path))

    def tearDown(self):
        shutil.rmtree(self.tmpdir, onerror=self._rmtree_error)
        super(GitPillarTestCase, self).tearDown()

    def _rmtree_error(self, func, path, excinfo):
        os.chmod(path, stat.S_IWRITE)
        func(path)

    def _create_repo(self):
        'create source Git repo in temp directory'
        repo = os.path.join(self.tmpdir, 'repo_pillar')
        os.makedirs(repo)
        subprocess.check_call(['git', 'init', repo])
        for filename in FILE_DATA:
            with salt.utils.fopen(os.path.join(repo, filename), 'w') as data_file:
                yaml.dump(FILE_DATA[filename], data_file)

        subprocess.check_call(['git', 'add', '.'], cwd=repo)
        subprocess.call(['git', 'config', 'user.email', COMMIT_USER_EMAIL],
                        cwd=repo)
        subprocess.call(['git', 'config', 'user.name', COMMIT_USER_NAME],
                        cwd=repo)
        subprocess.check_call(['git', 'commit', '-m', 'first commit'],
                              cwd=repo)
        return repo

    @property
    def conf_line(self):
        return 'master file://{0}'.format(self.repo_path)

    def test_base(self):
        'check direct call ``ext_pillar()`` interface'
        with patch.dict(git_pillar.__opts__, {'environment': None}):
            mypillar = git_pillar.ext_pillar('myminion',
                                             self.conf_line,
                                             {})
            self.assertEqual(PILLAR_CONTENT, mypillar)

    def test_from_upper(self):
        '''Check whole calling stack from parent Pillar instance

        This test is closer to what happens in real life, and demonstrates
        how ``compile_pillar()`` is called twice.

        This kind of test should/would become non-necessary, once git_pillar,
        all these pillar are called exactly in the same way (git is an
        exception for now), and don't recurse.
        '''
        with patch.dict(git_pillar.__opts__, {'ext_pillar': [dict(git=self.conf_line)]}):
            pil = Pillar(git_pillar.__opts__,
                         git_pillar.__grains__,
                         'myminion', None)
            self.assertEqual(PILLAR_CONTENT, pil.compile_pillar(pillar_dirs={}))

    def test_no_loop(self):
        '''Check that the reinstantiation of a pillar object does recurse.

        This test goes in great details of patching that the dedicated
        utilities might do in a simpler way.
        Namely, we replace the main ``ext_pillar`` entry function by one
        that keeps count of its calls.

        Otherwise, the fact that the :class:`MaximumRecursion` error is caught
        can go in the way on the testing.

        On the current code base, this test fails if the two first lines of
        :func:``git_pillar.ext_pillar`::

            if pillar_dirs is None:
                return

        are replaced by::

            if pillar_dirs is None:
                pillar_dirs = {}

        .. note:: the explicit anti-recursion protection does not prevent
                  looping between two different Git pillars.

        This test will help subsequent refactors, and also as a base for other
        external pillars of the same kind.
        '''
        repo2 = os.path.join(self.tmpdir, 'repo_pillar2')
        conf_line2 = 'master file://{0}'.format(repo2)
        subprocess.check_call(['git', 'clone', self.repo_path, repo2])
        with patch.dict(git_pillar.__opts__, {'ext_pillar': [dict(git=self.conf_line),
                                                             dict(git=conf_line2)]}):
            git_pillar._update(*conf_line2.split(None, 1))

            pil = Pillar(git_pillar.__opts__,
                         git_pillar.__grains__,
                         'myminion', 'base')

            orig_ext_pillar = pil.ext_pillars['git']
            orig_ext_pillar.count = 0

            def ext_pillar_count_calls(minion_id, repo_string, pillar_dirs):
                orig_ext_pillar.count += 1
                if orig_ext_pillar.count > 6:
                    # going all the way to an infinite loop is harsh on the
                    # test machine
                    raise RuntimeError('Infinite loop detected')
                return orig_ext_pillar(minion_id, repo_string, pillar_dirs)

            from salt.loader import LazyLoader
            orig_getitem = LazyLoader.__getitem__

            def __getitem__(self, key):
                if key == 'git.ext_pillar':
                    return ext_pillar_count_calls
                return orig_getitem(self, key)

            with patch.object(LazyLoader, '__getitem__', __getitem__):
                self.assertEqual(PILLAR_CONTENT, pil.compile_pillar(pillar_dirs={}))
                self.assertTrue(orig_ext_pillar.count < 7)
