summaryrefslogtreecommitdiffstats
path: root/Lib/importlib/test/import_/test_path.py
blob: c939907af1869b8223668b0663618f9d175c7757 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
from ..support import (mock_modules, import_state, import_, mock_path_hook,
                        importlib_only, uncache)

from contextlib import nested
from imp import new_module
import sys
from types import MethodType
import unittest


class BaseTests(unittest.TestCase):

    """When sys.meta_path cannot find the desired module, sys.path is
    consulted. For each entry on the sequence [order], sys.path_importer_cache
    is checked to see if it contains a key for the entry [cache check]. If an
    importer is found then it is consulted before trying the next entry in
    sys.path [cache use]. The 'path' argument to find_module() is never used
    when trying to find a module [path not used].

    If an entry from sys.path is not in sys.path_importer_cache, sys.path_hooks
    is called in turn [hooks order]. If a path hook cannot handle an entry,
    ImportError is raised [hook failure]. Otherwise the resulting object is
    cached in sys.path_importer_cache and then consulted [hook success]. If no
    hook is found, None is set in sys.path_importer_cache and the default
    importer is tried [no hook].

    For use of __path__ in a package, the above is all true, just substitute
    "sys.path" for "__path__".

    """

    def order_test(self, to_import, entry, search_path, path=[]):
        # [order]
        log = []
        class LogFindModule(mock_modules):
            def find_module(self, fullname):
                log.append(self)
                return super().find_module(fullname)

        assert len(search_path) == 2
        misser = LogFindModule(search_path[0])
        hitter = LogFindModule(to_import)
        with nested(misser, hitter):
            cache = dict(zip(search_path, (misser, hitter)))
            with import_state(path=path, path_importer_cache=cache):
                import_(to_import)
        self.assertEquals(log[0], misser)
        self.assertEquals(log[1], hitter)

    @importlib_only  # __import__ uses PyDict_GetItem(), bypassing log.
    def cache_use_test(self, to_import, entry, path=[]):
        # [cache check], [cache use]
        log = []
        class LoggingDict(dict):
            def __getitem__(self, item):
                log.append(item)
                return super(LoggingDict, self).__getitem__(item)

        with mock_modules(to_import) as importer:
            cache = LoggingDict()
            cache[entry] = importer
            with import_state(path=[entry], path_importer_cache=cache):
                module = import_(to_import, fromlist=['a'])
            self.assert_(module is importer[to_import])
        self.assertEquals(len(cache), 1)
        self.assertEquals([entry], log)

    def hooks_order_test(self, to_import, entry, path=[]):
        # [hooks order], [hooks failure], [hook success]
        log = []
        def logging_hook(entry):
            log.append(entry)
            raise ImportError
        with mock_modules(to_import) as importer:
            hitter = mock_path_hook(entry, importer=importer)
            path_hooks = [logging_hook, logging_hook, hitter]
            with import_state(path_hooks=path_hooks, path=path):
                import_(to_import)
                self.assertEquals(sys.path_importer_cache[entry], importer)
        self.assertEquals(len(log), 2)

    # [no hook] XXX Worry about after deciding how to handle the default hook.

    def path_argument_test(self, to_import):
        # [path not used]
        class BadImporter:
            """Class to help detect TypeError from calling find_module() with
            an improper number of arguments."""
            def find_module(name):
                raise ImportError

        try:
            import_(to_import)
        except ImportError:
            pass


class PathTests(BaseTests):

    """Tests for sys.path."""

    def test_order(self):
        self.order_test('hit', 'second', ['first', 'second'],
                        ['first', 'second'])

    def test_cache_use(self):
        entry = "found!"
        self.cache_use_test('hit', entry, [entry])

    def test_hooks_order(self):
        entry = "found!"
        self.hooks_order_test('hit', entry, [entry])

    def test_path_argument(self):
        name = 'total junk'
        with uncache(name):
            self.path_argument_test(name)


class __path__Tests(BaseTests):

    """Tests for __path__."""

    def run_test(self, test, entry, path, *args):
        with mock_modules('pkg.__init__') as importer:
            importer['pkg'].__path__ = path
            importer.load_module('pkg')
            test('pkg.hit', entry, *args)


    @importlib_only  # XXX Unknown reason why this fails.
    def test_order(self):
        self.run_test(self.order_test, 'second', ('first', 'second'), ['first',
            'second'])

    def test_cache_use(self):
        location = "I'm here!"
        self.run_test(self.cache_use_test, location, [location])

    def test_hooks_order(self):
        location = "I'm here!"
        self.run_test(self.hooks_order_test, location, [location])

    def test_path_argument(self):
        module = new_module('pkg')
        module.__path__ = ['random __path__']
        name = 'pkg.whatever'
        sys.modules['pkg'] = module
        with uncache('pkg', name):
            self.path_argument_test(name)


def test_main():
    from test.support import run_unittest
    run_unittest(PathTests, __path__Tests)

if __name__ == '__main__':
    test_main()