mapFolding 0.2.5__py3-none-any.whl → 0.2.7__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 (35) hide show
  1. {mapFolding-0.2.5.dist-info → mapFolding-0.2.7.dist-info}/METADATA +21 -7
  2. mapFolding-0.2.7.dist-info/RECORD +19 -0
  3. mapFolding-0.2.7.dist-info/top_level.txt +3 -0
  4. {mapFolding/someAssemblyRequired → someAssemblyRequired}/inlineAfunction.py +3 -3
  5. {mapFolding/someAssemblyRequired → someAssemblyRequired}/jobsAndTasks.py +5 -6
  6. {mapFolding/someAssemblyRequired → someAssemblyRequired}/makeNumbaJob.py +57 -31
  7. mapFolding/__init__.py +0 -12
  8. mapFolding/babbage.py +0 -35
  9. mapFolding/beDRY.py +0 -325
  10. mapFolding/importSelector.py +0 -7
  11. mapFolding/lovelace.py +0 -213
  12. mapFolding/oeis.py +0 -323
  13. mapFolding/startHere.py +0 -50
  14. mapFolding/theSSOT.py +0 -75
  15. mapFolding-0.2.5.dist-info/RECORD +0 -33
  16. mapFolding-0.2.5.dist-info/top_level.txt +0 -2
  17. tests/__init__.py +0 -1
  18. tests/conftest.py +0 -345
  19. tests/pythons_idiotic_namespace.py +0 -1
  20. tests/test_oeis.py +0 -194
  21. tests/test_other.py +0 -268
  22. tests/test_tasks.py +0 -31
  23. {mapFolding/benchmarks → benchmarks}/benchmarking.py +0 -0
  24. {mapFolding-0.2.5.dist-info → mapFolding-0.2.7.dist-info}/WHEEL +0 -0
  25. {mapFolding-0.2.5.dist-info → mapFolding-0.2.7.dist-info}/entry_points.txt +0 -0
  26. {mapFolding/reference → reference}/flattened.py +0 -0
  27. {mapFolding/reference → reference}/hunterNumba.py +0 -0
  28. {mapFolding/reference → reference}/irvineJavaPort.py +0 -0
  29. {mapFolding/reference → reference}/jax.py +0 -0
  30. {mapFolding/reference → reference}/lunnan.py +0 -0
  31. {mapFolding/reference → reference}/lunnanNumpy.py +0 -0
  32. {mapFolding/reference → reference}/lunnanWhile.py +0 -0
  33. {mapFolding/reference → reference}/rotatedEntryPoint.py +0 -0
  34. {mapFolding/reference → reference}/total_countPlus1vsPlusN.py +0 -0
  35. {mapFolding/someAssemblyRequired → someAssemblyRequired}/makeNuitkaSource.py +0 -0
@@ -1,33 +0,0 @@
1
- mapFolding/__init__.py,sha256=yZ_rcMMCco346M62nKzp90GPp9OV1UkkWSxKzP3ISPA,380
2
- mapFolding/babbage.py,sha256=51fO7lwcTsTvSMwzKW1G2nGslGoEQt19IgnqZi8znao,2222
3
- mapFolding/beDRY.py,sha256=IhBnlo-Lg8DBaebTDAyJ7OiGUce9OFbNUNHJmlB04L0,15835
4
- mapFolding/importSelector.py,sha256=sc9IGk8CpCNerFbuYnrroBH-itqsxjeBc4VYjiesOQo,310
5
- mapFolding/lovelace.py,sha256=iu7anbA_TacIAjc4EKkeBVxIJKAMdrYgvR4evzMZ1WY,15193
6
- mapFolding/oeis.py,sha256=_-fLGc1ybZ2eFxoiBrSmojMexeg6ROxtrLaBF2BzMn4,12144
7
- mapFolding/startHere.py,sha256=Bu4boZnxlx66IU7RIsBRq00JScE0DWqKLAclUMPGOSM,3892
8
- mapFolding/theSSOT.py,sha256=3Zty4rYWOqrwivuCaKA71R0HM4rjmvtkL_Bsn4ZhwFo,2318
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=Hxytb9xgJV_Mnh8us0mA_oKwanEQWJSj3hJc25nI9Mk,6143
20
- mapFolding/someAssemblyRequired/jobsAndTasks.py,sha256=u2ZtZ8xlQJALqQqQ8N7uBTNDbjG4OQ9nJsJZG5rLI8o,2189
21
- mapFolding/someAssemblyRequired/makeNuitkaSource.py,sha256=jTK34OWzm6OsgFPd2mHwETxFo2X83io0M4YiEHRgk3U,3262
22
- mapFolding/someAssemblyRequired/makeNumbaJob.py,sha256=YZ9JzzEieQH4sMqX84VGbpOJmdBatPanfmXFZ5V1Ex4,5109
23
- tests/__init__.py,sha256=eg9smg-6VblOr0kisM40CpGnuDtU2JgEEWGDTFVOlW8,57
24
- tests/conftest.py,sha256=AWB3m_jxMlkmOmGvk2ApJEk2ro5v8gmmJDcyLwN1oow,13761
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=1EtLe0te7qqdazFKvKIOHPYW_ENsqAu11nzJO0yAE_Q,12012
28
- tests/test_tasks.py,sha256=Nwe4iuSjwGZvsw5CXCcic7tkBxgM5JX9mrGZMDYhAwE,1785
29
- mapFolding-0.2.5.dist-info/METADATA,sha256=pKBorE-WQsa2LVETIUORCw-ae3cDPtUcLbE395-oVhs,6652
30
- mapFolding-0.2.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
31
- mapFolding-0.2.5.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
32
- mapFolding-0.2.5.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
33
- mapFolding-0.2.5.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,345 +0,0 @@
1
- """SSOT for Pytest.
2
- Other test modules must not import directly from the package being tested."""
3
-
4
- # TODO learn how to run tests and coverage analysis without `env = ["NUMBA_DISABLE_JIT=1"]`
5
-
6
- from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Set, Tuple, Type, Union
7
- import pathlib
8
- import pytest
9
- import random
10
- import shutil
11
- import unittest.mock
12
- import uuid
13
- from Z0Z_tools.pytest_parseParameters import makeTestSuiteConcurrencyLimit
14
- from Z0Z_tools.pytest_parseParameters import makeTestSuiteIntInnit
15
- from Z0Z_tools.pytest_parseParameters import makeTestSuiteOopsieKwargsie
16
- from mapFolding import countFolds, pathJobDEFAULT, indexMy, indexThe, indexTrack
17
- from mapFolding import defineConcurrencyLimit, intInnit, oopsieKwargsie, outfitCountFolds
18
- from mapFolding import oeisIDfor_n, getOEISids, clearOEIScache, getFilenameFoldsTotal
19
- from mapFolding.beDRY import getLeavesTotal, parseDimensions, validateListDimensions
20
- from mapFolding.beDRY import getTaskDivisions, makeConnectionGraph, setCPUlimit
21
- from mapFolding.beDRY import makeDataContainer
22
- from mapFolding.oeis import OEIS_for_n
23
- from mapFolding.oeis import _getFilenameOEISbFile
24
- from mapFolding.oeis import _getOEISidValues
25
- from mapFolding.oeis import _parseBFileOEIS
26
- from mapFolding.oeis import _validateOEISid
27
- from mapFolding.oeis import oeisIDsImplemented
28
- from mapFolding.oeis import settingsOEIS
29
-
30
- __all__ = [
31
- 'OEIS_for_n',
32
- '_getFilenameOEISbFile',
33
- '_getOEISidValues',
34
- '_parseBFileOEIS',
35
- '_validateOEISid',
36
- 'clearOEIScache',
37
- 'countFolds',
38
- 'defineConcurrencyLimit',
39
- 'expectSystemExit',
40
- 'getFilenameFoldsTotal',
41
- 'getLeavesTotal',
42
- 'getOEISids',
43
- 'getTaskDivisions',
44
- 'indexThe',
45
- 'intInnit',
46
- 'makeConnectionGraph',
47
- 'makeDataContainer',
48
- 'makeTestSuiteConcurrencyLimit',
49
- 'makeTestSuiteIntInnit',
50
- 'makeTestSuiteOopsieKwargsie',
51
- 'oeisIDfor_n',
52
- 'oeisIDsImplemented',
53
- 'oopsieKwargsie',
54
- 'outfitCountFolds',
55
- 'parseDimensions',
56
- 'setCPUlimit',
57
- 'settingsOEIS',
58
- 'standardCacheTest',
59
- 'standardComparison',
60
- 'validateListDimensions',
61
- ]
62
-
63
- def makeDictionaryFoldsTotalKnown() -> Dict[Tuple[int,...], int]:
64
- """Returns a dictionary mapping dimension tuples to their known folding totals."""
65
- dictionaryMapDimensionsToFoldsTotalKnown = {}
66
-
67
- for settings in settingsOEIS.values():
68
- sequence = settings['valuesKnown']
69
-
70
- for n, foldingsTotal in sequence.items():
71
- dimensions = settings['getDimensions'](n)
72
- dimensions.sort()
73
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(dimensions)] = foldingsTotal
74
-
75
- # Are we in a place that has jobs?
76
- if pathJobDEFAULT.exists():
77
- # Are there foldsTotal files?
78
- for pathFilenameFoldsTotal in pathJobDEFAULT.rglob('*.foldsTotal'):
79
- if pathFilenameFoldsTotal.is_file():
80
- try:
81
- listDimensions = eval(pathFilenameFoldsTotal.stem)
82
- except Exception:
83
- continue
84
- # Are the dimensions in the dictionary?
85
- if isinstance(listDimensions, list) and all(isinstance(dimension, int) for dimension in listDimensions):
86
- listDimensions.sort()
87
- if tuple(listDimensions) in dictionaryMapDimensionsToFoldsTotalKnown:
88
- continue
89
- # Are the contents a reasonably large integer?
90
- try:
91
- foldsTotal = pathFilenameFoldsTotal.read_text()
92
- except Exception:
93
- continue
94
- # Why did I sincerely believe this would only be three lines of code?
95
- if foldsTotal.isdigit() and int(foldsTotal) > 85109616 * 10**3:
96
- foldsTotal = int(foldsTotal)
97
- # You made it this far, so fuck it: put it in the dictionary
98
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(listDimensions)] = foldsTotal
99
- # The sunk-costs fallacy claims another victim!
100
-
101
- return dictionaryMapDimensionsToFoldsTotalKnown
102
-
103
- """
104
- Section: temporary paths and pathFilenames"""
105
-
106
- # SSOT for test data paths
107
- pathDataSamples = pathlib.Path("tests/dataSamples")
108
- pathTempRoot = pathDataSamples / "tmp"
109
-
110
- # The registrar maintains the register of temp files
111
- registerOfTempFiles: Set[pathlib.Path] = set()
112
-
113
- def addTempFileToRegister(path: pathlib.Path) -> None:
114
- """The registrar adds a temp file to the register."""
115
- registerOfTempFiles.add(path)
116
-
117
- def cleanupTempFileRegister() -> None:
118
- """The registrar cleans up temp files in the register."""
119
- for pathTemp in sorted(registerOfTempFiles, reverse=True):
120
- try:
121
- if pathTemp.is_file():
122
- pathTemp.unlink(missing_ok=True)
123
- elif pathTemp.is_dir():
124
- shutil.rmtree(pathTemp, ignore_errors=True)
125
- except Exception as ERRORmessage:
126
- print(f"Warning: Failed to clean up {pathTemp}: {ERRORmessage}")
127
- registerOfTempFiles.clear()
128
-
129
- @pytest.fixture(scope="session", autouse=True)
130
- def setupTeardownTestData() -> Generator[None, None, None]:
131
- """Auto-fixture to setup test data directories and cleanup after."""
132
- pathDataSamples.mkdir(exist_ok=True)
133
- pathTempRoot.mkdir(exist_ok=True)
134
- yield
135
- cleanupTempFileRegister()
136
-
137
- @pytest.fixture(autouse=True)
138
- def setupWarningsAsErrors():
139
- """Convert all warnings to errors for all tests."""
140
- import warnings
141
- warnings.filterwarnings("error")
142
- yield
143
- warnings.resetwarnings()
144
-
145
- @pytest.fixture
146
- def pathTempTesting(request: pytest.FixtureRequest) -> pathlib.Path:
147
- """Create a unique temp directory for each test function."""
148
- # Sanitize test name for filesystem compatibility
149
- sanitizedName = request.node.name.replace('[', '_').replace(']', '_').replace('/', '_')
150
- uniqueDirectory = f"{sanitizedName}_{uuid.uuid4()}"
151
- pathTemp = pathTempRoot / uniqueDirectory
152
- pathTemp.mkdir(parents=True, exist_ok=True)
153
-
154
- addTempFileToRegister(pathTemp)
155
- return pathTemp
156
-
157
- @pytest.fixture
158
- def pathCacheTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
159
- """Temporarily replace the OEIS cache directory with a test directory."""
160
- from mapFolding import oeis as there_must_be_a_better_way
161
- pathCacheOriginal = there_must_be_a_better_way._pathCache
162
- there_must_be_a_better_way._pathCache = pathTempTesting
163
- yield pathTempTesting
164
- there_must_be_a_better_way._pathCache = pathCacheOriginal
165
-
166
- @pytest.fixture
167
- def pathFilenameBenchmarksTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
168
- """Temporarily replace the benchmarks directory with a test directory."""
169
- from mapFolding.benchmarks import benchmarking
170
- pathFilenameOriginal = benchmarking.pathFilenameRecordedBenchmarks
171
- pathFilenameTest = pathTempTesting / "benchmarks.npy"
172
- benchmarking.pathFilenameRecordedBenchmarks = pathFilenameTest
173
- yield pathFilenameTest
174
- benchmarking.pathFilenameRecordedBenchmarks = pathFilenameOriginal
175
-
176
- @pytest.fixture
177
- def pathFilenameFoldsTotalTesting(pathTempTesting: pathlib.Path) -> pathlib.Path:
178
- return pathTempTesting.joinpath("foldsTotalTest.txt")
179
-
180
- """
181
- Section: Fixtures"""
182
-
183
- @pytest.fixture
184
- def foldsTotalKnown() -> Dict[Tuple[int,...], int]:
185
- """Returns a dictionary mapping dimension tuples to their known folding totals.
186
- NOTE I am not convinced this is the best way to do this.
187
- Advantage: I call `makeDictionaryFoldsTotalKnown()` from modules other than test modules.
188
- Preference: I _think_ I would prefer a SSOT function available to any module
189
- similar to `foldsTotalKnown = getFoldsTotalKnown(listDimensions)`."""
190
- return makeDictionaryFoldsTotalKnown()
191
-
192
- @pytest.fixture
193
- def listDimensionsTestFunctionality(oeisID_1random: str) -> List[int]:
194
- """To test functionality, get one `listDimensions` from `valuesTestValidation` if
195
- `validateListDimensions` approves. The algorithm can count the folds of the returned
196
- `listDimensions` in a short enough time suitable for testing."""
197
- while True:
198
- n = random.choice(settingsOEIS[oeisID_1random]['valuesTestValidation'])
199
- if n < 2:
200
- continue
201
- listDimensionsCandidate = settingsOEIS[oeisID_1random]['getDimensions'](n)
202
-
203
- try:
204
- return validateListDimensions(listDimensionsCandidate)
205
- except (ValueError, NotImplementedError):
206
- pass
207
-
208
- @pytest.fixture
209
- def listDimensionsTest_countFolds(oeisID: str) -> List[int]:
210
- """For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestValidation`
211
- if `validateListDimensions` approves. Each `listDimensions` is suitable for testing counts."""
212
- while True:
213
- n = random.choice(settingsOEIS[oeisID]['valuesTestValidation'])
214
- if n < 2:
215
- continue
216
- listDimensionsCandidate = settingsOEIS[oeisID]['getDimensions'](n)
217
-
218
- try:
219
- return validateListDimensions(listDimensionsCandidate)
220
- except (ValueError, NotImplementedError):
221
- pass
222
-
223
- @pytest.fixture
224
- def mockBenchmarkTimer() -> Generator[unittest.mock.MagicMock | unittest.mock.AsyncMock, Any, None]:
225
- """Mock time.perf_counter_ns for consistent benchmark timing."""
226
- with unittest.mock.patch('time.perf_counter_ns') as mockTimer:
227
- mockTimer.side_effect = [0, 1e9] # Start and end times for 1 second
228
- yield mockTimer
229
-
230
- @pytest.fixture(params=oeisIDsImplemented)
231
- def oeisID(request: pytest.FixtureRequest)-> str:
232
- return request.param
233
-
234
- @pytest.fixture
235
- def oeisID_1random() -> str:
236
- """Return one random valid OEIS ID."""
237
- return random.choice(oeisIDsImplemented)
238
-
239
- @pytest.fixture
240
- def mockFoldingFunction():
241
- """Creates a mock function that simulates _countFolds behavior."""
242
- def make_mock(foldsValue: int, listDimensions: List[int]):
243
- arraySize = getLeavesTotal(listDimensions)
244
- # The array needs to sum to our target value
245
- mock_array = makeDataContainer(arraySize)
246
- mock_array[arraySize - 1] = foldsValue # Put entire value in last position
247
-
248
- def mock_countfolds(**keywordArguments):
249
- keywordArguments['foldsSubTotals'][:] = mock_array
250
- return None
251
-
252
- return mock_countfolds
253
- return make_mock
254
-
255
- """
256
- Section: Standardized test structures"""
257
-
258
- def formatTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
259
- """Format assertion message for any test comparison."""
260
- return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
261
- f"Expected: {expected}\n"
262
- f"Got: {actual}")
263
-
264
- def standardComparison(expected: Any, functionTarget: Callable, *arguments: Any) -> None:
265
- """Template for tests expecting an error."""
266
- if type(expected) == Type[Exception]:
267
- messageExpected = expected.__name__
268
- else:
269
- messageExpected = expected
270
-
271
- try:
272
- messageActual = actual = functionTarget(*arguments)
273
- except Exception as actualError:
274
- messageActual = type(actualError).__name__
275
- actual = type(actualError)
276
-
277
- assert actual == expected, formatTestMessage(messageExpected, messageActual, functionTarget.__name__, *arguments)
278
-
279
- def expectSystemExit(expected: Union[str, int, Sequence[int]], functionTarget: Callable, *arguments: Any) -> None:
280
- """Template for tests expecting SystemExit.
281
-
282
- Parameters
283
- expected: Exit code expectation:
284
- - "error": any non-zero exit code
285
- - "nonError": specifically zero exit code
286
- - int: exact exit code match
287
- - Sequence[int]: exit code must be one of these values
288
- functionTarget: The function to test
289
- arguments: Arguments to pass to the function
290
- """
291
- with pytest.raises(SystemExit) as exitInfo:
292
- functionTarget(*arguments)
293
-
294
- exitCode = exitInfo.value.code
295
-
296
- if expected == "error":
297
- assert exitCode != 0, \
298
- f"Expected error exit (non-zero) but got code {exitCode}"
299
- elif expected == "nonError":
300
- assert exitCode == 0, \
301
- f"Expected non-error exit (0) but got code {exitCode}"
302
- elif isinstance(expected, (list, tuple)):
303
- assert exitCode in expected, \
304
- f"Expected exit code to be one of {expected} but got {exitCode}"
305
- else:
306
- assert exitCode == expected, \
307
- f"Expected exit code {expected} but got {exitCode}"
308
-
309
- def standardCacheTest(
310
- expected: Any,
311
- setupCacheFile: Optional[Callable[[pathlib.Path, str], None]],
312
- oeisID: str,
313
- pathCache: pathlib.Path
314
- ) -> None:
315
- """Template for tests involving OEIS cache operations.
316
-
317
- Parameters
318
- expected: Expected value or exception from _getOEISidValues
319
- setupCacheFile: Function to prepare the cache file before test
320
- oeisID: OEIS ID to test
321
- pathCache: Temporary cache directory path
322
- """
323
- pathFilenameCache = pathCache / _getFilenameOEISbFile(oeisID)
324
-
325
- # Setup cache file if provided
326
- if setupCacheFile:
327
- setupCacheFile(pathFilenameCache, oeisID)
328
-
329
- # Run test
330
- try:
331
- actual = _getOEISidValues(oeisID)
332
- messageActual = actual
333
- except Exception as actualError:
334
- actual = type(actualError)
335
- messageActual = type(actualError).__name__
336
-
337
- # Compare results
338
- if isinstance(expected, type) and issubclass(expected, Exception):
339
- messageExpected = expected.__name__
340
- assert isinstance(actual, expected), formatTestMessage(
341
- messageExpected, messageActual, "_getOEISidValues", oeisID)
342
- else:
343
- messageExpected = expected
344
- assert actual == expected, formatTestMessage(
345
- 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)