1# Copyright (c) Twisted Matrix Laboratories.
2# See LICENSE for details.
3
4import StringIO
5import gc
6import re
7import sys
8import textwrap
9import types
10
11from twisted.trial import unittest
12from twisted.trial.runner import (
13    TrialRunner, TestSuite, DestructiveTestSuite, TestLoader)
14from twisted.trial._dist.disttrial import DistTrialRunner
15from twisted.scripts import trial
16from twisted.python import util
17from twisted.python.usage import UsageError
18from twisted.python.filepath import FilePath
19
20from twisted.trial.test.test_loader import testNames
21
22pyunit = __import__('unittest')
23
24
25def sibpath(filename):
26    """
27    For finding files in twisted/trial/test
28    """
29    return util.sibpath(__file__, filename)
30
31
32
33class ForceGarbageCollection(unittest.SynchronousTestCase):
34    """
35    Tests for the --force-gc option.
36    """
37
38    def setUp(self):
39        self.config = trial.Options()
40        self.log = []
41        self.patch(gc, 'collect', self.collect)
42        test = pyunit.FunctionTestCase(self.simpleTest)
43        self.test = TestSuite([test, test])
44
45
46    def simpleTest(self):
47        """
48        A simple test method that records that it was run.
49        """
50        self.log.append('test')
51
52
53    def collect(self):
54        """
55        A replacement for gc.collect that logs calls to itself.
56        """
57        self.log.append('collect')
58
59
60    def makeRunner(self):
61        """
62        Return a L{TrialRunner} object that is safe to use in tests.
63        """
64        runner = trial._makeRunner(self.config)
65        runner.stream = StringIO.StringIO()
66        return runner
67
68
69    def test_forceGc(self):
70        """
71        Passing the --force-gc option to the trial script forces the garbage
72        collector to run before and after each test.
73        """
74        self.config['force-gc'] = True
75        self.config.postOptions()
76        runner = self.makeRunner()
77        runner.run(self.test)
78        self.assertEqual(self.log, ['collect', 'test', 'collect',
79                                    'collect', 'test', 'collect'])
80
81
82    def test_unforceGc(self):
83        """
84        By default, no garbage collection is forced.
85        """
86        self.config.postOptions()
87        runner = self.makeRunner()
88        runner.run(self.test)
89        self.assertEqual(self.log, ['test', 'test'])
90
91
92
93class TestSuiteUsed(unittest.SynchronousTestCase):
94    """
95    Check the category of tests suite used by the loader.
96    """
97
98    def setUp(self):
99        """
100        Create a trial configuration object.
101        """
102        self.config = trial.Options()
103
104
105    def test_defaultSuite(self):
106        """
107        By default, the loader should use L{DestructiveTestSuite}
108        """
109        loader = trial._getLoader(self.config)
110        self.assertEqual(loader.suiteFactory, DestructiveTestSuite)
111
112
113    def test_untilFailureSuite(self):
114        """
115        The C{until-failure} configuration uses the L{TestSuite} to keep
116        instances alive across runs.
117        """
118        self.config['until-failure'] = True
119        loader = trial._getLoader(self.config)
120        self.assertEqual(loader.suiteFactory, TestSuite)
121
122
123
124class TestModuleTest(unittest.SynchronousTestCase):
125    def setUp(self):
126        self.config = trial.Options()
127
128    def tearDown(self):
129        self.config = None
130
131    def test_testNames(self):
132        """
133        Check that the testNames helper method accurately collects the
134        names of tests in suite.
135        """
136        self.assertEqual(testNames(self), [self.id()])
137
138    def assertSuitesEqual(self, test1, names):
139        loader = TestLoader()
140        names1 = testNames(test1)
141        names2 = testNames(TestSuite(map(loader.loadByName, names)))
142        names1.sort()
143        names2.sort()
144        self.assertEqual(names1, names2)
145
146    def test_baseState(self):
147        self.assertEqual(0, len(self.config['tests']))
148
149    def test_testmoduleOnModule(self):
150        """
151        Check that --testmodule loads a suite which contains the tests
152        referred to in test-case-name inside its parameter.
153        """
154        self.config.opt_testmodule(sibpath('moduletest.py'))
155        self.assertSuitesEqual(trial._getSuite(self.config),
156                               ['twisted.trial.test.test_log'])
157
158    def test_testmoduleTwice(self):
159        """
160        When the same module is specified with two --testmodule flags, it
161        should only appear once in the suite.
162        """
163        self.config.opt_testmodule(sibpath('moduletest.py'))
164        self.config.opt_testmodule(sibpath('moduletest.py'))
165        self.assertSuitesEqual(trial._getSuite(self.config),
166                               ['twisted.trial.test.test_log'])
167
168    def test_testmoduleOnSourceAndTarget(self):
169        """
170        If --testmodule is specified twice, once for module A and once for
171        a module which refers to module A, then make sure module A is only
172        added once.
173        """
174        self.config.opt_testmodule(sibpath('moduletest.py'))
175        self.config.opt_testmodule(sibpath('test_log.py'))
176        self.assertSuitesEqual(trial._getSuite(self.config),
177                               ['twisted.trial.test.test_log'])
178
179    def test_testmoduleOnSelfModule(self):
180        """
181        When given a module that refers to *itself* in the test-case-name
182        variable, check that --testmodule only adds the tests once.
183        """
184        self.config.opt_testmodule(sibpath('moduleself.py'))
185        self.assertSuitesEqual(trial._getSuite(self.config),
186                               ['twisted.trial.test.moduleself'])
187
188    def test_testmoduleOnScript(self):
189        """
190        Check that --testmodule loads tests referred to in test-case-name
191        buffer variables.
192        """
193        self.config.opt_testmodule(sibpath('scripttest.py'))
194        self.assertSuitesEqual(trial._getSuite(self.config),
195                               ['twisted.trial.test.test_log',
196                                'twisted.trial.test.test_class'])
197
198    def test_testmoduleOnNonexistentFile(self):
199        """
200        Check that --testmodule displays a meaningful error message when
201        passed a non-existent filename.
202        """
203        buffy = StringIO.StringIO()
204        stderr, sys.stderr = sys.stderr, buffy
205        filename = 'test_thisbetternoteverexist.py'
206        try:
207            self.config.opt_testmodule(filename)
208            self.assertEqual(0, len(self.config['tests']))
209            self.assertEqual("File %r doesn't exist\n" % (filename,),
210                                 buffy.getvalue())
211        finally:
212            sys.stderr = stderr
213
214    def test_testmoduleOnEmptyVars(self):
215        """
216        Check that --testmodule adds no tests to the suite for modules
217        which lack test-case-name buffer variables.
218        """
219        self.config.opt_testmodule(sibpath('novars.py'))
220        self.assertEqual(0, len(self.config['tests']))
221
222    def test_testmoduleOnModuleName(self):
223        """
224        Check that --testmodule does *not* support module names as arguments
225        and that it displays a meaningful error message.
226        """
227        buffy = StringIO.StringIO()
228        stderr, sys.stderr = sys.stderr, buffy
229        moduleName = 'twisted.trial.test.test_script'
230        try:
231            self.config.opt_testmodule(moduleName)
232            self.assertEqual(0, len(self.config['tests']))
233            self.assertEqual("File %r doesn't exist\n" % (moduleName,),
234                                 buffy.getvalue())
235        finally:
236            sys.stderr = stderr
237
238    def test_parseLocalVariable(self):
239        declaration = '-*- test-case-name: twisted.trial.test.test_tests -*-'
240        localVars = trial._parseLocalVariables(declaration)
241        self.assertEqual({'test-case-name':
242                              'twisted.trial.test.test_tests'},
243                             localVars)
244
245    def test_trailingSemicolon(self):
246        declaration = '-*- test-case-name: twisted.trial.test.test_tests; -*-'
247        localVars = trial._parseLocalVariables(declaration)
248        self.assertEqual({'test-case-name':
249                              'twisted.trial.test.test_tests'},
250                             localVars)
251
252    def test_parseLocalVariables(self):
253        declaration = ('-*- test-case-name: twisted.trial.test.test_tests; '
254                       'foo: bar -*-')
255        localVars = trial._parseLocalVariables(declaration)
256        self.assertEqual({'test-case-name':
257                              'twisted.trial.test.test_tests',
258                              'foo': 'bar'},
259                             localVars)
260
261    def test_surroundingGuff(self):
262        declaration = ('## -*- test-case-name: '
263                       'twisted.trial.test.test_tests -*- #')
264        localVars = trial._parseLocalVariables(declaration)
265        self.assertEqual({'test-case-name':
266                              'twisted.trial.test.test_tests'},
267                             localVars)
268
269    def test_invalidLine(self):
270        self.failUnlessRaises(ValueError, trial._parseLocalVariables,
271                              'foo')
272
273    def test_invalidDeclaration(self):
274        self.failUnlessRaises(ValueError, trial._parseLocalVariables,
275                              '-*- foo -*-')
276        self.failUnlessRaises(ValueError, trial._parseLocalVariables,
277                              '-*- foo: bar; qux -*-')
278        self.failUnlessRaises(ValueError, trial._parseLocalVariables,
279                              '-*- foo: bar: baz; qux: qax -*-')
280
281    def test_variablesFromFile(self):
282        localVars = trial.loadLocalVariables(sibpath('moduletest.py'))
283        self.assertEqual({'test-case-name':
284                              'twisted.trial.test.test_log'},
285                             localVars)
286
287    def test_noVariablesInFile(self):
288        localVars = trial.loadLocalVariables(sibpath('novars.py'))
289        self.assertEqual({}, localVars)
290
291    def test_variablesFromScript(self):
292        localVars = trial.loadLocalVariables(sibpath('scripttest.py'))
293        self.assertEqual(
294            {'test-case-name': ('twisted.trial.test.test_log,'
295                                'twisted.trial.test.test_class')},
296            localVars)
297
298    def test_getTestModules(self):
299        modules = trial.getTestModules(sibpath('moduletest.py'))
300        self.assertEqual(modules, ['twisted.trial.test.test_log'])
301
302    def test_getTestModules_noVars(self):
303        modules = trial.getTestModules(sibpath('novars.py'))
304        self.assertEqual(len(modules), 0)
305
306    def test_getTestModules_multiple(self):
307        modules = trial.getTestModules(sibpath('scripttest.py'))
308        self.assertEqual(set(modules),
309                             set(['twisted.trial.test.test_log',
310                                  'twisted.trial.test.test_class']))
311
312    def test_looksLikeTestModule(self):
313        for filename in ['test_script.py', 'twisted/trial/test/test_script.py']:
314            self.failUnless(trial.isTestFile(filename),
315                            "%r should be a test file" % (filename,))
316        for filename in ['twisted/trial/test/moduletest.py',
317                         sibpath('scripttest.py'), sibpath('test_foo.bat')]:
318            self.failIf(trial.isTestFile(filename),
319                        "%r should *not* be a test file" % (filename,))
320
321
322class WithoutModuleTests(unittest.SynchronousTestCase):
323    """
324    Test the C{without-module} flag.
325    """
326
327    def setUp(self):
328        """
329        Create a L{trial.Options} object to be used in the tests, and save
330        C{sys.modules}.
331        """
332        self.config = trial.Options()
333        self.savedModules = dict(sys.modules)
334
335
336    def tearDown(self):
337        """
338        Restore C{sys.modules}.
339        """
340        for module in ('imaplib', 'smtplib'):
341            if module in self.savedModules:
342                sys.modules[module] = self.savedModules[module]
343            else:
344                sys.modules.pop(module, None)
345
346
347    def _checkSMTP(self):
348        """
349        Try to import the C{smtplib} module, and return it.
350        """
351        import smtplib
352        return smtplib
353
354
355    def _checkIMAP(self):
356        """
357        Try to import the C{imaplib} module, and return it.
358        """
359        import imaplib
360        return imaplib
361
362
363    def test_disableOneModule(self):
364        """
365        Check that after disabling a module, it can't be imported anymore.
366        """
367        self.config.parseOptions(["--without-module", "smtplib"])
368        self.assertRaises(ImportError, self._checkSMTP)
369        # Restore sys.modules
370        del sys.modules["smtplib"]
371        # Then the function should succeed
372        self.assertIsInstance(self._checkSMTP(), types.ModuleType)
373
374
375    def test_disableMultipleModules(self):
376        """
377        Check that several modules can be disabled at once.
378        """
379        self.config.parseOptions(["--without-module", "smtplib,imaplib"])
380        self.assertRaises(ImportError, self._checkSMTP)
381        self.assertRaises(ImportError, self._checkIMAP)
382        # Restore sys.modules
383        del sys.modules["smtplib"]
384        del sys.modules["imaplib"]
385        # Then the functions should succeed
386        self.assertIsInstance(self._checkSMTP(), types.ModuleType)
387        self.assertIsInstance(self._checkIMAP(), types.ModuleType)
388
389
390    def test_disableAlreadyImportedModule(self):
391        """
392        Disabling an already imported module should produce a warning.
393        """
394        self.assertIsInstance(self._checkSMTP(), types.ModuleType)
395        self.assertWarns(RuntimeWarning,
396                "Module 'smtplib' already imported, disabling anyway.",
397                trial.__file__,
398                self.config.parseOptions, ["--without-module", "smtplib"])
399        self.assertRaises(ImportError, self._checkSMTP)
400
401
402
403class CoverageTests(unittest.SynchronousTestCase):
404    """
405    Tests for the I{coverage} option.
406    """
407    if getattr(sys, 'gettrace', None) is None:
408        skip = (
409            "Cannot test trace hook installation without inspection API.")
410
411    def setUp(self):
412        """
413        Arrange for the current trace hook to be restored when the
414        test is complete.
415        """
416        self.addCleanup(sys.settrace, sys.gettrace())
417
418
419    def test_tracerInstalled(self):
420        """
421        L{trial.Options} handles C{"--coverage"} by installing a trace
422        hook to record coverage information.
423        """
424        options = trial.Options()
425        options.parseOptions(["--coverage"])
426        self.assertEqual(sys.gettrace(), options.tracer.globaltrace)
427
428
429    def test_coverdirDefault(self):
430        """
431        L{trial.Options.coverdir} returns a L{FilePath} based on the default
432        for the I{temp-directory} option if that option is not specified.
433        """
434        options = trial.Options()
435        self.assertEqual(
436            options.coverdir(),
437            FilePath(".").descendant([options["temp-directory"], "coverage"]))
438
439
440    def test_coverdirOverridden(self):
441        """
442        If a value is specified for the I{temp-directory} option,
443        L{trial.Options.coverdir} returns a child of that path.
444        """
445        path = self.mktemp()
446        options = trial.Options()
447        options.parseOptions(["--temp-directory", path])
448        self.assertEqual(
449            options.coverdir(), FilePath(path).child("coverage"))
450
451
452
453class OptionsTestCase(unittest.TestCase):
454    """
455    Tests for L{trial.Options}.
456    """
457
458    def setUp(self):
459        """
460        Build an L{Options} object to be used in the tests.
461        """
462        self.options = trial.Options()
463
464
465    def test_getWorkerArguments(self):
466        """
467        C{_getWorkerArguments} discards options like C{random} as they only
468        matter in the manager, and forwards options like C{recursionlimit} or
469        C{disablegc}.
470        """
471        self.addCleanup(sys.setrecursionlimit, sys.getrecursionlimit())
472        if gc.isenabled():
473            self.addCleanup(gc.enable)
474
475        self.options.parseOptions(["--recursionlimit", "2000", "--random",
476                                   "4", "--disablegc"])
477        args = self.options._getWorkerArguments()
478        self.assertIn("--disablegc", args)
479        args.remove("--disablegc")
480        self.assertEqual(["--recursionlimit", "2000"], args)
481
482
483    def test_jobsConflictWithDebug(self):
484        """
485        C{parseOptions} raises a C{UsageError} when C{--debug} is passed along
486        C{--jobs} as it's not supported yet.
487
488        @see: U{http://twistedmatrix.com/trac/ticket/5825}
489        """
490        error = self.assertRaises(
491            UsageError, self.options.parseOptions, ["--jobs", "4", "--debug"])
492        self.assertEqual("You can't specify --debug when using --jobs",
493                         str(error))
494
495
496    def test_jobsConflictWithProfile(self):
497        """
498        C{parseOptions} raises a C{UsageError} when C{--profile} is passed
499        along C{--jobs} as it's not supported yet.
500
501        @see: U{http://twistedmatrix.com/trac/ticket/5827}
502        """
503        error = self.assertRaises(
504            UsageError, self.options.parseOptions,
505            ["--jobs", "4", "--profile"])
506        self.assertEqual("You can't specify --profile when using --jobs",
507                         str(error))
508
509
510    def test_jobsConflictWithDebugStackTraces(self):
511        """
512        C{parseOptions} raises a C{UsageError} when C{--debug-stacktraces} is
513        passed along C{--jobs} as it's not supported yet.
514
515        @see: U{http://twistedmatrix.com/trac/ticket/5826}
516        """
517        error = self.assertRaises(
518            UsageError, self.options.parseOptions,
519            ["--jobs", "4", "--debug-stacktraces"])
520        self.assertEqual(
521            "You can't specify --debug-stacktraces when using --jobs",
522            str(error))
523
524
525    def test_jobsConflictWithExitFirst(self):
526        """
527        C{parseOptions} raises a C{UsageError} when C{--exitfirst} is passed
528        along C{--jobs} as it's not supported yet.
529
530        @see: U{http://twistedmatrix.com/trac/ticket/6436}
531        """
532        error = self.assertRaises(
533            UsageError, self.options.parseOptions,
534            ["--jobs", "4", "--exitfirst"])
535        self.assertEqual(
536            "You can't specify --exitfirst when using --jobs",
537            str(error))
538
539
540    def test_orderConflictWithRandom(self):
541        """
542        C{parseOptions} raises a C{UsageError} when C{--order} is passed along
543        with C{--random}.
544        """
545        error = self.assertRaises(
546            UsageError,
547            self.options.parseOptions,
548            ["--order", "alphabetical", "--random", "1234"])
549        self.assertEqual("You can't specify --random when using --order",
550                         str(error))
551
552
553
554class MakeRunnerTestCase(unittest.TestCase):
555    """
556    Tests for the L{_makeRunner} helper.
557    """
558
559    def setUp(self):
560        self.options = trial.Options()
561
562    def test_jobs(self):
563        """
564        L{_makeRunner} returns a L{DistTrialRunner} instance when the C{--jobs}
565        option is passed, and passes the C{workerNumber} and C{workerArguments}
566        parameters to it.
567        """
568        self.options.parseOptions(["--jobs", "4", "--force-gc"])
569        runner = trial._makeRunner(self.options)
570        self.assertIsInstance(runner, DistTrialRunner)
571        self.assertEqual(4, runner._workerNumber)
572        self.assertEqual(["--force-gc"], runner._workerArguments)
573
574
575    def test_dryRunWithJobs(self):
576        """
577        L{_makeRunner} returns a L{TrialRunner} instance in C{DRY_RUN} mode
578        when the C{--dry-run} option is passed, even if C{--jobs} is set.
579        """
580        self.options.parseOptions(["--jobs", "4", "--dry-run"])
581        runner = trial._makeRunner(self.options)
582        self.assertIsInstance(runner, TrialRunner)
583        self.assertEqual(TrialRunner.DRY_RUN, runner.mode)
584
585
586    def test_DebuggerNotFound(self):
587        namedAny = trial.reflect.namedAny
588
589        def namedAnyExceptdoNotFind(fqn):
590            if fqn == "doNotFind":
591                raise trial.reflect.ModuleNotFound(fqn)
592            return namedAny(fqn)
593
594        self.patch(trial.reflect, "namedAny", namedAnyExceptdoNotFind)
595
596        options = trial.Options()
597        options.parseOptions(["--debug", "--debugger", "doNotFind"])
598
599        self.assertRaises(trial._DebuggerNotFound, trial._makeRunner, options)
600
601
602    def test_exitfirst(self):
603        """
604        Passing C{--exitfirst} wraps the reporter with a
605        L{reporter._ExitWrapper} that stops on any non-success.
606        """
607        self.options.parseOptions(["--exitfirst"])
608        runner = trial._makeRunner(self.options)
609        self.assertTrue(runner._exitFirst)
610
611
612class TestRun(unittest.TestCase):
613    """
614    Tests for the L{run} function.
615    """
616
617    def setUp(self):
618        # don't re-parse cmdline options, because if --reactor was passed to
619        # the test run trial will try to restart the (already running) reactor
620        self.patch(trial.Options, "parseOptions", lambda self: None)
621
622
623    def test_debuggerNotFound(self):
624        """
625        When a debugger is not found, an error message is printed to the user.
626
627        """
628
629        def _makeRunner(*args, **kwargs):
630            raise trial._DebuggerNotFound('foo')
631        self.patch(trial, "_makeRunner", _makeRunner)
632
633        try:
634            trial.run()
635        except SystemExit as e:
636            self.assertIn("foo", str(e))
637        else:
638            self.fail("Should have exited due to non-existent debugger!")
639
640
641
642class TestArgumentOrderTests(unittest.TestCase):
643    """
644    Tests for the order-preserving behavior on provided command-line tests.
645    """
646
647    def setUp(self):
648        self.config = trial.Options()
649        self.loader = TestLoader()
650
651
652    def test_preserveArgumentOrder(self):
653        """
654        Multiple tests passed on the command line are not reordered.
655        """
656        tests = [
657            "twisted.trial.test.test_tests",
658            "twisted.trial.test.test_assertions",
659            "twisted.trial.test.test_deferreds",
660            ]
661        self.config.parseOptions(tests)
662
663        suite = trial._getSuite(self.config)
664        names = testNames(suite)
665
666        expectedSuite = TestSuite(map(self.loader.loadByName, tests))
667        expectedNames = testNames(expectedSuite)
668
669        self.assertEqual(names, expectedNames)
670
671
672
673class OrderTests(unittest.TestCase):
674    """
675    Tests for the --order option.
676    """
677    def setUp(self):
678        self.config = trial.Options()
679
680
681    def test_alphabetical(self):
682        """
683        --order=alphabetical causes trial to run tests alphabetically within
684        each test case.
685        """
686        self.config.parseOptions([
687            "--order", "alphabetical",
688            "twisted.trial.test.ordertests.FooTest"])
689
690        loader = trial._getLoader(self.config)
691        suite = loader.loadByNames(self.config['tests'])
692
693        self.assertEqual(
694            testNames(suite), [
695            'twisted.trial.test.ordertests.FooTest.test_first',
696            'twisted.trial.test.ordertests.FooTest.test_fourth',
697            'twisted.trial.test.ordertests.FooTest.test_second',
698            'twisted.trial.test.ordertests.FooTest.test_third'])
699
700
701    def test_alphabeticalModule(self):
702        """
703        --order=alphabetical causes trial to run test classes within a given
704        module alphabetically.
705        """
706        self.config.parseOptions([
707            "--order", "alphabetical", "twisted.trial.test.ordertests"])
708        loader = trial._getLoader(self.config)
709        suite = loader.loadByNames(self.config['tests'])
710
711        self.assertEqual(
712            testNames(suite), [
713            'twisted.trial.test.ordertests.BarTest.test_bar',
714            'twisted.trial.test.ordertests.BazTest.test_baz',
715            'twisted.trial.test.ordertests.FooTest.test_first',
716            'twisted.trial.test.ordertests.FooTest.test_fourth',
717            'twisted.trial.test.ordertests.FooTest.test_second',
718            'twisted.trial.test.ordertests.FooTest.test_third'])
719
720
721    def test_alphabeticalPackage(self):
722        """
723        --order=alphabetical causes trial to run test modules within a given
724        package alphabetically, with tests within each module alphabetized.
725        """
726        self.config.parseOptions([
727            "--order", "alphabetical", "twisted.trial.test"])
728        loader = trial._getLoader(self.config)
729        suite = loader.loadByNames(self.config['tests'])
730
731        names = testNames(suite)
732        self.assertTrue(names, msg="Failed to load any tests!")
733        self.assertEqual(names, sorted(names))
734
735
736    def test_toptobottom(self):
737        """
738        --order=toptobottom causes trial to run test methods within a given
739        test case from top to bottom as they are defined in the body of the
740        class.
741        """
742        self.config.parseOptions([
743            "--order", "toptobottom",
744            "twisted.trial.test.ordertests.FooTest"])
745
746        loader = trial._getLoader(self.config)
747        suite = loader.loadByNames(self.config['tests'])
748
749        self.assertEqual(
750            testNames(suite), [
751            'twisted.trial.test.ordertests.FooTest.test_first',
752            'twisted.trial.test.ordertests.FooTest.test_second',
753            'twisted.trial.test.ordertests.FooTest.test_third',
754            'twisted.trial.test.ordertests.FooTest.test_fourth'])
755
756
757    def test_toptobottomModule(self):
758        """
759        --order=toptobottom causes trial to run test classes within a given
760        module from top to bottom as they are defined in the module's source.
761        """
762        self.config.parseOptions([
763            "--order", "toptobottom", "twisted.trial.test.ordertests"])
764        loader = trial._getLoader(self.config)
765        suite = loader.loadByNames(self.config['tests'])
766
767        self.assertEqual(
768            testNames(suite), [
769            'twisted.trial.test.ordertests.FooTest.test_first',
770            'twisted.trial.test.ordertests.FooTest.test_second',
771            'twisted.trial.test.ordertests.FooTest.test_third',
772            'twisted.trial.test.ordertests.FooTest.test_fourth',
773            'twisted.trial.test.ordertests.BazTest.test_baz',
774            'twisted.trial.test.ordertests.BarTest.test_bar'])
775
776
777    def test_toptobottomPackage(self):
778        """
779        --order=toptobottom causes trial to run test modules within a given
780        package alphabetically, with tests within each module run top to
781        bottom.
782        """
783        self.config.parseOptions([
784            "--order", "toptobottom", "twisted.trial.test"])
785        loader = trial._getLoader(self.config)
786        suite = loader.loadByNames(self.config['tests'])
787
788        names = testNames(suite)
789        # twisted.trial.test.test_module, so split and key on the first 4 to
790        # get stable alphabetical sort on those
791        self.assertEqual(
792            names, sorted(names, key=lambda name : name.split(".")[:4]),
793        )
794
795
796    def test_toptobottomMissingSource(self):
797        """
798        --order=toptobottom detects the source line of methods from modules
799        whose source file is missing.
800        """
801        tempdir = self.mktemp().encode('utf-8')
802        package = FilePath(tempdir).child(b'twisted_toptobottom_temp')
803        package.makedirs()
804        package.child(b'__init__.py').setContent(b'')
805        package.child(b'test_missing.py').setContent(textwrap.dedent(b'''
806        from twisted.trial.unittest import TestCase
807        class TestMissing(TestCase):
808            def test_second(self): pass
809            def test_third(self): pass
810            def test_fourth(self): pass
811            def test_first(self): pass
812        '''))
813        pathEntry = package.parent().path.decode('utf-8')
814        sys.path.insert(0, pathEntry)
815        self.addCleanup(sys.path.remove, pathEntry)
816        from twisted_toptobottom_temp import test_missing
817        self.addCleanup(sys.modules.pop, 'twisted_toptobottom_temp')
818        self.addCleanup(sys.modules.pop, test_missing.__name__)
819        package.child(b'test_missing.py').remove()
820
821        self.config.parseOptions([
822            "--order", "toptobottom", "twisted.trial.test.ordertests"])
823        loader = trial._getLoader(self.config)
824        suite = loader.loadModule(test_missing)
825
826        self.assertEqual(
827            testNames(suite), [
828            'twisted_toptobottom_temp.test_missing.TestMissing.test_second',
829            'twisted_toptobottom_temp.test_missing.TestMissing.test_third',
830            'twisted_toptobottom_temp.test_missing.TestMissing.test_fourth',
831            'twisted_toptobottom_temp.test_missing.TestMissing.test_first'])
832
833
834    def test_unknownOrder(self):
835        """
836        An unknown order passed to --order raises a L{UsageError}.
837        """
838
839        self.assertRaises(
840            UsageError, self.config.parseOptions, ["--order", "I don't exist"])
841
842
843
844class HelpOrderTests(unittest.TestCase):
845    """
846    Tests for the --help-orders flag.
847    """
848    def test_help_ordersPrintsSynopsisAndQuits(self):
849        """
850        --help-orders prints each of the available orders and then exits.
851        """
852        self.patch(sys, "stdout", StringIO.StringIO())
853
854        exc = self.assertRaises(
855            SystemExit, trial.Options().parseOptions, ["--help-orders"])
856        self.assertEqual(exc.code, 0)
857
858        output = sys.stdout.getvalue()
859
860        msg = "%r with its description not properly described in %r"
861        for orderName, (orderDesc, _) in trial._runOrders.items():
862            match = re.search(
863                "%s.*%s" % (re.escape(orderName), re.escape(orderDesc)),
864                output,
865            )
866
867            self.assertTrue(match, msg=msg % (orderName, output))
868