mapFolding 0.2.6__py3-none-any.whl → 0.3.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.
Files changed (39) hide show
  1. {mapFolding-0.2.6.dist-info → mapFolding-0.3.0.dist-info}/METADATA +23 -7
  2. mapFolding-0.3.0.dist-info/RECORD +20 -0
  3. mapFolding-0.3.0.dist-info/top_level.txt +3 -0
  4. someAssemblyRequired/__init__.py +3 -0
  5. someAssemblyRequired/countInitialize.py +45 -0
  6. someAssemblyRequired/countParallel.py +52 -0
  7. someAssemblyRequired/countSequential.py +59 -0
  8. mapFolding/someAssemblyRequired/inlineAfunction.py → someAssemblyRequired/synthesizeModules.py +76 -41
  9. mapFolding/__init__.py +0 -12
  10. mapFolding/babbage.py +0 -35
  11. mapFolding/beDRY.py +0 -319
  12. mapFolding/importSelector.py +0 -7
  13. mapFolding/lovelace.py +0 -213
  14. mapFolding/oeis.py +0 -323
  15. mapFolding/someAssemblyRequired/jobsAndTasks.py +0 -47
  16. mapFolding/someAssemblyRequired/makeNuitkaSource.py +0 -99
  17. mapFolding/someAssemblyRequired/makeNumbaJob.py +0 -144
  18. mapFolding/startHere.py +0 -50
  19. mapFolding/theSSOT.py +0 -76
  20. mapFolding-0.2.6.dist-info/RECORD +0 -33
  21. mapFolding-0.2.6.dist-info/top_level.txt +0 -2
  22. tests/__init__.py +0 -1
  23. tests/conftest.py +0 -343
  24. tests/pythons_idiotic_namespace.py +0 -1
  25. tests/test_oeis.py +0 -194
  26. tests/test_other.py +0 -282
  27. tests/test_tasks.py +0 -31
  28. {mapFolding/benchmarks → benchmarks}/benchmarking.py +0 -0
  29. {mapFolding-0.2.6.dist-info → mapFolding-0.3.0.dist-info}/WHEEL +0 -0
  30. {mapFolding-0.2.6.dist-info → mapFolding-0.3.0.dist-info}/entry_points.txt +0 -0
  31. {mapFolding/reference → reference}/flattened.py +0 -0
  32. {mapFolding/reference → reference}/hunterNumba.py +0 -0
  33. {mapFolding/reference → reference}/irvineJavaPort.py +0 -0
  34. {mapFolding/reference → reference}/jax.py +0 -0
  35. {mapFolding/reference → reference}/lunnan.py +0 -0
  36. {mapFolding/reference → reference}/lunnanNumpy.py +0 -0
  37. {mapFolding/reference → reference}/lunnanWhile.py +0 -0
  38. {mapFolding/reference → reference}/rotatedEntryPoint.py +0 -0
  39. {mapFolding/reference → reference}/total_countPlus1vsPlusN.py +0 -0
mapFolding/theSSOT.py DELETED
@@ -1,76 +0,0 @@
1
- from typing import Any, Tuple, TypedDict
2
- import enum
3
- import numpy
4
- import numpy.typing
5
- import pathlib
6
- import sys
7
-
8
- datatypeModule = 'numpy'
9
-
10
- datatypeLarge = 'int64'
11
- datatypeDefault = datatypeLarge
12
- datatypeSmall = datatypeDefault
13
-
14
- make_dtype = lambda _datatype: eval(f"{datatypeModule}.{_datatype}")
15
-
16
- dtypeLarge = make_dtype(datatypeLarge)
17
- dtypeDefault = make_dtype(datatypeDefault)
18
- dtypeSmall = make_dtype(datatypeSmall)
19
-
20
- try:
21
- _pathModule = pathlib.Path(__file__).parent
22
- except NameError:
23
- _pathModule = pathlib.Path.cwd()
24
-
25
- pathJobDEFAULT = _pathModule / "jobs"
26
-
27
- if 'google.colab' in sys.modules:
28
- pathJobDEFAULT = pathlib.Path("/content/drive/MyDrive") / "jobs"
29
-
30
- @enum.verify(enum.CONTINUOUS, enum.UNIQUE) if sys.version_info >= (3, 11) else lambda x: x
31
- class EnumIndices(enum.IntEnum):
32
- """Base class for index enums."""
33
- @staticmethod
34
- def _generate_next_value_(name, start, count, last_values):
35
- """0-indexed."""
36
- return count
37
-
38
- def __index__(self) -> int:
39
- """Adapt enum to the ultra-rare event of indexing a NumPy 'ndarray', which is not the
40
- same as `array.array`. See NumPy.org; I think it will be very popular someday."""
41
- return self.value
42
-
43
- class indexMy(EnumIndices):
44
- """Indices for dynamic values."""
45
- dimensionsTotal = enum.auto() # connectionGraph.shape[0]
46
- dimensionsUnconstrained = enum.auto()
47
- gap1ndex = enum.auto()
48
- gap1ndexCeiling = enum.auto()
49
- indexDimension = enum.auto()
50
- indexLeaf = enum.auto()
51
- indexMiniGap = enum.auto()
52
- leaf1ndex = enum.auto()
53
- leafConnectee = enum.auto()
54
- taskDivisions = enum.auto()
55
- taskIndex = enum.auto()
56
-
57
- # class indexThe(EnumIndices):
58
- # """Indices for static values."""
59
- # dimensionsTotal = enum.auto() # connectionGraph.shape[0]
60
- # taskDivisions = enum.auto()
61
-
62
- class indexTrack(EnumIndices):
63
- """Indices for state tracking array."""
64
- leafAbove = enum.auto()
65
- leafBelow = enum.auto()
66
- countDimensionsGapped = enum.auto()
67
- gapRangeStart = enum.auto()
68
-
69
- class computationState(TypedDict):
70
- connectionGraph: numpy.typing.NDArray[numpy.integer[Any]]
71
- foldGroups: numpy.typing.NDArray[numpy.integer[Any]]
72
- gapsWhere: numpy.typing.NDArray[numpy.integer[Any]]
73
- mapShape: Tuple[int, ...]
74
- my: numpy.typing.NDArray[numpy.integer[Any]]
75
- # the: numpy.typing.NDArray[numpy.integer[Any]]
76
- track: numpy.typing.NDArray[numpy.integer[Any]]
@@ -1,33 +0,0 @@
1
- mapFolding/__init__.py,sha256=yZ_rcMMCco346M62nKzp90GPp9OV1UkkWSxKzP3ISPA,380
2
- mapFolding/babbage.py,sha256=IfUoov6WYf9ExpnUoOMgw17GmaLueWseANelnTjomUk,2144
3
- mapFolding/beDRY.py,sha256=SLN7Rmo8pZ1tXoV4ZDeXDrHoOpoRMxBiq1F2E_ngtIw,15496
4
- mapFolding/importSelector.py,sha256=uVdA2oUoo11Cq1QXfRslgvzdrkTUIWXzqN6-eADK1bA,373
5
- mapFolding/lovelace.py,sha256=1HIkzuI3SQ6HCAVcGKpHt6Q9IFneYddGa1V8xkwTy7Y,14967
6
- mapFolding/oeis.py,sha256=_-fLGc1ybZ2eFxoiBrSmojMexeg6ROxtrLaBF2BzMn4,12144
7
- mapFolding/startHere.py,sha256=7VShI9OHHb-CqSkQ4XHKSp2zGpbN0hue3xDD-HHpyuY,3922
8
- mapFolding/theSSOT.py,sha256=v31S2Z4A_x5si5UZHoVxhvVpMLYdknN79FQrFwZO0q4,2414
9
- mapFolding/benchmarks/benchmarking.py,sha256=HD_0NSvuabblg94ftDre6LFnXShTe8MYj3hIodW-zV0,3076
10
- mapFolding/reference/flattened.py,sha256=X9nvRzg7YDcpCtSDTL4YiidjshlX9rg2e6JVCY6i2u0,16547
11
- mapFolding/reference/hunterNumba.py,sha256=0giUyqAFzP-XKcq3Kz8wIWCK0BVFhjABVJ1s-w4Jhu0,7109
12
- mapFolding/reference/irvineJavaPort.py,sha256=Sj-63Z-OsGuDoEBXuxyjRrNmmyl0d7Yz_XuY7I47Oyg,4250
13
- mapFolding/reference/jax.py,sha256=bB34dGdi3VSz4cRFbmCPn_erAmQ3FyrSED8uJ7CsES0,14961
14
- mapFolding/reference/lunnan.py,sha256=XEcql_gxvCCghb6Or3qwmPbn4IZUbZTaSmw_fUjRxZE,5037
15
- mapFolding/reference/lunnanNumpy.py,sha256=HqDgSwTOZA-G0oophOEfc4zs25Mv4yw2aoF1v8miOLk,4653
16
- mapFolding/reference/lunnanWhile.py,sha256=7NY2IKO5XBgol0aWWF_Fi-7oTL9pvu_z6lB0TF1uVHk,4063
17
- mapFolding/reference/rotatedEntryPoint.py,sha256=z0QyDQtnMvXNj5ntWzzJUQUMFm1-xHGLVhtYzwmczUI,11530
18
- mapFolding/reference/total_countPlus1vsPlusN.py,sha256=usenM8Yn_G1dqlPl7NKKkcnbohBZVZBXTQRm2S3_EDA,8106
19
- mapFolding/someAssemblyRequired/inlineAfunction.py,sha256=JrmLc2w6MciC8nsxpIzea5rqQPxZi97e60irBxkHzro,6201
20
- mapFolding/someAssemblyRequired/jobsAndTasks.py,sha256=PR1waaYHMhUzNjRm6cgVg_AukLtKzLb2_NSOIn9APDY,2238
21
- mapFolding/someAssemblyRequired/makeNuitkaSource.py,sha256=jTK34OWzm6OsgFPd2mHwETxFo2X83io0M4YiEHRgk3U,3262
22
- mapFolding/someAssemblyRequired/makeNumbaJob.py,sha256=i9vYBfqtZdZp1sPEQ1McFao0pq4s_Ppo4VSkS4SFozU,5823
23
- tests/__init__.py,sha256=eg9smg-6VblOr0kisM40CpGnuDtU2JgEEWGDTFVOlW8,57
24
- tests/conftest.py,sha256=Xj-R4yTq5h8lVVKhPSh3UooI9rXzfjZhYw8sP-oYDcc,13588
25
- tests/pythons_idiotic_namespace.py,sha256=oOLDBergQqqhGuRpsXUnFD-R_6AlJipNKYHw-kk_OKw,33
26
- tests/test_oeis.py,sha256=vxnwO-cSR68htkyMh9QMVv-lvxBo6qlwPg1Rbx4JylY,7963
27
- tests/test_other.py,sha256=98W-xsNQQuxYd4OiaRWUG7rcr4E5Q3SLCIMOjxr7FEo,12820
28
- tests/test_tasks.py,sha256=Nwe4iuSjwGZvsw5CXCcic7tkBxgM5JX9mrGZMDYhAwE,1785
29
- mapFolding-0.2.6.dist-info/METADATA,sha256=8oni55uKek5pb5Hw8Mu3ISUJHUTYV3w0HYm2eLk0eGA,6652
30
- mapFolding-0.2.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
31
- mapFolding-0.2.6.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
32
- mapFolding-0.2.6.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
33
- mapFolding-0.2.6.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- mapFolding
2
- tests
tests/__init__.py DELETED
@@ -1 +0,0 @@
1
- from tests.conftest import makeDictionaryFoldsTotalKnown
tests/conftest.py DELETED
@@ -1,343 +0,0 @@
1
- """SSOT for Pytest"""
2
-
3
- # TODO learn how to run tests and coverage analysis without `env = ["NUMBA_DISABLE_JIT=1"]`
4
-
5
- from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Set, Tuple, Type, Union
6
- import pathlib
7
- import pytest
8
- import random
9
- import shutil
10
- import unittest.mock
11
- import uuid
12
- from Z0Z_tools.pytest_parseParameters import makeTestSuiteConcurrencyLimit
13
- from Z0Z_tools.pytest_parseParameters import makeTestSuiteIntInnit
14
- from Z0Z_tools.pytest_parseParameters import makeTestSuiteOopsieKwargsie
15
- from mapFolding import countFolds, pathJobDEFAULT, indexMy, indexTrack, saveFoldsTotal
16
- from mapFolding import defineConcurrencyLimit, intInnit, oopsieKwargsie, outfitCountFolds
17
- from mapFolding import oeisIDfor_n, getOEISids, clearOEIScache, getFilenameFoldsTotal
18
- from mapFolding.beDRY import getLeavesTotal, parseDimensions, validateListDimensions
19
- from mapFolding.beDRY import getTaskDivisions, makeConnectionGraph, setCPUlimit
20
- from mapFolding.beDRY import makeDataContainer
21
- from mapFolding.oeis import OEIS_for_n
22
- from mapFolding.oeis import _getFilenameOEISbFile
23
- from mapFolding.oeis import _getOEISidValues
24
- from mapFolding.oeis import _parseBFileOEIS
25
- from mapFolding.oeis import _validateOEISid
26
- from mapFolding.oeis import oeisIDsImplemented
27
- from mapFolding.oeis import settingsOEIS
28
-
29
- __all__ = [
30
- 'OEIS_for_n',
31
- '_getFilenameOEISbFile',
32
- '_getOEISidValues',
33
- '_parseBFileOEIS',
34
- '_validateOEISid',
35
- 'clearOEIScache',
36
- 'countFolds',
37
- 'defineConcurrencyLimit',
38
- 'expectSystemExit',
39
- 'getFilenameFoldsTotal',
40
- 'getLeavesTotal',
41
- 'getOEISids',
42
- 'getTaskDivisions',
43
- 'intInnit',
44
- 'makeConnectionGraph',
45
- 'makeDataContainer',
46
- 'makeTestSuiteConcurrencyLimit',
47
- 'makeTestSuiteIntInnit',
48
- 'makeTestSuiteOopsieKwargsie',
49
- 'oeisIDfor_n',
50
- 'oeisIDsImplemented',
51
- 'oopsieKwargsie',
52
- 'outfitCountFolds',
53
- 'parseDimensions',
54
- 'saveFoldsTotal',
55
- 'setCPUlimit',
56
- 'settingsOEIS',
57
- 'standardCacheTest',
58
- 'standardComparison',
59
- 'validateListDimensions',
60
- ]
61
-
62
- def makeDictionaryFoldsTotalKnown() -> Dict[Tuple[int,...], int]:
63
- """Returns a dictionary mapping dimension tuples to their known folding totals."""
64
- dictionaryMapDimensionsToFoldsTotalKnown = {}
65
-
66
- for settings in settingsOEIS.values():
67
- sequence = settings['valuesKnown']
68
-
69
- for n, foldingsTotal in sequence.items():
70
- dimensions = settings['getDimensions'](n)
71
- dimensions.sort()
72
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(dimensions)] = foldingsTotal
73
-
74
- # Are we in a place that has jobs?
75
- if pathJobDEFAULT.exists():
76
- # Are there foldsTotal files?
77
- for pathFilenameFoldsTotal in pathJobDEFAULT.rglob('*.foldsTotal'):
78
- if pathFilenameFoldsTotal.is_file():
79
- try:
80
- listDimensions = eval(pathFilenameFoldsTotal.stem)
81
- except Exception:
82
- continue
83
- # Are the dimensions in the dictionary?
84
- if isinstance(listDimensions, list) and all(isinstance(dimension, int) for dimension in listDimensions):
85
- listDimensions.sort()
86
- if tuple(listDimensions) in dictionaryMapDimensionsToFoldsTotalKnown:
87
- continue
88
- # Are the contents a reasonably large integer?
89
- try:
90
- foldsTotal = pathFilenameFoldsTotal.read_text()
91
- except Exception:
92
- continue
93
- # Why did I sincerely believe this would only be three lines of code?
94
- if foldsTotal.isdigit() and int(foldsTotal) > 85109616 * 10**3:
95
- foldsTotal = int(foldsTotal)
96
- # You made it this far, so fuck it: put it in the dictionary
97
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(listDimensions)] = foldsTotal
98
- # The sunk-costs fallacy claims another victim!
99
-
100
- return dictionaryMapDimensionsToFoldsTotalKnown
101
-
102
- """
103
- Section: temporary paths and pathFilenames"""
104
-
105
- # SSOT for test data paths
106
- pathDataSamples = pathlib.Path("tests/dataSamples")
107
- pathTempRoot = pathDataSamples / "tmp"
108
-
109
- # The registrar maintains the register of temp files
110
- registerOfTempFiles: Set[pathlib.Path] = set()
111
-
112
- def addTempFileToRegister(path: pathlib.Path) -> None:
113
- """The registrar adds a temp file to the register."""
114
- registerOfTempFiles.add(path)
115
-
116
- def cleanupTempFileRegister() -> None:
117
- """The registrar cleans up temp files in the register."""
118
- for pathTemp in sorted(registerOfTempFiles, reverse=True):
119
- try:
120
- if pathTemp.is_file():
121
- pathTemp.unlink(missing_ok=True)
122
- elif pathTemp.is_dir():
123
- shutil.rmtree(pathTemp, ignore_errors=True)
124
- except Exception as ERRORmessage:
125
- print(f"Warning: Failed to clean up {pathTemp}: {ERRORmessage}")
126
- registerOfTempFiles.clear()
127
-
128
- @pytest.fixture(scope="session", autouse=True)
129
- def setupTeardownTestData() -> Generator[None, None, None]:
130
- """Auto-fixture to setup test data directories and cleanup after."""
131
- pathDataSamples.mkdir(exist_ok=True)
132
- pathTempRoot.mkdir(exist_ok=True)
133
- yield
134
- cleanupTempFileRegister()
135
-
136
- @pytest.fixture(autouse=True)
137
- def setupWarningsAsErrors():
138
- """Convert all warnings to errors for all tests."""
139
- import warnings
140
- warnings.filterwarnings("error")
141
- yield
142
- warnings.resetwarnings()
143
-
144
- @pytest.fixture
145
- def pathTempTesting(request: pytest.FixtureRequest) -> pathlib.Path:
146
- """Create a unique temp directory for each test function."""
147
- # Sanitize test name for filesystem compatibility
148
- sanitizedName = request.node.name.replace('[', '_').replace(']', '_').replace('/', '_')
149
- uniqueDirectory = f"{sanitizedName}_{uuid.uuid4()}"
150
- pathTemp = pathTempRoot / uniqueDirectory
151
- pathTemp.mkdir(parents=True, exist_ok=True)
152
-
153
- addTempFileToRegister(pathTemp)
154
- return pathTemp
155
-
156
- @pytest.fixture
157
- def pathCacheTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
158
- """Temporarily replace the OEIS cache directory with a test directory."""
159
- from mapFolding import oeis as there_must_be_a_better_way
160
- pathCacheOriginal = there_must_be_a_better_way._pathCache
161
- there_must_be_a_better_way._pathCache = pathTempTesting
162
- yield pathTempTesting
163
- there_must_be_a_better_way._pathCache = pathCacheOriginal
164
-
165
- @pytest.fixture
166
- def pathFilenameBenchmarksTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
167
- """Temporarily replace the benchmarks directory with a test directory."""
168
- from mapFolding.benchmarks import benchmarking
169
- pathFilenameOriginal = benchmarking.pathFilenameRecordedBenchmarks
170
- pathFilenameTest = pathTempTesting / "benchmarks.npy"
171
- benchmarking.pathFilenameRecordedBenchmarks = pathFilenameTest
172
- yield pathFilenameTest
173
- benchmarking.pathFilenameRecordedBenchmarks = pathFilenameOriginal
174
-
175
- @pytest.fixture
176
- def pathFilenameFoldsTotalTesting(pathTempTesting: pathlib.Path) -> pathlib.Path:
177
- return pathTempTesting.joinpath("foldsTotalTest.txt")
178
-
179
- """
180
- Section: Fixtures"""
181
-
182
- @pytest.fixture
183
- def foldsTotalKnown() -> Dict[Tuple[int,...], int]:
184
- """Returns a dictionary mapping dimension tuples to their known folding totals.
185
- NOTE I am not convinced this is the best way to do this.
186
- Advantage: I call `makeDictionaryFoldsTotalKnown()` from modules other than test modules.
187
- Preference: I _think_ I would prefer a SSOT function available to any module
188
- similar to `foldsTotalKnown = getFoldsTotalKnown(listDimensions)`."""
189
- return makeDictionaryFoldsTotalKnown()
190
-
191
- @pytest.fixture
192
- def listDimensionsTestFunctionality(oeisID_1random: str) -> List[int]:
193
- """To test functionality, get one `listDimensions` from `valuesTestValidation` if
194
- `validateListDimensions` approves. The algorithm can count the folds of the returned
195
- `listDimensions` in a short enough time suitable for testing."""
196
- while True:
197
- n = random.choice(settingsOEIS[oeisID_1random]['valuesTestValidation'])
198
- if n < 2:
199
- continue
200
- listDimensionsCandidate = settingsOEIS[oeisID_1random]['getDimensions'](n)
201
-
202
- try:
203
- return validateListDimensions(listDimensionsCandidate)
204
- except (ValueError, NotImplementedError):
205
- pass
206
-
207
- @pytest.fixture
208
- def listDimensionsTest_countFolds(oeisID: str) -> List[int]:
209
- """For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestValidation`
210
- if `validateListDimensions` approves. Each `listDimensions` is suitable for testing counts."""
211
- while True:
212
- n = random.choice(settingsOEIS[oeisID]['valuesTestValidation'])
213
- if n < 2:
214
- continue
215
- listDimensionsCandidate = settingsOEIS[oeisID]['getDimensions'](n)
216
-
217
- try:
218
- return validateListDimensions(listDimensionsCandidate)
219
- except (ValueError, NotImplementedError):
220
- pass
221
-
222
- @pytest.fixture
223
- def mockBenchmarkTimer() -> Generator[unittest.mock.MagicMock | unittest.mock.AsyncMock, Any, None]:
224
- """Mock time.perf_counter_ns for consistent benchmark timing."""
225
- with unittest.mock.patch('time.perf_counter_ns') as mockTimer:
226
- mockTimer.side_effect = [0, 1e9] # Start and end times for 1 second
227
- yield mockTimer
228
-
229
- @pytest.fixture(params=oeisIDsImplemented)
230
- def oeisID(request: pytest.FixtureRequest)-> str:
231
- return request.param
232
-
233
- @pytest.fixture
234
- def oeisID_1random() -> str:
235
- """Return one random valid OEIS ID."""
236
- return random.choice(oeisIDsImplemented)
237
-
238
- @pytest.fixture
239
- def mockFoldingFunction():
240
- """Creates a mock function that simulates _countFolds behavior."""
241
- def make_mock(foldsValue: int, listDimensions: List[int]):
242
- mock_array = makeDataContainer(2)
243
- mock_array[0] = foldsValue
244
- mock_array[-1] = getLeavesTotal(listDimensions)
245
-
246
- def mock_countfolds(**keywordArguments):
247
- keywordArguments['foldGroups'][:] = mock_array
248
- return None
249
-
250
- return mock_countfolds
251
- return make_mock
252
-
253
- """
254
- Section: Standardized test structures"""
255
-
256
- def formatTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
257
- """Format assertion message for any test comparison."""
258
- return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
259
- f"Expected: {expected}\n"
260
- f"Got: {actual}")
261
-
262
- def standardComparison(expected: Any, functionTarget: Callable, *arguments: Any) -> None:
263
- """Template for tests expecting an error."""
264
- if type(expected) == Type[Exception]:
265
- messageExpected = expected.__name__
266
- else:
267
- messageExpected = expected
268
-
269
- try:
270
- messageActual = actual = functionTarget(*arguments)
271
- except Exception as actualError:
272
- messageActual = type(actualError).__name__
273
- actual = type(actualError)
274
-
275
- assert actual == expected, formatTestMessage(messageExpected, messageActual, functionTarget.__name__, *arguments)
276
-
277
- def expectSystemExit(expected: Union[str, int, Sequence[int]], functionTarget: Callable, *arguments: Any) -> None:
278
- """Template for tests expecting SystemExit.
279
-
280
- Parameters
281
- expected: Exit code expectation:
282
- - "error": any non-zero exit code
283
- - "nonError": specifically zero exit code
284
- - int: exact exit code match
285
- - Sequence[int]: exit code must be one of these values
286
- functionTarget: The function to test
287
- arguments: Arguments to pass to the function
288
- """
289
- with pytest.raises(SystemExit) as exitInfo:
290
- functionTarget(*arguments)
291
-
292
- exitCode = exitInfo.value.code
293
-
294
- if expected == "error":
295
- assert exitCode != 0, \
296
- f"Expected error exit (non-zero) but got code {exitCode}"
297
- elif expected == "nonError":
298
- assert exitCode == 0, \
299
- f"Expected non-error exit (0) but got code {exitCode}"
300
- elif isinstance(expected, (list, tuple)):
301
- assert exitCode in expected, \
302
- f"Expected exit code to be one of {expected} but got {exitCode}"
303
- else:
304
- assert exitCode == expected, \
305
- f"Expected exit code {expected} but got {exitCode}"
306
-
307
- def standardCacheTest(
308
- expected: Any,
309
- setupCacheFile: Optional[Callable[[pathlib.Path, str], None]],
310
- oeisID: str,
311
- pathCache: pathlib.Path
312
- ) -> None:
313
- """Template for tests involving OEIS cache operations.
314
-
315
- Parameters
316
- expected: Expected value or exception from _getOEISidValues
317
- setupCacheFile: Function to prepare the cache file before test
318
- oeisID: OEIS ID to test
319
- pathCache: Temporary cache directory path
320
- """
321
- pathFilenameCache = pathCache / _getFilenameOEISbFile(oeisID)
322
-
323
- # Setup cache file if provided
324
- if setupCacheFile:
325
- setupCacheFile(pathFilenameCache, oeisID)
326
-
327
- # Run test
328
- try:
329
- actual = _getOEISidValues(oeisID)
330
- messageActual = actual
331
- except Exception as actualError:
332
- actual = type(actualError)
333
- messageActual = type(actualError).__name__
334
-
335
- # Compare results
336
- if isinstance(expected, type) and issubclass(expected, Exception):
337
- messageExpected = expected.__name__
338
- assert isinstance(actual, expected), formatTestMessage(
339
- messageExpected, messageActual, "_getOEISidValues", oeisID)
340
- else:
341
- messageExpected = expected
342
- assert actual == expected, formatTestMessage(
343
- messageExpected, messageActual, "_getOEISidValues", oeisID)
@@ -1 +0,0 @@
1
- from mapFolding.theSSOT import *
tests/test_oeis.py DELETED
@@ -1,194 +0,0 @@
1
- from tests.conftest import *
2
- from contextlib import redirect_stdout
3
- from datetime import datetime, timedelta
4
- from typing import Optional, Tuple, Union
5
- import io
6
- import os
7
- import pathlib
8
- import pytest
9
- import random
10
- import re as regex
11
- import unittest
12
- import unittest.mock
13
- import urllib.error
14
- import urllib.request
15
-
16
- def test_aOFn_calculate_value(oeisID: str):
17
- for n in settingsOEIS[oeisID]['valuesTestValidation']:
18
- standardComparison(settingsOEIS[oeisID]['valuesKnown'][n], oeisIDfor_n, oeisID, n)
19
-
20
- @pytest.mark.parametrize("badID", ["A999999", " A999999 ", "A999999extra"])
21
- def test__validateOEISid_invalid_id(badID: str):
22
- standardComparison(KeyError, _validateOEISid, badID)
23
-
24
- def test__validateOEISid_partially_valid(oeisID_1random: str):
25
- standardComparison(KeyError, _validateOEISid, f"{oeisID_1random}extra")
26
-
27
- def test__validateOEISid_valid_id(oeisID: str):
28
- standardComparison(oeisID, _validateOEISid, oeisID)
29
-
30
- def test__validateOEISid_valid_id_case_insensitive(oeisID: str):
31
- standardComparison(oeisID.upper(), _validateOEISid, oeisID.lower())
32
- standardComparison(oeisID.upper(), _validateOEISid, oeisID.upper())
33
- standardComparison(oeisID.upper(), _validateOEISid, oeisID.swapcase())
34
-
35
- parameters_test_aOFn_invalid_n = [
36
- # (2, "ok"), # test the test template
37
- (-random.randint(1, 100), "randomNegative"),
38
- ("foo", "string"),
39
- (1.5, "float")
40
- ]
41
- badValues, badValuesIDs = zip(*parameters_test_aOFn_invalid_n)
42
- @pytest.mark.parametrize("badN", badValues, ids=badValuesIDs)
43
- def test_aOFn_invalid_n(oeisID_1random: str, badN):
44
- """Check that negative or non-integer n raises ValueError."""
45
- standardComparison(ValueError, oeisIDfor_n, oeisID_1random, badN)
46
-
47
- def test_aOFn_zeroDim_A001418():
48
- standardComparison(ArithmeticError, oeisIDfor_n, 'A001418', 0)
49
-
50
- # ===== OEIS Cache Tests =====
51
- @pytest.mark.parametrize("cacheExists", [True, False])
52
- @unittest.mock.patch('pathlib.Path.exists')
53
- @unittest.mock.patch('pathlib.Path.unlink')
54
- def test_clearOEIScache(mock_unlink: unittest.mock.MagicMock, mock_exists: unittest.mock.MagicMock, cacheExists: bool):
55
- """Test OEIS cache clearing with both existing and non-existing cache."""
56
- mock_exists.return_value = cacheExists
57
- clearOEIScache()
58
-
59
- if cacheExists:
60
- assert mock_unlink.call_count == len(settingsOEIS)
61
- mock_unlink.assert_has_calls([unittest.mock.call(missing_ok=True)] * len(settingsOEIS))
62
- else:
63
- mock_exists.assert_called_once()
64
- mock_unlink.assert_not_called()
65
-
66
- @pytest.mark.parametrize("scenarioCache", ["miss", "expired", "invalid"])
67
- def testCacheScenarios(pathCacheTesting: pathlib.Path, oeisID_1random: str, scenarioCache: str) -> None:
68
- """Test cache scenarios: missing file, expired file, and invalid file."""
69
-
70
- def setupCacheExpired(pathCache: pathlib.Path, oeisID: str) -> None:
71
- pathCache.write_text("# Old cache content")
72
- oldModificationTime = datetime.now() - timedelta(days=30)
73
- os.utime(pathCache, times=(oldModificationTime.timestamp(), oldModificationTime.timestamp()))
74
-
75
- def setupCacheInvalid(pathCache: pathlib.Path, oeisID: str) -> None:
76
- pathCache.write_text("Invalid content")
77
-
78
- if scenarioCache == "miss":
79
- standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], None, oeisID_1random, pathCacheTesting)
80
- elif scenarioCache == "expired":
81
- standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], setupCacheExpired, oeisID_1random, pathCacheTesting)
82
- else:
83
- standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], setupCacheInvalid, oeisID_1random, pathCacheTesting)
84
-
85
- def testInvalidFileContent(pathCacheTesting: pathlib.Path, oeisID_1random: str):
86
- pathFilenameCache = pathCacheTesting / _getFilenameOEISbFile(oeisID=oeisID_1random)
87
-
88
- # Write invalid content to cache
89
- pathFilenameCache.write_text("# A999999\n1 1\n2 2\n")
90
- modificationTimeOriginal = pathFilenameCache.stat().st_mtime
91
-
92
- # Function should detect invalid content, fetch fresh data, and update cache
93
- OEISsequence = _getOEISidValues(oeisID_1random)
94
-
95
- # Verify the function succeeded
96
- assert OEISsequence is not None
97
- # Verify cache was updated (modification time changed)
98
- assert pathFilenameCache.stat().st_mtime > modificationTimeOriginal
99
- # Verify cache now contains correct sequence ID
100
- assert f"# {oeisID_1random}" in pathFilenameCache.read_text()
101
-
102
- def testParseContentErrors():
103
- """Test invalid content parsing."""
104
- standardComparison(ValueError, _parseBFileOEIS, "Invalid content\n1 2\n", 'A001415')
105
-
106
- def testExtraComments(pathCacheTesting: pathlib.Path, oeisID_1random: str):
107
- pathFilenameCache = pathCacheTesting / _getFilenameOEISbFile(oeisID=oeisID_1random)
108
-
109
- # Write content with extra comment lines
110
- contentWithExtraComments = f"""# {oeisID_1random}
111
- # Normal place for comment line 1
112
- # Abnormal comment line
113
- 1 2
114
- 2 4
115
- 3 6
116
- # Another comment in the middle
117
- 4 8
118
- 5 10"""
119
- pathFilenameCache.write_text(contentWithExtraComments)
120
-
121
- OEISsequence = _getOEISidValues(oeisID_1random)
122
- # Verify sequence values are correct despite extra comments
123
- standardComparison(2, lambda d: d[1], OEISsequence) # First value
124
- standardComparison(8, lambda d: d[4], OEISsequence) # Value after mid-sequence comment
125
- standardComparison(10, lambda d: d[5], OEISsequence) # Last value
126
-
127
- def testNetworkError(monkeypatch: pytest.MonkeyPatch, pathCacheTesting: pathlib.Path):
128
- """Test network error handling."""
129
- def mockUrlopen(*args, **kwargs):
130
- raise urllib.error.URLError("Network error")
131
-
132
- monkeypatch.setattr(urllib.request, 'urlopen', mockUrlopen)
133
- standardComparison(urllib.error.URLError, _getOEISidValues, next(iter(settingsOEIS)))
134
-
135
- # ===== Command Line Interface Tests =====
136
- def testHelpText():
137
- """Test that help text is complete and examples are valid."""
138
- outputStream = io.StringIO()
139
- with redirect_stdout(outputStream):
140
- getOEISids()
141
-
142
- helpText = outputStream.getvalue()
143
-
144
- # Verify content
145
- for oeisID in oeisIDsImplemented:
146
- assert oeisID in helpText
147
- assert settingsOEIS[oeisID]['description'] in helpText
148
-
149
- # Extract and verify examples
150
-
151
- cliMatch = regex.search(r'OEIS_for_n (\w+) (\d+)', helpText)
152
- pythonMatch = regex.search(r"oeisIDfor_n\('(\w+)', (\d+)\)", helpText)
153
-
154
- assert cliMatch and pythonMatch, "Help text missing examples"
155
- oeisID, n = pythonMatch.groups()
156
- n = int(n)
157
-
158
- # Verify CLI and Python examples use same values
159
- assert cliMatch.groups() == (oeisID, str(n)), "CLI and Python examples inconsistent"
160
-
161
- # Verify the example works
162
- expectedValue = oeisIDfor_n(oeisID, n)
163
-
164
- # Test CLI execution of the example
165
- with unittest.mock.patch('sys.argv', ['OEIS_for_n', oeisID, str(n)]):
166
- outputStream = io.StringIO()
167
- with redirect_stdout(outputStream):
168
- OEIS_for_n()
169
- standardComparison(expectedValue, lambda: int(outputStream.getvalue().strip().split()[0]))
170
-
171
- def testCLI_InvalidInputs():
172
- """Test CLI error handling."""
173
- testCases = [
174
- (['OEIS_for_n'], "missing arguments"),
175
- (['OEIS_for_n', 'A999999', '1'], "invalid OEIS ID"),
176
- (['OEIS_for_n', 'A001415', '-1'], "negative n"),
177
- (['OEIS_for_n', 'A001415', 'abc'], "non-integer n"),
178
- ]
179
-
180
- for arguments, testID in testCases:
181
- with unittest.mock.patch('sys.argv', arguments):
182
- expectSystemExit("error", OEIS_for_n)
183
-
184
- def testCLI_HelpFlag():
185
- """Verify --help output contains required information."""
186
- with unittest.mock.patch('sys.argv', ['OEIS_for_n', '--help']):
187
- outputStream = io.StringIO()
188
- with redirect_stdout(outputStream):
189
- expectSystemExit("nonError", OEIS_for_n)
190
-
191
- helpOutput = outputStream.getvalue()
192
- assert "Available OEIS sequences:" in helpOutput
193
- assert "Usage examples:" in helpOutput
194
- assert all(oeisID in helpOutput for oeisID in oeisIDsImplemented)