def setContent(self, content, ext='.new'): """ Replace the file at this path with a new file that contains the given bytes, trying to avoid data-loss in the meanwhile. On UNIX-like platforms, this method does its best to ensure that by the time this method returns, either the old contents I{or} the new contents of the file will be present at this path for subsequent readers regardless of premature device removal, program crash, or power loss, making the following assumptions: - your filesystem is journaled (i.e. your filesystem will not I{itself} lose data due to power loss) - your filesystem's C{rename()} is atomic - your filesystem will not discard new data while preserving new metadata (see U{http://mjg59.livejournal.com/108257.html} for more detail) On most versions of Windows there is no atomic C{rename()} (see U{http://bit.ly/win32-overwrite} for more information), so this method is slightly less helpful. There is a small window where the file at this path may be deleted before the new file is moved to replace it: however, the new file will be fully written and flushed beforehand so in the unlikely event that there is a crash at that point, it should be possible for the user to manually recover the new version of their data. In the future, Twisted will support atomic file moves on those versions of Windows which I{do} support them: see U{Twisted ticket 3004<http://twistedmatrix.com/trac/ticket/3004>}. This method should be safe for use by multiple concurrent processes, but note that it is not easy to predict which process's contents will ultimately end up on disk if they invoke this method at close to the same time. @param content: The desired contents of the file at this path. @type content: L{str} @param ext: An extension to append to the temporary filename used to store the bytes while they are being written. This can be used to make sure that temporary files can be identified by their suffix, for cleanup in case of crashes. @type ext: C{str} """ sib = self.temporarySibling(ext) f = sib.open('w') try: f.write(content) finally: f.close() if platform.isWindows() and exists(self.path): os.unlink(self.path) os.rename(sib.path, self.path)
def child(self, path): if platform.isWindows() and path.count(":"): # Catch paths like C:blah that don't have a slash raise InsecurePath("%r contains a colon." % (path,)) norm = normpath(path) if slash in norm: raise InsecurePath("%r contains one or more directory separators" % (path,)) newpath = abspath(joinpath(self.path, norm)) if not newpath.startswith(self.path): raise InsecurePath("%r is not a child of %s" % (newpath, self.path)) return self.clonePath(newpath)
class PTYProcessTestsBuilder(ProcessTestsBuilderBase): """ Builder defining tests relating to L{IReactorProcess} for child processes which have a PTY. """ usePTY = True if platform.isWindows(): skip = "PTYs are not supported on Windows." elif platform.isMacOSX(): skippedReactors = { "twisted.internet.pollreactor.PollReactor": "OS X's poll() does not support PTYs" }
def cbExited((failure, )): # Trapping implicitly verifies that it's a Failure (rather than # an exception) and explicitly makes sure it's the right type. failure.trap(ProcessTerminated) err = failure.value if platform.isWindows(): # Windows can't really /have/ signals, so it certainly can't # report them as the reason for termination. Maybe there's # something better we could be doing here, anyway? Hard to # say. Anyway, this inconsistency between different platforms # is extremely unfortunate and I would remove it if I # could. -exarkun self.assertIdentical(err.signal, None) self.assertEqual(err.exitCode, 1) else: self.assertEqual(err.signal, sigNum) self.assertIdentical(err.exitCode, None)
def cbExited((failure,)): # Trapping implicitly verifies that it's a Failure (rather than # an exception) and explicitly makes sure it's the right type. failure.trap(ProcessTerminated) err = failure.value if platform.isWindows(): # Windows can't really /have/ signals, so it certainly can't # report them as the reason for termination. Maybe there's # something better we could be doing here, anyway? Hard to # say. Anyway, this inconsistency between different platforms # is extremely unfortunate and I would remove it if I # could. -exarkun self.assertIdentical(err.signal, None) self.assertEqual(err.exitCode, 1) else: self.assertEqual(err.signal, sigNum) self.assertIdentical(err.exitCode, None)
# Copyright (c) 2009 Twisted Matrix Laboratories. # See LICENSE for details. import sys from reqs.twisted.trial import unittest from reqs.twisted.python.runtime import platform from reqs.twisted.python.util import sibpath from reqs.twisted.internet.utils import getProcessOutputAndValue skipWindowsNopywin32 = None if platform.isWindows(): try: import win32process except ImportError: skipWindowsNopywin32 = ("On windows, spawnProcess is not available " "in the absence of win32process.") class QtreactorTestCase(unittest.TestCase): """ Tests for L{twisted.internet.qtreactor}. """ def test_importQtreactor(self): """ Attempting to import L{twisted.internet.qtreactor} should raise an C{ImportError} indicating that C{qtreactor} is no longer a part of Twisted. """ sys.modules["qtreactor"] = None from reqs.twisted.plugins.twisted_qtstub import errorMessage
# Copyright (c) 2009 Twisted Matrix Laboratories. # See LICENSE for details. import sys from reqs.twisted.trial import unittest from reqs.twisted.python.runtime import platform from reqs.twisted.python.util import sibpath from reqs.twisted.internet.utils import getProcessOutputAndValue skipWindowsNopywin32 = None if platform.isWindows(): try: import win32process except ImportError: skipWindowsNopywin32 = ("On windows, spawnProcess is not available " "in the absence of win32process.") class QtreactorTestCase(unittest.TestCase): """ Tests for L{twisted.internet.qtreactor}. """ def test_importQtreactor(self): """ Attempting to import L{twisted.internet.qtreactor} should raise an C{ImportError} indicating that C{qtreactor} is no longer a part of Twisted. """ sys.modules["qtreactor"] = None from reqs.twisted.plugins.twisted_qtstub import errorMessage
class ProcessTestsBuilder(ProcessTestsBuilderBase): """ Builder defining tests relating to L{IReactorProcess} for child processes which do not have a PTY. """ usePTY = False keepStdioOpenProgram = FilePath(__file__).sibling('process_helper.py').path if platform.isWindows(): keepStdioOpenArg = "windows" else: # Just a value that doesn't equal "windows" keepStdioOpenArg = "" # Define this test here because PTY-using processes only have stdin and # stdout and the test would need to be different for that to work. def test_childConnectionLost(self): """ L{IProcessProtocol.childConnectionLost} is called each time a file descriptor associated with a child process is closed. """ connected = Deferred() lost = {0: Deferred(), 1: Deferred(), 2: Deferred()} class Closer(ProcessProtocol): def makeConnection(self, transport): connected.callback(transport) def childConnectionLost(self, childFD): lost[childFD].callback(None) source = ("import os, sys\n" "while 1:\n" " line = sys.stdin.readline().strip()\n" " if not line:\n" " break\n" " os.close(int(line))\n") reactor = self.buildReactor() reactor.callWhenRunning(reactor.spawnProcess, Closer(), sys.executable, [sys.executable, "-c", source], usePTY=self.usePTY) def cbConnected(transport): transport.write('2\n') return lost[2].addCallback(lambda ign: transport) connected.addCallback(cbConnected) def lostSecond(transport): transport.write('1\n') return lost[1].addCallback(lambda ign: transport) connected.addCallback(lostSecond) def lostFirst(transport): transport.write('\n') connected.addCallback(lostFirst) connected.addErrback(err) def cbEnded(ignored): reactor.stop() connected.addCallback(cbEnded) self.runReactor(reactor) # This test is here because PTYProcess never delivers childConnectionLost. def test_processEnded(self): """ L{IProcessProtocol.processEnded} is called after the child process exits and L{IProcessProtocol.childConnectionLost} is called for each of its file descriptors. """ ended = Deferred() lost = [] class Ender(ProcessProtocol): def childDataReceived(self, fd, data): msg('childDataReceived(%d, %r)' % (fd, data)) self.transport.loseConnection() def childConnectionLost(self, childFD): msg('childConnectionLost(%d)' % (childFD, )) lost.append(childFD) def processExited(self, reason): msg('processExited(%r)' % (reason, )) def processEnded(self, reason): msg('processEnded(%r)' % (reason, )) ended.callback([reason]) reactor = self.buildReactor() reactor.callWhenRunning(reactor.spawnProcess, Ender(), sys.executable, [ sys.executable, self.keepStdioOpenProgram, "child", self.keepStdioOpenArg ], usePTY=self.usePTY) def cbEnded((failure, )): failure.trap(ProcessDone) self.assertEqual(set(lost), set([0, 1, 2])) ended.addCallback(cbEnded) ended.addErrback(err) ended.addCallback(lambda ign: reactor.stop()) self.runReactor(reactor) # This test is here because PTYProcess.loseConnection does not actually # close the file descriptors to the child process. This test needs to be # written fairly differently for PTYProcess. def test_processExited(self): """ L{IProcessProtocol.processExited} is called when the child process exits, even if file descriptors associated with the child are still open. """ exited = Deferred() allLost = Deferred() lost = [] class Waiter(ProcessProtocol): def childDataReceived(self, fd, data): msg('childDataReceived(%d, %r)' % (fd, data)) def childConnectionLost(self, childFD): msg('childConnectionLost(%d)' % (childFD, )) lost.append(childFD) if len(lost) == 3: allLost.callback(None) def processExited(self, reason): msg('processExited(%r)' % (reason, )) # See test_processExitedWithSignal exited.callback([reason]) self.transport.loseConnection() reactor = self.buildReactor() reactor.callWhenRunning(reactor.spawnProcess, Waiter(), sys.executable, [ sys.executable, self.keepStdioOpenProgram, "child", self.keepStdioOpenArg ], usePTY=self.usePTY) def cbExited((failure, )): failure.trap(ProcessDone) msg('cbExited; lost = %s' % (lost, )) self.assertEqual(lost, []) return allLost exited.addCallback(cbExited) def cbAllLost(ignored): self.assertEqual(set(lost), set([0, 1, 2])) exited.addCallback(cbAllLost) exited.addErrback(err) exited.addCallback(lambda ign: reactor.stop()) self.runReactor(reactor) def makeSourceFile(self, sourceLines): """ Write the given list of lines to a text file and return the absolute path to it. """ script = self.mktemp() scriptFile = file(script, 'wt') scriptFile.write(os.linesep.join(sourceLines) + os.linesep) scriptFile.close() return os.path.abspath(script) def test_shebang(self): """ Spawning a process with an executable which is a script starting with an interpreter definition line (#!) uses that interpreter to evaluate the script. """ SHEBANG_OUTPUT = 'this is the shebang output' scriptFile = self.makeSourceFile([ "#!%s" % (sys.executable, ), "import sys", "sys.stdout.write('%s')" % (SHEBANG_OUTPUT, ), "sys.stdout.flush()" ]) os.chmod(scriptFile, 0700) reactor = self.buildReactor() def cbProcessExited((out, err, code)): msg("cbProcessExited((%r, %r, %d))" % (out, err, code)) self.assertEqual(out, SHEBANG_OUTPUT) self.assertEqual(err, "") self.assertEqual(code, 0) def shutdown(passthrough): reactor.stop() return passthrough def start(): d = utils.getProcessOutputAndValue(scriptFile, reactor=reactor) d.addBoth(shutdown) d.addCallback(cbProcessExited) d.addErrback(err) reactor.callWhenRunning(start) self.runReactor(reactor) def test_processCommandLineArguments(self): """ Arguments given to spawnProcess are passed to the child process as originally intended. """ source = ( # On Windows, stdout is not opened in binary mode by default, # so newline characters are munged on writing, interfering with # the tests. 'import sys, os\n' 'try:\n' ' import msvcrt\n' ' msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)\n' 'except ImportError:\n' ' pass\n' 'for arg in sys.argv[1:]:\n' ' sys.stdout.write(arg + chr(0))\n' ' sys.stdout.flush()') args = ['hello', '"', ' \t|<>^&', r'"\\"hello\\"', r'"foo\ bar baz\""'] # Ensure that all non-NUL characters can be passed too. args.append(''.join(map(chr, xrange(1, 256)))) reactor = self.buildReactor() def processFinished(output): output = output.split('\0') # Drop the trailing \0. output.pop() self.assertEquals(args, output) def shutdown(result): reactor.stop() return result def spawnChild(): d = succeed(None) d.addCallback(lambda dummy: utils.getProcessOutput( sys.executable, ['-c', source] + args, reactor=reactor)) d.addCallback(processFinished) d.addBoth(shutdown) reactor.callWhenRunning(spawnChild) self.runReactor(reactor)
class SSLClientTestsMixin(ReactorBuilder): """ Mixin defining tests relating to L{ITLSTransport}. """ if FILETYPE_PEM is None: skip = "OpenSSL is unavailable" if platform.isWindows(): msg = ( "For some reason, these reactors don't deal with SSL " "disconnection correctly on Windows. See #3371.") skippedReactors = { "twisted.internet.glib2reactor.Glib2Reactor": msg, "twisted.internet.gtk2reactor.Gtk2Reactor": msg} _certificateText = ( "-----BEGIN CERTIFICATE-----\n" "MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER\n" "MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD\n" "ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n\n" "cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL\n" "MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv\n" "c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB\n" "BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh\n" "5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC\n" "MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl\n" "MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7\n" "hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT\n" "CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw\n" "dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx\n" "LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6\n" "BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++\n" "7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE\n" "WUQ9Ho4EzbYCOQ==\n" "-----END CERTIFICATE-----\n") _privateKeyText = ( "-----BEGIN RSA PRIVATE KEY-----\n" "MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh\n" "5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx\n" "OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT\n" "ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4\n" "nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2\n" "HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA\n" "oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=\n" "-----END RSA PRIVATE KEY-----\n") def getServerContext(self): """ Return a new SSL context suitable for use in a test server. """ cert = PrivateCertificate.load( self._certificateText, KeyPair.load(self._privateKeyText, FILETYPE_PEM), FILETYPE_PEM) return cert.options() def test_disconnectAfterWriteAfterStartTLS(self): """ L{ITCPTransport.loseConnection} ends a connection which was set up with L{ITLSTransport.startTLS} and which has recently been written to. This is intended to verify that a socket send error masked by the TLS implementation doesn't prevent the connection from being reported as closed. """ class ShortProtocol(Protocol): def connectionMade(self): if not ITLSTransport.providedBy(self.transport): # Functionality isn't available to be tested. finished = self.factory.finished self.factory.finished = None finished.errback(SkipTest("No ITLSTransport support")) return # Switch the transport to TLS. self.transport.startTLS(self.factory.context) # Force TLS to really get negotiated. If nobody talks, nothing # will happen. self.transport.write("x") def dataReceived(self, data): # Stuff some bytes into the socket. This mostly has the effect # of causing the next write to fail with ENOTCONN or EPIPE. # With the pyOpenSSL implementation of ITLSTransport, the error # is swallowed outside of the control of Twisted. self.transport.write("y") # Now close the connection, which requires a TLS close alert to # be sent. self.transport.loseConnection() def connectionLost(self, reason): # This is the success case. The client and the server want to # get here. finished = self.factory.finished if finished is not None: self.factory.finished = None finished.callback(reason) serverFactory = ServerFactory() serverFactory.finished = Deferred() serverFactory.protocol = ShortProtocol serverFactory.context = self.getServerContext() clientFactory = ClientFactory() clientFactory.finished = Deferred() clientFactory.protocol = ShortProtocol clientFactory.context = ClientContextFactory() clientFactory.context.method = serverFactory.context.method lostConnectionResults = [] finished = DeferredList( [serverFactory.finished, clientFactory.finished], consumeErrors=True) def cbFinished(results): lostConnectionResults.extend([results[0][1], results[1][1]]) finished.addCallback(cbFinished) reactor = self.buildReactor() port = reactor.listenTCP(0, serverFactory, interface='127.0.0.1') self.addCleanup(port.stopListening) connector = reactor.connectTCP( port.getHost().host, port.getHost().port, clientFactory) self.addCleanup(connector.disconnect) finished.addCallback(lambda ign: reactor.stop()) self.runReactor(reactor) lostConnectionResults[0].trap(ConnectionClosed) lostConnectionResults[1].trap(ConnectionClosed)