mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 15:11:09 -05:00
okay fine
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Subpackage containing the modules that implement the command line tools.
|
||||
|
||||
Note that these are imported by top-level scripts which are intended to be
|
||||
invoked directly from a shell.
|
||||
"""
|
||||
@@ -0,0 +1,455 @@
|
||||
# -*- test-case-name: twisted.test.test_twistd -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
import errno
|
||||
import os
|
||||
import pwd
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from twisted import copyright, logger
|
||||
from twisted.application import app, service
|
||||
from twisted.internet.interfaces import IReactorDaemonize
|
||||
from twisted.python import log, logfile, usage
|
||||
from twisted.python.runtime import platformType
|
||||
from twisted.python.util import gidFromString, switchUID, uidFromString, untilConcludes
|
||||
|
||||
if platformType == "win32":
|
||||
raise ImportError("_twistd_unix doesn't work on Windows.")
|
||||
|
||||
|
||||
def _umask(value):
|
||||
return int(value, 8)
|
||||
|
||||
|
||||
class ServerOptions(app.ServerOptions):
|
||||
synopsis = "Usage: twistd [options]"
|
||||
|
||||
optFlags = [
|
||||
["nodaemon", "n", "don't daemonize, don't use default umask of 0077"],
|
||||
["originalname", None, "Don't try to change the process name"],
|
||||
["syslog", None, "Log to syslog, not to file"],
|
||||
[
|
||||
"euid",
|
||||
"",
|
||||
"Set only effective user-id rather than real user-id. "
|
||||
"(This option has no effect unless the server is running as "
|
||||
"root, in which case it means not to shed all privileges "
|
||||
"after binding ports, retaining the option to regain "
|
||||
"privileges in cases such as spawning processes. "
|
||||
"Use with caution.)",
|
||||
],
|
||||
]
|
||||
|
||||
optParameters = [
|
||||
["prefix", None, "twisted", "use the given prefix when syslogging"],
|
||||
["pidfile", "", "twistd.pid", "Name of the pidfile"],
|
||||
["chroot", None, None, "Chroot to a supplied directory before running"],
|
||||
["uid", "u", None, "The uid to run as.", uidFromString],
|
||||
[
|
||||
"gid",
|
||||
"g",
|
||||
None,
|
||||
"The gid to run as. If not specified, the default gid "
|
||||
"associated with the specified --uid is used.",
|
||||
gidFromString,
|
||||
],
|
||||
["umask", None, None, "The (octal) file creation mask to apply.", _umask],
|
||||
]
|
||||
|
||||
compData = usage.Completions(
|
||||
optActions={
|
||||
"pidfile": usage.CompleteFiles("*.pid"),
|
||||
"chroot": usage.CompleteDirs(descr="chroot directory"),
|
||||
"gid": usage.CompleteGroups(descr="gid to run as"),
|
||||
"uid": usage.CompleteUsernames(descr="uid to run as"),
|
||||
"prefix": usage.Completer(descr="syslog prefix"),
|
||||
},
|
||||
)
|
||||
|
||||
def opt_version(self):
|
||||
"""
|
||||
Print version information and exit.
|
||||
"""
|
||||
print(f"twistd (the Twisted daemon) {copyright.version}", file=self.stdout)
|
||||
print(copyright.copyright, file=self.stdout)
|
||||
sys.exit()
|
||||
|
||||
def postOptions(self):
|
||||
app.ServerOptions.postOptions(self)
|
||||
if self["pidfile"]:
|
||||
self["pidfile"] = os.path.abspath(self["pidfile"])
|
||||
|
||||
|
||||
def checkPID(pidfile):
|
||||
if not pidfile:
|
||||
return
|
||||
if os.path.exists(pidfile):
|
||||
try:
|
||||
with open(pidfile) as f:
|
||||
pid = int(f.read())
|
||||
except ValueError:
|
||||
sys.exit(f"Pidfile {pidfile} contains non-numeric value")
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except OSError as why:
|
||||
if why.errno == errno.ESRCH:
|
||||
# The pid doesn't exist.
|
||||
log.msg(f"Removing stale pidfile {pidfile}", isError=True)
|
||||
os.remove(pidfile)
|
||||
else:
|
||||
sys.exit(
|
||||
"Can't check status of PID {} from pidfile {}: {}".format(
|
||||
pid, pidfile, why
|
||||
)
|
||||
)
|
||||
else:
|
||||
sys.exit(
|
||||
"""\
|
||||
Another twistd server is running, PID {}\n
|
||||
This could either be a previously started instance of your application or a
|
||||
different application entirely. To start a new one, either run it in some other
|
||||
directory, or use the --pidfile and --logfile parameters to avoid clashes.
|
||||
""".format(
|
||||
pid
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class UnixAppLogger(app.AppLogger):
|
||||
"""
|
||||
A logger able to log to syslog, to files, and to stdout.
|
||||
|
||||
@ivar _syslog: A flag indicating whether to use syslog instead of file
|
||||
logging.
|
||||
@type _syslog: C{bool}
|
||||
|
||||
@ivar _syslogPrefix: If C{sysLog} is C{True}, the string prefix to use for
|
||||
syslog messages.
|
||||
@type _syslogPrefix: C{str}
|
||||
|
||||
@ivar _nodaemon: A flag indicating the process will not be daemonizing.
|
||||
@type _nodaemon: C{bool}
|
||||
"""
|
||||
|
||||
def __init__(self, options):
|
||||
app.AppLogger.__init__(self, options)
|
||||
self._syslog = options.get("syslog", False)
|
||||
self._syslogPrefix = options.get("prefix", "")
|
||||
self._nodaemon = options.get("nodaemon", False)
|
||||
|
||||
def _getLogObserver(self):
|
||||
"""
|
||||
Create and return a suitable log observer for the given configuration.
|
||||
|
||||
The observer will go to syslog using the prefix C{_syslogPrefix} if
|
||||
C{_syslog} is true. Otherwise, it will go to the file named
|
||||
C{_logfilename} or, if C{_nodaemon} is true and C{_logfilename} is
|
||||
C{"-"}, to stdout.
|
||||
|
||||
@return: An object suitable to be passed to C{log.addObserver}.
|
||||
"""
|
||||
if self._syslog:
|
||||
from twisted.python import syslog
|
||||
|
||||
return syslog.SyslogObserver(self._syslogPrefix).emit
|
||||
|
||||
if self._logfilename == "-":
|
||||
if not self._nodaemon:
|
||||
sys.exit("Daemons cannot log to stdout, exiting!")
|
||||
logFile = sys.stdout
|
||||
elif self._nodaemon and not self._logfilename:
|
||||
logFile = sys.stdout
|
||||
else:
|
||||
if not self._logfilename:
|
||||
self._logfilename = "twistd.log"
|
||||
logFile = logfile.LogFile.fromFullPath(self._logfilename)
|
||||
try:
|
||||
import signal
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
# Override if signal is set to None or SIG_DFL (0)
|
||||
if not signal.getsignal(signal.SIGUSR1):
|
||||
|
||||
def rotateLog(signal, frame):
|
||||
from twisted.internet import reactor
|
||||
|
||||
reactor.callFromThread(logFile.rotate)
|
||||
|
||||
signal.signal(signal.SIGUSR1, rotateLog)
|
||||
return logger.textFileLogObserver(logFile)
|
||||
|
||||
|
||||
def launchWithName(name):
|
||||
if name and name != sys.argv[0]:
|
||||
exe = os.path.realpath(sys.executable)
|
||||
log.msg("Changing process name to " + name)
|
||||
os.execv(exe, [name, sys.argv[0], "--originalname"] + sys.argv[1:])
|
||||
|
||||
|
||||
class UnixApplicationRunner(app.ApplicationRunner):
|
||||
"""
|
||||
An ApplicationRunner which does Unix-specific things, like fork,
|
||||
shed privileges, and maintain a PID file.
|
||||
"""
|
||||
|
||||
loggerFactory = UnixAppLogger
|
||||
|
||||
def preApplication(self):
|
||||
"""
|
||||
Do pre-application-creation setup.
|
||||
"""
|
||||
checkPID(self.config["pidfile"])
|
||||
self.config["nodaemon"] = self.config["nodaemon"] or self.config["debug"]
|
||||
self.oldstdout = sys.stdout
|
||||
self.oldstderr = sys.stderr
|
||||
|
||||
def _formatChildException(self, exception):
|
||||
"""
|
||||
Format the C{exception} in preparation for writing to the
|
||||
status pipe. This does the right thing on Python 2 if the
|
||||
exception's message is Unicode, and in all cases limits the
|
||||
length of the message afte* encoding to 100 bytes.
|
||||
|
||||
This means the returned message may be truncated in the middle
|
||||
of a unicode escape.
|
||||
|
||||
@type exception: L{Exception}
|
||||
@param exception: The exception to format.
|
||||
|
||||
@return: The formatted message, suitable for writing to the
|
||||
status pipe.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
# On Python 2 this will encode Unicode messages with the ascii
|
||||
# codec and the backslashreplace error handler.
|
||||
exceptionLine = traceback.format_exception_only(exception.__class__, exception)[
|
||||
-1
|
||||
]
|
||||
# remove the trailing newline
|
||||
formattedMessage = f"1 {exceptionLine.strip()}"
|
||||
# On Python 3, encode the message the same way Python 2's
|
||||
# format_exception_only does
|
||||
formattedMessage = formattedMessage.encode("ascii", "backslashreplace")
|
||||
# By this point, the message has been encoded, if appropriate,
|
||||
# with backslashreplace on both Python 2 and Python 3.
|
||||
# Truncating the encoded message won't make it completely
|
||||
# unreadable, and the reader should print out the repr of the
|
||||
# message it receives anyway. What it will do, however, is
|
||||
# ensure that only 100 bytes are written to the status pipe,
|
||||
# ensuring that the child doesn't block because the pipe's
|
||||
# full. This assumes PIPE_BUF > 100!
|
||||
return formattedMessage[:100]
|
||||
|
||||
def postApplication(self):
|
||||
"""
|
||||
To be called after the application is created: start the application
|
||||
and run the reactor. After the reactor stops, clean up PID files and
|
||||
such.
|
||||
"""
|
||||
try:
|
||||
self.startApplication(self.application)
|
||||
except Exception as ex:
|
||||
statusPipe = self.config.get("statusPipe", None)
|
||||
if statusPipe is not None:
|
||||
message = self._formatChildException(ex)
|
||||
untilConcludes(os.write, statusPipe, message)
|
||||
untilConcludes(os.close, statusPipe)
|
||||
self.removePID(self.config["pidfile"])
|
||||
raise
|
||||
else:
|
||||
statusPipe = self.config.get("statusPipe", None)
|
||||
if statusPipe is not None:
|
||||
untilConcludes(os.write, statusPipe, b"0")
|
||||
untilConcludes(os.close, statusPipe)
|
||||
self.startReactor(None, self.oldstdout, self.oldstderr)
|
||||
self.removePID(self.config["pidfile"])
|
||||
|
||||
def removePID(self, pidfile):
|
||||
"""
|
||||
Remove the specified PID file, if possible. Errors are logged, not
|
||||
raised.
|
||||
|
||||
@type pidfile: C{str}
|
||||
@param pidfile: The path to the PID tracking file.
|
||||
"""
|
||||
if not pidfile:
|
||||
return
|
||||
try:
|
||||
os.unlink(pidfile)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EACCES or e.errno == errno.EPERM:
|
||||
log.msg("Warning: No permission to delete pid file")
|
||||
else:
|
||||
log.err(e, "Failed to unlink PID file:")
|
||||
except BaseException:
|
||||
log.err(None, "Failed to unlink PID file:")
|
||||
|
||||
def setupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile):
|
||||
"""
|
||||
Set the filesystem root, the working directory, and daemonize.
|
||||
|
||||
@type chroot: C{str} or L{None}
|
||||
@param chroot: If not None, a path to use as the filesystem root (using
|
||||
L{os.chroot}).
|
||||
|
||||
@type rundir: C{str}
|
||||
@param rundir: The path to set as the working directory.
|
||||
|
||||
@type nodaemon: C{bool}
|
||||
@param nodaemon: A flag which, if set, indicates that daemonization
|
||||
should not be done.
|
||||
|
||||
@type umask: C{int} or L{None}
|
||||
@param umask: The value to which to change the process umask.
|
||||
|
||||
@type pidfile: C{str} or L{None}
|
||||
@param pidfile: If not L{None}, the path to a file into which to put
|
||||
the PID of this process.
|
||||
"""
|
||||
daemon = not nodaemon
|
||||
|
||||
if chroot is not None:
|
||||
os.chroot(chroot)
|
||||
if rundir == ".":
|
||||
rundir = "/"
|
||||
os.chdir(rundir)
|
||||
if daemon and umask is None:
|
||||
umask = 0o077
|
||||
if umask is not None:
|
||||
os.umask(umask)
|
||||
if daemon:
|
||||
from twisted.internet import reactor
|
||||
|
||||
self.config["statusPipe"] = self.daemonize(reactor)
|
||||
if pidfile:
|
||||
with open(pidfile, "wb") as f:
|
||||
f.write(b"%d" % (os.getpid(),))
|
||||
|
||||
def daemonize(self, reactor):
|
||||
"""
|
||||
Daemonizes the application on Unix. This is done by the usual double
|
||||
forking approach.
|
||||
|
||||
@see: U{http://code.activestate.com/recipes/278731/}
|
||||
@see: W. Richard Stevens,
|
||||
"Advanced Programming in the Unix Environment",
|
||||
1992, Addison-Wesley, ISBN 0-201-56317-7
|
||||
|
||||
@param reactor: The reactor in use. If it provides
|
||||
L{IReactorDaemonize}, its daemonization-related callbacks will be
|
||||
invoked.
|
||||
|
||||
@return: A writable pipe to be used to report errors.
|
||||
@rtype: C{int}
|
||||
"""
|
||||
# If the reactor requires hooks to be called for daemonization, call
|
||||
# them. Currently the only reactor which provides/needs that is
|
||||
# KQueueReactor.
|
||||
if IReactorDaemonize.providedBy(reactor):
|
||||
reactor.beforeDaemonize()
|
||||
r, w = os.pipe()
|
||||
if os.fork(): # launch child and...
|
||||
code = self._waitForStart(r)
|
||||
os.close(r)
|
||||
os._exit(code) # kill off parent
|
||||
os.setsid()
|
||||
if os.fork(): # launch child and...
|
||||
os._exit(0) # kill off parent again.
|
||||
null = os.open("/dev/null", os.O_RDWR)
|
||||
for i in range(3):
|
||||
try:
|
||||
os.dup2(null, i)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EBADF:
|
||||
raise
|
||||
os.close(null)
|
||||
|
||||
if IReactorDaemonize.providedBy(reactor):
|
||||
reactor.afterDaemonize()
|
||||
|
||||
return w
|
||||
|
||||
def _waitForStart(self, readPipe: int) -> int:
|
||||
"""
|
||||
Wait for the daemonization success.
|
||||
|
||||
@param readPipe: file descriptor to read start information from.
|
||||
@type readPipe: C{int}
|
||||
|
||||
@return: code to be passed to C{os._exit}: 0 for success, 1 for error.
|
||||
@rtype: C{int}
|
||||
"""
|
||||
data = untilConcludes(os.read, readPipe, 100)
|
||||
dataRepr = repr(data[2:])
|
||||
if data != b"0":
|
||||
msg = (
|
||||
"An error has occurred: {}\nPlease look at log "
|
||||
"file for more information.\n".format(dataRepr)
|
||||
)
|
||||
untilConcludes(sys.__stderr__.write, msg)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def shedPrivileges(self, euid, uid, gid):
|
||||
"""
|
||||
Change the UID and GID or the EUID and EGID of this process.
|
||||
|
||||
@type euid: C{bool}
|
||||
@param euid: A flag which, if set, indicates that only the I{effective}
|
||||
UID and GID should be set.
|
||||
|
||||
@type uid: C{int} or L{None}
|
||||
@param uid: If not L{None}, the UID to which to switch.
|
||||
|
||||
@type gid: C{int} or L{None}
|
||||
@param gid: If not L{None}, the GID to which to switch.
|
||||
"""
|
||||
if uid is not None or gid is not None:
|
||||
extra = euid and "e" or ""
|
||||
desc = f"{extra}uid/{extra}gid {uid}/{gid}"
|
||||
try:
|
||||
switchUID(uid, gid, euid)
|
||||
except OSError as e:
|
||||
log.msg(
|
||||
"failed to set {}: {} (are you root?) -- "
|
||||
"exiting.".format(desc, e)
|
||||
)
|
||||
sys.exit(1)
|
||||
else:
|
||||
log.msg(f"set {desc}")
|
||||
|
||||
def startApplication(self, application):
|
||||
"""
|
||||
Configure global process state based on the given application and run
|
||||
the application.
|
||||
|
||||
@param application: An object which can be adapted to
|
||||
L{service.IProcess} and L{service.IService}.
|
||||
"""
|
||||
process = service.IProcess(application)
|
||||
if not self.config["originalname"]:
|
||||
launchWithName(process.processName)
|
||||
self.setupEnvironment(
|
||||
self.config["chroot"],
|
||||
self.config["rundir"],
|
||||
self.config["nodaemon"],
|
||||
self.config["umask"],
|
||||
self.config["pidfile"],
|
||||
)
|
||||
|
||||
service.IService(application).privilegedStartService()
|
||||
|
||||
uid, gid = self.config["uid"], self.config["gid"]
|
||||
if uid is None:
|
||||
uid = process.uid
|
||||
if gid is None:
|
||||
gid = process.gid
|
||||
if uid is not None and gid is None:
|
||||
gid = pwd.getpwuid(uid).pw_gid
|
||||
|
||||
self.shedPrivileges(self.config["euid"], uid, gid)
|
||||
app.startApplication(application, not self.config["no_save"])
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- test-case-name: twisted.test.test_twistd -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from twisted import copyright
|
||||
from twisted.application import app, internet, service
|
||||
from twisted.python import log
|
||||
|
||||
|
||||
class ServerOptions(app.ServerOptions):
|
||||
synopsis = "Usage: twistd [options]"
|
||||
|
||||
optFlags = [
|
||||
["nodaemon", "n", "(for backwards compatibility)."],
|
||||
]
|
||||
|
||||
def opt_version(self):
|
||||
"""
|
||||
Print version information and exit.
|
||||
"""
|
||||
print(
|
||||
f"twistd (the Twisted Windows runner) {copyright.version}",
|
||||
file=self.stdout,
|
||||
)
|
||||
print(copyright.copyright, file=self.stdout)
|
||||
sys.exit()
|
||||
|
||||
|
||||
class WindowsApplicationRunner(app.ApplicationRunner):
|
||||
"""
|
||||
An ApplicationRunner which avoids unix-specific things. No
|
||||
forking, no PID files, no privileges.
|
||||
"""
|
||||
|
||||
def preApplication(self):
|
||||
"""
|
||||
Do pre-application-creation setup.
|
||||
"""
|
||||
self.oldstdout = sys.stdout
|
||||
self.oldstderr = sys.stderr
|
||||
os.chdir(self.config["rundir"])
|
||||
|
||||
def postApplication(self):
|
||||
"""
|
||||
Start the application and run the reactor.
|
||||
"""
|
||||
service.IService(self.application).privilegedStartService()
|
||||
app.startApplication(self.application, not self.config["no_save"])
|
||||
app.startApplication(internet.TimerService(0.1, lambda: None), 0)
|
||||
self.startReactor(None, self.oldstdout, self.oldstderr)
|
||||
log.msg("Server Shut Down.")
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
#
|
||||
|
||||
"""
|
||||
HTML pretty-printing for Python source code.
|
||||
"""
|
||||
|
||||
|
||||
__version__ = "$Revision: 1.8 $"[11:-2]
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from twisted import copyright
|
||||
from twisted.python import htmlizer, usage
|
||||
|
||||
header = """<html><head>
|
||||
<title>%(title)s</title>
|
||||
<meta name=\"Generator\" content="%(generator)s" />
|
||||
%(alternate)s
|
||||
%(stylesheet)s
|
||||
</head>
|
||||
<body>
|
||||
"""
|
||||
footer = """</body>"""
|
||||
|
||||
styleLink = '<link rel="stylesheet" href="%s" type="text/css" />'
|
||||
alternateLink = '<link rel="alternate" href="%(source)s" type="text/x-python" />'
|
||||
|
||||
|
||||
class Options(usage.Options):
|
||||
synopsis = """{} [options] source.py
|
||||
""".format(
|
||||
os.path.basename(sys.argv[0]),
|
||||
)
|
||||
|
||||
optParameters = [
|
||||
("stylesheet", "s", None, "URL of stylesheet to link to."),
|
||||
]
|
||||
|
||||
compData = usage.Completions(
|
||||
extraActions=[usage.CompleteFiles("*.py", descr="source python file")]
|
||||
)
|
||||
|
||||
def parseArgs(self, filename):
|
||||
self["filename"] = filename
|
||||
|
||||
|
||||
def run():
|
||||
options = Options()
|
||||
try:
|
||||
options.parseOptions()
|
||||
except usage.UsageError as e:
|
||||
print(str(e))
|
||||
sys.exit(1)
|
||||
filename = options["filename"]
|
||||
if options.get("stylesheet") is not None:
|
||||
stylesheet = styleLink % (options["stylesheet"],)
|
||||
else:
|
||||
stylesheet = ""
|
||||
|
||||
with open(filename + ".html", "wb") as output:
|
||||
outHeader = header % {
|
||||
"title": filename,
|
||||
"generator": f"htmlizer/{copyright.longversion}",
|
||||
"alternate": alternateLink % {"source": filename},
|
||||
"stylesheet": stylesheet,
|
||||
}
|
||||
output.write(outHeader.encode("utf-8"))
|
||||
with open(filename, "rb") as f:
|
||||
htmlizer.filter(f, output, htmlizer.SmallerHTMLWriter)
|
||||
output.write(footer.encode("utf-8"))
|
||||
@@ -0,0 +1 @@
|
||||
If twist or twistd exit with a signal it now delivers that signal to itself instead of exiting normally. On Unix platforms this results in a nonzero exit code where previously a zero exit code was returned.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test package for L{twisted.scripts}.
|
||||
"""
|
||||
@@ -0,0 +1,143 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for the command-line scripts in the top-level I{bin/} directory.
|
||||
|
||||
Tests for actual functionality belong elsewhere, written in a way that doesn't
|
||||
involve launching child processes.
|
||||
"""
|
||||
|
||||
from os import chdir, devnull, getcwd
|
||||
from subprocess import PIPE, Popen
|
||||
from sys import executable
|
||||
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.python.modules import getModule
|
||||
from twisted.python.test.test_shellcomp import ZshScriptTestMixin
|
||||
from twisted.trial.unittest import SkipTest, TestCase
|
||||
|
||||
|
||||
def outputFromPythonScript(script, *args):
|
||||
"""
|
||||
Synchronously run a Python script, with the same Python interpreter that
|
||||
ran the process calling this function, using L{Popen}, using the given
|
||||
command-line arguments, with standard input and standard error both
|
||||
redirected to L{os.devnull}, and return its output as a string.
|
||||
|
||||
@param script: The path to the script.
|
||||
@type script: L{FilePath}
|
||||
|
||||
@param args: The command-line arguments to follow the script in its
|
||||
invocation (the desired C{sys.argv[1:]}).
|
||||
@type args: L{tuple} of L{str}
|
||||
|
||||
@return: the output passed to the proces's C{stdout}, without any messages
|
||||
from C{stderr}.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
with open(devnull, "rb") as nullInput, open(devnull, "wb") as nullError:
|
||||
process = Popen(
|
||||
[executable, script.path] + list(args),
|
||||
stdout=PIPE,
|
||||
stderr=nullError,
|
||||
stdin=nullInput,
|
||||
)
|
||||
stdout = process.communicate()[0]
|
||||
return stdout
|
||||
|
||||
|
||||
class ScriptTestsMixin:
|
||||
"""
|
||||
Mixin for L{TestCase} subclasses which defines a helper function for testing
|
||||
a Twisted-using script.
|
||||
"""
|
||||
|
||||
bin = getModule("twisted").pathEntry.filePath.child("bin")
|
||||
|
||||
def scriptTest(self, name):
|
||||
"""
|
||||
Verify that the given script runs and uses the version of Twisted
|
||||
currently being tested.
|
||||
|
||||
This only works when running tests against a vcs checkout of Twisted,
|
||||
since it relies on the scripts being in the place they are kept in
|
||||
version control, and exercises their logic for finding the right version
|
||||
of Twisted to use in that situation.
|
||||
|
||||
@param name: A path fragment, relative to the I{bin} directory of a
|
||||
Twisted source checkout, identifying a script to test.
|
||||
@type name: C{str}
|
||||
|
||||
@raise SkipTest: if the script is not where it is expected to be.
|
||||
"""
|
||||
script = self.bin.preauthChild(name)
|
||||
if not script.exists():
|
||||
raise SkipTest("Script tests do not apply to installed configuration.")
|
||||
|
||||
from twisted.copyright import version
|
||||
|
||||
scriptVersion = outputFromPythonScript(script, "--version")
|
||||
|
||||
self.assertIn(str(version), scriptVersion)
|
||||
|
||||
|
||||
class ScriptTests(TestCase, ScriptTestsMixin):
|
||||
"""
|
||||
Tests for the core scripts.
|
||||
"""
|
||||
|
||||
def test_twistd(self) -> None:
|
||||
self.scriptTest("twistd")
|
||||
|
||||
def test_twistdPathInsert(self):
|
||||
"""
|
||||
The twistd script adds the current working directory to sys.path so
|
||||
that it's able to import modules from it.
|
||||
"""
|
||||
script = self.bin.child("twistd")
|
||||
if not script.exists():
|
||||
raise SkipTest("Script tests do not apply to installed configuration.")
|
||||
cwd = getcwd()
|
||||
self.addCleanup(chdir, cwd)
|
||||
testDir = FilePath(self.mktemp())
|
||||
testDir.makedirs()
|
||||
chdir(testDir.path)
|
||||
testDir.child("bar.tac").setContent("import sys\n" "print sys.path\n")
|
||||
output = outputFromPythonScript(script, "-ny", "bar.tac")
|
||||
self.assertIn(repr(testDir.path), output)
|
||||
|
||||
def test_trial(self) -> None:
|
||||
self.scriptTest("trial")
|
||||
|
||||
def test_trialPathInsert(self):
|
||||
"""
|
||||
The trial script adds the current working directory to sys.path so that
|
||||
it's able to import modules from it.
|
||||
"""
|
||||
script = self.bin.child("trial")
|
||||
if not script.exists():
|
||||
raise SkipTest("Script tests do not apply to installed configuration.")
|
||||
cwd = getcwd()
|
||||
self.addCleanup(chdir, cwd)
|
||||
testDir = FilePath(self.mktemp())
|
||||
testDir.makedirs()
|
||||
chdir(testDir.path)
|
||||
testDir.child("foo.py").setContent("")
|
||||
output = outputFromPythonScript(script, "foo")
|
||||
self.assertIn("PASSED", output)
|
||||
|
||||
def test_pyhtmlizer(self) -> None:
|
||||
self.scriptTest("pyhtmlizer")
|
||||
|
||||
|
||||
class ZshIntegrationTests(TestCase, ZshScriptTestMixin):
|
||||
"""
|
||||
Test that zsh completion functions are generated without error
|
||||
"""
|
||||
|
||||
generateFor = [
|
||||
("twistd", "twisted.scripts.twistd.ServerOptions"),
|
||||
("trial", "twisted.scripts.trial.Options"),
|
||||
("pyhtmlizer", "twisted.scripts.htmlizer.Options"),
|
||||
]
|
||||
691
.venv/lib/python3.12/site-packages/twisted/scripts/trial.py
Normal file
691
.venv/lib/python3.12/site-packages/twisted/scripts/trial.py
Normal file
@@ -0,0 +1,691 @@
|
||||
# -*- test-case-name: twisted.trial.test.test_script -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
import gc
|
||||
import inspect
|
||||
import os
|
||||
import pdb
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
import trace
|
||||
import warnings
|
||||
from typing import NoReturn, Optional, Type
|
||||
|
||||
from twisted import plugin
|
||||
from twisted.application import app
|
||||
from twisted.internet import defer
|
||||
from twisted.python import failure, reflect, usage
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.python.reflect import namedModule
|
||||
from twisted.trial import itrial, runner
|
||||
from twisted.trial._dist.disttrial import DistTrialRunner
|
||||
from twisted.trial.unittest import TestSuite
|
||||
|
||||
# Yea, this is stupid. Leave it for command-line compatibility for a
|
||||
# while, though.
|
||||
TBFORMAT_MAP = {
|
||||
"plain": "default",
|
||||
"default": "default",
|
||||
"emacs": "brief",
|
||||
"brief": "brief",
|
||||
"cgitb": "verbose",
|
||||
"verbose": "verbose",
|
||||
}
|
||||
|
||||
|
||||
def _autoJobs() -> int:
|
||||
"""
|
||||
Heuristically guess the number of job workers to run.
|
||||
|
||||
When ``os.process_cpu_count()`` is available (Python 3.13+),
|
||||
return the number of logical CPUs usable by the current
|
||||
process. This respects the ``PYTHON_CPU_COUNT`` environment
|
||||
variable and/or ``python -X cpu_count`` flag.
|
||||
|
||||
Otherwise, if ``os.sched_getaffinity()`` is available (on some
|
||||
Unixes) this returns the number of CPUs this process is
|
||||
restricted to, under the assumption that this affinity will
|
||||
be inherited.
|
||||
|
||||
Otherwise, consult ``os.cpu_count()`` to get the number of
|
||||
logical CPUs.
|
||||
|
||||
Failing all else, return 1.
|
||||
|
||||
@returns: A strictly positive integer.
|
||||
"""
|
||||
number: Optional[int]
|
||||
if getattr(os, "process_cpu_count", None) is not None:
|
||||
number = os.process_cpu_count() # type: ignore[attr-defined]
|
||||
elif getattr(os, "sched_getaffinity", None) is not None:
|
||||
number = len(os.sched_getaffinity(0))
|
||||
else:
|
||||
number = os.cpu_count()
|
||||
if number is None or number < 1:
|
||||
return 1
|
||||
return number
|
||||
|
||||
|
||||
def _parseLocalVariables(line):
|
||||
"""
|
||||
Accepts a single line in Emacs local variable declaration format and
|
||||
returns a dict of all the variables {name: value}.
|
||||
Raises ValueError if 'line' is in the wrong format.
|
||||
|
||||
See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
|
||||
"""
|
||||
paren = "-*-"
|
||||
start = line.find(paren) + len(paren)
|
||||
end = line.rfind(paren)
|
||||
if start == -1 or end == -1:
|
||||
raise ValueError(f"{line!r} not a valid local variable declaration")
|
||||
items = line[start:end].split(";")
|
||||
localVars = {}
|
||||
for item in items:
|
||||
if len(item.strip()) == 0:
|
||||
continue
|
||||
split = item.split(":")
|
||||
if len(split) != 2:
|
||||
raise ValueError(f"{line!r} contains invalid declaration {item!r}")
|
||||
localVars[split[0].strip()] = split[1].strip()
|
||||
return localVars
|
||||
|
||||
|
||||
def loadLocalVariables(filename):
|
||||
"""
|
||||
Accepts a filename and attempts to load the Emacs variable declarations
|
||||
from that file, simulating what Emacs does.
|
||||
|
||||
See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
|
||||
"""
|
||||
with open(filename) as f:
|
||||
lines = [f.readline(), f.readline()]
|
||||
for line in lines:
|
||||
try:
|
||||
return _parseLocalVariables(line)
|
||||
except ValueError:
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
def getTestModules(filename):
|
||||
testCaseVar = loadLocalVariables(filename).get("test-case-name", None)
|
||||
if testCaseVar is None:
|
||||
return []
|
||||
return testCaseVar.split(",")
|
||||
|
||||
|
||||
def isTestFile(filename):
|
||||
"""
|
||||
Returns true if 'filename' looks like a file containing unit tests.
|
||||
False otherwise. Doesn't care whether filename exists.
|
||||
"""
|
||||
basename = os.path.basename(filename)
|
||||
return basename.startswith("test_") and os.path.splitext(basename)[1] == (".py")
|
||||
|
||||
|
||||
def _reporterAction():
|
||||
return usage.CompleteList([p.longOpt for p in plugin.getPlugins(itrial.IReporter)])
|
||||
|
||||
|
||||
def _maybeFindSourceLine(testThing):
|
||||
"""
|
||||
Try to find the source line of the given test thing.
|
||||
|
||||
@param testThing: the test item to attempt to inspect
|
||||
@type testThing: an L{TestCase}, test method, or module, though only the
|
||||
former two have a chance to succeed
|
||||
@rtype: int
|
||||
@return: the starting source line, or -1 if one couldn't be found
|
||||
"""
|
||||
|
||||
# an instance of L{TestCase} -- locate the test it will run
|
||||
method = getattr(testThing, "_testMethodName", None)
|
||||
if method is not None:
|
||||
testThing = getattr(testThing, method)
|
||||
|
||||
# If it's a function, we can get the line number even if the source file no
|
||||
# longer exists
|
||||
code = getattr(testThing, "__code__", None)
|
||||
if code is not None:
|
||||
return code.co_firstlineno
|
||||
|
||||
try:
|
||||
return inspect.getsourcelines(testThing)[1]
|
||||
except (OSError, TypeError):
|
||||
# either testThing is a module, which raised a TypeError, or the file
|
||||
# couldn't be read
|
||||
return -1
|
||||
|
||||
|
||||
# orders which can be passed to trial --order
|
||||
_runOrders = {
|
||||
"alphabetical": (
|
||||
"alphabetical order for test methods, arbitrary order for test cases",
|
||||
runner.name,
|
||||
),
|
||||
"toptobottom": (
|
||||
"attempt to run test cases and methods in the order they were defined",
|
||||
_maybeFindSourceLine,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _checkKnownRunOrder(order):
|
||||
"""
|
||||
Check that the given order is a known test running order.
|
||||
|
||||
Does nothing else, since looking up the appropriate callable to sort the
|
||||
tests should be done when it actually will be used, as the default argument
|
||||
will not be coerced by this function.
|
||||
|
||||
@param order: one of the known orders in C{_runOrders}
|
||||
@return: the order unmodified
|
||||
"""
|
||||
if order not in _runOrders:
|
||||
raise usage.UsageError(
|
||||
"--order must be one of: %s. See --help-orders for details"
|
||||
% (", ".join(repr(order) for order in _runOrders),)
|
||||
)
|
||||
return order
|
||||
|
||||
|
||||
class _BasicOptions:
|
||||
"""
|
||||
Basic options shared between trial and its local workers.
|
||||
"""
|
||||
|
||||
longdesc = (
|
||||
"trial loads and executes a suite of unit tests, obtained "
|
||||
"from modules, packages and files listed on the command line."
|
||||
)
|
||||
|
||||
optFlags = [
|
||||
["help", "h"],
|
||||
["no-recurse", "N", "Don't recurse into packages"],
|
||||
["help-orders", None, "Help on available test running orders"],
|
||||
["help-reporters", None, "Help on available output plugins (reporters)"],
|
||||
[
|
||||
"rterrors",
|
||||
"e",
|
||||
"realtime errors, print out tracebacks as " "soon as they occur",
|
||||
],
|
||||
["unclean-warnings", None, "Turn dirty reactor errors into warnings"],
|
||||
[
|
||||
"force-gc",
|
||||
None,
|
||||
"Have Trial run gc.collect() before and " "after each test case.",
|
||||
],
|
||||
[
|
||||
"exitfirst",
|
||||
"x",
|
||||
"Exit after the first non-successful result (cannot be "
|
||||
"specified along with --jobs).",
|
||||
],
|
||||
]
|
||||
|
||||
optParameters = [
|
||||
[
|
||||
"order",
|
||||
"o",
|
||||
None,
|
||||
"Specify what order to run test cases and methods. "
|
||||
"See --help-orders for more info.",
|
||||
_checkKnownRunOrder,
|
||||
],
|
||||
["random", "z", None, "Run tests in random order using the specified seed"],
|
||||
[
|
||||
"temp-directory",
|
||||
None,
|
||||
"_trial_temp",
|
||||
"Path to use as working directory for tests.",
|
||||
],
|
||||
[
|
||||
"reporter",
|
||||
None,
|
||||
"verbose",
|
||||
"The reporter to use for this test run. See --help-reporters for "
|
||||
"more info.",
|
||||
],
|
||||
]
|
||||
|
||||
compData = usage.Completions(
|
||||
optActions={
|
||||
"order": usage.CompleteList(_runOrders),
|
||||
"reporter": _reporterAction,
|
||||
"logfile": usage.CompleteFiles(descr="log file name"),
|
||||
"random": usage.Completer(descr="random seed"),
|
||||
},
|
||||
extraActions=[
|
||||
usage.CompleteFiles(
|
||||
"*.py",
|
||||
descr="file | module | package | TestCase | testMethod",
|
||||
repeat=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
tracer: Optional[trace.Trace] = None
|
||||
|
||||
def __init__(self):
|
||||
self["tests"] = []
|
||||
usage.Options.__init__(self)
|
||||
|
||||
def getSynopsis(self):
|
||||
executableName = reflect.filenameToModuleName(sys.argv[0])
|
||||
|
||||
if executableName.endswith(".__main__"):
|
||||
executableName = "{} -m {}".format(
|
||||
os.path.basename(sys.executable),
|
||||
executableName.replace(".__main__", ""),
|
||||
)
|
||||
|
||||
return """{} [options] [[file|package|module|TestCase|testmethod]...]
|
||||
""".format(
|
||||
executableName,
|
||||
)
|
||||
|
||||
def coverdir(self):
|
||||
"""
|
||||
Return a L{FilePath} representing the directory into which coverage
|
||||
results should be written.
|
||||
"""
|
||||
coverdir = "coverage"
|
||||
result = FilePath(self["temp-directory"]).child(coverdir)
|
||||
print(f"Setting coverage directory to {result.path}.")
|
||||
return result
|
||||
|
||||
# TODO: Some of the opt_* methods on this class have docstrings and some do
|
||||
# not. This is mostly because usage.Options's currently will replace
|
||||
# any intended output in optFlags and optParameters with the
|
||||
# docstring. See #6427. When that is fixed, all methods should be
|
||||
# given docstrings (and it should be verified that those with
|
||||
# docstrings already have content suitable for printing as usage
|
||||
# information).
|
||||
|
||||
def opt_coverage(self):
|
||||
"""
|
||||
Generate coverage information in the coverage file in the
|
||||
directory specified by the temp-directory option.
|
||||
"""
|
||||
self.tracer = trace.Trace(count=1, trace=0)
|
||||
sys.settrace(self.tracer.globaltrace)
|
||||
self["coverage"] = True
|
||||
|
||||
def opt_testmodule(self, filename):
|
||||
"""
|
||||
Filename to grep for test cases (-*- test-case-name).
|
||||
"""
|
||||
# If the filename passed to this parameter looks like a test module
|
||||
# we just add that to the test suite.
|
||||
#
|
||||
# If not, we inspect it for an Emacs buffer local variable called
|
||||
# 'test-case-name'. If that variable is declared, we try to add its
|
||||
# value to the test suite as a module.
|
||||
#
|
||||
# This parameter allows automated processes (like Buildbot) to pass
|
||||
# a list of files to Trial with the general expectation of "these files,
|
||||
# whatever they are, will get tested"
|
||||
if not os.path.isfile(filename):
|
||||
sys.stderr.write(f"File {filename!r} doesn't exist\n")
|
||||
return
|
||||
filename = os.path.abspath(filename)
|
||||
if isTestFile(filename):
|
||||
self["tests"].append(filename)
|
||||
else:
|
||||
self["tests"].extend(getTestModules(filename))
|
||||
|
||||
def opt_spew(self):
|
||||
"""
|
||||
Print an insanely verbose log of everything that happens. Useful
|
||||
when debugging freezes or locks in complex code.
|
||||
"""
|
||||
from twisted.python.util import spewer
|
||||
|
||||
sys.settrace(spewer)
|
||||
|
||||
def opt_help_orders(self):
|
||||
synopsis = (
|
||||
"Trial can attempt to run test cases and their methods in "
|
||||
"a few different orders. You can select any of the "
|
||||
"following options using --order=<foo>.\n"
|
||||
)
|
||||
|
||||
print(synopsis)
|
||||
for name, (description, _) in sorted(_runOrders.items()):
|
||||
print(" ", name, "\t", description)
|
||||
sys.exit(0)
|
||||
|
||||
def opt_help_reporters(self):
|
||||
synopsis = (
|
||||
"Trial's output can be customized using plugins called "
|
||||
"Reporters. You can\nselect any of the following "
|
||||
"reporters using --reporter=<foo>\n"
|
||||
)
|
||||
print(synopsis)
|
||||
for p in plugin.getPlugins(itrial.IReporter):
|
||||
print(" ", p.longOpt, "\t", p.description)
|
||||
sys.exit(0)
|
||||
|
||||
def opt_disablegc(self):
|
||||
"""
|
||||
Disable the garbage collector
|
||||
"""
|
||||
self["disablegc"] = True
|
||||
gc.disable()
|
||||
|
||||
def opt_tbformat(self, opt):
|
||||
"""
|
||||
Specify the format to display tracebacks with. Valid formats are
|
||||
'plain', 'emacs', and 'cgitb' which uses the nicely verbose stdlib
|
||||
cgitb.text function
|
||||
"""
|
||||
try:
|
||||
self["tbformat"] = TBFORMAT_MAP[opt]
|
||||
except KeyError:
|
||||
raise usage.UsageError("tbformat must be 'plain', 'emacs', or 'cgitb'.")
|
||||
|
||||
def opt_recursionlimit(self, arg):
|
||||
"""
|
||||
see sys.setrecursionlimit()
|
||||
"""
|
||||
try:
|
||||
sys.setrecursionlimit(int(arg))
|
||||
except (TypeError, ValueError):
|
||||
raise usage.UsageError("argument to recursionlimit must be an integer")
|
||||
else:
|
||||
self["recursionlimit"] = int(arg)
|
||||
|
||||
def opt_random(self, option):
|
||||
try:
|
||||
self["random"] = int(option)
|
||||
except ValueError:
|
||||
raise usage.UsageError("Argument to --random must be a positive integer")
|
||||
else:
|
||||
if self["random"] < 0:
|
||||
raise usage.UsageError(
|
||||
"Argument to --random must be a positive integer"
|
||||
)
|
||||
elif self["random"] == 0:
|
||||
self["random"] = int(time.time() * 100)
|
||||
|
||||
def opt_without_module(self, option):
|
||||
"""
|
||||
Fake the lack of the specified modules, separated with commas.
|
||||
"""
|
||||
self["without-module"] = option
|
||||
for module in option.split(","):
|
||||
if module in sys.modules:
|
||||
warnings.warn(
|
||||
"Module '%s' already imported, " "disabling anyway." % (module,),
|
||||
category=RuntimeWarning,
|
||||
)
|
||||
sys.modules[module] = None
|
||||
|
||||
def parseArgs(self, *args):
|
||||
self["tests"].extend(args)
|
||||
|
||||
def _loadReporterByName(self, name):
|
||||
for p in plugin.getPlugins(itrial.IReporter):
|
||||
qual = f"{p.module}.{p.klass}"
|
||||
if p.longOpt == name:
|
||||
return reflect.namedAny(qual)
|
||||
raise usage.UsageError(
|
||||
"Only pass names of Reporter plugins to "
|
||||
"--reporter. See --help-reporters for "
|
||||
"more info."
|
||||
)
|
||||
|
||||
def postOptions(self):
|
||||
# Only load reporters now, as opposed to any earlier, to avoid letting
|
||||
# application-defined plugins muck up reactor selecting by importing
|
||||
# t.i.reactor and causing the default to be installed.
|
||||
self["reporter"] = self._loadReporterByName(self["reporter"])
|
||||
if "tbformat" not in self:
|
||||
self["tbformat"] = "default"
|
||||
if self["order"] is not None and self["random"] is not None:
|
||||
raise usage.UsageError("You can't specify --random when using --order")
|
||||
|
||||
|
||||
class Options(_BasicOptions, usage.Options, app.ReactorSelectionMixin):
|
||||
"""
|
||||
Options to the trial command line tool.
|
||||
|
||||
@ivar _workerFlags: List of flags which are accepted by trial distributed
|
||||
workers. This is used by C{_getWorkerArguments} to build the command
|
||||
line arguments.
|
||||
@type _workerFlags: C{list}
|
||||
|
||||
@ivar _workerParameters: List of parameter which are accepted by trial
|
||||
distributed workers. This is used by C{_getWorkerArguments} to build
|
||||
the command line arguments.
|
||||
@type _workerParameters: C{list}
|
||||
"""
|
||||
|
||||
optFlags = [
|
||||
[
|
||||
"debug",
|
||||
"b",
|
||||
"Run tests in a debugger. If that debugger is "
|
||||
"pdb, will load '.pdbrc' from current directory if it exists.",
|
||||
],
|
||||
[
|
||||
"debug-stacktraces",
|
||||
"B",
|
||||
"Report Deferred creation and " "callback stack traces",
|
||||
],
|
||||
[
|
||||
"nopm",
|
||||
None,
|
||||
"don't automatically jump into debugger for " "postmorteming of exceptions",
|
||||
],
|
||||
["dry-run", "n", "do everything but run the tests"],
|
||||
["profile", None, "Run tests under the Python profiler"],
|
||||
["until-failure", "u", "Repeat test until it fails"],
|
||||
]
|
||||
|
||||
optParameters = [
|
||||
[
|
||||
"debugger",
|
||||
None,
|
||||
"pdb",
|
||||
"the fully qualified name of a debugger to " "use if --debug is passed",
|
||||
],
|
||||
["logfile", "l", "test.log", "log file name"],
|
||||
["jobs", "j", None, "Number of local workers to run"],
|
||||
]
|
||||
|
||||
compData = usage.Completions(
|
||||
optActions={
|
||||
"tbformat": usage.CompleteList(["plain", "emacs", "cgitb"]),
|
||||
"reporter": _reporterAction,
|
||||
},
|
||||
)
|
||||
|
||||
_workerFlags = ["disablegc", "force-gc", "coverage"]
|
||||
_workerParameters = ["recursionlimit", "reactor", "without-module"]
|
||||
|
||||
def opt_jobs(self, number):
|
||||
"""
|
||||
Number of local workers to run, a strictly positive integer or 'auto'
|
||||
to spawn one worker for each available CPU.
|
||||
"""
|
||||
if number == "auto":
|
||||
number = _autoJobs()
|
||||
else:
|
||||
try:
|
||||
number = int(number)
|
||||
except ValueError:
|
||||
raise usage.UsageError(
|
||||
"Expecting integer argument to jobs, got '%s'" % number
|
||||
)
|
||||
if number <= 0:
|
||||
raise usage.UsageError(
|
||||
"Argument to jobs must be a strictly positive integer or 'auto'"
|
||||
)
|
||||
self["jobs"] = number
|
||||
|
||||
def _getWorkerArguments(self):
|
||||
"""
|
||||
Return a list of options to pass to distributed workers.
|
||||
"""
|
||||
args = []
|
||||
for option in self._workerFlags:
|
||||
if self.get(option) is not None:
|
||||
if self[option]:
|
||||
args.append(f"--{option}")
|
||||
for option in self._workerParameters:
|
||||
if self.get(option) is not None:
|
||||
args.extend([f"--{option}", str(self[option])])
|
||||
return args
|
||||
|
||||
def postOptions(self):
|
||||
_BasicOptions.postOptions(self)
|
||||
if self["jobs"]:
|
||||
conflicts = ["debug", "profile", "debug-stacktraces"]
|
||||
for option in conflicts:
|
||||
if self[option]:
|
||||
raise usage.UsageError(
|
||||
"You can't specify --%s when using --jobs" % option
|
||||
)
|
||||
if self["nopm"]:
|
||||
if not self["debug"]:
|
||||
raise usage.UsageError("You must specify --debug when using " "--nopm ")
|
||||
failure.DO_POST_MORTEM = False
|
||||
|
||||
|
||||
def _initialDebugSetup(config: Options) -> None:
|
||||
# do this part of debug setup first for easy debugging of import failures
|
||||
if config["debug"]:
|
||||
failure.startDebugMode()
|
||||
if config["debug"] or config["debug-stacktraces"]:
|
||||
defer.setDebugging(True)
|
||||
|
||||
|
||||
def _getSuite(config: Options) -> TestSuite:
|
||||
loader = _getLoader(config)
|
||||
recurse = not config["no-recurse"]
|
||||
return loader.loadByNames(config["tests"], recurse=recurse)
|
||||
|
||||
|
||||
def _getLoader(config: Options) -> runner.TestLoader:
|
||||
loader = runner.TestLoader()
|
||||
if config["random"]:
|
||||
randomer = random.Random()
|
||||
randomer.seed(config["random"])
|
||||
loader.sorter = lambda x: randomer.random()
|
||||
print("Running tests shuffled with seed %d\n" % config["random"])
|
||||
elif config["order"]:
|
||||
_, sorter = _runOrders[config["order"]]
|
||||
loader.sorter = sorter
|
||||
if not config["until-failure"]:
|
||||
loader.suiteFactory = runner.DestructiveTestSuite
|
||||
return loader
|
||||
|
||||
|
||||
def _wrappedPdb():
|
||||
"""
|
||||
Wrap an instance of C{pdb.Pdb} with readline support and load any .rcs.
|
||||
|
||||
"""
|
||||
|
||||
dbg = pdb.Pdb()
|
||||
try:
|
||||
namedModule("readline")
|
||||
except ImportError:
|
||||
print("readline module not available")
|
||||
for path in (".pdbrc", "pdbrc"):
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
rcFile = open(path)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
with rcFile:
|
||||
dbg.rcLines.extend(rcFile.readlines())
|
||||
return dbg
|
||||
|
||||
|
||||
class _DebuggerNotFound(Exception):
|
||||
"""
|
||||
A debugger import failed.
|
||||
|
||||
Used to allow translating these errors into usage error messages.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def _makeRunner(config: Options) -> runner._Runner:
|
||||
"""
|
||||
Return a trial runner class set up with the parameters extracted from
|
||||
C{config}.
|
||||
|
||||
@return: A trial runner instance.
|
||||
"""
|
||||
cls: Type[runner._Runner] = runner.TrialRunner
|
||||
args = {
|
||||
"reporterFactory": config["reporter"],
|
||||
"tracebackFormat": config["tbformat"],
|
||||
"realTimeErrors": config["rterrors"],
|
||||
"uncleanWarnings": config["unclean-warnings"],
|
||||
"logfile": config["logfile"],
|
||||
"workingDirectory": config["temp-directory"],
|
||||
"exitFirst": config["exitfirst"],
|
||||
}
|
||||
if config["dry-run"]:
|
||||
args["mode"] = runner.TrialRunner.DRY_RUN
|
||||
elif config["jobs"]:
|
||||
cls = DistTrialRunner
|
||||
args["maxWorkers"] = config["jobs"]
|
||||
args["workerArguments"] = config._getWorkerArguments()
|
||||
else:
|
||||
if config["debug"]:
|
||||
args["mode"] = runner.TrialRunner.DEBUG
|
||||
debugger = config["debugger"]
|
||||
|
||||
if debugger != "pdb":
|
||||
try:
|
||||
args["debugger"] = reflect.namedAny(debugger)
|
||||
except reflect.ModuleNotFound:
|
||||
raise _DebuggerNotFound(
|
||||
f"{debugger!r} debugger could not be found."
|
||||
)
|
||||
else:
|
||||
args["debugger"] = _wrappedPdb()
|
||||
|
||||
args["profile"] = config["profile"]
|
||||
args["forceGarbageCollection"] = config["force-gc"]
|
||||
|
||||
return cls(**args)
|
||||
|
||||
|
||||
def run() -> NoReturn:
|
||||
if len(sys.argv) == 1:
|
||||
sys.argv.append("--help")
|
||||
config = Options()
|
||||
try:
|
||||
config.parseOptions()
|
||||
except usage.error as ue:
|
||||
raise SystemExit(f"{sys.argv[0]}: {ue}")
|
||||
_initialDebugSetup(config)
|
||||
|
||||
try:
|
||||
trialRunner = _makeRunner(config)
|
||||
except _DebuggerNotFound as e:
|
||||
raise SystemExit(f"{sys.argv[0]}: {str(e)}")
|
||||
|
||||
suite = _getSuite(config)
|
||||
if config["until-failure"]:
|
||||
testResult = trialRunner.runUntilFailure(suite)
|
||||
else:
|
||||
testResult = trialRunner.run(suite)
|
||||
if config.tracer:
|
||||
sys.settrace(None)
|
||||
results = config.tracer.results()
|
||||
results.write_results(
|
||||
show_missing=True, summary=False, coverdir=config.coverdir().path
|
||||
)
|
||||
sys.exit(not testResult.wasSuccessful())
|
||||
38
.venv/lib/python3.12/site-packages/twisted/scripts/twistd.py
Normal file
38
.venv/lib/python3.12/site-packages/twisted/scripts/twistd.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- test-case-name: twisted.test.test_twistd -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
The Twisted Daemon: platform-independent interface.
|
||||
|
||||
@author: Christopher Armstrong
|
||||
"""
|
||||
|
||||
|
||||
from twisted.application import app
|
||||
from twisted.python.runtime import platformType
|
||||
|
||||
if platformType == "win32":
|
||||
from twisted.scripts._twistw import (
|
||||
ServerOptions,
|
||||
WindowsApplicationRunner as _SomeApplicationRunner,
|
||||
)
|
||||
else:
|
||||
from twisted.scripts._twistd_unix import ( # type: ignore[assignment]
|
||||
ServerOptions,
|
||||
UnixApplicationRunner as _SomeApplicationRunner,
|
||||
)
|
||||
|
||||
|
||||
def runApp(config):
|
||||
runner = _SomeApplicationRunner(config)
|
||||
runner.run()
|
||||
if runner._exitSignal is not None:
|
||||
app._exitWithSignal(runner._exitSignal)
|
||||
|
||||
|
||||
def run():
|
||||
app.run(runApp, ServerOptions)
|
||||
|
||||
|
||||
__all__ = ["run", "runApp"]
|
||||
Reference in New Issue
Block a user