17.12
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# -*- test-case-name: twisted.trial._dist.test -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
This package implements the distributed Trial test runner:
|
||||
|
||||
- The L{twisted.trial._dist.disttrial} module implements a test runner which
|
||||
runs in a manager process and can launch additional worker processes in
|
||||
which to run tests and gather up results from all of them.
|
||||
|
||||
- The L{twisted.trial._dist.options} module defines command line options used
|
||||
to configure the distributed test runner.
|
||||
|
||||
- The L{twisted.trial._dist.managercommands} module defines AMP commands
|
||||
which are sent from worker processes back to the manager process to report
|
||||
the results of tests.
|
||||
|
||||
- The L{twisted.trial._dist.workercommands} module defines AMP commands which
|
||||
are sent from the manager process to the worker processes to control the
|
||||
execution of tests there.
|
||||
|
||||
- The L{twisted.trial._dist.distreporter} module defines a proxy for
|
||||
L{twisted.trial.itrial.IReporter} which enforces the typical requirement
|
||||
that results be passed to a reporter for only one test at a time, allowing
|
||||
any reporter to be used with despite disttrial's simultaneously running
|
||||
tests.
|
||||
|
||||
- The L{twisted.trial._dist.workerreporter} module implements a
|
||||
L{twisted.trial.itrial.IReporter} which is used by worker processes and
|
||||
reports results back to the manager process using AMP commands.
|
||||
|
||||
- The L{twisted.trial._dist.workertrial} module is a runnable script which is
|
||||
the main point for worker processes.
|
||||
|
||||
- The L{twisted.trial._dist.worker} process defines the manager's AMP
|
||||
protocol for accepting results from worker processes and a process protocol
|
||||
for use running workers as local child processes (as opposed to
|
||||
distributing them to another host).
|
||||
|
||||
@since: 12.3
|
||||
"""
|
||||
|
||||
# File descriptors numbers used to set up pipes with the worker.
|
||||
_WORKER_AMP_STDIN = 3
|
||||
|
||||
_WORKER_AMP_STDOUT = 4
|
||||
@@ -0,0 +1,470 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test for distributed trial worker side.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from zope.interface.verify import verifyObject
|
||||
|
||||
from twisted.trial.reporter import TestResult
|
||||
from twisted.trial.unittest import TestCase
|
||||
from twisted.trial._dist.worker import (
|
||||
LocalWorker, LocalWorkerAMP, LocalWorkerTransport, WorkerProtocol)
|
||||
from twisted.trial._dist import managercommands, workercommands
|
||||
|
||||
from twisted.scripts import trial
|
||||
from twisted.test.proto_helpers import StringTransport
|
||||
|
||||
from twisted.internet.interfaces import ITransport, IAddress
|
||||
from twisted.internet.defer import fail, succeed
|
||||
from twisted.internet.main import CONNECTION_DONE
|
||||
from twisted.internet.error import ConnectionDone
|
||||
from twisted.python.reflect import fullyQualifiedName
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.protocols.amp import AMP
|
||||
from twisted.python.compat import NativeStringIO
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class FakeAMP(AMP):
|
||||
"""
|
||||
A fake amp protocol.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class WorkerProtocolTests(TestCase):
|
||||
"""
|
||||
Tests for L{WorkerProtocol}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up a transport, a result stream and a protocol instance.
|
||||
"""
|
||||
self.serverTransport = StringTransport()
|
||||
self.clientTransport = StringTransport()
|
||||
self.server = WorkerProtocol()
|
||||
self.server.makeConnection(self.serverTransport)
|
||||
self.client = FakeAMP()
|
||||
self.client.makeConnection(self.clientTransport)
|
||||
|
||||
|
||||
def test_run(self):
|
||||
"""
|
||||
Calling the L{workercommands.Run} command on the client returns a
|
||||
response with C{success} sets to C{True}.
|
||||
"""
|
||||
d = self.client.callRemote(workercommands.Run, testCase="doesntexist")
|
||||
|
||||
def check(result):
|
||||
self.assertTrue(result['success'])
|
||||
|
||||
d.addCallback(check)
|
||||
self.server.dataReceived(self.clientTransport.value())
|
||||
self.clientTransport.clear()
|
||||
self.client.dataReceived(self.serverTransport.value())
|
||||
self.serverTransport.clear()
|
||||
return d
|
||||
|
||||
|
||||
def test_start(self):
|
||||
"""
|
||||
The C{start} command changes the current path.
|
||||
"""
|
||||
curdir = os.path.realpath(os.path.curdir)
|
||||
self.addCleanup(os.chdir, curdir)
|
||||
self.server.start('..')
|
||||
self.assertNotEqual(os.path.realpath(os.path.curdir), curdir)
|
||||
|
||||
|
||||
|
||||
class LocalWorkerAMPTests(TestCase):
|
||||
"""
|
||||
Test case for distributed trial's manager-side local worker AMP protocol
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.managerTransport = StringTransport()
|
||||
self.managerAMP = LocalWorkerAMP()
|
||||
self.managerAMP.makeConnection(self.managerTransport)
|
||||
self.result = TestResult()
|
||||
self.workerTransport = StringTransport()
|
||||
self.worker = AMP()
|
||||
self.worker.makeConnection(self.workerTransport)
|
||||
|
||||
config = trial.Options()
|
||||
self.testName = "twisted.doesnexist"
|
||||
config['tests'].append(self.testName)
|
||||
self.testCase = trial._getSuite(config)._tests.pop()
|
||||
|
||||
self.managerAMP.run(self.testCase, self.result)
|
||||
self.managerTransport.clear()
|
||||
|
||||
|
||||
def pumpTransports(self):
|
||||
"""
|
||||
Sends data from C{self.workerTransport} to C{self.managerAMP}, and then
|
||||
data from C{self.managerTransport} back to C{self.worker}.
|
||||
"""
|
||||
self.managerAMP.dataReceived(self.workerTransport.value())
|
||||
self.workerTransport.clear()
|
||||
self.worker.dataReceived(self.managerTransport.value())
|
||||
|
||||
|
||||
def test_runSuccess(self):
|
||||
"""
|
||||
Run a test, and succeed.
|
||||
"""
|
||||
results = []
|
||||
|
||||
d = self.worker.callRemote(managercommands.AddSuccess,
|
||||
testName=self.testName)
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_runExpectedFailure(self):
|
||||
"""
|
||||
Run a test, and fail expectedly.
|
||||
"""
|
||||
results = []
|
||||
|
||||
d = self.worker.callRemote(managercommands.AddExpectedFailure,
|
||||
testName=self.testName, error='error',
|
||||
todo='todoReason')
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertEqual(self.testCase, self.result.expectedFailures[0][0])
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_runError(self):
|
||||
"""
|
||||
Run a test, and encounter an error.
|
||||
"""
|
||||
results = []
|
||||
errorClass = fullyQualifiedName(ValueError)
|
||||
d = self.worker.callRemote(managercommands.AddError,
|
||||
testName=self.testName, error='error',
|
||||
errorClass=errorClass,
|
||||
frames=[])
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertEqual(self.testCase, self.result.errors[0][0])
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_runErrorWithFrames(self):
|
||||
"""
|
||||
L{LocalWorkerAMP._buildFailure} recreates the C{Failure.frames} from
|
||||
the C{frames} argument passed to C{AddError}.
|
||||
"""
|
||||
results = []
|
||||
errorClass = fullyQualifiedName(ValueError)
|
||||
d = self.worker.callRemote(managercommands.AddError,
|
||||
testName=self.testName, error='error',
|
||||
errorClass=errorClass,
|
||||
frames=["file.py", "invalid code", "3"])
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertEqual(self.testCase, self.result.errors[0][0])
|
||||
self.assertEqual(
|
||||
[('file.py', 'invalid code', 3, [], [])],
|
||||
self.result.errors[0][1].frames)
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_runFailure(self):
|
||||
"""
|
||||
Run a test, and fail.
|
||||
"""
|
||||
results = []
|
||||
failClass = fullyQualifiedName(RuntimeError)
|
||||
d = self.worker.callRemote(managercommands.AddFailure,
|
||||
testName=self.testName, fail='fail',
|
||||
failClass=failClass,
|
||||
frames=[])
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertEqual(self.testCase, self.result.failures[0][0])
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_runSkip(self):
|
||||
"""
|
||||
Run a test, but skip it.
|
||||
"""
|
||||
results = []
|
||||
|
||||
d = self.worker.callRemote(managercommands.AddSkip,
|
||||
testName=self.testName, reason='reason')
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertEqual(self.testCase, self.result.skips[0][0])
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_runUnexpectedSuccesses(self):
|
||||
"""
|
||||
Run a test, and succeed unexpectedly.
|
||||
"""
|
||||
results = []
|
||||
|
||||
d = self.worker.callRemote(managercommands.AddUnexpectedSuccess,
|
||||
testName=self.testName,
|
||||
todo='todo')
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertEqual(self.testCase, self.result.unexpectedSuccesses[0][0])
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_testWrite(self):
|
||||
"""
|
||||
L{LocalWorkerAMP.testWrite} writes the data received to its test
|
||||
stream.
|
||||
"""
|
||||
results = []
|
||||
stream = NativeStringIO()
|
||||
self.managerAMP.setTestStream(stream)
|
||||
|
||||
command = managercommands.TestWrite
|
||||
d = self.worker.callRemote(command,
|
||||
out="Some output")
|
||||
d.addCallback(lambda result: results.append(result['success']))
|
||||
self.pumpTransports()
|
||||
|
||||
self.assertEqual("Some output\n", stream.getvalue())
|
||||
self.assertTrue(results)
|
||||
|
||||
|
||||
def test_stopAfterRun(self):
|
||||
"""
|
||||
L{LocalWorkerAMP.run} calls C{stopTest} on its test result once the
|
||||
C{Run} commands has succeeded.
|
||||
"""
|
||||
result = object()
|
||||
stopped = []
|
||||
|
||||
def fakeCallRemote(command, testCase):
|
||||
return succeed(result)
|
||||
|
||||
self.managerAMP.callRemote = fakeCallRemote
|
||||
|
||||
class StopTestResult(TestResult):
|
||||
|
||||
def stopTest(self, test):
|
||||
stopped.append(test)
|
||||
|
||||
|
||||
d = self.managerAMP.run(self.testCase, StopTestResult())
|
||||
self.assertEqual([self.testCase], stopped)
|
||||
return d.addCallback(self.assertIdentical, result)
|
||||
|
||||
|
||||
|
||||
class FakeAMProtocol(AMP):
|
||||
"""
|
||||
A fake implementation of L{AMP} for testing.
|
||||
"""
|
||||
id = 0
|
||||
dataString = b""
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.dataString += data
|
||||
|
||||
|
||||
def setTestStream(self, stream):
|
||||
self.testStream = stream
|
||||
|
||||
|
||||
|
||||
class FakeTransport(object):
|
||||
"""
|
||||
A fake process transport implementation for testing.
|
||||
"""
|
||||
dataString = b""
|
||||
calls = 0
|
||||
|
||||
def writeToChild(self, fd, data):
|
||||
self.dataString += data
|
||||
|
||||
|
||||
def loseConnection(self):
|
||||
self.calls += 1
|
||||
|
||||
|
||||
|
||||
class LocalWorkerTests(TestCase):
|
||||
"""
|
||||
Tests for L{LocalWorker} and L{LocalWorkerTransport}.
|
||||
"""
|
||||
|
||||
def tidyLocalWorker(self, *args, **kwargs):
|
||||
"""
|
||||
Create a L{LocalWorker}, connect it to a transport, and ensure
|
||||
its log files are closed.
|
||||
|
||||
@param args: See L{LocalWorker}
|
||||
|
||||
@param kwargs: See L{LocalWorker}
|
||||
|
||||
@return: a L{LocalWorker} instance
|
||||
"""
|
||||
worker = LocalWorker(*args, **kwargs)
|
||||
worker.makeConnection(FakeTransport())
|
||||
self.addCleanup(worker._testLog.close)
|
||||
self.addCleanup(worker._outLog.close)
|
||||
self.addCleanup(worker._errLog.close)
|
||||
return worker
|
||||
|
||||
|
||||
def test_childDataReceived(self):
|
||||
"""
|
||||
L{LocalWorker.childDataReceived} forwards the received data to linked
|
||||
L{AMP} protocol if the right file descriptor, otherwise forwards to
|
||||
C{ProcessProtocol.childDataReceived}.
|
||||
"""
|
||||
localWorker = self.tidyLocalWorker(FakeAMProtocol(), '.', 'test.log')
|
||||
localWorker._outLog = BytesIO()
|
||||
localWorker.childDataReceived(4, b"foo")
|
||||
localWorker.childDataReceived(1, b"bar")
|
||||
self.assertEqual(b"foo", localWorker._ampProtocol.dataString)
|
||||
self.assertEqual(b"bar", localWorker._outLog.getvalue())
|
||||
|
||||
|
||||
def test_outReceived(self):
|
||||
"""
|
||||
L{LocalWorker.outReceived} logs the output into its C{_outLog} log
|
||||
file.
|
||||
"""
|
||||
localWorker = self.tidyLocalWorker(FakeAMProtocol(), '.', 'test.log')
|
||||
localWorker._outLog = BytesIO()
|
||||
data = b"The quick brown fox jumps over the lazy dog"
|
||||
localWorker.outReceived(data)
|
||||
self.assertEqual(data, localWorker._outLog.getvalue())
|
||||
|
||||
|
||||
def test_errReceived(self):
|
||||
"""
|
||||
L{LocalWorker.errReceived} logs the errors into its C{_errLog} log
|
||||
file.
|
||||
"""
|
||||
localWorker = self.tidyLocalWorker(FakeAMProtocol(), '.', 'test.log')
|
||||
localWorker._errLog = BytesIO()
|
||||
data = b"The quick brown fox jumps over the lazy dog"
|
||||
localWorker.errReceived(data)
|
||||
self.assertEqual(data, localWorker._errLog.getvalue())
|
||||
|
||||
|
||||
def test_write(self):
|
||||
"""
|
||||
L{LocalWorkerTransport.write} forwards the written data to the given
|
||||
transport.
|
||||
"""
|
||||
transport = FakeTransport()
|
||||
localTransport = LocalWorkerTransport(transport)
|
||||
data = b"The quick brown fox jumps over the lazy dog"
|
||||
localTransport.write(data)
|
||||
self.assertEqual(data, transport.dataString)
|
||||
|
||||
|
||||
def test_writeSequence(self):
|
||||
"""
|
||||
L{LocalWorkerTransport.writeSequence} forwards the written data to the
|
||||
given transport.
|
||||
"""
|
||||
transport = FakeTransport()
|
||||
localTransport = LocalWorkerTransport(transport)
|
||||
data = (b"The quick ", b"brown fox jumps ", b"over the lazy dog")
|
||||
localTransport.writeSequence(data)
|
||||
self.assertEqual(b"".join(data), transport.dataString)
|
||||
|
||||
|
||||
def test_loseConnection(self):
|
||||
"""
|
||||
L{LocalWorkerTransport.loseConnection} forwards the call to the given
|
||||
transport.
|
||||
"""
|
||||
transport = FakeTransport()
|
||||
localTransport = LocalWorkerTransport(transport)
|
||||
localTransport.loseConnection()
|
||||
|
||||
self.assertEqual(transport.calls, 1)
|
||||
|
||||
|
||||
def test_connectionLost(self):
|
||||
"""
|
||||
L{LocalWorker.connectionLost} closes the log streams.
|
||||
"""
|
||||
|
||||
localWorker = self.tidyLocalWorker(FakeAMProtocol(), '.', 'test.log')
|
||||
localWorker.connectionLost(None)
|
||||
self.assertTrue(localWorker._outLog.closed)
|
||||
self.assertTrue(localWorker._errLog.closed)
|
||||
self.assertTrue(localWorker._testLog.closed)
|
||||
|
||||
|
||||
def test_processEnded(self):
|
||||
"""
|
||||
L{LocalWorker.processEnded} calls C{connectionLost} on itself and on
|
||||
the L{AMP} protocol.
|
||||
"""
|
||||
|
||||
transport = FakeTransport()
|
||||
protocol = FakeAMProtocol()
|
||||
localWorker = LocalWorker(protocol, '.', 'test.log')
|
||||
localWorker.makeConnection(transport)
|
||||
localWorker.processEnded(Failure(CONNECTION_DONE))
|
||||
self.assertTrue(localWorker._outLog.closed)
|
||||
self.assertTrue(localWorker._errLog.closed)
|
||||
self.assertTrue(localWorker._testLog.closed)
|
||||
self.assertIdentical(None, protocol.transport)
|
||||
return self.assertFailure(localWorker.endDeferred, ConnectionDone)
|
||||
|
||||
|
||||
def test_addresses(self):
|
||||
"""
|
||||
L{LocalWorkerTransport.getPeer} and L{LocalWorkerTransport.getHost}
|
||||
return L{IAddress} objects.
|
||||
"""
|
||||
localTransport = LocalWorkerTransport(None)
|
||||
self.assertTrue(verifyObject(IAddress, localTransport.getPeer()))
|
||||
self.assertTrue(verifyObject(IAddress, localTransport.getHost()))
|
||||
|
||||
|
||||
def test_transport(self):
|
||||
"""
|
||||
L{LocalWorkerTransport} implements L{ITransport} to be able to be used
|
||||
by L{AMP}.
|
||||
"""
|
||||
localTransport = LocalWorkerTransport(None)
|
||||
self.assertTrue(verifyObject(ITransport, localTransport))
|
||||
|
||||
|
||||
def test_startError(self):
|
||||
"""
|
||||
L{LocalWorker} swallows the exceptions returned by the L{AMP} protocol
|
||||
start method, as it generates unnecessary errors.
|
||||
"""
|
||||
|
||||
def failCallRemote(command, directory):
|
||||
return fail(RuntimeError("oops"))
|
||||
|
||||
protocol = FakeAMProtocol()
|
||||
protocol.callRemote = failCallRemote
|
||||
self.tidyLocalWorker(protocol, '.', 'test.log')
|
||||
|
||||
self.assertEqual([], self.flushLoggedErrors(RuntimeError))
|
||||
@@ -0,0 +1,167 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.trial._dist.workerreporter}.
|
||||
"""
|
||||
|
||||
from twisted.python.compat import _PY3
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.trial.unittest import TestCase, Todo
|
||||
from twisted.trial._dist.workerreporter import WorkerReporter
|
||||
from twisted.trial._dist import managercommands
|
||||
|
||||
|
||||
class FakeAMProtocol(object):
|
||||
"""
|
||||
A fake C{AMP} implementations to track C{callRemote} calls.
|
||||
"""
|
||||
id = 0
|
||||
lastCall = None
|
||||
|
||||
def callRemote(self, command, **kwargs):
|
||||
self.lastCall = command
|
||||
self.lastArgs = kwargs
|
||||
|
||||
|
||||
|
||||
class WorkerReporterTests(TestCase):
|
||||
"""
|
||||
Tests for L{WorkerReporter}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.fakeAMProtocol = FakeAMProtocol()
|
||||
self.workerReporter = WorkerReporter(self.fakeAMProtocol)
|
||||
self.test = TestCase()
|
||||
|
||||
|
||||
def test_addSuccess(self):
|
||||
"""
|
||||
L{WorkerReporter.addSuccess} sends a L{managercommands.AddSuccess}
|
||||
command.
|
||||
"""
|
||||
self.workerReporter.addSuccess(self.test)
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddSuccess)
|
||||
|
||||
|
||||
def test_addError(self):
|
||||
"""
|
||||
L{WorkerReporter.addError} sends a L{managercommands.AddError} command.
|
||||
"""
|
||||
self.workerReporter.addError(self.test, Failure(RuntimeError('error')))
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddError)
|
||||
|
||||
|
||||
def test_addErrorTuple(self):
|
||||
"""
|
||||
Adding an error using L{WorkerReporter.addError} as a
|
||||
C{sys.exc_info}-style tuple sends an L{managercommands.AddError}
|
||||
command.
|
||||
"""
|
||||
self.workerReporter.addError(
|
||||
self.test, (RuntimeError, RuntimeError('error'), None))
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddError)
|
||||
|
||||
|
||||
def test_addFailure(self):
|
||||
"""
|
||||
L{WorkerReporter.addFailure} sends a L{managercommands.AddFailure}
|
||||
command.
|
||||
"""
|
||||
self.workerReporter.addFailure(self.test,
|
||||
Failure(RuntimeError('fail')))
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddFailure)
|
||||
|
||||
|
||||
def test_addFailureTuple(self):
|
||||
"""
|
||||
Adding a failure using L{WorkerReporter.addFailure} as a
|
||||
C{sys.exc_info}-style tuple sends an L{managercommands.AddFailure}
|
||||
message.
|
||||
"""
|
||||
self.workerReporter.addFailure(
|
||||
self.test, (RuntimeError, RuntimeError('fail'), None))
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddFailure)
|
||||
|
||||
|
||||
def test_addFailureNonASCII(self):
|
||||
"""
|
||||
L{WorkerReporter.addFailure} sends a L{managercommands.AddFailure}
|
||||
message when called with a L{Failure}, even if it includes encoded
|
||||
non-ASCII content.
|
||||
"""
|
||||
content = u"\N{SNOWMAN}".encode("utf-8")
|
||||
exception = RuntimeError(content)
|
||||
failure = Failure(exception)
|
||||
self.workerReporter.addFailure(self.test, failure)
|
||||
self.assertEqual(
|
||||
self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddFailure,
|
||||
)
|
||||
self.assertEqual(
|
||||
content,
|
||||
self.fakeAMProtocol.lastArgs["fail"],
|
||||
)
|
||||
if _PY3:
|
||||
test_addFailureNonASCII.skip = (
|
||||
"Exceptions only convert to unicode on Python 3"
|
||||
)
|
||||
|
||||
|
||||
def test_addSkip(self):
|
||||
"""
|
||||
L{WorkerReporter.addSkip} sends a L{managercommands.AddSkip} command.
|
||||
"""
|
||||
self.workerReporter.addSkip(self.test, 'reason')
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddSkip)
|
||||
|
||||
|
||||
def test_addExpectedFailure(self):
|
||||
"""
|
||||
L{WorkerReporter.addExpectedFailure} sends a
|
||||
L{managercommands.AddExpectedFailure} command.
|
||||
protocol.
|
||||
"""
|
||||
self.workerReporter.addExpectedFailure(
|
||||
self.test, Failure(RuntimeError('error')), Todo('todo'))
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddExpectedFailure)
|
||||
|
||||
|
||||
def test_addExpectedFailureNoTodo(self):
|
||||
"""
|
||||
L{WorkerReporter.addExpectedFailure} sends a
|
||||
L{managercommands.AddExpectedFailure} command.
|
||||
protocol.
|
||||
"""
|
||||
self.workerReporter.addExpectedFailure(
|
||||
self.test, Failure(RuntimeError('error')))
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddExpectedFailure)
|
||||
|
||||
|
||||
def test_addUnexpectedSuccess(self):
|
||||
"""
|
||||
L{WorkerReporter.addUnexpectedSuccess} sends a
|
||||
L{managercommands.AddUnexpectedSuccess} command.
|
||||
"""
|
||||
self.workerReporter.addUnexpectedSuccess(self.test, Todo('todo'))
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddUnexpectedSuccess)
|
||||
|
||||
|
||||
def test_addUnexpectedSuccessNoTodo(self):
|
||||
"""
|
||||
L{WorkerReporter.addUnexpectedSuccess} sends a
|
||||
L{managercommands.AddUnexpectedSuccess} command.
|
||||
"""
|
||||
self.workerReporter.addUnexpectedSuccess(self.test)
|
||||
self.assertEqual(self.fakeAMProtocol.lastCall,
|
||||
managercommands.AddUnexpectedSuccess)
|
||||
@@ -0,0 +1,184 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.trial._dist.workertrial}.
|
||||
"""
|
||||
|
||||
import errno
|
||||
import sys
|
||||
import os
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from twisted.protocols.amp import AMP
|
||||
from twisted.test.proto_helpers import StringTransport
|
||||
from twisted.trial.unittest import TestCase
|
||||
from twisted.trial._dist.workertrial import WorkerLogObserver, main, _setupPath
|
||||
from twisted.trial._dist import (
|
||||
workertrial, _WORKER_AMP_STDIN, _WORKER_AMP_STDOUT, workercommands,
|
||||
managercommands)
|
||||
|
||||
|
||||
|
||||
class FakeAMP(AMP):
|
||||
"""
|
||||
A fake amp protocol.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class WorkerLogObserverTests(TestCase):
|
||||
"""
|
||||
Tests for L{WorkerLogObserver}.
|
||||
"""
|
||||
|
||||
def test_emit(self):
|
||||
"""
|
||||
L{WorkerLogObserver} forwards data to L{managercommands.TestWrite}.
|
||||
"""
|
||||
calls = []
|
||||
|
||||
class FakeClient(object):
|
||||
|
||||
def callRemote(self, method, **kwargs):
|
||||
calls.append((method, kwargs))
|
||||
|
||||
observer = WorkerLogObserver(FakeClient())
|
||||
observer.emit({'message': ['Some log']})
|
||||
self.assertEqual(
|
||||
calls, [(managercommands.TestWrite, {'out': 'Some log'})])
|
||||
|
||||
|
||||
|
||||
class MainTests(TestCase):
|
||||
"""
|
||||
Tests for L{main}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.readStream = BytesIO()
|
||||
self.writeStream = BytesIO()
|
||||
self.patch(workertrial, 'startLoggingWithObserver',
|
||||
self.startLoggingWithObserver)
|
||||
self.addCleanup(setattr, sys, "argv", sys.argv)
|
||||
sys.argv = ["trial"]
|
||||
|
||||
|
||||
def fdopen(self, fd, mode=None):
|
||||
"""
|
||||
Fake C{os.fdopen} implementation which returns C{self.readStream} for
|
||||
the stdin fd and C{self.writeStream} for the stdout fd.
|
||||
"""
|
||||
if fd == _WORKER_AMP_STDIN:
|
||||
self.assertIdentical('rb', mode)
|
||||
return self.readStream
|
||||
elif fd == _WORKER_AMP_STDOUT:
|
||||
self.assertEqual('wb', mode)
|
||||
return self.writeStream
|
||||
else:
|
||||
raise AssertionError("Unexpected fd %r" % (fd,))
|
||||
|
||||
|
||||
def startLoggingWithObserver(self, emit, setStdout):
|
||||
"""
|
||||
Override C{startLoggingWithObserver} for not starting logging.
|
||||
"""
|
||||
self.assertFalse(setStdout)
|
||||
|
||||
|
||||
def test_empty(self):
|
||||
"""
|
||||
If no data is ever written, L{main} exits without writing data out.
|
||||
"""
|
||||
main(self.fdopen)
|
||||
self.assertEqual(b'', self.writeStream.getvalue())
|
||||
|
||||
|
||||
def test_forwardCommand(self):
|
||||
"""
|
||||
L{main} forwards data from its input stream to a L{WorkerProtocol}
|
||||
instance which writes data to the output stream.
|
||||
"""
|
||||
client = FakeAMP()
|
||||
clientTransport = StringTransport()
|
||||
client.makeConnection(clientTransport)
|
||||
client.callRemote(workercommands.Run, testCase="doesntexist")
|
||||
self.readStream = clientTransport.io
|
||||
self.readStream.seek(0, 0)
|
||||
main(self.fdopen)
|
||||
self.assertIn(
|
||||
b"No module named 'doesntexist'", self.writeStream.getvalue())
|
||||
|
||||
|
||||
def test_readInterrupted(self):
|
||||
"""
|
||||
If reading the input stream fails with a C{IOError} with errno
|
||||
C{EINTR}, L{main} ignores it and continues reading.
|
||||
"""
|
||||
excInfos = []
|
||||
|
||||
class FakeStream(object):
|
||||
count = 0
|
||||
|
||||
def read(oself, size):
|
||||
oself.count += 1
|
||||
if oself.count == 1:
|
||||
raise IOError(errno.EINTR)
|
||||
else:
|
||||
excInfos.append(sys.exc_info())
|
||||
return b''
|
||||
|
||||
self.readStream = FakeStream()
|
||||
main(self.fdopen)
|
||||
self.assertEqual(b'', self.writeStream.getvalue())
|
||||
self.assertEqual([(None, None, None)], excInfos)
|
||||
|
||||
|
||||
def test_otherReadError(self):
|
||||
"""
|
||||
L{main} only ignores C{IOError} with C{EINTR} errno: otherwise, the
|
||||
error pops out.
|
||||
"""
|
||||
|
||||
class FakeStream(object):
|
||||
count = 0
|
||||
|
||||
def read(oself, size):
|
||||
oself.count += 1
|
||||
if oself.count == 1:
|
||||
raise IOError("Something else")
|
||||
return ''
|
||||
|
||||
self.readStream = FakeStream()
|
||||
self.assertRaises(IOError, main, self.fdopen)
|
||||
|
||||
|
||||
|
||||
class SetupPathTests(TestCase):
|
||||
"""
|
||||
Tests for L{_setupPath} C{sys.path} manipulation.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.addCleanup(setattr, sys, "path", sys.path[:])
|
||||
|
||||
|
||||
def test_overridePath(self):
|
||||
"""
|
||||
L{_setupPath} overrides C{sys.path} if B{TRIAL_PYTHONPATH} is specified
|
||||
in the environment.
|
||||
"""
|
||||
environ = {"TRIAL_PYTHONPATH": os.pathsep.join(["foo", "bar"])}
|
||||
_setupPath(environ)
|
||||
self.assertEqual(["foo", "bar"], sys.path)
|
||||
|
||||
|
||||
def test_noVariable(self):
|
||||
"""
|
||||
L{_setupPath} doesn't change C{sys.path} if B{TRIAL_PYTHONPATH} is not
|
||||
present in the environment.
|
||||
"""
|
||||
originalPath = sys.path[:]
|
||||
_setupPath({})
|
||||
self.assertEqual(originalPath, sys.path)
|
||||
Reference in New Issue
Block a user