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
-1
View File
@@ -103,7 +103,6 @@ addons:
- python-dev - python-dev
- python-virtualenv - python-virtualenv
- gcc - gcc
- dialog
- libaugeas0 - libaugeas0
- libssl-dev - libssl-dev
- libffi-dev - libffi-dev
Vendored
+1
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] # VM needs more memory to run test suite, got "OSError: [Errno 12]
# Cannot allocate memory" when running # Cannot allocate memory" when running
# letsencrypt.client.tests.display.util_test.NcursesDisplayTest # letsencrypt.client.tests.display.util_test.NcursesDisplayTest
# We may no longer need this.
v.memory = 1024 v.memory = 1024
# Handle cases when the host is behind a private network by making the # Handle cases when the host is behind a private network by making the
+1 -2
View File
@@ -100,5 +100,4 @@ def _vhost_menu(domain, vhosts):
def _more_info_vhost(vhost): def _more_info_vhost(vhost):
zope.component.getUtility(interfaces.IDisplay).notification( zope.component.getUtility(interfaces.IDisplay).notification(
"Virtual Host Information:{0}{1}{0}{2}".format( "Virtual Host Information:{0}{1}{0}{2}".format(
os.linesep, "-" * (display_util.WIDTH - 4), str(vhost)), os.linesep, "-" * (display_util.WIDTH - 4), str(vhost)))
height=display_util.HEIGHT)
+3 -16
View File
@@ -376,7 +376,7 @@ class HelpfulArgumentParser(object):
# Do any post-parsing homework here # 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 parsed_args.noninteractive_mode = True
if parsed_args.staging or parsed_args.dry_run: if parsed_args.staging or parsed_args.dry_run:
@@ -388,17 +388,6 @@ class HelpfulArgumentParser(object):
if parsed_args.must_staple: if parsed_args.must_staple:
parsed_args.staple = True 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: if parsed_args.validate_hooks:
hooks.validate_hooks(parsed_args) hooks.validate_hooks(parsed_args)
@@ -677,16 +666,13 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
"e.g. -vvv.") "e.g. -vvv.")
helpful.add( helpful.add(
None, "-t", "--text", dest="text_mode", action="store_true", None, "-t", "--text", dest="text_mode", action="store_true",
help="Use the text output instead of the curses UI.") help=argparse.SUPPRESS)
helpful.add( helpful.add(
[None, "automation"], "-n", "--non-interactive", "--noninteractive", [None, "automation"], "-n", "--non-interactive", "--noninteractive",
dest="noninteractive_mode", action="store_true", dest="noninteractive_mode", action="store_true",
help="Run without ever asking for user input. This may require " help="Run without ever asking for user input. This may require "
"additional command line flags; the client will try to explain " "additional command line flags; the client will try to explain "
"which ones are required if it finds one missing") "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( helpful.add(
[None, "run", "certonly"], [None, "run", "certonly"],
"-d", "--domains", "--domain", dest="domains", "-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.") " shell constructs, so you can use this switch to disable it.")
helpful.add_deprecated_argument("--agree-dev-preview", 0) helpful.add_deprecated_argument("--agree-dev-preview", 0)
helpful.add_deprecated_argument("--dialog", 0)
_create_subparsers(helpful) _create_subparsers(helpful)
_paths_parser(helpful) _paths_parser(helpful)
-2
View File
@@ -219,7 +219,6 @@ def success_installation(domains):
_gen_https_names(domains), _gen_https_names(domains),
os.linesep, os.linesep,
os.linesep.join(_gen_ssl_lab_urls(domains))), os.linesep.join(_gen_ssl_lab_urls(domains))),
height=(10 + len(domains)),
pause=False) pause=False)
@@ -241,7 +240,6 @@ def success_renewal(domains, action):
os.linesep, os.linesep,
os.linesep.join(_gen_ssl_lab_urls(domains)), os.linesep.join(_gen_ssl_lab_urls(domains)),
action), action),
height=(14 + len(domains)),
pause=False) pause=False)
+2 -180
View File
@@ -1,9 +1,7 @@
"""Certbot display.""" """Certbot display."""
import logging
import os import os
import textwrap import textwrap
import dialog
import six import six
import zope.interface import zope.interface
@@ -12,17 +10,7 @@ from certbot import errors
from certbot.display import completer from certbot.display import completer
logger = logging.getLogger(__name__)
WIDTH = 72 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 # Display exit codes
OK = "ok" OK = "ok"
@@ -59,170 +47,6 @@ def _wrap_lines(msg):
return os.linesep.join(fixed_l) 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) @zope.interface.implementer(interfaces.IDisplay)
class FileDisplay(object): class FileDisplay(object):
"""File-based display.""" """File-based display."""
@@ -231,12 +55,11 @@ class FileDisplay(object):
super(FileDisplay, self).__init__() super(FileDisplay, self).__init__()
self.outfile = outfile self.outfile = outfile
def notification(self, message, height=10, pause=True): def notification(self, message, pause=True):
# pylint: disable=unused-argument # pylint: disable=unused-argument
"""Displays a notification and waits for user acceptance. """Displays a notification and waits for user acceptance.
:param str message: Message to display :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 :param bool pause: Whether or not the program should pause for the
user's confirmation user's confirmation
@@ -496,12 +319,11 @@ class NoninteractiveDisplay(object):
msg += "\n\n(You can set this with the {0} flag)".format(cli_flag) msg += "\n\n(You can set this with the {0} flag)".format(cli_flag)
raise errors.MissingCommandlineFlag(msg) raise errors.MissingCommandlineFlag(msg)
def notification(self, message, height=10, pause=False): def notification(self, message, pause=False):
# pylint: disable=unused-argument # pylint: disable=unused-argument
"""Displays a notification without waiting for user acceptance. """Displays a notification without waiting for user acceptance.
:param str message: Message to display to stdout :param str message: Message to display to stdout
:param int height: No effect for NoninteractiveDisplay
:param bool pause: The NoninteractiveDisplay waits for no keyboard :param bool pause: The NoninteractiveDisplay waits for no keyboard
""" """
+1 -2
View File
@@ -378,11 +378,10 @@ class IInstaller(IPlugin):
class IDisplay(zope.interface.Interface): class IDisplay(zope.interface.Interface):
"""Generic display.""" """Generic display."""
def notification(message, height, pause): def notification(message, pause):
"""Displays a string message """Displays a string message
:param str message: Message to display :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 :param bool pause: Whether or not the application should pause for
confirmation (if available) confirmation (if available)
-64
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)
+6 -18
View File
@@ -1,7 +1,6 @@
"""Certbot main entry point.""" """Certbot main entry point."""
from __future__ import print_function from __future__ import print_function
import atexit import atexit
import dialog
import functools import functools
import logging.handlers import logging.handlers
import os import os
@@ -27,7 +26,6 @@ from certbot import errors
from certbot import hooks from certbot import hooks
from certbot import interfaces from certbot import interfaces
from certbot import util from certbot import util
from certbot import log
from certbot import reporter from certbot import reporter
from certbot import renewal from certbot import renewal
from certbot import storage from certbot import storage
@@ -614,14 +612,9 @@ def setup_log_file_handler(config, logfile, fmt):
return handler, log_file_path return handler, log_file_path
def _cli_log_handler(config, level, fmt): def _cli_log_handler(level, fmt):
if config.text_mode or config.noninteractive_mode or config.verb == "renew": handler = colored_logging.StreamHandler()
handler = colored_logging.StreamHandler() handler.setFormatter(logging.Formatter(fmt))
handler.setFormatter(logging.Formatter(fmt))
else:
handler = log.DialogHandler()
# dialog box is small, display as less as possible
handler.setFormatter(logging.Formatter("%(message)s"))
handler.setLevel(level) handler.setLevel(level)
return handler return handler
@@ -641,7 +634,7 @@ def setup_logging(config):
level = -config.verbose_count * 10 level = -config.verbose_count * 10
file_handler, log_file_path = setup_log_file_handler( file_handler, log_file_path = setup_log_file_handler(
config, logfile=logfile, fmt=file_fmt) 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? # 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 # 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 # Tell the user a bit about what happened, without overwhelming
# them with a full traceback # them with a full traceback
if issubclass(exc_type, dialog.error): err = traceback.format_exception_only(exc_type, exc_value)[0]
err = exc_value.complete_message()
else:
err = traceback.format_exception_only(exc_type, exc_value)[0]
# Typical error from the ACME module: # Typical error from the ACME module:
# acme.messages.Error: urn:ietf:params:acme:error:malformed :: The # acme.messages.Error: urn:ietf:params:acme:error:malformed :: The
# request message was malformed :: Error creating new registration # 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")) displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w"))
elif config.noninteractive_mode: elif config.noninteractive_mode:
displayer = display_util.NoninteractiveDisplay(sys.stdout) displayer = display_util.NoninteractiveDisplay(sys.stdout)
elif config.text_mode:
displayer = display_util.FileDisplay(sys.stdout)
else: else:
displayer = display_util.NcursesDisplay() displayer = display_util.FileDisplay(sys.stdout)
zope.component.provideUtility(displayer) zope.component.provideUtility(displayer)
# Reporter # Reporter
+1 -1
View File
@@ -243,7 +243,7 @@ s.serve_forever()" """
# pylint: disable=no-self-use # pylint: disable=no-self-use
# TODO: IDisplay wraps messages, breaking the command # TODO: IDisplay wraps messages, breaking the command
#answer = zope.component.getUtility(interfaces.IDisplay).notification( #answer = zope.component.getUtility(interfaces.IDisplay).notification(
# message=message, height=25, pause=True) # message=message, pause=True)
sys.stdout.write(message) sys.stdout.write(message)
six.moves.input("Press ENTER to continue") six.moves.input("Press ENTER to continue")
+2 -4
View File
@@ -119,8 +119,7 @@ def choose_plugin(prepared, question):
z_util(interfaces.IDisplay).notification( z_util(interfaces.IDisplay).notification(
"The selected plugin encountered an error while parsing " "The selected plugin encountered an error while parsing "
"your server configuration and cannot be used. The error " "your server configuration and cannot be used. The error "
"was:\n\n{0}".format(plugin_ep.prepare()), "was:\n\n{0}".format(plugin_ep.prepare()), pause=False)
height=display_util.HEIGHT, pause=False)
else: else:
return plugin_ep return plugin_ep
elif code == display_util.HELP: elif code == display_util.HELP:
@@ -128,8 +127,7 @@ def choose_plugin(prepared, question):
msg = "Reported Error: %s" % prepared[index].prepare() msg = "Reported Error: %s" % prepared[index].prepare()
else: else:
msg = prepared[index].init().more_info() msg = prepared[index].init().more_info()
z_util(interfaces.IDisplay).notification( z_util(interfaces.IDisplay).notification(msg)
msg, height=display_util.HEIGHT)
else: else:
return None return None
+2 -3
View File
@@ -103,7 +103,7 @@ def already_listening_socket(port, renewer=False):
"Port {0} is already in use by another process. This will " "Port {0} is already in use by another process. This will "
"prevent us from binding to that port. Please stop the " "prevent us from binding to that port. Please stop the "
"process that is populating the port in question and try " "process that is populating the port in question and try "
"again. {1}".format(port, extra), height=13) "again. {1}".format(port, extra))
return True return True
finally: finally:
testsocket.close() testsocket.close()
@@ -151,8 +151,7 @@ def already_listening_psutil(port, renewer=False):
"The program {0} (process ID {1}) is already listening " "The program {0} (process ID {1}) is already listening "
"on TCP port {2}. This will prevent us from binding to " "on TCP port {2}. This will prevent us from binding to "
"that port. Please stop the {0} program temporarily " "that port. Please stop the {0} program temporarily "
"and then try again.{3}".format(name, pid, port, extra), "and then try again.{3}".format(name, pid, port, extra))
height=13)
return True return True
except (psutil.NoSuchProcess, psutil.AccessDenied): except (psutil.NoSuchProcess, psutil.AccessDenied):
# Perhaps the result of a race where the process could have # Perhaps the result of a race where the process could have
+2 -3
View File
@@ -140,9 +140,8 @@ to serve all files under specified web root ({0})."""
code, webroot = display.directory_select( code, webroot = display.directory_select(
"Input the webroot for {0}:".format(domain)) "Input the webroot for {0}:".format(domain))
if code == display_util.HELP: if code == display_util.HELP:
# Help can currently only be selected # Displaying help is not currently implemented
# when using the ncurses interface return None
display.notification(display_util.DSELECT_HELP)
elif code == display_util.CANCEL: elif code == display_util.CANCEL:
return None return None
else: # code == display_util.OK else: # code == display_util.OK
+1 -2
View File
@@ -111,8 +111,7 @@ class AuthenticatorTest(unittest.TestCase):
self.assertTrue(mock_display.notification.called) self.assertTrue(mock_display.notification.called)
for call in mock_display.notification.call_args_list: for call in mock_display.notification.call_args_list:
self.assertTrue(imaginary_dir in call[0][0] or self.assertTrue(imaginary_dir in call[0][0])
display_util.DSELECT_HELP == call[0][0])
self.assertTrue(mock_display.directory_select.called) self.assertTrue(mock_display.directory_select.called)
for call in mock_display.directory_select.call_args_list: for call in mock_display.directory_select.call_args_list:
+1 -3
View File
@@ -15,8 +15,6 @@ from certbot import errors
from certbot import interfaces from certbot import interfaces
from certbot import util from certbot import util
from certbot.display import util as display_util
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -183,7 +181,7 @@ class Reverter(object):
if for_logging: if for_logging:
return os.linesep.join(output) return os.linesep.join(output)
zope.component.getUtility(interfaces.IDisplay).notification( 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): def add_to_temp_checkpoint(self, save_files, save_notes):
"""Add files to temporary checkpoint. """Add files to temporary checkpoint.
+11 -26
View File
@@ -5,7 +5,6 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import dialog
import functools import functools
import itertools import itertools
import os 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.config_dir = os.path.join(self.tmp_dir, 'config')
self.work_dir = os.path.join(self.tmp_dir, 'work') self.work_dir = os.path.join(self.tmp_dir, 'work')
self.logs_dir = os.path.join(self.tmp_dir, 'logs') self.logs_dir = os.path.join(self.tmp_dir, 'logs')
os.mkdir(self.logs_dir)
self.standard_args = ['--config-dir', self.config_dir, self.standard_args = ['--config-dir', self.config_dir,
'--work-dir', self.work_dir, '--work-dir', self.work_dir,
'--logs-dir', self.logs_dir, '--text'] '--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("--configurator" in out)
self.assertTrue("how a cert is deployed" in out) self.assertTrue("how a cert is deployed" in out)
self.assertTrue("--manual-test-mode" 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']) out = self._help_output(['-h', 'nginx'])
if "nginx" in plugins: 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") self._cli_missing_flag(args, "--agree-tos")
@mock.patch('certbot.main.renew') @mock.patch('certbot.main.renew')
def test_gui(self, renew): def test_no_gui(self, renew):
args = ['renew', '--dialog'] args = ['renew', '--dialog']
# --text conflicts with --dialog # --dialog should have no effect
self.standard_args.remove('--text')
self._call(args) 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.client.acme_client.Client')
@mock.patch('certbot.main._determine_account') @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) log_out="not yet due", should_renew=False)
def _dump_log(self): def _dump_log(self):
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf: print("Logs:")
print("Logs:") log_path = os.path.join(self.logs_dir, "letsencrypt.log")
print(lf.read()) if os.path.exists(log_path):
with open(log_path) as lf:
print(lf.read())
def _make_lineage(self, testfile): def _make_lineage(self, testfile):
"""Creates a lineage defined by 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( mock_sys.exit.assert_called_with(''.join(
traceback.format_exception_only(KeyboardInterrupt, interrupt))) 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): def test_read_file(self):
rel_test_path = os.path.relpath(os.path.join(self.tmp_dir, 'foo')) rel_test_path = os.path.relpath(os.path.join(self.tmp_dir, 'foo'))
self.assertRaises( self.assertRaises(
@@ -1070,17 +1066,6 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue( self.assertTrue(
email in mock_utility().add_message.call_args[0][0]) 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): class DetermineAccountTest(unittest.TestCase):
"""Tests for certbot.cli._determine_account.""" """Tests for certbot.cli._determine_account."""
+1 -117
View File
@@ -13,122 +13,6 @@ CHOICES = [("First", "Description1"), ("Second", "Description2")]
TAGS = ["tag1", "tag2", "tag3"] TAGS = ["tag1", "tag2", "tag3"]
TAGS_CHOICES = [("1", "tag1"), ("2", "tag2"), ("3", "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): class FileOutputDisplayTest(unittest.TestCase):
"""Test stdout display. """Test stdout display.
@@ -142,7 +26,7 @@ class FileOutputDisplayTest(unittest.TestCase):
self.displayer = display_util.FileDisplay(self.mock_stdout) self.displayer = display_util.FileDisplay(self.mock_stdout)
def test_notification_no_pause(self): 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] string = self.mock_stdout.write.call_args[0][0]
self.assertTrue("message" in string) self.assertTrue("message" in string)
-48
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
+4 -5
View File
@@ -11,7 +11,6 @@ from certbot import colored_logging
from certbot import constants from certbot import constants
from certbot import configuration from certbot import configuration
from certbot import errors from certbot import errors
from certbot import log
from certbot.plugins import disco as plugins_disco from certbot.plugins import disco as plugins_disco
class MainTest(unittest.TestCase): class MainTest(unittest.TestCase):
@@ -55,9 +54,9 @@ class ObtainCertTest(unittest.TestCase):
mock_notification = self.mock_get_utility().notification mock_notification = self.mock_get_utility().notification
mock_notification.side_effect = self._assert_no_pause mock_notification.side_effect = self._assert_no_pause
mock_auth.return_value = ('reinstall', mock.ANY) 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 # pylint: disable=unused-argument
self.assertFalse(pause) self.assertFalse(pause)
@@ -89,7 +88,7 @@ class SetupLoggingTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.config = mock.Mock( self.config = mock.Mock(
logs_dir=tempfile.mkdtemp(), logs_dir=tempfile.mkdtemp(),
noninteractive_mode=False, quiet=False, text_mode=False, noninteractive_mode=False, quiet=False,
verbose_count=constants.CLI_DEFAULTS['verbose_count']) verbose_count=constants.CLI_DEFAULTS['verbose_count'])
def tearDown(self): def tearDown(self):
@@ -107,7 +106,7 @@ class SetupLoggingTest(unittest.TestCase):
cli_handler = mock_get_logger().addHandler.call_args_list[0][0][0] cli_handler = mock_get_logger().addHandler.call_args_list[0][0][0]
self.assertEqual(cli_handler.level, -self.config.verbose_count * 10) self.assertEqual(cli_handler.level, -self.config.verbose_count * 10)
self.assertTrue( self.assertTrue(
isinstance(cli_handler, log.DialogHandler)) isinstance(cli_handler, colored_logging.StreamHandler))
@mock.patch('certbot.main.logging.getLogger') @mock.patch('certbot.main.logging.getLogger')
def test_quiet_mode(self, mock_get_logger): def test_quiet_mode(self, mock_get_logger):
-10
View File
@@ -248,7 +248,6 @@ BootstrapDebCommon() {
python-dev \ python-dev \
$virtualenv \ $virtualenv \
gcc \ gcc \
dialog \
$augeas_pkg \ $augeas_pkg \
libssl-dev \ libssl-dev \
libffi-dev \ libffi-dev \
@@ -307,7 +306,6 @@ BootstrapRpmCommon() {
pkgs=" pkgs="
gcc gcc
dialog
augeas-libs augeas-libs
openssl openssl
openssl-devel openssl-devel
@@ -361,7 +359,6 @@ BootstrapSuseCommon() {
python-devel \ python-devel \
python-virtualenv \ python-virtualenv \
gcc \ gcc \
dialog \
augeas-lenses \ augeas-lenses \
libopenssl-devel \ libopenssl-devel \
libffi-devel \ libffi-devel \
@@ -380,7 +377,6 @@ BootstrapArchCommon() {
python2 python2
python-virtualenv python-virtualenv
gcc gcc
dialog
augeas augeas
openssl openssl
libffi libffi
@@ -404,7 +400,6 @@ BootstrapGentooCommon() {
PACKAGES=" PACKAGES="
dev-lang/python:2.7 dev-lang/python:2.7
dev-python/virtualenv dev-python/virtualenv
dev-util/dialog
app-admin/augeas app-admin/augeas
dev-libs/openssl dev-libs/openssl
dev-libs/libffi dev-libs/libffi
@@ -449,7 +444,6 @@ BootstrapMac() {
fi fi
$pkgcmd augeas $pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \ if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then -o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip. # We want to avoid using the system Python because it requires root to use pip.
@@ -496,7 +490,6 @@ BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \ if ! $SUDO urpmi --force \
git \ git \
gcc \ gcc \
cdialog \
python-augeas \ python-augeas \
libopenssl-devel \ libopenssl-devel \
libffi-devel \ libffi-devel \
@@ -701,9 +694,6 @@ pyRFC3339==1.0 \
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535 --hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
python-augeas==0.5.0 \ python-augeas==0.5.0 \
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
python2-pythondialog==3.3.0 \
--hash=sha256:04e93f24995c43dd90f338d5d865ca72ce3fb5a5358d4daa4965571db35fc3ec \
--hash=sha256:3e6f593fead98f8a526bc3e306933533236e33729f552f52896ea504f55313fa
pytz==2015.7 \ pytz==2015.7 \
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \ --hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \ --hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \
@@ -10,7 +10,6 @@ BootstrapArchCommon() {
python2 python2
python-virtualenv python-virtualenv
gcc gcc
dialog
augeas augeas
openssl openssl
libffi libffi
@@ -94,7 +94,6 @@ BootstrapDebCommon() {
python-dev \ python-dev \
$virtualenv \ $virtualenv \
gcc \ gcc \
dialog \
$augeas_pkg \ $augeas_pkg \
libssl-dev \ libssl-dev \
libffi-dev \ libffi-dev \
@@ -2,7 +2,6 @@ BootstrapGentooCommon() {
PACKAGES=" PACKAGES="
dev-lang/python:2.7 dev-lang/python:2.7
dev-python/virtualenv dev-python/virtualenv
dev-util/dialog
app-admin/augeas app-admin/augeas
dev-libs/openssl dev-libs/openssl
dev-libs/libffi dev-libs/libffi
@@ -15,7 +15,6 @@ BootstrapMac() {
fi fi
$pkgcmd augeas $pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \ if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then -o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip. # We want to avoid using the system Python because it requires root to use pip.
@@ -11,7 +11,6 @@ BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \ if ! $SUDO urpmi --force \
git \ git \
gcc \ gcc \
cdialog \
python-augeas \ python-augeas \
libopenssl-devel \ libopenssl-devel \
libffi-devel \ libffi-devel \
@@ -43,7 +43,6 @@ BootstrapRpmCommon() {
pkgs=" pkgs="
gcc gcc
dialog
augeas-libs augeas-libs
openssl openssl
openssl-devel openssl-devel
@@ -11,7 +11,6 @@ BootstrapSuseCommon() {
python-devel \ python-devel \
python-virtualenv \ python-virtualenv \
gcc \ gcc \
dialog \
augeas-lenses \ augeas-lenses \
libopenssl-devel \ libopenssl-devel \
libffi-devel \ libffi-devel \
@@ -110,9 +110,6 @@ pyRFC3339==1.0 \
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535 --hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
python-augeas==0.5.0 \ python-augeas==0.5.0 \
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
python2-pythondialog==3.3.0 \
--hash=sha256:04e93f24995c43dd90f338d5d865ca72ce3fb5a5358d4daa4965571db35fc3ec \
--hash=sha256:3e6f593fead98f8a526bc3e306933533236e33729f552f52896ea504f55313fa
pytz==2015.7 \ pytz==2015.7 \
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \ --hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \ --hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \
-6
View File
@@ -51,12 +51,6 @@ install_requires = [
'zope.interface', '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 # env markers in extras_require cause problems with older pip: #517
# Keep in sync with conditional_requirements.py. # Keep in sync with conditional_requirements.py.
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
+2 -2
View File
@@ -18,5 +18,5 @@ def test_visual(displayer, choices):
if __name__ == "__main__": if __name__ == "__main__":
for displayer in util.NcursesDisplay(), util.FileDisplay(sys.stdout): displayer = util.FileDisplay(sys.stdout):
test_visual(displayer, util_test.CHOICES) test_visual(displayer, util_test.CHOICES)
-1
View File
@@ -38,7 +38,6 @@ deps =
py{26,27}-oldest: dnspython>=1.12 py{26,27}-oldest: dnspython>=1.12
py{26,27}-oldest: psutil==2.1.0 py{26,27}-oldest: psutil==2.1.0
py{26,27}-oldest: PyOpenSSL==0.13 py{26,27}-oldest: PyOpenSSL==0.13
py{26,27}-oldest: python2-pythondialog==3.2.2rc1
[testenv:py33] [testenv:py33]
commands = commands =