650 lines
24 KiB
Python
650 lines
24 KiB
Python
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Tests for loading tests by name.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import unittest as pyunit
|
|
from hashlib import md5
|
|
from operator import attrgetter
|
|
from types import ModuleType
|
|
from typing import TYPE_CHECKING, Callable, Generator
|
|
|
|
from hamcrest import assert_that, equal_to, has_properties
|
|
from hamcrest.core.matcher import Matcher
|
|
|
|
from twisted.python import filepath, util
|
|
from twisted.python.modules import PythonAttribute, PythonModule, getModule
|
|
from twisted.python.reflect import ModuleNotFound
|
|
from twisted.trial import reporter, runner, unittest
|
|
from twisted.trial._asyncrunner import _iterateTests
|
|
from twisted.trial.itrial import ITestCase
|
|
from twisted.trial.test import packages
|
|
from .matchers import after
|
|
|
|
if TYPE_CHECKING:
|
|
from _typeshed import SupportsRichComparison
|
|
|
|
|
|
def testNames(tests: pyunit.TestCase | pyunit.TestSuite) -> list[str]:
|
|
"""
|
|
Return the id of each test within the given test suite or case.
|
|
"""
|
|
names = []
|
|
for test in _iterateTests(tests):
|
|
names.append(test.id())
|
|
return names
|
|
|
|
|
|
class FinderPy3Tests(packages.SysPathManglingTest):
|
|
def setUp(self) -> None: # type: ignore[override]
|
|
super().setUp()
|
|
self.loader = runner.TestLoader()
|
|
|
|
def test_findNonModule(self) -> None:
|
|
"""
|
|
findByName, if given something findable up until the last entry, will
|
|
raise AttributeError (as it cannot tell if 'nonexistent' here is
|
|
supposed to be a module or a class).
|
|
"""
|
|
self.assertRaises(
|
|
AttributeError, self.loader.findByName, "twisted.trial.test.nonexistent"
|
|
)
|
|
|
|
def test_findNonPackage(self) -> None:
|
|
self.assertRaises(ModuleNotFound, self.loader.findByName, "nonextant")
|
|
|
|
def test_findNonFile(self) -> None:
|
|
"""
|
|
findByName, given a file path that doesn't exist, will raise a
|
|
ValueError saying that it is not a Python file.
|
|
"""
|
|
path = util.sibpath(__file__, "nonexistent.py")
|
|
self.assertRaises(ValueError, self.loader.findByName, path)
|
|
|
|
def test_findFileWithImportError(self) -> None:
|
|
"""
|
|
findByName will re-raise ImportErrors inside modules that it has found
|
|
and imported.
|
|
"""
|
|
self.assertRaises(
|
|
ImportError, self.loader.findByName, "unimportablepackage.test_module"
|
|
)
|
|
|
|
|
|
def looselyResembles(module: ModuleType) -> Matcher[ModuleType]:
|
|
"""
|
|
Match a module with a L{ModuleSpec} like that of the given module.
|
|
|
|
@return: A matcher for a module spec that has the same name and origin as
|
|
the given module spec, though the origin may be structurally inequal
|
|
as long as it is semantically equal.
|
|
"""
|
|
expected = module.__spec__
|
|
# Technically possible but not expected in any of the tests written so
|
|
# far.
|
|
assert expected is not None
|
|
match_spec = has_properties(
|
|
{
|
|
"name": equal_to(expected.name),
|
|
"origin": after(
|
|
filepath.FilePath,
|
|
equal_to(filepath.FilePath(expected.origin)),
|
|
),
|
|
}
|
|
)
|
|
return after(attrgetter("__spec__"), match_spec)
|
|
|
|
|
|
class FileTests(packages.SysPathManglingTest):
|
|
"""
|
|
Tests for L{runner.filenameToModule}.
|
|
"""
|
|
|
|
def test_notFile(self) -> None:
|
|
"""
|
|
L{runner.filenameToModule} raises a C{ValueError} when a non-existing
|
|
file is passed.
|
|
"""
|
|
err = self.assertRaises(ValueError, runner.filenameToModule, "it")
|
|
self.assertEqual(str(err), "'it' doesn't exist")
|
|
|
|
def test_moduleInPath(self) -> None:
|
|
"""
|
|
If the file in question is a module on the Python path, then it should
|
|
properly import and return that module.
|
|
"""
|
|
sample1 = runner.filenameToModule(util.sibpath(__file__, "sample.py"))
|
|
from twisted.trial.test import sample as sample2
|
|
|
|
self.assertEqual(sample2, sample1)
|
|
|
|
def test_moduleNotInPath(self) -> None:
|
|
"""
|
|
If passed the path to a file containing the implementation of a
|
|
module within a package which is not on the import path,
|
|
L{runner.filenameToModule} returns a module object loosely
|
|
resembling the module defined by that file anyway.
|
|
"""
|
|
|
|
self.mangleSysPath(self.oldPath)
|
|
sample1 = runner.filenameToModule(
|
|
os.path.join(self.parent, "goodpackage", "test_sample.py")
|
|
)
|
|
self.assertEqual(sample1.__name__, "goodpackage.test_sample")
|
|
|
|
self.cleanUpModules()
|
|
self.mangleSysPath(self.newPath)
|
|
from goodpackage import test_sample as sample2 # type: ignore[import-not-found]
|
|
|
|
self.assertIsNot(sample1, sample2)
|
|
assert_that(sample1, looselyResembles(sample2))
|
|
|
|
def test_packageInPath(self) -> None:
|
|
"""
|
|
If the file in question is a package on the Python path, then it should
|
|
properly import and return that package.
|
|
"""
|
|
package1 = runner.filenameToModule(os.path.join(self.parent, "goodpackage"))
|
|
|
|
self.assertIs(package1, sys.modules["goodpackage"])
|
|
|
|
def test_packageNotInPath(self) -> None:
|
|
"""
|
|
If passed the path to a directory which represents a package which
|
|
is not on the import path, L{runner.filenameToModule} returns a
|
|
module object loosely resembling the package defined by that
|
|
directory anyway.
|
|
"""
|
|
self.mangleSysPath(self.oldPath)
|
|
package1 = runner.filenameToModule(os.path.join(self.parent, "goodpackage"))
|
|
self.assertEqual(package1.__name__, "goodpackage")
|
|
|
|
self.cleanUpModules()
|
|
self.mangleSysPath(self.newPath)
|
|
import goodpackage
|
|
|
|
self.assertIsNot(package1, goodpackage)
|
|
assert_that(package1, looselyResembles(goodpackage))
|
|
|
|
def test_directoryNotPackage(self) -> None:
|
|
"""
|
|
L{runner.filenameToModule} raises a C{ValueError} when the name of an
|
|
empty directory is passed that isn't considered a valid Python package
|
|
because it doesn't contain a C{__init__.py} file.
|
|
"""
|
|
emptyDir = filepath.FilePath(self.parent).child("emptyDirectory")
|
|
emptyDir.createDirectory()
|
|
|
|
err = self.assertRaises(ValueError, runner.filenameToModule, emptyDir.path)
|
|
self.assertEqual(str(err), f"{emptyDir.path!r} is not a package directory")
|
|
|
|
def test_filenameNotPython(self) -> None:
|
|
"""
|
|
L{runner.filenameToModule} raises a C{SyntaxError} when a non-Python
|
|
file is passed.
|
|
"""
|
|
filename = filepath.FilePath(self.parent).child("notpython")
|
|
filename.setContent(b"This isn't python")
|
|
self.assertRaises(SyntaxError, runner.filenameToModule, filename.path)
|
|
|
|
def test_filenameMatchesPackage(self) -> None:
|
|
"""
|
|
The C{__file__} attribute of the module should match the package name.
|
|
"""
|
|
filename = filepath.FilePath(self.parent).child("goodpackage.py")
|
|
filename.setContent(packages.testModule.encode("utf8"))
|
|
|
|
try:
|
|
module = runner.filenameToModule(filename.path)
|
|
self.assertEqual(filename.path, module.__file__)
|
|
finally:
|
|
filename.remove()
|
|
|
|
def test_directory(self) -> None:
|
|
"""
|
|
Test loader against a filesystem directory containing an empty
|
|
C{__init__.py} file. It should handle 'path' and 'path/' the same way.
|
|
"""
|
|
goodDir = filepath.FilePath(self.parent).child("goodDirectory")
|
|
goodDir.createDirectory()
|
|
goodDir.child("__init__.py").setContent(b"")
|
|
|
|
try:
|
|
module = runner.filenameToModule(goodDir.path)
|
|
self.assertTrue(module.__name__.endswith("goodDirectory"))
|
|
module = runner.filenameToModule(goodDir.path + os.path.sep)
|
|
self.assertTrue(module.__name__.endswith("goodDirectory"))
|
|
finally:
|
|
goodDir.remove()
|
|
|
|
|
|
class LoaderTests(packages.SysPathManglingTest):
|
|
"""
|
|
Tests for L{trial.TestLoader}.
|
|
"""
|
|
|
|
def setUp(self) -> None: # type: ignore[override]
|
|
self.loader = runner.TestLoader()
|
|
packages.SysPathManglingTest.setUp(self)
|
|
|
|
def test_sortCases(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadClass(sample.AlphabetTest)
|
|
self.assertEqual(
|
|
["test_a", "test_b", "test_c"],
|
|
[test._testMethodName for test in suite._tests],
|
|
)
|
|
newOrder = ["test_b", "test_c", "test_a"]
|
|
sortDict = dict(zip(newOrder, range(3)))
|
|
self.loader.sorter = lambda x: sortDict.get(x.shortDescription(), -1) # type: ignore[arg-type, union-attr, call-arg]
|
|
suite = self.loader.loadClass(sample.AlphabetTest)
|
|
self.assertEqual(newOrder, [test._testMethodName for test in suite._tests])
|
|
|
|
def test_loadFailure(self) -> None:
|
|
"""
|
|
Loading a test that fails and getting the result of it ends up with one
|
|
test ran and one failure.
|
|
"""
|
|
suite = self.loader.loadByName(
|
|
"twisted.trial.test.erroneous.TestRegularFail.test_fail"
|
|
)
|
|
result = reporter.TestResult()
|
|
suite.run(result)
|
|
self.assertEqual(result.testsRun, 1)
|
|
self.assertEqual(len(result.failures), 1)
|
|
|
|
def test_loadBadDecorator(self) -> None:
|
|
"""
|
|
A decorated test method for which the decorator has failed to set the
|
|
method's __name__ correctly is loaded and its name in the class scope
|
|
discovered.
|
|
"""
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadAnything(
|
|
sample.DecorationTest.test_badDecorator,
|
|
parent=sample.DecorationTest,
|
|
qualName=["sample", "DecorationTest", "test_badDecorator"],
|
|
)
|
|
self.assertEqual(1, suite.countTestCases())
|
|
self.assertEqual("test_badDecorator", suite._testMethodName)
|
|
|
|
def test_loadGoodDecorator(self) -> None:
|
|
"""
|
|
A decorated test method for which the decorator has set the method's
|
|
__name__ correctly is loaded and the only name by which it goes is used.
|
|
"""
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadAnything(
|
|
sample.DecorationTest.test_goodDecorator,
|
|
parent=sample.DecorationTest,
|
|
qualName=["sample", "DecorationTest", "test_goodDecorator"],
|
|
)
|
|
self.assertEqual(1, suite.countTestCases())
|
|
self.assertEqual("test_goodDecorator", suite._testMethodName)
|
|
|
|
def test_loadRenamedDecorator(self) -> None:
|
|
"""
|
|
Load a decorated method which has been copied to a new name inside the
|
|
class. Thus its __name__ and its key in the class's __dict__ no
|
|
longer match.
|
|
"""
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadAnything(
|
|
sample.DecorationTest.test_renamedDecorator,
|
|
parent=sample.DecorationTest,
|
|
qualName=["sample", "DecorationTest", "test_renamedDecorator"],
|
|
)
|
|
self.assertEqual(1, suite.countTestCases())
|
|
self.assertEqual("test_renamedDecorator", suite._testMethodName)
|
|
|
|
def test_loadClass(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadClass(sample.FooTest)
|
|
self.assertEqual(2, suite.countTestCases())
|
|
self.assertEqual(
|
|
["test_bar", "test_foo"], [test._testMethodName for test in suite._tests]
|
|
)
|
|
|
|
def test_loadNonClass(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
self.assertRaises(TypeError, self.loader.loadClass, sample)
|
|
self.assertRaises(TypeError, self.loader.loadClass, sample.FooTest.test_foo)
|
|
self.assertRaises(TypeError, self.loader.loadClass, "string")
|
|
self.assertRaises(TypeError, self.loader.loadClass, ("foo", "bar"))
|
|
|
|
def test_loadNonTestCase(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
self.assertRaises(ValueError, self.loader.loadClass, sample.NotATest)
|
|
|
|
def test_loadModule(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadModule(sample)
|
|
self.assertEqual(10, suite.countTestCases())
|
|
|
|
def test_loadNonModule(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
self.assertRaises(TypeError, self.loader.loadModule, sample.FooTest)
|
|
self.assertRaises(TypeError, self.loader.loadModule, sample.FooTest.test_foo)
|
|
self.assertRaises(TypeError, self.loader.loadModule, "string")
|
|
self.assertRaises(TypeError, self.loader.loadModule, ("foo", "bar"))
|
|
|
|
def test_loadPackage(self) -> None:
|
|
import goodpackage
|
|
|
|
suite = self.loader.loadPackage(goodpackage)
|
|
self.assertEqual(7, suite.countTestCases())
|
|
|
|
def test_loadNonPackage(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
self.assertRaises(TypeError, self.loader.loadPackage, sample.FooTest)
|
|
self.assertRaises(TypeError, self.loader.loadPackage, sample.FooTest.test_foo)
|
|
self.assertRaises(TypeError, self.loader.loadPackage, "string")
|
|
self.assertRaises(TypeError, self.loader.loadPackage, ("foo", "bar"))
|
|
|
|
def test_loadModuleAsPackage(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
## XXX -- should this instead raise a ValueError? -- jml
|
|
self.assertRaises(TypeError, self.loader.loadPackage, sample)
|
|
|
|
def test_loadPackageRecursive(self) -> None:
|
|
import goodpackage
|
|
|
|
suite = self.loader.loadPackage(goodpackage, recurse=True)
|
|
self.assertEqual(14, suite.countTestCases())
|
|
|
|
def test_loadAnythingOnModule(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadAnything(sample)
|
|
self.assertEqual(
|
|
sample.__name__, suite._tests[0]._tests[0].__class__.__module__
|
|
)
|
|
|
|
def test_loadAnythingOnClass(self) -> None:
|
|
from twisted.trial.test import sample
|
|
|
|
suite = self.loader.loadAnything(sample.FooTest)
|
|
self.assertEqual(2, suite.countTestCases())
|
|
|
|
def test_loadAnythingOnPackage(self) -> None:
|
|
import goodpackage
|
|
|
|
suite = self.loader.loadAnything(goodpackage)
|
|
self.assertTrue(isinstance(suite, self.loader.suiteFactory))
|
|
self.assertEqual(7, suite.countTestCases())
|
|
|
|
def test_loadAnythingOnPackageRecursive(self) -> None:
|
|
import goodpackage
|
|
|
|
suite = self.loader.loadAnything(goodpackage, recurse=True)
|
|
self.assertTrue(isinstance(suite, self.loader.suiteFactory))
|
|
self.assertEqual(14, suite.countTestCases())
|
|
|
|
def test_loadAnythingOnString(self) -> None:
|
|
# the important thing about this test is not the string-iness
|
|
# but the non-handledness.
|
|
self.assertRaises(TypeError, self.loader.loadAnything, "goodpackage")
|
|
|
|
def test_importErrors(self) -> None:
|
|
import package # type: ignore[import-not-found]
|
|
|
|
suite = self.loader.loadPackage(package, recurse=True)
|
|
result = reporter.Reporter()
|
|
suite.run(result)
|
|
self.assertEqual(False, result.wasSuccessful())
|
|
self.assertEqual(2, len(result.errors))
|
|
errors = [test.id() for test, error in result.errors]
|
|
errors.sort()
|
|
self.assertEqual(
|
|
errors, ["package.test_bad_module", "package.test_import_module"]
|
|
)
|
|
|
|
def test_differentInstances(self) -> None:
|
|
"""
|
|
L{TestLoader.loadClass} returns a suite with each test method
|
|
represented by a different instances of the L{TestCase} they are
|
|
defined on.
|
|
"""
|
|
|
|
class DistinctInstances(pyunit.TestCase):
|
|
def test_1(self) -> None:
|
|
self.first = "test1Run"
|
|
|
|
def test_2(self) -> None:
|
|
self.assertFalse(hasattr(self, "first"))
|
|
|
|
suite = self.loader.loadClass(DistinctInstances)
|
|
result = reporter.Reporter()
|
|
suite.run(result)
|
|
self.assertTrue(result.wasSuccessful())
|
|
|
|
def test_loadModuleWith_test_suite(self) -> None:
|
|
"""
|
|
Check that C{test_suite} is used when present and other L{TestCase}s are
|
|
not included.
|
|
"""
|
|
from twisted.trial.test import mockcustomsuite
|
|
|
|
suite = self.loader.loadModule(mockcustomsuite)
|
|
self.assertEqual(0, suite.countTestCases())
|
|
self.assertEqual("MyCustomSuite", getattr(suite, "name", None))
|
|
|
|
def test_loadModuleWith_testSuite(self) -> None:
|
|
"""
|
|
Check that C{testSuite} is used when present and other L{TestCase}s are
|
|
not included.
|
|
"""
|
|
from twisted.trial.test import mockcustomsuite2
|
|
|
|
suite = self.loader.loadModule(mockcustomsuite2)
|
|
self.assertEqual(0, suite.countTestCases())
|
|
self.assertEqual("MyCustomSuite", getattr(suite, "name", None))
|
|
|
|
def test_loadModuleWithBothCustom(self) -> None:
|
|
"""
|
|
Check that if C{testSuite} and C{test_suite} are both present in a
|
|
module then C{testSuite} gets priority.
|
|
"""
|
|
from twisted.trial.test import mockcustomsuite3
|
|
|
|
suite = self.loader.loadModule(mockcustomsuite3)
|
|
self.assertEqual("testSuite", getattr(suite, "name", None))
|
|
|
|
def test_customLoadRaisesAttributeError(self) -> None:
|
|
"""
|
|
Make sure that any C{AttributeError}s raised by C{testSuite} are not
|
|
swallowed by L{TestLoader}.
|
|
"""
|
|
|
|
def testSuite() -> None:
|
|
raise AttributeError("should be reraised")
|
|
|
|
from twisted.trial.test import mockcustomsuite2
|
|
|
|
mockcustomsuite2.testSuite, original = (testSuite, mockcustomsuite2.testSuite)
|
|
try:
|
|
self.assertRaises(AttributeError, self.loader.loadModule, mockcustomsuite2)
|
|
finally:
|
|
mockcustomsuite2.testSuite = original
|
|
|
|
# XXX - duplicated and modified from test_script
|
|
def assertSuitesEqual(
|
|
self,
|
|
test1: pyunit.TestCase | pyunit.TestSuite,
|
|
test2: pyunit.TestCase | pyunit.TestSuite,
|
|
) -> None:
|
|
names1 = testNames(test1)
|
|
names2 = testNames(test2)
|
|
names1.sort()
|
|
names2.sort()
|
|
self.assertEqual(names1, names2)
|
|
|
|
def test_loadByNamesDuplicate(self) -> None:
|
|
"""
|
|
Check that loadByNames ignores duplicate names
|
|
"""
|
|
module = "twisted.trial.test.test_log"
|
|
suite1 = self.loader.loadByNames([module, module], True)
|
|
suite2 = self.loader.loadByName(module, True)
|
|
self.assertSuitesEqual(suite1, suite2)
|
|
|
|
def test_loadByNamesPreservesOrder(self) -> None:
|
|
"""
|
|
L{TestLoader.loadByNames} preserves the order of tests provided to it.
|
|
"""
|
|
modules = [
|
|
"inheritancepackage.test_x.A.test_foo",
|
|
"twisted.trial.test.sample",
|
|
"goodpackage",
|
|
"twisted.trial.test.test_log",
|
|
"twisted.trial.test.sample.FooTest",
|
|
"package.test_module",
|
|
]
|
|
suite1 = self.loader.loadByNames(modules)
|
|
suite2 = runner.TestSuite(map(self.loader.loadByName, modules))
|
|
self.assertEqual(testNames(suite1), testNames(suite2))
|
|
|
|
def test_loadDifferentNames(self) -> None:
|
|
"""
|
|
Check that loadByNames loads all the names that it is given
|
|
"""
|
|
modules = ["goodpackage", "package.test_module"]
|
|
suite1 = self.loader.loadByNames(modules)
|
|
suite2 = runner.TestSuite(map(self.loader.loadByName, modules))
|
|
self.assertSuitesEqual(suite1, suite2)
|
|
|
|
def test_loadInheritedMethods(self) -> None:
|
|
"""
|
|
Check that test methods names which are inherited from are all
|
|
loaded rather than just one.
|
|
"""
|
|
methods = [
|
|
"inheritancepackage.test_x.A.test_foo",
|
|
"inheritancepackage.test_x.B.test_foo",
|
|
]
|
|
suite1 = self.loader.loadByNames(methods)
|
|
suite2 = runner.TestSuite(map(self.loader.loadByName, methods))
|
|
self.assertSuitesEqual(suite1, suite2)
|
|
|
|
|
|
class ZipLoadingTests(LoaderTests):
|
|
def setUp(self) -> None: # type: ignore[override]
|
|
from twisted.python.test.test_zippath import zipit
|
|
|
|
LoaderTests.setUp(self)
|
|
zipit(self.parent, self.parent + ".zip")
|
|
self.parent += ".zip"
|
|
self.mangleSysPath(self.oldPath + [self.parent])
|
|
|
|
|
|
class PackageOrderingTests(packages.SysPathManglingTest):
|
|
def setUp(self) -> None: # type: ignore[override]
|
|
self.loader = runner.TestLoader()
|
|
self.topDir = self.mktemp()
|
|
parent = os.path.join(self.topDir, "uberpackage")
|
|
os.makedirs(parent)
|
|
open(os.path.join(parent, "__init__.py"), "wb").close()
|
|
packages.SysPathManglingTest.setUp(self, parent)
|
|
self.mangleSysPath(self.oldPath + [self.topDir])
|
|
|
|
def _trialSortAlgorithm(
|
|
self, sorter: Callable[[PythonModule | PythonAttribute], SupportsRichComparison]
|
|
) -> Generator[PythonModule | PythonAttribute, None, None]:
|
|
"""
|
|
Right now, halfway by accident, trial sorts like this:
|
|
|
|
1. all modules are grouped together in one list and sorted.
|
|
|
|
2. within each module, the classes are grouped together in one list
|
|
and sorted.
|
|
|
|
3. finally within each class, each test method is grouped together
|
|
in a list and sorted.
|
|
|
|
This attempts to return a sorted list of testable thingies following
|
|
those rules, so that we can compare the behavior of loadPackage.
|
|
|
|
The things that show as 'cases' are errors from modules which failed to
|
|
import, and test methods. Let's gather all those together.
|
|
"""
|
|
pkg = getModule("uberpackage")
|
|
testModules = []
|
|
for testModule in pkg.walkModules():
|
|
if testModule.name.split(".")[-1].startswith("test_"):
|
|
testModules.append(testModule)
|
|
sortedModules = sorted(testModules, key=sorter) # ONE
|
|
for modinfo in sortedModules:
|
|
# Now let's find all the classes.
|
|
module = modinfo.load(None)
|
|
if module is None:
|
|
yield modinfo
|
|
else:
|
|
testClasses = []
|
|
for attrib in modinfo.iterAttributes():
|
|
if runner.isTestCase(attrib.load()):
|
|
testClasses.append(attrib)
|
|
sortedClasses = sorted(testClasses, key=sorter) # TWO
|
|
for clsinfo in sortedClasses:
|
|
testMethods = []
|
|
for attr in clsinfo.iterAttributes():
|
|
if attr.name.split(".")[-1].startswith("test"):
|
|
testMethods.append(attr)
|
|
sortedMethods = sorted(testMethods, key=sorter) # THREE
|
|
yield from sortedMethods
|
|
|
|
def loadSortedPackages(
|
|
self, sorter: Callable[[runner._Loadable], SupportsRichComparison] = runner.name
|
|
) -> None:
|
|
"""
|
|
Verify that packages are loaded in the correct order.
|
|
"""
|
|
import uberpackage # type: ignore[import-not-found]
|
|
|
|
self.loader.sorter = sorter
|
|
suite = self.loader.loadPackage(uberpackage, recurse=True)
|
|
# XXX: Work around strange, unexplained Zope crap.
|
|
# jml, 2007-11-15.
|
|
suite = unittest.decorate(suite, ITestCase)
|
|
resultingTests = list(_iterateTests(suite))
|
|
manifest = list(self._trialSortAlgorithm(sorter))
|
|
for number, (manifestTest, actualTest) in enumerate(
|
|
zip(manifest, resultingTests)
|
|
):
|
|
self.assertEqual(
|
|
manifestTest.name,
|
|
actualTest.id(),
|
|
"#%d: %s != %s" % (number, manifestTest.name, actualTest.id()),
|
|
)
|
|
self.assertEqual(len(manifest), len(resultingTests))
|
|
|
|
def test_sortPackagesDefaultOrder(self) -> None:
|
|
self.loadSortedPackages()
|
|
|
|
def test_sortPackagesSillyOrder(self) -> None:
|
|
def sillySorter(s: runner._Loadable) -> str:
|
|
# This has to work on fully-qualified class names and class
|
|
# objects, which is silly, but it's the "spec", such as it is.
|
|
# if isinstance(s, type):
|
|
# return s.__module__+'.'+s.__name__
|
|
n = runner.name(s)
|
|
d = md5(n.encode("utf8")).hexdigest()
|
|
return d
|
|
|
|
self.loadSortedPackages(sillySorter)
|