Skip to content

Commit 5afaa8b

Browse files
rustyconoverclaude
andcommitted
Improve test coverage and type annotations
Test coverage improvements: - exceptions.py: 67% -> 99% (SchemaValidationError detailed messages) - function_storage.py: 83% -> 98% (global_delete, global_exists, queue_clear) - scalar_function.py: 86% -> 93% (RowCountMismatchError when output > input) - worker.py: 88% -> 92% (registry caching, _suggest_similar_names) Type annotation improvements: - arguments.py: Replace _MISSING: Any with proper _MissingType sentinel class - cli.py: Replace _writer: Any with _writer: pq.ParquetWriter | None New test files: - tests/test_exceptions.py - tests/test_function_storage.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ab4336b commit 5afaa8b

7 files changed

Lines changed: 531 additions & 11 deletions

File tree

.beads/issues.jsonl

Lines changed: 6 additions & 6 deletions
Large diffs are not rendered by default.

tests/scalar/test_function.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,35 @@ def compute(self, batch: pa.RecordBatch) -> pa.Array[Any]:
251251
assert output.log_message.level == Level.EXCEPTION
252252
assert "same row count" in output.log_message.message.lower()
253253

254+
def test_row_count_exceeds_input(self) -> None:
255+
"""Test that output with more rows than input raises error (lines 134-142)."""
256+
257+
class TooManyRows(ScalarFunction):
258+
@property
259+
def output_type(self) -> pa.DataType:
260+
return pa.int64()
261+
262+
def compute(self, batch: pa.RecordBatch) -> pa.Array[Any]:
263+
# Return MORE rows than input (expanding rows is not allowed)
264+
return pa.array([1, 2, 3, 4, 5])
265+
266+
input_schema = pa.schema([("x", pa.int64())])
267+
invocation = make_scalar_invocation(input_schema)
268+
func = TooManyRows(invocation=invocation, logger=structlog.get_logger())
269+
270+
generator = func.run()
271+
next(generator)
272+
273+
# Input has 3 rows, output has 5 rows
274+
input_batch = pa.RecordBatch.from_pydict({"x": [1, 2, 3]}, schema=input_schema)
275+
output = generator.send(ProtocolInput(batch=input_batch))
276+
277+
# Should have an exception log message
278+
assert output.log_message is not None
279+
assert output.log_message.level == Level.EXCEPTION
280+
# Check that the error message mentions "more rows" (lines 134-142)
281+
assert "more rows than input" in output.log_message.message.lower()
282+
254283
def test_empty_batch(self) -> None:
255284
"""Test handling of empty batches."""
256285

tests/test_exceptions.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
"""Tests for vgi.exceptions module."""
2+
3+
import pyarrow as pa
4+
import pytest
5+
6+
from tests.conftest import make_schema
7+
from vgi.exceptions import ExecutionIdentifierError, SchemaValidationError
8+
9+
10+
class TestExecutionIdentifierError:
11+
"""Tests for ExecutionIdentifierError."""
12+
13+
def test_basic_error(self) -> None:
14+
"""Test that ExecutionIdentifierError can be raised."""
15+
with pytest.raises(ExecutionIdentifierError):
16+
raise ExecutionIdentifierError("test message")
17+
18+
19+
class TestSchemaValidationError:
20+
"""Tests for SchemaValidationError detailed message building."""
21+
22+
def test_simple_message_without_schemas(self) -> None:
23+
"""Test error with just a message, no schemas."""
24+
error = SchemaValidationError("Simple error")
25+
assert str(error) == "Simple error"
26+
assert error.expected is None
27+
assert error.actual is None
28+
29+
def test_message_with_context(self) -> None:
30+
"""Test that context is included in detailed message."""
31+
expected = make_schema([pa.field("x", pa.int64())])
32+
actual = make_schema([pa.field("y", pa.int64())])
33+
34+
error = SchemaValidationError(
35+
"Schema mismatch",
36+
expected=expected,
37+
actual=actual,
38+
context="output from transform()",
39+
)
40+
41+
message = str(error)
42+
assert "Context: output from transform()" in message
43+
assert error.context == "output from transform()"
44+
45+
def test_missing_fields_reported(self) -> None:
46+
"""Test that missing fields are reported."""
47+
expected = make_schema(
48+
[
49+
pa.field("a", pa.int64()),
50+
pa.field("b", pa.string()),
51+
]
52+
)
53+
actual = make_schema([pa.field("a", pa.int64())])
54+
55+
error = SchemaValidationError(
56+
"Schema mismatch",
57+
expected=expected,
58+
actual=actual,
59+
)
60+
61+
message = str(error)
62+
assert "Missing fields (expected but not found):" in message
63+
assert "b: string" in message
64+
65+
def test_extra_fields_reported(self) -> None:
66+
"""Test that extra fields are reported."""
67+
expected = make_schema([pa.field("a", pa.int64())])
68+
actual = make_schema(
69+
[
70+
pa.field("a", pa.int64()),
71+
pa.field("extra", pa.float64()),
72+
]
73+
)
74+
75+
error = SchemaValidationError(
76+
"Schema mismatch",
77+
expected=expected,
78+
actual=actual,
79+
)
80+
81+
message = str(error)
82+
assert "Extra fields (found but not expected):" in message
83+
assert "extra: double" in message
84+
85+
def test_type_mismatch_reported(self) -> None:
86+
"""Test that type mismatches are reported (lines 116-119, 149-151)."""
87+
expected = make_schema(
88+
[
89+
pa.field("x", pa.int64()),
90+
pa.field("y", pa.string()),
91+
]
92+
)
93+
actual = make_schema(
94+
[
95+
pa.field("x", pa.float64()), # Different type
96+
pa.field("y", pa.string()),
97+
]
98+
)
99+
100+
error = SchemaValidationError(
101+
"Schema mismatch",
102+
expected=expected,
103+
actual=actual,
104+
)
105+
106+
message = str(error)
107+
assert "Type mismatches:" in message
108+
assert "x: expected int64, got double" in message
109+
110+
def test_nullable_mismatch_reported(self) -> None:
111+
"""Test that nullable mismatches are reported (lines 120-123)."""
112+
expected = make_schema(
113+
[
114+
pa.field("x", pa.int64(), nullable=False),
115+
]
116+
)
117+
actual = make_schema(
118+
[
119+
pa.field("x", pa.int64(), nullable=True),
120+
]
121+
)
122+
123+
error = SchemaValidationError(
124+
"Schema mismatch",
125+
expected=expected,
126+
actual=actual,
127+
)
128+
129+
message = str(error)
130+
assert "Type mismatches:" in message
131+
assert "x: expected non-nullable, got nullable" in message
132+
133+
def test_field_order_difference_reported(self) -> None:
134+
"""Test that field order differences are reported (lines 128-131, 155-157)."""
135+
expected = make_schema(
136+
[
137+
pa.field("a", pa.int64()),
138+
pa.field("b", pa.string()),
139+
pa.field("c", pa.float64()),
140+
]
141+
)
142+
# Same fields, different order
143+
actual = make_schema(
144+
[
145+
pa.field("c", pa.float64()),
146+
pa.field("a", pa.int64()),
147+
pa.field("b", pa.string()),
148+
]
149+
)
150+
151+
error = SchemaValidationError(
152+
"Schema mismatch",
153+
expected=expected,
154+
actual=actual,
155+
)
156+
157+
message = str(error)
158+
assert "Field order differs:" in message
159+
assert "Expected: ['a', 'b', 'c']" in message
160+
assert "Actual: ['c', 'a', 'b']" in message
161+
162+
def test_schema_summary_included(self) -> None:
163+
"""Test that full schema summary is included."""
164+
expected = make_schema([pa.field("x", pa.int64(), nullable=True)])
165+
actual = make_schema([pa.field("y", pa.string(), nullable=False)])
166+
167+
error = SchemaValidationError(
168+
"Schema mismatch",
169+
expected=expected,
170+
actual=actual,
171+
)
172+
173+
message = str(error)
174+
assert "Expected schema:" in message
175+
assert "x: int64 (nullable)" in message
176+
assert "Actual schema:" in message
177+
assert "y: string" in message
178+
179+
def test_multiple_type_mismatches(self) -> None:
180+
"""Test multiple type mismatches are all reported."""
181+
expected = make_schema(
182+
[
183+
pa.field("a", pa.int64()),
184+
pa.field("b", pa.string()),
185+
pa.field("c", pa.float64()),
186+
]
187+
)
188+
actual = make_schema(
189+
[
190+
pa.field("a", pa.int32()), # Mismatch
191+
pa.field("b", pa.binary()), # Mismatch
192+
pa.field("c", pa.float64()), # OK
193+
]
194+
)
195+
196+
error = SchemaValidationError(
197+
"Schema mismatch",
198+
expected=expected,
199+
actual=actual,
200+
)
201+
202+
message = str(error)
203+
assert "a: expected int64, got int32" in message
204+
assert "b: expected string, got binary" in message
205+
206+
def test_order_not_checked_when_other_differences_exist(self) -> None:
207+
"""Test that order is only checked when fields match exactly."""
208+
expected = make_schema(
209+
[
210+
pa.field("a", pa.int64()),
211+
pa.field("b", pa.string()),
212+
]
213+
)
214+
# Missing field 'b', has extra 'c' - order check shouldn't trigger
215+
actual = make_schema(
216+
[
217+
pa.field("c", pa.float64()),
218+
pa.field("a", pa.int64()),
219+
]
220+
)
221+
222+
error = SchemaValidationError(
223+
"Schema mismatch",
224+
expected=expected,
225+
actual=actual,
226+
)
227+
228+
message = str(error)
229+
# Should NOT have order differs since there are missing/extra fields
230+
assert "Field order differs:" not in message
231+
assert "Missing fields" in message
232+
assert "Extra fields" in message

0 commit comments

Comments
 (0)