lsst-utils 25.2023.2800__py3-none-any.whl → 29.2025.4800__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lsst/utils/__init__.py +0 -1
- lsst/utils/argparsing.py +79 -0
- lsst/utils/classes.py +26 -9
- lsst/utils/db_auth.py +339 -0
- lsst/utils/deprecated.py +4 -4
- lsst/utils/inheritDoc.py +30 -4
- lsst/utils/introspection.py +242 -23
- lsst/utils/iteration.py +188 -2
- lsst/utils/logging.py +78 -12
- lsst/utils/packages.py +211 -55
- lsst/utils/plotting/__init__.py +15 -0
- lsst/utils/plotting/figures.py +159 -0
- lsst/utils/plotting/limits.py +12 -1
- lsst/utils/plotting/publication_plots.py +184 -0
- lsst/utils/plotting/rubin.mplstyle +46 -0
- lsst/utils/tests.py +112 -57
- lsst/utils/threads.py +1 -1
- lsst/utils/timer.py +189 -45
- lsst/utils/usage.py +2 -2
- lsst/utils/version.py +1 -1
- lsst/utils/wrappers.py +65 -22
- {lsst_utils-25.2023.2800.dist-info → lsst_utils-29.2025.4800.dist-info}/METADATA +18 -12
- lsst_utils-29.2025.4800.dist-info/RECORD +32 -0
- {lsst_utils-25.2023.2800.dist-info → lsst_utils-29.2025.4800.dist-info}/WHEEL +1 -1
- lsst/utils/ellipsis.py +0 -66
- lsst/utils/get_caller_name.py +0 -47
- lsst_utils-25.2023.2800.dist-info/RECORD +0 -28
- {lsst_utils-25.2023.2800.dist-info → lsst_utils-29.2025.4800.dist-info/licenses}/COPYRIGHT +0 -0
- {lsst_utils-25.2023.2800.dist-info → lsst_utils-29.2025.4800.dist-info/licenses}/LICENSE +0 -0
- {lsst_utils-25.2023.2800.dist-info → lsst_utils-29.2025.4800.dist-info}/top_level.txt +0 -0
- {lsst_utils-25.2023.2800.dist-info → lsst_utils-29.2025.4800.dist-info}/zip-safe +0 -0
lsst/utils/tests.py
CHANGED
|
@@ -9,22 +9,22 @@
|
|
|
9
9
|
# Use of this source code is governed by a 3-clause BSD-style
|
|
10
10
|
# license that can be found in the LICENSE file.
|
|
11
11
|
|
|
12
|
-
"""Support code for running unit tests"""
|
|
12
|
+
"""Support code for running unit tests."""
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
|
-
"init",
|
|
18
|
-
"MemoryTestCase",
|
|
19
17
|
"ExecutablesTestCase",
|
|
20
18
|
"ImportTestCase",
|
|
21
|
-
"
|
|
19
|
+
"MemoryTestCase",
|
|
22
20
|
"TestCase",
|
|
23
21
|
"assertFloatsAlmostEqual",
|
|
24
|
-
"assertFloatsNotEqual",
|
|
25
22
|
"assertFloatsEqual",
|
|
26
|
-
"
|
|
23
|
+
"assertFloatsNotEqual",
|
|
27
24
|
"classParameters",
|
|
25
|
+
"debugger",
|
|
26
|
+
"getTempFilePath",
|
|
27
|
+
"init",
|
|
28
28
|
"methodParameters",
|
|
29
29
|
"temporaryDirectory",
|
|
30
30
|
]
|
|
@@ -32,7 +32,6 @@ __all__ = [
|
|
|
32
32
|
import contextlib
|
|
33
33
|
import functools
|
|
34
34
|
import gc
|
|
35
|
-
import importlib.resources as resources
|
|
36
35
|
import inspect
|
|
37
36
|
import itertools
|
|
38
37
|
import os
|
|
@@ -43,7 +42,8 @@ import sys
|
|
|
43
42
|
import tempfile
|
|
44
43
|
import unittest
|
|
45
44
|
import warnings
|
|
46
|
-
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
|
45
|
+
from collections.abc import Callable, Container, Iterable, Iterator, Mapping, Sequence
|
|
46
|
+
from importlib import resources
|
|
47
47
|
from typing import Any, ClassVar
|
|
48
48
|
|
|
49
49
|
import numpy
|
|
@@ -129,12 +129,12 @@ unittest.defaultTestLoader.suiteClass = _suiteClassWrapper
|
|
|
129
129
|
class MemoryTestCase(unittest.TestCase):
|
|
130
130
|
"""Check for resource leaks."""
|
|
131
131
|
|
|
132
|
-
ignore_regexps: list[str] = []
|
|
132
|
+
ignore_regexps: ClassVar[list[str]] = []
|
|
133
133
|
"""List of regexps to ignore when checking for open files."""
|
|
134
134
|
|
|
135
135
|
@classmethod
|
|
136
136
|
def tearDownClass(cls) -> None:
|
|
137
|
-
"""Reset the leak counter when the tests have been completed"""
|
|
137
|
+
"""Reset the leak counter when the tests have been completed."""
|
|
138
138
|
init()
|
|
139
139
|
|
|
140
140
|
def testFileDescriptorLeaks(self) -> None:
|
|
@@ -153,6 +153,7 @@ class MemoryTestCase(unittest.TestCase):
|
|
|
153
153
|
for f in now_open
|
|
154
154
|
if not f.endswith(".car")
|
|
155
155
|
and not f.startswith("/proc/")
|
|
156
|
+
and not f.startswith("/sys/")
|
|
156
157
|
and not f.endswith(".ttf")
|
|
157
158
|
and not (f.startswith("/var/lib/") and f.endswith("/passwd"))
|
|
158
159
|
and not f.endswith("astropy.log")
|
|
@@ -165,7 +166,7 @@ class MemoryTestCase(unittest.TestCase):
|
|
|
165
166
|
if diff:
|
|
166
167
|
for f in diff:
|
|
167
168
|
print(f"File open: {f}")
|
|
168
|
-
self.fail("Failed to close
|
|
169
|
+
self.fail(f"Failed to close {len(diff)} file{'s' if len(diff) != 1 else ''}")
|
|
169
170
|
|
|
170
171
|
|
|
171
172
|
class ExecutablesTestCase(unittest.TestCase):
|
|
@@ -346,6 +347,16 @@ class ImportTestCase(unittest.TestCase):
|
|
|
346
347
|
PACKAGES: ClassVar[Iterable[str]] = ()
|
|
347
348
|
"""Packages to be imported."""
|
|
348
349
|
|
|
350
|
+
SKIP_FILES: ClassVar[Mapping[str, Container[str]]] = {}
|
|
351
|
+
"""Files to be skipped importing; specified as key-value pairs.
|
|
352
|
+
|
|
353
|
+
The key is the package name and the value is a set of files names in that
|
|
354
|
+
package to skip.
|
|
355
|
+
|
|
356
|
+
Note: Files with names not ending in .py or beginning with leading double
|
|
357
|
+
underscores are always skipped.
|
|
358
|
+
"""
|
|
359
|
+
|
|
349
360
|
_n_registered = 0
|
|
350
361
|
"""Number of packages registered for testing by this class."""
|
|
351
362
|
|
|
@@ -367,7 +378,7 @@ class ImportTestCase(unittest.TestCase):
|
|
|
367
378
|
for mod in cls.PACKAGES:
|
|
368
379
|
test_name = "test_import_" + mod.replace(".", "_")
|
|
369
380
|
|
|
370
|
-
def test_import(*args: Any) -> None:
|
|
381
|
+
def test_import(*args: Any, mod=mod) -> None:
|
|
371
382
|
self = args[0]
|
|
372
383
|
self.assertImport(mod)
|
|
373
384
|
|
|
@@ -378,15 +389,19 @@ class ImportTestCase(unittest.TestCase):
|
|
|
378
389
|
# If there are no packages listed that is likely a mistake and
|
|
379
390
|
# so register a failing test.
|
|
380
391
|
if cls._n_registered == 0:
|
|
381
|
-
|
|
392
|
+
cls.test_no_packages_registered = cls._test_no_packages_registered_for_import_testing
|
|
382
393
|
|
|
383
394
|
def assertImport(self, root_pkg):
|
|
384
395
|
for file in resources.files(root_pkg).iterdir():
|
|
385
396
|
file = file.name
|
|
397
|
+
# When support for python 3.9 is dropped, this could be updated to
|
|
398
|
+
# use match case construct.
|
|
386
399
|
if not file.endswith(".py"):
|
|
387
400
|
continue
|
|
388
401
|
if file.startswith("__"):
|
|
389
402
|
continue
|
|
403
|
+
if file in self.SKIP_FILES.get(root_pkg, ()):
|
|
404
|
+
continue
|
|
390
405
|
root, _ = os.path.splitext(file)
|
|
391
406
|
module_name = f"{root_pkg}.{root}"
|
|
392
407
|
with self.subTest(module=module_name):
|
|
@@ -399,7 +414,7 @@ class ImportTestCase(unittest.TestCase):
|
|
|
399
414
|
@contextlib.contextmanager
|
|
400
415
|
def getTempFilePath(ext: str, expectOutput: bool = True) -> Iterator[str]:
|
|
401
416
|
"""Return a path suitable for a temporary file and try to delete the
|
|
402
|
-
file on success
|
|
417
|
+
file on success.
|
|
403
418
|
|
|
404
419
|
If the with block completes successfully then the file is deleted,
|
|
405
420
|
if possible; failure results in a printed warning.
|
|
@@ -419,11 +434,11 @@ def getTempFilePath(ext: str, expectOutput: bool = True) -> Iterator[str]:
|
|
|
419
434
|
If `False`, a file should not be present when the context manager
|
|
420
435
|
exits.
|
|
421
436
|
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
Yields
|
|
438
|
+
------
|
|
424
439
|
path : `str`
|
|
425
440
|
Path for a temporary file. The path is a combination of the caller's
|
|
426
|
-
file path and the name of the top-level function
|
|
441
|
+
file path and the name of the top-level function.
|
|
427
442
|
|
|
428
443
|
Examples
|
|
429
444
|
--------
|
|
@@ -432,6 +447,8 @@ def getTempFilePath(ext: str, expectOutput: bool = True) -> Iterator[str]:
|
|
|
432
447
|
# file tests/testFoo.py
|
|
433
448
|
import unittest
|
|
434
449
|
import lsst.utils.tests
|
|
450
|
+
|
|
451
|
+
|
|
435
452
|
class FooTestCase(unittest.TestCase):
|
|
436
453
|
def testBasics(self):
|
|
437
454
|
self.runTest()
|
|
@@ -445,6 +462,8 @@ def getTempFilePath(ext: str, expectOutput: bool = True) -> Iterator[str]:
|
|
|
445
462
|
# at the end of this "with" block the path tmpFile will be
|
|
446
463
|
# deleted, but only if the file exists and the "with"
|
|
447
464
|
# block terminated normally (rather than with an exception)
|
|
465
|
+
|
|
466
|
+
|
|
448
467
|
...
|
|
449
468
|
"""
|
|
450
469
|
stack = inspect.stack()
|
|
@@ -464,7 +483,11 @@ def getTempFilePath(ext: str, expectOutput: bool = True) -> Iterator[str]:
|
|
|
464
483
|
callerFileName = os.path.splitext(callerFileNameWithExt)[0]
|
|
465
484
|
outDir = os.path.join(callerDir, ".tests")
|
|
466
485
|
if not os.path.isdir(outDir):
|
|
467
|
-
|
|
486
|
+
# No .tests directory implies we are not running with sconsUtils.
|
|
487
|
+
# Need to use the current working directory, the callerDir, or
|
|
488
|
+
# /tmp equivalent. If cwd is used if must be as an absolute path
|
|
489
|
+
# in case the test code changes cwd.
|
|
490
|
+
outDir = os.path.abspath(os.path.curdir)
|
|
468
491
|
prefix = f"{callerFileName}_{callerFuncName}-"
|
|
469
492
|
outPath = tempfile.mktemp(dir=outDir, suffix=ext, prefix=prefix)
|
|
470
493
|
if os.path.exists(outPath):
|
|
@@ -473,10 +496,8 @@ def getTempFilePath(ext: str, expectOutput: bool = True) -> Iterator[str]:
|
|
|
473
496
|
# Use stacklevel 3 so that the warning is reported from the end of the
|
|
474
497
|
# with block
|
|
475
498
|
warnings.warn(f"Unexpectedly found pre-existing tempfile named {outPath!r}", stacklevel=3)
|
|
476
|
-
|
|
499
|
+
with contextlib.suppress(OSError):
|
|
477
500
|
os.remove(outPath)
|
|
478
|
-
except OSError:
|
|
479
|
-
pass
|
|
480
501
|
|
|
481
502
|
yield outPath
|
|
482
503
|
|
|
@@ -506,13 +527,23 @@ class TestCase(unittest.TestCase):
|
|
|
506
527
|
def inTestCase(func: Callable) -> Callable:
|
|
507
528
|
"""Add a free function to our custom TestCase class, while
|
|
508
529
|
also making it available as a free function.
|
|
530
|
+
|
|
531
|
+
Parameters
|
|
532
|
+
----------
|
|
533
|
+
func : `~collections.abc.Callable`
|
|
534
|
+
Function to be added to `unittest.TestCase` class.
|
|
535
|
+
|
|
536
|
+
Returns
|
|
537
|
+
-------
|
|
538
|
+
func : `~collections.abc.Callable`
|
|
539
|
+
The given function.
|
|
509
540
|
"""
|
|
510
541
|
setattr(TestCase, func.__name__, func)
|
|
511
542
|
return func
|
|
512
543
|
|
|
513
544
|
|
|
514
545
|
def debugger(*exceptions):
|
|
515
|
-
"""Enter the debugger when there's an uncaught exception
|
|
546
|
+
"""Enter the debugger when there's an uncaught exception.
|
|
516
547
|
|
|
517
548
|
To use, just slap a ``@debugger()`` on your function.
|
|
518
549
|
|
|
@@ -525,6 +556,12 @@ def debugger(*exceptions):
|
|
|
525
556
|
Code provided by "Rosh Oxymoron" on StackOverflow:
|
|
526
557
|
http://stackoverflow.com/questions/4398967/python-unit-testing-automatically-running-the-debugger-when-a-test-fails
|
|
527
558
|
|
|
559
|
+
Parameters
|
|
560
|
+
----------
|
|
561
|
+
*exceptions : `Exception`
|
|
562
|
+
Specific exception classes to catch. Default is to catch
|
|
563
|
+
`AssertionError`.
|
|
564
|
+
|
|
528
565
|
Notes
|
|
529
566
|
-----
|
|
530
567
|
Consider using ``pytest --pdb`` instead of this decorator.
|
|
@@ -560,13 +597,13 @@ def plotImageDiff(
|
|
|
560
597
|
Parameters
|
|
561
598
|
----------
|
|
562
599
|
lhs : `numpy.ndarray`
|
|
563
|
-
LHS values to compare; a 2-d NumPy array
|
|
600
|
+
LHS values to compare; a 2-d NumPy array.
|
|
564
601
|
rhs : `numpy.ndarray`
|
|
565
|
-
RHS values to compare; a 2-d NumPy array
|
|
602
|
+
RHS values to compare; a 2-d NumPy array.
|
|
566
603
|
bad : `numpy.ndarray`
|
|
567
|
-
A 2-d boolean NumPy array of values to emphasize in the plots
|
|
604
|
+
A 2-d boolean NumPy array of values to emphasize in the plots.
|
|
568
605
|
diff : `numpy.ndarray`
|
|
569
|
-
|
|
606
|
+
Difference array; a 2-d NumPy array, or None to show lhs-rhs.
|
|
570
607
|
plotFileName : `str`
|
|
571
608
|
Filename to save the plot to. If None, the plot will be displayed in
|
|
572
609
|
a window.
|
|
@@ -577,11 +614,20 @@ def plotImageDiff(
|
|
|
577
614
|
wrapped in a try/except block within packages that do not depend on
|
|
578
615
|
`matplotlib` (including `~lsst.utils`).
|
|
579
616
|
"""
|
|
580
|
-
|
|
617
|
+
if plotFileName is None:
|
|
618
|
+
# We need to create an interactive plot with pyplot.
|
|
619
|
+
from matplotlib import pyplot
|
|
620
|
+
|
|
621
|
+
fig = pyplot.figure()
|
|
622
|
+
else:
|
|
623
|
+
# We can create a non-interactive figure.
|
|
624
|
+
from .plotting import make_figure
|
|
625
|
+
|
|
626
|
+
fig = make_figure()
|
|
581
627
|
|
|
582
628
|
if diff is None:
|
|
583
629
|
diff = lhs - rhs
|
|
584
|
-
|
|
630
|
+
|
|
585
631
|
if bad is not None:
|
|
586
632
|
# make an rgba image that's red and transparent where not bad
|
|
587
633
|
badImage = numpy.zeros(bad.shape + (4,), dtype=numpy.uint8)
|
|
@@ -594,29 +640,29 @@ def plotImageDiff(
|
|
|
594
640
|
vmin2 = numpy.min(diff)
|
|
595
641
|
vmax2 = numpy.max(diff)
|
|
596
642
|
for n, (image, title) in enumerate([(lhs, "lhs"), (rhs, "rhs"), (diff, "diff")]):
|
|
597
|
-
|
|
598
|
-
im1 =
|
|
643
|
+
ax = fig.add_subplot(2, 3, n + 1)
|
|
644
|
+
im1 = ax.imshow(
|
|
599
645
|
image, cmap=pyplot.cm.gray, interpolation="nearest", origin="lower", vmin=vmin1, vmax=vmax1
|
|
600
646
|
)
|
|
601
647
|
if bad is not None:
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
im2 =
|
|
648
|
+
ax.imshow(badImage, alpha=0.2, interpolation="nearest", origin="lower")
|
|
649
|
+
ax.axis("off")
|
|
650
|
+
ax.set_title(title)
|
|
651
|
+
ax = fig.add_subplot(2, 3, n + 4)
|
|
652
|
+
im2 = ax.imshow(
|
|
607
653
|
image, cmap=pyplot.cm.gray, interpolation="nearest", origin="lower", vmin=vmin2, vmax=vmax2
|
|
608
654
|
)
|
|
609
655
|
if bad is not None:
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
cax1 =
|
|
615
|
-
|
|
616
|
-
cax2 =
|
|
617
|
-
|
|
656
|
+
ax.imshow(badImage, alpha=0.2, interpolation="nearest", origin="lower")
|
|
657
|
+
ax.axis("off")
|
|
658
|
+
ax.set_title(title)
|
|
659
|
+
fig.subplots_adjust(left=0.05, bottom=0.05, top=0.92, right=0.75, wspace=0.05, hspace=0.05)
|
|
660
|
+
cax1 = fig.add_subplot([0.8, 0.55, 0.05, 0.4])
|
|
661
|
+
fig.colorbar(im1, cax=cax1)
|
|
662
|
+
cax2 = fig.add_subplot([0.8, 0.05, 0.05, 0.4])
|
|
663
|
+
fig.colorbar(im2, cax=cax2)
|
|
618
664
|
if plotFileName:
|
|
619
|
-
|
|
665
|
+
fig.savefig(plotFileName)
|
|
620
666
|
else:
|
|
621
667
|
pyplot.show()
|
|
622
668
|
|
|
@@ -812,6 +858,8 @@ def assertFloatsNotEqual(
|
|
|
812
858
|
rhs : scalar or array-like
|
|
813
859
|
RHS value(s) to compare; may be a scalar or array-like of any
|
|
814
860
|
dimension.
|
|
861
|
+
**kwds : `~typing.Any`
|
|
862
|
+
Keyword parameters forwarded to `assertFloatsAlmostEqual`.
|
|
815
863
|
|
|
816
864
|
Raises
|
|
817
865
|
------
|
|
@@ -844,6 +892,8 @@ def assertFloatsEqual(
|
|
|
844
892
|
rhs : scalar or array-like
|
|
845
893
|
RHS value(s) to compare; may be a scalar or array-like of any
|
|
846
894
|
dimension.
|
|
895
|
+
**kwargs : `~typing.Any`
|
|
896
|
+
Keyword parameters forwarded to `assertFloatsAlmostEqual`.
|
|
847
897
|
|
|
848
898
|
Raises
|
|
849
899
|
------
|
|
@@ -887,7 +937,7 @@ def _settingsIterator(settings: dict[str, Sequence[Any]]) -> Iterator[dict[str,
|
|
|
887
937
|
|
|
888
938
|
|
|
889
939
|
def classParameters(**settings: Sequence[Any]) -> Callable:
|
|
890
|
-
"""Class decorator for generating unit tests
|
|
940
|
+
"""Class decorator for generating unit tests.
|
|
891
941
|
|
|
892
942
|
This decorator generates classes with class variables according to the
|
|
893
943
|
supplied ``settings``.
|
|
@@ -903,8 +953,7 @@ def classParameters(**settings: Sequence[Any]) -> Callable:
|
|
|
903
953
|
::
|
|
904
954
|
|
|
905
955
|
@classParameters(foo=[1, 2], bar=[3, 4])
|
|
906
|
-
class MyTestCase(unittest.TestCase):
|
|
907
|
-
...
|
|
956
|
+
class MyTestCase(unittest.TestCase): ...
|
|
908
957
|
|
|
909
958
|
will generate two classes, as if you wrote::
|
|
910
959
|
|
|
@@ -913,6 +962,7 @@ def classParameters(**settings: Sequence[Any]) -> Callable:
|
|
|
913
962
|
bar = 3
|
|
914
963
|
...
|
|
915
964
|
|
|
965
|
+
|
|
916
966
|
class MyTestCase_2_4(unittest.TestCase):
|
|
917
967
|
foo = 2
|
|
918
968
|
bar = 4
|
|
@@ -949,8 +999,7 @@ def methodParameters(**settings: Sequence[Any]) -> Callable:
|
|
|
949
999
|
.. code-block:: python
|
|
950
1000
|
|
|
951
1001
|
@methodParameters(foo=[1, 2], bar=[3, 4])
|
|
952
|
-
def testSomething(self, foo, bar):
|
|
953
|
-
...
|
|
1002
|
+
def testSomething(self, foo, bar): ...
|
|
954
1003
|
|
|
955
1004
|
will run:
|
|
956
1005
|
|
|
@@ -965,7 +1014,7 @@ def methodParameters(**settings: Sequence[Any]) -> Callable:
|
|
|
965
1014
|
def wrapper(self: unittest.TestCase, *args: Any, **kwargs: Any) -> None:
|
|
966
1015
|
for params in _settingsIterator(settings):
|
|
967
1016
|
kwargs.update(params)
|
|
968
|
-
with self.subTest(**params):
|
|
1017
|
+
with self.subTest(**{k: repr(v) for k, v in params.items()}):
|
|
969
1018
|
func(self, *args, **kwargs)
|
|
970
1019
|
|
|
971
1020
|
return wrapper
|
|
@@ -974,7 +1023,7 @@ def methodParameters(**settings: Sequence[Any]) -> Callable:
|
|
|
974
1023
|
|
|
975
1024
|
|
|
976
1025
|
def _cartesianProduct(settings: Mapping[str, Sequence[Any]]) -> Mapping[str, Sequence[Any]]:
|
|
977
|
-
"""Return the cartesian product of the settings
|
|
1026
|
+
"""Return the cartesian product of the settings.
|
|
978
1027
|
|
|
979
1028
|
Parameters
|
|
980
1029
|
----------
|
|
@@ -1007,7 +1056,7 @@ def _cartesianProduct(settings: Mapping[str, Sequence[Any]]) -> Mapping[str, Seq
|
|
|
1007
1056
|
|
|
1008
1057
|
|
|
1009
1058
|
def classParametersProduct(**settings: Sequence[Any]) -> Callable:
|
|
1010
|
-
"""Class decorator for generating unit tests
|
|
1059
|
+
"""Class decorator for generating unit tests.
|
|
1011
1060
|
|
|
1012
1061
|
This decorator generates classes with class variables according to the
|
|
1013
1062
|
cartesian product of the supplied ``settings``.
|
|
@@ -1023,8 +1072,7 @@ def classParametersProduct(**settings: Sequence[Any]) -> Callable:
|
|
|
1023
1072
|
.. code-block:: python
|
|
1024
1073
|
|
|
1025
1074
|
@classParametersProduct(foo=[1, 2], bar=[3, 4])
|
|
1026
|
-
class MyTestCase(unittest.TestCase):
|
|
1027
|
-
...
|
|
1075
|
+
class MyTestCase(unittest.TestCase): ...
|
|
1028
1076
|
|
|
1029
1077
|
will generate four classes, as if you wrote::
|
|
1030
1078
|
|
|
@@ -1035,16 +1083,19 @@ def classParametersProduct(**settings: Sequence[Any]) -> Callable:
|
|
|
1035
1083
|
bar = 3
|
|
1036
1084
|
...
|
|
1037
1085
|
|
|
1086
|
+
|
|
1038
1087
|
class MyTestCase_1_4(unittest.TestCase):
|
|
1039
1088
|
foo = 1
|
|
1040
1089
|
bar = 4
|
|
1041
1090
|
...
|
|
1042
1091
|
|
|
1092
|
+
|
|
1043
1093
|
class MyTestCase_2_3(unittest.TestCase):
|
|
1044
1094
|
foo = 2
|
|
1045
1095
|
bar = 3
|
|
1046
1096
|
...
|
|
1047
1097
|
|
|
1098
|
+
|
|
1048
1099
|
class MyTestCase_2_4(unittest.TestCase):
|
|
1049
1100
|
foo = 2
|
|
1050
1101
|
bar = 4
|
|
@@ -1067,9 +1118,8 @@ def methodParametersProduct(**settings: Sequence[Any]) -> Callable:
|
|
|
1067
1118
|
**settings : `dict` (`str`: iterable)
|
|
1068
1119
|
The parameter combinations to test. Each should be an iterable.
|
|
1069
1120
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1121
|
+
Examples
|
|
1122
|
+
--------
|
|
1073
1123
|
@methodParametersProduct(foo=[1, 2], bar=["black", "white"])
|
|
1074
1124
|
def testSomething(self, foo, bar):
|
|
1075
1125
|
...
|
|
@@ -1090,6 +1140,11 @@ def temporaryDirectory() -> Iterator[str]:
|
|
|
1090
1140
|
|
|
1091
1141
|
The difference from `tempfile.TemporaryDirectory` is that this ignores
|
|
1092
1142
|
errors when deleting a directory, which may happen with some filesystems.
|
|
1143
|
+
|
|
1144
|
+
Yields
|
|
1145
|
+
------
|
|
1146
|
+
`str`
|
|
1147
|
+
Name of the temporary directory.
|
|
1093
1148
|
"""
|
|
1094
1149
|
tmpdir = tempfile.mkdtemp()
|
|
1095
1150
|
yield tmpdir
|