import os
import tempfile
import unittest
from mock import patch
from ddt import ddt, file_data

import pavelib.quality
import paver.easy
from paver.easy import BuildFailure


@ddt
class TestPaverQualityViolations(unittest.TestCase):

    def setUp(self):
        self.f = tempfile.NamedTemporaryFile(delete=False)
        self.f.close()

    def test_pylint_parser_other_string(self):
        with open(self.f.name, 'w') as f:
            f.write("hello")
        num = pavelib.quality._count_pylint_violations(f.name)
        self.assertEqual(num, 0)

    def test_pylint_parser_pep8(self):
        # Pep8 violations should be ignored.
        with open(self.f.name, 'w') as f:
            f.write("foo/hello/test.py:304:15: E203 whitespace before ':'")
        num = pavelib.quality._count_pylint_violations(f.name)
        self.assertEqual(num, 0)

    @file_data('pylint_test_list.json')
    def test_pylint_parser_count_violations(self, value):
    # Tests:
    #     * Different types of violations
    #     * One violation covering multiple lines
        with open(self.f.name, 'w') as f:
            f.write(value)
        num = pavelib.quality._count_pylint_violations(f.name)
        self.assertEqual(num, 1)

    def test_pep8_parser(self):
        with open(self.f.name, 'w') as f:
            f.write("hello\nhithere")
        num = pavelib.quality._count_pep8_violations(f.name)
        self.assertEqual(num, 2)

    def tearDown(self):
        os.remove(self.f.name)


class TestPaverRunQuality(unittest.TestCase):
    """
    For testing the paver run_quality task
    """

    def setUp(self):

        # mock the @needs decorator to skip it
        self._mock_paver_needs = patch.object(pavelib.quality.run_quality, 'needs').start()
        self._mock_paver_needs.return_value = 0
        self._mock_paver_sh = patch('pavelib.quality.sh').start()
        self.addCleanup(self._mock_paver_sh.stop())
        self.addCleanup(self._mock_paver_needs.stop())

    def test_failure_on_diffquality_pep8(self):
        """
       If pep8 diff-quality fails due to the percentage threshold, pylint
       should still be run
        """

        # Underlying sh call must fail when it is running the pep8 diff-quality task
        self._mock_paver_sh.side_effect = CustomShMock().fail_on_pep8
        with self.assertRaises(SystemExit):
            pavelib.quality.run_quality("")
            self.assertRaises(BuildFailure)
        # Test that both pep8 and pylint were called by counting the calls
        self.assertEqual(self._mock_paver_sh.call_count, 2)

    def test_failure_on_diffquality_pylint(self):
        """
        If diff-quality fails on pylint, the paver task should also fail
        """

        # Underlying sh call must fail when it is running the pylint diff-quality task
        self._mock_paver_sh.side_effect = CustomShMock().fail_on_pylint
        with self.assertRaises(SystemExit):
            pavelib.quality.run_quality("")
            self.assertRaises(BuildFailure)
        # Test that both pep8 and pylint were called by counting the calls
        self.assertEqual(self._mock_paver_sh.call_count, 2)

    def test_other_exception(self):
        """
        If diff-quality fails for an unknown reason on the first run (pep8), then
        pylint should not be run
        """
        self._mock_paver_sh.side_effect = [Exception('unrecognized failure!'), 0]
        with self.assertRaises(Exception):
            pavelib.quality.run_quality("")
        # Test that pylint is NOT called by counting calls
        self.assertEqual(self._mock_paver_sh.call_count, 1)

    def test_no_diff_quality_failures(self):
        # Assert nothing is raised
        pavelib.quality.run_quality("")
        self.assertEqual(self._mock_paver_sh.call_count, 2)


class CustomShMock(object):
    """
    Diff-quality makes a number of sh calls. None of those calls should be made during tests; however, some
    of them need to have certain responses.
    """

    def fail_on_pep8(self, arg):
        """
        For our tests, we need the call for diff-quality running pep8 reports to fail, since that is what
        is going to fail when we pass in a percentage ("p") requirement.
        """
        if "pep8" in arg:
            # Essentially mock diff-quality exiting with 1
            paver.easy.sh("exit 1")
        else:
            return

    def fail_on_pylint(self, arg):
        """
        For our tests, we need the call for diff-quality running pep8 reports to fail, since that is what
        is going to fail when we pass in a percentage ("p") requirement.
        """
        if "pylint" in arg:
            # Essentially mock diff-quality exiting with 1
            paver.easy.sh("exit 1")
        else:
            return