mapFolding 0.6.0__py3-none-any.whl → 0.7.0__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.
- mapFolding/__init__.py +6 -104
- mapFolding/basecamp.py +12 -8
- mapFolding/beDRY.py +96 -286
- mapFolding/filesystem.py +87 -0
- mapFolding/noHomeYet.py +20 -0
- mapFolding/oeis.py +46 -39
- mapFolding/reference/flattened.py +377 -0
- mapFolding/reference/hunterNumba.py +132 -0
- mapFolding/reference/irvineJavaPort.py +120 -0
- mapFolding/reference/jax.py +208 -0
- mapFolding/reference/lunnan.py +153 -0
- mapFolding/reference/lunnanNumpy.py +123 -0
- mapFolding/reference/lunnanWhile.py +121 -0
- mapFolding/reference/rotatedEntryPoint.py +240 -0
- mapFolding/reference/total_countPlus1vsPlusN.py +211 -0
- mapFolding/someAssemblyRequired/Z0Z_workbench.py +34 -0
- mapFolding/someAssemblyRequired/__init__.py +16 -0
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +21 -0
- mapFolding/someAssemblyRequired/ingredientsNumba.py +100 -0
- mapFolding/someAssemblyRequired/synthesizeCountingFunctions.py +7 -0
- mapFolding/someAssemblyRequired/synthesizeDataConverters.py +135 -0
- mapFolding/someAssemblyRequired/synthesizeNumba.py +91 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +417 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +91 -0
- mapFolding/someAssemblyRequired/transformationTools.py +425 -0
- mapFolding/someAssemblyRequired/whatWillBe.py +311 -0
- mapFolding/syntheticModules/__init__.py +0 -0
- mapFolding/syntheticModules/dataNamespaceFlattened.py +30 -0
- mapFolding/syntheticModules/numbaCount.py +90 -0
- mapFolding/syntheticModules/numbaCountExample.py +158 -0
- mapFolding/syntheticModules/numbaCountSequential.py +110 -0
- mapFolding/syntheticModules/numbaCount_doTheNeedful.py +13 -0
- mapFolding/syntheticModules/numba_doTheNeedful.py +12 -0
- mapFolding/syntheticModules/numba_doTheNeedfulExample.py +13 -0
- mapFolding/theDao.py +203 -227
- mapFolding/theSSOT.py +255 -102
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/METADATA +7 -6
- mapfolding-0.7.0.dist-info/RECORD +50 -0
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/WHEEL +1 -1
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/top_level.txt +1 -0
- tests/__init__.py +0 -0
- tests/conftest.py +278 -0
- tests/test_computations.py +49 -0
- tests/test_filesystem.py +52 -0
- tests/test_oeis.py +128 -0
- tests/test_other.py +84 -0
- tests/test_tasks.py +50 -0
- mapFolding/theConfiguration.py +0 -58
- mapFolding/theSSOTdatatypes.py +0 -155
- mapFolding/theWrongWay.py +0 -7
- mapfolding-0.6.0.dist-info/RECORD +0 -16
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/LICENSE +0 -0
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/entry_points.txt +0 -0
tests/conftest.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
from collections.abc import Callable, Generator, Sequence
|
|
2
|
+
from mapFolding.theSSOT import getAlgorithmDispatcher, getSourceAlgorithm, getPackageDispatcher, theModuleOfSyntheticModules, FREAKOUT
|
|
3
|
+
from mapFolding.beDRY import getLeavesTotal, validateListDimensions, makeDataContainer
|
|
4
|
+
from mapFolding.oeis import oeisIDsImplemented, settingsOEIS
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, ContextManager
|
|
7
|
+
import importlib.util
|
|
8
|
+
import pytest
|
|
9
|
+
import random
|
|
10
|
+
import shutil
|
|
11
|
+
import unittest.mock
|
|
12
|
+
import uuid
|
|
13
|
+
# TODO learn how to run tests and coverage analysis without `env = ["NUMBA_DISABLE_JIT=1"]`
|
|
14
|
+
|
|
15
|
+
# SSOT for test data paths and filenames
|
|
16
|
+
pathDataSamples = Path("tests/dataSamples")
|
|
17
|
+
# NOTE `tmp` is not a diminutive form of temporary: it signals a technical term. And "temp" is strongly disfavored.
|
|
18
|
+
pathTmpRoot: Path = pathDataSamples / "tmp"
|
|
19
|
+
|
|
20
|
+
# The registrar maintains the register of temp files
|
|
21
|
+
registerOfTemporaryFilesystemObjects: set[Path] = set()
|
|
22
|
+
|
|
23
|
+
def registrarRecordsTmpObject(path: Path) -> None:
|
|
24
|
+
"""The registrar adds a tmp file to the register."""
|
|
25
|
+
registerOfTemporaryFilesystemObjects.add(path)
|
|
26
|
+
|
|
27
|
+
def registrarDeletesTmpObjects() -> None:
|
|
28
|
+
"""The registrar cleans up tmp files in the register."""
|
|
29
|
+
for pathTmp in sorted(registerOfTemporaryFilesystemObjects, reverse=True):
|
|
30
|
+
try:
|
|
31
|
+
if pathTmp.is_file():
|
|
32
|
+
pathTmp.unlink(missing_ok=True)
|
|
33
|
+
elif pathTmp.is_dir():
|
|
34
|
+
shutil.rmtree(pathTmp, ignore_errors=True)
|
|
35
|
+
except Exception as ERRORmessage:
|
|
36
|
+
print(f"Warning: Failed to clean up {pathTmp}: {ERRORmessage}")
|
|
37
|
+
registerOfTemporaryFilesystemObjects.clear()
|
|
38
|
+
|
|
39
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
40
|
+
def setupTeardownTmpObjects() -> Generator[None, None, None]:
|
|
41
|
+
"""Auto-fixture to setup test data directories and cleanup after."""
|
|
42
|
+
pathDataSamples.mkdir(exist_ok=True)
|
|
43
|
+
pathTmpRoot.mkdir(exist_ok=True)
|
|
44
|
+
yield
|
|
45
|
+
registrarDeletesTmpObjects()
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def pathTmpTesting(request: pytest.FixtureRequest) -> Path:
|
|
49
|
+
# "Z0Z_" ensures the directory name does not start with a number, which would make it an invalid Python identifier
|
|
50
|
+
pathTmp = pathTmpRoot / ("Z0Z_" + str(uuid.uuid4().hex))
|
|
51
|
+
pathTmp.mkdir(parents=True, exist_ok=False)
|
|
52
|
+
|
|
53
|
+
registrarRecordsTmpObject(pathTmp)
|
|
54
|
+
return pathTmp
|
|
55
|
+
|
|
56
|
+
@pytest.fixture
|
|
57
|
+
def pathFilenameTmpTesting(request: pytest.FixtureRequest) -> Path:
|
|
58
|
+
try:
|
|
59
|
+
extension = request.param
|
|
60
|
+
except AttributeError:
|
|
61
|
+
extension = ".txt"
|
|
62
|
+
|
|
63
|
+
# "Z0Z_" ensures the name does not start with a number, which would make it an invalid Python identifier
|
|
64
|
+
uuidHex = uuid.uuid4().hex
|
|
65
|
+
subpath = "Z0Z_" + uuidHex[0:-8]
|
|
66
|
+
filenameStem = "Z0Z_" + uuidHex[-8:None]
|
|
67
|
+
|
|
68
|
+
pathFilenameTmp = Path(pathTmpRoot, subpath, filenameStem + extension)
|
|
69
|
+
pathFilenameTmp.parent.mkdir(parents=True, exist_ok=False)
|
|
70
|
+
|
|
71
|
+
registrarRecordsTmpObject(pathFilenameTmp)
|
|
72
|
+
return pathFilenameTmp
|
|
73
|
+
|
|
74
|
+
@pytest.fixture
|
|
75
|
+
def pathCacheTesting(pathTmpTesting: Path) -> Generator[Path, Any, None]:
|
|
76
|
+
"""Temporarily replace the OEIS cache directory with a test directory."""
|
|
77
|
+
import mapFolding.oeis as oeis
|
|
78
|
+
pathCacheOriginal = oeis.pathCache
|
|
79
|
+
oeis.pathCache = pathTmpTesting
|
|
80
|
+
yield pathTmpTesting
|
|
81
|
+
oeis.pathCache = pathCacheOriginal
|
|
82
|
+
|
|
83
|
+
@pytest.fixture
|
|
84
|
+
def pathFilenameFoldsTotalTesting(pathTmpTesting: Path) -> Path:
|
|
85
|
+
return pathTmpTesting.joinpath("foldsTotalTest.txt")
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
Section: Fixtures"""
|
|
89
|
+
|
|
90
|
+
@pytest.fixture(autouse=True)
|
|
91
|
+
def setupWarningsAsErrors() -> Generator[None, Any, None]:
|
|
92
|
+
"""Convert all warnings to errors for all tests."""
|
|
93
|
+
import warnings
|
|
94
|
+
warnings.filterwarnings("error")
|
|
95
|
+
yield
|
|
96
|
+
warnings.resetwarnings()
|
|
97
|
+
|
|
98
|
+
@pytest.fixture
|
|
99
|
+
def listDimensionsTestCountFolds(oeisID: str):
|
|
100
|
+
"""For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestValidation`
|
|
101
|
+
if `validateListDimensions` approves. Each `listDimensions` is suitable for testing counts."""
|
|
102
|
+
while True:
|
|
103
|
+
n = random.choice(settingsOEIS[oeisID]['valuesTestValidation'])
|
|
104
|
+
if n < 2:
|
|
105
|
+
continue
|
|
106
|
+
listDimensionsCandidate = list(settingsOEIS[oeisID]['getMapShape'](n))
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
return validateListDimensions(listDimensionsCandidate)
|
|
110
|
+
except (ValueError, NotImplementedError):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
@pytest.fixture
|
|
114
|
+
def mapShapeTestFunctionality(oeisID_1random: str):
|
|
115
|
+
"""To test functionality, get one `listDimensions` from `valuesTestValidation` if
|
|
116
|
+
`validateListDimensions` approves. The algorithm can count the folds of the returned
|
|
117
|
+
`listDimensions` in a short enough time suitable for testing."""
|
|
118
|
+
while True:
|
|
119
|
+
n = random.choice(settingsOEIS[oeisID_1random]['valuesTestValidation'])
|
|
120
|
+
if n < 2:
|
|
121
|
+
continue
|
|
122
|
+
listDimensionsCandidate = list(settingsOEIS[oeisID_1random]['getMapShape'](n))
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
return validateListDimensions(listDimensionsCandidate)
|
|
126
|
+
except (ValueError, NotImplementedError):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
@pytest.fixture
|
|
130
|
+
def listDimensionsTestParallelization(oeisID: str) -> list[int]:
|
|
131
|
+
"""For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestParallelization`"""
|
|
132
|
+
n = random.choice(settingsOEIS[oeisID]['valuesTestParallelization'])
|
|
133
|
+
return list(settingsOEIS[oeisID]['getMapShape'](n))
|
|
134
|
+
|
|
135
|
+
@pytest.fixture
|
|
136
|
+
def mockBenchmarkTimer() -> Generator[unittest.mock.MagicMock | unittest.mock.AsyncMock, Any, None]:
|
|
137
|
+
"""Mock time.perf_counter_ns for consistent benchmark timing."""
|
|
138
|
+
with unittest.mock.patch('time.perf_counter_ns') as mockTimer:
|
|
139
|
+
mockTimer.side_effect = [0, 1e9] # Start and end times for 1 second
|
|
140
|
+
yield mockTimer
|
|
141
|
+
|
|
142
|
+
@pytest.fixture
|
|
143
|
+
def mockFoldingFunction() -> Callable[..., Callable[..., None]]:
|
|
144
|
+
"""Creates a mock function that simulates _countFolds behavior."""
|
|
145
|
+
def make_mock(foldsValue: int, listDimensions: list[int]) -> Callable[..., None]:
|
|
146
|
+
mock_array = makeDataContainer(2)
|
|
147
|
+
mock_array[0] = foldsValue
|
|
148
|
+
mapShape = validateListDimensions(listDimensions)
|
|
149
|
+
mock_array[-1] = getLeavesTotal(mapShape)
|
|
150
|
+
|
|
151
|
+
def mock_countFolds(**keywordArguments: Any) -> None:
|
|
152
|
+
keywordArguments['foldGroups'][:] = mock_array
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
return mock_countFolds
|
|
156
|
+
return make_mock
|
|
157
|
+
|
|
158
|
+
@pytest.fixture
|
|
159
|
+
def mockDispatcher() -> Callable[[Any], ContextManager[Any]]:
|
|
160
|
+
"""Context manager for mocking dispatcher callable."""
|
|
161
|
+
def wrapper(mockFunction: Any) -> ContextManager[Any]:
|
|
162
|
+
dispatcherCallable = getPackageDispatcher()
|
|
163
|
+
return unittest.mock.patch(
|
|
164
|
+
f"{dispatcherCallable.__module__}.{dispatcherCallable.__name__}",
|
|
165
|
+
side_effect=mockFunction
|
|
166
|
+
)
|
|
167
|
+
return wrapper
|
|
168
|
+
|
|
169
|
+
@pytest.fixture(params=oeisIDsImplemented)
|
|
170
|
+
def oeisID(request: pytest.FixtureRequest) -> Any:
|
|
171
|
+
return request.param
|
|
172
|
+
|
|
173
|
+
@pytest.fixture
|
|
174
|
+
def oeisID_1random() -> str:
|
|
175
|
+
"""Return one random valid OEIS ID."""
|
|
176
|
+
return random.choice(oeisIDsImplemented)
|
|
177
|
+
|
|
178
|
+
@pytest.fixture
|
|
179
|
+
def useThisDispatcher() -> Generator[Callable[..., None], Any, None]:
|
|
180
|
+
"""A fixture providing a context manager for temporarily replacing the dispatcher.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
A context manager for patching the dispatcher
|
|
184
|
+
"""
|
|
185
|
+
import mapFolding.basecamp as basecamp
|
|
186
|
+
dispatcherOriginal = basecamp.getPackageDispatcher
|
|
187
|
+
|
|
188
|
+
def patchDispatcher(callableTarget: Callable[..., Any]) -> None:
|
|
189
|
+
def callableParameterized(*arguments: Any, **keywordArguments: Any) -> Callable[..., Any]:
|
|
190
|
+
return callableTarget
|
|
191
|
+
basecamp.getPackageDispatcher = callableParameterized
|
|
192
|
+
|
|
193
|
+
yield patchDispatcher
|
|
194
|
+
basecamp.getPackageDispatcher = dispatcherOriginal
|
|
195
|
+
|
|
196
|
+
@pytest.fixture
|
|
197
|
+
def useAlgorithmSourceDispatcher(useThisDispatcher: Callable[..., Any]) -> Generator[None, None, None]:
|
|
198
|
+
"""Temporarily patches getDispatcherCallable to return the algorithm dispatcher."""
|
|
199
|
+
useThisDispatcher(getAlgorithmDispatcher())
|
|
200
|
+
yield
|
|
201
|
+
|
|
202
|
+
@pytest.fixture
|
|
203
|
+
def syntheticDispatcherFixture(useThisDispatcher: Callable[..., Any]) -> Callable[..., Any]:
|
|
204
|
+
listCallablesInline = listNumbaCallableDispatchees
|
|
205
|
+
callableDispatcher = True
|
|
206
|
+
algorithmSource = getSourceAlgorithm()
|
|
207
|
+
relativePathWrite = theModuleOfSyntheticModules
|
|
208
|
+
filenameModuleWrite = 'pytestCount.py'
|
|
209
|
+
formatFilenameWrite = "pytest_{callableTarget}.py"
|
|
210
|
+
listSynthesizedModules: list[YouOughtaKnow] = makeFlowNumbaOptimized(listCallablesInline, callableDispatcher, algorithmSource, relativePathWrite, filenameModuleWrite, formatFilenameWrite)
|
|
211
|
+
dispatcherSynthetic: YouOughtaKnow | None = None
|
|
212
|
+
for stuff in listSynthesizedModules:
|
|
213
|
+
registrarRecordsTmpObject(stuff.pathFilenameForMe)
|
|
214
|
+
if stuff.callableSynthesized not in listCallablesInline:
|
|
215
|
+
dispatcherSynthetic = stuff
|
|
216
|
+
|
|
217
|
+
if dispatcherSynthetic is None:
|
|
218
|
+
raise FREAKOUT
|
|
219
|
+
|
|
220
|
+
dispatcherSpec = importlib.util.spec_from_file_location(dispatcherSynthetic.callableSynthesized, dispatcherSynthetic.pathFilenameForMe)
|
|
221
|
+
if dispatcherSpec is None:
|
|
222
|
+
raise ImportError(f"{dispatcherSynthetic.pathFilenameForMe=}")
|
|
223
|
+
if dispatcherSpec.loader is None:
|
|
224
|
+
raise ImportError(f"Failed to get loader for module {dispatcherSynthetic.pathFilenameForMe}")
|
|
225
|
+
|
|
226
|
+
dispatcherModule = importlib.util.module_from_spec(dispatcherSpec)
|
|
227
|
+
dispatcherSpec.loader.exec_module(dispatcherModule)
|
|
228
|
+
callableDispatcherSynthetic = getattr(dispatcherModule, dispatcherSynthetic.callableSynthesized)
|
|
229
|
+
|
|
230
|
+
useThisDispatcher(callableDispatcherSynthetic)
|
|
231
|
+
return callableDispatcherSynthetic
|
|
232
|
+
|
|
233
|
+
def uniformTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
234
|
+
"""Format assertion message for any test comparison."""
|
|
235
|
+
return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
|
|
236
|
+
f"Expected: {expected}\n"
|
|
237
|
+
f"Got: {actual}")
|
|
238
|
+
|
|
239
|
+
def standardizedEqualToCallableReturn(expected: Any, functionTarget: Callable[..., Any], *arguments: Any) -> None:
|
|
240
|
+
"""Use with callables that produce a return or an error."""
|
|
241
|
+
if type(expected) is type[Exception]:
|
|
242
|
+
messageExpected = expected.__name__
|
|
243
|
+
else:
|
|
244
|
+
messageExpected = expected
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
messageActual = actual = functionTarget(*arguments)
|
|
248
|
+
except Exception as actualError:
|
|
249
|
+
messageActual = type(actualError).__name__
|
|
250
|
+
actual = type(actualError)
|
|
251
|
+
|
|
252
|
+
assert actual == expected, uniformTestMessage(messageExpected, messageActual, functionTarget.__name__, *arguments)
|
|
253
|
+
|
|
254
|
+
def standardizedSystemExit(expected: str | int | Sequence[int], functionTarget: Callable[..., Any], *arguments: Any) -> None:
|
|
255
|
+
"""Template for tests expecting SystemExit.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
expected: Exit code expectation:
|
|
259
|
+
- "error": any non-zero exit code
|
|
260
|
+
- "nonError": specifically zero exit code
|
|
261
|
+
- int: exact exit code match
|
|
262
|
+
- Sequence[int]: exit code must be one of these values
|
|
263
|
+
functionTarget: The function to test
|
|
264
|
+
arguments: Arguments to pass to the function
|
|
265
|
+
"""
|
|
266
|
+
with pytest.raises(SystemExit) as exitInfo:
|
|
267
|
+
functionTarget(*arguments)
|
|
268
|
+
|
|
269
|
+
exitCode = exitInfo.value.code
|
|
270
|
+
|
|
271
|
+
if expected == "error":
|
|
272
|
+
assert exitCode != 0, f"Expected error exit (non-zero) but got code {exitCode}"
|
|
273
|
+
elif expected == "nonError":
|
|
274
|
+
assert exitCode == 0, f"Expected non-error exit (0) but got code {exitCode}"
|
|
275
|
+
elif isinstance(expected, (list, tuple)):
|
|
276
|
+
assert exitCode in expected, f"Expected exit code to be one of {expected} but got {exitCode}"
|
|
277
|
+
else:
|
|
278
|
+
assert exitCode == expected, f"Expected exit code {expected} but got {exitCode}"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from mapFolding.basecamp import countFolds
|
|
2
|
+
from mapFolding.filesystem import getPathFilenameFoldsTotal
|
|
3
|
+
from mapFolding.noHomeYet import getFoldsTotalKnown
|
|
4
|
+
from mapFolding.oeis import settingsOEIS, oeisIDfor_n
|
|
5
|
+
# from mapFolding.someAssemblyRequired import writeJobNumba
|
|
6
|
+
from tests.conftest import standardizedEqualToCallableReturn, registrarRecordsTmpObject
|
|
7
|
+
import importlib.util
|
|
8
|
+
import pytest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
|
|
12
|
+
def test_algorithmSourceParallel(listDimensionsTestParallelization, useAlgorithmSourceDispatcher: None) -> None:
|
|
13
|
+
standardizedEqualToCallableReturn(getFoldsTotalKnown(tuple(listDimensionsTestParallelization)), countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
14
|
+
|
|
15
|
+
def test_algorithmSourceSequential(listDimensionsTestCountFolds, useAlgorithmSourceDispatcher: None) -> None:
|
|
16
|
+
standardizedEqualToCallableReturn(getFoldsTotalKnown(tuple(listDimensionsTestCountFolds)), countFolds, listDimensionsTestCountFolds)
|
|
17
|
+
|
|
18
|
+
def test_aOFn_calculate_value(oeisID: str) -> None:
|
|
19
|
+
for n in settingsOEIS[oeisID]['valuesTestValidation']:
|
|
20
|
+
standardizedEqualToCallableReturn(settingsOEIS[oeisID]['valuesKnown'][n], oeisIDfor_n, oeisID, n)
|
|
21
|
+
|
|
22
|
+
# @pytest.mark.parametrize('pathFilenameTmpTesting', ['.py'], indirect=True)
|
|
23
|
+
# def test_writeJobNumba(listDimensionsTestCountFolds: list[int], pathFilenameTmpTesting: Path) -> None:
|
|
24
|
+
# from mapFolding.syntheticModules import numbaCount
|
|
25
|
+
# algorithmSourceHARDCODED: ModuleType = numbaCount
|
|
26
|
+
# algorithmSource = algorithmSourceHARDCODED
|
|
27
|
+
# callableTargetHARDCODED = 'countSequential'
|
|
28
|
+
# callableTarget = callableTargetHARDCODED
|
|
29
|
+
# pathFilenameModule = writeJobNumba(listDimensionsTestCountFolds, algorithmSource, callableTarget, pathFilenameWriteJob=pathFilenameTmpTesting.absolute())
|
|
30
|
+
|
|
31
|
+
# Don_Lapre_Road_to_Self_Improvement = importlib.util.spec_from_file_location("__main__", pathFilenameModule)
|
|
32
|
+
# if Don_Lapre_Road_to_Self_Improvement is None:
|
|
33
|
+
# raise ImportError(f"Failed to create module specification from {pathFilenameModule}")
|
|
34
|
+
# if Don_Lapre_Road_to_Self_Improvement.loader is None:
|
|
35
|
+
# raise ImportError(f"Failed to get loader for module {pathFilenameModule}")
|
|
36
|
+
# module = importlib.util.module_from_spec(Don_Lapre_Road_to_Self_Improvement)
|
|
37
|
+
|
|
38
|
+
# module.__name__ = "__main__"
|
|
39
|
+
# Don_Lapre_Road_to_Self_Improvement.loader.exec_module(module)
|
|
40
|
+
|
|
41
|
+
# pathFilenameFoldsTotal = getPathFilenameFoldsTotal(listDimensionsTestCountFolds)
|
|
42
|
+
# registrarRecordsTmpObject(pathFilenameFoldsTotal)
|
|
43
|
+
# standardizedEqualTo(str(foldsTotalKnown[tuple(listDimensionsTestCountFolds)]), pathFilenameFoldsTotal.read_text().strip)
|
|
44
|
+
|
|
45
|
+
# def test_syntheticParallel(syntheticDispatcherFixture: None, listDimensionsTestParallelization: list[int], foldsTotalKnown: dict[tuple[int, ...], int]):
|
|
46
|
+
# standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestParallelization)], countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
47
|
+
|
|
48
|
+
# def test_syntheticSequential(syntheticDispatcherFixture: None, listDimensionsTestCountFolds: list[int], foldsTotalKnown: dict[tuple[int, ...], int]):
|
|
49
|
+
# standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestCountFolds)], countFolds, listDimensionsTestCountFolds)
|
tests/test_filesystem.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from contextlib import redirect_stdout
|
|
2
|
+
from mapFolding.filesystem import getFilenameFoldsTotal, getPathFilenameFoldsTotal, saveFoldsTotal
|
|
3
|
+
from mapFolding.beDRY import validateListDimensions
|
|
4
|
+
from mapFolding.theSSOT import getPathJobRootDEFAULT
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
import io
|
|
8
|
+
import numpy
|
|
9
|
+
import pytest
|
|
10
|
+
import unittest.mock
|
|
11
|
+
|
|
12
|
+
def test_saveFoldsTotal_fallback(pathTmpTesting: Path) -> None:
|
|
13
|
+
foldsTotal = 123
|
|
14
|
+
pathFilename = pathTmpTesting / "foldsTotal.txt"
|
|
15
|
+
with unittest.mock.patch("pathlib.Path.write_text", side_effect=OSError("Simulated write failure")):
|
|
16
|
+
with unittest.mock.patch("os.getcwd", return_value=str(pathTmpTesting)):
|
|
17
|
+
capturedOutput = io.StringIO()
|
|
18
|
+
with redirect_stdout(capturedOutput):
|
|
19
|
+
saveFoldsTotal(pathFilename, foldsTotal)
|
|
20
|
+
fallbackFiles = list(pathTmpTesting.glob("foldsTotalYO_*.txt"))
|
|
21
|
+
assert len(fallbackFiles) == 1, "Fallback file was not created upon write failure."
|
|
22
|
+
|
|
23
|
+
@pytest.mark.parametrize("listDimensions, expectedFilename", [
|
|
24
|
+
([11, 13], "p11x13.foldsTotal"),
|
|
25
|
+
([17, 13, 11], "p11x13x17.foldsTotal"),
|
|
26
|
+
])
|
|
27
|
+
def test_getFilenameFoldsTotal(listDimensions: list[int], expectedFilename: str) -> None:
|
|
28
|
+
"""Test that getFilenameFoldsTotal generates correct filenames with dimensions sorted."""
|
|
29
|
+
mapShape = validateListDimensions(listDimensions)
|
|
30
|
+
filenameActual = getFilenameFoldsTotal(mapShape)
|
|
31
|
+
assert filenameActual == expectedFilename, f"Expected filename {expectedFilename} but got {filenameActual}"
|
|
32
|
+
|
|
33
|
+
def test_getPathFilenameFoldsTotal_defaultPath(mapShapeTestFunctionality: tuple[int, ...]) -> None:
|
|
34
|
+
"""Test getPathFilenameFoldsTotal with default path."""
|
|
35
|
+
pathFilenameFoldsTotal = getPathFilenameFoldsTotal(mapShapeTestFunctionality)
|
|
36
|
+
assert pathFilenameFoldsTotal.is_absolute(), "Path should be absolute"
|
|
37
|
+
assert pathFilenameFoldsTotal.name == getFilenameFoldsTotal(mapShapeTestFunctionality), "Filename should match getFilenameFoldsTotal output"
|
|
38
|
+
assert pathFilenameFoldsTotal.parent == getPathJobRootDEFAULT(), "Parent directory should match default job root"
|
|
39
|
+
|
|
40
|
+
def test_getPathFilenameFoldsTotal_relativeFilename(mapShapeTestFunctionality: tuple[int, ...]) -> None:
|
|
41
|
+
"""Test getPathFilenameFoldsTotal with relative filename."""
|
|
42
|
+
relativeFilename = Path("custom/path/test.foldsTotal")
|
|
43
|
+
pathFilenameFoldsTotal = getPathFilenameFoldsTotal(mapShapeTestFunctionality, relativeFilename)
|
|
44
|
+
assert pathFilenameFoldsTotal.is_absolute(), "Path should be absolute"
|
|
45
|
+
assert pathFilenameFoldsTotal == getPathJobRootDEFAULT() / relativeFilename, "Relative path should be appended to default job root"
|
|
46
|
+
|
|
47
|
+
def test_getPathFilenameFoldsTotal_createsDirs(pathTmpTesting: Path, mapShapeTestFunctionality: tuple[int, ...]) -> None:
|
|
48
|
+
"""Test that getPathFilenameFoldsTotal creates necessary directories."""
|
|
49
|
+
nestedPath = pathTmpTesting / "deep/nested/structure"
|
|
50
|
+
pathFilenameFoldsTotal = getPathFilenameFoldsTotal(mapShapeTestFunctionality, nestedPath)
|
|
51
|
+
assert pathFilenameFoldsTotal.parent.exists(), "Parent directories should be created"
|
|
52
|
+
assert pathFilenameFoldsTotal.parent.is_dir(), "Created path should be a directory"
|
tests/test_oeis.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from contextlib import redirect_stdout
|
|
2
|
+
from mapFolding.oeis import oeisIDfor_n, getOEISids, clearOEIScache, getOEISidValues, OEIS_for_n, oeisIDsImplemented, settingsOEIS, validateOEISid
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from tests.conftest import standardizedEqualToCallableReturn, standardizedSystemExit
|
|
5
|
+
from typing import Any, NoReturn
|
|
6
|
+
from urllib.error import URLError
|
|
7
|
+
import io
|
|
8
|
+
import pytest
|
|
9
|
+
import random
|
|
10
|
+
import re as regex
|
|
11
|
+
import unittest.mock
|
|
12
|
+
import urllib.request
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize("badID", ["A999999", " A999999 ", "A999999extra"])
|
|
15
|
+
def test__validateOEISid_invalid_id(badID: str) -> None:
|
|
16
|
+
standardizedEqualToCallableReturn(KeyError, validateOEISid, badID)
|
|
17
|
+
|
|
18
|
+
def test__validateOEISid_partially_valid(oeisID_1random: str) -> None:
|
|
19
|
+
standardizedEqualToCallableReturn(KeyError, validateOEISid, f"{oeisID_1random}extra")
|
|
20
|
+
|
|
21
|
+
def test__validateOEISid_valid_id(oeisID: str) -> None:
|
|
22
|
+
standardizedEqualToCallableReturn(oeisID, validateOEISid, oeisID)
|
|
23
|
+
|
|
24
|
+
def test__validateOEISid_valid_id_case_insensitive(oeisID: str) -> None:
|
|
25
|
+
standardizedEqualToCallableReturn(oeisID.upper(), validateOEISid, oeisID.lower())
|
|
26
|
+
standardizedEqualToCallableReturn(oeisID.upper(), validateOEISid, oeisID.upper())
|
|
27
|
+
standardizedEqualToCallableReturn(oeisID.upper(), validateOEISid, oeisID.swapcase())
|
|
28
|
+
|
|
29
|
+
parameters_test_aOFn_invalid_n = [
|
|
30
|
+
(-random.randint(1, 100), "randomNegative"),
|
|
31
|
+
("foo", "string"),
|
|
32
|
+
(1.5, "float")
|
|
33
|
+
]
|
|
34
|
+
badValues, badValuesIDs = zip(*parameters_test_aOFn_invalid_n)
|
|
35
|
+
@pytest.mark.parametrize("badN", badValues, ids=badValuesIDs)
|
|
36
|
+
def test_aOFn_invalid_n(oeisID_1random: str, badN: Any) -> None:
|
|
37
|
+
"""Check that negative or non-integer n raises ValueError."""
|
|
38
|
+
standardizedEqualToCallableReturn(ValueError, oeisIDfor_n, oeisID_1random, badN)
|
|
39
|
+
|
|
40
|
+
def test_aOFn_zeroDim_A001418() -> None:
|
|
41
|
+
standardizedEqualToCallableReturn(ArithmeticError, oeisIDfor_n, 'A001418', 0)
|
|
42
|
+
|
|
43
|
+
# ===== OEIS Cache Tests =====
|
|
44
|
+
@pytest.mark.parametrize("cacheExists", [True, False])
|
|
45
|
+
@unittest.mock.patch('pathlib.Path.exists')
|
|
46
|
+
@unittest.mock.patch('pathlib.Path.unlink')
|
|
47
|
+
def test_clearOEIScache(mock_unlink: unittest.mock.MagicMock, mock_exists: unittest.mock.MagicMock, cacheExists: bool) -> None:
|
|
48
|
+
"""Test OEIS cache clearing with both existing and non-existing cache."""
|
|
49
|
+
mock_exists.return_value = cacheExists
|
|
50
|
+
clearOEIScache()
|
|
51
|
+
|
|
52
|
+
if cacheExists:
|
|
53
|
+
# Each OEIS ID has two cache files
|
|
54
|
+
expected_calls = len(settingsOEIS) * 2
|
|
55
|
+
assert mock_unlink.call_count == expected_calls
|
|
56
|
+
mock_unlink.assert_has_calls([unittest.mock.call(missing_ok=True)] * expected_calls)
|
|
57
|
+
else:
|
|
58
|
+
mock_exists.assert_called_once()
|
|
59
|
+
mock_unlink.assert_not_called()
|
|
60
|
+
|
|
61
|
+
def testNetworkError(monkeypatch: pytest.MonkeyPatch, pathCacheTesting: Path) -> None:
|
|
62
|
+
"""Test network error handling."""
|
|
63
|
+
def mockUrlopen(*args: Any, **kwargs: Any) -> NoReturn:
|
|
64
|
+
raise URLError("Network error")
|
|
65
|
+
|
|
66
|
+
monkeypatch.setattr(urllib.request, 'urlopen', mockUrlopen)
|
|
67
|
+
standardizedEqualToCallableReturn(URLError, getOEISidValues, next(iter(settingsOEIS)))
|
|
68
|
+
|
|
69
|
+
# ===== Command Line Interface Tests =====
|
|
70
|
+
def testHelpText() -> None:
|
|
71
|
+
"""Test that help text is complete and examples are valid."""
|
|
72
|
+
outputStream = io.StringIO()
|
|
73
|
+
with redirect_stdout(outputStream):
|
|
74
|
+
getOEISids()
|
|
75
|
+
|
|
76
|
+
helpText = outputStream.getvalue()
|
|
77
|
+
|
|
78
|
+
# Verify content
|
|
79
|
+
for oeisID in oeisIDsImplemented:
|
|
80
|
+
assert oeisID in helpText
|
|
81
|
+
assert settingsOEIS[oeisID]['description'] in helpText
|
|
82
|
+
|
|
83
|
+
# Extract and verify examples
|
|
84
|
+
|
|
85
|
+
cliMatch = regex.search(r'OEIS_for_n (\w+) (\d+)', helpText)
|
|
86
|
+
pythonMatch = regex.search(r"oeisIDfor_n\('(\w+)', (\d+)\)", helpText)
|
|
87
|
+
|
|
88
|
+
assert cliMatch and pythonMatch, "Help text missing examples"
|
|
89
|
+
oeisID, n = pythonMatch.groups()
|
|
90
|
+
n = int(n)
|
|
91
|
+
|
|
92
|
+
# Verify CLI and Python examples use same values
|
|
93
|
+
assert cliMatch.groups() == (oeisID, str(n)), "CLI and Python examples inconsistent"
|
|
94
|
+
|
|
95
|
+
# Verify the example works
|
|
96
|
+
expectedValue = oeisIDfor_n(oeisID, n)
|
|
97
|
+
|
|
98
|
+
# Test CLI execution of the example
|
|
99
|
+
with unittest.mock.patch('sys.argv', ['OEIS_for_n', oeisID, str(n)]):
|
|
100
|
+
outputStream = io.StringIO()
|
|
101
|
+
with redirect_stdout(outputStream):
|
|
102
|
+
OEIS_for_n()
|
|
103
|
+
standardizedEqualToCallableReturn(expectedValue, lambda: int(outputStream.getvalue().strip().split()[0]))
|
|
104
|
+
|
|
105
|
+
def testCLI_InvalidInputs() -> None:
|
|
106
|
+
"""Test CLI error handling."""
|
|
107
|
+
testCases = [
|
|
108
|
+
(['OEIS_for_n'], "missing arguments"),
|
|
109
|
+
(['OEIS_for_n', 'A999999', '1'], "invalid OEIS ID"),
|
|
110
|
+
(['OEIS_for_n', 'A001415', '-1'], "negative n"),
|
|
111
|
+
(['OEIS_for_n', 'A001415', 'abc'], "non-integer n"),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
for arguments, _testID in testCases:
|
|
115
|
+
with unittest.mock.patch('sys.argv', arguments):
|
|
116
|
+
standardizedSystemExit("error", OEIS_for_n)
|
|
117
|
+
|
|
118
|
+
def testCLI_HelpFlag() -> None:
|
|
119
|
+
"""Verify --help output contains required information."""
|
|
120
|
+
with unittest.mock.patch('sys.argv', ['OEIS_for_n', '--help']):
|
|
121
|
+
outputStream = io.StringIO()
|
|
122
|
+
with redirect_stdout(outputStream):
|
|
123
|
+
standardizedSystemExit("nonError", OEIS_for_n)
|
|
124
|
+
|
|
125
|
+
helpOutput = outputStream.getvalue()
|
|
126
|
+
assert "Available OEIS sequences:" in helpOutput
|
|
127
|
+
assert "Usage examples:" in helpOutput
|
|
128
|
+
assert all(oeisID in helpOutput for oeisID in oeisIDsImplemented)
|
tests/test_other.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from mapFolding.beDRY import getLeavesTotal, setCPUlimit, validateListDimensions
|
|
3
|
+
from tests.conftest import standardizedEqualToCallableReturn
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
from Z0Z_tools import intInnit
|
|
6
|
+
from Z0Z_tools.pytestForYourUse import PytestFor_intInnit, PytestFor_oopsieKwargsie
|
|
7
|
+
import numba
|
|
8
|
+
import pytest
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
@pytest.mark.parametrize("listDimensions,expected_intInnit,expected_validateListDimensions", [
|
|
12
|
+
(None, ValueError, ValueError), # None instead of list
|
|
13
|
+
(['a'], ValueError, ValueError), # string
|
|
14
|
+
([-4, 2], [-4, 2], ValueError), # negative
|
|
15
|
+
([-3], [-3], ValueError), # negative
|
|
16
|
+
([0, 0], [0, 0], NotImplementedError), # no positive dimensions
|
|
17
|
+
([0, 5, 6], [0, 5, 6], (5, 6)), # zeros ignored
|
|
18
|
+
([0], [0], NotImplementedError), # edge case
|
|
19
|
+
([1, 2, 3, 4, 5], [1, 2, 3, 4, 5], (1, 2, 3, 4, 5)), # sequential
|
|
20
|
+
([1, sys.maxsize], [1, sys.maxsize], (1, sys.maxsize)), # maxint
|
|
21
|
+
([7.5], ValueError, ValueError), # float
|
|
22
|
+
([1] * 1000, [1] * 1000, (1,) * 1000), # long list
|
|
23
|
+
([11], [11], NotImplementedError), # single dimension
|
|
24
|
+
([13, 0, 17], [13, 0, 17], (13, 17)), # zeros handled
|
|
25
|
+
([2, 2, 2, 2], [2, 2, 2, 2], (2, 2, 2, 2)), # repeated dimensions
|
|
26
|
+
([2, 3, 4], [2, 3, 4], (2, 3, 4)),
|
|
27
|
+
([2, 3], [2, 3], (2, 3)),
|
|
28
|
+
([2] * 11, [2] * 11, (2,) * 11), # power of 2
|
|
29
|
+
([3, 2], [3, 2], (2, 3)), # return value is sorted
|
|
30
|
+
([3] * 5, [3] * 5, (3,) * 5), # power of 3
|
|
31
|
+
([None], TypeError, TypeError), # None
|
|
32
|
+
([True], TypeError, TypeError), # bool
|
|
33
|
+
([[17, 39]], TypeError, TypeError), # nested
|
|
34
|
+
([], ValueError, ValueError), # empty
|
|
35
|
+
([complex(1,1)], ValueError, ValueError), # complex number
|
|
36
|
+
([float('inf')], ValueError, ValueError), # infinity
|
|
37
|
+
([float('nan')], ValueError, ValueError), # NaN
|
|
38
|
+
([sys.maxsize - 1, 1], [sys.maxsize - 1, 1], (1, sys.maxsize - 1)), # near maxint
|
|
39
|
+
([sys.maxsize // 2, sys.maxsize // 2, 2], [sys.maxsize // 2, sys.maxsize // 2, 2], (2, sys.maxsize // 2, sys.maxsize // 2)), # overflow protection
|
|
40
|
+
([sys.maxsize, sys.maxsize], [sys.maxsize, sys.maxsize], (sys.maxsize, sys.maxsize)), # overflow protection
|
|
41
|
+
(range(3, 7), [3, 4, 5, 6], (3, 4, 5, 6)), # range sequence type
|
|
42
|
+
(tuple([3, 5, 7]), [3, 5, 7], (3, 5, 7)), # tuple sequence type
|
|
43
|
+
])
|
|
44
|
+
def test_listDimensionsAsParameter(listDimensions: None | list[str] | list[int] | list[float] | list[None] | list[bool] | list[list[int]] | list[complex] | range | tuple[int, ...],
|
|
45
|
+
expected_intInnit: type[ValueError] | list[int] | type[TypeError],
|
|
46
|
+
expected_validateListDimensions: type[ValueError] | type[NotImplementedError] | tuple[int, ...] | type[TypeError]) -> None:
|
|
47
|
+
"""Test both validateListDimensions and getLeavesTotal with the same inputs."""
|
|
48
|
+
standardizedEqualToCallableReturn(expected_intInnit, intInnit, listDimensions)
|
|
49
|
+
standardizedEqualToCallableReturn(expected_validateListDimensions, validateListDimensions, listDimensions)
|
|
50
|
+
|
|
51
|
+
def test_getLeavesTotal_edge_cases() -> None:
|
|
52
|
+
"""Test edge cases for getLeavesTotal."""
|
|
53
|
+
# Order independence
|
|
54
|
+
standardizedEqualToCallableReturn(getLeavesTotal((2, 3, 4)), getLeavesTotal, (4, 2, 3))
|
|
55
|
+
|
|
56
|
+
# Input preservation
|
|
57
|
+
mapShape = (2, 3)
|
|
58
|
+
standardizedEqualToCallableReturn(6, getLeavesTotal, mapShape)
|
|
59
|
+
# Remove the lambda entirely for a simpler approach
|
|
60
|
+
assert mapShape == (2, 3), "Input tuple was modified"
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_intInnit())
|
|
63
|
+
def testIntInnit(nameOfTest: str, callablePytest: Callable[[], None]) -> None:
|
|
64
|
+
callablePytest()
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_oopsieKwargsie())
|
|
67
|
+
def testOopsieKwargsie(nameOfTest: str, callablePytest: Callable[[], None]) -> None:
|
|
68
|
+
callablePytest()
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize("CPUlimit, expectedLimit", [
|
|
71
|
+
(None, numba.get_num_threads()),
|
|
72
|
+
(False, numba.get_num_threads()),
|
|
73
|
+
(True, 1),
|
|
74
|
+
(4, 4),
|
|
75
|
+
(0.5, max(1, numba.get_num_threads() // 2)),
|
|
76
|
+
(-0.5, max(1, numba.get_num_threads() // 2)),
|
|
77
|
+
(-2, max(1, numba.get_num_threads() - 2)),
|
|
78
|
+
(0, numba.get_num_threads()),
|
|
79
|
+
(1, 1),
|
|
80
|
+
])
|
|
81
|
+
def test_setCPUlimit(CPUlimit: None | float | bool | Literal[4] | Literal[-2] | Literal[0] | Literal[1], expectedLimit: Any | int) -> None:
|
|
82
|
+
from mapFolding.theSSOT import concurrencyPackage
|
|
83
|
+
if concurrencyPackage == 'numba':
|
|
84
|
+
standardizedEqualToCallableReturn(expectedLimit, setCPUlimit, CPUlimit)
|
tests/test_tasks.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
from mapFolding.basecamp import countFolds
|
|
3
|
+
from mapFolding.beDRY import getTaskDivisions, setCPUlimit, validateListDimensions, getLeavesTotal
|
|
4
|
+
from mapFolding.noHomeYet import getFoldsTotalKnown
|
|
5
|
+
from tests.conftest import standardizedEqualToCallableReturn
|
|
6
|
+
from Z0Z_tools.pytestForYourUse import PytestFor_defineConcurrencyLimit
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
# TODO add a test. `C` = number of logical cores available. `n = C + 1`. Ensure that `[2,n]` is computed correctly.
|
|
11
|
+
# Or, probably smarter: limit the number of cores, then run a test with C+1.
|
|
12
|
+
|
|
13
|
+
def test_countFoldsComputationDivisionsInvalid(mapShapeTestFunctionality: tuple[int, ...]) -> None:
|
|
14
|
+
standardizedEqualToCallableReturn(ValueError, countFolds, mapShapeTestFunctionality, None, {"wrong": "value"})
|
|
15
|
+
|
|
16
|
+
def test_countFoldsComputationDivisionsMaximum(listDimensionsTestParallelization: list[int]) -> None:
|
|
17
|
+
standardizedEqualToCallableReturn(getFoldsTotalKnown(tuple(listDimensionsTestParallelization)), countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_defineConcurrencyLimit())
|
|
20
|
+
def test_defineConcurrencyLimit(nameOfTest: str, callablePytest: Callable[[], None]) -> None:
|
|
21
|
+
callablePytest()
|
|
22
|
+
|
|
23
|
+
@pytest.mark.parametrize("CPUlimitParameter", [{"invalid": True}, ["weird"]])
|
|
24
|
+
def test_countFolds_cpuLimitOopsie(mapShapeTestFunctionality: tuple[int, ...], CPUlimitParameter: dict[str, bool] | list[str]) -> None:
|
|
25
|
+
standardizedEqualToCallableReturn(ValueError, countFolds, mapShapeTestFunctionality, None, 'cpu', CPUlimitParameter)
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("computationDivisions, concurrencyLimit, listDimensions, expectedTaskDivisions", [
|
|
28
|
+
(None, 4, [9, 11], 0),
|
|
29
|
+
("maximum", 4, [7, 11], 77),
|
|
30
|
+
("cpu", 4, [3, 7], 4),
|
|
31
|
+
(["invalid"], 4, [19, 23], ValueError),
|
|
32
|
+
(20, 4, [3,5], ValueError)
|
|
33
|
+
])
|
|
34
|
+
def test_getTaskDivisions(computationDivisions: None | list[str] | Literal['maximum'] | Literal['cpu'] | Literal[20],
|
|
35
|
+
concurrencyLimit: Literal[4],
|
|
36
|
+
listDimensions: list[int],
|
|
37
|
+
expectedTaskDivisions: type[ValueError] | Literal[0] | Literal[77] | Literal[4]) -> None:
|
|
38
|
+
mapShape = validateListDimensions(listDimensions)
|
|
39
|
+
leavesTotal = getLeavesTotal(mapShape)
|
|
40
|
+
standardizedEqualToCallableReturn(expectedTaskDivisions, getTaskDivisions, computationDivisions, concurrencyLimit, leavesTotal)
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("expected,parameter", [
|
|
43
|
+
(ValueError, [4]), # list
|
|
44
|
+
(ValueError, (2,)), # tuple
|
|
45
|
+
(ValueError, {2}), # set
|
|
46
|
+
(ValueError, {"cores": 2}), # dict
|
|
47
|
+
])
|
|
48
|
+
def test_setCPUlimitMalformedParameter(expected: type[ValueError] | Literal[2], parameter: list[int] | tuple[int] | set[int] | dict[str, int] | Literal['2']) -> None:
|
|
49
|
+
"""Test that invalid CPUlimit types are properly handled."""
|
|
50
|
+
standardizedEqualToCallableReturn(expected, setCPUlimit, parameter)
|