summaryrefslogtreecommitdiff
path: root/docutils/test/alltests.py
blob: d31e75d675e5e7e0fd64788b6d8cc75d7512791c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/bin/sh
''''exec python3 -u "$0" "$@" #'''

# $Id$
# Author: David Goodger <goodger@python.org>,
#         Garth Kidd <garth@deadlybloodyserious.com>
# Copyright: This module has been placed in the public domain.

__doc__ = """\
All modules named 'test_*.py' in the current directory, and recursively in
subdirectories (packages) called 'test_*', are loaded and test suites within
are run.
"""

import time
# Start point for actual elapsed time, including imports
# and setup outside of unittest.
start = time.time()

import atexit               # noqa: E402
import glob                 # noqa: E402
import importlib            # noqa: E402
import os                   # noqa: E402
from pathlib import Path    # noqa: E402
import platform             # noqa: E402
import sys                  # noqa: E402

# Prepend the "docutils root" to the Python library path
# so we import the local `docutils` package.
# For Python < 3.9, we need `resolve()` to ensure an absolute path.
# https://docs.python.org/3/whatsnew/3.9.html#other-language-changes
DOCUTILS_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(DOCUTILS_ROOT))

import docutils             # noqa: E402


class Tee:

    """Write to a file and a stream (default: stdout) simultaneously."""

    def __init__(self, filename, stream=sys.__stdout__):
        self.file = open(filename, 'w', encoding='utf-8',
                         errors='backslashreplace')
        atexit.register(self.close)
        self.stream = stream
        self.encoding = getattr(stream, 'encoding', None)

    def close(self):
        self.file.close()
        self.file = None

    def write(self, string):
        try:
            self.stream.write(string)
        except UnicodeEncodeError:
            bstring = string.encode(self.encoding, errors='backslashreplace')
            self.stream.write(bstring.decode())
        if self.file:
            self.file.write(string)

    def flush(self):
        self.stream.flush()
        if self.file:
            self.file.flush()


# must redirect stderr *before* first import of unittest
sys.stdout = sys.stderr = Tee('alltests.out')

import unittest  # NoQA: E402


def loadTestModules(path):
    """
    Return a test suite composed of all the tests from modules in a directory.

    Search for modules in directory `path`, beginning with `name`.
    Then search subdirectories (also beginning with `name`)
    recursively.  Subdirectories must be Python packages; they must contain an
    '__init__.py' module.
    """
    testLoader = unittest.defaultTestLoader
    testSuite = unittest.TestSuite()
    testModules = []
    path = os.path.abspath(path)        # current working dir if `path` empty
    paths = [path]
    while paths:
        p = paths.pop() + '/test_'
        for file_path in glob.glob(p + '*.py'):
            testModules.append(path2mod(os.path.relpath(file_path, path)))
        for file_path in glob.glob(p + '*/__init__.py'):
            paths.append(os.path.dirname(file_path))
    # Import modules and add their tests to the suite.
    sys.path.insert(0, path)
    for mod in testModules:
        try:
            module = importlib.import_module(mod)
        except ImportError:
            print(f"ERROR: Can't import {mod}, skipping its tests:",
                  file=sys.stderr)
            sys.excepthook(*sys.exc_info())
        else:
            # if there's a suite defined, incorporate its contents
            try:
                suite = module.suite
            except AttributeError:
                # Look for individual tests
                moduleTests = testLoader.loadTestsFromModule(module)
                # unittest.TestSuite.addTests() doesn't work as advertised,
                # as it can't load tests from another TestSuite, so we have
                # to cheat:
                testSuite.addTest(moduleTests)
            else:
                if not callable(suite):
                    raise AssertionError(f"don't understand suite ({mod})")
                testSuite.addTest(suite())
    sys.path.pop(0)
    return testSuite


def path2mod(path):
    """Convert a file path to a dotted module name."""
    return path[:-3].replace(os.sep, '.')


class NumbersTestResult(unittest.TextTestResult):
    """Result class that counts subTests."""
    def addSubTest(self, test, subtest, error):
        super().addSubTest(test, subtest, error)
        self.testsRun += 1
        if self.dots:
            self.stream.write('.' if error is None else 'E')
            self.stream.flush()


if __name__ == '__main__':
    suite = loadTestModules(DOCUTILS_ROOT/'test')
    print(f'Testing Docutils {docutils.__version__} '
          f'with Python {sys.version.split()[0]} '
          f'on {time.strftime("%Y-%m-%d at %H:%M:%S")}')
    print(f'OS: {platform.system()} {platform.release()} {platform.version()} '
          f'({sys.platform}, {platform.platform()})')
    print(f'Working directory: {os.getcwd()}')
    print(f'Docutils package: {os.path.dirname(docutils.__file__)}')
    sys.stdout.flush()
    result = unittest.TextTestRunner(resultclass=NumbersTestResult).run(suite)
    finish = time.time()
    print(f'Elapsed time: {finish - start:.3f} seconds')
    sys.exit(not result.wasSuccessful())