Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 0259828

Browse files
committed
error handling
1 parent ed90de4 commit 0259828

3 files changed

Lines changed: 31 additions & 4 deletions

File tree

packages/jumpstarter-driver-qemu/jumpstarter_driver_qemu/driver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async def flash(self, source, partition: str | None = None):
5151
async with await FileWriteStream.from_path(self.parent.validate_partition(partition)) as stream:
5252
async with self.resource(source) as res:
5353
# Wrap with auto-decompression to handle .gz, .xz, .bz2, .zstd files
54-
async for chunk in AutoDecompressIterator(source=res.__aiter__()):
54+
async for chunk in AutoDecompressIterator(source=res):
5555
await stream.send(chunk)
5656

5757
@export

packages/jumpstarter/jumpstarter/streams/encoding.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,26 @@ class AutoDecompressIterator(AsyncIterator[bytes]):
162162

163163
source: AsyncIterator[bytes]
164164
_decompressor: Any = field(init=False, default=None)
165+
_compression: Compression | None = field(init=False, default=None)
165166
_detected: bool = field(init=False, default=False)
166167
_buffer: bytes = field(init=False, default=b"")
167168
_exhausted: bool = field(init=False, default=False)
168169

170+
def _call_decompressor(self, method_name: str, *args) -> bytes:
171+
"""Call decompressor method with error handling.
172+
173+
Args:
174+
method_name: decompressor method to call
175+
*args: Arguments to the method
176+
"""
177+
try:
178+
method = getattr(self._decompressor, method_name)
179+
return method(*args)
180+
except (zlib.error, lzma.LZMAError, OSError, zstd.ZstdError) as e:
181+
raise RuntimeError(
182+
f"Failed to decompress {self._compression}: {e}"
183+
) from e
184+
169185
async def _detect_compression(self) -> None:
170186
"""Read enough bytes to detect compression format."""
171187
# Buffer data until we have enough for detection
@@ -180,6 +196,7 @@ async def _detect_compression(self) -> None:
180196
# Detect compression from buffered data
181197
compression = detect_compression_from_signature(self._buffer)
182198
if compression is not None:
199+
self._compression = compression
183200
self._decompressor = create_decompressor(compression)
184201

185202
self._detected = True
@@ -194,7 +211,7 @@ async def __anext__(self) -> bytes:
194211
data = self._buffer
195212
self._buffer = b""
196213
if self._decompressor is not None:
197-
return self._decompressor.decompress(data)
214+
return self._call_decompressor("decompress", data)
198215
return data
199216

200217
# Stream exhausted
@@ -208,14 +225,14 @@ async def __anext__(self) -> bytes:
208225
self._exhausted = True
209226
# Flush any remaining data from decompressor (gzip needs this)
210227
if self._decompressor is not None and hasattr(self._decompressor, "flush"):
211-
remaining = self._decompressor.flush()
228+
remaining = self._call_decompressor("flush")
212229
self._decompressor = None
213230
if remaining:
214231
return remaining
215232
raise
216233

217234
if self._decompressor is not None:
218-
return self._decompressor.decompress(chunk)
235+
return self._call_decompressor("decompress", chunk)
219236
return chunk
220237

221238
def __aiter__(self) -> AsyncIterator[bytes]:

packages/jumpstarter/jumpstarter/streams/encoding_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,13 @@ async def test_large_data(self):
164164
original = b"x" * 1024 * 1024 # 1MB of data
165165
compressed = gzip.compress(original)
166166
await self._decompress_and_check(compressed, original, chunk_size=65536)
167+
168+
async def test_corrupted_gzip(self):
169+
"""Corrupted gzip data should raise RuntimeError with clear message."""
170+
# Create fake gzip data: valid signature but corrupted payload
171+
corrupted = b"\x1f\x8b\x08" + b"corrupted data here"
172+
173+
with pytest.raises(RuntimeError, match=r"Failed to decompress gzip:.*"):
174+
chunks = []
175+
async for chunk in AutoDecompressIterator(source=self._async_iter_from_bytes(corrupted, 16)):
176+
chunks.append(chunk)

0 commit comments

Comments
 (0)