mapFolding 0.2.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.
tests/test_oeis.py ADDED
@@ -0,0 +1,195 @@
1
+ from .conftest import *
2
+ from contextlib import redirect_stdout
3
+ from datetime import datetime, timedelta
4
+ import io
5
+ import os
6
+ import pathlib
7
+ import pytest
8
+ import random
9
+ import re as regex
10
+ import unittest
11
+ import unittest.mock
12
+ import urllib.error
13
+ import urllib.request
14
+
15
+ def test_aOFn_calculate_value(oeisID: str):
16
+ for n in settingsOEIS[oeisID]['valuesTestValidation']:
17
+ standardComparison(settingsOEIS[oeisID]['valuesKnown'][n], oeisIDfor_n, oeisID, n)
18
+
19
+ @pytest.mark.parametrize("badID", ["A999999", " A999999 ", "A999999extra"])
20
+ def test__validateOEISid_invalid_id(badID: str):
21
+ standardComparison(KeyError, _validateOEISid, badID)
22
+
23
+ def test__validateOEISid_partially_valid(oeisID_1random: str):
24
+ standardComparison(KeyError, _validateOEISid, f"{oeisID_1random}extra")
25
+
26
+ def test__validateOEISid_valid_id(oeisID: str):
27
+ standardComparison(oeisID, _validateOEISid, oeisID)
28
+
29
+ def test__validateOEISid_valid_id_case_insensitive(oeisID: str):
30
+ standardComparison(oeisID.upper(), _validateOEISid, oeisID.lower())
31
+ standardComparison(oeisID.upper(), _validateOEISid, oeisID.upper())
32
+ standardComparison(oeisID.upper(), _validateOEISid, oeisID.swapcase())
33
+
34
+ parameters_test_aOFn_invalid_n = [
35
+ # (2, "ok"), # test the test template
36
+ (-random.randint(1, 100), "randomNegative"),
37
+ ("foo", "string"),
38
+ (1.5, "float")
39
+ ]
40
+ badValues, badValuesIDs = zip(*parameters_test_aOFn_invalid_n)
41
+ @pytest.mark.parametrize("badN", badValues, ids=badValuesIDs)
42
+ def test_aOFn_invalid_n(oeisID_1random: str, badN):
43
+ """Check that negative or non-integer n raises ValueError."""
44
+ standardComparison(ValueError, oeisIDfor_n, oeisID_1random, badN)
45
+
46
+ def test_aOFn_zeroDim_A001418():
47
+ standardComparison(ArithmeticError, oeisIDfor_n, 'A001418', 0)
48
+
49
+ # ===== OEIS Cache Tests =====
50
+ @pytest.mark.parametrize("cacheExists", [True, False])
51
+ @unittest.mock.patch('pathlib.Path.exists')
52
+ @unittest.mock.patch('pathlib.Path.unlink')
53
+ def test_clearOEIScache(mock_unlink: unittest.mock.MagicMock, mock_exists: unittest.mock.MagicMock, cacheExists: bool):
54
+ """Test OEIS cache clearing with both existing and non-existing cache."""
55
+ mock_exists.return_value = cacheExists
56
+ clearOEIScache()
57
+
58
+ if cacheExists:
59
+ assert mock_unlink.call_count == len(settingsOEIS)
60
+ mock_unlink.assert_has_calls([unittest.mock.call(missing_ok=True)] * len(settingsOEIS))
61
+ else:
62
+ mock_exists.assert_called_once()
63
+ mock_unlink.assert_not_called()
64
+
65
+ def testCacheMiss(pathCacheTesting: pathlib.Path, oeisID_1random: str):
66
+ """Test cache miss scenario - cache file doesn't exist."""
67
+ # No setup function needed - we want to test missing cache
68
+ standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], None, oeisID_1random, pathCacheTesting)
69
+
70
+ def testCacheExpired(pathCacheTesting: pathlib.Path, oeisID_1random: str):
71
+ """Test expired cache scenario."""
72
+ def setupExpiredCache(pathCache: pathlib.Path, oeisID: str) -> None:
73
+ pathCache.write_text("# Old cache content")
74
+ oldModificationTime = datetime.now() - timedelta(days=30)
75
+ os.utime(pathCache, times=(oldModificationTime.timestamp(), oldModificationTime.timestamp()))
76
+
77
+ standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], setupExpiredCache, oeisID_1random, pathCacheTesting)
78
+
79
+ def testInvalidCache(pathCacheTesting: pathlib.Path, oeisID_1random: str):
80
+ """Test invalid cache content scenario."""
81
+ def setupInvalidCache(pathCache: pathlib.Path, oeisID: str) -> None:
82
+ pathCache.write_text("Invalid content")
83
+
84
+ standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], setupInvalidCache, oeisID_1random, pathCacheTesting)
85
+
86
+ def testInvalidFileContent(pathCacheTesting: pathlib.Path, oeisID_1random: str):
87
+ pathFilenameCache = pathCacheTesting / _formatFilenameCache.format(oeisID=oeisID_1random)
88
+
89
+ # Write invalid content to cache
90
+ pathFilenameCache.write_text("# A999999\n1 1\n2 2\n")
91
+ modificationTimeOriginal = pathFilenameCache.stat().st_mtime
92
+
93
+ # Function should detect invalid content, fetch fresh data, and update cache
94
+ OEISsequence = _getOEISidValues(oeisID_1random)
95
+
96
+ # Verify the function succeeded
97
+ assert OEISsequence is not None
98
+ # Verify cache was updated (modification time changed)
99
+ assert pathFilenameCache.stat().st_mtime > modificationTimeOriginal
100
+ # Verify cache now contains correct sequence ID
101
+ assert f"# {oeisID_1random}" in pathFilenameCache.read_text()
102
+
103
+ def testNetworkError(monkeypatch: pytest.MonkeyPatch, pathCacheTesting: pathlib.Path):
104
+ def mockUrlopen(*args, **kwargs):
105
+ raise urllib.error.URLError("Network error")
106
+
107
+ monkeypatch.setattr(urllib.request, 'urlopen', mockUrlopen)
108
+ with pytest.raises(urllib.error.URLError):
109
+ _getOEISidValues(next(iter(settingsOEIS)))
110
+
111
+ def testParseContentErrors():
112
+ """Test invalid content parsing."""
113
+ standardComparison(ValueError, _parseBFileOEIS, "Invalid content\n1 2\n", 'A001415')
114
+
115
+ def testExtraComments(pathCacheTesting: pathlib.Path, oeisID_1random: str):
116
+ pathFilenameCache = pathCacheTesting / _formatFilenameCache.format(oeisID=oeisID_1random)
117
+
118
+ # Write content with extra comment lines
119
+ contentWithExtraComments = f"""# {oeisID_1random}
120
+ # Extra comment line 1
121
+ # Extra comment line 2
122
+ 1 2
123
+ 2 4
124
+ 3 6
125
+ # Another comment in the middle
126
+ 4 8
127
+ 5 10"""
128
+ pathFilenameCache.write_text(contentWithExtraComments)
129
+
130
+ OEISsequence = _getOEISidValues(oeisID_1random)
131
+ # Verify sequence values are correct despite extra comments
132
+ standardComparison(2, lambda d: d[1], OEISsequence) # First value
133
+ standardComparison(8, lambda d: d[4], OEISsequence) # Value after mid-sequence comment
134
+ standardComparison(10, lambda d: d[5], OEISsequence) # Last value
135
+
136
+ # ===== Command Line Interface Tests =====
137
+ def testHelpText():
138
+ """Test that help text is complete and examples are valid."""
139
+ outputStream = io.StringIO()
140
+ with redirect_stdout(outputStream):
141
+ getOEISids()
142
+
143
+ helpText = outputStream.getvalue()
144
+
145
+ # Verify content
146
+ for oeisID in oeisIDsImplemented:
147
+ assert oeisID in helpText
148
+ assert settingsOEIS[oeisID]['description'] in helpText
149
+
150
+ # Extract and verify examples
151
+
152
+ cliMatch = regex.search(r'OEIS_for_n (\w+) (\d+)', helpText)
153
+ pythonMatch = regex.search(r"oeisIDfor_n\('(\w+)', (\d+)\)", helpText)
154
+
155
+ assert cliMatch and pythonMatch, "Help text missing examples"
156
+ oeisID, n = pythonMatch.groups()
157
+ n = int(n)
158
+
159
+ # Verify CLI and Python examples use same values
160
+ assert cliMatch.groups() == (oeisID, str(n)), "CLI and Python examples inconsistent"
161
+
162
+ # Verify the example works
163
+ expectedValue = oeisIDfor_n(oeisID, n)
164
+
165
+ # Test CLI execution of the example
166
+ with unittest.mock.patch('sys.argv', ['OEIS_for_n', oeisID, str(n)]):
167
+ outputStream = io.StringIO()
168
+ with redirect_stdout(outputStream):
169
+ OEIS_for_n()
170
+ standardComparison(expectedValue, lambda: int(outputStream.getvalue().strip().split()[0]))
171
+
172
+ def testCLI_InvalidInputs():
173
+ """Test CLI error handling."""
174
+ testCases = [
175
+ (['OEIS_for_n'], "missing arguments"),
176
+ (['OEIS_for_n', 'A999999', '1'], "invalid OEIS ID"),
177
+ (['OEIS_for_n', 'A001415', '-1'], "negative n"),
178
+ (['OEIS_for_n', 'A001415', 'abc'], "non-integer n"),
179
+ ]
180
+
181
+ for arguments, testID in testCases:
182
+ with unittest.mock.patch('sys.argv', arguments):
183
+ expectSystemExit("error", OEIS_for_n)
184
+
185
+ def testCLI_HelpFlag():
186
+ """Verify --help output contains required information."""
187
+ with unittest.mock.patch('sys.argv', ['OEIS_for_n', '--help']):
188
+ outputStream = io.StringIO()
189
+ with redirect_stdout(outputStream):
190
+ expectSystemExit("nonError", OEIS_for_n)
191
+
192
+ helpOutput = outputStream.getvalue()
193
+ assert "Available OEIS sequences:" in helpOutput
194
+ assert "Usage examples:" in helpOutput
195
+ assert all(oeisID in helpOutput for oeisID in oeisIDsImplemented)
tests/test_other.py ADDED
@@ -0,0 +1,71 @@
1
+ from .conftest import *
2
+ import pytest
3
+ import sys
4
+ # TODO test `outfitFoldings`, especially that `listDimensions` is sorted.
5
+
6
+ @pytest.mark.parametrize("listDimensions,expected_intInnit,expected_parseListDimensions,expected_validateListDimensions,expected_getLeavesTotal", [
7
+ (None, ValueError, ValueError, ValueError, ValueError), # None instead of list
8
+ (['a'], ValueError, ValueError, ValueError, ValueError), # string
9
+ ([-4, 2], [-4, 2], ValueError, ValueError, ValueError), # negative
10
+ ([-3], [-3], ValueError, ValueError, ValueError), # negative
11
+ ([0, 0], [0, 0], [0, 0], NotImplementedError, 0), # no positive dimensions
12
+ ([0, 5, 6], [0, 5, 6], [0, 5, 6], [5, 6], 30), # zeros ignored
13
+ ([0], [0], [0], NotImplementedError, 0), # edge case
14
+ ([1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], 120), # sequential
15
+ ([1, sys.maxsize], [1, sys.maxsize], [1, sys.maxsize], [1, sys.maxsize], sys.maxsize), # maxint
16
+ ([7.5], ValueError, ValueError, ValueError, ValueError), # float
17
+ ([1] * 1000, [1] * 1000, [1] * 1000, [1] * 1000, 1), # long list
18
+ ([11], [11], [11], NotImplementedError, 11), # single dimension
19
+ ([13, 0, 17], [13, 0, 17], [13, 0, 17], [13, 17], 221), # zeros handled
20
+ ([2, 2, 2, 2], [2, 2, 2, 2], [2, 2, 2, 2], [2, 2, 2, 2], 16), # repeated dimensions
21
+ ([2, 3, 4], [2, 3, 4], [2, 3, 4], [2, 3, 4], 24),
22
+ ([2, 3], [2, 3], [2, 3], [2, 3], 6),
23
+ ([2] * 11, [2] * 11, [2] * 11, [2] * 11, 2048), # power of 2
24
+ ([3, 2], [3, 2], [3, 2], [3, 2], 6), # return value is the input when valid
25
+ ([3] * 5, [3] * 5, [3] * 5, [3, 3, 3, 3, 3], 243), # power of 3
26
+ ([None], TypeError, TypeError, TypeError, TypeError), # None
27
+ ([True], TypeError, TypeError, TypeError, TypeError), # bool
28
+ ([[17, 39]], TypeError, TypeError, TypeError, TypeError), # nested
29
+ ([], ValueError, ValueError, ValueError, ValueError), # empty
30
+ ([complex(1,1)], ValueError, ValueError, ValueError, ValueError), # complex number
31
+ ([float('inf')], ValueError, ValueError, ValueError, ValueError), # infinity
32
+ ([float('nan')], ValueError, ValueError, ValueError, ValueError), # NaN
33
+ ([sys.maxsize - 1, 1], [sys.maxsize - 1, 1], [sys.maxsize - 1, 1], [sys.maxsize - 1, 1], sys.maxsize - 1), # near maxint
34
+ ([sys.maxsize // 2, sys.maxsize // 2, 2], [sys.maxsize // 2, sys.maxsize // 2, 2], [sys.maxsize // 2, sys.maxsize // 2, 2], [sys.maxsize // 2, sys.maxsize // 2, 2], OverflowError), # overflow protection
35
+ ([sys.maxsize, sys.maxsize], [sys.maxsize, sys.maxsize], [sys.maxsize, sys.maxsize], [sys.maxsize, sys.maxsize], OverflowError), # overflow protection
36
+ (range(3, 7), [3, 4, 5, 6], [3, 4, 5, 6], [3, 4, 5, 6], 360), # range sequence type
37
+ (tuple([3, 5, 7]), [3, 5, 7], [3, 5, 7], [3, 5, 7], 105), # tuple sequence type
38
+ ])
39
+ def test_listDimensionsAsParameter(listDimensions: None | list[str] | list[int] | list[float] | list[None] | list[bool] | list[list[int]] | list[complex] | range | tuple[int, ...], expected_intInnit: type[ValueError] | list[int] | type[TypeError], expected_parseListDimensions: type[ValueError] | list[int] | type[TypeError], expected_validateListDimensions: type[ValueError] | type[NotImplementedError] | list[int] | type[TypeError], expected_getLeavesTotal: type[ValueError] | int | type[TypeError] | type[OverflowError]) -> None:
40
+ """Test both validateListDimensions and getLeavesTotal with the same inputs."""
41
+ standardComparison(expected_intInnit, intInnit, listDimensions)
42
+ standardComparison(expected_parseListDimensions, parseDimensions, listDimensions)
43
+ standardComparison(expected_validateListDimensions, validateListDimensions, listDimensions)
44
+ standardComparison(expected_getLeavesTotal, getLeavesTotal, listDimensions)
45
+
46
+ def test_getLeavesTotal_edge_cases() -> None:
47
+ """Test edge cases for getLeavesTotal."""
48
+ # Order independence
49
+ standardComparison(getLeavesTotal([2, 3, 4]), getLeavesTotal, [4, 2, 3])
50
+
51
+ # Immutability
52
+ listOriginal = [2, 3]
53
+ standardComparison(6, getLeavesTotal, listOriginal)
54
+ standardComparison([2, 3], lambda x: x, listOriginal) # Check that the list wasn't modified
55
+
56
+ # ===== Parse Integers Tests =====
57
+ def test_intInnit() -> None:
58
+ """Test integer parsing using the test suite generator."""
59
+ for testName, testFunction in makeTestSuiteIntInnit(intInnit).items():
60
+ testFunction()
61
+
62
+ def test_oopsieKwargsie() -> None:
63
+ """Test handling of unexpected keyword arguments."""
64
+ for testName, testFunction in makeTestSuiteOopsieKwargsie(oopsieKwargsie).items():
65
+ testFunction()
66
+
67
+ def test_countFolds_invalid_computationDivisions() -> None:
68
+ standardComparison(ValueError, countFolds, [2, 2], {"wrong": "value"})
69
+
70
+ def test_parseListDimensions_noDimensions() -> None:
71
+ standardComparison(ValueError, parseDimensions, [])
tests/test_tasks.py ADDED
@@ -0,0 +1,18 @@
1
+ from .conftest import *
2
+ import pytest
3
+ from typing import List, Dict, Tuple
4
+
5
+ # TODO add a test. `C` = number of logical cores available. `n = C + 1`. Ensure that `[2,n]` is computed correctly.
6
+
7
+ def test_foldings_computationDivisions(listDimensionsTest_countFolds: List[int], foldsTotalKnown: Dict[Tuple[int, ...], int]) -> None:
8
+ standardComparison(foldsTotalKnown[tuple(listDimensionsTest_countFolds)], countFolds, listDimensionsTest_countFolds, True)
9
+
10
+ def test_defineConcurrencyLimit() -> None:
11
+ testSuite = makeTestSuiteConcurrencyLimit(defineConcurrencyLimit)
12
+ for testName, testFunction in testSuite.items():
13
+ testFunction()
14
+
15
+ @pytest.mark.parametrize("cpuLimitValue", [{"invalid": True}, ["weird"]])
16
+ def test_countFolds_cpuLimitOopsie(cpuLimitValue: Dict[str, bool] | List[str]) -> None:
17
+ # This forces CPUlimit = oopsieKwargsie(cpuLimitValue).
18
+ standardComparison(ValueError, countFolds, [2, 2], True, cpuLimitValue)