_timelex.__init__ only checked for a `read` attribute on the input
stream, which passes for anything that auto-vivifies attributes, like
a MagicMock. If one of those reaches parse() (typically by accident,
e.g. a mocking mistake in a caller's own test suite), get_token() never
sees a real empty string to signal EOF and keeps calling read(1)
forever, allocating memory the whole time until the process runs out.
_timelex now checks that read() actually returned a str each time it's
called, and raises a clear TypeError the first time it doesn't, rather
than looping.
Fixes dateutil#1428
Fixes #1428.
If something that isn't a string or a real character stream reaches parse(), _timelex.__init__ only checks for a read attribute before accepting it. That passes for anything that auto-vivifies attributes, like a MagicMock, which is exactly how this got reported: a test suite accidentally passed a mock object into parse() instead of a real value.
Once that happens, get_token() calls self.instream.read(1) in a loop waiting for a falsy value to signal EOF. A MagicMock is never falsy and never returns a real empty string, so the loop never terminates, and it keeps allocating memory the whole time until the process runs out and the test suite just hangs with no useful error, which is what made this so hard to track down originally.
_timelex now checks that read() actually returned a str on every call, and raises a clear TypeError the moment it doesn't, instead of looping.
Added a regression test that reproduces the exact scenario from the issue (a MagicMock reaching parse()), verified against both the fixed and unfixed code with a bounded subprocess timeout, since the whole point of the bug is that it doesn't fail cleanly without the fix.