~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/test/test_twistd.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
 
2
# See LICENSE for details.
 
3
 
 
4
"""
 
5
Tests for L{twisted.application.app} and L{twisted.scripts.twistd}.
 
6
"""
 
7
 
 
8
import signal, inspect, errno
 
9
 
 
10
import os, sys, cPickle, StringIO
 
11
try:
 
12
    import pwd, grp
 
13
except ImportError:
 
14
    pwd = grp = None
 
15
 
 
16
from zope.interface import implements
 
17
 
 
18
from twisted.trial import unittest
 
19
 
 
20
from twisted.application import service, app
 
21
from twisted.scripts import twistd
 
22
from twisted.python import log
 
23
from twisted.python.usage import UsageError
 
24
from twisted.python.log import ILogObserver
 
25
from twisted.python.versions import Version
 
26
from twisted.python.components import Componentized
 
27
from twisted.internet.defer import Deferred
 
28
from twisted.python.fakepwd import UserDatabase
 
29
 
 
30
try:
 
31
    from twisted.python import syslog
 
32
except ImportError:
 
33
    syslog = None
 
34
 
 
35
try:
 
36
    from twisted.scripts import _twistd_unix
 
37
except ImportError:
 
38
    _twistd_unix = None
 
39
else:
 
40
    from twisted.scripts._twistd_unix import UnixApplicationRunner
 
41
    from twisted.scripts._twistd_unix import UnixAppLogger
 
42
 
 
43
try:
 
44
    import profile
 
45
except ImportError:
 
46
    profile = None
 
47
 
 
48
try:
 
49
    import hotshot
 
50
    import hotshot.stats
 
51
except (ImportError, SystemExit):
 
52
    # For some reasons, hotshot.stats seems to raise SystemExit on some
 
53
    # distributions, probably when considered non-free.  See the import of
 
54
    # this module in twisted.application.app for more details.
 
55
    hotshot = None
 
56
 
 
57
try:
 
58
    import pstats
 
59
    import cProfile
 
60
except ImportError:
 
61
    cProfile = None
 
62
 
 
63
 
 
64
 
 
65
def patchUserDatabase(patch, user, uid, group, gid):
 
66
    """
 
67
    Patch L{pwd.getpwnam} so that it behaves as though only one user exists
 
68
    and patch L{grp.getgrnam} so that it behaves as though only one group
 
69
    exists.
 
70
 
 
71
    @param patch: A function like L{TestCase.patch} which will be used to
 
72
        install the fake implementations.
 
73
 
 
74
    @type user: C{str}
 
75
    @param user: The name of the single user which will exist.
 
76
 
 
77
    @type uid: C{int}
 
78
    @param uid: The UID of the single user which will exist.
 
79
 
 
80
    @type group: C{str}
 
81
    @param group: The name of the single user which will exist.
 
82
 
 
83
    @type gid: C{int}
 
84
    @param gid: The GID of the single group which will exist.
 
85
    """
 
86
    # Try not to be an unverified fake, but try not to depend on quirks of
 
87
    # the system either (eg, run as a process with a uid and gid which
 
88
    # equal each other, and so doesn't reliably test that uid is used where
 
89
    # uid should be used and gid is used where gid should be used). -exarkun
 
90
    pwent = pwd.getpwuid(os.getuid())
 
91
    grent = grp.getgrgid(os.getgid())
 
92
 
 
93
    database = UserDatabase()
 
94
    database.addUser(
 
95
        user, pwent.pw_passwd, uid, pwent.pw_gid,
 
96
        pwent.pw_gecos, pwent.pw_dir, pwent.pw_shell)
 
97
 
 
98
    def getgrnam(name):
 
99
        result = list(grent)
 
100
        result[result.index(grent.gr_name)] = group
 
101
        result[result.index(grent.gr_gid)] = gid
 
102
        result = tuple(result)
 
103
        return {group: result}[name]
 
104
 
 
105
    patch(pwd, "getpwnam", database.getpwnam)
 
106
    patch(grp, "getgrnam", getgrnam)
 
107
 
 
108
 
 
109
 
 
110
class MockServiceMaker(object):
 
111
    """
 
112
    A non-implementation of L{twisted.application.service.IServiceMaker}.
 
113
    """
 
114
    tapname = 'ueoa'
 
115
 
 
116
    def makeService(self, options):
 
117
        """
 
118
        Take a L{usage.Options} instance and return a
 
119
        L{service.IService} provider.
 
120
        """
 
121
        self.options = options
 
122
        self.service = service.Service()
 
123
        return self.service
 
124
 
 
125
 
 
126
 
 
127
class CrippledAppLogger(app.AppLogger):
 
128
    """
 
129
    @see: CrippledApplicationRunner.
 
130
    """
 
131
 
 
132
    def start(self, application):
 
133
        pass
 
134
 
 
135
 
 
136
 
 
137
class CrippledApplicationRunner(twistd._SomeApplicationRunner):
 
138
    """
 
139
    An application runner that cripples the platform-specific runner and
 
140
    nasty side-effect-having code so that we can use it without actually
 
141
    running any environment-affecting code.
 
142
    """
 
143
    loggerFactory = CrippledAppLogger
 
144
 
 
145
    def preApplication(self):
 
146
        pass
 
147
 
 
148
 
 
149
    def postApplication(self):
 
150
        pass
 
151
 
 
152
 
 
153
 
 
154
class ServerOptionsTest(unittest.TestCase):
 
155
    """
 
156
    Non-platform-specific tests for the pltaform-specific ServerOptions class.
 
157
    """
 
158
 
 
159
    def test_postOptionsSubCommandCausesNoSave(self):
 
160
        """
 
161
        postOptions should set no_save to True when a subcommand is used.
 
162
        """
 
163
        config = twistd.ServerOptions()
 
164
        config.subCommand = 'ueoa'
 
165
        config.postOptions()
 
166
        self.assertEquals(config['no_save'], True)
 
167
 
 
168
 
 
169
    def test_postOptionsNoSubCommandSavesAsUsual(self):
 
170
        """
 
171
        If no sub command is used, postOptions should not touch no_save.
 
172
        """
 
173
        config = twistd.ServerOptions()
 
174
        config.postOptions()
 
175
        self.assertEquals(config['no_save'], False)
 
176
 
 
177
 
 
178
    def test_reportProfileDeprecation(self):
 
179
        """
 
180
        Check that the --report-profile option prints a C{DeprecationWarning}.
 
181
        """
 
182
        config = twistd.ServerOptions()
 
183
        self.assertWarns(
 
184
            DeprecationWarning, "--report-profile option is deprecated and "
 
185
            "a no-op since Twisted 8.0.", app.__file__,
 
186
            config.parseOptions, ["--report-profile", "foo"])
 
187
 
 
188
 
 
189
    def test_listAllProfilers(self):
 
190
        """
 
191
        All the profilers that can be used in L{app.AppProfiler} are listed in
 
192
        the help output.
 
193
        """
 
194
        config = twistd.ServerOptions()
 
195
        helpOutput = str(config)
 
196
        for profiler in app.AppProfiler.profilers:
 
197
            self.assertIn(profiler, helpOutput)
 
198
 
 
199
 
 
200
    def test_defaultUmask(self):
 
201
        """
 
202
        The default value for the C{umask} option is C{None}.
 
203
        """
 
204
        config = twistd.ServerOptions()
 
205
        self.assertEqual(config['umask'], None)
 
206
 
 
207
 
 
208
    def test_umask(self):
 
209
        """
 
210
        The value given for the C{umask} option is parsed as an octal integer
 
211
        literal.
 
212
        """
 
213
        config = twistd.ServerOptions()
 
214
        config.parseOptions(['--umask', '123'])
 
215
        self.assertEqual(config['umask'], 83)
 
216
        config.parseOptions(['--umask', '0123'])
 
217
        self.assertEqual(config['umask'], 83)
 
218
 
 
219
 
 
220
    def test_invalidUmask(self):
 
221
        """
 
222
        If a value is given for the C{umask} option which cannot be parsed as
 
223
        an integer, L{UsageError} is raised by L{ServerOptions.parseOptions}.
 
224
        """
 
225
        config = twistd.ServerOptions()
 
226
        self.assertRaises(UsageError, config.parseOptions, ['--umask', 'abcdef'])
 
227
 
 
228
    if _twistd_unix is None:
 
229
        msg = "twistd unix not available"
 
230
        test_defaultUmask.skip = test_umask.skip = test_invalidUmask.skip = msg
 
231
 
 
232
 
 
233
 
 
234
class TapFileTest(unittest.TestCase):
 
235
    """
 
236
    Test twistd-related functionality that requires a tap file on disk.
 
237
    """
 
238
 
 
239
    def setUp(self):
 
240
        """
 
241
        Create a trivial Application and put it in a tap file on disk.
 
242
        """
 
243
        self.tapfile = self.mktemp()
 
244
        f = file(self.tapfile, 'wb')
 
245
        cPickle.dump(service.Application("Hi!"), f)
 
246
        f.close()
 
247
 
 
248
 
 
249
    def test_createOrGetApplicationWithTapFile(self):
 
250
        """
 
251
        Ensure that the createOrGetApplication call that 'twistd -f foo.tap'
 
252
        makes will load the Application out of foo.tap.
 
253
        """
 
254
        config = twistd.ServerOptions()
 
255
        config.parseOptions(['-f', self.tapfile])
 
256
        application = CrippledApplicationRunner(config).createOrGetApplication()
 
257
        self.assertEquals(service.IService(application).name, 'Hi!')
 
258
 
 
259
 
 
260
 
 
261
class TestLoggerFactory(object):
 
262
    """
 
263
    A logger factory for L{TestApplicationRunner}.
 
264
    """
 
265
 
 
266
    def __init__(self, runner):
 
267
        self.runner = runner
 
268
 
 
269
 
 
270
    def start(self, application):
 
271
        """
 
272
        Save the logging start on the C{runner} instance.
 
273
        """
 
274
        self.runner.order.append("log")
 
275
        self.runner.hadApplicationLogObserver = hasattr(self.runner,
 
276
                                                        'application')
 
277
 
 
278
 
 
279
    def stop(self):
 
280
        """
 
281
        Don't log anything.
 
282
        """
 
283
 
 
284
 
 
285
 
 
286
class TestApplicationRunner(app.ApplicationRunner):
 
287
    """
 
288
    An ApplicationRunner which tracks the environment in which its methods are
 
289
    called.
 
290
    """
 
291
 
 
292
    def __init__(self, options):
 
293
        app.ApplicationRunner.__init__(self, options)
 
294
        self.order = []
 
295
        self.logger = TestLoggerFactory(self)
 
296
 
 
297
 
 
298
    def preApplication(self):
 
299
        self.order.append("pre")
 
300
        self.hadApplicationPreApplication = hasattr(self, 'application')
 
301
 
 
302
 
 
303
    def postApplication(self):
 
304
        self.order.append("post")
 
305
        self.hadApplicationPostApplication = hasattr(self, 'application')
 
306
 
 
307
 
 
308
 
 
309
class ApplicationRunnerTest(unittest.TestCase):
 
310
    """
 
311
    Non-platform-specific tests for the platform-specific ApplicationRunner.
 
312
    """
 
313
    def setUp(self):
 
314
        config = twistd.ServerOptions()
 
315
        self.serviceMaker = MockServiceMaker()
 
316
        # Set up a config object like it's been parsed with a subcommand
 
317
        config.loadedPlugins = {'test_command': self.serviceMaker}
 
318
        config.subOptions = object()
 
319
        config.subCommand = 'test_command'
 
320
        self.config = config
 
321
 
 
322
 
 
323
    def test_applicationRunnerGetsCorrectApplication(self):
 
324
        """
 
325
        Ensure that a twistd plugin gets used in appropriate ways: it
 
326
        is passed its Options instance, and the service it returns is
 
327
        added to the application.
 
328
        """
 
329
        arunner = CrippledApplicationRunner(self.config)
 
330
        arunner.run()
 
331
 
 
332
        self.assertIdentical(
 
333
            self.serviceMaker.options, self.config.subOptions,
 
334
            "ServiceMaker.makeService needs to be passed the correct "
 
335
            "sub Command object.")
 
336
        self.assertIdentical(
 
337
            self.serviceMaker.service,
 
338
            service.IService(arunner.application).services[0],
 
339
            "ServiceMaker.makeService's result needs to be set as a child "
 
340
            "of the Application.")
 
341
 
 
342
 
 
343
    def test_preAndPostApplication(self):
 
344
        """
 
345
        Test thet preApplication and postApplication methods are
 
346
        called by ApplicationRunner.run() when appropriate.
 
347
        """
 
348
        s = TestApplicationRunner(self.config)
 
349
        s.run()
 
350
        self.assertFalse(s.hadApplicationPreApplication)
 
351
        self.assertTrue(s.hadApplicationPostApplication)
 
352
        self.assertTrue(s.hadApplicationLogObserver)
 
353
        self.assertEquals(s.order, ["pre", "log", "post"])
 
354
 
 
355
 
 
356
    def _applicationStartsWithConfiguredID(self, argv, uid, gid):
 
357
        """
 
358
        Assert that given a particular command line, an application is started
 
359
        as a particular UID/GID.
 
360
 
 
361
        @param argv: A list of strings giving the options to parse.
 
362
        @param uid: An integer giving the expected UID.
 
363
        @param gid: An integer giving the expected GID.
 
364
        """
 
365
        self.config.parseOptions(argv)
 
366
 
 
367
        events = []
 
368
        class FakeUnixApplicationRunner(twistd._SomeApplicationRunner):
 
369
            def setupEnvironment(self, chroot, rundir, nodaemon, umask,
 
370
                                 pidfile):
 
371
                events.append('environment')
 
372
 
 
373
            def shedPrivileges(self, euid, uid, gid):
 
374
                events.append(('privileges', euid, uid, gid))
 
375
 
 
376
            def startReactor(self, reactor, oldstdout, oldstderr):
 
377
                events.append('reactor')
 
378
 
 
379
            def removePID(self, pidfile):
 
380
                pass
 
381
 
 
382
 
 
383
        class FakeService(object):
 
384
            implements(service.IService, service.IProcess)
 
385
 
 
386
            processName = None
 
387
 
 
388
            def privilegedStartService(self):
 
389
                events.append('privilegedStartService')
 
390
 
 
391
            def startService(self):
 
392
                events.append('startService')
 
393
 
 
394
            def stopService(self):
 
395
                pass
 
396
 
 
397
        runner = FakeUnixApplicationRunner(self.config)
 
398
        runner.preApplication()
 
399
        runner.application = FakeService()
 
400
        runner.postApplication()
 
401
 
 
402
        self.assertEqual(
 
403
            events,
 
404
            ['environment', 'privilegedStartService',
 
405
             ('privileges', False, uid, gid), 'startService', 'reactor'])
 
406
 
 
407
 
 
408
    def test_applicationStartsWithConfiguredNumericIDs(self):
 
409
        """
 
410
        L{postApplication} should change the UID and GID to the values
 
411
        specified as numeric strings by the configuration after running
 
412
        L{service.IService.privilegedStartService} and before running
 
413
        L{service.IService.startService}.
 
414
        """
 
415
        uid = 1234
 
416
        gid = 4321
 
417
        self._applicationStartsWithConfiguredID(
 
418
            ["--uid", str(uid), "--gid", str(gid)], uid, gid)
 
419
 
 
420
 
 
421
    def test_applicationStartsWithConfiguredNameIDs(self):
 
422
        """
 
423
        L{postApplication} should change the UID and GID to the values
 
424
        specified as user and group names by the configuration after running
 
425
        L{service.IService.privilegedStartService} and before running
 
426
        L{service.IService.startService}.
 
427
        """
 
428
        user = "foo"
 
429
        uid = 1234
 
430
        group = "bar"
 
431
        gid = 4321
 
432
        patchUserDatabase(self.patch, user, uid, group, gid)
 
433
        self._applicationStartsWithConfiguredID(
 
434
            ["--uid", user, "--gid", group], uid, gid)
 
435
 
 
436
    if getattr(os, 'setuid', None) is None:
 
437
        msg = "Platform does not support --uid/--gid twistd options."
 
438
        test_applicationStartsWithConfiguredNameIDs.skip = msg
 
439
        test_applicationStartsWithConfiguredNumericIDs.skip = msg
 
440
        del msg
 
441
 
 
442
 
 
443
    def test_startReactorRunsTheReactor(self):
 
444
        """
 
445
        L{startReactor} calls L{reactor.run}.
 
446
        """
 
447
        reactor = DummyReactor()
 
448
        runner = app.ApplicationRunner({
 
449
                "profile": False,
 
450
                "profiler": "profile",
 
451
                "debug": False})
 
452
        runner.startReactor(reactor, None, None)
 
453
        self.assertTrue(
 
454
            reactor.called, "startReactor did not call reactor.run()")
 
455
 
 
456
 
 
457
    def test_legacyApplicationRunnerGetLogObserver(self):
 
458
        """
 
459
        L{app.ApplicationRunner} subclasses can have a getLogObserver that used
 
460
        to return a log observer. This test is there to ensure that it's
 
461
        supported but it raises a warning when used.
 
462
        """
 
463
        observer = []
 
464
        self.addCleanup(log.removeObserver, observer.append)
 
465
        class GetLogObserverRunner(app.ApplicationRunner):
 
466
            def getLogObserver(self):
 
467
                return observer.append
 
468
 
 
469
            def startLogging(self, observer):
 
470
                """
 
471
                Override C{startLogging} to call L{log.addObserver} instead of
 
472
                L{log.startLoggingWithObserver}.
 
473
                """
 
474
                log.addObserver(observer)
 
475
                self.logger._initialLog()
 
476
 
 
477
            def preApplication(self):
 
478
                pass
 
479
 
 
480
            def postApplication(self):
 
481
                pass
 
482
 
 
483
            def createOrGetApplication(self):
 
484
                pass
 
485
 
 
486
        conf = twistd.ServerOptions()
 
487
        runner = GetLogObserverRunner(conf)
 
488
        self.assertWarns(DeprecationWarning,
 
489
            "Specifying a log observer with getLogObserver is "
 
490
            "deprecated. Please use a loggerFactory instead.",
 
491
            app.__file__, runner.run)
 
492
        self.assertEquals(len(observer), 3)
 
493
 
 
494
 
 
495
 
 
496
class UnixApplicationRunnerSetupEnvironmentTests(unittest.TestCase):
 
497
    """
 
498
    Tests for L{UnixApplicationRunner.setupEnvironment}.
 
499
 
 
500
    @ivar root: The root of the filesystem, or C{unset} if none has been
 
501
        specified with a call to L{os.chroot} (patched for this TestCase with
 
502
        L{UnixApplicationRunnerSetupEnvironmentTests.chroot ).
 
503
 
 
504
    @ivar cwd: The current working directory of the process, or C{unset} if
 
505
        none has been specified with a call to L{os.chdir} (patched for this
 
506
        TestCase with L{UnixApplicationRunnerSetupEnvironmentTests.chdir).
 
507
 
 
508
    @ivar mask: The current file creation mask of the process, or C{unset} if
 
509
        none has been specified with a call to L{os.umask} (patched for this
 
510
        TestCase with L{UnixApplicationRunnerSetupEnvironmentTests.umask).
 
511
 
 
512
    @ivar daemon: A boolean indicating whether daemonization has been performed
 
513
        by a call to L{_twistd_unix.daemonize} (patched for this TestCase with
 
514
        L{UnixApplicationRunnerSetupEnvironmentTests.
 
515
    """
 
516
    if _twistd_unix is None:
 
517
        skip = "twistd unix not available"
 
518
 
 
519
    unset = object()
 
520
 
 
521
    def setUp(self):
 
522
        self.root = self.unset
 
523
        self.cwd = self.unset
 
524
        self.mask = self.unset
 
525
        self.daemon = False
 
526
        self.pid = os.getpid()
 
527
        self.patch(os, 'chroot', lambda path: setattr(self, 'root', path))
 
528
        self.patch(os, 'chdir', lambda path: setattr(self, 'cwd', path))
 
529
        self.patch(os, 'umask', lambda mask: setattr(self, 'mask', mask))
 
530
        self.patch(_twistd_unix, "daemonize", self.daemonize)
 
531
        self.runner = UnixApplicationRunner({})
 
532
 
 
533
 
 
534
    def daemonize(self):
 
535
        """
 
536
        Indicate that daemonization has happened and change the PID so that the
 
537
        value written to the pidfile can be tested in the daemonization case.
 
538
        """
 
539
        self.daemon = True
 
540
        self.patch(os, 'getpid', lambda: self.pid + 1)
 
541
 
 
542
 
 
543
    def test_chroot(self):
 
544
        """
 
545
        L{UnixApplicationRunner.setupEnvironment} changes the root of the
 
546
        filesystem if passed a non-C{None} value for the C{chroot} parameter.
 
547
        """
 
548
        self.runner.setupEnvironment("/foo/bar", ".", True, None, None)
 
549
        self.assertEqual(self.root, "/foo/bar")
 
550
 
 
551
 
 
552
    def test_noChroot(self):
 
553
        """
 
554
        L{UnixApplicationRunner.setupEnvironment} does not change the root of
 
555
        the filesystem if passed C{None} for the C{chroot} parameter.
 
556
        """
 
557
        self.runner.setupEnvironment(None, ".", True, None, None)
 
558
        self.assertIdentical(self.root, self.unset)
 
559
 
 
560
 
 
561
    def test_changeWorkingDirectory(self):
 
562
        """
 
563
        L{UnixApplicationRunner.setupEnvironment} changes the working directory
 
564
        of the process to the path given for the C{rundir} parameter.
 
565
        """
 
566
        self.runner.setupEnvironment(None, "/foo/bar", True, None, None)
 
567
        self.assertEqual(self.cwd, "/foo/bar")
 
568
 
 
569
 
 
570
    def test_daemonize(self):
 
571
        """
 
572
        L{UnixApplicationRunner.setupEnvironment} daemonizes the process if
 
573
        C{False} is passed for the C{nodaemon} parameter.
 
574
        """
 
575
        self.runner.setupEnvironment(None, ".", False, None, None)
 
576
        self.assertTrue(self.daemon)
 
577
 
 
578
 
 
579
    def test_noDaemonize(self):
 
580
        """
 
581
        L{UnixApplicationRunner.setupEnvironment} does not daemonize the
 
582
        process if C{True} is passed for the C{nodaemon} parameter.
 
583
        """
 
584
        self.runner.setupEnvironment(None, ".", True, None, None)
 
585
        self.assertFalse(self.daemon)
 
586
 
 
587
 
 
588
    def test_nonDaemonPIDFile(self):
 
589
        """
 
590
        L{UnixApplicationRunner.setupEnvironment} writes the process's PID to
 
591
        the file specified by the C{pidfile} parameter.
 
592
        """
 
593
        pidfile = self.mktemp()
 
594
        self.runner.setupEnvironment(None, ".", True, None, pidfile)
 
595
        fObj = file(pidfile)
 
596
        pid = int(fObj.read())
 
597
        fObj.close()
 
598
        self.assertEqual(pid, self.pid)
 
599
 
 
600
 
 
601
    def test_daemonPIDFile(self):
 
602
        """
 
603
        L{UnixApplicationRunner.setupEnvironment} writes the daemonized
 
604
        process's PID to the file specified by the C{pidfile} parameter if
 
605
        C{nodaemon} is C{False}.
 
606
        """
 
607
        pidfile = self.mktemp()
 
608
        self.runner.setupEnvironment(None, ".", False, None, pidfile)
 
609
        fObj = file(pidfile)
 
610
        pid = int(fObj.read())
 
611
        fObj.close()
 
612
        self.assertEqual(pid, self.pid + 1)
 
613
 
 
614
 
 
615
    def test_umask(self):
 
616
        """
 
617
        L{UnixApplicationRunner.setupEnvironment} changes the process umask to
 
618
        the value specified by the C{umask} parameter.
 
619
        """
 
620
        self.runner.setupEnvironment(None, ".", False, 123, None)
 
621
        self.assertEqual(self.mask, 123)
 
622
 
 
623
 
 
624
    def test_noDaemonizeNoUmask(self):
 
625
        """
 
626
        L{UnixApplicationRunner.setupEnvironment} doesn't change the process
 
627
        umask if C{None} is passed for the C{umask} parameter and C{True} is
 
628
        passed for the C{nodaemon} parameter.
 
629
        """
 
630
        self.runner.setupEnvironment(None, ".", True, None, None)
 
631
        self.assertIdentical(self.mask, self.unset)
 
632
 
 
633
 
 
634
    def test_daemonizedNoUmask(self):
 
635
        """
 
636
        L{UnixApplicationRunner.setupEnvironment} changes the process umask to
 
637
        C{0077} if C{None} is passed for the C{umask} parameter and C{False} is
 
638
        passed for the C{nodaemon} parameter.
 
639
        """
 
640
        self.runner.setupEnvironment(None, ".", False, None, None)
 
641
        self.assertEqual(self.mask, 0077)
 
642
 
 
643
 
 
644
 
 
645
class UnixApplicationRunnerStartApplicationTests(unittest.TestCase):
 
646
    """
 
647
    Tests for L{UnixApplicationRunner.startApplication}.
 
648
    """
 
649
    if _twistd_unix is None:
 
650
        skip = "twistd unix not available"
 
651
 
 
652
    def test_setupEnvironment(self):
 
653
        """
 
654
        L{UnixApplicationRunner.startApplication} calls
 
655
        L{UnixApplicationRunner.setupEnvironment} with the chroot, rundir,
 
656
        nodaemon, umask, and pidfile parameters from the configuration it is
 
657
        constructed with.
 
658
        """
 
659
        options = twistd.ServerOptions()
 
660
        options.parseOptions([
 
661
                '--nodaemon',
 
662
                '--umask', '0070',
 
663
                '--chroot', '/foo/chroot',
 
664
                '--rundir', '/foo/rundir',
 
665
                '--pidfile', '/foo/pidfile'])
 
666
        application = service.Application("test_setupEnvironment")
 
667
        self.runner = UnixApplicationRunner(options)
 
668
 
 
669
        args = []
 
670
        def fakeSetupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile):
 
671
            args.extend((chroot, rundir, nodaemon, umask, pidfile))
 
672
 
 
673
        # Sanity check
 
674
        self.assertEqual(
 
675
            inspect.getargspec(self.runner.setupEnvironment),
 
676
            inspect.getargspec(fakeSetupEnvironment))
 
677
 
 
678
        self.patch(UnixApplicationRunner, 'setupEnvironment', fakeSetupEnvironment)
 
679
        self.patch(UnixApplicationRunner, 'shedPrivileges', lambda *a, **kw: None)
 
680
        self.patch(app, 'startApplication', lambda *a, **kw: None)
 
681
        self.runner.startApplication(application)
 
682
 
 
683
        self.assertEqual(
 
684
            args,
 
685
            ['/foo/chroot', '/foo/rundir', True, 56, '/foo/pidfile'])
 
686
 
 
687
 
 
688
 
 
689
class UnixApplicationRunnerRemovePID(unittest.TestCase):
 
690
    """
 
691
    Tests for L{UnixApplicationRunner.removePID}.
 
692
    """
 
693
    if _twistd_unix is None:
 
694
        skip = "twistd unix not available"
 
695
 
 
696
 
 
697
    def test_removePID(self):
 
698
        """
 
699
        L{UnixApplicationRunner.removePID} deletes the file the name of
 
700
        which is passed to it.
 
701
        """
 
702
        runner = UnixApplicationRunner({})
 
703
        path = self.mktemp()
 
704
        os.makedirs(path)
 
705
        pidfile = os.path.join(path, "foo.pid")
 
706
        file(pidfile, "w").close()
 
707
        runner.removePID(pidfile)
 
708
        self.assertFalse(os.path.exists(pidfile))
 
709
 
 
710
 
 
711
    def test_removePIDErrors(self):
 
712
        """
 
713
        Calling L{UnixApplicationRunner.removePID} with a non-existent filename logs
 
714
        an OSError.
 
715
        """
 
716
        runner = UnixApplicationRunner({})
 
717
        runner.removePID("fakepid")
 
718
        errors = self.flushLoggedErrors(OSError)
 
719
        self.assertEquals(len(errors), 1)
 
720
        self.assertEquals(errors[0].value.errno, errno.ENOENT)
 
721
 
 
722
 
 
723
 
 
724
class DummyReactor(object):
 
725
    """
 
726
    A dummy reactor, only providing a C{run} method and checking that it
 
727
    has been called.
 
728
 
 
729
    @ivar called: if C{run} has been called or not.
 
730
    @type called: C{bool}
 
731
    """
 
732
    called = False
 
733
 
 
734
    def run(self):
 
735
        """
 
736
        A fake run method, checking that it's been called one and only time.
 
737
        """
 
738
        if self.called:
 
739
            raise RuntimeError("Already called")
 
740
        self.called = True
 
741
 
 
742
 
 
743
 
 
744
class AppProfilingTestCase(unittest.TestCase):
 
745
    """
 
746
    Tests for L{app.AppProfiler}.
 
747
    """
 
748
 
 
749
    def test_profile(self):
 
750
        """
 
751
        L{app.ProfileRunner.run} should call the C{run} method of the reactor
 
752
        and save profile data in the specified file.
 
753
        """
 
754
        config = twistd.ServerOptions()
 
755
        config["profile"] = self.mktemp()
 
756
        config["profiler"] = "profile"
 
757
        profiler = app.AppProfiler(config)
 
758
        reactor = DummyReactor()
 
759
 
 
760
        profiler.run(reactor)
 
761
 
 
762
        self.assertTrue(reactor.called)
 
763
        data = file(config["profile"]).read()
 
764
        self.assertIn("DummyReactor.run", data)
 
765
        self.assertIn("function calls", data)
 
766
 
 
767
    if profile is None:
 
768
        test_profile.skip = "profile module not available"
 
769
 
 
770
 
 
771
    def _testStats(self, statsClass, profile):
 
772
        out = StringIO.StringIO()
 
773
 
 
774
        # Patch before creating the pstats, because pstats binds self.stream to
 
775
        # sys.stdout early in 2.5 and newer.
 
776
        stdout = self.patch(sys, 'stdout', out)
 
777
 
 
778
        # If pstats.Stats can load the data and then reformat it, then the
 
779
        # right thing probably happened.
 
780
        stats = statsClass(profile)
 
781
        stats.print_stats()
 
782
        stdout.restore()
 
783
 
 
784
        data = out.getvalue()
 
785
        self.assertIn("function calls", data)
 
786
        self.assertIn("(run)", data)
 
787
 
 
788
 
 
789
    def test_profileSaveStats(self):
 
790
        """
 
791
        With the C{savestats} option specified, L{app.ProfileRunner.run}
 
792
        should save the raw stats object instead of a summary output.
 
793
        """
 
794
        config = twistd.ServerOptions()
 
795
        config["profile"] = self.mktemp()
 
796
        config["profiler"] = "profile"
 
797
        config["savestats"] = True
 
798
        profiler = app.AppProfiler(config)
 
799
        reactor = DummyReactor()
 
800
 
 
801
        profiler.run(reactor)
 
802
 
 
803
        self.assertTrue(reactor.called)
 
804
        self._testStats(pstats.Stats, config['profile'])
 
805
 
 
806
    if profile is None:
 
807
        test_profileSaveStats.skip = "profile module not available"
 
808
 
 
809
 
 
810
    def test_withoutProfile(self):
 
811
        """
 
812
        When the C{profile} module is not present, L{app.ProfilerRunner.run}
 
813
        should raise a C{SystemExit} exception.
 
814
        """
 
815
        savedModules = sys.modules.copy()
 
816
 
 
817
        config = twistd.ServerOptions()
 
818
        config["profiler"] = "profile"
 
819
        profiler = app.AppProfiler(config)
 
820
 
 
821
        sys.modules["profile"] = None
 
822
        try:
 
823
            self.assertRaises(SystemExit, profiler.run, None)
 
824
        finally:
 
825
            sys.modules.clear()
 
826
            sys.modules.update(savedModules)
 
827
 
 
828
 
 
829
    def test_profilePrintStatsError(self):
 
830
        """
 
831
        When an error happens during the print of the stats, C{sys.stdout}
 
832
        should be restored to its initial value.
 
833
        """
 
834
        class ErroneousProfile(profile.Profile):
 
835
            def print_stats(self):
 
836
                raise RuntimeError("Boom")
 
837
        self.patch(profile, "Profile", ErroneousProfile)
 
838
 
 
839
        config = twistd.ServerOptions()
 
840
        config["profile"] = self.mktemp()
 
841
        config["profiler"] = "profile"
 
842
        profiler = app.AppProfiler(config)
 
843
        reactor = DummyReactor()
 
844
 
 
845
        oldStdout = sys.stdout
 
846
        self.assertRaises(RuntimeError, profiler.run, reactor)
 
847
        self.assertIdentical(sys.stdout, oldStdout)
 
848
 
 
849
    if profile is None:
 
850
        test_profilePrintStatsError.skip = "profile module not available"
 
851
 
 
852
 
 
853
    def test_hotshot(self):
 
854
        """
 
855
        L{app.HotshotRunner.run} should call the C{run} method of the reactor
 
856
        and save profile data in the specified file.
 
857
        """
 
858
        config = twistd.ServerOptions()
 
859
        config["profile"] = self.mktemp()
 
860
        config["profiler"] = "hotshot"
 
861
        profiler = app.AppProfiler(config)
 
862
        reactor = DummyReactor()
 
863
 
 
864
        profiler.run(reactor)
 
865
 
 
866
        self.assertTrue(reactor.called)
 
867
        data = file(config["profile"]).read()
 
868
        self.assertIn("run", data)
 
869
        self.assertIn("function calls", data)
 
870
 
 
871
    if hotshot is None:
 
872
        test_hotshot.skip = "hotshot module not available"
 
873
 
 
874
 
 
875
    def test_hotshotSaveStats(self):
 
876
        """
 
877
        With the C{savestats} option specified, L{app.HotshotRunner.run} should
 
878
        save the raw stats object instead of a summary output.
 
879
        """
 
880
        config = twistd.ServerOptions()
 
881
        config["profile"] = self.mktemp()
 
882
        config["profiler"] = "hotshot"
 
883
        config["savestats"] = True
 
884
        profiler = app.AppProfiler(config)
 
885
        reactor = DummyReactor()
 
886
 
 
887
        profiler.run(reactor)
 
888
 
 
889
        self.assertTrue(reactor.called)
 
890
        self._testStats(hotshot.stats.load, config['profile'])
 
891
 
 
892
    if hotshot is None:
 
893
        test_hotshotSaveStats.skip = "hotshot module not available"
 
894
 
 
895
 
 
896
    def test_withoutHotshot(self):
 
897
        """
 
898
        When the C{hotshot} module is not present, L{app.HotshotRunner.run}
 
899
        should raise a C{SystemExit} exception and log the C{ImportError}.
 
900
        """
 
901
        savedModules = sys.modules.copy()
 
902
        sys.modules["hotshot"] = None
 
903
 
 
904
        config = twistd.ServerOptions()
 
905
        config["profiler"] = "hotshot"
 
906
        profiler = app.AppProfiler(config)
 
907
        try:
 
908
            self.assertRaises(SystemExit, profiler.run, None)
 
909
        finally:
 
910
            sys.modules.clear()
 
911
            sys.modules.update(savedModules)
 
912
 
 
913
 
 
914
    def test_nothotshotDeprecation(self):
 
915
        """
 
916
        Check that switching on the C{nothotshot} option produces a warning and
 
917
        sets the profiler to B{profile}.
 
918
        """
 
919
        config = twistd.ServerOptions()
 
920
        config['nothotshot'] = True
 
921
        profiler = self.assertWarns(DeprecationWarning,
 
922
            "The --nothotshot option is deprecated. Please specify the "
 
923
            "profiler name using the --profiler option",
 
924
            app.__file__, app.AppProfiler, config)
 
925
        self.assertEquals(profiler.profiler, "profile")
 
926
 
 
927
 
 
928
    def test_hotshotPrintStatsError(self):
 
929
        """
 
930
        When an error happens while printing the stats, C{sys.stdout}
 
931
        should be restored to its initial value.
 
932
        """
 
933
        class ErroneousStats(pstats.Stats):
 
934
            def print_stats(self):
 
935
                raise RuntimeError("Boom")
 
936
        self.patch(pstats, "Stats", ErroneousStats)
 
937
 
 
938
        config = twistd.ServerOptions()
 
939
        config["profile"] = self.mktemp()
 
940
        config["profiler"] = "hotshot"
 
941
        profiler = app.AppProfiler(config)
 
942
        reactor = DummyReactor()
 
943
 
 
944
        oldStdout = sys.stdout
 
945
        self.assertRaises(RuntimeError, profiler.run, reactor)
 
946
        self.assertIdentical(sys.stdout, oldStdout)
 
947
 
 
948
    if hotshot is None:
 
949
        test_hotshotPrintStatsError.skip = "hotshot module not available"
 
950
 
 
951
 
 
952
    def test_cProfile(self):
 
953
        """
 
954
        L{app.CProfileRunner.run} should call the C{run} method of the
 
955
        reactor and save profile data in the specified file.
 
956
        """
 
957
        config = twistd.ServerOptions()
 
958
        config["profile"] = self.mktemp()
 
959
        config["profiler"] = "cProfile"
 
960
        profiler = app.AppProfiler(config)
 
961
        reactor = DummyReactor()
 
962
 
 
963
        profiler.run(reactor)
 
964
 
 
965
        self.assertTrue(reactor.called)
 
966
        data = file(config["profile"]).read()
 
967
        self.assertIn("run", data)
 
968
        self.assertIn("function calls", data)
 
969
 
 
970
    if cProfile is None:
 
971
        test_cProfile.skip = "cProfile module not available"
 
972
 
 
973
 
 
974
    def test_cProfileSaveStats(self):
 
975
        """
 
976
        With the C{savestats} option specified,
 
977
        L{app.CProfileRunner.run} should save the raw stats object
 
978
        instead of a summary output.
 
979
        """
 
980
        config = twistd.ServerOptions()
 
981
        config["profile"] = self.mktemp()
 
982
        config["profiler"] = "cProfile"
 
983
        config["savestats"] = True
 
984
        profiler = app.AppProfiler(config)
 
985
        reactor = DummyReactor()
 
986
 
 
987
        profiler.run(reactor)
 
988
 
 
989
        self.assertTrue(reactor.called)
 
990
        self._testStats(pstats.Stats, config['profile'])
 
991
 
 
992
    if cProfile is None:
 
993
        test_cProfileSaveStats.skip = "cProfile module not available"
 
994
 
 
995
 
 
996
    def test_withoutCProfile(self):
 
997
        """
 
998
        When the C{cProfile} module is not present,
 
999
        L{app.CProfileRunner.run} should raise a C{SystemExit}
 
1000
        exception and log the C{ImportError}.
 
1001
        """
 
1002
        savedModules = sys.modules.copy()
 
1003
        sys.modules["cProfile"] = None
 
1004
 
 
1005
        config = twistd.ServerOptions()
 
1006
        config["profiler"] = "cProfile"
 
1007
        profiler = app.AppProfiler(config)
 
1008
        try:
 
1009
            self.assertRaises(SystemExit, profiler.run, None)
 
1010
        finally:
 
1011
            sys.modules.clear()
 
1012
            sys.modules.update(savedModules)
 
1013
 
 
1014
 
 
1015
    def test_unknownProfiler(self):
 
1016
        """
 
1017
        Check that L{app.AppProfiler} raises L{SystemExit} when given an
 
1018
        unknown profiler name.
 
1019
        """
 
1020
        config = twistd.ServerOptions()
 
1021
        config["profile"] = self.mktemp()
 
1022
        config["profiler"] = "foobar"
 
1023
 
 
1024
        error = self.assertRaises(SystemExit, app.AppProfiler, config)
 
1025
        self.assertEquals(str(error), "Unsupported profiler name: foobar")
 
1026
 
 
1027
 
 
1028
    def test_defaultProfiler(self):
 
1029
        """
 
1030
        L{app.Profiler} defaults to the hotshot profiler if not specified.
 
1031
        """
 
1032
        profiler = app.AppProfiler({})
 
1033
        self.assertEquals(profiler.profiler, "hotshot")
 
1034
 
 
1035
 
 
1036
    def test_profilerNameCaseInsentive(self):
 
1037
        """
 
1038
        The case of the profiler name passed to L{app.AppProfiler} is not
 
1039
        relevant.
 
1040
        """
 
1041
        profiler = app.AppProfiler({"profiler": "HotShot"})
 
1042
        self.assertEquals(profiler.profiler, "hotshot")
 
1043
 
 
1044
 
 
1045
    def test_oldRunWithProfiler(self):
 
1046
        """
 
1047
        L{app.runWithProfiler} should print a C{DeprecationWarning} pointing
 
1048
        at L{AppProfiler}.
 
1049
        """
 
1050
        class DummyProfiler(object):
 
1051
            called = False
 
1052
            def run(self, reactor):
 
1053
                self.called = True
 
1054
        profiler = DummyProfiler()
 
1055
        self.patch(app, "AppProfiler", lambda conf: profiler)
 
1056
 
 
1057
        def runWithProfiler():
 
1058
            return app.runWithProfiler(DummyReactor(), {})
 
1059
 
 
1060
        self.assertWarns(DeprecationWarning,
 
1061
                "runWithProfiler is deprecated since Twisted 8.0. "
 
1062
                "Use ProfileRunner instead.", __file__,
 
1063
                runWithProfiler)
 
1064
        self.assertTrue(profiler.called)
 
1065
 
 
1066
 
 
1067
    def test_oldRunWithHotshot(self):
 
1068
        """
 
1069
        L{app.runWithHotshot} should print a C{DeprecationWarning} pointing
 
1070
        at L{AppProfiler}.
 
1071
        """
 
1072
        class DummyProfiler(object):
 
1073
            called = False
 
1074
            def run(self, reactor):
 
1075
                self.called = True
 
1076
        profiler = DummyProfiler()
 
1077
        self.patch(app, "AppProfiler", lambda conf: profiler)
 
1078
 
 
1079
        def runWithHotshot():
 
1080
            return app.runWithHotshot(DummyReactor(), {})
 
1081
 
 
1082
        self.assertWarns(DeprecationWarning,
 
1083
                "runWithHotshot is deprecated since Twisted 8.0. "
 
1084
                "Use HotshotRunner instead.", __file__,
 
1085
                runWithHotshot)
 
1086
        self.assertTrue(profiler.called)
 
1087
 
 
1088
 
 
1089
 
 
1090
def _patchFileLogObserver(patch):
 
1091
    """
 
1092
    Patch L{log.FileLogObserver} to record every call and keep a reference to
 
1093
    the passed log file for tests.
 
1094
 
 
1095
    @param patch: a callback for patching (usually L{unittest.TestCase.patch}).
 
1096
 
 
1097
    @return: the list that keeps track of the log files.
 
1098
    @rtype: C{list}
 
1099
    """
 
1100
    logFiles = []
 
1101
    oldFileLobObserver = log.FileLogObserver
 
1102
    def FileLogObserver(logFile):
 
1103
        logFiles.append(logFile)
 
1104
        return oldFileLobObserver(logFile)
 
1105
    patch(log, 'FileLogObserver', FileLogObserver)
 
1106
    return logFiles
 
1107
 
 
1108
 
 
1109
 
 
1110
class AppLoggerTestCase(unittest.TestCase):
 
1111
    """
 
1112
    Tests for L{app.AppLogger}.
 
1113
 
 
1114
    @ivar observers: list of observers installed during the tests.
 
1115
    @type observers: C{list}
 
1116
    """
 
1117
 
 
1118
    def setUp(self):
 
1119
        """
 
1120
        Override L{log.addObserver} so that we can trace the observers
 
1121
        installed in C{self.observers}.
 
1122
        """
 
1123
        self.observers = []
 
1124
        def startLoggingWithObserver(observer):
 
1125
            self.observers.append(observer)
 
1126
            log.addObserver(observer)
 
1127
        self.patch(log, 'startLoggingWithObserver', startLoggingWithObserver)
 
1128
 
 
1129
 
 
1130
    def tearDown(self):
 
1131
        """
 
1132
        Remove all installed observers.
 
1133
        """
 
1134
        for observer in self.observers:
 
1135
            log.removeObserver(observer)
 
1136
 
 
1137
 
 
1138
    def _checkObserver(self, logs):
 
1139
        """
 
1140
        Ensure that initial C{twistd} logs are written to the given list.
 
1141
 
 
1142
        @type logs: C{list}
 
1143
        @param logs: The list whose C{append} method was specified as the
 
1144
            initial log observer.
 
1145
        """
 
1146
        self.assertEquals(self.observers, [logs.append])
 
1147
        self.assertIn("starting up", logs[0]["message"][0])
 
1148
        self.assertIn("reactor class", logs[1]["message"][0])
 
1149
 
 
1150
 
 
1151
    def test_start(self):
 
1152
        """
 
1153
        L{app.AppLogger.start} calls L{log.addObserver}, and then writes some
 
1154
        messages about twistd and the reactor.
 
1155
        """
 
1156
        logger = app.AppLogger({})
 
1157
        observer = []
 
1158
        logger._getLogObserver = lambda: observer.append
 
1159
        logger.start(Componentized())
 
1160
        self._checkObserver(observer)
 
1161
 
 
1162
 
 
1163
    def test_startUsesApplicationLogObserver(self):
 
1164
        """
 
1165
        When the L{ILogObserver} component is available on the application,
 
1166
        that object will be used as the log observer instead of constructing a
 
1167
        new one.
 
1168
        """
 
1169
        application = Componentized()
 
1170
        logs = []
 
1171
        application.setComponent(ILogObserver, logs.append)
 
1172
        logger = app.AppLogger({})
 
1173
        logger.start(application)
 
1174
        self._checkObserver(logs)
 
1175
 
 
1176
 
 
1177
    def test_getLogObserverStdout(self):
 
1178
        """
 
1179
        When logfile is empty or set to C{-}, L{app.AppLogger._getLogObserver}
 
1180
        returns a log observer pointing at C{sys.stdout}.
 
1181
        """
 
1182
        logger = app.AppLogger({"logfile": "-"})
 
1183
        logFiles = _patchFileLogObserver(self.patch)
 
1184
 
 
1185
        observer = logger._getLogObserver()
 
1186
 
 
1187
        self.assertEquals(len(logFiles), 1)
 
1188
        self.assertIdentical(logFiles[0], sys.stdout)
 
1189
 
 
1190
        logger = app.AppLogger({"logfile": ""})
 
1191
        observer = logger._getLogObserver()
 
1192
 
 
1193
        self.assertEquals(len(logFiles), 2)
 
1194
        self.assertIdentical(logFiles[1], sys.stdout)
 
1195
 
 
1196
 
 
1197
    def test_getLogObserverFile(self):
 
1198
        """
 
1199
        When passing the C{logfile} option, L{app.AppLogger._getLogObserver}
 
1200
        returns a log observer pointing at the specified path.
 
1201
        """
 
1202
        logFiles = _patchFileLogObserver(self.patch)
 
1203
        filename = self.mktemp()
 
1204
        logger = app.AppLogger({"logfile": filename})
 
1205
 
 
1206
        observer = logger._getLogObserver()
 
1207
 
 
1208
        self.assertEquals(len(logFiles), 1)
 
1209
        self.assertEquals(logFiles[0].path,
 
1210
                          os.path.abspath(filename))
 
1211
 
 
1212
 
 
1213
    def test_stop(self):
 
1214
        """
 
1215
        L{app.AppLogger.stop} removes the observer created in C{start}, and
 
1216
        reinitialize its C{_observer} so that if C{stop} is called several
 
1217
        times it doesn't break.
 
1218
        """
 
1219
        removed = []
 
1220
        observer = object()
 
1221
        def remove(observer):
 
1222
            removed.append(observer)
 
1223
        self.patch(log, 'removeObserver', remove)
 
1224
        logger = app.AppLogger({})
 
1225
        logger._observer = observer
 
1226
        logger.stop()
 
1227
        self.assertEquals(removed, [observer])
 
1228
        logger.stop()
 
1229
        self.assertEquals(removed, [observer])
 
1230
        self.assertIdentical(logger._observer, None)
 
1231
 
 
1232
 
 
1233
 
 
1234
class UnixAppLoggerTestCase(unittest.TestCase):
 
1235
    """
 
1236
    Tests for L{UnixAppLogger}.
 
1237
 
 
1238
    @ivar signals: list of signal handlers installed.
 
1239
    @type signals: C{list}
 
1240
    """
 
1241
    if _twistd_unix is None:
 
1242
        skip = "twistd unix not available"
 
1243
 
 
1244
    def setUp(self):
 
1245
        """
 
1246
        Fake C{signal.signal} for not installing the handlers but saving them
 
1247
        in C{self.signals}.
 
1248
        """
 
1249
        self.signals = []
 
1250
        def fakeSignal(sig, f):
 
1251
            self.signals.append((sig, f))
 
1252
        self.patch(signal, "signal", fakeSignal)
 
1253
 
 
1254
 
 
1255
    def test_getLogObserverStdout(self):
 
1256
        """
 
1257
        When non-daemonized and C{logfile} is empty or set to C{-},
 
1258
        L{UnixAppLogger._getLogObserver} returns a log observer pointing at
 
1259
        C{sys.stdout}.
 
1260
        """
 
1261
        logFiles = _patchFileLogObserver(self.patch)
 
1262
 
 
1263
        logger = UnixAppLogger({"logfile": "-", "nodaemon": True})
 
1264
        observer = logger._getLogObserver()
 
1265
        self.assertEquals(len(logFiles), 1)
 
1266
        self.assertIdentical(logFiles[0], sys.stdout)
 
1267
 
 
1268
        logger = UnixAppLogger({"logfile": "", "nodaemon": True})
 
1269
        observer = logger._getLogObserver()
 
1270
        self.assertEquals(len(logFiles), 2)
 
1271
        self.assertIdentical(logFiles[1], sys.stdout)
 
1272
 
 
1273
 
 
1274
    def test_getLogObserverStdoutDaemon(self):
 
1275
        """
 
1276
        When daemonized and C{logfile} is set to C{-},
 
1277
        L{UnixAppLogger._getLogObserver} raises C{SystemExit}.
 
1278
        """
 
1279
        logger = UnixAppLogger({"logfile": "-", "nodaemon": False})
 
1280
        error = self.assertRaises(SystemExit, logger._getLogObserver)
 
1281
        self.assertEquals(str(error), "Daemons cannot log to stdout, exiting!")
 
1282
 
 
1283
 
 
1284
    def test_getLogObserverFile(self):
 
1285
        """
 
1286
        When C{logfile} contains a file name, L{app.AppLogger._getLogObserver}
 
1287
        returns a log observer pointing at the specified path, and a signal
 
1288
        handler rotating the log is installed.
 
1289
        """
 
1290
        logFiles = _patchFileLogObserver(self.patch)
 
1291
        filename = self.mktemp()
 
1292
        logger = UnixAppLogger({"logfile": filename})
 
1293
        observer = logger._getLogObserver()
 
1294
 
 
1295
        self.assertEquals(len(logFiles), 1)
 
1296
        self.assertEquals(logFiles[0].path,
 
1297
                          os.path.abspath(filename))
 
1298
 
 
1299
        self.assertEquals(len(self.signals), 1)
 
1300
        self.assertEquals(self.signals[0][0], signal.SIGUSR1)
 
1301
 
 
1302
        d = Deferred()
 
1303
        def rotate():
 
1304
            d.callback(None)
 
1305
        logFiles[0].rotate = rotate
 
1306
 
 
1307
        rotateLog = self.signals[0][1]
 
1308
        rotateLog(None, None)
 
1309
        return d
 
1310
 
 
1311
 
 
1312
    def test_getLogObserverDontOverrideSignalHandler(self):
 
1313
        """
 
1314
        If a signal handler is already installed,
 
1315
        L{UnixAppLogger._getLogObserver} doesn't override it.
 
1316
        """
 
1317
        def fakeGetSignal(sig):
 
1318
            self.assertEquals(sig, signal.SIGUSR1)
 
1319
            return object()
 
1320
        self.patch(signal, "getsignal", fakeGetSignal)
 
1321
        filename = self.mktemp()
 
1322
        logger = UnixAppLogger({"logfile": filename})
 
1323
        observer = logger._getLogObserver()
 
1324
 
 
1325
        self.assertEquals(self.signals, [])
 
1326
 
 
1327
 
 
1328
    def test_getLogObserverDefaultFile(self):
 
1329
        """
 
1330
        When daemonized and C{logfile} is empty, the observer returned by
 
1331
        L{UnixAppLogger._getLogObserver} points at C{twistd.log} in the current
 
1332
        directory.
 
1333
        """
 
1334
        logFiles = _patchFileLogObserver(self.patch)
 
1335
        logger = UnixAppLogger({"logfile": "", "nodaemon": False})
 
1336
        observer = logger._getLogObserver()
 
1337
 
 
1338
        self.assertEquals(len(logFiles), 1)
 
1339
        self.assertEquals(logFiles[0].path,
 
1340
                          os.path.abspath("twistd.log"))
 
1341
 
 
1342
 
 
1343
    def test_getLogObserverSyslog(self):
 
1344
        """
 
1345
        If C{syslog} is set to C{True}, L{UnixAppLogger._getLogObserver} starts
 
1346
        a L{syslog.SyslogObserver} with given C{prefix}.
 
1347
        """
 
1348
        class fakesyslogobserver(object):
 
1349
            def __init__(self, prefix):
 
1350
                fakesyslogobserver.prefix = prefix
 
1351
            def emit(self, eventDict):
 
1352
                pass
 
1353
        self.patch(syslog, "SyslogObserver", fakesyslogobserver)
 
1354
        logger = UnixAppLogger({"syslog": True, "prefix": "test-prefix"})
 
1355
        observer = logger._getLogObserver()
 
1356
        self.assertEquals(fakesyslogobserver.prefix, "test-prefix")
 
1357
 
 
1358
    if syslog is None:
 
1359
        test_getLogObserverSyslog.skip = "Syslog not available"
 
1360
 
 
1361
 
 
1362
 
 
1363
class DeprecationTests(unittest.TestCase):
 
1364
    """
 
1365
    Tests for deprecated features.
 
1366
    """
 
1367
 
 
1368
    def test_initialLog(self):
 
1369
        """
 
1370
        L{app.initialLog} is deprecated.
 
1371
        """
 
1372
        logs = []
 
1373
        log.addObserver(logs.append)
 
1374
        self.addCleanup(log.removeObserver, logs.append)
 
1375
        self.callDeprecated(Version("Twisted", 8, 2, 0), app.initialLog)
 
1376
        self.assertEquals(len(logs), 2)
 
1377
        self.assertIn("starting up", logs[0]["message"][0])
 
1378
        self.assertIn("reactor class", logs[1]["message"][0])