toil.test

Base testing class for Toil.

Subpackages

Package Contents

Classes

concat

A literal iterable to combine sequence literals (lists, set) with generators or list comprehensions.

ExceptionalThread

A thread whose join() method re-raises exceptions raised during run(). While join() is

ToilTest

A common base class for Toil tests.

ApplianceTestSupport

A Toil test that runs a user script on a minimal cluster of appliance containers.

Functions

applianceSelf([forceDockerAppliance])

Return the fully qualified name of the Docker image to start Toil appliance containers from.

toilPackageDirPath()

Return the absolute path of the directory that corresponds to the top-level toil package.

have_working_nvidia_docker_runtime()

Return True if Docker exists and can handle an "nvidia" runtime and the "--gpus" option.

have_working_nvidia_smi()

Return True if the nvidia-smi binary, from nvidia's CUDA userspace

mkdtemp([suffix, prefix, dir])

Make a temporary directory like tempfile.mkdtemp, but with relaxed permissions.

cpu_count()

Get the rounded-up integer number of whole CPUs available.

get_temp_file([suffix, rootDir])

Return a string representing a temporary file, that must be manually deleted.

needs_env_var(var_name[, comment])

Use as a decorator before test classes or methods to run only if the given

needs_rsync3(test_item)

Decorate classes or methods that depend on any features from rsync version 3.0.0+.

needs_online(test_item)

Use as a decorator before test classes or methods to run only if we are meant to talk to the Internet.

needs_aws_s3(test_item)

Use as a decorator before test classes or methods to run only if AWS S3 is usable.

needs_aws_ec2(test_item)

Use as a decorator before test classes or methods to run only if AWS EC2 is usable.

needs_aws_batch(test_item)

Use as a decorator before test classes or methods to run only if AWS Batch

needs_google_storage(test_item)

Use as a decorator before test classes or methods to run only if Google

needs_google_project(test_item)

Use as a decorator before test classes or methods to run only if we have a Google Cloud project set.

needs_gridengine(test_item)

Use as a decorator before test classes or methods to run only if GridEngine is installed.

needs_torque(test_item)

Use as a decorator before test classes or methods to run only if PBS/Torque is installed.

needs_kubernetes_installed(test_item)

Use as a decorator before test classes or methods to run only if Kubernetes is installed.

needs_kubernetes(test_item)

Use as a decorator before test classes or methods to run only if Kubernetes is installed and configured.

needs_mesos(test_item)

Use as a decorator before test classes or methods to run only if Mesos is installed.

needs_slurm(test_item)

Use as a decorator before test classes or methods to run only if Slurm is installed.

needs_htcondor(test_item)

Use a decorator before test classes or methods to run only if the HTCondor is installed.

needs_lsf(test_item)

Use as a decorator before test classes or methods to only run them if LSF is installed.

needs_java(test_item)

Use as a test decorator to run only if java is installed.

needs_docker(test_item)

Use as a decorator before test classes or methods to only run them if

needs_singularity(test_item)

Use as a decorator before test classes or methods to only run them if

needs_singularity_or_docker(test_item)

Use as a decorator before test classes or methods to only run them if

needs_local_cuda(test_item)

Use as a decorator before test classes or methods to only run them if

needs_docker_cuda(test_item)

Use as a decorator before test classes or methods to only run them if

needs_encryption(test_item)

Use as a decorator before test classes or methods to only run them if PyNaCl is installed

needs_cwl(test_item)

Use as a decorator before test classes or methods to only run them if CWLTool is installed

needs_wdl(test_item)

Use as a decorator before test classes or methods to only run them if miniwdl is installed

needs_server(test_item)

Use as a decorator before test classes or methods to only run them if Connexion is installed.

needs_celery_broker(test_item)

Use as a decorator before test classes or methods to run only if RabbitMQ is set up to take Celery jobs.

needs_wes_server(test_item)

Use as a decorator before test classes or methods to run only if a WES

needs_local_appliance(test_item)

Use as a decorator before test classes or methods to only run them if

needs_fetchable_appliance(test_item)

Use as a decorator before test classes or methods to only run them if

integrative(test_item)

Use this to decorate integration tests so as to skip them during regular builds.

slow(test_item)

Use this decorator to identify tests that are slow and not critical.

timeLimit(seconds)

Use to limit the execution time of a function.

make_tests(generalMethod, targetClass, **kwargs)

This method dynamically generates test methods using the generalMethod as a template. Each

Attributes

memoize

Memoize a function result based on its parameters using this decorator.

distVersion

logger

MT

methodNamePartRegex

exception toil.test.ApplianceImageNotFound(origAppliance, url, statusCode)[source]

Bases: docker.errors.ImageNotFound

Error raised when using TOIL_APPLIANCE_SELF results in an HTTP error.

Parameters:
  • origAppliance (str) – The full url of the docker image originally specified by the user (or the default). e.g. “quay.io/ucsc_cgl/toil:latest”

  • url (str) – The URL at which the image’s manifest is supposed to appear

  • statusCode (int) – the failing HTTP status code returned by the URL

toil.test.applianceSelf(forceDockerAppliance=False)[source]

Return the fully qualified name of the Docker image to start Toil appliance containers from.

The result is determined by the current version of Toil and three environment variables: TOIL_DOCKER_REGISTRY, TOIL_DOCKER_NAME and TOIL_APPLIANCE_SELF.

TOIL_DOCKER_REGISTRY specifies an account on a publicly hosted docker registry like Quay or Docker Hub. The default is UCSC’s CGL account on Quay.io where the Toil team publishes the official appliance images. TOIL_DOCKER_NAME specifies the base name of the image. The default of toil will be adequate in most cases. TOIL_APPLIANCE_SELF fully qualifies the appliance image, complete with registry, image name and version tag, overriding both TOIL_DOCKER_NAME and TOIL_DOCKER_REGISTRY` as well as the version tag of the image. Setting TOIL_APPLIANCE_SELF will not be necessary in most cases.

Parameters:

forceDockerAppliance (bool)

Return type:

str

toil.test.toilPackageDirPath()[source]

Return the absolute path of the directory that corresponds to the top-level toil package.

The return value is guaranteed to end in ‘/toil’.

Return type:

str

toil.test.have_working_nvidia_docker_runtime()[source]

Return True if Docker exists and can handle an “nvidia” runtime and the “–gpus” option.

Return type:

bool

toil.test.have_working_nvidia_smi()[source]

Return True if the nvidia-smi binary, from nvidia’s CUDA userspace utilities, is installed and can be run successfully.

TODO: This isn’t quite the same as the check that cwltool uses to decide if it can fulfill a CUDARequirement.

Return type:

bool

toil.test.mkdtemp(suffix=None, prefix=None, dir=None)[source]

Make a temporary directory like tempfile.mkdtemp, but with relaxed permissions.

The permissions on the directory will be 711 instead of 700, allowing the group and all other users to traverse the directory. This is necessary if the direcotry is on NFS and the Docker daemon would like to mount it or a file inside it into a container, because on NFS even the Docker daemon appears bound by the file permissions.

See <https://github.com/DataBiosphere/toil/issues/4644>, and <https://stackoverflow.com/a/67928880> which talks about a similar problem but in the context of user namespaces.

Parameters:
  • suffix (Optional[str])

  • prefix (Optional[str])

  • dir (Optional[str])

Return type:

str

class toil.test.concat(*args)[source]

A literal iterable to combine sequence literals (lists, set) with generators or list comprehensions.

Instead of

>>> [ -1 ] + [ x * 2 for x in range( 3 ) ] + [ -1 ]
[-1, 0, 2, 4, -1]

you can write

>>> list( concat( -1, ( x * 2 for x in range( 3 ) ), -1 ) )
[-1, 0, 2, 4, -1]

This is slightly shorter (not counting the list constructor) and does not involve array construction or concatenation.

Note that concat() flattens (or chains) all iterable arguments into a single result iterable:

>>> list( concat( 1, range( 2, 4 ), 4 ) )
[1, 2, 3, 4]

It only does so one level deep. If you need to recursively flatten a data structure, check out crush().

If you want to prevent that flattening for an iterable argument, wrap it in concat():

>>> list( concat( 1, concat( range( 2, 4 ) ), 4 ) )
[1, range(2, 4), 4]

Some more example.

>>> list( concat() ) # empty concat
[]
>>> list( concat( 1 ) ) # non-iterable
[1]
>>> list( concat( concat() ) ) # empty iterable
[]
>>> list( concat( concat( 1 ) ) ) # singleton iterable
[1]
>>> list( concat( 1, concat( 2 ), 3 ) ) # flattened iterable
[1, 2, 3]
>>> list( concat( 1, [2], 3 ) ) # flattened iterable
[1, 2, 3]
>>> list( concat( 1, concat( [2] ), 3 ) ) # protecting an iterable from being flattened
[1, [2], 3]
>>> list( concat( 1, concat( [2], 3 ), 4 ) ) # protection only works with a single argument
[1, 2, 3, 4]
>>> list( concat( 1, 2, concat( 3, 4 ), 5, 6 ) )
[1, 2, 3, 4, 5, 6]
>>> list( concat( 1, 2, concat( [ 3, 4 ] ), 5, 6 ) )
[1, 2, [3, 4], 5, 6]

Note that while strings are technically iterable, concat() does not flatten them.

>>> list( concat( 'ab' ) )
['ab']
>>> list( concat( concat( 'ab' ) ) )
['ab']
Parameters:

args (Any)

__iter__()[source]
Return type:

Iterator[Any]

toil.test.memoize

Memoize a function result based on its parameters using this decorator.

For example, this can be used in place of lazy initialization. If the decorating function is invoked by multiple threads, the decorated function may be called more than once with the same arguments.

class toil.test.ExceptionalThread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)[source]

Bases: threading.Thread

A thread whose join() method re-raises exceptions raised during run(). While join() is idempotent, the exception is only during the first invocation of join() that successfully joined the thread. If join() times out, no exception will be re reraised even though an exception might already have occured in run().

When subclassing this thread, override tryRun() instead of run().

>>> def f():
...     assert 0
>>> t = ExceptionalThread(target=f)
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
>>> class MyThread(ExceptionalThread):
...     def tryRun( self ):
...         assert 0
>>> t = MyThread()
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
exc_info
run()[source]

Method representing the thread’s activity.

You may override this method in a subclass. The standard run() method invokes the callable object passed to the object’s constructor as the target argument, if any, with sequential and keyword arguments taken from the args and kwargs arguments, respectively.

Return type:

None

tryRun()[source]
Return type:

None

join(*args, **kwargs)[source]

Wait until the thread terminates.

This blocks the calling thread until the thread whose join() method is called terminates – either normally or through an unhandled exception or until the optional timeout occurs.

When the timeout argument is present and not None, it should be a floating point number specifying a timeout for the operation in seconds (or fractions thereof). As join() always returns None, you must call is_alive() after join() to decide whether a timeout happened – if the thread is still alive, the join() call timed out.

When the timeout argument is not present or None, the operation will block until the thread terminates.

A thread can be join()ed many times.

join() raises a RuntimeError if an attempt is made to join the current thread as that would cause a deadlock. It is also an error to join() a thread before it has been started and attempts to do so raises the same exception.

Parameters:
Return type:

None

toil.test.cpu_count()[source]

Get the rounded-up integer number of whole CPUs available.

Counts hyperthreads as CPUs.

Uses the system’s actual CPU count, or the current v1 cgroup’s quota per period, if the quota is set.

Ignores the cgroup’s cpu shares value, because it’s extremely difficult to interpret. See https://github.com/kubernetes/kubernetes/issues/81021.

Caches result for efficiency.

Returns:

Integer count of available CPUs, minimum 1.

Return type:

int

toil.test.distVersion = '6.2.0a1'
toil.test.logger
class toil.test.ToilTest(methodName='runTest')[source]

Bases: unittest.TestCase

A common base class for Toil tests.

Please have every test case directly or indirectly inherit this one.

When running tests you may optionally set the TOIL_TEST_TEMP environment variable to the path of a directory where you want temporary test files be placed. The directory will be created if it doesn’t exist. The path may be relative in which case it will be assumed to be relative to the project root. If TOIL_TEST_TEMP is not defined, temporary files and directories will be created in the system’s default location for such files and any temporary files or directories left over from tests will be removed automatically removed during tear down. Otherwise, left-over files will not be removed.

setup_method(method)[source]
Parameters:

method (Any)

Return type:

None

classmethod setUpClass()[source]

Hook method for setting up class fixture before running tests in the class.

Return type:

None

classmethod tearDownClass()[source]

Hook method for deconstructing the class fixture after running all tests in the class.

Return type:

None

setUp()[source]

Hook method for setting up the test fixture before exercising it.

Return type:

None

tearDown()[source]

Hook method for deconstructing the test fixture after testing it.

Return type:

None

classmethod awsRegion()[source]

Pick an appropriate AWS region.

Use us-west-2 unless running on EC2, in which case use the region in which the instance is located

Return type:

str

toil.test.MT
toil.test.get_temp_file(suffix='', rootDir=None)[source]

Return a string representing a temporary file, that must be manually deleted.

Parameters:
  • suffix (str)

  • rootDir (Optional[str])

Return type:

str

toil.test.needs_env_var(var_name, comment=None)[source]

Use as a decorator before test classes or methods to run only if the given environment variable is set. Can include a comment saying what the variable should be set to.

Parameters:
  • var_name (str)

  • comment (Optional[str])

Return type:

Callable[[MT], MT]

toil.test.needs_rsync3(test_item)[source]

Decorate classes or methods that depend on any features from rsync version 3.0.0+.

Necessary because utilsTest.testAWSProvisionerUtils() uses option –protect-args which is only available in rsync 3

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_online(test_item)[source]

Use as a decorator before test classes or methods to run only if we are meant to talk to the Internet.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_aws_s3(test_item)[source]

Use as a decorator before test classes or methods to run only if AWS S3 is usable.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_aws_ec2(test_item)[source]

Use as a decorator before test classes or methods to run only if AWS EC2 is usable.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_aws_batch(test_item)[source]

Use as a decorator before test classes or methods to run only if AWS Batch is usable.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_google_storage(test_item)[source]

Use as a decorator before test classes or methods to run only if Google Cloud is installed and we ought to be able to access public Google Storage URIs.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_google_project(test_item)[source]

Use as a decorator before test classes or methods to run only if we have a Google Cloud project set.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_gridengine(test_item)[source]

Use as a decorator before test classes or methods to run only if GridEngine is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_torque(test_item)[source]

Use as a decorator before test classes or methods to run only if PBS/Torque is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_kubernetes_installed(test_item)[source]

Use as a decorator before test classes or methods to run only if Kubernetes is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_kubernetes(test_item)[source]

Use as a decorator before test classes or methods to run only if Kubernetes is installed and configured.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_mesos(test_item)[source]

Use as a decorator before test classes or methods to run only if Mesos is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_slurm(test_item)[source]

Use as a decorator before test classes or methods to run only if Slurm is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_htcondor(test_item)[source]

Use a decorator before test classes or methods to run only if the HTCondor is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_lsf(test_item)[source]

Use as a decorator before test classes or methods to only run them if LSF is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_java(test_item)[source]

Use as a test decorator to run only if java is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_docker(test_item)[source]

Use as a decorator before test classes or methods to only run them if docker is installed and docker-based tests are enabled.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_singularity(test_item)[source]

Use as a decorator before test classes or methods to only run them if singularity is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_singularity_or_docker(test_item)[source]

Use as a decorator before test classes or methods to only run them if docker is installed and docker-based tests are enabled, or if Singularity is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_local_cuda(test_item)[source]

Use as a decorator before test classes or methods to only run them if a CUDA setup legible to cwltool (i.e. providing userspace nvidia-smi) is present.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_docker_cuda(test_item)[source]

Use as a decorator before test classes or methods to only run them if a CUDA setup is available through Docker.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_encryption(test_item)[source]

Use as a decorator before test classes or methods to only run them if PyNaCl is installed and configured.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_cwl(test_item)[source]

Use as a decorator before test classes or methods to only run them if CWLTool is installed and configured.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_wdl(test_item)[source]

Use as a decorator before test classes or methods to only run them if miniwdl is installed and configured.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_server(test_item)[source]

Use as a decorator before test classes or methods to only run them if Connexion is installed.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_celery_broker(test_item)[source]

Use as a decorator before test classes or methods to run only if RabbitMQ is set up to take Celery jobs.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_wes_server(test_item)[source]

Use as a decorator before test classes or methods to run only if a WES server is available to run against.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_local_appliance(test_item)[source]

Use as a decorator before test classes or methods to only run them if the Toil appliance Docker image is downloaded.

Parameters:

test_item (MT)

Return type:

MT

toil.test.needs_fetchable_appliance(test_item)[source]

Use as a decorator before test classes or methods to only run them if the Toil appliance Docker image is able to be downloaded from the Internet.

Parameters:

test_item (MT)

Return type:

MT

toil.test.integrative(test_item)[source]

Use this to decorate integration tests so as to skip them during regular builds.

We define integration tests as A) involving other, non-Toil software components that we develop and/or B) having a higher cost (time or money).

Parameters:

test_item (MT)

Return type:

MT

toil.test.slow(test_item)[source]

Use this decorator to identify tests that are slow and not critical. Skip if TOIL_TEST_QUICK is true.

Parameters:

test_item (MT)

Return type:

MT

toil.test.methodNamePartRegex
toil.test.timeLimit(seconds)[source]

Use to limit the execution time of a function.

Raises an exception if the execution of the function takes more than the specified amount of time. See <http://stackoverflow.com/a/601168>.

Parameters:

seconds (int) – maximum allowable time, in seconds

Return type:

Generator[None, None, None]

>>> import time
>>> with timeLimit(2):
...    time.sleep(1)
>>> import time
>>> with timeLimit(1):
...    time.sleep(2)
Traceback (most recent call last):
    ...
RuntimeError: Timed out
toil.test.make_tests(generalMethod, targetClass, **kwargs)[source]

This method dynamically generates test methods using the generalMethod as a template. Each generated function is the result of a unique combination of parameters applied to the generalMethod. Each of the parameters has a corresponding string that will be used to name the method. These generated functions are named in the scheme: test_[generalMethodName]___[ firstParamaterName]_[someValueName]__[secondParamaterName]_…

The arguments following the generalMethodName should be a series of one or more dictionaries of the form {str : type, …} where the key represents the name of the value. The names will be used to represent the permutation of values passed for each parameter in the generalMethod.

The generated method names will list the parameters in lexicographic order by parameter name.

Parameters:
  • generalMethod – A method that will be parameterized with values passed as kwargs. Note that the generalMethod must be a regular method.

  • targetClass – This represents the class to which the generated test methods will be bound. If no targetClass is specified the class of the generalMethod is assumed the target.

  • kwargs – a series of dictionaries defining values, and their respective names where each keyword is the name of a parameter in generalMethod.

>>> class Foo:
...     def has(self, num, letter):
...         return num, letter
...
...     def hasOne(self, num):
...         return num
>>> class Bar(Foo):
...     pass
>>> make_tests(Foo.has, Bar, num={'one':1, 'two':2}, letter={'a':'a', 'b':'b'})
>>> b = Bar()

Note that num comes lexicographically before letter and so appears first in the generated method names.

>>> assert b.test_has__letter_a__num_one() == b.has(1, 'a')
>>> assert b.test_has__letter_b__num_one() == b.has(1, 'b')
>>> assert b.test_has__letter_a__num_two() == b.has(2, 'a')
>>> assert b.test_has__letter_b__num_two() == b.has(2, 'b')
>>> f = Foo()
>>> hasattr(f, 'test_has__num_one__letter_a')  # should be false because Foo has no test methods
False
class toil.test.ApplianceTestSupport(methodName='runTest')[source]

Bases: ToilTest

A Toil test that runs a user script on a minimal cluster of appliance containers.

i.e. one leader container and one worker container.

class Appliance(outer, mounts, cleanMounts=False)[source]

Bases: toil.lib.threading.ExceptionalThread

A thread whose join() method re-raises exceptions raised during run(). While join() is idempotent, the exception is only during the first invocation of join() that successfully joined the thread. If join() times out, no exception will be re reraised even though an exception might already have occured in run().

When subclassing this thread, override tryRun() instead of run().

>>> def f():
...     assert 0
>>> t = ExceptionalThread(target=f)
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
>>> class MyThread(ExceptionalThread):
...     def tryRun( self ):
...         assert 0
>>> t = MyThread()
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
Parameters:
lock
__enter__()[source]
Return type:

Appliance

__exit__(exc_type, exc_val, exc_tb)[source]
Parameters:
Return type:

Literal[False]

tryRun()[source]
Return type:

None

runOnAppliance(*args, **kwargs)[source]
Parameters:
  • args (str)

  • kwargs (Any)

Return type:

None

writeToAppliance(path, contents)[source]
Parameters:
  • path (str)

  • contents (Any)

Return type:

None

deployScript(path, packagePath, script)[source]

Deploy a Python module on the appliance.

Parameters:
  • path (str) – the path (absolute or relative to the WORDIR of the appliance container) to the root of the package hierarchy where the given module should be placed. The given directory should be on the Python path.

  • packagePath (str) – the desired fully qualified module name (dotted form) of the module

  • script (str|callable) – the contents of the Python module. If a callable is given, its source code will be extracted. This is a convenience that lets you embed user scripts into test code as nested function.

Return type:

None

class LeaderThread(outer, mounts, cleanMounts=False)[source]

Bases: ApplianceTestSupport.Appliance

A thread whose join() method re-raises exceptions raised during run(). While join() is idempotent, the exception is only during the first invocation of join() that successfully joined the thread. If join() times out, no exception will be re reraised even though an exception might already have occured in run().

When subclassing this thread, override tryRun() instead of run().

>>> def f():
...     assert 0
>>> t = ExceptionalThread(target=f)
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
>>> class MyThread(ExceptionalThread):
...     def tryRun( self ):
...         assert 0
>>> t = MyThread()
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
Parameters:
class WorkerThread(outer, mounts, numCores)[source]

Bases: ApplianceTestSupport.Appliance

A thread whose join() method re-raises exceptions raised during run(). While join() is idempotent, the exception is only during the first invocation of join() that successfully joined the thread. If join() times out, no exception will be re reraised even though an exception might already have occured in run().

When subclassing this thread, override tryRun() instead of run().

>>> def f():
...     assert 0
>>> t = ExceptionalThread(target=f)
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
>>> class MyThread(ExceptionalThread):
...     def tryRun( self ):
...         assert 0
>>> t = MyThread()
>>> t.start()
>>> t.join()
Traceback (most recent call last):
...
AssertionError
Parameters: