Skip to content

runner

Unit tests facility.

Attributes

mlogger = get_logger(__name__) module-attribute

DEBUG_OKAY_RESULT = 'PASSED' module-attribute

DEBUG_FAIL_RESULT = 'FAILED' module-attribute

RESULT_TEST_SUITE_START = '<div class="unittest unitteststart">Test Suite: {suite}</div>' module-attribute

RESULT_DIV_OKAY = '<div class="unittest unittestokay">:white_heavy_check_mark: PASSED {test}</div>' module-attribute

RESULT_DIV_FAIL = '<div class="unittest unittestfail">:cross_mark: FAILED {test}</div>' module-attribute

RESULT_DIV_ERROR = '<div class="unittest unittesterror">:heavy_large_circle: ERROR {test}</div>' module-attribute

Classes

OutputWriter()

Output writer for tests results.

Source code in pyrevitlib/pyrevit/unittests/runner.py
def __init__(self):
    self._output = get_output()

Methods:

write(output_str)

Prints the results to the output window.

Parameters:

Name Type Description Default
output_str str

Text to output

required
Source code in pyrevitlib/pyrevit/unittests/runner.py
def write(self, output_str):
    """Prints the results to the output window.

    Args:
        output_str (str): Text to output
    """
    self._output.print_html(output_str)

PyRevitTestResult(verbosity, suite_name=None)

Bases: TestResult

Pyrevit Test Result.

Also writes JUnit XML when :data:pyrevit.unittests.junit.PATH_ENV_VAR is set, so the same run that reports to the output window can be read by CI. Reporting is off unless that variable is set, so running tests by hand leaves no files behind.

Parameters:

Name Type Description Default
verbosity int

verbosity level.

required
suite_name str

suite name for the report. Defaults to the runner's.

None
Source code in pyrevitlib/pyrevit/unittests/runner.py
def __init__(self, verbosity, suite_name=None):
    super(PyRevitTestResult, self).__init__(verbosity=verbosity)
    self.writer = OutputWriter()
    self.suite_name = suite_name or junit.DEFAULT_SUITE_NAME
    self._junit = junit.JUnitReport(name=self.suite_name)
    self._started = {}

Attributes

writer = OutputWriter() instance-attribute
suite_name = suite_name or junit.DEFAULT_SUITE_NAME instance-attribute

Methods:

getDescription(test) staticmethod

Returns the description of the test.

Parameters:

Name Type Description Default
test TestCase

Unit test.

required

Returns:

Type Description
str

test description

Source code in pyrevitlib/pyrevit/unittests/runner.py
@staticmethod
def getDescription(test):
    """Returns the description of the test.

    Args:
        test (TestCase): Unit test.

    Returns:
        (str): test description
    """
    return test.shortDescription() or test
stopTestRun()

Writes the JUnit report, when one is configured.

The whole report goes out in one file here rather than per test, so the result is a single well-formed document. Reporting is off unless junit.PATH_ENV_VAR is set, so running tests by hand leaves no files behind.

Source code in pyrevitlib/pyrevit/unittests/runner.py
def stopTestRun(self):
    """Writes the JUnit report, when one is configured.

    The whole report goes out in one file here rather than per test, so the result is
    a single well-formed document. Reporting is off unless
    `junit.PATH_ENV_VAR` is set, so running tests by hand leaves no files behind.
    """
    stop_test_run = getattr(super(PyRevitTestResult, self), "stopTestRun", None)
    if stop_test_run is not None:
        stop_test_run()

    try:
        written = self._junit.write()
        if written:
            mlogger.debug("Wrote JUnit report: %s", written)
    except Exception as report_error:
        mlogger.warning("Could not write JUnit report: %s", report_error)
startTest(test)

Starts the test.

Parameters:

Name Type Description Default
test TestCase

unit test

required
Source code in pyrevitlib/pyrevit/unittests/runner.py
def startTest(self, test):
    """Starts the test.

    Args:
        test (TestCase): unit test
    """
    super(PyRevitTestResult, self).startTest(test)
    self._started[id(test)] = time.time()
    mlogger.debug("Running test: %s", self.getDescription(test))
addSuccess(test)

Adds a test success.

Parameters:

Name Type Description Default
test TestCase

unit test case

required
Source code in pyrevitlib/pyrevit/unittests/runner.py
def addSuccess(self, test):
    """Adds a test success.

    Args:
        test (TestCase): unit test case
    """
    super(PyRevitTestResult, self).addSuccess(test)
    mlogger.debug(DEBUG_OKAY_RESULT)
    self.writer.write(RESULT_DIV_OKAY.format(test=self.getDescription(test)))
    self._report("success", test)
addError(test, err)

Adds a test error.

Parameters:

Name Type Description Default
test TestCase

unit test case

required
err OptExcInfo

test exception info

required
Source code in pyrevitlib/pyrevit/unittests/runner.py
def addError(self, test, err):
    """Adds a test error.

    Args:
        test (TestCase): unit test case
        err (OptExcInfo): test exception info
    """
    super(PyRevitTestResult, self).addError(test, err)
    mlogger.debug(DEBUG_FAIL_RESULT)
    self.writer.write(RESULT_DIV_ERROR.format(test=self.getDescription(test)))
    details = self._exception_detail(test, err)
    self._report("error", test, message=_exception_summary(err), detail=details)
    self._write_exception(details)
addFailure(test, err)

Adds a test failure.

Parameters:

Name Type Description Default
test TestCase

unit test case

required
err OptExcInfo

test exception info

required
Source code in pyrevitlib/pyrevit/unittests/runner.py
def addFailure(self, test, err):
    """Adds a test failure.

    Args:
        test (TestCase): unit test case
        err (OptExcInfo): test exception info
    """
    super(PyRevitTestResult, self).addFailure(test, err)
    mlogger.debug(DEBUG_FAIL_RESULT)
    self.writer.write(RESULT_DIV_FAIL.format(test=self.getDescription(test)))
    details = self._exception_detail(test, err)
    self._report("failure", test, message=_exception_summary(err), detail=details)
    self._write_exception(details)
addSkip(test, reason)

Adds a skipped test.

Parameters:

Name Type Description Default
test TestCase

unit test case

required
reason str

why it was skipped

required
Source code in pyrevitlib/pyrevit/unittests/runner.py
def addSkip(self, test, reason):
    """Adds a skipped test.

    Args:
        test (TestCase): unit test case
        reason (str): why it was skipped
    """
    super(PyRevitTestResult, self).addSkip(test, reason)
    self._report("skipped", test, message=reason)

PyRevitTestRunner(verbosity=1, failfast=False, use_buffer=False, resultclass=None, suite_name=None)

Bases: object

Test runner.

Parameters:

Name Type Description Default
verbosity int

level of vermosity. Defaults to 1.

1
failfast bool

if True, stops at the first failure. Defaults to False.

False
use_buffer bool

use a buffer. Defaults to False.

False
resultclass type

Class to use to hold the results. Defaults to PyRevitTestResult.

None
suite_name str

suite name recorded in the JUnit report.

None
Source code in pyrevitlib/pyrevit/unittests/runner.py
def __init__(
    self,
    verbosity=1,
    failfast=False,
    use_buffer=False,
    resultclass=None,
    suite_name=None,
):
    self.verbosity = verbosity
    self.failfast = failfast
    self.use_buffer = use_buffer
    self.suite_name = suite_name
    if resultclass is not None:
        self.resultclass = resultclass

Attributes

resultclass = PyRevitTestResult class-attribute instance-attribute
verbosity = verbosity instance-attribute
failfast = failfast instance-attribute
use_buffer = use_buffer instance-attribute
suite_name = suite_name instance-attribute

Methods:

run(test)

Runs a test suite.

Parameters:

Name Type Description Default
test TestSuite

Test suite to run

required

Returns:

Type Description
PyRevitTestResult

Test suite results.

Source code in pyrevitlib/pyrevit/unittests/runner.py
def run(self, test):
    """Runs a test suite.

    Args:
        test (TestSuite): Test suite to run

    Returns:
        (PyRevitTestResult): Test suite results.
    """
    # setup results object
    result = self._make_result()
    result.failfast = self.failfast
    result.buffer = self.use_buffer

    # start clock
    start_time = time.time()

    # find run test methods
    start_test_run = getattr(result, "startTestRun", None)
    if start_test_run is not None:
        start_test_run()
    try:
        test(result)
    finally:
        stop_test_run = getattr(result, "stopTestRun", None)
        if stop_test_run is not None:
            stop_test_run()

    # stop clock and calculate run time
    stop_time = time.time()
    time_taken = stop_time - start_time

    # print errots
    result.printErrors()
    test_count = result.testsRun
    mlogger.debug(
        "Ran %d test%s in %.3fs",
        test_count,
        test_count != 1 and "s" or "",
        time_taken,
    )

    expected_fails = unexpected_successes = skipped = 0
    try:
        results = map(
            len,
            (result.expectedFailures, result.unexpectedSuccesses, result.skipped),
        )
    except AttributeError:
        pass
    else:
        expected_fails, unexpected_successes, skipped = results

    infos = []
    if not result.wasSuccessful():
        mlogger.debug("FAILED")
        failed, errored = map(len, (result.failures, result.errors))
        if failed:
            infos.append("failures=%d" % failed)
        if errored:
            infos.append("errors=%d" % errored)
    else:
        mlogger.debug(DEBUG_OKAY_RESULT)

    if skipped:
        infos.append("skipped=%d" % skipped)
    if expected_fails:
        infos.append("expected failures=%d" % expected_fails)
    if unexpected_successes:
        infos.append("unexpected successes=%d" % unexpected_successes)
    if infos:
        mlogger.debug(" (%s)", (", ".join(infos),))

    return result

Functions:

run_module_tests(test_module)

Runs the unit tests of the given module.

Parameters:

Name Type Description Default
test_module module

module with tests

required

Returns:

Type Description
PyRevitTestResult

tests results.

Source code in pyrevitlib/pyrevit/unittests/runner.py
def run_module_tests(test_module):
    """Runs the unit tests of the given module.

    Args:
        test_module (module): module with tests

    Returns:
        (PyRevitTestResult): tests results.
    """
    test_runner = PyRevitTestRunner(suite_name=test_module.__name__)
    test_loader = TestLoader()
    # load all testcases from the given module into a testsuite
    test_suite = test_loader.loadTestsFromModule(test_module)
    # run the test suite
    mlogger.debug("Running test suite for module: %s", test_module)
    OutputWriter().write(RESULT_TEST_SUITE_START.format(suite=test_module.__name__))
    return test_runner.run(test_suite)

run_test_case(test_case)

Runs the unit test of the given TestCase class.

Parameters:

Name Type Description Default
test_case type[TestCase]

TestCase class with tests

required

Returns:

Type Description
PyRevitTestResult

tests results.

Source code in pyrevitlib/pyrevit/unittests/runner.py
def run_test_case(test_case):
    """Runs the unit test of the given TestCase class.

    Args:
        test_case (type[TestCase]): TestCase class with tests

    Returns:
        (PyRevitTestResult): tests results.
    """
    suite_name = "{0}.{1}".format(test_case.__module__, test_case.__name__)
    test_runner = PyRevitTestRunner(suite_name=suite_name)
    suite = TestLoader().loadTestsFromTestCase(test_case)
    OutputWriter().write(RESULT_TEST_SUITE_START.format(suite=suite.__class__.__name__))
    return test_runner.run(suite)

assert_module_tests_successful(test_module)

Runs a module's unit tests and fails the command if any of them failed.

run_module_tests only reports; discarding its result makes a red suite look like a green button, so command scripts must go through this instead.

Parameters:

Name Type Description Default
test_module module

module with tests

required

Returns:

Type Description
PyRevitTestResult

tests results.

Raises:

Type Description
AssertionError

if the module has failures, errors, or unexpected successes. The traceback of each offending test is already in the output window, so the message only needs to name the module and the counts.

Note

IronPython 2.7's TestResult.wasSuccessful ignores unexpectedSuccesses (unittest/result.py returns len(self.failures) == len(self.errors) == 0), so on the default engine an @expectedFailure that starts passing does not fail the command. CPython does count them, which is why the third count is listed.

Source code in pyrevitlib/pyrevit/unittests/runner.py
def assert_module_tests_successful(test_module):
    """Runs a module's unit tests and fails the command if any of them failed.

    `run_module_tests` only reports; discarding its result makes a red suite
    look like a green button, so command scripts must go through this instead.

    Args:
        test_module (module): module with tests

    Returns:
        (PyRevitTestResult): tests results.

    Raises:
        AssertionError: if the module has failures, errors, or unexpected
            successes. The traceback of each offending test is already in the
            output window, so the message only needs to name the module and the
            counts.

    Note:
        IronPython 2.7's `TestResult.wasSuccessful` ignores
        `unexpectedSuccesses` (`unittest/result.py` returns
        `len(self.failures) == len(self.errors) == 0`), so on the default engine
        an `@expectedFailure` that starts passing does not fail the command.
        CPython does count them, which is why the third count is listed.
    """
    result = run_module_tests(test_module)
    if result.wasSuccessful():
        return result

    counts = [
        "{}={}".format(label, len(issues))
        for label, issues in (
            ("failures", result.failures),
            ("errors", result.errors),
            ("unexpected successes", result.unexpectedSuccesses),
        )
        if issues
    ]
    raise AssertionError(
        "Unit test failures in {}: {}".format(test_module.__name__, ", ".join(counts))
    )