* Tests: unroll test_multiworlds.TestTwoPlayerMulti Also adds a helper function that other tests can use to unroll tests. * Docs: add more details to docs/tests.md * Explain parametrization, subtests and link to the new helper * Mention some performance details and work-arounds * Mention multithreading / pytest-xdist * Tests: make param.classvar_matrix accept sets * CI: add test/param.py to type checking * Tests: add missing typing to test/param.py * Tests: fix typo in test/param.py doc comment Co-authored-by: qwint <qwint.42@gmail.com> * update docs * Docs: reword note on performance --------- Co-authored-by: qwint <qwint.42@gmail.com>
		
			
				
	
	
		
			47 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			47 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import itertools
 | 
						|
import sys
 | 
						|
from typing import Any, Callable, Iterable
 | 
						|
 | 
						|
 | 
						|
def classvar_matrix(**kwargs: Iterable[Any]) -> Callable[[type], None]:
 | 
						|
    """
 | 
						|
    Create a new class for each variation of input, allowing to generate a TestCase matrix / parametrization that
 | 
						|
    supports multi-threading and has better reporting for ``unittest --durations=...`` and ``pytest --durations=...``
 | 
						|
    than subtests.
 | 
						|
 | 
						|
    The kwargs will be set as ClassVars in the newly created classes. Use as ::
 | 
						|
 | 
						|
        @classvar_matrix(var_name=[value1, value2])
 | 
						|
        class MyTestCase(unittest.TestCase):
 | 
						|
            var_name: typing.ClassVar[...]
 | 
						|
 | 
						|
    :param kwargs: A dict of ClassVars to set, where key is the variable name and value is a list of all values.
 | 
						|
    :return: A decorator to be applied to a class.
 | 
						|
    """
 | 
						|
    keys: tuple[str]
 | 
						|
    values: Iterable[Iterable[Any]]
 | 
						|
    keys, values = zip(*kwargs.items())
 | 
						|
    values = map(lambda v: sorted(v) if isinstance(v, (set, frozenset)) else v, values)
 | 
						|
    permutations_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
 | 
						|
 | 
						|
    def decorator(cls: type) -> None:
 | 
						|
        mod = sys.modules[cls.__module__]
 | 
						|
 | 
						|
        for permutation in permutations_dicts:
 | 
						|
 | 
						|
            class Unrolled(cls):  # type: ignore
 | 
						|
                pass
 | 
						|
 | 
						|
            for k, v in permutation.items():
 | 
						|
                setattr(Unrolled, k, v)
 | 
						|
            params = ", ".join([f"{k}={repr(v)}" for k, v in permutation.items()])
 | 
						|
            params = f"{{{params}}}"
 | 
						|
 | 
						|
            Unrolled.__module__ = cls.__module__
 | 
						|
            Unrolled.__qualname__ = f"{cls.__qualname__}{params}"
 | 
						|
            setattr(mod, f"{cls.__name__}{params}", Unrolled)
 | 
						|
 | 
						|
        return None
 | 
						|
 | 
						|
    return decorator
 |