get mostly-full coverage for rlcompleter, rename, export
This commit is contained in:
parent
8882e6f64e
commit
bdef446ad4
12
docs/api.md
12
docs/api.md
|
@ -226,18 +226,18 @@ The code-entry Helper object has the following API:
|
||||||
`MustChooseNameplateFirstError` will be raised. May only be called once,
|
`MustChooseNameplateFirstError` will be raised. May only be called once,
|
||||||
after which `AlreadyChoseWordsError` is raised.
|
after which `AlreadyChoseWordsError` is raised.
|
||||||
|
|
||||||
The `rlcompleter` wrapper is a function that knows how to use the code-entry
|
The `input_with_completion` wrapper is a function that knows how to use the
|
||||||
helper to do tab completion of wormhole codes:
|
code-entry helper to do tab completion of wormhole codes:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from wormhole import create, rlcompleter_helper
|
from wormhole import create, input_with_completion
|
||||||
w = create(appid, relay_url, reactor)
|
w = create(appid, relay_url, reactor)
|
||||||
rlcompleter_helper("Wormhole code:", w.input_code(), reactor)
|
input_with_completion("Wormhole code:", w.input_code(), reactor)
|
||||||
d = w.when_code()
|
d = w.when_code()
|
||||||
```
|
```
|
||||||
|
|
||||||
This helper runs python's `rawinput()` function inside a thread, since
|
This helper runs python's (raw) `input()` function inside a thread, since
|
||||||
`rawinput()` normally blocks.
|
`input()` normally blocks.
|
||||||
|
|
||||||
The two machines participating in the wormhole setup are not distinguished:
|
The two machines participating in the wormhole setup are not distinguished:
|
||||||
it doesn't matter which one goes first, and both use the same Wormhole
|
it doesn't matter which one goes first, and both use the same Wormhole
|
||||||
|
|
|
@ -2,3 +2,8 @@
|
||||||
from ._version import get_versions
|
from ._version import get_versions
|
||||||
__version__ = get_versions()['version']
|
__version__ = get_versions()['version']
|
||||||
del get_versions
|
del get_versions
|
||||||
|
|
||||||
|
from .wormhole import create
|
||||||
|
from ._rlcompleter import input_with_completion
|
||||||
|
|
||||||
|
__all__ = ["create", "input_with_completion", "__version__"]
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
import sys
|
import traceback
|
||||||
import six
|
from sys import stderr
|
||||||
|
try:
|
||||||
|
import readline
|
||||||
|
except ImportError:
|
||||||
|
readline = None
|
||||||
|
from six.moves import input
|
||||||
from attr import attrs, attrib
|
from attr import attrs, attrib
|
||||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||||
from twisted.internet.threads import deferToThread, blockingCallFromThread
|
from twisted.internet.threads import deferToThread, blockingCallFromThread
|
||||||
|
|
||||||
import os
|
#import os
|
||||||
errf = None
|
#errf = None
|
||||||
if os.path.exists("err"):
|
#if os.path.exists("err"):
|
||||||
errf = open("err", "w")
|
# errf = open("err", "w")
|
||||||
def debug(*args, **kwargs):
|
def debug(*args, **kwargs):
|
||||||
if errf:
|
# if errf:
|
||||||
kwargs["file"] = errf
|
# kwargs["file"] = errf
|
||||||
print(*args, **kwargs)
|
# print(*args, **kwargs)
|
||||||
errf.flush()
|
# errf.flush()
|
||||||
|
pass
|
||||||
|
|
||||||
@attrs
|
@attrs
|
||||||
class CodeInputter(object):
|
class CodeInputter(object):
|
||||||
|
@ -28,20 +34,19 @@ class CodeInputter(object):
|
||||||
def bcft(self, f, *a, **kw):
|
def bcft(self, f, *a, **kw):
|
||||||
return blockingCallFromThread(self._reactor, f, *a, **kw)
|
return blockingCallFromThread(self._reactor, f, *a, **kw)
|
||||||
|
|
||||||
def wrap_completer(self, text, state):
|
def completer(self, text, state):
|
||||||
try:
|
try:
|
||||||
return self.completer(text, state)
|
return self._wrapped_completer(text, state)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# completer exceptions are normally silently discarded, which
|
# completer exceptions are normally silently discarded, which
|
||||||
# makes debugging challenging
|
# makes debugging challenging
|
||||||
print("completer exception: %s" % e)
|
print("completer exception: %s" % e)
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def completer(self, text, state):
|
def _wrapped_completer(self, text, state):
|
||||||
self.used_completion = True
|
self.used_completion = True
|
||||||
import readline
|
# if we get here, then readline must be active
|
||||||
ct = readline.get_completion_type()
|
ct = readline.get_completion_type()
|
||||||
if state == 0:
|
if state == 0:
|
||||||
debug("completer starting (%s) (state=0) (ct=%d)" % (text, ct))
|
debug("completer starting (%s) (state=0) (ct=%d)" % (text, ct))
|
||||||
|
@ -109,21 +114,20 @@ class CodeInputter(object):
|
||||||
self._input_helper.choose_nameplate(nameplate)
|
self._input_helper.choose_nameplate(nameplate)
|
||||||
self._input_helper.choose_words(words)
|
self._input_helper.choose_words(words)
|
||||||
|
|
||||||
def input_code_with_completion(prompt, input_helper, reactor):
|
def _input_code_with_completion(prompt, input_helper, reactor):
|
||||||
c = CodeInputter(input_helper, reactor)
|
c = CodeInputter(input_helper, reactor)
|
||||||
try:
|
if readline is not None:
|
||||||
import readline
|
|
||||||
if readline.__doc__ and "libedit" in readline.__doc__:
|
if readline.__doc__ and "libedit" in readline.__doc__:
|
||||||
readline.parse_and_bind("bind ^I rl_complete")
|
readline.parse_and_bind("bind ^I rl_complete")
|
||||||
else:
|
else:
|
||||||
readline.parse_and_bind("tab: complete")
|
readline.parse_and_bind("tab: complete")
|
||||||
readline.set_completer(c.wrap_completer)
|
readline.set_completer(c.completer)
|
||||||
readline.set_completer_delims("")
|
readline.set_completer_delims("")
|
||||||
debug("==== readline-based completion is prepared")
|
debug("==== readline-based completion is prepared")
|
||||||
except ImportError:
|
else:
|
||||||
debug("==== unable to import readline, disabling completion")
|
debug("==== unable to import readline, disabling completion")
|
||||||
pass
|
pass
|
||||||
code = six.moves.input(prompt)
|
code = input(prompt)
|
||||||
# Code is str(bytes) on py2, and str(unicode) on py3. We want unicode.
|
# Code is str(bytes) on py2, and str(unicode) on py3. We want unicode.
|
||||||
if isinstance(code, bytes):
|
if isinstance(code, bytes):
|
||||||
code = code.decode("utf-8")
|
code = code.decode("utf-8")
|
||||||
|
@ -137,8 +141,7 @@ def warn_readline():
|
||||||
# input_code_with_completion() when SIGINT happened, the readline
|
# input_code_with_completion() when SIGINT happened, the readline
|
||||||
# thread will be blocked waiting for something on stdin. Trick the
|
# thread will be blocked waiting for something on stdin. Trick the
|
||||||
# user into satisfying the blocking read so we can exit.
|
# user into satisfying the blocking read so we can exit.
|
||||||
print("\nCommand interrupted: please press Return to quit",
|
print("\nCommand interrupted: please press Return to quit", file=stderr)
|
||||||
file=sys.stderr)
|
|
||||||
|
|
||||||
# Other potential approaches to this problem:
|
# Other potential approaches to this problem:
|
||||||
# * hard-terminate our process with os._exit(1), but make sure the
|
# * hard-terminate our process with os._exit(1), but make sure the
|
||||||
|
@ -165,10 +168,10 @@ def warn_readline():
|
||||||
# readline finish.
|
# readline finish.
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def rlcompleter_helper(prompt, input_helper, reactor):
|
def input_with_completion(prompt, input_helper, reactor):
|
||||||
t = reactor.addSystemEventTrigger("before", "shutdown", warn_readline)
|
t = reactor.addSystemEventTrigger("before", "shutdown", warn_readline)
|
||||||
#input_helper.refresh_nameplates()
|
#input_helper.refresh_nameplates()
|
||||||
used_completion = yield deferToThread(input_code_with_completion,
|
used_completion = yield deferToThread(_input_code_with_completion,
|
||||||
prompt, input_helper, reactor)
|
prompt, input_helper, reactor)
|
||||||
reactor.removeSystemEventTrigger(t)
|
reactor.removeSystemEventTrigger(t)
|
||||||
returnValue(used_completion)
|
returnValue(used_completion)
|
||||||
|
|
|
@ -5,7 +5,7 @@ from humanize import naturalsize
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||||
from twisted.python import log
|
from twisted.python import log
|
||||||
from .. import wormhole, __version__
|
from wormhole import create, input_with_completion, __version__
|
||||||
from ..transit import TransitReceiver
|
from ..transit import TransitReceiver
|
||||||
from ..errors import TransferError, WormholeClosedError, NoTorError
|
from ..errors import TransferError, WormholeClosedError, NoTorError
|
||||||
from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr,
|
from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr,
|
||||||
|
@ -64,11 +64,11 @@ class TwistedReceiver:
|
||||||
|
|
||||||
wh = CLIWelcomeHandler(self.args.relay_url, __version__,
|
wh = CLIWelcomeHandler(self.args.relay_url, __version__,
|
||||||
self.args.stderr)
|
self.args.stderr)
|
||||||
w = wormhole.create(self.args.appid or APPID, self.args.relay_url,
|
w = create(self.args.appid or APPID, self.args.relay_url,
|
||||||
self._reactor,
|
self._reactor,
|
||||||
tor_manager=self._tor_manager,
|
tor_manager=self._tor_manager,
|
||||||
timing=self.args.timing,
|
timing=self.args.timing,
|
||||||
welcome_handler=wh.handle_welcome)
|
welcome_handler=wh.handle_welcome)
|
||||||
# I wanted to do this instead:
|
# I wanted to do this instead:
|
||||||
#
|
#
|
||||||
# try:
|
# try:
|
||||||
|
@ -168,10 +168,10 @@ class TwistedReceiver:
|
||||||
if code:
|
if code:
|
||||||
w.set_code(code)
|
w.set_code(code)
|
||||||
else:
|
else:
|
||||||
from .._rlcompleter import rlcompleter_helper
|
prompt = "Enter receive wormhole code: "
|
||||||
used_completion = yield rlcompleter_helper("Enter receive wormhole code: ",
|
used_completion = yield input_with_completion(prompt,
|
||||||
w.input_code(),
|
w.input_code(),
|
||||||
self._reactor)
|
self._reactor)
|
||||||
if not used_completion:
|
if not used_completion:
|
||||||
print(" (note: you can use <Tab> to complete words)",
|
print(" (note: you can use <Tab> to complete words)",
|
||||||
file=self.args.stderr)
|
file=self.args.stderr)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from twisted.protocols import basic
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||||
from ..errors import TransferError, WormholeClosedError, NoTorError
|
from ..errors import TransferError, WormholeClosedError, NoTorError
|
||||||
from .. import wormhole, __version__
|
from wormhole import create, __version__
|
||||||
from ..transit import TransitSender
|
from ..transit import TransitSender
|
||||||
from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr
|
from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr
|
||||||
from .welcome import CLIWelcomeHandler
|
from .welcome import CLIWelcomeHandler
|
||||||
|
@ -55,11 +55,11 @@ class Sender:
|
||||||
|
|
||||||
wh = CLIWelcomeHandler(self._args.relay_url, __version__,
|
wh = CLIWelcomeHandler(self._args.relay_url, __version__,
|
||||||
self._args.stderr)
|
self._args.stderr)
|
||||||
w = wormhole.create(self._args.appid or APPID, self._args.relay_url,
|
w = create(self._args.appid or APPID, self._args.relay_url,
|
||||||
self._reactor,
|
self._reactor,
|
||||||
tor_manager=self._tor_manager,
|
tor_manager=self._tor_manager,
|
||||||
timing=self._timing,
|
timing=self._timing,
|
||||||
welcome_handler=wh.handle_welcome)
|
welcome_handler=wh.handle_welcome)
|
||||||
d = self._go(w)
|
d = self._go(w)
|
||||||
|
|
||||||
# if we succeed, we should close and return the w.close results
|
# if we succeed, we should close and return the w.close results
|
||||||
|
|
336
src/wormhole/test/test_rlcompleter.py
Normal file
336
src/wormhole/test/test_rlcompleter.py
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
from __future__ import print_function, absolute_import, unicode_literals
|
||||||
|
import mock
|
||||||
|
from itertools import count
|
||||||
|
from twisted.trial import unittest
|
||||||
|
from twisted.internet import reactor
|
||||||
|
from twisted.internet.defer import inlineCallbacks
|
||||||
|
from twisted.internet.threads import deferToThread
|
||||||
|
from .._rlcompleter import (input_with_completion,
|
||||||
|
_input_code_with_completion,
|
||||||
|
CodeInputter, warn_readline)
|
||||||
|
APPID = "appid"
|
||||||
|
|
||||||
|
class Input(unittest.TestCase):
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_wrapper(self):
|
||||||
|
helper = object()
|
||||||
|
trueish = object()
|
||||||
|
with mock.patch("wormhole._rlcompleter._input_code_with_completion",
|
||||||
|
return_value=trueish) as m:
|
||||||
|
used_completion = yield input_with_completion("prompt:", helper,
|
||||||
|
reactor)
|
||||||
|
self.assertIs(used_completion, trueish)
|
||||||
|
self.assertEqual(m.mock_calls,
|
||||||
|
[mock.call("prompt:", helper, reactor)])
|
||||||
|
# note: if this test fails, the warn_readline() message will probably
|
||||||
|
# get written to stderr
|
||||||
|
|
||||||
|
class Sync(unittest.TestCase):
|
||||||
|
# exercise _input_code_with_completion, which uses the blocking builtin
|
||||||
|
# "input()" function, hence _input_code_with_completion is usually in a
|
||||||
|
# thread with deferToThread
|
||||||
|
|
||||||
|
@mock.patch("wormhole._rlcompleter.CodeInputter")
|
||||||
|
@mock.patch("wormhole._rlcompleter.readline",
|
||||||
|
__doc__="I am GNU readline")
|
||||||
|
@mock.patch("wormhole._rlcompleter.input", return_value="code")
|
||||||
|
def test_readline(self, input, readline, ci):
|
||||||
|
c = mock.Mock(name="inhibit parenting")
|
||||||
|
c.completer = object()
|
||||||
|
trueish = object()
|
||||||
|
c.used_completion = trueish
|
||||||
|
ci.configure_mock(return_value=c)
|
||||||
|
prompt = object()
|
||||||
|
input_helper = object()
|
||||||
|
reactor = object()
|
||||||
|
used = _input_code_with_completion(prompt, input_helper, reactor)
|
||||||
|
self.assertIs(used, trueish)
|
||||||
|
self.assertEqual(ci.mock_calls, [mock.call(input_helper, reactor)])
|
||||||
|
self.assertEqual(c.mock_calls, [mock.call.finish("code")])
|
||||||
|
self.assertEqual(input.mock_calls, [mock.call(prompt)])
|
||||||
|
self.assertEqual(readline.mock_calls,
|
||||||
|
[mock.call.parse_and_bind("tab: complete"),
|
||||||
|
mock.call.set_completer(c.completer),
|
||||||
|
mock.call.set_completer_delims(""),
|
||||||
|
])
|
||||||
|
|
||||||
|
@mock.patch("wormhole._rlcompleter.CodeInputter")
|
||||||
|
@mock.patch("wormhole._rlcompleter.readline")
|
||||||
|
@mock.patch("wormhole._rlcompleter.input", return_value="code")
|
||||||
|
def test_readline_no_docstring(self, input, readline, ci):
|
||||||
|
del readline.__doc__ # when in doubt, it assumes GNU readline
|
||||||
|
c = mock.Mock(name="inhibit parenting")
|
||||||
|
c.completer = object()
|
||||||
|
trueish = object()
|
||||||
|
c.used_completion = trueish
|
||||||
|
ci.configure_mock(return_value=c)
|
||||||
|
prompt = object()
|
||||||
|
input_helper = object()
|
||||||
|
reactor = object()
|
||||||
|
used = _input_code_with_completion(prompt, input_helper, reactor)
|
||||||
|
self.assertIs(used, trueish)
|
||||||
|
self.assertEqual(ci.mock_calls, [mock.call(input_helper, reactor)])
|
||||||
|
self.assertEqual(c.mock_calls, [mock.call.finish("code")])
|
||||||
|
self.assertEqual(input.mock_calls, [mock.call(prompt)])
|
||||||
|
self.assertEqual(readline.mock_calls,
|
||||||
|
[mock.call.parse_and_bind("tab: complete"),
|
||||||
|
mock.call.set_completer(c.completer),
|
||||||
|
mock.call.set_completer_delims(""),
|
||||||
|
])
|
||||||
|
|
||||||
|
@mock.patch("wormhole._rlcompleter.CodeInputter")
|
||||||
|
@mock.patch("wormhole._rlcompleter.readline",
|
||||||
|
__doc__="I am libedit")
|
||||||
|
@mock.patch("wormhole._rlcompleter.input", return_value="code")
|
||||||
|
def test_libedit(self, input, readline, ci):
|
||||||
|
c = mock.Mock(name="inhibit parenting")
|
||||||
|
c.completer = object()
|
||||||
|
trueish = object()
|
||||||
|
c.used_completion = trueish
|
||||||
|
ci.configure_mock(return_value=c)
|
||||||
|
prompt = object()
|
||||||
|
input_helper = object()
|
||||||
|
reactor = object()
|
||||||
|
used = _input_code_with_completion(prompt, input_helper, reactor)
|
||||||
|
self.assertIs(used, trueish)
|
||||||
|
self.assertEqual(ci.mock_calls, [mock.call(input_helper, reactor)])
|
||||||
|
self.assertEqual(c.mock_calls, [mock.call.finish("code")])
|
||||||
|
self.assertEqual(input.mock_calls, [mock.call(prompt)])
|
||||||
|
self.assertEqual(readline.mock_calls,
|
||||||
|
[mock.call.parse_and_bind("bind ^I rl_complete"),
|
||||||
|
mock.call.set_completer(c.completer),
|
||||||
|
mock.call.set_completer_delims(""),
|
||||||
|
])
|
||||||
|
|
||||||
|
@mock.patch("wormhole._rlcompleter.CodeInputter")
|
||||||
|
@mock.patch("wormhole._rlcompleter.readline", None)
|
||||||
|
@mock.patch("wormhole._rlcompleter.input", return_value="code")
|
||||||
|
def test_no_readline(self, input, ci):
|
||||||
|
c = mock.Mock(name="inhibit parenting")
|
||||||
|
c.completer = object()
|
||||||
|
trueish = object()
|
||||||
|
c.used_completion = trueish
|
||||||
|
ci.configure_mock(return_value=c)
|
||||||
|
prompt = object()
|
||||||
|
input_helper = object()
|
||||||
|
reactor = object()
|
||||||
|
used = _input_code_with_completion(prompt, input_helper, reactor)
|
||||||
|
self.assertIs(used, trueish)
|
||||||
|
self.assertEqual(ci.mock_calls, [mock.call(input_helper, reactor)])
|
||||||
|
self.assertEqual(c.mock_calls, [mock.call.finish("code")])
|
||||||
|
self.assertEqual(input.mock_calls, [mock.call(prompt)])
|
||||||
|
|
||||||
|
@mock.patch("wormhole._rlcompleter.CodeInputter")
|
||||||
|
@mock.patch("wormhole._rlcompleter.readline", None)
|
||||||
|
@mock.patch("wormhole._rlcompleter.input", return_value=b"code")
|
||||||
|
def test_bytes(self, input, ci):
|
||||||
|
c = mock.Mock(name="inhibit parenting")
|
||||||
|
c.completer = object()
|
||||||
|
trueish = object()
|
||||||
|
c.used_completion = trueish
|
||||||
|
ci.configure_mock(return_value=c)
|
||||||
|
prompt = object()
|
||||||
|
input_helper = object()
|
||||||
|
reactor = object()
|
||||||
|
used = _input_code_with_completion(prompt, input_helper, reactor)
|
||||||
|
self.assertIs(used, trueish)
|
||||||
|
self.assertEqual(ci.mock_calls, [mock.call(input_helper, reactor)])
|
||||||
|
self.assertEqual(c.mock_calls, [mock.call.finish(u"code")])
|
||||||
|
self.assertEqual(input.mock_calls, [mock.call(prompt)])
|
||||||
|
|
||||||
|
def get_completions(c, prefix):
|
||||||
|
completions = []
|
||||||
|
for state in count(0):
|
||||||
|
text = c.completer(prefix, state)
|
||||||
|
if text is None:
|
||||||
|
return completions
|
||||||
|
completions.append(text)
|
||||||
|
|
||||||
|
class Completion(unittest.TestCase):
|
||||||
|
def test_simple(self):
|
||||||
|
# no actual completion
|
||||||
|
helper = mock.Mock()
|
||||||
|
c = CodeInputter(helper, "reactor")
|
||||||
|
c.finish("1-code-ghost")
|
||||||
|
self.assertFalse(c.used_completion)
|
||||||
|
self.assertEqual(helper.mock_calls,
|
||||||
|
[mock.call.choose_nameplate("1"),
|
||||||
|
mock.call.choose_words("code-ghost")])
|
||||||
|
|
||||||
|
@mock.patch("wormhole._rlcompleter.readline",
|
||||||
|
get_completion_type=mock.Mock(return_value=0))
|
||||||
|
def test_call(self, readline):
|
||||||
|
# check that it calls _commit_and_build_completions correctly
|
||||||
|
helper = mock.Mock()
|
||||||
|
c = CodeInputter(helper, "reactor")
|
||||||
|
|
||||||
|
# pretend nameplates: 1, 12, 34
|
||||||
|
|
||||||
|
# first call will be with "1"
|
||||||
|
cabc = mock.Mock(return_value=["1", "12"])
|
||||||
|
c._commit_and_build_completions = cabc
|
||||||
|
|
||||||
|
self.assertEqual(get_completions(c, "1"), ["1", "12"])
|
||||||
|
self.assertEqual(cabc.mock_calls, [mock.call("1")])
|
||||||
|
|
||||||
|
# then "12"
|
||||||
|
cabc.reset_mock()
|
||||||
|
cabc.configure_mock(return_value=["12"])
|
||||||
|
self.assertEqual(get_completions(c, "12"), ["12"])
|
||||||
|
self.assertEqual(cabc.mock_calls, [mock.call("12")])
|
||||||
|
|
||||||
|
# now we have three "a" words: "and", "ark", "aaah!zombies!!"
|
||||||
|
cabc.reset_mock()
|
||||||
|
cabc.configure_mock(return_value=["aargh", "ark", "aaah!zombies!!"])
|
||||||
|
self.assertEqual(get_completions(c, "12-a"),
|
||||||
|
["aargh", "ark", "aaah!zombies!!"])
|
||||||
|
self.assertEqual(cabc.mock_calls, [mock.call("12-a")])
|
||||||
|
|
||||||
|
cabc.reset_mock()
|
||||||
|
cabc.configure_mock(return_value=["aargh", "aaah!zombies!!"])
|
||||||
|
self.assertEqual(get_completions(c, "12-aa"),
|
||||||
|
["aargh", "aaah!zombies!!"])
|
||||||
|
self.assertEqual(cabc.mock_calls, [mock.call("12-aa")])
|
||||||
|
|
||||||
|
cabc.reset_mock()
|
||||||
|
cabc.configure_mock(return_value=["aaah!zombies!!"])
|
||||||
|
self.assertEqual(get_completions(c, "12-aaa"), ["aaah!zombies!!"])
|
||||||
|
self.assertEqual(cabc.mock_calls, [mock.call("12-aaa")])
|
||||||
|
|
||||||
|
c.finish("1-code")
|
||||||
|
self.assert_(c.used_completion)
|
||||||
|
|
||||||
|
def test_wrap_error(self):
|
||||||
|
helper = mock.Mock()
|
||||||
|
c = CodeInputter(helper, "reactor")
|
||||||
|
c._wrapped_completer = mock.Mock(side_effect=ValueError("oops"))
|
||||||
|
with mock.patch("wormhole._rlcompleter.traceback") as traceback:
|
||||||
|
with mock.patch("wormhole._rlcompleter.print") as mock_print:
|
||||||
|
with self.assertRaises(ValueError) as e:
|
||||||
|
c.completer("text", 0)
|
||||||
|
self.assertEqual(traceback.mock_calls, [mock.call.print_exc()])
|
||||||
|
self.assertEqual(mock_print.mock_calls,
|
||||||
|
[mock.call("completer exception: oops")])
|
||||||
|
self.assertEqual(str(e.exception), "oops")
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_build_completions(self):
|
||||||
|
rn = mock.Mock()
|
||||||
|
# InputHelper.get_nameplate_completions returns just the suffixes
|
||||||
|
gnc = mock.Mock() # get_nameplate_completions
|
||||||
|
cn = mock.Mock() # choose_nameplate
|
||||||
|
gwc = mock.Mock() # get_word_completions
|
||||||
|
cw = mock.Mock() # choose_words
|
||||||
|
helper = mock.Mock(refresh_nameplates=rn,
|
||||||
|
get_nameplate_completions=gnc,
|
||||||
|
choose_nameplate=cn,
|
||||||
|
get_word_completions=gwc,
|
||||||
|
choose_words=cw,
|
||||||
|
)
|
||||||
|
# this needs a real reactor, for blockingCallFromThread
|
||||||
|
c = CodeInputter(helper, reactor)
|
||||||
|
cabc = c._commit_and_build_completions
|
||||||
|
|
||||||
|
# 1 TAB -> 1, 12 (and refresh_nameplates)
|
||||||
|
gnc.configure_mock(return_value=["", "2"])
|
||||||
|
matches = yield deferToThread(cabc, "1")
|
||||||
|
self.assertEqual(matches, ["1", "12"])
|
||||||
|
self.assertEqual(rn.mock_calls, [mock.call()])
|
||||||
|
self.assertEqual(gnc.mock_calls, [mock.call("1")])
|
||||||
|
self.assertEqual(cn.mock_calls, [])
|
||||||
|
rn.reset_mock()
|
||||||
|
gnc.reset_mock()
|
||||||
|
|
||||||
|
# current: 12 TAB -> (and refresh_nameplates)
|
||||||
|
# want: 12 TAB -> 12- (and choose_nameplate)
|
||||||
|
gnc.configure_mock(return_value=[""])
|
||||||
|
matches = yield deferToThread(cabc, "12")
|
||||||
|
self.assertEqual(matches, ["12"]) # 12-
|
||||||
|
self.assertEqual(rn.mock_calls, [mock.call()])
|
||||||
|
self.assertEqual(gnc.mock_calls, [mock.call("12")])
|
||||||
|
self.assertEqual(cn.mock_calls, []) # 12
|
||||||
|
rn.reset_mock()
|
||||||
|
gnc.reset_mock()
|
||||||
|
|
||||||
|
# current: 12-a TAB -> and ark aaah!zombies!! (and choose nameplate)
|
||||||
|
gnc.configure_mock(side_effect=ValueError)
|
||||||
|
gwc.configure_mock(return_value=["nd", "rk", "aah!zombies!!"])
|
||||||
|
matches = yield deferToThread(cabc, "12-a")
|
||||||
|
# matches are always sorted
|
||||||
|
self.assertEqual(matches, ["12-aaah!zombies!!", "12-and", "12-ark"])
|
||||||
|
self.assertEqual(rn.mock_calls, [])
|
||||||
|
self.assertEqual(gnc.mock_calls, [])
|
||||||
|
self.assertEqual(cn.mock_calls, [mock.call("12")])
|
||||||
|
self.assertEqual(gwc.mock_calls, [mock.call("a")])
|
||||||
|
cn.reset_mock()
|
||||||
|
gwc.reset_mock()
|
||||||
|
|
||||||
|
# current: 12-and-b TAB -> bat bet but
|
||||||
|
gnc.configure_mock(side_effect=ValueError)
|
||||||
|
gwc.configure_mock(return_value=["at", "et", "ut"])
|
||||||
|
matches = yield deferToThread(cabc, "12-and-b")
|
||||||
|
self.assertEqual(matches, ["12-and-bat", "12-and-bet", "12-and-but"])
|
||||||
|
self.assertEqual(rn.mock_calls, [])
|
||||||
|
self.assertEqual(gnc.mock_calls, [])
|
||||||
|
self.assertEqual(cn.mock_calls, [])
|
||||||
|
self.assertEqual(gwc.mock_calls, [mock.call("and-b")])
|
||||||
|
cn.reset_mock()
|
||||||
|
gwc.reset_mock()
|
||||||
|
|
||||||
|
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")
|
||||||
|
with self.assertRaises(ValueError) as e:
|
||||||
|
c.finish("1")
|
||||||
|
self.assertEqual(str(e.exception), "incomplete wormhole code")
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_rollback_nameplate_during_completion(self):
|
||||||
|
helper = mock.Mock()
|
||||||
|
gwc = helper.get_word_completions = mock.Mock()
|
||||||
|
gwc.configure_mock(return_value=["de", "urt"])
|
||||||
|
c = CodeInputter(helper, reactor)
|
||||||
|
cabc = c._commit_and_build_completions
|
||||||
|
matches = yield deferToThread(cabc, "1-co") # this commits us to 1-
|
||||||
|
self.assertEqual(helper.mock_calls,
|
||||||
|
[mock.call.choose_nameplate("1"),
|
||||||
|
mock.call.get_word_completions("co")])
|
||||||
|
self.assertEqual(matches, ["1-code", "1-court"])
|
||||||
|
helper.reset_mock()
|
||||||
|
with self.assertRaises(ValueError) as e:
|
||||||
|
yield deferToThread(cabc, "2-co")
|
||||||
|
self.assertEqual(str(e.exception),
|
||||||
|
"nameplate (NN-) already entered, cannot go back")
|
||||||
|
self.assertEqual(helper.mock_calls, [])
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_rollback_nameplate_during_finish(self):
|
||||||
|
helper = mock.Mock()
|
||||||
|
gwc = helper.get_word_completions = mock.Mock()
|
||||||
|
gwc.configure_mock(return_value=["de", "urt"])
|
||||||
|
c = CodeInputter(helper, reactor)
|
||||||
|
cabc = c._commit_and_build_completions
|
||||||
|
matches = yield deferToThread(cabc, "1-co") # this commits us to 1-
|
||||||
|
self.assertEqual(helper.mock_calls,
|
||||||
|
[mock.call.choose_nameplate("1"),
|
||||||
|
mock.call.get_word_completions("co")])
|
||||||
|
self.assertEqual(matches, ["1-code", "1-court"])
|
||||||
|
helper.reset_mock()
|
||||||
|
with self.assertRaises(ValueError) as e:
|
||||||
|
c.finish("2-code")
|
||||||
|
self.assertEqual(str(e.exception),
|
||||||
|
"nameplate (NN-) already entered, cannot go back")
|
||||||
|
self.assertEqual(helper.mock_calls, [])
|
||||||
|
|
||||||
|
@mock.patch("wormhole._rlcompleter.stderr")
|
||||||
|
def test_warn_readline(self, stderr):
|
||||||
|
# there is no good way to test that this function gets used at the
|
||||||
|
# right time, since it involves a reactor and a "system event
|
||||||
|
# trigger", but let's at least make sure it's invocable
|
||||||
|
warn_readline()
|
||||||
|
expected ="\nCommand interrupted: please press Return to quit"
|
||||||
|
self.assertEqual(stderr.mock_calls, [mock.call.write(expected),
|
||||||
|
mock.call.write("\n")])
|
Loading…
Reference in New Issue
Block a user