1import os.path
2from os.path import abspath
3import re
4import sys
5import types
6import pickle
7from test import support
8from test.support import import_helper
9import test.test_importlib.util
10
11import unittest
12import unittest.mock
13import unittest.test
14
15
16class TestableTestProgram(unittest.TestProgram):
17    module = None
18    exit = True
19    defaultTest = failfast = catchbreak = buffer = None
20    verbosity = 1
21    progName = ''
22    testRunner = testLoader = None
23
24    def __init__(self):
25        pass
26
27
28class TestDiscovery(unittest.TestCase):
29
30    # Heavily mocked tests so I can avoid hitting the filesystem
31    def test_get_name_from_path(self):
32        loader = unittest.TestLoader()
33        loader._top_level_dir = '/foo'
34        name = loader._get_name_from_path('/foo/bar/baz.py')
35        self.assertEqual(name, 'bar.baz')
36
37        if not __debug__:
38            # asserts are off
39            return
40
41        with self.assertRaises(AssertionError):
42            loader._get_name_from_path('/bar/baz.py')
43
44    def test_find_tests(self):
45        loader = unittest.TestLoader()
46
47        original_listdir = os.listdir
48        def restore_listdir():
49            os.listdir = original_listdir
50        original_isfile = os.path.isfile
51        def restore_isfile():
52            os.path.isfile = original_isfile
53        original_isdir = os.path.isdir
54        def restore_isdir():
55            os.path.isdir = original_isdir
56
57        path_lists = [['test2.py', 'test1.py', 'not_a_test.py', 'test_dir',
58                       'test.foo', 'test-not-a-module.py', 'another_dir'],
59                      ['test4.py', 'test3.py', ]]
60        os.listdir = lambda path: path_lists.pop(0)
61        self.addCleanup(restore_listdir)
62
63        def isdir(path):
64            return path.endswith('dir')
65        os.path.isdir = isdir
66        self.addCleanup(restore_isdir)
67
68        def isfile(path):
69            # another_dir is not a package and so shouldn't be recursed into
70            return not path.endswith('dir') and not 'another_dir' in path
71        os.path.isfile = isfile
72        self.addCleanup(restore_isfile)
73
74        loader._get_module_from_name = lambda path: path + ' module'
75        orig_load_tests = loader.loadTestsFromModule
76        def loadTestsFromModule(module, pattern=None):
77            # This is where load_tests is called.
78            base = orig_load_tests(module, pattern=pattern)
79            return base + [module + ' tests']
80        loader.loadTestsFromModule = loadTestsFromModule
81        loader.suiteClass = lambda thing: thing
82
83        top_level = os.path.abspath('/foo')
84        loader._top_level_dir = top_level
85        suite = list(loader._find_tests(top_level, 'test*.py'))
86
87        # The test suites found should be sorted alphabetically for reliable
88        # execution order.
89        expected = [[name + ' module tests'] for name in
90                    ('test1', 'test2', 'test_dir')]
91        expected.extend([[('test_dir.%s' % name) + ' module tests'] for name in
92                    ('test3', 'test4')])
93        self.assertEqual(suite, expected)
94
95    def test_find_tests_socket(self):
96        # A socket is neither a directory nor a regular file.
97        # https://bugs.python.org/issue25320
98        loader = unittest.TestLoader()
99
100        original_listdir = os.listdir
101        def restore_listdir():
102            os.listdir = original_listdir
103        original_isfile = os.path.isfile
104        def restore_isfile():
105            os.path.isfile = original_isfile
106        original_isdir = os.path.isdir
107        def restore_isdir():
108            os.path.isdir = original_isdir
109
110        path_lists = [['socket']]
111        os.listdir = lambda path: path_lists.pop(0)
112        self.addCleanup(restore_listdir)
113
114        os.path.isdir = lambda path: False
115        self.addCleanup(restore_isdir)
116
117        os.path.isfile = lambda path: False
118        self.addCleanup(restore_isfile)
119
120        loader._get_module_from_name = lambda path: path + ' module'
121        orig_load_tests = loader.loadTestsFromModule
122        def loadTestsFromModule(module, pattern=None):
123            # This is where load_tests is called.
124            base = orig_load_tests(module, pattern=pattern)
125            return base + [module + ' tests']
126        loader.loadTestsFromModule = loadTestsFromModule
127        loader.suiteClass = lambda thing: thing
128
129        top_level = os.path.abspath('/foo')
130        loader._top_level_dir = top_level
131        suite = list(loader._find_tests(top_level, 'test*.py'))
132
133        self.assertEqual(suite, [])
134
135    def test_find_tests_with_package(self):
136        loader = unittest.TestLoader()
137
138        original_listdir = os.listdir
139        def restore_listdir():
140            os.listdir = original_listdir
141        original_isfile = os.path.isfile
142        def restore_isfile():
143            os.path.isfile = original_isfile
144        original_isdir = os.path.isdir
145        def restore_isdir():
146            os.path.isdir = original_isdir
147
148        directories = ['a_directory', 'test_directory', 'test_directory2']
149        path_lists = [directories, [], [], []]
150        os.listdir = lambda path: path_lists.pop(0)
151        self.addCleanup(restore_listdir)
152
153        os.path.isdir = lambda path: True
154        self.addCleanup(restore_isdir)
155
156        os.path.isfile = lambda path: os.path.basename(path) not in directories
157        self.addCleanup(restore_isfile)
158
159        class Module(object):
160            paths = []
161            load_tests_args = []
162
163            def __init__(self, path):
164                self.path = path
165                self.paths.append(path)
166                if os.path.basename(path) == 'test_directory':
167                    def load_tests(loader, tests, pattern):
168                        self.load_tests_args.append((loader, tests, pattern))
169                        return [self.path + ' load_tests']
170                    self.load_tests = load_tests
171
172            def __eq__(self, other):
173                return self.path == other.path
174
175        loader._get_module_from_name = lambda name: Module(name)
176        orig_load_tests = loader.loadTestsFromModule
177        def loadTestsFromModule(module, pattern=None):
178            # This is where load_tests is called.
179            base = orig_load_tests(module, pattern=pattern)
180            return base + [module.path + ' module tests']
181        loader.loadTestsFromModule = loadTestsFromModule
182        loader.suiteClass = lambda thing: thing
183
184        loader._top_level_dir = '/foo'
185        # this time no '.py' on the pattern so that it can match
186        # a test package
187        suite = list(loader._find_tests('/foo', 'test*'))
188
189        # We should have loaded tests from the a_directory and test_directory2
190        # directly and via load_tests for the test_directory package, which
191        # still calls the baseline module loader.
192        self.assertEqual(suite,
193                         [['a_directory module tests'],
194                          ['test_directory load_tests',
195                           'test_directory module tests'],
196                          ['test_directory2 module tests']])
197
198
199        # The test module paths should be sorted for reliable execution order
200        self.assertEqual(Module.paths,
201                         ['a_directory', 'test_directory', 'test_directory2'])
202
203        # load_tests should have been called once with loader, tests and pattern
204        # (but there are no tests in our stub module itself, so that is [] at
205        # the time of call).
206        self.assertEqual(Module.load_tests_args,
207                         [(loader, [], 'test*')])
208
209    def test_find_tests_default_calls_package_load_tests(self):
210        loader = unittest.TestLoader()
211
212        original_listdir = os.listdir
213        def restore_listdir():
214            os.listdir = original_listdir
215        original_isfile = os.path.isfile
216        def restore_isfile():
217            os.path.isfile = original_isfile
218        original_isdir = os.path.isdir
219        def restore_isdir():
220            os.path.isdir = original_isdir
221
222        directories = ['a_directory', 'test_directory', 'test_directory2']
223        path_lists = [directories, [], [], []]
224        os.listdir = lambda path: path_lists.pop(0)
225        self.addCleanup(restore_listdir)
226
227        os.path.isdir = lambda path: True
228        self.addCleanup(restore_isdir)
229
230        os.path.isfile = lambda path: os.path.basename(path) not in directories
231        self.addCleanup(restore_isfile)
232
233        class Module(object):
234            paths = []
235            load_tests_args = []
236
237            def __init__(self, path):
238                self.path = path
239                self.paths.append(path)
240                if os.path.basename(path) == 'test_directory':
241                    def load_tests(loader, tests, pattern):
242                        self.load_tests_args.append((loader, tests, pattern))
243                        return [self.path + ' load_tests']
244                    self.load_tests = load_tests
245
246            def __eq__(self, other):
247                return self.path == other.path
248
249        loader._get_module_from_name = lambda name: Module(name)
250        orig_load_tests = loader.loadTestsFromModule
251        def loadTestsFromModule(module, pattern=None):
252            # This is where load_tests is called.
253            base = orig_load_tests(module, pattern=pattern)
254            return base + [module.path + ' module tests']
255        loader.loadTestsFromModule = loadTestsFromModule
256        loader.suiteClass = lambda thing: thing
257
258        loader._top_level_dir = '/foo'
259        # this time no '.py' on the pattern so that it can match
260        # a test package
261        suite = list(loader._find_tests('/foo', 'test*.py'))
262
263        # We should have loaded tests from the a_directory and test_directory2
264        # directly and via load_tests for the test_directory package, which
265        # still calls the baseline module loader.
266        self.assertEqual(suite,
267                         [['a_directory module tests'],
268                          ['test_directory load_tests',
269                           'test_directory module tests'],
270                          ['test_directory2 module tests']])
271        # The test module paths should be sorted for reliable execution order
272        self.assertEqual(Module.paths,
273                         ['a_directory', 'test_directory', 'test_directory2'])
274
275
276        # load_tests should have been called once with loader, tests and pattern
277        self.assertEqual(Module.load_tests_args,
278                         [(loader, [], 'test*.py')])
279
280    def test_find_tests_customize_via_package_pattern(self):
281        # This test uses the example 'do-nothing' load_tests from
282        # https://docs.python.org/3/library/unittest.html#load-tests-protocol
283        # to make sure that that actually works.
284        # Housekeeping
285        original_listdir = os.listdir
286        def restore_listdir():
287            os.listdir = original_listdir
288        self.addCleanup(restore_listdir)
289        original_isfile = os.path.isfile
290        def restore_isfile():
291            os.path.isfile = original_isfile
292        self.addCleanup(restore_isfile)
293        original_isdir = os.path.isdir
294        def restore_isdir():
295            os.path.isdir = original_isdir
296        self.addCleanup(restore_isdir)
297        self.addCleanup(sys.path.remove, abspath('/foo'))
298
299        # Test data: we expect the following:
300        # a listdir to find our package, and isfile and isdir checks on it.
301        # a module-from-name call to turn that into a module
302        # followed by load_tests.
303        # then our load_tests will call discover() which is messy
304        # but that finally chains into find_tests again for the child dir -
305        # which is why we don't have an infinite loop.
306        # We expect to see:
307        # the module load tests for both package and plain module called,
308        # and the plain module result nested by the package module load_tests
309        # indicating that it was processed and could have been mutated.
310        vfs = {abspath('/foo'): ['my_package'],
311               abspath('/foo/my_package'): ['__init__.py', 'test_module.py']}
312        def list_dir(path):
313            return list(vfs[path])
314        os.listdir = list_dir
315        os.path.isdir = lambda path: not path.endswith('.py')
316        os.path.isfile = lambda path: path.endswith('.py')
317
318        class Module(object):
319            paths = []
320            load_tests_args = []
321
322            def __init__(self, path):
323                self.path = path
324                self.paths.append(path)
325                if path.endswith('test_module'):
326                    def load_tests(loader, tests, pattern):
327                        self.load_tests_args.append((loader, tests, pattern))
328                        return [self.path + ' load_tests']
329                else:
330                    def load_tests(loader, tests, pattern):
331                        self.load_tests_args.append((loader, tests, pattern))
332                        # top level directory cached on loader instance
333                        __file__ = '/foo/my_package/__init__.py'
334                        this_dir = os.path.dirname(__file__)
335                        pkg_tests = loader.discover(
336                            start_dir=this_dir, pattern=pattern)
337                        return [self.path + ' load_tests', tests
338                            ] + pkg_tests
339                self.load_tests = load_tests
340
341            def __eq__(self, other):
342                return self.path == other.path
343
344        loader = unittest.TestLoader()
345        loader._get_module_from_name = lambda name: Module(name)
346        loader.suiteClass = lambda thing: thing
347
348        loader._top_level_dir = abspath('/foo')
349        # this time no '.py' on the pattern so that it can match
350        # a test package
351        suite = list(loader._find_tests(abspath('/foo'), 'test*.py'))
352
353        # We should have loaded tests from both my_package and
354        # my_package.test_module, and also run the load_tests hook in both.
355        # (normally this would be nested TestSuites.)
356        self.assertEqual(suite,
357                         [['my_package load_tests', [],
358                          ['my_package.test_module load_tests']]])
359        # Parents before children.
360        self.assertEqual(Module.paths,
361                         ['my_package', 'my_package.test_module'])
362
363        # load_tests should have been called twice with loader, tests and pattern
364        self.assertEqual(Module.load_tests_args,
365                         [(loader, [], 'test*.py'),
366                          (loader, [], 'test*.py')])
367
368    def test_discover(self):
369        loader = unittest.TestLoader()
370
371        original_isfile = os.path.isfile
372        original_isdir = os.path.isdir
373        def restore_isfile():
374            os.path.isfile = original_isfile
375
376        os.path.isfile = lambda path: False
377        self.addCleanup(restore_isfile)
378
379        orig_sys_path = sys.path[:]
380        def restore_path():
381            sys.path[:] = orig_sys_path
382        self.addCleanup(restore_path)
383
384        full_path = os.path.abspath(os.path.normpath('/foo'))
385        with self.assertRaises(ImportError):
386            loader.discover('/foo/bar', top_level_dir='/foo')
387
388        self.assertEqual(loader._top_level_dir, full_path)
389        self.assertIn(full_path, sys.path)
390
391        os.path.isfile = lambda path: True
392        os.path.isdir = lambda path: True
393
394        def restore_isdir():
395            os.path.isdir = original_isdir
396        self.addCleanup(restore_isdir)
397
398        _find_tests_args = []
399        def _find_tests(start_dir, pattern, namespace=None):
400            _find_tests_args.append((start_dir, pattern))
401            return ['tests']
402        loader._find_tests = _find_tests
403        loader.suiteClass = str
404
405        suite = loader.discover('/foo/bar/baz', 'pattern', '/foo/bar')
406
407        top_level_dir = os.path.abspath('/foo/bar')
408        start_dir = os.path.abspath('/foo/bar/baz')
409        self.assertEqual(suite, "['tests']")
410        self.assertEqual(loader._top_level_dir, top_level_dir)
411        self.assertEqual(_find_tests_args, [(start_dir, 'pattern')])
412        self.assertIn(top_level_dir, sys.path)
413
414    def test_discover_start_dir_is_package_calls_package_load_tests(self):
415        # This test verifies that the package load_tests in a package is indeed
416        # invoked when the start_dir is a package (and not the top level).
417        # http://bugs.python.org/issue22457
418
419        # Test data: we expect the following:
420        # an isfile to verify the package, then importing and scanning
421        # as per _find_tests' normal behaviour.
422        # We expect to see our load_tests hook called once.
423        vfs = {abspath('/toplevel'): ['startdir'],
424               abspath('/toplevel/startdir'): ['__init__.py']}
425        def list_dir(path):
426            return list(vfs[path])
427        self.addCleanup(setattr, os, 'listdir', os.listdir)
428        os.listdir = list_dir
429        self.addCleanup(setattr, os.path, 'isfile', os.path.isfile)
430        os.path.isfile = lambda path: path.endswith('.py')
431        self.addCleanup(setattr, os.path, 'isdir', os.path.isdir)
432        os.path.isdir = lambda path: not path.endswith('.py')
433        self.addCleanup(sys.path.remove, abspath('/toplevel'))
434
435        class Module(object):
436            paths = []
437            load_tests_args = []
438
439            def __init__(self, path):
440                self.path = path
441
442            def load_tests(self, loader, tests, pattern):
443                return ['load_tests called ' + self.path]
444
445            def __eq__(self, other):
446                return self.path == other.path
447
448        loader = unittest.TestLoader()
449        loader._get_module_from_name = lambda name: Module(name)
450        loader.suiteClass = lambda thing: thing
451
452        suite = loader.discover('/toplevel/startdir', top_level_dir='/toplevel')
453
454        # We should have loaded tests from the package __init__.
455        # (normally this would be nested TestSuites.)
456        self.assertEqual(suite,
457                         [['load_tests called startdir']])
458
459    def setup_import_issue_tests(self, fakefile):
460        listdir = os.listdir
461        os.listdir = lambda _: [fakefile]
462        isfile = os.path.isfile
463        os.path.isfile = lambda _: True
464        orig_sys_path = sys.path[:]
465        def restore():
466            os.path.isfile = isfile
467            os.listdir = listdir
468            sys.path[:] = orig_sys_path
469        self.addCleanup(restore)
470
471    def setup_import_issue_package_tests(self, vfs):
472        self.addCleanup(setattr, os, 'listdir', os.listdir)
473        self.addCleanup(setattr, os.path, 'isfile', os.path.isfile)
474        self.addCleanup(setattr, os.path, 'isdir', os.path.isdir)
475        self.addCleanup(sys.path.__setitem__, slice(None), list(sys.path))
476        def list_dir(path):
477            return list(vfs[path])
478        os.listdir = list_dir
479        os.path.isdir = lambda path: not path.endswith('.py')
480        os.path.isfile = lambda path: path.endswith('.py')
481
482    def test_discover_with_modules_that_fail_to_import(self):
483        loader = unittest.TestLoader()
484
485        self.setup_import_issue_tests('test_this_does_not_exist.py')
486
487        suite = loader.discover('.')
488        self.assertIn(os.getcwd(), sys.path)
489        self.assertEqual(suite.countTestCases(), 1)
490        # Errors loading the suite are also captured for introspection.
491        self.assertNotEqual([], loader.errors)
492        self.assertEqual(1, len(loader.errors))
493        error = loader.errors[0]
494        self.assertTrue(
495            'Failed to import test module: test_this_does_not_exist' in error,
496            'missing error string in %r' % error)
497        test = list(list(suite)[0])[0] # extract test from suite
498
499        with self.assertRaises(ImportError):
500            test.test_this_does_not_exist()
501
502    def test_discover_with_init_modules_that_fail_to_import(self):
503        vfs = {abspath('/foo'): ['my_package'],
504               abspath('/foo/my_package'): ['__init__.py', 'test_module.py']}
505        self.setup_import_issue_package_tests(vfs)
506        import_calls = []
507        def _get_module_from_name(name):
508            import_calls.append(name)
509            raise ImportError("Cannot import Name")
510        loader = unittest.TestLoader()
511        loader._get_module_from_name = _get_module_from_name
512        suite = loader.discover(abspath('/foo'))
513
514        self.assertIn(abspath('/foo'), sys.path)
515        self.assertEqual(suite.countTestCases(), 1)
516        # Errors loading the suite are also captured for introspection.
517        self.assertNotEqual([], loader.errors)
518        self.assertEqual(1, len(loader.errors))
519        error = loader.errors[0]
520        self.assertTrue(
521            'Failed to import test module: my_package' in error,
522            'missing error string in %r' % error)
523        test = list(list(suite)[0])[0] # extract test from suite
524        with self.assertRaises(ImportError):
525            test.my_package()
526        self.assertEqual(import_calls, ['my_package'])
527
528        # Check picklability
529        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
530            pickle.loads(pickle.dumps(test, proto))
531
532    def test_discover_with_module_that_raises_SkipTest_on_import(self):
533        if not unittest.BaseTestSuite._cleanup:
534            raise unittest.SkipTest("Suite cleanup is disabled")
535
536        loader = unittest.TestLoader()
537
538        def _get_module_from_name(name):
539            raise unittest.SkipTest('skipperoo')
540        loader._get_module_from_name = _get_module_from_name
541
542        self.setup_import_issue_tests('test_skip_dummy.py')
543
544        suite = loader.discover('.')
545        self.assertEqual(suite.countTestCases(), 1)
546
547        result = unittest.TestResult()
548        suite.run(result)
549        self.assertEqual(len(result.skipped), 1)
550
551        # Check picklability
552        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
553            pickle.loads(pickle.dumps(suite, proto))
554
555    def test_discover_with_init_module_that_raises_SkipTest_on_import(self):
556        if not unittest.BaseTestSuite._cleanup:
557            raise unittest.SkipTest("Suite cleanup is disabled")
558
559        vfs = {abspath('/foo'): ['my_package'],
560               abspath('/foo/my_package'): ['__init__.py', 'test_module.py']}
561        self.setup_import_issue_package_tests(vfs)
562        import_calls = []
563        def _get_module_from_name(name):
564            import_calls.append(name)
565            raise unittest.SkipTest('skipperoo')
566        loader = unittest.TestLoader()
567        loader._get_module_from_name = _get_module_from_name
568        suite = loader.discover(abspath('/foo'))
569
570        self.assertIn(abspath('/foo'), sys.path)
571        self.assertEqual(suite.countTestCases(), 1)
572        result = unittest.TestResult()
573        suite.run(result)
574        self.assertEqual(len(result.skipped), 1)
575        self.assertEqual(result.testsRun, 1)
576        self.assertEqual(import_calls, ['my_package'])
577
578        # Check picklability
579        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
580            pickle.loads(pickle.dumps(suite, proto))
581
582    def test_command_line_handling_parseArgs(self):
583        program = TestableTestProgram()
584
585        args = []
586        program._do_discovery = args.append
587        program.parseArgs(['something', 'discover'])
588        self.assertEqual(args, [[]])
589
590        args[:] = []
591        program.parseArgs(['something', 'discover', 'foo', 'bar'])
592        self.assertEqual(args, [['foo', 'bar']])
593
594    def test_command_line_handling_discover_by_default(self):
595        program = TestableTestProgram()
596
597        args = []
598        program._do_discovery = args.append
599        program.parseArgs(['something'])
600        self.assertEqual(args, [[]])
601        self.assertEqual(program.verbosity, 1)
602        self.assertIs(program.buffer, False)
603        self.assertIs(program.catchbreak, False)
604        self.assertIs(program.failfast, False)
605
606    def test_command_line_handling_discover_by_default_with_options(self):
607        program = TestableTestProgram()
608
609        args = []
610        program._do_discovery = args.append
611        program.parseArgs(['something', '-v', '-b', '-v', '-c', '-f'])
612        self.assertEqual(args, [[]])
613        self.assertEqual(program.verbosity, 2)
614        self.assertIs(program.buffer, True)
615        self.assertIs(program.catchbreak, True)
616        self.assertIs(program.failfast, True)
617
618
619    def test_command_line_handling_do_discovery_too_many_arguments(self):
620        program = TestableTestProgram()
621        program.testLoader = None
622
623        with support.captured_stderr() as stderr, \
624             self.assertRaises(SystemExit) as cm:
625            # too many args
626            program._do_discovery(['one', 'two', 'three', 'four'])
627        self.assertEqual(cm.exception.args, (2,))
628        self.assertIn('usage:', stderr.getvalue())
629
630
631    def test_command_line_handling_do_discovery_uses_default_loader(self):
632        program = object.__new__(unittest.TestProgram)
633        program._initArgParsers()
634
635        class Loader(object):
636            args = []
637            def discover(self, start_dir, pattern, top_level_dir):
638                self.args.append((start_dir, pattern, top_level_dir))
639                return 'tests'
640
641        program.testLoader = Loader()
642        program._do_discovery(['-v'])
643        self.assertEqual(Loader.args, [('.', 'test*.py', None)])
644
645    def test_command_line_handling_do_discovery_calls_loader(self):
646        program = TestableTestProgram()
647
648        class Loader(object):
649            args = []
650            def discover(self, start_dir, pattern, top_level_dir):
651                self.args.append((start_dir, pattern, top_level_dir))
652                return 'tests'
653
654        program._do_discovery(['-v'], Loader=Loader)
655        self.assertEqual(program.verbosity, 2)
656        self.assertEqual(program.test, 'tests')
657        self.assertEqual(Loader.args, [('.', 'test*.py', None)])
658
659        Loader.args = []
660        program = TestableTestProgram()
661        program._do_discovery(['--verbose'], Loader=Loader)
662        self.assertEqual(program.test, 'tests')
663        self.assertEqual(Loader.args, [('.', 'test*.py', None)])
664
665        Loader.args = []
666        program = TestableTestProgram()
667        program._do_discovery([], Loader=Loader)
668        self.assertEqual(program.test, 'tests')
669        self.assertEqual(Loader.args, [('.', 'test*.py', None)])
670
671        Loader.args = []
672        program = TestableTestProgram()
673        program._do_discovery(['fish'], Loader=Loader)
674        self.assertEqual(program.test, 'tests')
675        self.assertEqual(Loader.args, [('fish', 'test*.py', None)])
676
677        Loader.args = []
678        program = TestableTestProgram()
679        program._do_discovery(['fish', 'eggs'], Loader=Loader)
680        self.assertEqual(program.test, 'tests')
681        self.assertEqual(Loader.args, [('fish', 'eggs', None)])
682
683        Loader.args = []
684        program = TestableTestProgram()
685        program._do_discovery(['fish', 'eggs', 'ham'], Loader=Loader)
686        self.assertEqual(program.test, 'tests')
687        self.assertEqual(Loader.args, [('fish', 'eggs', 'ham')])
688
689        Loader.args = []
690        program = TestableTestProgram()
691        program._do_discovery(['-s', 'fish'], Loader=Loader)
692        self.assertEqual(program.test, 'tests')
693        self.assertEqual(Loader.args, [('fish', 'test*.py', None)])
694
695        Loader.args = []
696        program = TestableTestProgram()
697        program._do_discovery(['-t', 'fish'], Loader=Loader)
698        self.assertEqual(program.test, 'tests')
699        self.assertEqual(Loader.args, [('.', 'test*.py', 'fish')])
700
701        Loader.args = []
702        program = TestableTestProgram()
703        program._do_discovery(['-p', 'fish'], Loader=Loader)
704        self.assertEqual(program.test, 'tests')
705        self.assertEqual(Loader.args, [('.', 'fish', None)])
706        self.assertFalse(program.failfast)
707        self.assertFalse(program.catchbreak)
708
709        Loader.args = []
710        program = TestableTestProgram()
711        program._do_discovery(['-p', 'eggs', '-s', 'fish', '-v', '-f', '-c'],
712                              Loader=Loader)
713        self.assertEqual(program.test, 'tests')
714        self.assertEqual(Loader.args, [('fish', 'eggs', None)])
715        self.assertEqual(program.verbosity, 2)
716        self.assertTrue(program.failfast)
717        self.assertTrue(program.catchbreak)
718
719    def setup_module_clash(self):
720        class Module(object):
721            __file__ = 'bar/foo.py'
722        sys.modules['foo'] = Module
723        full_path = os.path.abspath('foo')
724        original_listdir = os.listdir
725        original_isfile = os.path.isfile
726        original_isdir = os.path.isdir
727        original_realpath = os.path.realpath
728
729        def cleanup():
730            os.listdir = original_listdir
731            os.path.isfile = original_isfile
732            os.path.isdir = original_isdir
733            os.path.realpath = original_realpath
734            del sys.modules['foo']
735            if full_path in sys.path:
736                sys.path.remove(full_path)
737        self.addCleanup(cleanup)
738
739        def listdir(_):
740            return ['foo.py']
741        def isfile(_):
742            return True
743        def isdir(_):
744            return True
745        os.listdir = listdir
746        os.path.isfile = isfile
747        os.path.isdir = isdir
748        if os.name == 'nt':
749            # ntpath.realpath may inject path prefixes when failing to
750            # resolve real files, so we substitute abspath() here instead.
751            os.path.realpath = os.path.abspath
752        return full_path
753
754    def test_detect_module_clash(self):
755        full_path = self.setup_module_clash()
756        loader = unittest.TestLoader()
757
758        mod_dir = os.path.abspath('bar')
759        expected_dir = os.path.abspath('foo')
760        msg = re.escape(r"'foo' module incorrectly imported from %r. Expected %r. "
761                "Is this module globally installed?" % (mod_dir, expected_dir))
762        self.assertRaisesRegex(
763            ImportError, '^%s$' % msg, loader.discover,
764            start_dir='foo', pattern='foo.py'
765        )
766        self.assertEqual(sys.path[0], full_path)
767
768    def test_module_symlink_ok(self):
769        full_path = self.setup_module_clash()
770
771        original_realpath = os.path.realpath
772
773        mod_dir = os.path.abspath('bar')
774        expected_dir = os.path.abspath('foo')
775
776        def cleanup():
777            os.path.realpath = original_realpath
778        self.addCleanup(cleanup)
779
780        def realpath(path):
781            if path == os.path.join(mod_dir, 'foo.py'):
782                return os.path.join(expected_dir, 'foo.py')
783            return path
784        os.path.realpath = realpath
785        loader = unittest.TestLoader()
786        loader.discover(start_dir='foo', pattern='foo.py')
787
788    def test_discovery_from_dotted_path(self):
789        loader = unittest.TestLoader()
790
791        tests = [self]
792        expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__))
793
794        self.wasRun = False
795        def _find_tests(start_dir, pattern, namespace=None):
796            self.wasRun = True
797            self.assertEqual(start_dir, expectedPath)
798            return tests
799        loader._find_tests = _find_tests
800        suite = loader.discover('unittest.test')
801        self.assertTrue(self.wasRun)
802        self.assertEqual(suite._tests, tests)
803
804
805    def test_discovery_from_dotted_path_builtin_modules(self):
806
807        loader = unittest.TestLoader()
808
809        listdir = os.listdir
810        os.listdir = lambda _: ['test_this_does_not_exist.py']
811        isfile = os.path.isfile
812        isdir = os.path.isdir
813        os.path.isdir = lambda _: False
814        orig_sys_path = sys.path[:]
815        def restore():
816            os.path.isfile = isfile
817            os.path.isdir = isdir
818            os.listdir = listdir
819            sys.path[:] = orig_sys_path
820        self.addCleanup(restore)
821
822        with self.assertRaises(TypeError) as cm:
823            loader.discover('sys')
824        self.assertEqual(str(cm.exception),
825                         'Can not use builtin modules '
826                         'as dotted module names')
827
828    def test_discovery_from_dotted_namespace_packages(self):
829        loader = unittest.TestLoader()
830
831        package = types.ModuleType('package')
832        package.__path__ = ['/a', '/b']
833        package.__spec__ = types.SimpleNamespace(
834           loader=None,
835           submodule_search_locations=['/a', '/b']
836        )
837
838        def _import(packagename, *args, **kwargs):
839            sys.modules[packagename] = package
840            return package
841
842        _find_tests_args = []
843        def _find_tests(start_dir, pattern, namespace=None):
844            _find_tests_args.append((start_dir, pattern))
845            return ['%s/tests' % start_dir]
846
847        loader._find_tests = _find_tests
848        loader.suiteClass = list
849
850        with unittest.mock.patch('builtins.__import__', _import):
851            # Since loader.discover() can modify sys.path, restore it when done.
852            with import_helper.DirsOnSysPath():
853                # Make sure to remove 'package' from sys.modules when done.
854                with test.test_importlib.util.uncache('package'):
855                    suite = loader.discover('package')
856
857        self.assertEqual(suite, ['/a/tests', '/b/tests'])
858
859    def test_discovery_failed_discovery(self):
860        loader = unittest.TestLoader()
861        package = types.ModuleType('package')
862
863        def _import(packagename, *args, **kwargs):
864            sys.modules[packagename] = package
865            return package
866
867        with unittest.mock.patch('builtins.__import__', _import):
868            # Since loader.discover() can modify sys.path, restore it when done.
869            with import_helper.DirsOnSysPath():
870                # Make sure to remove 'package' from sys.modules when done.
871                with test.test_importlib.util.uncache('package'):
872                    with self.assertRaises(TypeError) as cm:
873                        loader.discover('package')
874                    self.assertEqual(str(cm.exception),
875                                     'don\'t know how to discover from {!r}'
876                                     .format(package))
877
878
879if __name__ == '__main__':
880    unittest.main()
881