Skip to content

Commit 5e19a00

Browse files
committed
fix + tests
1 parent f00d36b commit 5e19a00

File tree

2 files changed

+63
-7
lines changed

2 files changed

+63
-7
lines changed

llsd/base.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,26 @@ def _reset(self, something):
410410
# string is so large that the overhead of copying it into a
411411
# BytesIO is significant, advise caller to pass a stream instead.
412412
self._stream = io.BytesIO(something)
413-
elif something.seekable():
414-
# 'something' is already a seekable stream, use directly
415-
self._stream = something
413+
elif isinstance(something, io.IOBase):
414+
# 'something' is a proper IO stream
415+
if something.seekable():
416+
# Seekable stream, use directly
417+
self._stream = something
418+
elif something.readable():
419+
# Readable but not seekable, wrap in BufferedReader
420+
self._stream = io.BufferedReader(something)
421+
else:
422+
raise LLSDParseError(
423+
"Cannot parse LLSD from non-readable stream."
424+
)
416425
else:
417-
# 'something' isn't seekable, wrap in BufferedReader
418-
# (let BufferedReader handle the problem of passing an
419-
# inappropriate object)
420-
self._stream = io.BufferedReader(something)
426+
# Invalid input type - raise a clear error
427+
# This catches MagicMock and other non-stream objects that might
428+
# have read/seek attributes but aren't actual IO streams
429+
raise LLSDParseError(
430+
f"Cannot parse LLSD from {type(something).__name__}. "
431+
"Expected bytes or a file-like object (io.IOBase subclass)."
432+
)
421433

422434
def starts_with(self, pattern):
423435
"""

tests/llsd_test.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,3 +1977,47 @@ def test_uuid_map_key(self):
19771977
self.assertEqual(llsd.format_notation(llsdmap), b"{'00000000-0000-0000-0000-000000000000':'uuid'}")
19781978

19791979

1980+
class InvalidInputTypes(unittest.TestCase):
1981+
'''
1982+
Tests for handling invalid input types that should raise LLSDParseError
1983+
instead of hanging or consuming infinite memory.
1984+
'''
1985+
1986+
def test_parse_magicmock_raises_error(self):
1987+
'''
1988+
Parsing a MagicMock object should raise LLSDParseError, not hang.
1989+
This is a regression test for a bug where llsd.parse() would go into
1990+
an infinite loop when passed a MagicMock (e.g., from an improperly
1991+
mocked requests.Response.content).
1992+
'''
1993+
from unittest.mock import MagicMock
1994+
mock = MagicMock()
1995+
with self.assertRaises(llsd.LLSDParseError) as context:
1996+
llsd.parse(mock)
1997+
self.assertIn('MagicMock', str(context.exception))
1998+
1999+
def test_parse_string_raises_error(self):
2000+
'''
2001+
Parsing a string (not bytes) should raise LLSDParseError.
2002+
'''
2003+
with self.assertRaises(llsd.LLSDParseError) as context:
2004+
llsd.parse('not bytes')
2005+
self.assertIn('str', str(context.exception))
2006+
2007+
def test_parse_none_raises_error(self):
2008+
'''
2009+
Parsing None should raise LLSDParseError.
2010+
'''
2011+
with self.assertRaises(llsd.LLSDParseError) as context:
2012+
llsd.parse(None)
2013+
self.assertIn('NoneType', str(context.exception))
2014+
2015+
def test_parse_int_raises_error(self):
2016+
'''
2017+
Parsing an integer should raise LLSDParseError.
2018+
'''
2019+
with self.assertRaises(llsd.LLSDParseError) as context:
2020+
llsd.parse(42)
2021+
self.assertIn('int', str(context.exception))
2022+
2023+

0 commit comments

Comments
 (0)