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=loader.TestLoader):
161        # handle command line args for test discovery
162        self.progName = '%s discover' % self.progName
163        import optparse
164        parser = optparse.OptionParser()
165        parser.prog = self.progName
166        parser.add_option('-v', '--verbose', dest='verbose', default=False,
167                          help='Verbose output', action='store_true')
168        if self.failfast != False:
169            parser.add_option('-f', '--failfast', dest='failfast', default=False,
170                              help='Stop on first fail or error',
171                              action='store_true')
172        if self.catchbreak != False:
173            parser.add_option('-c', '--catch', dest='catchbreak', default=False,
174                              help='Catch ctrl-C and display results so far',
175                              action='store_true')
176        if self.buffer != False:
177            parser.add_option('-b', '--buffer', dest='buffer', default=False,
178                              help='Buffer stdout and stderr during tests',
179                              action='store_true')
180        parser.add_option('-s', '--start-directory', dest='start', default='.',
181                          help="Directory to start discovery ('.' default)")
182        parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
183                          help="Pattern to match tests ('test*.py' default)")
184        parser.add_option('-t', '--top-level-directory', dest='top', default=None,
185                          help='Top level directory of project (defaults to start directory)')
186
187        options, args = parser.parse_args(argv)
188        if len(args) > 3:
189            self.usageExit()
190
191        for name, value in zip(('start', 'pattern', 'top'), args):
192            setattr(options, name, value)
193
194        # only set options from the parsing here
195        # if they weren't set explicitly in the constructor
196        if self.failfast is None:
197            self.failfast = options.failfast
198        if self.catchbreak is None:
199            self.catchbreak = options.catchbreak
200        if self.buffer is None:
201            self.buffer = options.buffer
202
203        if options.verbose:
204            self.verbosity = 2
205
206        start_dir = options.start
207        pattern = options.pattern
208        top_level_dir = options.top
209
210        loader = Loader()
211        self.test = loader.discover(start_dir, pattern, top_level_dir)
212
213    def runTests(self):
214        if self.catchbreak:
215            installHandler()
216        if self.testRunner is None:
217            self.testRunner = runner.TextTestRunner
218        if isinstance(self.testRunner, (type, types.ClassType)):
219            try:
220                testRunner = self.testRunner(verbosity=self.verbosity,
221                                             failfast=self.failfast,
222                                             buffer=self.buffer)
223            except TypeError:
224                # didn't accept the verbosity, buffer or failfast arguments
225                testRunner = self.testRunner()
226        else:
227            # it is assumed to be a TestRunner instance
228            testRunner = self.testRunner
229        self.result = testRunner.run(self.test)
230        if self.exit:
231            sys.exit(not self.result.wasSuccessful())
232
233main = TestProgram
234