Skip to content

Commit 2428163

Browse files
committed
better documentation and testing of umbi.tar
1 parent 69974cc commit 2428163

9 files changed

Lines changed: 396 additions & 162 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ Individual tools can be run manually:
105105
**[Ruff](https://github.com/astral-sh/ruff)** -- Code formatting and linting. Config: [pyproject.toml](pyproject.toml) (`[tool.ruff]`)
106106
```bash
107107
ruff check . # check for issues
108-
ruff format . # format code
109108
ruff check . --fix # check and fix
109+
ruff format . # format code
110110
```
111111

112112
**[Pyright](https://github.com/microsoft/pyright)** -- Static type checking. Config: [pyproject.toml](pyproject.toml) (`[tool.pyright]`)

examples/misc/ats_smg_to_prism.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import pathlib
99

1010
import umbi
11-
import umbi.ats.examples
1211

1312
log = logging.getLogger(__name__)
1413

@@ -33,6 +32,7 @@ def ats_smg_to_prism(ats: umbi.ats.ExplicitAts, output_path: pathlib.Path) -> No
3332
f.write(f"global state: [0..{ats.num_states - 1}];\n\n")
3433

3534
# player declarations
35+
player_to_states = None
3636
if ats.num_players > 0:
3737
# group states by player
3838
player_to_states = {}
@@ -60,6 +60,7 @@ def ats_smg_to_prism(ats: umbi.ats.ExplicitAts, output_path: pathlib.Path) -> No
6060

6161
# group states by player for module generation
6262
if ats.num_players > 0:
63+
assert player_to_states is not None
6364
for player in sorted(player_to_states.keys()):
6465
f.write(f"module player{player}\n")
6566

tests/tar/test_tar_coder.py

Lines changed: 173 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,31 @@ def test_add_and_read_json(self):
1818
tc.add_json("data.json", json_obj)
1919

2020
loaded = tc.read_json("data.json")
21+
assert loaded is not None
2122
assert loaded == json_obj
2223

2324
def test_read_json_optional_missing(self):
2425
"""Test reading missing optional JSON file returns None."""
2526
tc = TarCoder()
26-
result = tc.read_json("missing.json", required=False)
27+
result = tc.read_json("missing.json", optional=True)
2728
assert result is None
2829

2930
def test_read_json_required_missing(self):
3031
"""Test reading missing required JSON file raises error."""
3132
tc = TarCoder()
3233
with pytest.raises(KeyError):
33-
tc.read_json("missing.json", required=True)
34+
tc.read_json("missing.json")
3435

3536
def test_add_json_required_none(self):
3637
"""Test that adding None for required JSON raises error."""
3738
tc = TarCoder()
3839
with pytest.raises(ValueError):
39-
tc.add_json("data.json", None, required=True)
40+
tc.add_json("data.json", None)
4041

4142
def test_add_json_optional_none(self):
4243
"""Test that adding None for optional JSON does nothing."""
4344
tc = TarCoder()
44-
tc.add_json("data.json", None, required=False)
45+
tc.add_json("data.json", None, optional=True)
4546
assert not tc.has_file("data.json")
4647

4748
def test_complex_json(self):
@@ -51,6 +52,7 @@ def test_complex_json(self):
5152

5253
tc.add_json("complex.json", json_obj)
5354
loaded = tc.read_json("complex.json")
55+
assert loaded is not None
5456
assert loaded == json_obj
5557

5658

@@ -66,14 +68,15 @@ def test_add_and_read_vector(self):
6668
tc.add_vector("values.bin", sized_type, values)
6769

6870
loaded = tc.read_vector("values.bin", sized_type)
71+
assert loaded is not None
6972
assert loaded == values
7073

7174
def test_read_vector_optional_missing(self):
7275
"""Test reading missing optional vector returns None."""
7376
tc = TarCoder()
7477
sized_type = SizedType(NumericPrimitiveType.UINT, 32)
7578

76-
result = tc.read_vector("missing.bin", sized_type, required=False)
79+
result = tc.read_vector("missing.bin", sized_type, optional=True)
7780
assert result is None
7881

7982
def test_read_vector_required_missing(self):
@@ -82,15 +85,15 @@ def test_read_vector_required_missing(self):
8285
sized_type = SizedType(NumericPrimitiveType.UINT, 32)
8386

8487
with pytest.raises(KeyError):
85-
tc.read_vector("missing.bin", sized_type, required=True)
88+
tc.read_vector("missing.bin", sized_type)
8689

8790
def test_add_vector_required_none(self):
8891
"""Test that adding None for required vector raises error."""
8992
tc = TarCoder()
9093
sized_type = SizedType(NumericPrimitiveType.UINT, 32)
9194

9295
with pytest.raises(ValueError):
93-
tc.add_vector("values.bin", sized_type, None, required=True)
96+
tc.add_vector("values.bin", sized_type, None)
9497

9598
def test_vector_roundtrip(self):
9699
"""Test vector round-trip conversion."""
@@ -101,6 +104,7 @@ def test_vector_roundtrip(self):
101104
tc1.add_vector("data.bin", sized_type, original)
102105

103106
loaded = tc1.read_vector("data.bin", sized_type)
107+
assert loaded is not None
104108
assert loaded == original
105109

106110

@@ -115,6 +119,7 @@ def test_add_and_read_bitvector(self):
115119
tc.add_bitvector("bits.bin", bitvector)
116120

117121
loaded = tc.read_bitvector("bits.bin", num_entries=5)
122+
assert loaded is not None
118123
# May be padded, so check the first 5 elements
119124
assert loaded[:5] == bitvector
120125

@@ -127,6 +132,7 @@ def test_bitvector_padding(self):
127132
tc.add_bitvector("bits.bin", bitvector, pad_to_8_bytes=True)
128133

129134
loaded = tc.read_bitvector("bits.bin", num_entries=5)
135+
assert loaded is not None
130136
assert loaded[:5] == bitvector
131137
# Remaining should be False (padding)
132138
assert all(not bit for bit in loaded[5:])
@@ -139,6 +145,7 @@ def test_bitvector_no_padding(self):
139145
tc.add_bitvector("bits.bin", bitvector, pad_to_8_bytes=False)
140146

141147
loaded = tc.read_bitvector("bits.bin", num_entries=8)
148+
assert loaded is not None
142149
assert loaded == bitvector
143150

144151
def test_bitvector_truncation(self):
@@ -151,10 +158,159 @@ def test_bitvector_truncation(self):
151158

152159
# Read only 8 entries
153160
loaded = tc.read_bitvector("bits.bin", num_entries=8)
161+
assert loaded is not None
154162
assert len(loaded) == 8
155163
assert loaded == bitvector[:8]
156164

157165

166+
class TestTarCoderVectorWithRanges:
167+
"""Test vector with CSR ranges reading and writing."""
168+
169+
def test_add_and_read_vector_with_ranges(self):
170+
"""Test adding and reading a vector with CSR ranges."""
171+
tc = TarCoder()
172+
vector = [1, 2, 3, 4, 5, 6, 7, 8]
173+
sized_type = SizedType(NumericPrimitiveType.UINT, 32)
174+
175+
tc.add_vector_with_ranges("data.bin", sized_type, vector, "ranges.bin")
176+
177+
loaded = tc.read_vector_with_ranges("data.bin", NumericPrimitiveType.UINT, filename_csr="ranges.bin")
178+
assert loaded is not None
179+
assert loaded == vector
180+
181+
def test_read_vector_with_ranges_optional_missing(self):
182+
"""Test reading missing optional vector with ranges returns None."""
183+
tc = TarCoder()
184+
result = tc.read_vector_with_ranges(
185+
"missing.bin", NumericPrimitiveType.UINT, optional=True, filename_csr="ranges.bin"
186+
)
187+
assert result is None
188+
189+
def test_read_vector_with_ranges_required_missing(self):
190+
"""Test reading missing required vector with ranges raises error."""
191+
tc = TarCoder()
192+
with pytest.raises(KeyError):
193+
tc.read_vector_with_ranges("missing.bin", NumericPrimitiveType.UINT, filename_csr="ranges.bin")
194+
195+
def test_read_vector_with_ranges_missing_csr(self):
196+
"""Test that missing CSR file raises error even if main file exists."""
197+
tc = TarCoder()
198+
vector = [1, 2, 3, 4, 5]
199+
sized_type = SizedType(NumericPrimitiveType.UINT, 32)
200+
201+
tc.add_vector_with_ranges("data.bin", sized_type, vector, "ranges.bin")
202+
# Remove the CSR file using dict interface
203+
tc.pop("ranges.bin", None)
204+
205+
with pytest.raises(KeyError):
206+
tc.read_vector_with_ranges("data.bin", NumericPrimitiveType.UINT, filename_csr="ranges.bin")
207+
208+
def test_add_vector_with_ranges_required_none(self):
209+
"""Test that adding None for required vector with ranges raises error."""
210+
tc = TarCoder()
211+
sized_type = SizedType(NumericPrimitiveType.UINT, 32)
212+
213+
with pytest.raises(ValueError):
214+
tc.add_vector_with_ranges("data.bin", sized_type, None, "ranges.bin")
215+
216+
def test_add_vector_with_ranges_optional_none(self):
217+
"""Test that adding None for optional vector with ranges does nothing."""
218+
tc = TarCoder()
219+
sized_type = SizedType(NumericPrimitiveType.UINT, 32)
220+
221+
tc.add_vector_with_ranges("data.bin", sized_type, None, "ranges.bin", optional=True)
222+
assert not tc.has_file("data.bin")
223+
assert not tc.has_file("ranges.bin")
224+
225+
def test_vector_with_ranges_roundtrip(self):
226+
"""Test vector with ranges round-trip conversion."""
227+
original = [100, 200, 300, 400, 500, 600]
228+
sized_type = SizedType(NumericPrimitiveType.UINT, 64)
229+
230+
tc1 = TarCoder()
231+
tc1.add_vector_with_ranges("data.bin", sized_type, original, "ranges.bin")
232+
233+
loaded = tc1.read_vector_with_ranges("data.bin", NumericPrimitiveType.UINT, filename_csr="ranges.bin")
234+
assert loaded is not None
235+
assert loaded == original
236+
237+
238+
class TestTarCoderStrings:
239+
"""Test string vector reading and writing with CSR."""
240+
241+
def test_add_and_read_strings(self):
242+
"""Test adding and reading strings with CSR."""
243+
tc = TarCoder()
244+
strings = ["hello", "world", "test", "data"]
245+
246+
tc.add_strings("strings.bin", strings, "strings_csr.bin")
247+
248+
loaded = tc.read_strings("strings.bin", filename_csr="strings_csr.bin")
249+
assert loaded is not None
250+
assert loaded == strings
251+
252+
def test_read_strings_optional_missing(self):
253+
"""Test reading missing optional strings returns None."""
254+
tc = TarCoder()
255+
result = tc.read_strings("missing.bin", optional=True, filename_csr="strings_csr.bin")
256+
assert result is None
257+
258+
def test_read_strings_required_missing(self):
259+
"""Test reading missing required strings raises error."""
260+
tc = TarCoder()
261+
with pytest.raises(KeyError):
262+
tc.read_strings("missing.bin", filename_csr="strings_csr.bin")
263+
264+
def test_read_strings_missing_csr(self):
265+
"""Test that missing CSR file raises error even if main file exists."""
266+
tc = TarCoder()
267+
strings = ["a", "b", "c"]
268+
269+
tc.add_strings("strings.bin", strings, "strings_csr.bin")
270+
# Remove the CSR file using dict interface
271+
tc.pop("strings_csr.bin", None)
272+
273+
with pytest.raises(KeyError):
274+
tc.read_strings("strings.bin", filename_csr="strings_csr.bin")
275+
276+
def test_add_strings_required_none(self):
277+
"""Test that adding None for required strings raises error."""
278+
tc = TarCoder()
279+
280+
with pytest.raises(ValueError):
281+
tc.add_strings("strings.bin", None, "strings_csr.bin")
282+
283+
def test_add_strings_optional_none(self):
284+
"""Test that adding None for optional strings does nothing."""
285+
tc = TarCoder()
286+
287+
tc.add_strings("strings.bin", None, "strings_csr.bin", optional=True)
288+
assert not tc.has_file("strings.bin")
289+
assert not tc.has_file("strings_csr.bin")
290+
291+
def test_strings_roundtrip(self):
292+
"""Test strings round-trip conversion."""
293+
original = ["foo", "bar", "baz", "qux", "long string with spaces"]
294+
295+
tc1 = TarCoder()
296+
tc1.add_strings("strings.bin", original, "strings_csr.bin")
297+
298+
loaded = tc1.read_strings("strings.bin", filename_csr="strings_csr.bin")
299+
assert loaded is not None
300+
assert loaded == original
301+
302+
def test_strings_single_empty_string(self):
303+
"""Test handling of empty strings in list."""
304+
tc = TarCoder()
305+
strings = ["", "non-empty", ""]
306+
307+
tc.add_strings("strings.bin", strings, "strings_csr.bin")
308+
309+
loaded = tc.read_strings("strings.bin", filename_csr="strings_csr.bin")
310+
assert loaded is not None
311+
assert loaded == strings
312+
313+
158314
class TestTarCoderIntegration:
159315
"""Integration tests for TarCoder."""
160316

@@ -168,17 +324,23 @@ def test_multiple_files(self):
168324
tc.add_bitvector("flags.bin", [True, False, True, False])
169325

170326
# Verify all files exist and can be read
171-
assert tc.read_json("metadata.json") == {"version": 1}
172-
assert tc.read_vector("values.bin", SizedType(NumericPrimitiveType.UINT, 32)) == [1, 2, 3]
173-
assert tc.read_bitvector("flags.bin", num_entries=4)[:4] == [True, False, True, False]
327+
json_data = tc.read_json("metadata.json")
328+
assert json_data is not None
329+
assert json_data == {"version": 1}
330+
vector_data = tc.read_vector("values.bin", SizedType(NumericPrimitiveType.UINT, 32))
331+
assert vector_data is not None
332+
assert vector_data == [1, 2, 3]
333+
bits_data = tc.read_bitvector("flags.bin", num_entries=4)
334+
assert bits_data is not None
335+
assert bits_data[:4] == [True, False, True, False]
174336

175337
def test_tar_coder_inherits_tarfile(self):
176338
"""Test that TarCoder has all TarFile functionality."""
177339
tc = TarCoder()
178340

179341
# Test dict interface
180342
tc["raw.bin"] = b"raw data"
181-
assert tc.get_file("raw.bin") == b"raw data"
343+
assert tc.read_file("raw.bin") == b"raw data"
182344

183345
# Test add_file
184346
tc.add_file("file.bin", b"content")

tests/tar/test_tar_file.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_add_and_retrieve_file(self):
2323
tf.add_file("test.txt", data)
2424

2525
assert tf.has_file("test.txt")
26-
assert tf.get_file("test.txt") == data
26+
assert tf.read_file("test.txt") == data
2727

2828
def test_has_file(self):
2929
"""Test checking if file exists."""
@@ -37,12 +37,12 @@ def test_get_required_file_missing(self):
3737
"""Test that missing required file raises KeyError."""
3838
tf = TarFile()
3939
with pytest.raises(KeyError):
40-
tf.get_file("missing.txt", required=True)
40+
tf.read_file("missing.txt")
4141

4242
def test_get_optional_file_missing(self):
4343
"""Test that missing optional file returns None."""
4444
tf = TarFile()
45-
result = tf.get_file("missing.txt", required=False)
45+
result = tf.read_file("missing.txt", optional=True)
4646
assert result is None
4747

4848
def test_add_file_duplicate_without_overwrite(self):
@@ -59,7 +59,9 @@ def test_add_file_duplicate_with_overwrite(self):
5959
tf.add_file("file.txt", b"original")
6060
tf.add_file("file.txt", b"new", overwrite=True)
6161

62-
assert tf.get_file("file.txt") == b"new"
62+
result = tf.read_file("file.txt")
63+
assert result is not None
64+
assert result == b"new"
6365

6466
def test_filenames_property(self):
6567
"""Test filenames property."""
@@ -92,8 +94,12 @@ def test_write_and_read_tarfile(self, tmp_path: pathlib.Path):
9294
tf2 = TarFile(tarpath)
9395
assert tf2.has_file("file1.txt")
9496
assert tf2.has_file("file2.txt")
95-
assert tf2.get_file("file1.txt") == b"content1"
96-
assert tf2.get_file("file2.txt") == b"content2"
97+
content1 = tf2.read_file("file1.txt")
98+
assert content1 is not None
99+
assert content1 == b"content1"
100+
content2 = tf2.read_file("file2.txt")
101+
assert content2 is not None
102+
assert content2 == b"content2"
97103

98104
def test_write_and_read_empty_tarfile(self, tmp_path: pathlib.Path):
99105
"""Test writing and reading an empty tarfile."""
@@ -115,7 +121,9 @@ def test_write_with_different_compression(self, tmp_path: pathlib.Path):
115121
tf.write(tarpath, compression=compression) # type: ignore
116122

117123
tf_loaded = TarFile(tarpath)
118-
assert tf_loaded.get_file("test.txt") == b"data"
124+
data = tf_loaded.read_file("test.txt")
125+
assert data is not None
126+
assert data == b"data"
119127

120128
def test_read_nonexistent_file(self, tmp_path: pathlib.Path):
121129
"""Test that reading nonexistent tarfile raises error."""

0 commit comments

Comments
 (0)