"""Tests for scripts in the Tools directory.

This file contains regression tests for some of the scripts found in the
Tools directory of a Python checkout or tarball, such as reindent.py.
"""

import os
import runpy
import sys
import unittest
import shutil
from cStringIO import StringIO
import subprocess
import sysconfig
import tempfile
import textwrap
from test import test_support
from test.script_helper import assert_python_ok, temp_dir

if not sysconfig.is_python_build():
    # XXX some installers do contain the tools, should we detect that
    # and run the tests in that case too?
    raise unittest.SkipTest('test irrelevant for an installed Python')

basepath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
                        'Tools')
scriptsdir = os.path.join(basepath, 'scripts')


class ReindentTests(unittest.TestCase):
    script = os.path.join(scriptsdir, 'reindent.py')

    def test_noargs(self):
        assert_python_ok(self.script)

    def test_help(self):
        rc, out, err = assert_python_ok(self.script, '-h')
        self.assertEqual(out, b'')
        self.assertGreater(err, b'')


class PindentTests(unittest.TestCase):
    script = os.path.join(scriptsdir, 'pindent.py')

    def assertFileEqual(self, fn1, fn2):
        with open(fn1) as f1, open(fn2) as f2:
            self.assertEqual(f1.readlines(), f2.readlines())

    def pindent(self, source, *args):
        proc = subprocess.Popen(
                (sys.executable, self.script) + args,
                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                universal_newlines=True)
        out, err = proc.communicate(source)
        self.assertIsNone(err)
        return out

    def lstriplines(self, data):
        return '\n'.join(line.lstrip() for line in data.splitlines()) + '\n'

    def test_selftest(self):
        self.maxDiff = None
        with temp_dir() as directory:
            data_path = os.path.join(directory, '_test.py')
            with open(self.script) as f:
                closed = f.read()
            with open(data_path, 'w') as f:
                f.write(closed)

            rc, out, err = assert_python_ok(self.script, '-d', data_path)
            self.assertEqual(out, b'')
            self.assertEqual(err, b'')
            backup = data_path + '~'
            self.assertTrue(os.path.exists(backup))
            with open(backup) as f:
                self.assertEqual(f.read(), closed)
            with open(data_path) as f:
                clean = f.read()
            compile(clean, '_test.py', 'exec')
            self.assertEqual(self.pindent(clean, '-c'), closed)
            self.assertEqual(self.pindent(closed, '-d'), clean)

            rc, out, err = assert_python_ok(self.script, '-c', data_path)
            self.assertEqual(out, b'')
            self.assertEqual(err, b'')
            with open(backup) as f:
                self.assertEqual(f.read(), clean)
            with open(data_path) as f:
                self.assertEqual(f.read(), closed)

            broken = self.lstriplines(closed)
            with open(data_path, 'w') as f:
                f.write(broken)
            rc, out, err = assert_python_ok(self.script, '-r', data_path)
            self.assertEqual(out, b'')
            self.assertEqual(err, b'')
            with open(backup) as f:
                self.assertEqual(f.read(), broken)
            with open(data_path) as f:
                indented = f.read()
            compile(indented, '_test.py', 'exec')
            self.assertEqual(self.pindent(broken, '-r'), indented)

    def pindent_test(self, clean, closed):
        self.assertEqual(self.pindent(clean, '-c'), closed)
        self.assertEqual(self.pindent(closed, '-d'), clean)
        broken = self.lstriplines(closed)
        self.assertEqual(self.pindent(broken, '-r', '-e', '-s', '4'), closed)

    def test_statements(self):
        clean = textwrap.dedent("""\
            if a:
                pass

            if a:
                pass
            else:
                pass

            if a:
                pass
            elif:
                pass
            else:
                pass

            while a:
                break

            while a:
                break
            else:
                pass

            for i in a:
                break

            for i in a:
                break
            else:
                pass

            try:
                pass
            finally:
                pass

            try:
                pass
            except TypeError:
                pass
            except ValueError:
                pass
            else:
                pass

            try:
                pass
            except TypeError:
                pass
            except ValueError:
                pass
            finally:
                pass

            with a:
                pass

            class A:
                pass

            def f():
                pass
            """)

        closed = textwrap.dedent("""\
            if a:
                pass
            # end if

            if a:
                pass
            else:
                pass
            # end if

            if a:
                pass
            elif:
                pass
            else:
                pass
            # end if

            while a:
                break
            # end while

            while a:
                break
            else:
                pass
            # end while

            for i in a:
                break
            # end for

            for i in a:
                break
            else:
                pass
            # end for

            try:
                pass
            finally:
                pass
            # end try

            try:
                pass
            except TypeError:
                pass
            except ValueError:
                pass
            else:
                pass
            # end try

            try:
                pass
            except TypeError:
                pass
            except ValueError:
                pass
            finally:
                pass
            # end try

            with a:
                pass
            # end with

            class A:
                pass
            # end class A

            def f():
                pass
            # end def f
            """)
        self.pindent_test(clean, closed)

    def test_multilevel(self):
        clean = textwrap.dedent("""\
            def foobar(a, b):
                if a == b:
                    a = a+1
                elif a < b:
                    b = b-1
                    if b > a: a = a-1
                else:
                    print 'oops!'
            """)
        closed = textwrap.dedent("""\
            def foobar(a, b):
                if a == b:
                    a = a+1
                elif a < b:
                    b = b-1
                    if b > a: a = a-1
                    # end if
                else:
                    print 'oops!'
                # end if
            # end def foobar
            """)
        self.pindent_test(clean, closed)

    def test_preserve_indents(self):
        clean = textwrap.dedent("""\
            if a:
                     if b:
                              pass
            """)
        closed = textwrap.dedent("""\
            if a:
                     if b:
                              pass
                     # end if
            # end if
            """)
        self.assertEqual(self.pindent(clean, '-c'), closed)
        self.assertEqual(self.pindent(closed, '-d'), clean)
        broken = self.lstriplines(closed)
        self.assertEqual(self.pindent(broken, '-r', '-e', '-s', '9'), closed)
        clean = textwrap.dedent("""\
            if a:
            \tif b:
            \t\tpass
            """)
        closed = textwrap.dedent("""\
            if a:
            \tif b:
            \t\tpass
            \t# end if
            # end if
            """)
        self.assertEqual(self.pindent(clean, '-c'), closed)
        self.assertEqual(self.pindent(closed, '-d'), clean)
        broken = self.lstriplines(closed)
        self.assertEqual(self.pindent(broken, '-r'), closed)

    def test_escaped_newline(self):
        clean = textwrap.dedent("""\
            class\\
            \\
             A:
               def\
            \\
            f:
                  pass
            """)
        closed = textwrap.dedent("""\
            class\\
            \\
             A:
               def\
            \\
            f:
                  pass
               # end def f
            # end class A
            """)
        self.assertEqual(self.pindent(clean, '-c'), closed)
        self.assertEqual(self.pindent(closed, '-d'), clean)

    def test_empty_line(self):
        clean = textwrap.dedent("""\
            if a:

                pass
            """)
        closed = textwrap.dedent("""\
            if a:

                pass
            # end if
            """)
        self.pindent_test(clean, closed)

    def test_oneline(self):
        clean = textwrap.dedent("""\
            if a: pass
            """)
        closed = textwrap.dedent("""\
            if a: pass
            # end if
            """)
        self.pindent_test(clean, closed)


class FixcidTests(unittest.TestCase):
    def test_parse_strings(self):
        old1 = 'int xx = "xx\\"xx"[xx];\n'
        old2 = "int xx = 'x\\'xx' + xx;\n"
        output = self.run_script(old1 + old2)
        new1 = 'int yy = "xx\\"xx"[yy];\n'
        new2 = "int yy = 'x\\'xx' + yy;\n"
        self.assertMultiLineEqual(output,
            "1\n"
            "< {old1}"
            "> {new1}"
            "{new1}"
            "2\n"
            "< {old2}"
            "> {new2}"
            "{new2}".format(old1=old1, old2=old2, new1=new1, new2=new2)
        )

    def test_alter_comments(self):
        output = self.run_script(
            substfile=
                "xx yy\n"
                "*aa bb\n",
            args=("-c", "-",),
            input=
                "/* xx altered */\n"
                "int xx;\n"
                "/* aa unaltered */\n"
                "int aa;\n",
        )
        self.assertMultiLineEqual(output,
            "1\n"
            "< /* xx altered */\n"
            "> /* yy altered */\n"
            "/* yy altered */\n"
            "2\n"
            "< int xx;\n"
            "> int yy;\n"
            "int yy;\n"
            "/* aa unaltered */\n"
            "4\n"
            "< int aa;\n"
            "> int bb;\n"
            "int bb;\n"
        )

    def test_directory(self):
        os.mkdir(test_support.TESTFN)
        self.addCleanup(test_support.rmtree, test_support.TESTFN)
        c_filename = os.path.join(test_support.TESTFN, "file.c")
        with open(c_filename, "w") as file:
            file.write("int xx;\n")
        with open(os.path.join(test_support.TESTFN, "file.py"), "w") as file:
            file.write("xx = 'unaltered'\n")
        script = os.path.join(scriptsdir, "fixcid.py")
        # ignore dbg() messages
        with test_support.captured_stderr() as stderr:
            output = self.run_script(args=(test_support.TESTFN,))
        self.assertMultiLineEqual(output,
            "{}:\n"
            "1\n"
            '< int xx;\n'
            '> int yy;\n'.format(c_filename),
            "stderr: %s" % stderr.getvalue()
        )

    def run_script(self, input="", args=("-",), substfile="xx yy\n"):
        substfilename = test_support.TESTFN + ".subst"
        with open(substfilename, "w") as file:
            file.write(substfile)
        self.addCleanup(test_support.unlink, substfilename)

        argv = ["fixcid.py", "-s", substfilename] + list(args)
        script = os.path.join(scriptsdir, "fixcid.py")
        with test_support.swap_attr(sys, "argv", argv), \
                test_support.swap_attr(sys, "stdin", StringIO(input)), \
                test_support.captured_stdout() as output:
            try:
                runpy.run_path(script, run_name="__main__")
            except SystemExit as exit:
                self.assertEqual(exit.code, 0)
        return output.getvalue()


class LllTests(unittest.TestCase):

    script = os.path.join(scriptsdir, 'lll.py')

    @unittest.skipUnless(hasattr(os, 'symlink'), 'Requires symlink support')
    def test_lll_multiple_dirs(self):
        dir1 = tempfile.mkdtemp()
        dir2 = tempfile.mkdtemp()
        self.addCleanup(test_support.rmtree, dir1)
        self.addCleanup(test_support.rmtree, dir2)
        fn1 = os.path.join(dir1, 'foo1')
        fn2 = os.path.join(dir2, 'foo2')
        for fn, dir in (fn1, dir1), (fn2, dir2):
            open(fn, 'w').close()
            os.symlink(fn, os.path.join(dir, 'symlink'))

        rc, out, err = assert_python_ok(self.script, dir1, dir2)
        self.assertEqual(out,
            '{dir1}:\n'
            'symlink -> {fn1}\n'
            '\n'
            '{dir2}:\n'
            'symlink -> {fn2}\n'
            .format(dir1=dir1, fn1=fn1, dir2=dir2, fn2=fn2)
        )


def test_main():
    test_support.run_unittest(*[obj for obj in globals().values()
                                    if isinstance(obj, type)])


if __name__ == '__main__':
    unittest.main()
