Remove the curses dialog, thereby deprecating the --help and --dialog command line options (#3665)

* Remove the curses dialog, thereby deprecating the --help and --dialog command line options

* Deprecate --dialog and suppress --text
This commit is contained in:
Erica Portnoy 2016-10-21 15:45:57 -07:00 committed by Brad Warren
parent ce252bd6c9
commit d54cb76432
31 changed files with 41 additions and 526 deletions

View file

@ -103,7 +103,6 @@ addons:
- python-dev
- python-virtualenv
- gcc
- dialog
- libaugeas0
- libssl-dev
- libffi-dev

1
Vagrantfile vendored
View file

@ -29,6 +29,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# VM needs more memory to run test suite, got "OSError: [Errno 12]
# Cannot allocate memory" when running
# letsencrypt.client.tests.display.util_test.NcursesDisplayTest
# We may no longer need this.
v.memory = 1024
# Handle cases when the host is behind a private network by making the

View file

@ -100,5 +100,4 @@ def _vhost_menu(domain, vhosts):
def _more_info_vhost(vhost):
zope.component.getUtility(interfaces.IDisplay).notification(
"Virtual Host Information:{0}{1}{0}{2}".format(
os.linesep, "-" * (display_util.WIDTH - 4), str(vhost)),
height=display_util.HEIGHT)
os.linesep, "-" * (display_util.WIDTH - 4), str(vhost)))

View file

@ -376,7 +376,7 @@ class HelpfulArgumentParser(object):
# Do any post-parsing homework here
if self.verb == "renew" and not parsed_args.dialog_mode:
if self.verb == "renew":
parsed_args.noninteractive_mode = True
if parsed_args.staging or parsed_args.dry_run:
@ -388,17 +388,6 @@ class HelpfulArgumentParser(object):
if parsed_args.must_staple:
parsed_args.staple = True
# Avoid conflicting args
conficting_args = ["quiet", "noninteractive_mode", "text_mode"]
if parsed_args.dialog_mode:
for arg in conficting_args:
if getattr(parsed_args, arg):
raise errors.Error(
("Conflicting values for displayer."
" {0} conflicts with dialog_mode").format(arg))
elif parsed_args.verbose_count > flag_default("verbose_count"):
parsed_args.text_mode = True
if parsed_args.validate_hooks:
hooks.validate_hooks(parsed_args)
@ -677,16 +666,13 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
"e.g. -vvv.")
helpful.add(
None, "-t", "--text", dest="text_mode", action="store_true",
help="Use the text output instead of the curses UI.")
help=argparse.SUPPRESS)
helpful.add(
[None, "automation"], "-n", "--non-interactive", "--noninteractive",
dest="noninteractive_mode", action="store_true",
help="Run without ever asking for user input. This may require "
"additional command line flags; the client will try to explain "
"which ones are required if it finds one missing")
helpful.add(
None, "--dialog", dest="dialog_mode", action="store_true",
help="Run using interactive dialog menus")
helpful.add(
[None, "run", "certonly"],
"-d", "--domains", "--domain", dest="domains",
@ -890,6 +876,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
" shell constructs, so you can use this switch to disable it.")
helpful.add_deprecated_argument("--agree-dev-preview", 0)
helpful.add_deprecated_argument("--dialog", 0)
_create_subparsers(helpful)
_paths_parser(helpful)

View file

@ -219,7 +219,6 @@ def success_installation(domains):
_gen_https_names(domains),
os.linesep,
os.linesep.join(_gen_ssl_lab_urls(domains))),
height=(10 + len(domains)),
pause=False)
@ -241,7 +240,6 @@ def success_renewal(domains, action):
os.linesep,
os.linesep.join(_gen_ssl_lab_urls(domains)),
action),
height=(14 + len(domains)),
pause=False)

View file

@ -1,9 +1,7 @@
"""Certbot display."""
import logging
import os
import textwrap
import dialog
import six
import zope.interface
@ -12,17 +10,7 @@ from certbot import errors
from certbot.display import completer
logger = logging.getLogger(__name__)
WIDTH = 72
HEIGHT = 20
DSELECT_HELP = (
"Use the arrow keys or Tab to move between window elements. Space can be "
"used to complete the input path with the selected element in the "
"directory window. Pressing enter will select the currently highlighted "
"button.")
"""Help text on how to use dialog's dselect."""
# Display exit codes
OK = "ok"
@ -59,170 +47,6 @@ def _wrap_lines(msg):
return os.linesep.join(fixed_l)
def _clean(dialog_result):
"""Treat sundy python-dialog return codes as CANCEL
:param tuple dialog_result: (code, result)
:returns: the argument but with unknown codes set to -1 (Error)
:rtype: tuple
"""
code, result = dialog_result
if code in (OK, HELP):
return dialog_result
elif code in (CANCEL, ESC):
return (CANCEL, result)
else:
logger.debug("Surprising dialog return code %s", code)
return (CANCEL, result)
@zope.interface.implementer(interfaces.IDisplay)
class NcursesDisplay(object):
"""Ncurses-based display."""
def __init__(self, width=WIDTH, height=HEIGHT):
super(NcursesDisplay, self).__init__()
self.dialog = dialog.Dialog(autowidgetsize=True)
assert OK == self.dialog.DIALOG_OK, "What kind of absurdity is this?"
self.width = width
self.height = height
def notification(self, message, height=10, pause=False):
# pylint: disable=unused-argument
"""Display a notification to the user and wait for user acceptance.
.. todo:: It probably makes sense to use one of the transient message
types for pause. It isn't straightforward how best to approach
the matter though given the context of our messages.
http://pythondialog.sourceforge.net/doc/widgets.html#displaying-transient-messages
:param str message: Message to display
:param int height: Height of the dialog box
:param bool pause: Not applicable to NcursesDisplay
"""
self.dialog.msgbox(message)
def menu(self, message, choices, ok_label="OK", cancel_label="Cancel",
help_label="", **unused_kwargs):
"""Display a menu.
:param str message: title of menu
:param choices: menu lines, len must be > 0
:type choices: list of tuples (`tag`, `item`) tags must be unique or
list of items (tags will be enumerated)
:param str ok_label: label of the OK button
:param str help_label: label of the help button
:param dict unused_kwargs: absorbs default / cli_args
:returns: tuple of the form (`code`, `index`) where
`code` - display exit code
`int` - index of the selected item
:rtype: tuple
"""
menu_options = {
"choices": choices,
"ok_label": ok_label,
"cancel_label": cancel_label,
"help_button": bool(help_label),
"help_label": help_label,
"width": self.width,
"height": self.height,
"menu_height": self.height - 6,
}
# Can accept either tuples or just the actual choices
if choices and isinstance(choices[0], tuple):
# pylint: disable=star-args
code, selection = _clean(self.dialog.menu(message, **menu_options))
# Return the selection index
for i, choice in enumerate(choices):
if choice[0] == selection:
return code, i
return code, -1
else:
# "choices" is not formatted the way the dialog.menu expects...
menu_options["choices"] = [
(str(i), choice) for i, choice in enumerate(choices, 1)
]
# pylint: disable=star-args
code, index = _clean(self.dialog.menu(message, **menu_options))
if code == CANCEL or index == "":
return code, -1
return code, int(index) - 1
def input(self, message, **unused_kwargs):
"""Display an input box to the user.
:param str message: Message to display that asks for input.
:param dict _kwargs: absorbs default / cli_args
:returns: tuple of the form (`code`, `string`) where
`code` - display exit code
`string` - input entered by the user
"""
return self.dialog.inputbox(message)
def yesno(self, message, yes_label="Yes", no_label="No", **unused_kwargs):
"""Display a Yes/No dialog box.
Yes and No label must begin with different letters.
:param str message: message to display to user
:param str yes_label: label on the "yes" button
:param str no_label: label on the "no" button
:param dict _kwargs: absorbs default / cli_args
:returns: if yes_label was selected
:rtype: bool
"""
return self.dialog.DIALOG_OK == self.dialog.yesno(
message, yes_label=yes_label, no_label=no_label)
def checklist(self, message, tags, default_status=True, **unused_kwargs):
"""Displays a checklist.
:param message: Message to display before choices
:param list tags: where each is of type :class:`str` len(tags) > 0
:param bool default_status: If True, items are in a selected state by
default.
:param dict _kwargs: absorbs default / cli_args
:returns: tuple of the form (`code`, `list_tags`) where
`code` - display exit code
`list_tags` - list of str tags selected by the user
"""
choices = [(tag, "", default_status) for tag in tags]
return self.dialog.checklist(message, choices=choices)
def directory_select(self, message, **unused_kwargs):
"""Display a directory selection screen.
:param str message: prompt to give the user
:returns: tuple of the form (`code`, `string`) where
`code` - display exit code
`string` - input entered by the user
"""
root_directory = os.path.abspath(os.sep)
return self.dialog.dselect(
filepath=root_directory, help_button=True, title=message)
@zope.interface.implementer(interfaces.IDisplay)
class FileDisplay(object):
"""File-based display."""
@ -231,12 +55,11 @@ class FileDisplay(object):
super(FileDisplay, self).__init__()
self.outfile = outfile
def notification(self, message, height=10, pause=True):
def notification(self, message, pause=True):
# pylint: disable=unused-argument
"""Displays a notification and waits for user acceptance.
:param str message: Message to display
:param int height: No effect for FileDisplay
:param bool pause: Whether or not the program should pause for the
user's confirmation
@ -496,12 +319,11 @@ class NoninteractiveDisplay(object):
msg += "\n\n(You can set this with the {0} flag)".format(cli_flag)
raise errors.MissingCommandlineFlag(msg)
def notification(self, message, height=10, pause=False):
def notification(self, message, pause=False):
# pylint: disable=unused-argument
"""Displays a notification without waiting for user acceptance.
:param str message: Message to display to stdout
:param int height: No effect for NoninteractiveDisplay
:param bool pause: The NoninteractiveDisplay waits for no keyboard
"""

View file

@ -378,11 +378,10 @@ class IInstaller(IPlugin):
class IDisplay(zope.interface.Interface):
"""Generic display."""
def notification(message, height, pause):
def notification(message, pause):
"""Displays a string message
:param str message: Message to display
:param int height: Height of dialog box if applicable
:param bool pause: Whether or not the application should pause for
confirmation (if available)

View file

@ -1,64 +0,0 @@
"""Logging utilities."""
import logging
import dialog
from certbot.display import util as display_util
class DialogHandler(logging.Handler): # pylint: disable=too-few-public-methods
"""Logging handler using dialog info box.
:ivar int height: Height of the info box (without padding).
:ivar int width: Width of the info box (without padding).
:ivar list lines: Lines to be displayed in the info box.
:ivar d: Instance of :class:`dialog.Dialog`.
"""
PADDING_HEIGHT = 2
PADDING_WIDTH = 4
def __init__(self, level=logging.NOTSET, height=display_util.HEIGHT,
width=display_util.WIDTH - 4, d=None):
# Handler not new-style -> no super
logging.Handler.__init__(self, level)
self.height = height
self.width = width
# "dialog" collides with module name...
self.d = dialog.Dialog() if d is None else d
self.lines = []
def emit(self, record):
"""Emit message to a dialog info box.
Only show the last (self.height) lines; note that lines can wrap
at self.width, so a single line could actually be multiple
lines.
"""
for line in self.format(record).splitlines():
# check for lines that would wrap
cur_out = line
while len(cur_out) > self.width:
# find first space before self.width chars into cur_out
last_space_pos = cur_out.rfind(' ', 0, self.width)
if last_space_pos == -1:
# no spaces, just cut them off at whatever
self.lines.append(cur_out[0:self.width])
cur_out = cur_out[self.width:]
else:
# cut off at last space
self.lines.append(cur_out[0:last_space_pos])
cur_out = cur_out[last_space_pos + 1:]
if cur_out != '':
self.lines.append(cur_out)
# show last 16 lines
content = '\n'.join(self.lines[-self.height:])
# add the padding around the box
self.d.infobox(
content, self.height + self.PADDING_HEIGHT,
self.width + self.PADDING_WIDTH)

View file

@ -1,7 +1,6 @@
"""Certbot main entry point."""
from __future__ import print_function
import atexit
import dialog
import functools
import logging.handlers
import os
@ -27,7 +26,6 @@ from certbot import errors
from certbot import hooks
from certbot import interfaces
from certbot import util
from certbot import log
from certbot import reporter
from certbot import renewal
from certbot import storage
@ -614,14 +612,9 @@ def setup_log_file_handler(config, logfile, fmt):
return handler, log_file_path
def _cli_log_handler(config, level, fmt):
if config.text_mode or config.noninteractive_mode or config.verb == "renew":
handler = colored_logging.StreamHandler()
handler.setFormatter(logging.Formatter(fmt))
else:
handler = log.DialogHandler()
# dialog box is small, display as less as possible
handler.setFormatter(logging.Formatter("%(message)s"))
def _cli_log_handler(level, fmt):
handler = colored_logging.StreamHandler()
handler.setFormatter(logging.Formatter(fmt))
handler.setLevel(level)
return handler
@ -641,7 +634,7 @@ def setup_logging(config):
level = -config.verbose_count * 10
file_handler, log_file_path = setup_log_file_handler(
config, logfile=logfile, fmt=file_fmt)
cli_handler = _cli_log_handler(config, level, cli_fmt)
cli_handler = _cli_log_handler(level, cli_fmt)
# TODO: use fileConfig?
@ -687,10 +680,7 @@ def _handle_exception(exc_type, exc_value, trace, config):
# Here we're passing a client or ACME error out to the client at the shell
# Tell the user a bit about what happened, without overwhelming
# them with a full traceback
if issubclass(exc_type, dialog.error):
err = exc_value.complete_message()
else:
err = traceback.format_exception_only(exc_type, exc_value)[0]
err = traceback.format_exception_only(exc_type, exc_value)[0]
# Typical error from the ACME module:
# acme.messages.Error: urn:ietf:params:acme:error:malformed :: The
# request message was malformed :: Error creating new registration
@ -764,10 +754,8 @@ def main(cli_args=sys.argv[1:]):
displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w"))
elif config.noninteractive_mode:
displayer = display_util.NoninteractiveDisplay(sys.stdout)
elif config.text_mode:
displayer = display_util.FileDisplay(sys.stdout)
else:
displayer = display_util.NcursesDisplay()
displayer = display_util.FileDisplay(sys.stdout)
zope.component.provideUtility(displayer)
# Reporter

View file

@ -243,7 +243,7 @@ s.serve_forever()" """
# pylint: disable=no-self-use
# TODO: IDisplay wraps messages, breaking the command
#answer = zope.component.getUtility(interfaces.IDisplay).notification(
# message=message, height=25, pause=True)
# message=message, pause=True)
sys.stdout.write(message)
six.moves.input("Press ENTER to continue")

View file

@ -119,8 +119,7 @@ def choose_plugin(prepared, question):
z_util(interfaces.IDisplay).notification(
"The selected plugin encountered an error while parsing "
"your server configuration and cannot be used. The error "
"was:\n\n{0}".format(plugin_ep.prepare()),
height=display_util.HEIGHT, pause=False)
"was:\n\n{0}".format(plugin_ep.prepare()), pause=False)
else:
return plugin_ep
elif code == display_util.HELP:
@ -128,8 +127,7 @@ def choose_plugin(prepared, question):
msg = "Reported Error: %s" % prepared[index].prepare()
else:
msg = prepared[index].init().more_info()
z_util(interfaces.IDisplay).notification(
msg, height=display_util.HEIGHT)
z_util(interfaces.IDisplay).notification(msg)
else:
return None

View file

@ -103,7 +103,7 @@ def already_listening_socket(port, renewer=False):
"Port {0} is already in use by another process. This will "
"prevent us from binding to that port. Please stop the "
"process that is populating the port in question and try "
"again. {1}".format(port, extra), height=13)
"again. {1}".format(port, extra))
return True
finally:
testsocket.close()
@ -151,8 +151,7 @@ def already_listening_psutil(port, renewer=False):
"The program {0} (process ID {1}) is already listening "
"on TCP port {2}. This will prevent us from binding to "
"that port. Please stop the {0} program temporarily "
"and then try again.{3}".format(name, pid, port, extra),
height=13)
"and then try again.{3}".format(name, pid, port, extra))
return True
except (psutil.NoSuchProcess, psutil.AccessDenied):
# Perhaps the result of a race where the process could have

View file

@ -140,9 +140,8 @@ to serve all files under specified web root ({0})."""
code, webroot = display.directory_select(
"Input the webroot for {0}:".format(domain))
if code == display_util.HELP:
# Help can currently only be selected
# when using the ncurses interface
display.notification(display_util.DSELECT_HELP)
# Displaying help is not currently implemented
return None
elif code == display_util.CANCEL:
return None
else: # code == display_util.OK

View file

@ -111,8 +111,7 @@ class AuthenticatorTest(unittest.TestCase):
self.assertTrue(mock_display.notification.called)
for call in mock_display.notification.call_args_list:
self.assertTrue(imaginary_dir in call[0][0] or
display_util.DSELECT_HELP == call[0][0])
self.assertTrue(imaginary_dir in call[0][0])
self.assertTrue(mock_display.directory_select.called)
for call in mock_display.directory_select.call_args_list:

View file

@ -15,8 +15,6 @@ from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.display import util as display_util
logger = logging.getLogger(__name__)
@ -183,7 +181,7 @@ class Reverter(object):
if for_logging:
return os.linesep.join(output)
zope.component.getUtility(interfaces.IDisplay).notification(
os.linesep.join(output), display_util.HEIGHT)
os.linesep.join(output))
def add_to_temp_checkpoint(self, save_files, save_notes):
"""Add files to temporary checkpoint.

View file

@ -5,7 +5,6 @@
from __future__ import print_function
import argparse
import dialog
import functools
import itertools
import os
@ -51,6 +50,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.config_dir = os.path.join(self.tmp_dir, 'config')
self.work_dir = os.path.join(self.tmp_dir, 'work')
self.logs_dir = os.path.join(self.tmp_dir, 'logs')
os.mkdir(self.logs_dir)
self.standard_args = ['--config-dir', self.config_dir,
'--work-dir', self.work_dir,
'--logs-dir', self.logs_dir, '--text']
@ -98,6 +98,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue("--configurator" in out)
self.assertTrue("how a cert is deployed" in out)
self.assertTrue("--manual-test-mode" in out)
self.assertTrue("--text" not in out)
self.assertTrue("--dialog" not in out)
out = self._help_output(['-h', 'nginx'])
if "nginx" in plugins:
@ -163,12 +165,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._cli_missing_flag(args, "--agree-tos")
@mock.patch('certbot.main.renew')
def test_gui(self, renew):
def test_no_gui(self, renew):
args = ['renew', '--dialog']
# --text conflicts with --dialog
self.standard_args.remove('--text')
# --dialog should have no effect
self._call(args)
self.assertFalse(renew.call_args[0][0].noninteractive_mode)
self.assertTrue(renew.call_args[0][0].noninteractive_mode)
@mock.patch('certbot.main.client.acme_client.Client')
@mock.patch('certbot.main._determine_account')
@ -656,9 +657,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
log_out="not yet due", should_renew=False)
def _dump_log(self):
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
print("Logs:")
print(lf.read())
print("Logs:")
log_path = os.path.join(self.logs_dir, "letsencrypt.log")
if os.path.exists(log_path):
with open(log_path) as lf:
print(lf.read())
def _make_lineage(self, testfile):
"""Creates a lineage defined by testfile.
@ -977,13 +980,6 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_sys.exit.assert_called_with(''.join(
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
# Test dialog errors
exception = dialog.error(message="test message")
main._handle_exception(
dialog.DialogError, exc_value=exception, trace=None, config=None)
error_msg = mock_sys.exit.call_args_list[-1][0][0]
self.assertTrue("test message" in error_msg)
def test_read_file(self):
rel_test_path = os.path.relpath(os.path.join(self.tmp_dir, 'foo'))
self.assertRaises(
@ -1070,17 +1066,6 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue(
email in mock_utility().add_message.call_args[0][0])
def test_conflicting_args(self):
args = ['renew', '--dialog', '--text']
self.assertRaises(errors.Error, self._call, args)
def test_text_mode_when_verbose(self):
parse = self._get_argument_parser()
short_args = ['-v']
namespace = parse(short_args)
self.assertTrue(namespace.text_mode)
class DetermineAccountTest(unittest.TestCase):
"""Tests for certbot.cli._determine_account."""

View file

@ -13,122 +13,6 @@ CHOICES = [("First", "Description1"), ("Second", "Description2")]
TAGS = ["tag1", "tag2", "tag3"]
TAGS_CHOICES = [("1", "tag1"), ("2", "tag2"), ("3", "tag3")]
class NcursesDisplayTest(unittest.TestCase):
"""Test ncurses display.
Since this is mostly a wrapper, it might be more helpful to test the
actual dialog boxes. The test file located in ./tests/display.py
(relative to the root of the repository) will actually display the
various boxes but requires the user to do the verification. If
something seems amiss please use that test script to debug it, the
automatic tests rely on too much mocking.
"""
def setUp(self):
super(NcursesDisplayTest, self).setUp()
self.displayer = display_util.NcursesDisplay()
self.default_menu_options = {
"choices": CHOICES,
"ok_label": "OK",
"cancel_label": "Cancel",
"help_button": False,
"help_label": "",
"width": display_util.WIDTH,
"height": display_util.HEIGHT,
"menu_height": display_util.HEIGHT - 6,
}
@mock.patch("certbot.display.util.dialog.Dialog.msgbox")
def test_notification(self, mock_msgbox):
"""Kind of worthless... one liner."""
self.displayer.notification("message")
self.assertEqual(mock_msgbox.call_count, 1)
@mock.patch("certbot.display.util.dialog.Dialog.menu")
def test_menu_tag_and_desc(self, mock_menu):
mock_menu.return_value = (display_util.OK, "First")
ret = self.displayer.menu("Message", CHOICES)
mock_menu.assert_called_with("Message", **self.default_menu_options)
self.assertEqual(ret, (display_util.OK, 0))
@mock.patch("certbot.display.util.dialog.Dialog.menu")
def test_menu_tag_and_desc_cancel(self, mock_menu):
mock_menu.return_value = (display_util.CANCEL, "")
ret = self.displayer.menu("Message", CHOICES)
mock_menu.assert_called_with("Message", **self.default_menu_options)
self.assertEqual(ret, (display_util.CANCEL, -1))
@mock.patch("certbot.display.util.dialog.Dialog.menu")
def test_menu_desc_only(self, mock_menu):
mock_menu.return_value = (display_util.OK, "1")
ret = self.displayer.menu("Message", TAGS, help_label="More Info")
self.default_menu_options.update(
choices=TAGS_CHOICES, help_button=True, help_label="More Info")
mock_menu.assert_called_with("Message", **self.default_menu_options)
self.assertEqual(ret, (display_util.OK, 0))
@mock.patch("certbot.display.util.dialog.Dialog.menu")
def test_menu_desc_only_help(self, mock_menu):
mock_menu.return_value = (display_util.HELP, "2")
ret = self.displayer.menu("Message", TAGS, help_label="More Info")
self.assertEqual(ret, (display_util.HELP, 1))
@mock.patch("certbot.display.util.dialog.Dialog.menu")
def test_menu_desc_only_cancel(self, mock_menu):
mock_menu.return_value = (display_util.CANCEL, "")
ret = self.displayer.menu("Message", TAGS, help_label="More Info")
self.assertEqual(ret, (display_util.CANCEL, -1))
@mock.patch("certbot.display.util."
"dialog.Dialog.inputbox")
def test_input(self, mock_input):
mock_input.return_value = (mock.MagicMock(), mock.MagicMock())
self.displayer.input("message")
self.assertEqual(mock_input.call_count, 1)
@mock.patch("certbot.display.util.dialog.Dialog.yesno")
def test_yesno(self, mock_yesno):
mock_yesno.return_value = display_util.OK
self.assertTrue(self.displayer.yesno("message"))
mock_yesno.assert_called_with(
"message", yes_label="Yes", no_label="No")
@mock.patch("certbot.display.util."
"dialog.Dialog.checklist")
def test_checklist(self, mock_checklist):
mock_checklist.return_value = (mock.MagicMock(), mock.MagicMock())
self.displayer.checklist("message", TAGS)
choices = [
(TAGS[0], "", True),
(TAGS[1], "", True),
(TAGS[2], "", True),
]
mock_checklist.assert_called_with("message", choices=choices)
@mock.patch("certbot.display.util.dialog.Dialog.dselect")
def test_directory_select(self, mock_dselect):
mock_dselect.return_value = (mock.MagicMock(), mock.MagicMock())
self.displayer.directory_select("message")
self.assertEqual(mock_dselect.call_count, 1)
class FileOutputDisplayTest(unittest.TestCase):
"""Test stdout display.
@ -142,7 +26,7 @@ class FileOutputDisplayTest(unittest.TestCase):
self.displayer = display_util.FileDisplay(self.mock_stdout)
def test_notification_no_pause(self):
self.displayer.notification("message", 10, False)
self.displayer.notification("message", False)
string = self.mock_stdout.write.call_args[0][0]
self.assertTrue("message" in string)

View file

@ -1,48 +0,0 @@
"""Tests for certbot.log."""
import logging
import unittest
import mock
class DialogHandlerTest(unittest.TestCase):
def setUp(self):
self.d = mock.MagicMock()
from certbot.log import DialogHandler
self.handler = DialogHandler(height=2, width=6, d=self.d)
self.handler.PADDING_HEIGHT = 2
self.handler.PADDING_WIDTH = 4
def test_adds_padding(self):
self.handler.emit(logging.makeLogRecord({}))
self.d.infobox.assert_called_once_with(mock.ANY, 4, 10)
def test_args_in_msg_get_replaced(self):
assert len('123456') <= self.handler.width
self.handler.emit(logging.makeLogRecord(
{'msg': '123%s', 'args': (456,)}))
self.d.infobox.assert_called_once_with('123456', mock.ANY, mock.ANY)
def test_wraps_nospace_is_greedy(self):
assert len('1234567') > self.handler.width
self.handler.emit(logging.makeLogRecord({'msg': '1234567'}))
self.d.infobox.assert_called_once_with('123456\n7', mock.ANY, mock.ANY)
def test_wraps_at_whitespace(self):
assert len('123 567') > self.handler.width
self.handler.emit(logging.makeLogRecord({'msg': '123 567'}))
self.d.infobox.assert_called_once_with('123\n567', mock.ANY, mock.ANY)
def test_only_last_lines_are_printed(self):
assert len('a\nb\nc'.split()) > self.handler.height
self.handler.emit(logging.makeLogRecord({'msg': 'a\n\nb\nc'}))
self.d.infobox.assert_called_once_with('b\nc', mock.ANY, mock.ANY)
def test_non_str(self):
self.handler.emit(logging.makeLogRecord({'msg': {'foo': 'bar'}}))
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -11,7 +11,6 @@ from certbot import colored_logging
from certbot import constants
from certbot import configuration
from certbot import errors
from certbot import log
from certbot.plugins import disco as plugins_disco
class MainTest(unittest.TestCase):
@ -55,9 +54,9 @@ class ObtainCertTest(unittest.TestCase):
mock_notification = self.mock_get_utility().notification
mock_notification.side_effect = self._assert_no_pause
mock_auth.return_value = ('reinstall', mock.ANY)
self._call('certonly --webroot -d example.com -t'.split())
self._call('certonly --webroot -d example.com'.split())
def _assert_no_pause(self, message, height=42, pause=True):
def _assert_no_pause(self, message, pause=True):
# pylint: disable=unused-argument
self.assertFalse(pause)
@ -89,7 +88,7 @@ class SetupLoggingTest(unittest.TestCase):
def setUp(self):
self.config = mock.Mock(
logs_dir=tempfile.mkdtemp(),
noninteractive_mode=False, quiet=False, text_mode=False,
noninteractive_mode=False, quiet=False,
verbose_count=constants.CLI_DEFAULTS['verbose_count'])
def tearDown(self):
@ -107,7 +106,7 @@ class SetupLoggingTest(unittest.TestCase):
cli_handler = mock_get_logger().addHandler.call_args_list[0][0][0]
self.assertEqual(cli_handler.level, -self.config.verbose_count * 10)
self.assertTrue(
isinstance(cli_handler, log.DialogHandler))
isinstance(cli_handler, colored_logging.StreamHandler))
@mock.patch('certbot.main.logging.getLogger')
def test_quiet_mode(self, mock_get_logger):

View file

@ -248,7 +248,6 @@ BootstrapDebCommon() {
python-dev \
$virtualenv \
gcc \
dialog \
$augeas_pkg \
libssl-dev \
libffi-dev \
@ -307,7 +306,6 @@ BootstrapRpmCommon() {
pkgs="
gcc
dialog
augeas-libs
openssl
openssl-devel
@ -361,7 +359,6 @@ BootstrapSuseCommon() {
python-devel \
python-virtualenv \
gcc \
dialog \
augeas-lenses \
libopenssl-devel \
libffi-devel \
@ -380,7 +377,6 @@ BootstrapArchCommon() {
python2
python-virtualenv
gcc
dialog
augeas
openssl
libffi
@ -404,7 +400,6 @@ BootstrapGentooCommon() {
PACKAGES="
dev-lang/python:2.7
dev-python/virtualenv
dev-util/dialog
app-admin/augeas
dev-libs/openssl
dev-libs/libffi
@ -449,7 +444,6 @@ BootstrapMac() {
fi
$pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip.
@ -496,7 +490,6 @@ BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \
git \
gcc \
cdialog \
python-augeas \
libopenssl-devel \
libffi-devel \
@ -701,9 +694,6 @@ pyRFC3339==1.0 \
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
python-augeas==0.5.0 \
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
python2-pythondialog==3.3.0 \
--hash=sha256:04e93f24995c43dd90f338d5d865ca72ce3fb5a5358d4daa4965571db35fc3ec \
--hash=sha256:3e6f593fead98f8a526bc3e306933533236e33729f552f52896ea504f55313fa
pytz==2015.7 \
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \

View file

@ -10,7 +10,6 @@ BootstrapArchCommon() {
python2
python-virtualenv
gcc
dialog
augeas
openssl
libffi

View file

@ -94,7 +94,6 @@ BootstrapDebCommon() {
python-dev \
$virtualenv \
gcc \
dialog \
$augeas_pkg \
libssl-dev \
libffi-dev \

View file

@ -2,7 +2,6 @@ BootstrapGentooCommon() {
PACKAGES="
dev-lang/python:2.7
dev-python/virtualenv
dev-util/dialog
app-admin/augeas
dev-libs/openssl
dev-libs/libffi

View file

@ -15,7 +15,6 @@ BootstrapMac() {
fi
$pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip.

View file

@ -11,7 +11,6 @@ BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \
git \
gcc \
cdialog \
python-augeas \
libopenssl-devel \
libffi-devel \

View file

@ -43,7 +43,6 @@ BootstrapRpmCommon() {
pkgs="
gcc
dialog
augeas-libs
openssl
openssl-devel

View file

@ -11,7 +11,6 @@ BootstrapSuseCommon() {
python-devel \
python-virtualenv \
gcc \
dialog \
augeas-lenses \
libopenssl-devel \
libffi-devel \

View file

@ -110,9 +110,6 @@ pyRFC3339==1.0 \
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
python-augeas==0.5.0 \
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
python2-pythondialog==3.3.0 \
--hash=sha256:04e93f24995c43dd90f338d5d865ca72ce3fb5a5358d4daa4965571db35fc3ec \
--hash=sha256:3e6f593fead98f8a526bc3e306933533236e33729f552f52896ea504f55313fa
pytz==2015.7 \
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \

View file

@ -51,12 +51,6 @@ install_requires = [
'zope.interface',
]
# Debian squeeze support, cf. #280
if sys.version_info[0] == 2:
install_requires.append('python2-pythondialog>=3.2.2rc1')
else:
install_requires.append('pythondialog>=3.2.2rc1')
# env markers in extras_require cause problems with older pip: #517
# Keep in sync with conditional_requirements.py.
if sys.version_info < (2, 7):

View file

@ -18,5 +18,5 @@ def test_visual(displayer, choices):
if __name__ == "__main__":
for displayer in util.NcursesDisplay(), util.FileDisplay(sys.stdout):
test_visual(displayer, util_test.CHOICES)
displayer = util.FileDisplay(sys.stdout):
test_visual(displayer, util_test.CHOICES)

View file

@ -38,7 +38,6 @@ deps =
py{26,27}-oldest: dnspython>=1.12
py{26,27}-oldest: psutil==2.1.0
py{26,27}-oldest: PyOpenSSL==0.13
py{26,27}-oldest: python2-pythondialog==3.2.2rc1
[testenv:py33]
commands =