StructResult 0.9.0__tar.gz → 0.9.2__tar.gz
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.
- {structresult-0.9.0 → structresult-0.9.2}/PKG-INFO +1 -1
- {structresult-0.9.0 → structresult-0.9.2}/pyproject.toml +1 -1
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult/result.py +45 -15
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult.egg-info/PKG-INFO +1 -1
- structresult-0.9.2/test/test_Sequence.py +486 -0
- {structresult-0.9.0 → structresult-0.9.2}/test/test_result.py +3 -3
- structresult-0.9.0/test/test_Sequence.py +0 -251
- {structresult-0.9.0 → structresult-0.9.2}/README.md +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/setup.cfg +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult/__init__.py +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult/formatter.py +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult/py.typed +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult.egg-info/SOURCES.txt +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult.egg-info/dependency_links.txt +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult.egg-info/requires.txt +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/src/StructResult.egg-info/top_level.txt +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/test/benchmark.py +0 -0
- {structresult-0.9.0 → structresult-0.9.2}/test/test_formatter.py +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
|
-
from typing import Optional, Self, Protocol, Iterator, Any, Never
|
|
3
|
-
|
|
2
|
+
from typing import Optional, Self, Protocol, Iterator, Any, Never, ClassVar, Final, TypeVar, overload
|
|
4
3
|
"""
|
|
5
4
|
Functional error handling system with:
|
|
6
5
|
- Result composition
|
|
@@ -15,6 +14,9 @@ Core concepts:
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class Result(Protocol):
|
|
17
|
+
value: Any
|
|
18
|
+
err: Optional[ExceptionGroup]
|
|
19
|
+
|
|
18
20
|
"""Protocol for operation results"""
|
|
19
21
|
def is_ok(self) -> bool:
|
|
20
22
|
"""Returns True if successful (no errors)"""
|
|
@@ -24,6 +26,7 @@ class Result(Protocol):
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class Ok(Result):
|
|
29
|
+
|
|
27
30
|
"""Singleton success marker without value"""
|
|
28
31
|
def is_ok(self) -> bool:
|
|
29
32
|
return True
|
|
@@ -39,12 +42,18 @@ class Ok(Result):
|
|
|
39
42
|
"""Always return OK"""
|
|
40
43
|
return OK
|
|
41
44
|
|
|
45
|
+
@property
|
|
46
|
+
def err(self) -> None: # type: ignore[override]
|
|
47
|
+
return None
|
|
48
|
+
|
|
42
49
|
|
|
43
50
|
OK = Ok()
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
class Null:
|
|
47
54
|
"""Non value result marker"""
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
return "NULL"
|
|
48
57
|
|
|
49
58
|
|
|
50
59
|
NULL = Null()
|
|
@@ -81,7 +90,7 @@ class ErrorPropagator(Result, Protocol):
|
|
|
81
90
|
self.err = ExceptionGroup(err.message, (*self.err.exceptions, err))
|
|
82
91
|
return self
|
|
83
92
|
|
|
84
|
-
def propagate_err[T](self, res: "Collector[T] | ErrorAccumulator") -> T | Null:
|
|
93
|
+
def propagate_err[T](self, res: "Collector[T] | ErrorAccumulator | Ok") -> T | Null | Ok:
|
|
85
94
|
"""Merges errors from another result and returns its value:
|
|
86
95
|
1. If res has errors - merges them into current
|
|
87
96
|
2. Returns res's value (if exists)
|
|
@@ -110,6 +119,10 @@ class Error(ErrorPropagator):
|
|
|
110
119
|
"""Always raises exception"""
|
|
111
120
|
raise self.err
|
|
112
121
|
|
|
122
|
+
@property
|
|
123
|
+
def value(self) -> Null:
|
|
124
|
+
return NULL
|
|
125
|
+
|
|
113
126
|
|
|
114
127
|
@dataclass(slots=True)
|
|
115
128
|
class StrictOk(ErrorPropagator):
|
|
@@ -207,8 +220,8 @@ class Collector[T](ErrorPropagator, Protocol):
|
|
|
207
220
|
"""Protocol for value containers with error handling"""
|
|
208
221
|
value: T
|
|
209
222
|
|
|
210
|
-
def
|
|
211
|
-
return
|
|
223
|
+
def unpack(self) -> tuple[T, Optional[ExceptionGroup]]:
|
|
224
|
+
return self.value, self.err
|
|
212
225
|
|
|
213
226
|
def unwrap(self) -> T:
|
|
214
227
|
"""Returns value or raises exception if errors exist"""
|
|
@@ -245,26 +258,20 @@ class Option[T](Simple[Optional[T]], Result):
|
|
|
245
258
|
|
|
246
259
|
|
|
247
260
|
@dataclass(slots=True)
|
|
248
|
-
class List[T](Collector[list[Optional[T | Ok]]], Result):
|
|
261
|
+
class List[T](Collector[list[Optional[T | Ok | Null]]], Result):
|
|
249
262
|
"""List collector with error accumulation"""
|
|
250
|
-
value: list[Optional[T] | Ok] = field(init=False, default_factory=list)
|
|
263
|
+
value: list[Optional[T] | Ok | Null] = field(init=False, default_factory=list)
|
|
251
264
|
err: Optional[ExceptionGroup] = field(init=False, default=None)
|
|
252
265
|
|
|
253
|
-
def append(self, res:
|
|
266
|
+
def append(self, res: Option[T] | Simple[T] | Error | Ok) -> None:
|
|
254
267
|
"""Appends result with rules:
|
|
255
268
|
- For OK: adds OK marker
|
|
256
269
|
- For Error: adds None and merges errors
|
|
257
270
|
- For Collector: adds value and merges errors
|
|
258
271
|
"""
|
|
259
|
-
if isinstance(res, Ok):
|
|
260
|
-
self.value.append(OK)
|
|
261
|
-
return
|
|
262
272
|
if res.err is not None:
|
|
263
273
|
self.append_err(res.err)
|
|
264
|
-
|
|
265
|
-
self.value.append(None)
|
|
266
|
-
else:
|
|
267
|
-
self.value.append(res.value)
|
|
274
|
+
self.value.append(res.value)
|
|
268
275
|
|
|
269
276
|
def __add__(self, other: Option[T] | Simple[T] | Error | Ok) -> Self:
|
|
270
277
|
self.append(other)
|
|
@@ -274,6 +281,9 @@ class List[T](Collector[list[Optional[T | Ok]]], Result):
|
|
|
274
281
|
type SimpleOrError[T: Any] = Simple[T] | Error
|
|
275
282
|
|
|
276
283
|
|
|
284
|
+
T1 = TypeVar('T1')
|
|
285
|
+
|
|
286
|
+
|
|
277
287
|
class Sequence[*Ts](Collector[tuple[*Ts]], Result):
|
|
278
288
|
"""
|
|
279
289
|
A strictly-typed heterogeneous sequence container with error handling capabilities.
|
|
@@ -299,6 +309,26 @@ class Sequence[*Ts](Collector[tuple[*Ts]], Result):
|
|
|
299
309
|
self.value = values
|
|
300
310
|
self.err = err
|
|
301
311
|
|
|
312
|
+
@overload
|
|
313
|
+
def add(self, res: Collector[T1]) -> "Sequence[*Ts, T1]": ...
|
|
314
|
+
|
|
315
|
+
@overload
|
|
316
|
+
def add(self, res: Error) -> "Sequence[*Ts, Null]": ...
|
|
317
|
+
|
|
318
|
+
@overload
|
|
319
|
+
def add(self, res: Ok) -> "Sequence[*Ts, Ok]": ...
|
|
320
|
+
|
|
321
|
+
def add(self, res: Collector[T1] | Error | Ok) -> "Sequence[*Ts, Any]":
|
|
322
|
+
"""Basic adding method"""
|
|
323
|
+
new_value = self.value + (res.value,)
|
|
324
|
+
new = Sequence(*new_value, err=self.err)
|
|
325
|
+
if res.err is not None:
|
|
326
|
+
new.append_err(res.err)
|
|
327
|
+
return new
|
|
328
|
+
|
|
329
|
+
def __str__(self) -> str:
|
|
330
|
+
return f"({", ".join(map(str, self.value))}){"" if not self.err else str(self.err)}"
|
|
331
|
+
|
|
302
332
|
|
|
303
333
|
__all__ = [
|
|
304
334
|
"SimpleOrError"
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from src.StructResult.result import Sequence, Option, Simple, OK, Error, NULL
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestSequence(unittest.TestCase):
|
|
6
|
+
"""Tests for Sequence class"""
|
|
7
|
+
|
|
8
|
+
def test_basic_creation(self) -> None:
|
|
9
|
+
"""Test basic sequence creation"""
|
|
10
|
+
seq = Sequence(1, "hello", True)
|
|
11
|
+
self.assertEqual(seq.value, (1, "hello", True))
|
|
12
|
+
self.assertIsNone(seq.err)
|
|
13
|
+
self.assertTrue(seq.is_ok())
|
|
14
|
+
|
|
15
|
+
def test_type_annotations(self) -> None:
|
|
16
|
+
"""Test strict type annotations"""
|
|
17
|
+
seq_int_str: Sequence[int, str] = Sequence(1, "test")
|
|
18
|
+
seq_mixed: Sequence[int, str, bool] = Sequence(1, "hello", True)
|
|
19
|
+
|
|
20
|
+
# Type checking should work
|
|
21
|
+
first: int = seq_int_str.value[0]
|
|
22
|
+
second: str = seq_int_str.value[1]
|
|
23
|
+
|
|
24
|
+
self.assertEqual(first, 1)
|
|
25
|
+
self.assertEqual(second, "test")
|
|
26
|
+
|
|
27
|
+
def test_empty_sequence(self) -> None:
|
|
28
|
+
"""Test empty sequence creation"""
|
|
29
|
+
seq = Sequence()
|
|
30
|
+
self.assertEqual(seq.value, ())
|
|
31
|
+
self.assertIsNone(seq.err)
|
|
32
|
+
self.assertTrue(seq.is_ok())
|
|
33
|
+
|
|
34
|
+
def test_with_errors(self) -> None:
|
|
35
|
+
"""Test sequence with errors"""
|
|
36
|
+
error = ExceptionGroup("test", [ValueError("error1")])
|
|
37
|
+
seq = Sequence(1, "test", err=error)
|
|
38
|
+
|
|
39
|
+
self.assertEqual(seq.value, (1, "test"))
|
|
40
|
+
self.assertIs(seq.err, error)
|
|
41
|
+
self.assertFalse(seq.is_ok())
|
|
42
|
+
|
|
43
|
+
def test_unwrap_success(self) -> None:
|
|
44
|
+
"""Test successful unwrap"""
|
|
45
|
+
seq = Sequence(1, "hello", 3.14)
|
|
46
|
+
result = seq.unwrap()
|
|
47
|
+
|
|
48
|
+
self.assertEqual(result, (1, "hello", 3.14))
|
|
49
|
+
self.assertIsInstance(result, tuple)
|
|
50
|
+
|
|
51
|
+
def test_unwrap_failure(self) -> None:
|
|
52
|
+
"""Test unwrap with errors"""
|
|
53
|
+
error = ExceptionGroup("test", [ValueError("error1")])
|
|
54
|
+
seq = Sequence(1, "test", err=error)
|
|
55
|
+
|
|
56
|
+
with self.assertRaises(ExceptionGroup) as context:
|
|
57
|
+
seq.unwrap()
|
|
58
|
+
|
|
59
|
+
self.assertIs(context.exception, error)
|
|
60
|
+
|
|
61
|
+
def test_err_propagation(self) -> None:
|
|
62
|
+
"""Test error propagation compatibility"""
|
|
63
|
+
seq = Sequence(1, "test")
|
|
64
|
+
|
|
65
|
+
# Should work with ErrorPropagator protocol
|
|
66
|
+
self.assertTrue(hasattr(seq, 'append_e'))
|
|
67
|
+
self.assertTrue(hasattr(seq, 'append_err'))
|
|
68
|
+
self.assertTrue(hasattr(seq, 'propagate_err'))
|
|
69
|
+
|
|
70
|
+
def test_collector_protocol(self) -> None:
|
|
71
|
+
"""Test Collector protocol compliance"""
|
|
72
|
+
seq = Sequence(1, "hello")
|
|
73
|
+
|
|
74
|
+
# Should support iteration
|
|
75
|
+
values, err = seq.unpack()
|
|
76
|
+
self.assertEqual(values, (1, "hello"))
|
|
77
|
+
self.assertIsNone(err)
|
|
78
|
+
|
|
79
|
+
# Should have value attribute
|
|
80
|
+
self.assertTrue(hasattr(seq, 'value'))
|
|
81
|
+
self.assertTrue(hasattr(seq, 'err'))
|
|
82
|
+
|
|
83
|
+
def test_result_protocol(self) -> None:
|
|
84
|
+
"""Test Result protocol compliance"""
|
|
85
|
+
seq = Sequence(1, "test")
|
|
86
|
+
|
|
87
|
+
self.assertTrue(hasattr(seq, 'is_ok'))
|
|
88
|
+
self.assertTrue(hasattr(seq, 'unwrap'))
|
|
89
|
+
self.assertTrue(seq.is_ok())
|
|
90
|
+
|
|
91
|
+
def test_complex_types(self) -> None:
|
|
92
|
+
"""Test sequences with complex types"""
|
|
93
|
+
# Nested sequences
|
|
94
|
+
inner_seq = Sequence("nested")
|
|
95
|
+
outer_seq = Sequence(1, inner_seq, True)
|
|
96
|
+
|
|
97
|
+
self.assertEqual(outer_seq.value[0], 1)
|
|
98
|
+
self.assertEqual(outer_seq.value[1].value, ("nested",))
|
|
99
|
+
self.assertEqual(outer_seq.value[2], True)
|
|
100
|
+
|
|
101
|
+
def test_equality(self) -> None:
|
|
102
|
+
"""Test sequence equality"""
|
|
103
|
+
seq1 = Sequence(1, "test")
|
|
104
|
+
seq2 = Sequence(1, "test")
|
|
105
|
+
seq3 = Sequence(1, "different")
|
|
106
|
+
|
|
107
|
+
# Different instances with same values
|
|
108
|
+
self.assertEqual(seq1.value, seq2.value)
|
|
109
|
+
self.assertNotEqual(seq1.value, seq3.value)
|
|
110
|
+
|
|
111
|
+
def test_len_behavior(self) -> None:
|
|
112
|
+
"""Test sequence length behavior"""
|
|
113
|
+
seq_single = Sequence(1)
|
|
114
|
+
seq_multi = Sequence(1, "two", 3.0)
|
|
115
|
+
|
|
116
|
+
self.assertEqual(len(seq_single.value), 1)
|
|
117
|
+
self.assertEqual(len(seq_multi.value), 3)
|
|
118
|
+
|
|
119
|
+
def test_pattern_matching(self) -> None:
|
|
120
|
+
"""Test sequence pattern matching support"""
|
|
121
|
+
seq = Sequence(1, "hello", True)
|
|
122
|
+
|
|
123
|
+
# Basic unpacking
|
|
124
|
+
a, b, c = seq.value
|
|
125
|
+
self.assertEqual(a, 1)
|
|
126
|
+
self.assertEqual(b, "hello")
|
|
127
|
+
self.assertEqual(c, True)
|
|
128
|
+
|
|
129
|
+
def test_error_accumulation(self) -> None:
|
|
130
|
+
"""Test error accumulation functionality"""
|
|
131
|
+
seq = Sequence(1, "test")
|
|
132
|
+
|
|
133
|
+
# Add first error
|
|
134
|
+
seq.append_e(ValueError("first error"), "context1")
|
|
135
|
+
self.assertIsNotNone(seq.err)
|
|
136
|
+
if seq.err is not None:
|
|
137
|
+
self.assertEqual(len(seq.err.exceptions), 1)
|
|
138
|
+
|
|
139
|
+
# Add second error with same message (should merge)
|
|
140
|
+
seq.append_e(TypeError("second error"), "context1")
|
|
141
|
+
if seq.err is not None:
|
|
142
|
+
self.assertEqual(len(seq.err.exceptions), 2)
|
|
143
|
+
|
|
144
|
+
# Add error with different message (should nest)
|
|
145
|
+
seq.append_e(RuntimeError("third error"), "context2")
|
|
146
|
+
if seq.err is not None:
|
|
147
|
+
self.assertEqual(seq.err.message, "context2")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestSequenceIntegration(unittest.TestCase):
|
|
151
|
+
"""Integration tests with other system components"""
|
|
152
|
+
|
|
153
|
+
def test_with_simple_collector(self) -> None:
|
|
154
|
+
"""Test integration with Simple collector"""
|
|
155
|
+
simple = Simple(42)
|
|
156
|
+
seq = Sequence(simple.value, "additional")
|
|
157
|
+
|
|
158
|
+
self.assertEqual(seq.value, (42, "additional"))
|
|
159
|
+
self.assertTrue(seq.is_ok())
|
|
160
|
+
|
|
161
|
+
def test_with_option_collector(self) -> None:
|
|
162
|
+
"""Test integration with Option collector"""
|
|
163
|
+
option = Option(100) # Assuming Option can be created with value
|
|
164
|
+
seq = Sequence(option.value, "test")
|
|
165
|
+
|
|
166
|
+
self.assertEqual(seq.value[0], 100)
|
|
167
|
+
self.assertEqual(seq.value[1], "test")
|
|
168
|
+
|
|
169
|
+
def test_error_propagation_from_other_collectors(self) -> None:
|
|
170
|
+
"""Test error propagation from other collectors"""
|
|
171
|
+
# Create a collector with error
|
|
172
|
+
error_collector = Simple(100)
|
|
173
|
+
error_collector.append_e(ValueError("source error"))
|
|
174
|
+
|
|
175
|
+
seq = Sequence("start")
|
|
176
|
+
result = seq.propagate_err(error_collector)
|
|
177
|
+
|
|
178
|
+
self.assertEqual(result, 100) # Should return the value
|
|
179
|
+
self.assertIsNotNone(seq.err) # But errors should be propagated
|
|
180
|
+
if seq.err is not None:
|
|
181
|
+
self.assertEqual(len(seq.err.exceptions), 1)
|
|
182
|
+
|
|
183
|
+
def test_with_ok_singleton(self) -> None:
|
|
184
|
+
"""Test integration with OK singleton"""
|
|
185
|
+
seq = Sequence(OK, "regular value")
|
|
186
|
+
|
|
187
|
+
self.assertIs(seq.value[0], OK)
|
|
188
|
+
self.assertEqual(seq.value[1], "regular value")
|
|
189
|
+
self.assertTrue(seq.is_ok())
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class TestSequenceEdgeCases(unittest.TestCase):
|
|
193
|
+
"""Edge case tests for Sequence"""
|
|
194
|
+
|
|
195
|
+
def test_none_values(self) -> None:
|
|
196
|
+
"""Test sequences with None values"""
|
|
197
|
+
seq = Sequence(None, "test", None)
|
|
198
|
+
self.assertEqual(seq.value, (None, "test", None))
|
|
199
|
+
self.assertEqual(seq.unwrap(), (None, "test", None))
|
|
200
|
+
|
|
201
|
+
def test_large_sequences(self) -> None:
|
|
202
|
+
"""Test sequences with many elements"""
|
|
203
|
+
large_tuple = tuple(range(100))
|
|
204
|
+
seq = Sequence(*large_tuple)
|
|
205
|
+
self.assertEqual(seq.value, large_tuple)
|
|
206
|
+
self.assertEqual(len(seq.value), 100)
|
|
207
|
+
|
|
208
|
+
def test_nested_tuples(self) -> None:
|
|
209
|
+
"""Test sequences containing nested tuples"""
|
|
210
|
+
nested = (1, 2, 3)
|
|
211
|
+
seq = Sequence(nested, "test")
|
|
212
|
+
self.assertEqual(seq.value[0], nested)
|
|
213
|
+
self.assertEqual(seq.value[1], "test")
|
|
214
|
+
|
|
215
|
+
def test_serialization(self) -> None:
|
|
216
|
+
"""Test basic serialization properties"""
|
|
217
|
+
seq = Sequence(1, "test")
|
|
218
|
+
|
|
219
|
+
# Should be picklable
|
|
220
|
+
import pickle
|
|
221
|
+
pickled = pickle.dumps(seq)
|
|
222
|
+
unpickled = pickle.loads(pickled)
|
|
223
|
+
|
|
224
|
+
self.assertEqual(unpickled.value, seq.value)
|
|
225
|
+
self.assertEqual(unpickled.err, seq.err)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestSequenceAdd(unittest.TestCase):
|
|
229
|
+
"""Tests for Sequence.add method"""
|
|
230
|
+
|
|
231
|
+
def test_add_simple_value(self) -> None:
|
|
232
|
+
"""Test adding Simple value to sequence"""
|
|
233
|
+
seq1: Sequence[int, str] = Sequence(1, "hello")
|
|
234
|
+
simple_val = Simple(3.14)
|
|
235
|
+
|
|
236
|
+
seq2 = seq1.add(simple_val)
|
|
237
|
+
|
|
238
|
+
self.assertEqual(seq2.value, (1, "hello", 3.14))
|
|
239
|
+
self.assertIsNone(seq2.err)
|
|
240
|
+
self.assertTrue(seq2.is_ok())
|
|
241
|
+
|
|
242
|
+
def test_add_ok_singleton(self) -> None:
|
|
243
|
+
"""Test adding OK singleton to sequence"""
|
|
244
|
+
seq1: Sequence[int, str] = Sequence(1, "hello")
|
|
245
|
+
|
|
246
|
+
seq2 = seq1.add(OK)
|
|
247
|
+
|
|
248
|
+
self.assertEqual(seq2.value, (1, "hello", OK))
|
|
249
|
+
self.assertIsNone(seq2.err)
|
|
250
|
+
self.assertTrue(seq2.is_ok())
|
|
251
|
+
|
|
252
|
+
def test_add_error(self) -> None:
|
|
253
|
+
"""Test adding Error to sequence"""
|
|
254
|
+
seq1: Sequence[int, str] = Sequence(1, "hello")
|
|
255
|
+
error = Error.from_e(ValueError("test error"))
|
|
256
|
+
|
|
257
|
+
seq2 = seq1.add(error)
|
|
258
|
+
|
|
259
|
+
# Value should include Null for error
|
|
260
|
+
self.assertEqual(seq2.value, (1, "hello", NULL))
|
|
261
|
+
self.assertIsNotNone(seq2.err)
|
|
262
|
+
self.assertFalse(seq2.is_ok())
|
|
263
|
+
if seq2.err:
|
|
264
|
+
self.assertEqual(len(seq2.err.exceptions), 1)
|
|
265
|
+
|
|
266
|
+
def test_add_preserves_existing_errors(self) -> None:
|
|
267
|
+
"""Test that add preserves errors from original sequence"""
|
|
268
|
+
seq1: Sequence[int, str] = Sequence(1, "hello",
|
|
269
|
+
err=ExceptionGroup("original", [ValueError("original error")]))
|
|
270
|
+
simple_val = Simple(3.14)
|
|
271
|
+
|
|
272
|
+
seq2 = seq1.add(simple_val)
|
|
273
|
+
|
|
274
|
+
self.assertEqual(seq2.value, (1, "hello", 3.14))
|
|
275
|
+
self.assertIsNotNone(seq2.err)
|
|
276
|
+
if seq2.err:
|
|
277
|
+
self.assertEqual(seq2.err.message, "original")
|
|
278
|
+
self.assertEqual(len(seq2.err.exceptions), 1)
|
|
279
|
+
|
|
280
|
+
def test_add_merges_errors_from_both_sides(self) -> None:
|
|
281
|
+
"""Test that errors from both original and added result are merged"""
|
|
282
|
+
seq1: Sequence[int, str] = Sequence(1, "hello",
|
|
283
|
+
err=ExceptionGroup("original", [ValueError("error1")]))
|
|
284
|
+
error_val = Error.from_e(TypeError("error2"), "added")
|
|
285
|
+
|
|
286
|
+
seq2 = seq1.add(error_val)
|
|
287
|
+
|
|
288
|
+
self.assertEqual(seq2.value, (1, "hello", NULL))
|
|
289
|
+
self.assertIsNotNone(seq2.err)
|
|
290
|
+
# Errors should be merged with proper nesting
|
|
291
|
+
if seq2.err:
|
|
292
|
+
self.assertIsNotNone(seq2.err.subgroup(TypeError))
|
|
293
|
+
|
|
294
|
+
def test_add_to_empty_sequence(self) -> None:
|
|
295
|
+
"""Test adding to empty sequence"""
|
|
296
|
+
seq1 = Sequence()
|
|
297
|
+
simple_val = Simple("test")
|
|
298
|
+
|
|
299
|
+
seq2 = seq1.add(simple_val)
|
|
300
|
+
|
|
301
|
+
self.assertEqual(seq2.value, ("test",))
|
|
302
|
+
self.assertIsNone(seq2.err)
|
|
303
|
+
self.assertTrue(seq2.is_ok())
|
|
304
|
+
|
|
305
|
+
def test_add_multiple_times(self) -> None:
|
|
306
|
+
"""Test chaining multiple add operations"""
|
|
307
|
+
seq = Sequence(1)
|
|
308
|
+
seq = seq.add(Simple("hello"))
|
|
309
|
+
seq = seq.add(Simple(3.14))
|
|
310
|
+
seq = seq.add(OK)
|
|
311
|
+
|
|
312
|
+
self.assertEqual(seq.value, (1, "hello", 3.14, OK))
|
|
313
|
+
self.assertIsNone(seq.err)
|
|
314
|
+
self.assertTrue(seq.is_ok())
|
|
315
|
+
|
|
316
|
+
def test_add_with_option_type(self) -> None:
|
|
317
|
+
"""Test adding Option values"""
|
|
318
|
+
seq1: Sequence[int] = Sequence(1)
|
|
319
|
+
option_val = Option("test") # Assuming Option works like Simple for value
|
|
320
|
+
|
|
321
|
+
seq2 = seq1.add(option_val)
|
|
322
|
+
|
|
323
|
+
self.assertEqual(seq2.value, (1, "test"))
|
|
324
|
+
self.assertIsNone(seq2.err)
|
|
325
|
+
|
|
326
|
+
def test_type_annotations_after_add(self) -> None:
|
|
327
|
+
"""Test that type annotations are correct after add"""
|
|
328
|
+
seq1: Sequence[int, str] = Sequence(1, "hello")
|
|
329
|
+
|
|
330
|
+
# Adding Simple should preserve type info
|
|
331
|
+
seq2 = seq1.add(Simple(3.14))
|
|
332
|
+
self.assertEqual(seq2.value[0], 1) # int
|
|
333
|
+
self.assertEqual(seq2.value[1], "hello") # str
|
|
334
|
+
self.assertEqual(seq2.value[2], 3.14) # float
|
|
335
|
+
|
|
336
|
+
# Adding OK should work
|
|
337
|
+
seq3 = seq2.add(OK)
|
|
338
|
+
self.assertIs(seq3.value[3], OK)
|
|
339
|
+
|
|
340
|
+
# Adding Error should include Null
|
|
341
|
+
seq4 = seq3.add(Error.from_e(ValueError("test")))
|
|
342
|
+
self.assertIs(seq4.value[4], NULL)
|
|
343
|
+
|
|
344
|
+
def test_add_error_propagation_behavior(self) -> None:
|
|
345
|
+
"""Test that error propagation works correctly in add"""
|
|
346
|
+
# Create a result with error
|
|
347
|
+
error_result = Simple("will error")
|
|
348
|
+
error_result.append_e(ValueError("source error"), "context")
|
|
349
|
+
|
|
350
|
+
seq1: Sequence[int] = Sequence(1)
|
|
351
|
+
seq2 = seq1.add(error_result)
|
|
352
|
+
|
|
353
|
+
# Should have the error from the added result
|
|
354
|
+
self.assertIsNotNone(seq2.err)
|
|
355
|
+
self.assertEqual(seq2.err.message, "context")
|
|
356
|
+
self.assertEqual(len(seq2.err.exceptions), 1)
|
|
357
|
+
|
|
358
|
+
def test_add_preserves_sequence_immutability(self) -> None:
|
|
359
|
+
"""Test that original sequence is not modified by add"""
|
|
360
|
+
seq1: Sequence[int, str] = Sequence(1, "hello")
|
|
361
|
+
original_value = seq1.value
|
|
362
|
+
original_err = seq1.err
|
|
363
|
+
|
|
364
|
+
seq2 = seq1.add(Simple(3.14))
|
|
365
|
+
|
|
366
|
+
# Original should be unchanged
|
|
367
|
+
self.assertEqual(seq1.value, original_value)
|
|
368
|
+
self.assertEqual(seq1.err, original_err)
|
|
369
|
+
|
|
370
|
+
# New sequence should be different
|
|
371
|
+
self.assertIsNot(seq1, seq2)
|
|
372
|
+
self.assertEqual(seq2.value, (1, "hello", 3.14))
|
|
373
|
+
|
|
374
|
+
def test_add_with_none_values(self) -> None:
|
|
375
|
+
"""Test adding results with None values"""
|
|
376
|
+
seq1: Sequence[int] = Sequence(1)
|
|
377
|
+
option_with_none = Option(None) # Optional value
|
|
378
|
+
|
|
379
|
+
seq2 = seq1.add(option_with_none)
|
|
380
|
+
|
|
381
|
+
self.assertEqual(seq2.value, (1, None))
|
|
382
|
+
self.assertIsNone(seq2.err)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class TestSequenceAddEdgeCases(unittest.TestCase):
|
|
386
|
+
"""Edge case tests for Sequence.add"""
|
|
387
|
+
|
|
388
|
+
def test_add_after_error_accumulation(self) -> None:
|
|
389
|
+
"""Test adding to sequence that already has multiple errors"""
|
|
390
|
+
seq1 = Sequence(1, "test")
|
|
391
|
+
seq1.append_e(ValueError("error1"), "validation")
|
|
392
|
+
seq1.append_e(TypeError("error2"), "validation")
|
|
393
|
+
|
|
394
|
+
seq2 = seq1.add(Simple(3.14))
|
|
395
|
+
|
|
396
|
+
self.assertEqual(seq2.value, (1, "test", 3.14))
|
|
397
|
+
self.assertIsNotNone(seq2.err)
|
|
398
|
+
self.assertEqual(len(seq2.err.exceptions), 2)
|
|
399
|
+
|
|
400
|
+
def test_add_complex_error_structure(self) -> None:
|
|
401
|
+
"""Test adding with complex ExceptionGroup structures"""
|
|
402
|
+
complex_error = ExceptionGroup("complex", [
|
|
403
|
+
ValueError("err1"),
|
|
404
|
+
ExceptionGroup("nested", [TypeError("err2")])
|
|
405
|
+
])
|
|
406
|
+
|
|
407
|
+
seq1 = Sequence(1)
|
|
408
|
+
error_result = Error(complex_error)
|
|
409
|
+
|
|
410
|
+
seq2 = seq1.add(error_result)
|
|
411
|
+
|
|
412
|
+
self.assertEqual(seq2.value, (1, NULL))
|
|
413
|
+
self.assertIsNotNone(seq2.err)
|
|
414
|
+
# Complex structure should be preserved
|
|
415
|
+
self.assertEqual(seq2.err.message, "complex")
|
|
416
|
+
|
|
417
|
+
def test_add_with_custom_collector(self) -> None:
|
|
418
|
+
"""Test adding custom collector types"""
|
|
419
|
+
|
|
420
|
+
class CustomCollector(Simple[str]):
|
|
421
|
+
def custom_method(self) -> str:
|
|
422
|
+
return self.value.upper()
|
|
423
|
+
|
|
424
|
+
custom = CustomCollector("test")
|
|
425
|
+
seq1 = Sequence(1)
|
|
426
|
+
|
|
427
|
+
seq2 = seq1.add(custom)
|
|
428
|
+
|
|
429
|
+
self.assertEqual(seq2.value, (1, "test"))
|
|
430
|
+
self.assertIsNone(seq2.err)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class TestSequenceAddTypeSafety(unittest.TestCase):
|
|
434
|
+
"""Type safety tests for Sequence.add"""
|
|
435
|
+
|
|
436
|
+
def test_type_inference_chain(self) -> None:
|
|
437
|
+
"""Test that type inference works through add chain"""
|
|
438
|
+
# Start with specific types
|
|
439
|
+
seq: Sequence[int] = Sequence(1)
|
|
440
|
+
|
|
441
|
+
# Each add should properly infer new type
|
|
442
|
+
seq = seq.add(Simple("hello")) # Should be Sequence[int, str]
|
|
443
|
+
self.assertEqual(seq.value[0], 1)
|
|
444
|
+
self.assertEqual(seq.value[1], "hello")
|
|
445
|
+
|
|
446
|
+
seq = seq.add(Simple(3.14)) # Should be Sequence[int, str, float]
|
|
447
|
+
self.assertEqual(seq.value[2], 3.14)
|
|
448
|
+
|
|
449
|
+
seq = seq.add(OK) # Should be Sequence[int, str, float, Ok]
|
|
450
|
+
self.assertIs(seq.value[3], OK)
|
|
451
|
+
|
|
452
|
+
def test_unwrap_after_add_operations(self) -> None:
|
|
453
|
+
"""Test that unwrap works correctly after multiple adds"""
|
|
454
|
+
seq = Sequence(1)
|
|
455
|
+
seq: Sequence[int, str] = seq.add(Simple("test"))
|
|
456
|
+
seq = seq.add(Simple(True))
|
|
457
|
+
|
|
458
|
+
result = seq.unwrap()
|
|
459
|
+
self.assertEqual(result, (1, "test", True))
|
|
460
|
+
self.assertIsInstance(result, tuple)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class TestSequenceTyping(unittest.TestCase):
|
|
464
|
+
"""Test type annotations are correct"""
|
|
465
|
+
|
|
466
|
+
def test_type_annotations(self) -> None:
|
|
467
|
+
"""Test that type annotations work correctly"""
|
|
468
|
+
# These should not cause type errors when checked with mypy/pyright
|
|
469
|
+
seq1: Sequence[int, str] = Sequence(1, "hello")
|
|
470
|
+
seq2: Sequence[int, str, bool] = Sequence(1, "test", True)
|
|
471
|
+
seq3: Sequence[()] = Sequence() # Empty tuple type
|
|
472
|
+
|
|
473
|
+
# Type narrowing should work
|
|
474
|
+
if seq1.is_ok():
|
|
475
|
+
values: tuple[int, str] = seq1.unwrap()
|
|
476
|
+
self.assertEqual(values, (1, "hello"))
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def suite() -> unittest.TestSuite:
|
|
480
|
+
"""Create test suite"""
|
|
481
|
+
test_suite = unittest.TestSuite()
|
|
482
|
+
test_suite.addTest(unittest.makeSuite(TestSequence))
|
|
483
|
+
test_suite.addTest(unittest.makeSuite(TestSequenceIntegration))
|
|
484
|
+
test_suite.addTest(unittest.makeSuite(TestSequenceEdgeCases))
|
|
485
|
+
test_suite.addTest(unittest.makeSuite(TestSequenceTyping))
|
|
486
|
+
return test_suite
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
from unittest.mock import patch
|
|
3
3
|
from typing import Any
|
|
4
|
-
from src.StructResult.result import Option, Bool, Ok, OK, Error, List, SimpleOrError, Simple, Sequence
|
|
4
|
+
from src.StructResult.result import Option, Bool, Ok, OK, Error, List, SimpleOrError, Simple, Sequence, Null
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class TestResultSystem(unittest.TestCase):
|
|
@@ -141,7 +141,7 @@ class TestResultSystem(unittest.TestCase):
|
|
|
141
141
|
self.assertEqual(len(lst.value), 3)
|
|
142
142
|
self.assertEqual(lst.value[0], 42)
|
|
143
143
|
self.assertEqual(lst.value[1], "hello")
|
|
144
|
-
self.assertIsInstance(lst.value[2],
|
|
144
|
+
self.assertIsInstance(lst.value[2], Null)
|
|
145
145
|
|
|
146
146
|
def test_propagate_none(self) -> None:
|
|
147
147
|
res: Option[int] = Option(42)
|
|
@@ -192,7 +192,7 @@ class TestResultSystem(unittest.TestCase):
|
|
|
192
192
|
|
|
193
193
|
def test_iterator_protocol(self) -> None:
|
|
194
194
|
res: Option[str] = Option("test")
|
|
195
|
-
values =
|
|
195
|
+
values = res.unpack()
|
|
196
196
|
self.assertEqual(len(values), 2)
|
|
197
197
|
self.assertEqual(values[0], "test")
|
|
198
198
|
self.assertIsNone(values[1])
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from src.StructResult.result import Sequence, Option, Simple, OK
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class TestSequence(unittest.TestCase):
|
|
6
|
-
"""Tests for Sequence class"""
|
|
7
|
-
|
|
8
|
-
def test_basic_creation(self) -> None:
|
|
9
|
-
"""Test basic sequence creation"""
|
|
10
|
-
seq = Sequence(1, "hello", True)
|
|
11
|
-
self.assertEqual(seq.value, (1, "hello", True))
|
|
12
|
-
self.assertIsNone(seq.err)
|
|
13
|
-
self.assertTrue(seq.is_ok())
|
|
14
|
-
|
|
15
|
-
def test_type_annotations(self) -> None:
|
|
16
|
-
"""Test strict type annotations"""
|
|
17
|
-
seq_int_str: Sequence[int, str] = Sequence(1, "test")
|
|
18
|
-
seq_mixed: Sequence[int, str, bool] = Sequence(1, "hello", True)
|
|
19
|
-
|
|
20
|
-
# Type checking should work
|
|
21
|
-
first: int = seq_int_str.value[0]
|
|
22
|
-
second: str = seq_int_str.value[1]
|
|
23
|
-
|
|
24
|
-
self.assertEqual(first, 1)
|
|
25
|
-
self.assertEqual(second, "test")
|
|
26
|
-
|
|
27
|
-
def test_empty_sequence(self) -> None:
|
|
28
|
-
"""Test empty sequence creation"""
|
|
29
|
-
seq = Sequence()
|
|
30
|
-
self.assertEqual(seq.value, ())
|
|
31
|
-
self.assertIsNone(seq.err)
|
|
32
|
-
self.assertTrue(seq.is_ok())
|
|
33
|
-
|
|
34
|
-
def test_with_errors(self) -> None:
|
|
35
|
-
"""Test sequence with errors"""
|
|
36
|
-
error = ExceptionGroup("test", [ValueError("error1")])
|
|
37
|
-
seq = Sequence(1, "test", err=error)
|
|
38
|
-
|
|
39
|
-
self.assertEqual(seq.value, (1, "test"))
|
|
40
|
-
self.assertIs(seq.err, error)
|
|
41
|
-
self.assertFalse(seq.is_ok())
|
|
42
|
-
|
|
43
|
-
def test_unwrap_success(self) -> None:
|
|
44
|
-
"""Test successful unwrap"""
|
|
45
|
-
seq = Sequence(1, "hello", 3.14)
|
|
46
|
-
result = seq.unwrap()
|
|
47
|
-
|
|
48
|
-
self.assertEqual(result, (1, "hello", 3.14))
|
|
49
|
-
self.assertIsInstance(result, tuple)
|
|
50
|
-
|
|
51
|
-
def test_unwrap_failure(self) -> None:
|
|
52
|
-
"""Test unwrap with errors"""
|
|
53
|
-
error = ExceptionGroup("test", [ValueError("error1")])
|
|
54
|
-
seq = Sequence(1, "test", err=error)
|
|
55
|
-
|
|
56
|
-
with self.assertRaises(ExceptionGroup) as context:
|
|
57
|
-
seq.unwrap()
|
|
58
|
-
|
|
59
|
-
self.assertIs(context.exception, error)
|
|
60
|
-
|
|
61
|
-
def test_err_propagation(self) -> None:
|
|
62
|
-
"""Test error propagation compatibility"""
|
|
63
|
-
seq = Sequence(1, "test")
|
|
64
|
-
|
|
65
|
-
# Should work with ErrorPropagator protocol
|
|
66
|
-
self.assertTrue(hasattr(seq, 'append_e'))
|
|
67
|
-
self.assertTrue(hasattr(seq, 'append_err'))
|
|
68
|
-
self.assertTrue(hasattr(seq, 'propagate_err'))
|
|
69
|
-
|
|
70
|
-
def test_collector_protocol(self) -> None:
|
|
71
|
-
"""Test Collector protocol compliance"""
|
|
72
|
-
seq = Sequence(1, "hello")
|
|
73
|
-
|
|
74
|
-
# Should support iteration
|
|
75
|
-
values, err = tuple(seq)
|
|
76
|
-
self.assertEqual(values, (1, "hello"))
|
|
77
|
-
self.assertIsNone(err)
|
|
78
|
-
|
|
79
|
-
# Should have value attribute
|
|
80
|
-
self.assertTrue(hasattr(seq, 'value'))
|
|
81
|
-
self.assertTrue(hasattr(seq, 'err'))
|
|
82
|
-
|
|
83
|
-
def test_result_protocol(self) -> None:
|
|
84
|
-
"""Test Result protocol compliance"""
|
|
85
|
-
seq = Sequence(1, "test")
|
|
86
|
-
|
|
87
|
-
self.assertTrue(hasattr(seq, 'is_ok'))
|
|
88
|
-
self.assertTrue(hasattr(seq, 'unwrap'))
|
|
89
|
-
self.assertTrue(seq.is_ok())
|
|
90
|
-
|
|
91
|
-
def test_complex_types(self) -> None:
|
|
92
|
-
"""Test sequences with complex types"""
|
|
93
|
-
# Nested sequences
|
|
94
|
-
inner_seq = Sequence("nested")
|
|
95
|
-
outer_seq = Sequence(1, inner_seq, True)
|
|
96
|
-
|
|
97
|
-
self.assertEqual(outer_seq.value[0], 1)
|
|
98
|
-
self.assertEqual(outer_seq.value[1].value, ("nested",))
|
|
99
|
-
self.assertEqual(outer_seq.value[2], True)
|
|
100
|
-
|
|
101
|
-
def test_equality(self) -> None:
|
|
102
|
-
"""Test sequence equality"""
|
|
103
|
-
seq1 = Sequence(1, "test")
|
|
104
|
-
seq2 = Sequence(1, "test")
|
|
105
|
-
seq3 = Sequence(1, "different")
|
|
106
|
-
|
|
107
|
-
# Different instances with same values
|
|
108
|
-
self.assertEqual(seq1.value, seq2.value)
|
|
109
|
-
self.assertNotEqual(seq1.value, seq3.value)
|
|
110
|
-
|
|
111
|
-
def test_len_behavior(self) -> None:
|
|
112
|
-
"""Test sequence length behavior"""
|
|
113
|
-
seq_single = Sequence(1)
|
|
114
|
-
seq_multi = Sequence(1, "two", 3.0)
|
|
115
|
-
|
|
116
|
-
self.assertEqual(len(seq_single.value), 1)
|
|
117
|
-
self.assertEqual(len(seq_multi.value), 3)
|
|
118
|
-
|
|
119
|
-
def test_pattern_matching(self) -> None:
|
|
120
|
-
"""Test sequence pattern matching support"""
|
|
121
|
-
seq = Sequence(1, "hello", True)
|
|
122
|
-
|
|
123
|
-
# Basic unpacking
|
|
124
|
-
a, b, c = seq.value
|
|
125
|
-
self.assertEqual(a, 1)
|
|
126
|
-
self.assertEqual(b, "hello")
|
|
127
|
-
self.assertEqual(c, True)
|
|
128
|
-
|
|
129
|
-
def test_error_accumulation(self) -> None:
|
|
130
|
-
"""Test error accumulation functionality"""
|
|
131
|
-
seq = Sequence(1, "test")
|
|
132
|
-
|
|
133
|
-
# Add first error
|
|
134
|
-
seq.append_e(ValueError("first error"), "context1")
|
|
135
|
-
self.assertIsNotNone(seq.err)
|
|
136
|
-
if seq.err is not None:
|
|
137
|
-
self.assertEqual(len(seq.err.exceptions), 1)
|
|
138
|
-
|
|
139
|
-
# Add second error with same message (should merge)
|
|
140
|
-
seq.append_e(TypeError("second error"), "context1")
|
|
141
|
-
if seq.err is not None:
|
|
142
|
-
self.assertEqual(len(seq.err.exceptions), 2)
|
|
143
|
-
|
|
144
|
-
# Add error with different message (should nest)
|
|
145
|
-
seq.append_e(RuntimeError("third error"), "context2")
|
|
146
|
-
if seq.err is not None:
|
|
147
|
-
self.assertEqual(seq.err.message, "context2")
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
class TestSequenceIntegration(unittest.TestCase):
|
|
151
|
-
"""Integration tests with other system components"""
|
|
152
|
-
|
|
153
|
-
def test_with_simple_collector(self) -> None:
|
|
154
|
-
"""Test integration with Simple collector"""
|
|
155
|
-
simple = Simple(42)
|
|
156
|
-
seq = Sequence(simple.value, "additional")
|
|
157
|
-
|
|
158
|
-
self.assertEqual(seq.value, (42, "additional"))
|
|
159
|
-
self.assertTrue(seq.is_ok())
|
|
160
|
-
|
|
161
|
-
def test_with_option_collector(self) -> None:
|
|
162
|
-
"""Test integration with Option collector"""
|
|
163
|
-
option = Option(100) # Assuming Option can be created with value
|
|
164
|
-
seq = Sequence(option.value, "test")
|
|
165
|
-
|
|
166
|
-
self.assertEqual(seq.value[0], 100)
|
|
167
|
-
self.assertEqual(seq.value[1], "test")
|
|
168
|
-
|
|
169
|
-
def test_error_propagation_from_other_collectors(self) -> None:
|
|
170
|
-
"""Test error propagation from other collectors"""
|
|
171
|
-
# Create a collector with error
|
|
172
|
-
error_collector = Simple(100)
|
|
173
|
-
error_collector.append_e(ValueError("source error"))
|
|
174
|
-
|
|
175
|
-
seq = Sequence("start")
|
|
176
|
-
result = seq.propagate_err(error_collector)
|
|
177
|
-
|
|
178
|
-
self.assertEqual(result, 100) # Should return the value
|
|
179
|
-
self.assertIsNotNone(seq.err) # But errors should be propagated
|
|
180
|
-
if seq.err is not None:
|
|
181
|
-
self.assertEqual(len(seq.err.exceptions), 1)
|
|
182
|
-
|
|
183
|
-
def test_with_ok_singleton(self) -> None:
|
|
184
|
-
"""Test integration with OK singleton"""
|
|
185
|
-
seq = Sequence(OK, "regular value")
|
|
186
|
-
|
|
187
|
-
self.assertIs(seq.value[0], OK)
|
|
188
|
-
self.assertEqual(seq.value[1], "regular value")
|
|
189
|
-
self.assertTrue(seq.is_ok())
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
class TestSequenceEdgeCases(unittest.TestCase):
|
|
193
|
-
"""Edge case tests for Sequence"""
|
|
194
|
-
|
|
195
|
-
def test_none_values(self) -> None:
|
|
196
|
-
"""Test sequences with None values"""
|
|
197
|
-
seq = Sequence(None, "test", None)
|
|
198
|
-
self.assertEqual(seq.value, (None, "test", None))
|
|
199
|
-
self.assertEqual(seq.unwrap(), (None, "test", None))
|
|
200
|
-
|
|
201
|
-
def test_large_sequences(self) -> None:
|
|
202
|
-
"""Test sequences with many elements"""
|
|
203
|
-
large_tuple = tuple(range(100))
|
|
204
|
-
seq = Sequence(*large_tuple)
|
|
205
|
-
self.assertEqual(seq.value, large_tuple)
|
|
206
|
-
self.assertEqual(len(seq.value), 100)
|
|
207
|
-
|
|
208
|
-
def test_nested_tuples(self) -> None:
|
|
209
|
-
"""Test sequences containing nested tuples"""
|
|
210
|
-
nested = (1, 2, 3)
|
|
211
|
-
seq = Sequence(nested, "test")
|
|
212
|
-
self.assertEqual(seq.value[0], nested)
|
|
213
|
-
self.assertEqual(seq.value[1], "test")
|
|
214
|
-
|
|
215
|
-
def test_serialization(self) -> None:
|
|
216
|
-
"""Test basic serialization properties"""
|
|
217
|
-
seq = Sequence(1, "test")
|
|
218
|
-
|
|
219
|
-
# Should be picklable
|
|
220
|
-
import pickle
|
|
221
|
-
pickled = pickle.dumps(seq)
|
|
222
|
-
unpickled = pickle.loads(pickled)
|
|
223
|
-
|
|
224
|
-
self.assertEqual(unpickled.value, seq.value)
|
|
225
|
-
self.assertEqual(unpickled.err, seq.err)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
class TestSequenceTyping(unittest.TestCase):
|
|
229
|
-
"""Test type annotations are correct"""
|
|
230
|
-
|
|
231
|
-
def test_type_annotations(self) -> None:
|
|
232
|
-
"""Test that type annotations work correctly"""
|
|
233
|
-
# These should not cause type errors when checked with mypy/pyright
|
|
234
|
-
seq1: Sequence[int, str] = Sequence(1, "hello")
|
|
235
|
-
seq2: Sequence[int, str, bool] = Sequence(1, "test", True)
|
|
236
|
-
seq3: Sequence[()] = Sequence() # Empty tuple type
|
|
237
|
-
|
|
238
|
-
# Type narrowing should work
|
|
239
|
-
if seq1.is_ok():
|
|
240
|
-
values: tuple[int, str] = seq1.unwrap()
|
|
241
|
-
self.assertEqual(values, (1, "hello"))
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def suite():
|
|
245
|
-
"""Create test suite"""
|
|
246
|
-
test_suite = unittest.TestSuite()
|
|
247
|
-
test_suite.addTest(unittest.makeSuite(TestSequence))
|
|
248
|
-
test_suite.addTest(unittest.makeSuite(TestSequenceIntegration))
|
|
249
|
-
test_suite.addTest(unittest.makeSuite(TestSequenceEdgeCases))
|
|
250
|
-
test_suite.addTest(unittest.makeSuite(TestSequenceTyping))
|
|
251
|
-
return test_suite
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|