"""Test the test support."""
from __future__ import absolute_import

import filecmp
import re
import sys
from contextlib import contextmanager
from os.path import isdir, join

import pytest

from tests.lib import SRC_DIR


@contextmanager
def assert_error_startswith(exc_type, expected_start):
    """
    Assert that an exception is raised starting with a certain message.
    """
    with pytest.raises(exc_type) as err:
        yield

    assert str(err.value).startswith(expected_start), (
        'full message: {}'.format(err.value)
    )


def test_tmp_dir_exists_in_env(script):
    """
    Test that $TMPDIR == env.temp_path and path exists and env.assert_no_temp()
    passes (in fast env)
    """
    # need these tests to ensure the assert_no_temp feature of scripttest is
    # working
    script.assert_no_temp()  # this fails if env.tmp_path doesn't exist
    assert script.environ['TMPDIR'] == script.temp_path
    assert isdir(script.temp_path)


def test_correct_pip_version(script):
    """
    Check we are running proper version of pip in run_pip.
    """
    # output is like:
    # pip PIPVERSION from PIPDIRECTORY (python PYVERSION)
    result = script.pip('--version')

    # compare the directory tree of the invoked pip with that of this source
    # distribution
    pip_folder_outputed = re.match(
        r'pip \d+(\.[\d]+)+(\.?(b|rc|dev|pre|post)\d+)? from (.*) '
        r'\(python \d(.[\d])+\)$',
        result.stdout
    ).group(4)
    pip_folder = join(SRC_DIR, 'src', 'pip')

    diffs = filecmp.dircmp(pip_folder, pip_folder_outputed)

    # If any non-matching .py files exist, we have a problem: run_pip
    # is picking up some other version!  N.B. if this project acquires
    # primary resources other than .py files, this code will need
    # maintenance
    mismatch_py = [
        x for x in diffs.left_only + diffs.right_only + diffs.diff_files
        if x.endswith('.py')
    ]
    assert not mismatch_py, (
        'mismatched source files in {pip_folder!r} '
        'and {pip_folder_outputed!r}: {mismatch_py!r}'.format(**locals())
    )


def test_as_import(script):
    """ test that pip.__init__.py does not shadow
    the command submodule with a dictionary
    """
    import pip._internal.commands.install as inst
    assert inst is not None


class TestPipTestEnvironment:

    def run_stderr_with_prefix(self, script, prefix, **kwargs):
        """
        Call run() that prints stderr with the given prefix.
        """
        text = '{}: hello, world\\n'.format(prefix)
        command = 'import sys; sys.stderr.write("{}")'.format(text)
        args = [sys.executable, '-c', command]
        script.run(*args, **kwargs)

    def run_with_log_command(self, script, sub_string, **kwargs):
        """
        Call run() on a command that logs a "%"-style format string using
        the given substring as the string's replacement field.
        """
        command = (
            "import logging; logging.basicConfig(level='INFO'); "
            "logging.getLogger().info('sub: {}', 'foo')"
        ).format(sub_string)
        args = [sys.executable, '-c', command]
        script.run(*args, **kwargs)

    @pytest.mark.parametrize('prefix', (
        'DEBUG',
        'INFO',
        'FOO',
    ))
    def test_run__allowed_stderr(self, script, prefix):
        """
        Test calling run() with allowed stderr.
        """
        # Check that no error happens.
        self.run_stderr_with_prefix(script, prefix)

    def test_run__allow_stderr_warning(self, script):
        """
        Test passing allow_stderr_warning=True.
        """
        # Check that no error happens.
        self.run_stderr_with_prefix(
            script, 'WARNING', allow_stderr_warning=True,
        )

        # Check that an error still happens with ERROR.
        expected_start = 'stderr has an unexpected error'
        with assert_error_startswith(RuntimeError, expected_start):
            self.run_stderr_with_prefix(
                script, 'ERROR', allow_stderr_warning=True,
            )

    @pytest.mark.parametrize('prefix', (
        'DEPRECATION',
        'WARNING',
        'ERROR',
    ))
    def test_run__allow_stderr_error(self, script, prefix):
        """
        Test passing allow_stderr_error=True.
        """
        # Check that no error happens.
        self.run_stderr_with_prefix(script, prefix, allow_stderr_error=True)

    @pytest.mark.parametrize('prefix, expected_start', (
        ('DEPRECATION', 'stderr has an unexpected warning'),
        ('WARNING', 'stderr has an unexpected warning'),
        ('ERROR', 'stderr has an unexpected error'),
    ))
    def test_run__unexpected_stderr(self, script, prefix, expected_start):
        """
        Test calling run() with unexpected stderr output.
        """
        with assert_error_startswith(RuntimeError, expected_start):
            self.run_stderr_with_prefix(script, prefix)

    def test_run__logging_error(self, script):
        """
        Test calling run() with an unexpected logging error.
        """
        # Pass a good substitution string.
        self.run_with_log_command(script, sub_string='%r')

        expected_start = 'stderr has a logging error, which is never allowed'
        with assert_error_startswith(RuntimeError, expected_start):
            # Pass a bad substitution string.  Also, pass
            # allow_stderr_error=True to check that the RuntimeError occurs
            # even under the stricter test condition of when we are allowing
            # other types of errors.
            self.run_with_log_command(
                script, sub_string='{!r}', allow_stderr_error=True,
            )

    def test_run__allow_stderr_error_false_error_with_expect_error(
        self, script,
    ):
        """
        Test passing allow_stderr_error=False with expect_error=True.
        """
        expected_start = (
            'cannot pass allow_stderr_error=False with expect_error=True'
        )
        with assert_error_startswith(RuntimeError, expected_start):
            script.run('python', allow_stderr_error=False, expect_error=True)

    def test_run__allow_stderr_warning_false_error_with_expect_stderr(
        self, script,
    ):
        """
        Test passing allow_stderr_warning=False with expect_stderr=True.
        """
        expected_start = (
            'cannot pass allow_stderr_warning=False with expect_stderr=True'
        )
        with assert_error_startswith(RuntimeError, expected_start):
            script.run(
                'python', allow_stderr_warning=False, expect_stderr=True,
            )

    @pytest.mark.parametrize('arg_name', (
        'expect_error',
        'allow_stderr_error',
    ))
    def test_run__allow_stderr_warning_false_error(self, script, arg_name):
        """
        Test passing allow_stderr_warning=False when it is not allowed.
        """
        kwargs = {'allow_stderr_warning': False, arg_name: True}
        expected_start = (
            'cannot pass allow_stderr_warning=False with '
            'allow_stderr_error=True'
        )
        with assert_error_startswith(RuntimeError, expected_start):
            script.run('python', **kwargs)

    def test_run__expect_error_fails_when_zero_returncode(self, script):
        expected_start = 'Script passed unexpectedly'
        with assert_error_startswith(AssertionError, expected_start):
            script.run(
                'python', expect_error=True
            )

    def test_run__no_expect_error_fails_when_nonzero_returncode(self, script):
        expected_start = 'Script returned code: 1'
        with assert_error_startswith(AssertionError, expected_start):
            script.run(
                'python', '-c', 'import sys; sys.exit(1)'
            )
