_rlcompleter: use blockingCallFromThread for all Helper APIs

We were missing two (the calls to choose_nameplate() and choose_words() that
happen after the input() function has finished, but while we're still inside
the thread that makes it safe for input() to block). This almost certainly
caused the crash seen in issue #280.

Update the tests to match: CodeInputter.finish must now be called with
deferToThread from inside tests, or the internal blockingCallFromThread must
be stubbed out.
This commit is contained in:
Brian Warner 2018-02-14 00:40:18 -08:00
parent 3847339f43
commit af406a600e
2 changed files with 12 additions and 4 deletions

View File

@ -134,11 +134,13 @@ class CodeInputter(object):
raise AlreadyInputNameplateError("nameplate (%s-) already entered, cannot go back" % self._committed_nameplate)
else:
debug(" choose_nameplate(%s)" % nameplate)
self._input_helper.choose_nameplate(nameplate)
self.bcft(self._input_helper.choose_nameplate, nameplate)
debug(" choose_words(%s)" % words)
self._input_helper.choose_words(words)
self.bcft(self._input_helper.choose_words, words)
def _input_code_with_completion(prompt, input_helper, reactor):
# reminder: this all occurs in a separate thread. All calls to input_helper
# must go through blockingCallFromThread()
c = CodeInputter(input_helper, reactor)
if readline is not None:
if readline.__doc__ and "libedit" in readline.__doc__:

View File

@ -147,11 +147,15 @@ def get_completions(c, prefix):
return completions
completions.append(text)
def fake_blockingCallFromThread(f, *a, **kw):
return f(*a, **kw)
class Completion(unittest.TestCase):
def test_simple(self):
# no actual completion
helper = mock.Mock()
c = CodeInputter(helper, "reactor")
c.bcft = fake_blockingCallFromThread
c.finish("1-code-ghost")
self.assertFalse(c.used_completion)
self.assertEqual(helper.mock_calls,
@ -164,6 +168,7 @@ class Completion(unittest.TestCase):
# check that it calls _commit_and_build_completions correctly
helper = mock.Mock()
c = CodeInputter(helper, "reactor")
c.bcft = fake_blockingCallFromThread
# pretend nameplates: 1, 12, 34
@ -304,12 +309,13 @@ class Completion(unittest.TestCase):
self.assertEqual(gwc.mock_calls, [mock.call("and-b")])
gwc.reset_mock()
c.finish("12-and-bat")
yield deferToThread(c.finish, "12-and-bat")
self.assertEqual(cw.mock_calls, [mock.call("and-bat")])
def test_incomplete_code(self):
helper = mock.Mock()
c = CodeInputter(helper, "reactor")
c.bcft = fake_blockingCallFromThread
with self.assertRaises(KeyFormatError) as e:
c.finish("1")
self.assertEqual(str(e.exception), "incomplete wormhole code")
@ -349,7 +355,7 @@ class Completion(unittest.TestCase):
self.assertEqual(matches, ["1-code", "1-court"])
helper.reset_mock()
with self.assertRaises(AlreadyInputNameplateError) as e:
c.finish("2-code")
yield deferToThread(c.finish, "2-code")
self.assertEqual(str(e.exception),
"nameplate (1-) already entered, cannot go back")
self.assertEqual(helper.mock_calls, [])