1"""Unittest main program"""
2
3import sys
4import os
5import types
6
7from . import loader, runner
8from .signals import installHandler
9
10__unittest = True
11
12FAILFAST     = "  -f, --failfast   Stop on first failure\n"
13CATCHBREAK   = "  -c, --catch      Catch control-C and display results\n"
14BUFFEROUTPUT = "  -b, --buffer     Buffer stdout and stderr during test runs\n"
15
16USAGE_AS_MAIN = """\
17Usage: %(progName)s [options] [tests]
18
19Options:
20  -h, --help       Show this message
21  -v, --verbose    Verbose output
22  -q, --quiet      Minimal output
23%(failfast)s%(catchbreak)s%(buffer)s
24Examples:
25  %(progName)s test_module               - run tests from test_module
26  %(progName)s module.TestClass          - run tests from module.TestClass
27  %(progName)s module.Class.test_method  - run specified test method
28
29[tests] can be a list of any number of test modules, classes and test
30methods.
31
32Alternative Usage: %(progName)s discover [options]
33
34Options:
35  -v, --verbose    Verbose output
36%(failfast)s%(catchbreak)s%(buffer)s  -s directory     Directory to start discovery ('.' default)
37  -p pattern       Pattern to match test files ('test*.py' default)
38  -t directory     Top level directory of project (default to
39                   start directory)
40
41For test discovery all test modules must be importable from the top
42level directory of the project.
43"""
44
45USAGE_FROM_MODULE = """\
46Usage: %(progName)s [options] [test] [...]
47
48Options:
49  -h, --help       Show this message
50  -v, --verbose    Verbose output
51  -q, --quiet      Minimal output
52%(failfast)s%(catchbreak)s%(buffer)s
53Examples:
54  %(progName)s                               - run default set of tests
55  %(progName)s MyTestSuite                   - run suite 'MyTestSuite'
56  %(progName)s MyTestCase.testSomething      - run MyTestCase.testSomething
57  %(progName)s MyTestCase                    - run all 'test*' test methods
58                                               in MyTestCase
59"""
60
61
62
63class TestProgram(object):
64    """A command-line program that runs a set of tests; this is primarily
65       for making test modules conveniently executable.
66    """
67    USAGE = USAGE_FROM_MODULE
68
69    # defaults for testing
70    failfast = catchbreak = buffer = progName = None
71
72    def __init__(self, module='__main__', defaultTest=None, argv=None,
73                    testRunner=None, testLoader=loader.defaultTestLoader,
74                    exit=True, verbosity=1, failfast=None, catchbreak=None,
75                    buffer=None):
76        if isinstance(module, basestring):
77            self.module = __import__(module)
78            for part in module.split('.')[1:]:
79                self.module = getattr(self.module, part)
80        else:
81            self.module = module
82        if argv is None:
83            argv = sys.argv
84
85        self.exit = exit
86        self.failfast = failfast
87        self.catchbreak = catchbreak
88        self.verbosity = verbosity
89        self.buffer = buffer
90        self.defaultTest = defaultTest
91        self.testRunner = testRunner
92        self.testLoader = testLoader
93        self.progName = os.path.basename(argv[0])
94        self.parseArgs(argv)
95        self.runTests()
96
97    def usageExit(self, msg=None):
98        if msg:
99            print msg
100        usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
101                 'buffer': ''}
102        if self.failfast != False:
103            usage['failfast'] = FAILFAST
104        if self.catchbreak != False:
105            usage['catchbreak'] = CATCHBREAK
106        if self.buffer != False:
107            usage['buffer'] = BUFFEROUTPUT
108        print self.USAGE % usage
109        sys.exit(2)
110
111    def parseArgs(self, argv):
112        if len(argv) > 1 and argv[1].lower() == 'discover':
113            self._do_discovery(argv[2:])
114            return
115
116        import getopt
117        long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer']
118        try:
119            options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts)
120            for opt, value in options:
121                if opt in ('-h','-H','--help'):
122                    self.usageExit()
123                if opt in ('-q','--quiet'):
124                    self.verbosity = 0
125                if opt in ('-v','--verbose'):
126                    self.verbosity = 2
127                if opt in ('-f','--failfast'):
128                    if self.failfast is None:
129                        self.failfast = True
130                    # Should this raise an exception if -f is not valid?
131                if opt in ('-c','--catch'):
132                    if self.catchbreak is None:
133                        self.catchbreak = True
134                    # Should this raise an exception if -c is not valid?
135                if opt in ('-b','--buffer'):
136                    if self.buffer is None:
137                        self.buffer = True
138                    # Should this raise an exception if -b is not valid?
139            if len(args) == 0 and self.defaultTest is None:
140                # createTests will load tests from self.module
141                self.testNames = None
142            elif len(args) > 0:
143                self.testNames = args
144                if __name__ == '__main__':
145                    # to support python -m unittest ...
146                    self.module = None
147            else:
148                self.testNames = (self.defaultTest,)
149            self.createTests()
150        except getopt.error, msg:
151            self.usageExit(msg)
152
153    def createTests(self):
154        if self.testNames is None:
155            self.test = self.testLoader.loadTestsFromModule(self.module)
156        else:
157            self.test = self.testLoader.loadTestsFromNames(self.testNames,
158                                                           self.module)
159
160    def _do_discovery(self, argv, Loader=None):
161        if Loader is None:
162            Loader = lambda: self.testLoader
163
164        # handle command line args for test discovery
165        self.progName = '%s discover' % self.progName
166        import optparse
167        parser = optparse.OptionParser()
168        parser.prog = self.progName
169        parser.add_option('-v', '--verbose', dest='verbose', default=False,
170                          help='Verbose output', action='store_true')
171        if self.failfast != False:
172            parser.add_option('-f', '--failfast', dest='failfast', default=False,
173                              help='Stop on first fail or error',
174                              action='store_true')
175        if self.catchbreak != False:
176            parser.add_option('-c', '--catch', dest='catchbreak', default=False,
177                              help='Catch Ctrl-C and display results so far',
178                              action='store_true')
179        if self.buffer != False:
180            parser.add_option('-b', '--buffer', dest='buffer', default=False,
181                              help='Buffer stdout and stderr during tests',
182                              action='store_true')
183        parser.add_option('-s', '--start-directory', dest='start', default='.',
184                          help="Directory to start discovery ('.' default)")
185        parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
186                          help="Pattern to match tests ('test*.py' default)")
187        parser.add_option('-t', '--top-level-directory', dest='top', default=None,
188                          help='Top level directory of project (defaults to start directory)')
189
190        options, args = parser.parse_args(argv)
191        if len(args) > 3:
192            self.usageExit()
193
194        for name, value in zip(('start', 'pattern', 'top'), args):
195            setattr(options, name, value)
196
197        # only set options from the parsing here
198        # if they weren't set explicitly in the constructor
199        if self.failfast is None:
200            self.failfast = options.failfast
201        if self.catchbreak is None:
202            self.catchbreak = options.catchbreak
203        if self.buffer is None:
204            self.buffer = options.buffer
205
206        if options.verbose:
207            self.verbosity = 2
208
209        start_dir = options.start
210        pattern = options.pattern
211        top_level_dir = options.top
212
213        loader = Loader()
214        self.test = loader.discover(start_dir, pattern, top_level_dir)
215
216    def runTests(self):
217        if self.catchbreak:
218            installHandler()
219        if self.testRunner is None:
220            self.testRunner = runner.TextTestRunner
221        if isinstance(self.testRunner, (type, types.ClassType)):
222            try:
223                testRunner = self.testRunner(verbosity=self.verbosity,
224                                             failfast=self.failfast,
225                                             buffer=self.buffer)
226            except TypeError:
227                # didn't accept the verbosity, buffer or failfast arguments
228                testRunner = self.testRunner()
229        else:
230            # it is assumed to be a TestRunner instance
231            testRunner = self.testRunner
232        self.result = testRunner.run(self.test)
233        if self.exit:
234            sys.exit(not self.result.wasSuccessful())
235
236main = TestProgram
237