/
usr
/
lib
/
python3
/
dist-packages
/
twisted
/
test
/
/usr/lib/python3/dist-packages/twisted/test
mkdir
upload
Name
Size
Mode
Actions
__pycache__/
-
0755
rm
cert.pem.no_trailing_newline
1414
0644
edit
dl
rm
crash_test_dummy.py
549
0644
edit
dl
rm
ignore_test_failure.py
31459
0644
edit
dl
rm
iosim.py
18163
0644
edit
dl
rm
key.pem.no_trailing_newline
1707
0644
edit
dl
rm
mock_win32process.py
1299
0644
edit
dl
rm
myrebuilder1.py
151
0644
edit
dl
rm
myrebuilder2.py
151
0644
edit
dl
rm
plugin_basic.py
901
0644
edit
dl
rm
plugin_extra1.py
392
0644
edit
dl
rm
plugin_extra2.py
550
0644
edit
dl
rm
process_cmdline.py
123
0644
edit
dl
rm
process_echoer.py
214
0644
edit
dl
rm
process_fds.py
984
0644
edit
dl
rm
process_getargv.py
234
0644
edit
dl
rm
process_getenv.py
268
0644
edit
dl
rm
process_linger.py
297
0644
edit
dl
rm
process_reader.py
178
0644
edit
dl
rm
process_signal.py
220
0644
edit
dl
rm
process_stdinreader.py
739
0644
edit
dl
rm
process_tester.py
779
0644
edit
dl
rm
process_tty.py
130
0644
edit
dl
rm
process_twisted.py
1182
0644
edit
dl
rm
proto_helpers.py
1368
0644
edit
dl
rm
reflect_helper_IE.py
60
0644
edit
dl
rm
reflect_helper_VE.py
81
0644
edit
dl
rm
reflect_helper_ZDE.py
48
0644
edit
dl
rm
server.pem
4444
0644
edit
dl
rm
ssl_helpers.py
1650
0644
edit
dl
rm
stdio_test_consumer.py
1168
0644
edit
dl
rm
stdio_test_halfclose.py
2047
0644
edit
dl
rm
stdio_test_hostpeer.py
1089
0644
edit
dl
rm
stdio_test_lastwrite.py
1154
0644
edit
dl
rm
stdio_test_loseconn.py
1583
0644
edit
dl
rm
stdio_test_producer.py
1484
0644
edit
dl
rm
stdio_test_write.py
902
0644
edit
dl
rm
stdio_test_writeseq.py
894
0644
edit
dl
rm
testutils.py
5178
0644
edit
dl
rm
test_abstract.py
3435
0644
edit
dl
rm
test_adbapi.py
26175
0644
edit
dl
rm
test_amp.py
110514
0644
edit
dl
rm
test_application.py
34140
0644
edit
dl
rm
test_compat.py
18172
0644
edit
dl
rm
test_context.py
1464
0644
edit
dl
rm
test_cooperator.py
21339
0644
edit
dl
rm
test_defer.py
124782
0644
edit
dl
rm
test_defgen.py
10634
0644
edit
dl
rm
test_dict.py
1528
0644
edit
dl
rm
test_dirdbm.py
6851
0644
edit
dl
rm
test_error.py
8926
0644
edit
dl
rm
test_factories.py
4567
0644
edit
dl
rm
test_fdesc.py
7452
0644
edit
dl
rm
test_finger.py
1894
0644
edit
dl
rm
test_formmethod.py
3961
0644
edit
dl
rm
test_ftp.py
130002
0644
edit
dl
rm
test_ftp_options.py
2688
0644
edit
dl
rm
test_htb.py
3154
0644
edit
dl
rm
test_ident.py
6718
0644
edit
dl
rm
test_internet.py
46469
0644
edit
dl
rm
test_iosim.py
8908
0644
edit
dl
rm
test_iutils.py
13632
0644
edit
dl
rm
test_lockfile.py
15336
0644
edit
dl
rm
test_log.py
36073
0644
edit
dl
rm
test_logfile.py
17891
0644
edit
dl
rm
test_loopback.py
14321
0644
edit
dl
rm
test_main.py
2166
0644
edit
dl
rm
test_memcache.py
25284
0644
edit
dl
rm
test_modules.py
17428
0644
edit
dl
rm
test_monkey.py
5519
0644
edit
dl
rm
test_paths.py
70972
0644
edit
dl
rm
test_pcp.py
12528
0644
edit
dl
rm
test_persisted.py
13407
0644
edit
dl
rm
test_plugin.py
25384
0644
edit
dl
rm
test_policies.py
33061
0644
edit
dl
rm
test_postfix.py
4427
0644
edit
dl
rm
test_process.py
87203
0644
edit
dl
rm
test_protocols.py
7334
0644
edit
dl
rm
test_randbytes.py
3270
0644
edit
dl
rm
test_rebuild.py
7323
0644
edit
dl
rm
test_reflect.py
24469
0644
edit
dl
rm
test_roots.py
1643
0644
edit
dl
rm
test_shortcut.py
1962
0644
edit
dl
rm
test_sip.py
25498
0644
edit
dl
rm
test_sob.py
5667
0644
edit
dl
rm
test_socks.py
17496
0644
edit
dl
rm
test_ssl.py
23301
0644
edit
dl
rm
test_sslverify.py
116627
0644
edit
dl
rm
test_stateful.py
2015
0644
edit
dl
rm
test_stdio.py
12730
0644
edit
dl
rm
test_strerror.py
5247
0644
edit
dl
rm
test_strports.py
1705
0644
edit
dl
rm
test_task.py
46348
0644
edit
dl
rm
test_tcp.py
65813
0644
edit
dl
rm
test_tcp_internals.py
13036
0644
edit
dl
rm
test_text.py
6367
0644
edit
dl
rm
test_threadable.py
3338
0644
edit
dl
rm
test_threadpool.py
21956
0644
edit
dl
rm
test_threads.py
13211
0644
edit
dl
rm
test_tpfile.py
1545
0644
edit
dl
rm
test_twistd.py
73768
0644
edit
dl
rm
test_twisted.py
6277
0644
edit
dl
rm
test_udp.py
25012
0644
edit
dl
rm
test_unix.py
13579
0644
edit
dl
rm
test_usage.py
23311
0644
edit
dl
rm
__init__.py
475
0644
edit
dl
rm
Edit:
/usr/lib/python3/dist-packages/twisted/test/test_twistd.py
(73768B)
# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Tests for L{twisted.application.app} and L{twisted.scripts.twistd}. """ import errno import inspect import os import pickle import signal import sys try: import grp as _grp import pwd as _pwd except ImportError: pwd = None grp = None else: pwd = _pwd grp = _grp from io import StringIO from unittest import skipIf from zope.interface import implementer from zope.interface.verify import verifyObject from twisted import internet, logger, plugin from twisted.application import app, reactors, service from twisted.application.service import IServiceMaker from twisted.internet.base import ReactorBase from twisted.internet.defer import Deferred from twisted.internet.interfaces import IReactorDaemonize, _ISupportsExitSignalCapturing from twisted.internet.test.modulehelpers import AlternateReactor from twisted.logger import ILogObserver, globalLogBeginner, globalLogPublisher from twisted.python import util from twisted.python.components import Componentized from twisted.python.fakepwd import UserDatabase from twisted.python.log import ILogObserver as LegacyILogObserver, textFromEventDict from twisted.python.reflect import requireModule from twisted.python.runtime import platformType from twisted.python.usage import UsageError from twisted.scripts import twistd from twisted.test.proto_helpers import MemoryReactor from twisted.test.test_process import MockOS from twisted.trial.unittest import TestCase _twistd_unix = requireModule("twistd.scripts._twistd_unix") if _twistd_unix: from twisted.scripts._twistd_unix import ( UnixApplicationRunner, UnixAppLogger, checkPID, ) syslog = requireModule("twistd.python.syslog") profile = requireModule("profile") pstats = requireModule("pstats") cProfile = requireModule("cProfile") def patchUserDatabase(patch, user, uid, group, gid): """ Patch L{pwd.getpwnam} so that it behaves as though only one user exists and patch L{grp.getgrnam} so that it behaves as though only one group exists. @param patch: A function like L{TestCase.patch} which will be used to install the fake implementations. @type user: C{str} @param user: The name of the single user which will exist. @type uid: C{int} @param uid: The UID of the single user which will exist. @type group: C{str} @param group: The name of the single user which will exist. @type gid: C{int} @param gid: The GID of the single group which will exist. """ # Try not to be an unverified fake, but try not to depend on quirks of # the system either (eg, run as a process with a uid and gid which # equal each other, and so doesn't reliably test that uid is used where # uid should be used and gid is used where gid should be used). -exarkun pwent = pwd.getpwuid(os.getuid()) grent = grp.getgrgid(os.getgid()) database = UserDatabase() database.addUser( user, pwent.pw_passwd, uid, gid, pwent.pw_gecos, pwent.pw_dir, pwent.pw_shell ) def getgrnam(name): result = list(grent) result[result.index(grent.gr_name)] = group result[result.index(grent.gr_gid)] = gid result = tuple(result) return {group: result}[name] patch(pwd, "getpwnam", database.getpwnam) patch(grp, "getgrnam", getgrnam) patch(pwd, "getpwuid", database.getpwuid) class MockServiceMaker: """ A non-implementation of L{twisted.application.service.IServiceMaker}. """ tapname = "ueoa" def makeService(self, options): """ Take a L{usage.Options} instance and return a L{service.IService} provider. """ self.options = options self.service = service.Service() return self.service class CrippledAppLogger(app.AppLogger): """ @see: CrippledApplicationRunner. """ def start(self, application): pass class CrippledApplicationRunner(twistd._SomeApplicationRunner): """ An application runner that cripples the platform-specific runner and nasty side-effect-having code so that we can use it without actually running any environment-affecting code. """ loggerFactory = CrippledAppLogger def preApplication(self): pass def postApplication(self): pass class ServerOptionsTests(TestCase): """ Non-platform-specific tests for the platform-specific ServerOptions class. """ def test_subCommands(self): """ subCommands is built from IServiceMaker plugins, and is sorted alphabetically. """ class FakePlugin: def __init__(self, name): self.tapname = name self._options = "options for " + name self.description = "description of " + name def options(self): return self._options apple = FakePlugin("apple") banana = FakePlugin("banana") coconut = FakePlugin("coconut") donut = FakePlugin("donut") def getPlugins(interface): self.assertEqual(interface, IServiceMaker) yield coconut yield banana yield donut yield apple config = twistd.ServerOptions() self.assertEqual(config._getPlugins, plugin.getPlugins) config._getPlugins = getPlugins # "subCommands is a list of 4-tuples of (command name, command # shortcut, parser class, documentation)." subCommands = config.subCommands expectedOrder = [apple, banana, coconut, donut] for subCommand, expectedCommand in zip(subCommands, expectedOrder): name, shortcut, parserClass, documentation = subCommand self.assertEqual(name, expectedCommand.tapname) self.assertIsNone(shortcut) self.assertEqual(parserClass(), expectedCommand._options), self.assertEqual(documentation, expectedCommand.description) def test_sortedReactorHelp(self): """ Reactor names are listed alphabetically by I{--help-reactors}. """ class FakeReactorInstaller: def __init__(self, name): self.shortName = "name of " + name self.description = "description of " + name self.moduleName = "twisted.internet.default" apple = FakeReactorInstaller("apple") banana = FakeReactorInstaller("banana") coconut = FakeReactorInstaller("coconut") donut = FakeReactorInstaller("donut") def getReactorTypes(): yield coconut yield banana yield donut yield apple config = twistd.ServerOptions() self.assertEqual(config._getReactorTypes, reactors.getReactorTypes) config._getReactorTypes = getReactorTypes config.messageOutput = StringIO() self.assertRaises(SystemExit, config.parseOptions, ["--help-reactors"]) helpOutput = config.messageOutput.getvalue() indexes = [] for reactor in apple, banana, coconut, donut: def getIndex(s): self.assertIn(s, helpOutput) indexes.append(helpOutput.index(s)) getIndex(reactor.shortName) getIndex(reactor.description) self.assertEqual( indexes, sorted(indexes), "reactor descriptions were not in alphabetical order: {!r}".format( helpOutput ), ) def test_postOptionsSubCommandCausesNoSave(self): """ postOptions should set no_save to True when a subcommand is used. """ config = twistd.ServerOptions() config.subCommand = "ueoa" config.postOptions() self.assertTrue(config["no_save"]) def test_postOptionsNoSubCommandSavesAsUsual(self): """ If no sub command is used, postOptions should not touch no_save. """ config = twistd.ServerOptions() config.postOptions() self.assertFalse(config["no_save"]) def test_listAllProfilers(self): """ All the profilers that can be used in L{app.AppProfiler} are listed in the help output. """ config = twistd.ServerOptions() helpOutput = str(config) for profiler in app.AppProfiler.profilers: self.assertIn(profiler, helpOutput) @skipIf(not _twistd_unix, "twistd unix not available") def test_defaultUmask(self): """ The default value for the C{umask} option is L{None}. """ config = twistd.ServerOptions() self.assertIsNone(config["umask"]) @skipIf(not _twistd_unix, "twistd unix not available") def test_umask(self): """ The value given for the C{umask} option is parsed as an octal integer literal. """ config = twistd.ServerOptions() config.parseOptions(["--umask", "123"]) self.assertEqual(config["umask"], 83) config.parseOptions(["--umask", "0123"]) self.assertEqual(config["umask"], 83) @skipIf(not _twistd_unix, "twistd unix not available") def test_invalidUmask(self): """ If a value is given for the C{umask} option which cannot be parsed as an integer, L{UsageError} is raised by L{ServerOptions.parseOptions}. """ config = twistd.ServerOptions() self.assertRaises(UsageError, config.parseOptions, ["--umask", "abcdef"]) def test_unimportableConfiguredLogObserver(self): """ C{--logger} with an unimportable module raises a L{UsageError}. """ config = twistd.ServerOptions() e = self.assertRaises( UsageError, config.parseOptions, ["--logger", "no.such.module.I.hope"] ) self.assertTrue( e.args[0].startswith( "Logger 'no.such.module.I.hope' could not be imported: " "'no.such.module.I.hope' does not name an object" ) ) self.assertNotIn("\n", e.args[0]) def test_badAttributeWithConfiguredLogObserver(self): """ C{--logger} with a non-existent object raises a L{UsageError}. """ config = twistd.ServerOptions() e = self.assertRaises( UsageError, config.parseOptions, ["--logger", "twisted.test.test_twistd.FOOBAR"], ) self.assertTrue( e.args[0].startswith( "Logger 'twisted.test.test_twistd.FOOBAR' could not be " "imported: module 'twisted.test.test_twistd' " "has no attribute 'FOOBAR'" ) ) self.assertNotIn("\n", e.args[0]) def test_version(self): """ C{--version} prints the version. """ from twisted import copyright if platformType == "win32": name = "(the Twisted Windows runner)" else: name = "(the Twisted daemon)" expectedOutput = "twistd {} {}\n{}\n".format( name, copyright.version, copyright.copyright ) stdout = StringIO() config = twistd.ServerOptions(stdout=stdout) e = self.assertRaises(SystemExit, config.parseOptions, ["--version"]) self.assertIs(e.code, None) self.assertEqual(stdout.getvalue(), expectedOutput) def test_printSubCommandForUsageError(self): """ Command is printed when an invalid option is requested. """ stdout = StringIO() config = twistd.ServerOptions(stdout=stdout) self.assertRaises(UsageError, config.parseOptions, ["web --foo"]) @skipIf(not _twistd_unix, "twistd unix not available") class CheckPIDTests(TestCase): """ Tests for L{checkPID}. """ def test_notExists(self): """ Nonexistent PID file is not an error. """ self.patch(os.path, "exists", lambda _: False) checkPID("non-existent PID file") def test_nonNumeric(self): """ Non-numeric content in a PID file causes a system exit. """ pidfile = self.mktemp() with open(pidfile, "w") as f: f.write("non-numeric") e = self.assertRaises(SystemExit, checkPID, pidfile) self.assertIn("non-numeric value", e.code) def test_anotherRunning(self): """ Another running twistd server causes a system exit. """ pidfile = self.mktemp() with open(pidfile, "w") as f: f.write("42") def kill(pid, sig): pass self.patch(os, "kill", kill) e = self.assertRaises(SystemExit, checkPID, pidfile) self.assertIn("Another twistd server", e.code) def test_stale(self): """ Stale PID file is removed without causing a system exit. """ pidfile = self.mktemp() with open(pidfile, "w") as f: f.write(str(os.getpid() + 1)) def kill(pid, sig): raise OSError(errno.ESRCH, "fake") self.patch(os, "kill", kill) checkPID(pidfile) self.assertFalse(os.path.exists(pidfile)) def test_unexpectedOSError(self): """ An unexpected L{OSError} when checking the validity of a PID in a C{pidfile} terminates the process via L{SystemExit}. """ pidfile = self.mktemp() with open(pidfile, "w") as f: f.write("3581") def kill(pid, sig): raise OSError(errno.EBADF, "fake") self.patch(os, "kill", kill) e = self.assertRaises(SystemExit, checkPID, pidfile) self.assertIsNot(e.code, None) self.assertTrue(e.args[0].startswith("Can't check status of PID")) class TapFileTests(TestCase): """ Test twistd-related functionality that requires a tap file on disk. """ def setUp(self): """ Create a trivial Application and put it in a tap file on disk. """ self.tapfile = self.mktemp() with open(self.tapfile, "wb") as f: pickle.dump(service.Application("Hi!"), f) def test_createOrGetApplicationWithTapFile(self): """ Ensure that the createOrGetApplication call that 'twistd -f foo.tap' makes will load the Application out of foo.tap. """ config = twistd.ServerOptions() config.parseOptions(["-f", self.tapfile]) application = CrippledApplicationRunner(config).createOrGetApplication() self.assertEqual(service.IService(application).name, "Hi!") class TestLoggerFactory: """ A logger factory for L{TestApplicationRunner}. """ def __init__(self, runner): self.runner = runner def start(self, application): """ Save the logging start on the C{runner} instance. """ self.runner.order.append("log") self.runner.hadApplicationLogObserver = hasattr(self.runner, "application") def stop(self): """ Don't log anything. """ class TestApplicationRunner(app.ApplicationRunner): """ An ApplicationRunner which tracks the environment in which its methods are called. """ def __init__(self, options): app.ApplicationRunner.__init__(self, options) self.order = [] self.logger = TestLoggerFactory(self) def preApplication(self): self.order.append("pre") self.hadApplicationPreApplication = hasattr(self, "application") def postApplication(self): self.order.append("post") self.hadApplicationPostApplication = hasattr(self, "application") class ApplicationRunnerTests(TestCase): """ Non-platform-specific tests for the platform-specific ApplicationRunner. """ def setUp(self): config = twistd.ServerOptions() self.serviceMaker = MockServiceMaker() # Set up a config object like it's been parsed with a subcommand config.loadedPlugins = {"test_command": self.serviceMaker} config.subOptions = object() config.subCommand = "test_command" self.config = config def test_applicationRunnerGetsCorrectApplication(self): """ Ensure that a twistd plugin gets used in appropriate ways: it is passed its Options instance, and the service it returns is added to the application. """ arunner = CrippledApplicationRunner(self.config) arunner.run() self.assertIs( self.serviceMaker.options, self.config.subOptions, "ServiceMaker.makeService needs to be passed the correct " "sub Command object.", ) self.assertIs( self.serviceMaker.service, service.IService(arunner.application).services[0], "ServiceMaker.makeService's result needs to be set as a child " "of the Application.", ) def test_preAndPostApplication(self): """ Test thet preApplication and postApplication methods are called by ApplicationRunner.run() when appropriate. """ s = TestApplicationRunner(self.config) s.run() self.assertFalse(s.hadApplicationPreApplication) self.assertTrue(s.hadApplicationPostApplication) self.assertTrue(s.hadApplicationLogObserver) self.assertEqual(s.order, ["pre", "log", "post"]) def _applicationStartsWithConfiguredID(self, argv, uid, gid): """ Assert that given a particular command line, an application is started as a particular UID/GID. @param argv: A list of strings giving the options to parse. @param uid: An integer giving the expected UID. @param gid: An integer giving the expected GID. """ self.config.parseOptions(argv) events = [] class FakeUnixApplicationRunner(twistd._SomeApplicationRunner): def setupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile): events.append("environment") def shedPrivileges(self, euid, uid, gid): events.append(("privileges", euid, uid, gid)) def startReactor(self, reactor, oldstdout, oldstderr): events.append("reactor") def removePID(self, pidfile): pass @implementer(service.IService, service.IProcess) class FakeService: parent = None running = None name = None processName = None uid = None gid = None def setName(self, name): pass def setServiceParent(self, parent): pass def disownServiceParent(self): pass def privilegedStartService(self): events.append("privilegedStartService") def startService(self): events.append("startService") def stopService(self): pass application = FakeService() verifyObject(service.IService, application) verifyObject(service.IProcess, application) runner = FakeUnixApplicationRunner(self.config) runner.preApplication() runner.application = application runner.postApplication() self.assertEqual( events, [ "environment", "privilegedStartService", ("privileges", False, uid, gid), "startService", "reactor", ], ) @skipIf( not getattr(os, "setuid", None), "Platform does not support --uid/--gid twistd options.", ) def test_applicationStartsWithConfiguredNumericIDs(self): """ L{postApplication} should change the UID and GID to the values specified as numeric strings by the configuration after running L{service.IService.privilegedStartService} and before running L{service.IService.startService}. """ uid = 1234 gid = 4321 self._applicationStartsWithConfiguredID( ["--uid", str(uid), "--gid", str(gid)], uid, gid ) @skipIf( not getattr(os, "setuid", None), "Platform does not support --uid/--gid twistd options.", ) def test_applicationStartsWithConfiguredNameIDs(self): """ L{postApplication} should change the UID and GID to the values specified as user and group names by the configuration after running L{service.IService.privilegedStartService} and before running L{service.IService.startService}. """ user = "foo" uid = 1234 group = "bar" gid = 4321 patchUserDatabase(self.patch, user, uid, group, gid) self._applicationStartsWithConfiguredID( ["--uid", user, "--gid", group], uid, gid ) def test_startReactorRunsTheReactor(self): """ L{startReactor} calls L{reactor.run}. """ reactor = DummyReactor() runner = app.ApplicationRunner( {"profile": False, "profiler": "profile", "debug": False} ) runner.startReactor(reactor, None, None) self.assertTrue(reactor.called, "startReactor did not call reactor.run()") def test_applicationRunnerChoosesReactorIfNone(self): """ L{ApplicationRunner} chooses a reactor if none is specified. """ reactor = DummyReactor() self.patch(internet, "reactor", reactor) runner = app.ApplicationRunner( {"profile": False, "profiler": "profile", "debug": False} ) runner.startReactor(None, None, None) self.assertTrue(reactor.called) def test_applicationRunnerCapturesSignal(self): """ If the reactor exits with a signal, the application runner caches the signal. """ class DummyReactorWithSignal(ReactorBase): """ A dummy reactor, providing a C{run} method, and setting the _exitSignal attribute to a nonzero value. """ def installWaker(self): """ Dummy method, does nothing. """ def run(self): """ A fake run method setting _exitSignal to a nonzero value """ self._exitSignal = 2 reactor = DummyReactorWithSignal() runner = app.ApplicationRunner( {"profile": False, "profiler": "profile", "debug": False} ) runner.startReactor(reactor, None, None) self.assertEquals(2, runner._exitSignal) def test_applicationRunnerIgnoresNoSignal(self): """ The runner sets its _exitSignal instance attribute to None if the reactor does not implement L{_ISupportsExitSignalCapturing}. """ class DummyReactorWithExitSignalAttribute: """ A dummy reactor, providing a C{run} method, and setting the _exitSignal attribute to a nonzero value. """ def installWaker(self): """ Dummy method, does nothing. """ def run(self): """ A fake run method setting _exitSignal to a nonzero value that should be ignored. """ self._exitSignal = 2 reactor = DummyReactorWithExitSignalAttribute() runner = app.ApplicationRunner( {"profile": False, "profiler": "profile", "debug": False} ) runner.startReactor(reactor, None, None) self.assertEquals(None, runner._exitSignal) @skipIf(not _twistd_unix, "twistd unix not available") class UnixApplicationRunnerSetupEnvironmentTests(TestCase): """ Tests for L{UnixApplicationRunner.setupEnvironment}. @ivar root: The root of the filesystem, or C{unset} if none has been specified with a call to L{os.chroot} (patched for this TestCase with L{UnixApplicationRunnerSetupEnvironmentTests.chroot}). @ivar cwd: The current working directory of the process, or C{unset} if none has been specified with a call to L{os.chdir} (patched for this TestCase with L{UnixApplicationRunnerSetupEnvironmentTests.chdir}). @ivar mask: The current file creation mask of the process, or C{unset} if none has been specified with a call to L{os.umask} (patched for this TestCase with L{UnixApplicationRunnerSetupEnvironmentTests.umask}). @ivar daemon: A boolean indicating whether daemonization has been performed by a call to L{_twistd_unix.daemonize} (patched for this TestCase with L{UnixApplicationRunnerSetupEnvironmentTests}. """ unset = object() def setUp(self): self.root = self.unset self.cwd = self.unset self.mask = self.unset self.daemon = False self.pid = os.getpid() self.patch(os, "chroot", lambda path: setattr(self, "root", path)) self.patch(os, "chdir", lambda path: setattr(self, "cwd", path)) self.patch(os, "umask", lambda mask: setattr(self, "mask", mask)) self.runner = UnixApplicationRunner(twistd.ServerOptions()) self.runner.daemonize = self.daemonize def daemonize(self, reactor): """ Indicate that daemonization has happened and change the PID so that the value written to the pidfile can be tested in the daemonization case. """ self.daemon = True self.patch(os, "getpid", lambda: self.pid + 1) def test_chroot(self): """ L{UnixApplicationRunner.setupEnvironment} changes the root of the filesystem if passed a non-L{None} value for the C{chroot} parameter. """ self.runner.setupEnvironment("/foo/bar", ".", True, None, None) self.assertEqual(self.root, "/foo/bar") def test_noChroot(self): """ L{UnixApplicationRunner.setupEnvironment} does not change the root of the filesystem if passed L{None} for the C{chroot} parameter. """ self.runner.setupEnvironment(None, ".", True, None, None) self.assertIs(self.root, self.unset) def test_changeWorkingDirectory(self): """ L{UnixApplicationRunner.setupEnvironment} changes the working directory of the process to the path given for the C{rundir} parameter. """ self.runner.setupEnvironment(None, "/foo/bar", True, None, None) self.assertEqual(self.cwd, "/foo/bar") def test_daemonize(self): """ L{UnixApplicationRunner.setupEnvironment} daemonizes the process if C{False} is passed for the C{nodaemon} parameter. """ with AlternateReactor(FakeDaemonizingReactor()): self.runner.setupEnvironment(None, ".", False, None, None) self.assertTrue(self.daemon) def test_noDaemonize(self): """ L{UnixApplicationRunner.setupEnvironment} does not daemonize the process if C{True} is passed for the C{nodaemon} parameter. """ self.runner.setupEnvironment(None, ".", True, None, None) self.assertFalse(self.daemon) def test_nonDaemonPIDFile(self): """ L{UnixApplicationRunner.setupEnvironment} writes the process's PID to the file specified by the C{pidfile} parameter. """ pidfile = self.mktemp() self.runner.setupEnvironment(None, ".", True, None, pidfile) with open(pidfile, "rb") as f: pid = int(f.read()) self.assertEqual(pid, self.pid) def test_daemonPIDFile(self): """ L{UnixApplicationRunner.setupEnvironment} writes the daemonized process's PID to the file specified by the C{pidfile} parameter if C{nodaemon} is C{False}. """ pidfile = self.mktemp() with AlternateReactor(FakeDaemonizingReactor()): self.runner.setupEnvironment(None, ".", False, None, pidfile) with open(pidfile, "rb") as f: pid = int(f.read()) self.assertEqual(pid, self.pid + 1) def test_umask(self): """ L{UnixApplicationRunner.setupEnvironment} changes the process umask to the value specified by the C{umask} parameter. """ with AlternateReactor(FakeDaemonizingReactor()): self.runner.setupEnvironment(None, ".", False, 123, None) self.assertEqual(self.mask, 123) def test_noDaemonizeNoUmask(self): """ L{UnixApplicationRunner.setupEnvironment} doesn't change the process umask if L{None} is passed for the C{umask} parameter and C{True} is passed for the C{nodaemon} parameter. """ self.runner.setupEnvironment(None, ".", True, None, None) self.assertIs(self.mask, self.unset) def test_daemonizedNoUmask(self): """ L{UnixApplicationRunner.setupEnvironment} changes the process umask to C{0077} if L{None} is passed for the C{umask} parameter and C{False} is passed for the C{nodaemon} parameter. """ with AlternateReactor(FakeDaemonizingReactor()): self.runner.setupEnvironment(None, ".", False, None, None) self.assertEqual(self.mask, 0o077) @skipIf(not _twistd_unix, "twistd unix not available") class UnixApplicationRunnerStartApplicationTests(TestCase): """ Tests for L{UnixApplicationRunner.startApplication}. """ def test_setupEnvironment(self): """ L{UnixApplicationRunner.startApplication} calls L{UnixApplicationRunner.setupEnvironment} with the chroot, rundir, nodaemon, umask, and pidfile parameters from the configuration it is constructed with. """ options = twistd.ServerOptions() options.parseOptions( [ "--nodaemon", "--umask", "0070", "--chroot", "/foo/chroot", "--rundir", "/foo/rundir", "--pidfile", "/foo/pidfile", ] ) application = service.Application("test_setupEnvironment") self.runner = UnixApplicationRunner(options) args = [] def fakeSetupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile): args.extend((chroot, rundir, nodaemon, umask, pidfile)) # Sanity check setupEnvironmentParameters = inspect.signature( self.runner.setupEnvironment ).parameters fakeSetupEnvironmentParameters = inspect.signature( fakeSetupEnvironment ).parameters # inspect.signature() does not return "self" in the signature of # a class method, so we need to omit it when comparing the # the signature of a plain method fakeSetupEnvironmentParameters = fakeSetupEnvironmentParameters.copy() fakeSetupEnvironmentParameters.pop("self") self.assertEqual(setupEnvironmentParameters, fakeSetupEnvironmentParameters) self.patch(UnixApplicationRunner, "setupEnvironment", fakeSetupEnvironment) self.patch(UnixApplicationRunner, "shedPrivileges", lambda *a, **kw: None) self.patch(app, "startApplication", lambda *a, **kw: None) self.runner.startApplication(application) self.assertEqual(args, ["/foo/chroot", "/foo/rundir", True, 56, "/foo/pidfile"]) def test_shedPrivileges(self): """ L{UnixApplicationRunner.shedPrivileges} switches the user ID of the process. """ def switchUIDPass(uid, gid, euid): self.assertEqual(uid, 200) self.assertEqual(gid, 54) self.assertEqual(euid, 35) self.patch(_twistd_unix, "switchUID", switchUIDPass) runner = UnixApplicationRunner({}) runner.shedPrivileges(35, 200, 54) def test_shedPrivilegesError(self): """ An unexpected L{OSError} when calling L{twisted.scripts._twistd_unix.shedPrivileges} terminates the process via L{SystemExit}. """ def switchUIDFail(uid, gid, euid): raise OSError(errno.EBADF, "fake") runner = UnixApplicationRunner({}) self.patch(_twistd_unix, "switchUID", switchUIDFail) exc = self.assertRaises(SystemExit, runner.shedPrivileges, 35, 200, None) self.assertEqual(exc.code, 1) def _setUID(self, wantedUser, wantedUid, wantedGroup, wantedGid): """ Common code for tests which try to pass the the UID to L{UnixApplicationRunner}. """ patchUserDatabase(self.patch, wantedUser, wantedUid, wantedGroup, wantedGid) def initgroups(uid, gid): self.assertEqual(uid, wantedUid) self.assertEqual(gid, wantedGid) def setuid(uid): self.assertEqual(uid, wantedUid) def setgid(gid): self.assertEqual(gid, wantedGid) self.patch(util, "initgroups", initgroups) self.patch(os, "setuid", setuid) self.patch(os, "setgid", setgid) options = twistd.ServerOptions() options.parseOptions(["--nodaemon", "--uid", str(wantedUid)]) application = service.Application("test_setupEnvironment") self.runner = UnixApplicationRunner(options) runner = UnixApplicationRunner(options) runner.startApplication(application) def test_setUidWithoutGid(self): """ Starting an application with L{UnixApplicationRunner} configured with a UID and no GUID will result in the GUID being set to the default GUID for that UID. """ self._setUID("foo", 5151, "bar", 4242) def test_setUidSameAsCurrentUid(self): """ If the specified UID is the same as the current UID of the process, then a warning is displayed. """ currentUid = os.getuid() self._setUID("morefoo", currentUid, "morebar", 4343) warningsShown = self.flushWarnings() self.assertEqual(1, len(warningsShown)) expectedWarning = ( "tried to drop privileges and setuid {} but uid is already {}; " "should we be root? Continuing.".format(currentUid, currentUid) ) self.assertEqual(expectedWarning, warningsShown[0]["message"]) @skipIf(not _twistd_unix, "twistd unix not available") class UnixApplicationRunnerRemovePIDTests(TestCase): """ Tests for L{UnixApplicationRunner.removePID}. """ def test_removePID(self): """ L{UnixApplicationRunner.removePID} deletes the file the name of which is passed to it. """ runner = UnixApplicationRunner({}) path = self.mktemp() os.makedirs(path) pidfile = os.path.join(path, "foo.pid") open(pidfile, "w").close() runner.removePID(pidfile) self.assertFalse(os.path.exists(pidfile)) def test_removePIDErrors(self): """ Calling L{UnixApplicationRunner.removePID} with a non-existent filename logs an OSError. """ runner = UnixApplicationRunner({}) runner.removePID("fakepid") errors = self.flushLoggedErrors(OSError) self.assertEqual(len(errors), 1) self.assertEqual(errors[0].value.errno, errno.ENOENT) class FakeNonDaemonizingReactor: """ A dummy reactor, providing C{beforeDaemonize} and C{afterDaemonize} methods, but not announcing this, and logging whether the methods have been called. @ivar _beforeDaemonizeCalled: if C{beforeDaemonize} has been called or not. @type _beforeDaemonizeCalled: C{bool} @ivar _afterDaemonizeCalled: if C{afterDaemonize} has been called or not. @type _afterDaemonizeCalled: C{bool} """ def __init__(self): self._beforeDaemonizeCalled = False self._afterDaemonizeCalled = False def beforeDaemonize(self): self._beforeDaemonizeCalled = True def afterDaemonize(self): self._afterDaemonizeCalled = True def addSystemEventTrigger(self, *args, **kw): """ Skip event registration. """ @implementer(IReactorDaemonize) class FakeDaemonizingReactor(FakeNonDaemonizingReactor): """ A dummy reactor, providing C{beforeDaemonize} and C{afterDaemonize} methods, announcing this, and logging whether the methods have been called. """ class DummyReactor: """ A dummy reactor, only providing a C{run} method and checking that it has been called. @ivar called: if C{run} has been called or not. @type called: C{bool} """ called = False def run(self): """ A fake run method, checking that it's been called one and only time. """ if self.called: raise RuntimeError("Already called") self.called = True class AppProfilingTests(TestCase): """ Tests for L{app.AppProfiler}. """ @skipIf(not profile, "profile module not available") def test_profile(self): """ L{app.ProfileRunner.run} should call the C{run} method of the reactor and save profile data in the specified file. """ config = twistd.ServerOptions() config["profile"] = self.mktemp() config["profiler"] = "profile" profiler = app.AppProfiler(config) reactor = DummyReactor() profiler.run(reactor) self.assertTrue(reactor.called) with open(config["profile"]) as f: data = f.read() self.assertIn("DummyReactor.run", data) self.assertIn("function calls", data) def _testStats(self, statsClass, profile): out = StringIO() # Patch before creating the pstats, because pstats binds self.stream to # sys.stdout early in 2.5 and newer. stdout = self.patch(sys, "stdout", out) # If pstats.Stats can load the data and then reformat it, then the # right thing probably happened. stats = statsClass(profile) stats.print_stats() stdout.restore() data = out.getvalue() self.assertIn("function calls", data) self.assertIn("(run)", data) @skipIf(not profile, "profile module not available") def test_profileSaveStats(self): """ With the C{savestats} option specified, L{app.ProfileRunner.run} should save the raw stats object instead of a summary output. """ config = twistd.ServerOptions() config["profile"] = self.mktemp() config["profiler"] = "profile" config["savestats"] = True profiler = app.AppProfiler(config) reactor = DummyReactor() profiler.run(reactor) self.assertTrue(reactor.called) self._testStats(pstats.Stats, config["profile"]) def test_withoutProfile(self): """ When the C{profile} module is not present, L{app.ProfilerRunner.run} should raise a C{SystemExit} exception. """ savedModules = sys.modules.copy() config = twistd.ServerOptions() config["profiler"] = "profile" profiler = app.AppProfiler(config) sys.modules["profile"] = None try: self.assertRaises(SystemExit, profiler.run, None) finally: sys.modules.clear() sys.modules.update(savedModules) @skipIf(not profile, "profile module not available") def test_profilePrintStatsError(self): """ When an error happens during the print of the stats, C{sys.stdout} should be restored to its initial value. """ class ErroneousProfile(profile.Profile): def print_stats(self): raise RuntimeError("Boom") self.patch(profile, "Profile", ErroneousProfile) config = twistd.ServerOptions() config["profile"] = self.mktemp() config["profiler"] = "profile" profiler = app.AppProfiler(config) reactor = DummyReactor() oldStdout = sys.stdout self.assertRaises(RuntimeError, profiler.run, reactor) self.assertIs(sys.stdout, oldStdout) @skipIf(not cProfile, "cProfile module not available") def test_cProfile(self): """ L{app.CProfileRunner.run} should call the C{run} method of the reactor and save profile data in the specified file. """ config = twistd.ServerOptions() config["profile"] = self.mktemp() config["profiler"] = "cProfile" profiler = app.AppProfiler(config) reactor = DummyReactor() profiler.run(reactor) self.assertTrue(reactor.called) with open(config["profile"]) as f: data = f.read() self.assertIn("run", data) self.assertIn("function calls", data) @skipIf(not cProfile, "cProfile module not available") def test_cProfileSaveStats(self): """ With the C{savestats} option specified, L{app.CProfileRunner.run} should save the raw stats object instead of a summary output. """ config = twistd.ServerOptions() config["profile"] = self.mktemp() config["profiler"] = "cProfile" config["savestats"] = True profiler = app.AppProfiler(config) reactor = DummyReactor() profiler.run(reactor) self.assertTrue(reactor.called) self._testStats(pstats.Stats, config["profile"]) def test_withoutCProfile(self): """ When the C{cProfile} module is not present, L{app.CProfileRunner.run} should raise a C{SystemExit} exception and log the C{ImportError}. """ savedModules = sys.modules.copy() sys.modules["cProfile"] = None config = twistd.ServerOptions() config["profiler"] = "cProfile" profiler = app.AppProfiler(config) try: self.assertRaises(SystemExit, profiler.run, None) finally: sys.modules.clear() sys.modules.update(savedModules) def test_unknownProfiler(self): """ Check that L{app.AppProfiler} raises L{SystemExit} when given an unknown profiler name. """ config = twistd.ServerOptions() config["profile"] = self.mktemp() config["profiler"] = "foobar" error = self.assertRaises(SystemExit, app.AppProfiler, config) self.assertEqual(str(error), "Unsupported profiler name: foobar") def test_defaultProfiler(self): """ L{app.Profiler} defaults to the cprofile profiler if not specified. """ profiler = app.AppProfiler({}) self.assertEqual(profiler.profiler, "cprofile") def test_profilerNameCaseInsentive(self): """ The case of the profiler name passed to L{app.AppProfiler} is not relevant. """ profiler = app.AppProfiler({"profiler": "CprOfile"}) self.assertEqual(profiler.profiler, "cprofile") def _patchTextFileLogObserver(patch): """ Patch L{logger.textFileLogObserver} to record every call and keep a reference to the passed log file for tests. @param patch: a callback for patching (usually L{TestCase.patch}). @return: the list that keeps track of the log files. @rtype: C{list} """ logFiles = [] oldFileLogObserver = logger.textFileLogObserver def observer(logFile, *args, **kwargs): logFiles.append(logFile) return oldFileLogObserver(logFile, *args, **kwargs) patch(logger, "textFileLogObserver", observer) return logFiles def _setupSyslog(testCase): """ Make fake syslog, and return list to which prefix and then log messages will be appended if it is used. """ logMessages = [] class fakesyslogobserver: def __init__(self, prefix): logMessages.append(prefix) def emit(self, eventDict): logMessages.append(eventDict) testCase.patch(syslog, "SyslogObserver", fakesyslogobserver) return logMessages class AppLoggerTests(TestCase): """ Tests for L{app.AppLogger}. @ivar observers: list of observers installed during the tests. @type observers: C{list} """ def setUp(self): """ Override L{globaLogBeginner.beginLoggingTo} so that we can trace the observers installed in C{self.observers}. """ self.observers = [] def beginLoggingTo(observers): for observer in observers: self.observers.append(observer) globalLogPublisher.addObserver(observer) self.patch(globalLogBeginner, "beginLoggingTo", beginLoggingTo) def tearDown(self): """ Remove all installed observers. """ for observer in self.observers: globalLogPublisher.removeObserver(observer) def _makeObserver(self): """ Make a new observer which captures all logs sent to it. @return: An observer that stores all logs sent to it. @rtype: Callable that implements L{ILogObserver}. """ @implementer(ILogObserver) class TestObserver: _logs = [] def __call__(self, event): self._logs.append(event) return TestObserver() def _checkObserver(self, observer): """ Ensure that initial C{twistd} logs are written to logs. @param observer: The observer made by L{self._makeObserver). """ self.assertEqual(self.observers, [observer]) self.assertIn("starting up", observer._logs[0]["log_format"]) self.assertIn("reactor class", observer._logs[1]["log_format"]) def test_start(self): """ L{app.AppLogger.start} calls L{globalLogBeginner.addObserver}, and then writes some messages about twistd and the reactor. """ logger = app.AppLogger({}) observer = self._makeObserver() logger._getLogObserver = lambda: observer logger.start(Componentized()) self._checkObserver(observer) def test_startUsesApplicationLogObserver(self): """ When the L{ILogObserver} component is available on the application, that object will be used as the log observer instead of constructing a new one. """ application = Componentized() observer = self._makeObserver() application.setComponent(ILogObserver, observer) logger = app.AppLogger({}) logger.start(application) self._checkObserver(observer) def _setupConfiguredLogger( self, application, extraLogArgs={}, appLogger=app.AppLogger ): """ Set up an AppLogger which exercises the C{logger} configuration option. @type application: L{Componentized} @param application: The L{Application} object to pass to L{app.AppLogger.start}. @type extraLogArgs: C{dict} @param extraLogArgs: extra values to pass to AppLogger. @type appLogger: L{AppLogger} class, or a subclass @param appLogger: factory for L{AppLogger} instances. @rtype: C{list} @return: The logs accumulated by the log observer. """ observer = self._makeObserver() logArgs = {"logger": lambda: observer} logArgs.update(extraLogArgs) logger = appLogger(logArgs) logger.start(application) return observer def test_startUsesConfiguredLogObserver(self): """ When the C{logger} key is specified in the configuration dictionary (i.e., when C{--logger} is passed to twistd), the initial log observer will be the log observer returned from the callable which the value refers to in FQPN form. """ application = Componentized() self._checkObserver(self._setupConfiguredLogger(application)) def test_configuredLogObserverBeatsComponent(self): """ C{--logger} takes precedence over a L{ILogObserver} component set on Application. """ observer = self._makeObserver() application = Componentized() application.setComponent(ILogObserver, observer) self._checkObserver(self._setupConfiguredLogger(application)) self.assertEqual(observer._logs, []) def test_configuredLogObserverBeatsLegacyComponent(self): """ C{--logger} takes precedence over a L{LegacyILogObserver} component set on Application. """ nonlogs = [] application = Componentized() application.setComponent(LegacyILogObserver, nonlogs.append) self._checkObserver(self._setupConfiguredLogger(application)) self.assertEqual(nonlogs, []) def test_loggerComponentBeatsLegacyLoggerComponent(self): """ A L{ILogObserver} takes precedence over a L{LegacyILogObserver} component set on Application. """ nonlogs = [] observer = self._makeObserver() application = Componentized() application.setComponent(ILogObserver, observer) application.setComponent(LegacyILogObserver, nonlogs.append) logger = app.AppLogger({}) logger.start(application) self._checkObserver(observer) self.assertEqual(nonlogs, []) @skipIf(not _twistd_unix, "twistd unix not available") @skipIf(not syslog, "syslog not available") def test_configuredLogObserverBeatsSyslog(self): """ C{--logger} takes precedence over a C{--syslog} command line argument. """ logs = _setupSyslog(self) application = Componentized() self._checkObserver( self._setupConfiguredLogger(application, {"syslog": True}, UnixAppLogger) ) self.assertEqual(logs, []) def test_configuredLogObserverBeatsLogfile(self): """ C{--logger} takes precedence over a C{--logfile} command line argument. """ application = Componentized() path = self.mktemp() self._checkObserver( self._setupConfiguredLogger(application, {"logfile": "path"}) ) self.assertFalse(os.path.exists(path)) def test_getLogObserverStdout(self): """ When logfile is empty or set to C{-}, L{app.AppLogger._getLogObserver} returns a log observer pointing at C{sys.stdout}. """ logger = app.AppLogger({"logfile": "-"}) logFiles = _patchTextFileLogObserver(self.patch) logger._getLogObserver() self.assertEqual(len(logFiles), 1) self.assertIs(logFiles[0], sys.stdout) logger = app.AppLogger({"logfile": ""}) logger._getLogObserver() self.assertEqual(len(logFiles), 2) self.assertIs(logFiles[1], sys.stdout) def test_getLogObserverFile(self): """ When passing the C{logfile} option, L{app.AppLogger._getLogObserver} returns a log observer pointing at the specified path. """ logFiles = _patchTextFileLogObserver(self.patch) filename = self.mktemp() sut = app.AppLogger({"logfile": filename}) observer = sut._getLogObserver() self.addCleanup(observer._outFile.close) self.assertEqual(len(logFiles), 1) self.assertEqual(logFiles[0].path, os.path.abspath(filename)) def test_stop(self): """ L{app.AppLogger.stop} removes the observer created in C{start}, and reinitialize its C{_observer} so that if C{stop} is called several times it doesn't break. """ removed = [] observer = object() def remove(observer): removed.append(observer) self.patch(globalLogPublisher, "removeObserver", remove) logger = app.AppLogger({}) logger._observer = observer logger.stop() self.assertEqual(removed, [observer]) logger.stop() self.assertEqual(removed, [observer]) self.assertIsNone(logger._observer) def test_legacyObservers(self): """ L{app.AppLogger} using a legacy logger observer still works, wrapping it in a compat shim. """ logs = [] logger = app.AppLogger({}) @implementer(LegacyILogObserver) class LoggerObserver: """ An observer which implements the legacy L{LegacyILogObserver}. """ def __call__(self, x): """ Add C{x} to the logs list. """ logs.append(x) logger._observerFactory = lambda: LoggerObserver() logger.start(Componentized()) self.assertIn("starting up", textFromEventDict(logs[0])) warnings = self.flushWarnings([self.test_legacyObservers]) self.assertEqual(len(warnings), 0, warnings) def test_unmarkedObserversDeprecated(self): """ L{app.AppLogger} using a logger observer which does not implement L{ILogObserver} or L{LegacyILogObserver} will be wrapped in a compat shim and raise a L{DeprecationWarning}. """ logs = [] logger = app.AppLogger({}) logger._getLogObserver = lambda: logs.append logger.start(Componentized()) self.assertIn("starting up", textFromEventDict(logs[0])) warnings = self.flushWarnings([self.test_unmarkedObserversDeprecated]) self.assertEqual(len(warnings), 1, warnings) self.assertEqual( warnings[0]["message"], ( "Passing a logger factory which makes log observers " "which do not implement twisted.logger.ILogObserver " "or twisted.python.log.ILogObserver to " "twisted.application.app.AppLogger was deprecated " "in Twisted 16.2. Please use a factory that " "produces twisted.logger.ILogObserver (or the " "legacy twisted.python.log.ILogObserver) " "implementing objects instead." ), ) @skipIf(not _twistd_unix, "twistd unix not available") class UnixAppLoggerTests(TestCase): """ Tests for L{UnixAppLogger}. @ivar signals: list of signal handlers installed. @type signals: C{list} """ def setUp(self): """ Fake C{signal.signal} for not installing the handlers but saving them in C{self.signals}. """ self.signals = [] def fakeSignal(sig, f): self.signals.append((sig, f)) self.patch(signal, "signal", fakeSignal) def test_getLogObserverStdout(self): """ When non-daemonized and C{logfile} is empty or set to C{-}, L{UnixAppLogger._getLogObserver} returns a log observer pointing at C{sys.stdout}. """ logFiles = _patchTextFileLogObserver(self.patch) logger = UnixAppLogger({"logfile": "-", "nodaemon": True}) logger._getLogObserver() self.assertEqual(len(logFiles), 1) self.assertIs(logFiles[0], sys.stdout) logger = UnixAppLogger({"logfile": "", "nodaemon": True}) logger._getLogObserver() self.assertEqual(len(logFiles), 2) self.assertIs(logFiles[1], sys.stdout) def test_getLogObserverStdoutDaemon(self): """ When daemonized and C{logfile} is set to C{-}, L{UnixAppLogger._getLogObserver} raises C{SystemExit}. """ logger = UnixAppLogger({"logfile": "-", "nodaemon": False}) error = self.assertRaises(SystemExit, logger._getLogObserver) self.assertEqual(str(error), "Daemons cannot log to stdout, exiting!") def test_getLogObserverFile(self): """ When C{logfile} contains a file name, L{app.AppLogger._getLogObserver} returns a log observer pointing at the specified path, and a signal handler rotating the log is installed. """ logFiles = _patchTextFileLogObserver(self.patch) filename = self.mktemp() sut = UnixAppLogger({"logfile": filename}) observer = sut._getLogObserver() self.addCleanup(observer._outFile.close) self.assertEqual(len(logFiles), 1) self.assertEqual(logFiles[0].path, os.path.abspath(filename)) self.assertEqual(len(self.signals), 1) self.assertEqual(self.signals[0][0], signal.SIGUSR1) d = Deferred() def rotate(): d.callback(None) logFiles[0].rotate = rotate rotateLog = self.signals[0][1] rotateLog(None, None) return d def test_getLogObserverDontOverrideSignalHandler(self): """ If a signal handler is already installed, L{UnixAppLogger._getLogObserver} doesn't override it. """ def fakeGetSignal(sig): self.assertEqual(sig, signal.SIGUSR1) return object() self.patch(signal, "getsignal", fakeGetSignal) filename = self.mktemp() sut = UnixAppLogger({"logfile": filename}) observer = sut._getLogObserver() self.addCleanup(observer._outFile.close) self.assertEqual(self.signals, []) def test_getLogObserverDefaultFile(self): """ When daemonized and C{logfile} is empty, the observer returned by L{UnixAppLogger._getLogObserver} points at C{twistd.log} in the current directory. """ logFiles = _patchTextFileLogObserver(self.patch) logger = UnixAppLogger({"logfile": "", "nodaemon": False}) logger._getLogObserver() self.assertEqual(len(logFiles), 1) self.assertEqual(logFiles[0].path, os.path.abspath("twistd.log")) @skipIf(not _twistd_unix, "twistd unix not available") def test_getLogObserverSyslog(self): """ If C{syslog} is set to C{True}, L{UnixAppLogger._getLogObserver} starts a L{syslog.SyslogObserver} with given C{prefix}. """ logs = _setupSyslog(self) logger = UnixAppLogger({"syslog": True, "prefix": "test-prefix"}) observer = logger._getLogObserver() self.assertEqual(logs, ["test-prefix"]) observer({"a": "b"}) self.assertEqual(logs, ["test-prefix", {"a": "b"}]) @skipIf(not _twistd_unix, "twistd unix support not available") class DaemonizeTests(TestCase): """ Tests for L{_twistd_unix.UnixApplicationRunner} daemonization. """ def setUp(self): self.mockos = MockOS() self.config = twistd.ServerOptions() self.patch(_twistd_unix, "os", self.mockos) self.runner = _twistd_unix.UnixApplicationRunner(self.config) self.runner.application = service.Application("Hi!") self.runner.oldstdout = sys.stdout self.runner.oldstderr = sys.stderr self.runner.startReactor = lambda *args: None def test_success(self): """ When double fork succeeded in C{daemonize}, the child process writes B{0} to the status pipe. """ with AlternateReactor(FakeDaemonizingReactor()): self.runner.postApplication() self.assertEqual( self.mockos.actions, [ ("chdir", "."), ("umask", 0o077), ("fork", True), "setsid", ("fork", True), ("write", -2, b"0"), ("unlink", "twistd.pid"), ], ) self.assertEqual(self.mockos.closed, [-3, -2]) def test_successInParent(self): """ The parent process initiating the C{daemonize} call reads data from the status pipe and then exit the process. """ self.mockos.child = False self.mockos.readData = b"0" with AlternateReactor(FakeDaemonizingReactor()): self.assertRaises(SystemError, self.runner.postApplication) self.assertEqual( self.mockos.actions, [ ("chdir", "."), ("umask", 0o077), ("fork", True), ("read", -1, 100), ("exit", 0), ("unlink", "twistd.pid"), ], ) self.assertEqual(self.mockos.closed, [-1]) def test_successEINTR(self): """ If the C{os.write} call to the status pipe raises an B{EINTR} error, the process child retries to write. """ written = [] def raisingWrite(fd, data): written.append((fd, data)) if len(written) == 1: raise OSError(errno.EINTR) self.mockos.write = raisingWrite with AlternateReactor(FakeDaemonizingReactor()): self.runner.postApplication() self.assertEqual( self.mockos.actions, [ ("chdir", "."), ("umask", 0o077), ("fork", True), "setsid", ("fork", True), ("unlink", "twistd.pid"), ], ) self.assertEqual(self.mockos.closed, [-3, -2]) self.assertEqual([(-2, b"0"), (-2, b"0")], written) def test_successInParentEINTR(self): """ If the C{os.read} call on the status pipe raises an B{EINTR} error, the parent child retries to read. """ read = [] def raisingRead(fd, size): read.append((fd, size)) if len(read) == 1: raise OSError(errno.EINTR) return b"0" self.mockos.read = raisingRead self.mockos.child = False with AlternateReactor(FakeDaemonizingReactor()): self.assertRaises(SystemError, self.runner.postApplication) self.assertEqual( self.mockos.actions, [ ("chdir", "."), ("umask", 0o077), ("fork", True), ("exit", 0), ("unlink", "twistd.pid"), ], ) self.assertEqual(self.mockos.closed, [-1]) self.assertEqual([(-1, 100), (-1, 100)], read) def assertErrorWritten(self, raised, reported): """ Assert L{UnixApplicationRunner.postApplication} writes C{reported} to its status pipe if the service raises an exception whose message is C{raised}. """ class FakeService(service.Service): def startService(self): raise RuntimeError(raised) errorService = FakeService() errorService.setServiceParent(self.runner.application) with AlternateReactor(FakeDaemonizingReactor()): self.assertRaises(RuntimeError, self.runner.postApplication) self.assertEqual( self.mockos.actions, [ ("chdir", "."), ("umask", 0o077), ("fork", True), "setsid", ("fork", True), ("write", -2, reported), ("unlink", "twistd.pid"), ], ) self.assertEqual(self.mockos.closed, [-3, -2]) def test_error(self): """ If an error happens during daemonization, the child process writes the exception error to the status pipe. """ self.assertErrorWritten( raised="Something is wrong", reported=b"1 RuntimeError: Something is wrong" ) def test_unicodeError(self): """ If an error happens during daemonization, and that error's message is Unicode, the child encodes the message as ascii with backslash Unicode code points. """ self.assertErrorWritten(raised="\u2022", reported=b"1 RuntimeError: \\u2022") def assertErrorInParentBehavior(self, readData, errorMessage, mockOSActions): """ Make L{os.read} appear to return C{readData}, and assert that L{UnixApplicationRunner.postApplication} writes C{errorMessage} to standard error and executes the calls against L{os} functions specified in C{mockOSActions}. """ self.mockos.child = False self.mockos.readData = readData errorIO = StringIO() self.patch(sys, "__stderr__", errorIO) with AlternateReactor(FakeDaemonizingReactor()): self.assertRaises(SystemError, self.runner.postApplication) self.assertEqual(errorIO.getvalue(), errorMessage) self.assertEqual(self.mockos.actions, mockOSActions) self.assertEqual(self.mockos.closed, [-1]) def test_errorInParent(self): """ When the child writes an error message to the status pipe during daemonization, the parent writes the repr of the message to C{stderr} and exits with non-zero status code. """ self.assertErrorInParentBehavior( readData=b"1 Exception: An identified error", errorMessage=( "An error has occurred: b'Exception: An identified error'\n" "Please look at log file for more information.\n" ), mockOSActions=[ ("chdir", "."), ("umask", 0o077), ("fork", True), ("read", -1, 100), ("exit", 1), ("unlink", "twistd.pid"), ], ) def test_nonASCIIErrorInParent(self): """ When the child writes a non-ASCII error message to the status pipe during daemonization, the parent writes the repr of the message to C{stderr} and exits with a non-zero status code. """ self.assertErrorInParentBehavior( readData=b"1 Exception: \xff", errorMessage=( "An error has occurred: b'Exception: \\xff'\n" "Please look at log file for more information.\n" ), mockOSActions=[ ("chdir", "."), ("umask", 0o077), ("fork", True), ("read", -1, 100), ("exit", 1), ("unlink", "twistd.pid"), ], ) def test_errorInParentWithTruncatedUnicode(self): """ When the child writes a non-ASCII error message to the status pipe during daemonization, and that message is too longer, the parent writes the repr of the truncated message to C{stderr} and exits with a non-zero status code. """ truncatedMessage = b"1 RuntimeError: " + b"\\u2022" * 14 # the escape sequence will appear to be escaped twice, because # we're getting the repr reportedMessage = "b'RuntimeError: {}'".format(r"\\u2022" * 14) self.assertErrorInParentBehavior( readData=truncatedMessage, errorMessage=( "An error has occurred: {}\n" "Please look at log file for more information.\n".format( reportedMessage ) ), mockOSActions=[ ("chdir", "."), ("umask", 0o077), ("fork", True), ("read", -1, 100), ("exit", 1), ("unlink", "twistd.pid"), ], ) def test_errorMessageTruncated(self): """ If an error occurs during daemonization and its message is too long, it's truncated by the child. """ self.assertErrorWritten( raised="x" * 200, reported=b"1 RuntimeError: " + b"x" * 84 ) def test_unicodeErrorMessageTruncated(self): """ If an error occurs during daemonization and its message is unicode and too long, it's truncated by the child, even if this splits a unicode escape sequence. """ self.assertErrorWritten( raised="\u2022" * 30, reported=b"1 RuntimeError: " + b"\\u2022" * 14, ) def test_hooksCalled(self): """ C{daemonize} indeed calls L{IReactorDaemonize.beforeDaemonize} and L{IReactorDaemonize.afterDaemonize} if the reactor implements L{IReactorDaemonize}. """ reactor = FakeDaemonizingReactor() self.runner.daemonize(reactor) self.assertTrue(reactor._beforeDaemonizeCalled) self.assertTrue(reactor._afterDaemonizeCalled) def test_hooksNotCalled(self): """ C{daemonize} does NOT call L{IReactorDaemonize.beforeDaemonize} or L{IReactorDaemonize.afterDaemonize} if the reactor does NOT implement L{IReactorDaemonize}. """ reactor = FakeNonDaemonizingReactor() self.runner.daemonize(reactor) self.assertFalse(reactor._beforeDaemonizeCalled) self.assertFalse(reactor._afterDaemonizeCalled) @implementer(_ISupportsExitSignalCapturing) class SignalCapturingMemoryReactor(MemoryReactor): """ MemoryReactor that implements the _ISupportsExitSignalCapturing interface, all other operations identical to MemoryReactor. """ @property def _exitSignal(self): return self._val @_exitSignal.setter def _exitSignal(self, val): self._val = val class StubApplicationRunnerWithSignal(twistd._SomeApplicationRunner): """ An application runner that uses a SignalCapturingMemoryReactor and has a _signalValue attribute that it will set in the reactor. @ivar _signalValue: The signal value to set on the reactor's _exitSignal attribute. """ loggerFactory = CrippledAppLogger def __init__(self, config): super().__init__(config) self._signalValue = None def preApplication(self): """ Does nothing. """ def postApplication(self): """ Instantiate a SignalCapturingMemoryReactor and start it in the runner. """ reactor = SignalCapturingMemoryReactor() reactor._exitSignal = self._signalValue self.startReactor(reactor, sys.stdout, sys.stderr) def stubApplicationRunnerFactoryCreator(signum): """ Create a factory function to instantiate a StubApplicationRunnerWithSignal that will report signum as the captured signal.. @param signum: The integer signal number or None @type signum: C{int} or C{None} @return: A factory function to create stub runners. @rtype: stubApplicationRunnerFactory """ def stubApplicationRunnerFactory(config): """ Create a StubApplicationRunnerWithSignal using a reactor that implements _ISupportsExitSignalCapturing and whose _exitSignal attribute is set to signum. @param config: The runner configuration, platform dependent. @type config: L{twisted.scripts.twistd.ServerOptions} @return: A runner to use for the test. @rtype: twisted.test.test_twistd.StubApplicationRunnerWithSignal """ runner = StubApplicationRunnerWithSignal(config) runner._signalValue = signum return runner return stubApplicationRunnerFactory class ExitWithSignalTests(TestCase): """ Tests for L{twisted.application.app._exitWithSignal}. """ def setUp(self): """ Set up the server options and a fake for use by test cases. """ self.config = twistd.ServerOptions() self.config.loadedPlugins = {"test_command": MockServiceMaker()} self.config.subOptions = object() self.config.subCommand = "test_command" self.fakeKillArgs = [None, None] def fakeKill(pid, sig): """ Fake method to capture arguments passed to os.kill. @param pid: The pid of the process being killed. @param sig: The signal sent to the process. """ self.fakeKillArgs[0] = pid self.fakeKillArgs[1] = sig self.patch(os, "kill", fakeKill) def test_exitWithSignal(self): """ exitWithSignal replaces the existing signal handler with the default handler and sends the replaced signal to the current process. """ fakeSignalArgs = [None, None] def fake_signal(sig, handler): fakeSignalArgs[0] = sig fakeSignalArgs[1] = handler self.patch(signal, "signal", fake_signal) app._exitWithSignal(signal.SIGINT) self.assertEquals(fakeSignalArgs[0], signal.SIGINT) self.assertEquals(fakeSignalArgs[1], signal.SIG_DFL) self.assertEquals(self.fakeKillArgs[0], os.getpid()) self.assertEquals(self.fakeKillArgs[1], signal.SIGINT) def test_normalExit(self): """ _exitWithSignal is not called if the runner does not exit with a signal. """ self.patch( twistd, "_SomeApplicationRunner", stubApplicationRunnerFactoryCreator(None) ) twistd.runApp(self.config) self.assertIsNone(self.fakeKillArgs[0]) self.assertIsNone(self.fakeKillArgs[1]) def test_runnerExitsWithSignal(self): """ _exitWithSignal is called when the runner exits with a signal. """ self.patch( twistd, "_SomeApplicationRunner", stubApplicationRunnerFactoryCreator(signal.SIGINT), ) twistd.runApp(self.config) self.assertEquals(self.fakeKillArgs[0], os.getpid()) self.assertEquals(self.fakeKillArgs[1], signal.SIGINT)
Save
cmd:
run