# Test packages (dotted-name import)

import sys
import os
import tempfile
import textwrap
import unittest


# Helpers to create and destroy hierarchies.

def cleanout(root):
    names = os.listdir(root)
    for name in names:
        fullname = os.path.join(root, name)
        if os.path.isdir(fullname) and not os.path.islink(fullname):
            cleanout(fullname)
        else:
            os.remove(fullname)
    os.rmdir(root)

def fixdir(lst):
    if "__builtins__" in lst:
        lst.remove("__builtins__")
    if "__initializing__" in lst:
        lst.remove("__initializing__")
    return lst


# XXX Things to test
#
# import package without __init__
# import package with __init__
# __init__ importing submodule
# __init__ importing global module
# __init__ defining variables
# submodule importing other submodule
# submodule importing global module
# submodule import submodule via global name
# from package import submodule
# from package import subpackage
# from package import variable (defined in __init__)
# from package import * (defined in __init__)


class TestPkg(unittest.TestCase):

    def setUp(self):
        self.root = None
        self.pkgname = None
        self.syspath = list(sys.path)
        self.modules_to_cleanup = set()  # Populated by mkhier().

    def tearDown(self):
        sys.path[:] = self.syspath
        for modulename in self.modules_to_cleanup:
            if modulename in sys.modules:
                del sys.modules[modulename]
        if self.root: # Only clean if the test was actually run
            cleanout(self.root)

        # delete all modules concerning the tested hierarchy
        if self.pkgname:
            modules = [name for name in sys.modules
                       if self.pkgname in name.split('.')]
            for name in modules:
                del sys.modules[name]

    def run_code(self, code):
        exec(textwrap.dedent(code), globals(), {"self": self})

    def mkhier(self, descr):
        root = tempfile.mkdtemp()
        sys.path.insert(0, root)
        if not os.path.isdir(root):
            os.mkdir(root)
        for name, contents in descr:
            comps = name.split()
            self.modules_to_cleanup.add('.'.join(comps))
            fullname = root
            for c in comps:
                fullname = os.path.join(fullname, c)
            if contents is None:
                os.mkdir(fullname)
            else:
                with open(fullname, "w") as f:
                    f.write(contents)
                    if not contents.endswith('\n'):
                        f.write('\n')
        self.root = root
        # package name is the name of the first item
        self.pkgname = descr[0][0]

    def test_1(self):
        hier = [("t1", None), ("t1 __init__.py", "")]
        self.mkhier(hier)
        import t1  # noqa: F401

    def test_2(self):
        hier = [
         ("t2", None),
         ("t2 __init__.py", "'doc for t2'"),
         ("t2 sub", None),
         ("t2 sub __init__.py", ""),
         ("t2 sub subsub", None),
         ("t2 sub subsub __init__.py", "spam = 1"),
        ]
        self.mkhier(hier)

        import t2.sub
        import t2.sub.subsub
        self.assertEqual(t2.__name__, "t2")
        self.assertEqual(t2.sub.__name__, "t2.sub")
        self.assertEqual(t2.sub.subsub.__name__, "t2.sub.subsub")

        # This exec crap is needed because Py3k forbids 'import *' outside
        # of module-scope and __import__() is insufficient for what we need.
        s = """
            import t2
            from t2 import *
            self.assertEqual(dir(), ['self', 'sub', 't2'])
            """
        self.run_code(s)

        from t2 import sub
        from t2.sub import subsub
        from t2.sub.subsub import spam  # noqa: F401
        self.assertEqual(sub.__name__, "t2.sub")
        self.assertEqual(subsub.__name__, "t2.sub.subsub")
        self.assertEqual(sub.subsub.__name__, "t2.sub.subsub")
        for name in ['spam', 'sub', 'subsub', 't2']:
            self.assertTrue(locals()["name"], "Failed to import %s" % name)

        import t2.sub
        import t2.sub.subsub
        self.assertEqual(t2.__name__, "t2")
        self.assertEqual(t2.sub.__name__, "t2.sub")
        self.assertEqual(t2.sub.subsub.__name__, "t2.sub.subsub")

        s = """
            from t2 import *
            self.assertEqual(dir(), ['self', 'sub'])
            """
        self.run_code(s)

    def test_3(self):
        hier = [
                ("t3", None),
                ("t3 __init__.py", ""),
                ("t3 sub", None),
                ("t3 sub __init__.py", ""),
                ("t3 sub subsub", None),
                ("t3 sub subsub __init__.py", "spam = 1"),
               ]
        self.mkhier(hier)

        import t3.sub.subsub
        self.assertEqual(t3.__name__, "t3")
        self.assertEqual(t3.sub.__name__, "t3.sub")
        self.assertEqual(t3.sub.subsub.__name__, "t3.sub.subsub")

    def test_4(self):
        hier = [
        ("t4.py", "raise RuntimeError('Shouldnt load t4.py')"),
        ("t4", None),
        ("t4 __init__.py", ""),
        ("t4 sub.py", "raise RuntimeError('Shouldnt load sub.py')"),
        ("t4 sub", None),
        ("t4 sub __init__.py", ""),
        ("t4 sub subsub.py",
         "raise RuntimeError('Shouldnt load subsub.py')"),
        ("t4 sub subsub", None),
        ("t4 sub subsub __init__.py", "spam = 1"),
               ]
        self.mkhier(hier)

        s = """
            from t4.sub.subsub import *
            self.assertEqual(spam, 1)
            """
        self.run_code(s)

    def test_5(self):
        hier = [
        ("t5", None),
        ("t5 __init__.py", "import t5.foo"),
        ("t5 string.py", "spam = 1"),
        ("t5 foo.py",
         "from . import string; assert string.spam == 1"),
         ]
        self.mkhier(hier)

        import t5
        s = """
            from t5 import *
            self.assertEqual(dir(), ['foo', 'self', 'string', 't5'])
            """
        self.run_code(s)

        import t5
        self.assertEqual(fixdir(dir(t5)),
                         ['__cached__', '__doc__', '__file__', '__loader__',
                          '__name__', '__package__', '__path__', '__spec__',
                          'foo', 'string', 't5'])
        self.assertEqual(fixdir(dir(t5.foo)),
                         ['__cached__', '__doc__', '__file__', '__loader__',
                          '__name__', '__package__', '__spec__', 'string'])
        self.assertEqual(fixdir(dir(t5.string)),
                         ['__cached__', '__doc__', '__file__', '__loader__',
                          '__name__', '__package__', '__spec__', 'spam'])

    def test_6(self):
        hier = [
                ("t6", None),
                ("t6 __init__.py",
                 "__all__ = ['spam', 'ham', 'eggs']"),
                ("t6 spam.py", ""),
                ("t6 ham.py", ""),
                ("t6 eggs.py", ""),
               ]
        self.mkhier(hier)

        import t6
        self.assertEqual(fixdir(dir(t6)),
                         ['__all__', '__cached__', '__doc__', '__file__',
                          '__loader__', '__name__', '__package__', '__path__',
                          '__spec__'])
        s = """
            import t6
            from t6 import *
            self.assertEqual(fixdir(dir(t6)),
                             ['__all__', '__cached__', '__doc__', '__file__',
                              '__loader__', '__name__', '__package__',
                              '__path__', '__spec__', 'eggs', 'ham', 'spam'])
            self.assertEqual(dir(), ['eggs', 'ham', 'self', 'spam', 't6'])
            """
        self.run_code(s)

    def test_7(self):
        hier = [
                ("t7.py", ""),
                ("t7", None),
                ("t7 __init__.py", ""),
                ("t7 sub.py",
                 "raise RuntimeError('Shouldnt load sub.py')"),
                ("t7 sub", None),
                ("t7 sub __init__.py", ""),
                ("t7 sub .py",
                 "raise RuntimeError('Shouldnt load subsub.py')"),
                ("t7 sub subsub", None),
                ("t7 sub subsub __init__.py",
                 "spam = 1"),
               ]
        self.mkhier(hier)


        t7, sub, subsub = None, None, None
        import t7 as tas
        self.assertEqual(fixdir(dir(tas)),
                         ['__cached__', '__doc__', '__file__', '__loader__',
                          '__name__', '__package__', '__path__', '__spec__'])
        self.assertFalse(t7)
        from t7 import sub as subpar
        self.assertEqual(fixdir(dir(subpar)),
                         ['__cached__', '__doc__', '__file__', '__loader__',
                          '__name__', '__package__', '__path__', '__spec__'])
        self.assertFalse(t7)
        self.assertFalse(sub)
        from t7.sub import subsub as subsubsub
        self.assertEqual(fixdir(dir(subsubsub)),
                         ['__cached__', '__doc__', '__file__', '__loader__',
                          '__name__', '__package__', '__path__', '__spec__',
                          'spam'])
        self.assertFalse(t7)
        self.assertFalse(sub)
        self.assertFalse(subsub)
        from t7.sub.subsub import spam as ham
        self.assertEqual(ham, 1)
        self.assertFalse(t7)
        self.assertFalse(sub)
        self.assertFalse(subsub)

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_8(self):
        hier = [
                ("t8", None),
                ("t8 __init__"+os.extsep+"py", "'doc for t8'"),
               ]
        self.mkhier(hier)

        import t8
        self.assertEqual(t8.__doc__, "doc for t8")

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