Fix circular import (#8967)

* add internal display util

* Move display constants internal.

* move other utilities internal

* fix OK and CANCEL documentation
This commit is contained in:
Brad Warren
2021-08-05 08:49:20 +02:00
committed by GitHub
parent 7b78770010
commit 3058b6e748
10 changed files with 312 additions and 269 deletions
+37 -57
View File
@@ -1,7 +1,6 @@
"""This modules define the actual display implementations used in Certbot""" """This modules define the actual display implementations used in Certbot"""
import logging import logging
import sys import sys
import textwrap
from typing import Any from typing import Any
from typing import Optional from typing import Optional
from typing import Union from typing import Union
@@ -13,11 +12,22 @@ from certbot import errors
from certbot import interfaces from certbot import interfaces
from certbot._internal import constants from certbot._internal import constants
from certbot._internal.display import completer from certbot._internal.display import completer
from certbot._internal.display import util
from certbot.compat import os from certbot.compat import os
from certbot.display import util
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Display exit codes
OK = "ok"
"""Display exit code indicating user acceptance."""
CANCEL = "cancel"
"""Display exit code for a user canceling the display."""
# Display constants
SIDE_FRAME = ("- " * 39) + "-"
"""Display boundary (alternates spaces, so when copy-pasted, markdown doesn't interpret
it as a heading)"""
# This class holds the global state of the display service. Using this class # This class holds the global state of the display service. Using this class
# eliminates potential gotchas that exist if self.display was just a global # eliminates potential gotchas that exist if self.display was just a global
@@ -62,7 +72,7 @@ class FileDisplay:
""" """
if wrap: if wrap:
message = _wrap_lines(message) message = util.wrap_lines(message)
logger.debug("Notifying user: %s", message) logger.debug("Notifying user: %s", message)
@@ -70,7 +80,7 @@ class FileDisplay:
(("{line}{frame}{line}" if decorate else "") + (("{line}{frame}{line}" if decorate else "") +
"{msg}{line}" + "{msg}{line}" +
("{frame}{line}" if decorate else "")) ("{frame}{line}" if decorate else ""))
.format(line=os.linesep, frame=util.SIDE_FRAME, msg=message) .format(line=os.linesep, frame=SIDE_FRAME, msg=message)
) )
self.outfile.flush() self.outfile.flush()
@@ -105,7 +115,7 @@ class FileDisplay:
""" """
if self._return_default(message, default, cli_flag, force_interactive): if self._return_default(message, default, cli_flag, force_interactive):
return util.OK, default return OK, default
self._print_menu(message, choices) self._print_menu(message, choices)
@@ -130,15 +140,16 @@ class FileDisplay:
""" """
if self._return_default(message, default, cli_flag, force_interactive): if self._return_default(message, default, cli_flag, force_interactive):
return util.OK, default return OK, default
# Trailing space must be added outside of _wrap_lines to be preserved # Trailing space must be added outside of util.wrap_lines to
message = _wrap_lines("%s (Enter 'c' to cancel):" % message) + " " # be preserved
message = util.wrap_lines("%s (Enter 'c' to cancel):" % message) + " "
ans = util.input_with_timeout(message) ans = util.input_with_timeout(message)
if ans in ("c", "C"): if ans in ("c", "C"):
return util.CANCEL, "-1" return CANCEL, "-1"
return util.OK, ans return OK, ans
def yesno(self, message, yes_label="Yes", no_label="No", default=None, def yesno(self, message, yes_label="Yes", no_label="No", default=None,
cli_flag=None, force_interactive=False, **unused_kwargs): cli_flag=None, force_interactive=False, **unused_kwargs):
@@ -162,16 +173,16 @@ class FileDisplay:
if self._return_default(message, default, cli_flag, force_interactive): if self._return_default(message, default, cli_flag, force_interactive):
return default return default
message = _wrap_lines(message) message = util.wrap_lines(message)
self.outfile.write("{0}{frame}{msg}{0}{frame}".format( self.outfile.write("{0}{frame}{msg}{0}{frame}".format(
os.linesep, frame=util.SIDE_FRAME + os.linesep, msg=message)) os.linesep, frame=SIDE_FRAME + os.linesep, msg=message))
self.outfile.flush() self.outfile.flush()
while True: while True:
ans = util.input_with_timeout("{yes}/{no}: ".format( ans = util.input_with_timeout("{yes}/{no}: ".format(
yes=_parens_around_char(yes_label), yes=util.parens_around_char(yes_label),
no=_parens_around_char(no_label))) no=util.parens_around_char(no_label)))
# Couldn't get pylint indentation right with elif # Couldn't get pylint indentation right with elif
# elif doesn't matter in this situation # elif doesn't matter in this situation
@@ -200,7 +211,7 @@ class FileDisplay:
""" """
if self._return_default(message, default, cli_flag, force_interactive): if self._return_default(message, default, cli_flag, force_interactive):
return util.OK, default return OK, default
while True: while True:
self._print_menu(message, tags) self._print_menu(message, tags)
@@ -210,7 +221,7 @@ class FileDisplay:
"blank to select all options shown", "blank to select all options shown",
force_interactive=True) force_interactive=True)
if code == util.OK: if code == OK:
if not ans.strip(): if not ans.strip():
ans = " ".join(str(x) for x in range(1, len(tags)+1)) ans = " ".join(str(x) for x in range(1, len(tags)+1))
indices = util.separate_list_input(ans) indices = util.separate_list_input(ans)
@@ -334,17 +345,17 @@ class FileDisplay:
# Write out the message to the user # Write out the message to the user
self.outfile.write( self.outfile.write(
"{new}{msg}{new}".format(new=os.linesep, msg=message)) "{new}{msg}{new}".format(new=os.linesep, msg=message))
self.outfile.write(util.SIDE_FRAME + os.linesep) self.outfile.write(SIDE_FRAME + os.linesep)
# Write out the menu choices # Write out the menu choices
for i, desc in enumerate(choices, 1): for i, desc in enumerate(choices, 1):
msg = "{num}: {desc}".format(num=i, desc=desc) msg = "{num}: {desc}".format(num=i, desc=desc)
self.outfile.write(_wrap_lines(msg)) self.outfile.write(util.wrap_lines(msg))
# Keep this outside of the textwrap # Keep this outside of the textwrap
self.outfile.write(os.linesep) self.outfile.write(os.linesep)
self.outfile.write(util.SIDE_FRAME + os.linesep) self.outfile.write(SIDE_FRAME + os.linesep)
self.outfile.flush() self.outfile.flush()
def _get_valid_int_ans(self, max_): def _get_valid_int_ans(self, max_):
@@ -369,7 +380,7 @@ class FileDisplay:
while selection < 1: while selection < 1:
ans = util.input_with_timeout(input_msg) ans = util.input_with_timeout(input_msg)
if ans.startswith("c") or ans.startswith("C"): if ans.startswith("c") or ans.startswith("C"):
return util.CANCEL, -1 return CANCEL, -1
try: try:
selection = int(ans) selection = int(ans)
if selection < 1 or selection > max_: if selection < 1 or selection > max_:
@@ -381,7 +392,7 @@ class FileDisplay:
"{0}** Invalid input **{0}".format(os.linesep)) "{0}** Invalid input **{0}".format(os.linesep))
self.outfile.flush() self.outfile.flush()
return util.OK, selection return OK, selection
# This use of IDisplay can be removed when this class is no longer accessible # This use of IDisplay can be removed when this class is no longer accessible
@@ -414,7 +425,7 @@ class NoninteractiveDisplay:
""" """
if wrap: if wrap:
message = _wrap_lines(message) message = util.wrap_lines(message)
logger.debug("Notifying user: %s", message) logger.debug("Notifying user: %s", message)
@@ -422,7 +433,7 @@ class NoninteractiveDisplay:
(("{line}{frame}{line}" if decorate else "") + (("{line}{frame}{line}" if decorate else "") +
"{msg}{line}" + "{msg}{line}" +
("{frame}{line}" if decorate else "")) ("{frame}{line}" if decorate else ""))
.format(line=os.linesep, frame=util.SIDE_FRAME, msg=message) .format(line=os.linesep, frame=SIDE_FRAME, msg=message)
) )
self.outfile.flush() self.outfile.flush()
@@ -448,7 +459,7 @@ class NoninteractiveDisplay:
if default is None: if default is None:
self._interaction_fail(message, cli_flag, "Choices: " + repr(choices)) self._interaction_fail(message, cli_flag, "Choices: " + repr(choices))
return util.OK, default return OK, default
def input(self, message, default=None, cli_flag=None, **unused_kwargs): def input(self, message, default=None, cli_flag=None, **unused_kwargs):
"""Accept input from the user. """Accept input from the user.
@@ -464,7 +475,7 @@ class NoninteractiveDisplay:
""" """
if default is None: if default is None:
self._interaction_fail(message, cli_flag) self._interaction_fail(message, cli_flag)
return util.OK, default return OK, default
def yesno(self, message, yes_label=None, no_label=None, # pylint: disable=unused-argument def yesno(self, message, yes_label=None, no_label=None, # pylint: disable=unused-argument
default=None, cli_flag=None, **unused_kwargs): default=None, cli_flag=None, **unused_kwargs):
@@ -498,7 +509,7 @@ class NoninteractiveDisplay:
""" """
if default is None: if default is None:
self._interaction_fail(message, cli_flag, "? ".join(tags)) self._interaction_fail(message, cli_flag, "? ".join(tags))
return util.OK, default return OK, default
def directory_select(self, message, default=None, def directory_select(self, message, default=None,
cli_flag=None, **unused_kwargs): cli_flag=None, **unused_kwargs):
@@ -551,34 +562,3 @@ def set_display(display: Any) -> None:
zope.component.provideUtility(display, interfaces.IDisplay) zope.component.provideUtility(display, interfaces.IDisplay)
_SERVICE.display = display _SERVICE.display = display
def _wrap_lines(msg):
"""Format lines nicely to 80 chars.
:param str msg: Original message
:returns: Formatted message respecting newlines in message
:rtype: str
"""
lines = msg.splitlines()
fixed_l = []
for line in lines:
fixed_l.append(textwrap.fill(
line,
80,
break_long_words=False,
break_on_hyphens=False))
return '\n'.join(fixed_l)
def _parens_around_char(label):
"""Place parens around first character of label.
:param str label: Must contain at least one character
"""
return "({first}){rest}".format(first=label[0], rest=label[1:])
+106
View File
@@ -0,0 +1,106 @@
"""Internal Certbot display utilities."""
from typing import List
import textwrap
import sys
from certbot.compat import misc
def wrap_lines(msg):
"""Format lines nicely to 80 chars.
:param str msg: Original message
:returns: Formatted message respecting newlines in message
:rtype: str
"""
lines = msg.splitlines()
fixed_l = []
for line in lines:
fixed_l.append(textwrap.fill(
line,
80,
break_long_words=False,
break_on_hyphens=False))
return '\n'.join(fixed_l)
def parens_around_char(label):
"""Place parens around first character of label.
:param str label: Must contain at least one character
"""
return "({first}){rest}".format(first=label[0], rest=label[1:])
def input_with_timeout(prompt=None, timeout=36000.0):
"""Get user input with a timeout.
Behaves the same as the builtin input, however, an error is raised if
a user doesn't answer after timeout seconds. The default timeout
value was chosen to place it just under 12 hours for users following
our advice and running Certbot twice a day.
:param str prompt: prompt to provide for input
:param float timeout: maximum number of seconds to wait for input
:returns: user response
:rtype: str
:raises errors.Error if no answer is given before the timeout
"""
# use of sys.stdin and sys.stdout to mimic the builtin input based on
# https://github.com/python/cpython/blob/baf7bb30a02aabde260143136bdf5b3738a1d409/Lib/getpass.py#L129
if prompt:
sys.stdout.write(prompt)
sys.stdout.flush()
line = misc.readline_with_timeout(timeout, prompt)
if not line:
raise EOFError
return line.rstrip('\n')
def separate_list_input(input_):
"""Separate a comma or space separated list.
:param str input_: input from the user
:returns: strings
:rtype: list
"""
no_commas = input_.replace(",", " ")
# Each string is naturally unicode, this causes problems with M2Crypto SANs
# TODO: check if above is still true when M2Crypto is gone ^
return [str(string) for string in no_commas.split()]
def summarize_domain_list(domains: List[str]) -> str:
"""Summarizes a list of domains in the format of:
example.com.com and N more domains
or if there is are only two domains:
example.com and www.example.com
or if there is only one domain:
example.com
:param list domains: `str` list of domains
:returns: the domain list summary
:rtype: str
"""
if not domains:
return ""
l = len(domains)
if l == 1:
return domains[0]
elif l == 2:
return " and ".join(domains)
else:
return "{0} and {1} more domains".format(domains[0], l-1)
+4 -3
View File
@@ -38,6 +38,7 @@ from certbot._internal import snap_config
from certbot._internal import storage from certbot._internal import storage
from certbot._internal import updater from certbot._internal import updater
from certbot._internal.display import obj as display_obj from certbot._internal.display import obj as display_obj
from certbot._internal.display import util as internal_display_util
from certbot._internal.plugins import disco as plugins_disco from certbot._internal.plugins import disco as plugins_disco
from certbot._internal.plugins import selection as plug_sel from certbot._internal.plugins import selection as plug_sel
from certbot.compat import filesystem from certbot.compat import filesystem
@@ -111,7 +112,7 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N
"{action} for {domains}".format( "{action} for {domains}".format(
action="Simulating renewal of an existing certificate" action="Simulating renewal of an existing certificate"
if config.dry_run else "Renewing an existing certificate", if config.dry_run else "Renewing an existing certificate",
domains=display_util.summarize_domain_list(domains or lineage.names()) domains=internal_display_util.summarize_domain_list(domains or lineage.names())
) )
) )
renewal.renew_cert(config, domains, le_client, lineage) renewal.renew_cert(config, domains, le_client, lineage)
@@ -122,7 +123,7 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N
"{action} for {domains}".format( "{action} for {domains}".format(
action="Simulating a certificate request" if config.dry_run else action="Simulating a certificate request" if config.dry_run else
"Requesting a certificate", "Requesting a certificate",
domains=display_util.summarize_domain_list(domains) domains=internal_display_util.summarize_domain_list(domains)
) )
) )
lineage = le_client.obtain_and_enroll_certificate(domains, certname) lineage = le_client.obtain_and_enroll_certificate(domains, certname)
@@ -1339,7 +1340,7 @@ def _csr_get_and_save_cert(config, le_client):
"{action} for {domains}".format( "{action} for {domains}".format(
action="Simulating a certificate request" if config.dry_run else action="Simulating a certificate request" if config.dry_run else
"Requesting a certificate", "Requesting a certificate",
domains=display_util.summarize_domain_list(csr_names) domains=internal_display_util.summarize_domain_list(csr_names)
) )
) )
cert, chain = le_client.obtain_certificate_from_csr(csr) cert, chain = le_client.obtain_certificate_from_csr(csr)
+3 -2
View File
@@ -26,6 +26,7 @@ from certbot._internal import constants
from certbot._internal import hooks from certbot._internal import hooks
from certbot._internal import storage from certbot._internal import storage
from certbot._internal import updater from certbot._internal import updater
from certbot._internal.display import obj as display_obj
from certbot._internal.plugins import disco as plugins_disco from certbot._internal.plugins import disco as plugins_disco
from certbot.compat import os from certbot.compat import os
from certbot.display import util as display_util from certbot.display import util as display_util
@@ -363,7 +364,7 @@ def _renew_describe_results(config: interfaces.IConfig, renew_successes: List[st
notify = display_util.notify notify = display_util.notify
notify_error = logger.error notify_error = logger.error
notify('\n{}'.format(display_util.SIDE_FRAME)) notify('\n{}'.format(display_obj.SIDE_FRAME))
renewal_noun = "simulated renewal" if config.dry_run else "renewal" renewal_noun = "simulated renewal" if config.dry_run else "renewal"
@@ -393,7 +394,7 @@ def _renew_describe_results(config: interfaces.IConfig, renew_successes: List[st
"were invalid: ") "were invalid: ")
notify(report(parse_failures, "parsefail")) notify(report(parse_failures, "parsefail"))
notify(display_util.SIDE_FRAME) notify(display_obj.SIDE_FRAME)
def handle_renewal_request(config): def handle_renewal_request(config):
+2 -1
View File
@@ -4,6 +4,7 @@ from textwrap import indent
from certbot import errors from certbot import errors
from certbot import util from certbot import util
from certbot._internal.display import util as internal_display_util
from certbot.compat import os from certbot.compat import os
from certbot.display import util as display_util from certbot.display import util as display_util
@@ -193,7 +194,7 @@ def _choose_names_manually(prompt_prefix=""):
invalid_domains = {} invalid_domains = {}
retry_message = "" retry_message = ""
try: try:
domain_list = display_util.separate_list_input(input_) domain_list = internal_display_util.separate_list_input(input_)
except UnicodeEncodeError: except UnicodeEncodeError:
domain_list = [] domain_list = []
retry_message = ( retry_message = (
+15 -84
View File
@@ -9,42 +9,42 @@ should be used whenever:
Other messages can use the `logging` module. See `log.py`. Other messages can use the `logging` module. See `log.py`.
""" """
import logging
import sys
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
from certbot.compat import misc # These specific imports from certbot._internal.display.obj and
# These imports are done to not break the public API of the module. # certbot._internal.display.util are done to not break the public API of this
# module.
from certbot._internal.display.obj import FileDisplay # pylint: disable=unused-import from certbot._internal.display.obj import FileDisplay # pylint: disable=unused-import
from certbot._internal.display.obj import NoninteractiveDisplay # pylint: disable=unused-import from certbot._internal.display.obj import NoninteractiveDisplay # pylint: disable=unused-import
from certbot._internal.display.obj import SIDE_FRAME # pylint: disable=unused-import
from certbot._internal.display.util import input_with_timeout # pylint: disable=unused-import
from certbot._internal.display.util import separate_list_input # pylint: disable=unused-import
from certbot._internal.display.util import summarize_domain_list # pylint: disable=unused-import
from certbot._internal.display import obj from certbot._internal.display import obj
logger = logging.getLogger(__name__)
WIDTH = 72 # These constants are defined this way to make them easier to document with
# Sphinx and to not couple our public docstrings to our internal ones.
# Display exit codes OK = obj.OK
OK = "ok"
"""Display exit code indicating user acceptance.""" """Display exit code indicating user acceptance."""
CANCEL = "cancel" CANCEL = obj.CANCEL
"""Display exit code for a user canceling the display.""" """Display exit code for a user canceling the display."""
# These constants are unused and should be removed in a major release of
# Certbot.
WIDTH = 72
HELP = "help" HELP = "help"
"""Display exit code when for when the user requests more help. (UNUSED)""" """Display exit code when for when the user requests more help. (UNUSED)"""
ESC = "esc" ESC = "esc"
"""Display exit code when the user hits Escape (UNUSED)""" """Display exit code when the user hits Escape (UNUSED)"""
# Display constants
SIDE_FRAME = ("- " * 39) + "-"
"""Display boundary (alternates spaces, so when copy-pasted, markdown doesn't interpret
it as a heading)"""
def notify(msg: str) -> None: def notify(msg: str) -> None:
"""Display a basic status message. """Display a basic status message.
@@ -186,36 +186,6 @@ def directory_select(message: str, default: Optional[str] = None, cli_flag: Opti
force_interactive=force_interactive) force_interactive=force_interactive)
def input_with_timeout(prompt=None, timeout=36000.0):
"""Get user input with a timeout.
Behaves the same as the builtin input, however, an error is raised if
a user doesn't answer after timeout seconds. The default timeout
value was chosen to place it just under 12 hours for users following
our advice and running Certbot twice a day.
:param str prompt: prompt to provide for input
:param float timeout: maximum number of seconds to wait for input
:returns: user response
:rtype: str
:raises errors.Error if no answer is given before the timeout
"""
# use of sys.stdin and sys.stdout to mimic the builtin input based on
# https://github.com/python/cpython/blob/baf7bb30a02aabde260143136bdf5b3738a1d409/Lib/getpass.py#L129
if prompt:
sys.stdout.write(prompt)
sys.stdout.flush()
line = misc.readline_with_timeout(timeout, prompt)
if not line:
raise EOFError
return line.rstrip('\n')
def assert_valid_call(prompt, default, cli_flag, force_interactive): def assert_valid_call(prompt, default, cli_flag, force_interactive):
"""Verify that provided arguments is a valid IDisplay call. """Verify that provided arguments is a valid IDisplay call.
@@ -232,42 +202,3 @@ def assert_valid_call(prompt, default, cli_flag, force_interactive):
msg += ("\nYou can set an answer to " msg += ("\nYou can set an answer to "
"this prompt with the {0} flag".format(cli_flag)) "this prompt with the {0} flag".format(cli_flag))
assert default is not None or force_interactive, msg assert default is not None or force_interactive, msg
def separate_list_input(input_):
"""Separate a comma or space separated list.
:param str input_: input from the user
:returns: strings
:rtype: list
"""
no_commas = input_.replace(",", " ")
# Each string is naturally unicode, this causes problems with M2Crypto SANs
# TODO: check if above is still true when M2Crypto is gone ^
return [str(string) for string in no_commas.split()]
def summarize_domain_list(domains: List[str]) -> str:
"""Summarizes a list of domains in the format of:
example.com.com and N more domains
or if there is are only two domains:
example.com and www.example.com
or if there is only one domain:
example.com
:param list domains: `str` list of domains
:returns: the domain list summary
:rtype: str
"""
if not domains:
return ""
l = len(domains)
if l == 1:
return domains[0]
elif l == 2:
return " and ".join(domains)
else:
return "{0} and {1} more domains".format(domains[0], l-1)
+129
View File
@@ -0,0 +1,129 @@
"""Test :mod:`certbot._internal.display.util`."""
import io
import socket
import tempfile
import unittest
from certbot import errors
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock
class WrapLinesTest(unittest.TestCase):
def test_wrap_lines(self):
from certbot._internal.display.util import wrap_lines
msg = ("This is just a weak test{0}"
"This function is only meant to be for easy viewing{0}"
"Test a really really really really really really really really "
"really really really really long line...".format('\n'))
text = wrap_lines(msg)
self.assertEqual(text.count('\n'), 3)
class PlaceParensTest(unittest.TestCase):
@classmethod
def _call(cls, label):
from certbot._internal.display.util import parens_around_char
return parens_around_char(label)
def test_single_letter(self):
self.assertEqual("(a)", self._call("a"))
def test_multiple(self):
self.assertEqual("(L)abel", self._call("Label"))
self.assertEqual("(y)es please", self._call("yes please"))
class InputWithTimeoutTest(unittest.TestCase):
"""Tests for certbot._internal.display.util.input_with_timeout."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot._internal.display.util import input_with_timeout
return input_with_timeout(*args, **kwargs)
def test_eof(self):
with tempfile.TemporaryFile("r+") as f:
with mock.patch("certbot._internal.display.util.sys.stdin", new=f):
self.assertRaises(EOFError, self._call)
def test_input(self, prompt=None):
expected = "foo bar"
stdin = io.StringIO(expected + "\n")
with mock.patch("certbot.compat.misc.select.select") as mock_select:
mock_select.return_value = ([stdin], [], [],)
self.assertEqual(self._call(prompt), expected)
@mock.patch("certbot._internal.display.util.sys.stdout")
def test_input_with_prompt(self, mock_stdout):
prompt = "test prompt: "
self.test_input(prompt)
mock_stdout.write.assert_called_once_with(prompt)
mock_stdout.flush.assert_called_once_with()
def test_timeout(self):
stdin = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
stdin.bind(('', 0))
stdin.listen(1)
with mock.patch("certbot._internal.display.util.sys.stdin", stdin):
self.assertRaises(errors.Error, self._call, timeout=0.001)
stdin.close()
class SeparateListInputTest(unittest.TestCase):
"""Test Module functions."""
def setUp(self):
self.exp = ["a", "b", "c", "test"]
@classmethod
def _call(cls, input_):
from certbot._internal.display.util import separate_list_input
return separate_list_input(input_)
def test_commas(self):
self.assertEqual(self._call("a,b,c,test"), self.exp)
def test_spaces(self):
self.assertEqual(self._call("a b c test"), self.exp)
def test_both(self):
self.assertEqual(self._call("a, b, c, test"), self.exp)
def test_mess(self):
actual = [
self._call(" a , b c \t test"),
self._call(",a, ,, , b c test "),
self._call(",,,,, , a b,,, , c,test"),
]
for act in actual:
self.assertEqual(act, self.exp)
class SummarizeDomainListTest(unittest.TestCase):
@classmethod
def _call(cls, domains):
from certbot._internal.display.util import summarize_domain_list
return summarize_domain_list(domains)
def test_single_domain(self):
self.assertEqual("example.com", self._call(["example.com"]))
def test_two_domains(self):
self.assertEqual("example.com and example.org",
self._call(["example.com", "example.org"]))
def test_many_domains(self):
self.assertEqual("example.com and 2 more domains",
self._call(["example.com", "example.org", "a.example.com"]))
def test_empty_domains(self):
self.assertEqual("", self._call([]))
if __name__ == "__main__":
unittest.main() # pragma: no cover
+15 -35
View File
@@ -32,7 +32,7 @@ class FileOutputDisplayTest(unittest.TestCase):
mock_logger.debug.assert_called_with("Notifying user: %s", "message") mock_logger.debug.assert_called_with("Notifying user: %s", "message")
def test_notification_pause(self): def test_notification_pause(self):
input_with_timeout = "certbot.display.util.input_with_timeout" input_with_timeout = "certbot._internal.display.util.input_with_timeout"
with mock.patch(input_with_timeout, return_value="enter"): with mock.patch(input_with_timeout, return_value="enter"):
self.displayer.notification("message", force_interactive=True) self.displayer.notification("message", force_interactive=True)
@@ -67,7 +67,7 @@ class FileOutputDisplayTest(unittest.TestCase):
self.assertIn("- - - ", string) self.assertIn("- - - ", string)
self.assertIn("message2" + os.linesep, string) self.assertIn("message2" + os.linesep, string)
@mock.patch("certbot.display.util." @mock.patch("certbot._internal.display.obj."
"FileDisplay._get_valid_int_ans") "FileDisplay._get_valid_int_ans")
def test_menu(self, mock_ans): def test_menu(self, mock_ans):
mock_ans.return_value = (display_util.OK, 1) mock_ans.return_value = (display_util.OK, 1)
@@ -81,14 +81,14 @@ class FileOutputDisplayTest(unittest.TestCase):
self.assertEqual(result, (display_util.OK, default)) self.assertEqual(result, (display_util.OK, default))
def test_input_cancel(self): def test_input_cancel(self):
input_with_timeout = "certbot.display.util.input_with_timeout" input_with_timeout = "certbot._internal.display.util.input_with_timeout"
with mock.patch(input_with_timeout, return_value="c"): with mock.patch(input_with_timeout, return_value="c"):
code, _ = self.displayer.input("message", force_interactive=True) code, _ = self.displayer.input("message", force_interactive=True)
self.assertTrue(code, display_util.CANCEL) self.assertTrue(code, display_util.CANCEL)
def test_input_normal(self): def test_input_normal(self):
input_with_timeout = "certbot.display.util.input_with_timeout" input_with_timeout = "certbot._internal.display.util.input_with_timeout"
with mock.patch(input_with_timeout, return_value="domain.com"): with mock.patch(input_with_timeout, return_value="domain.com"):
code, input_ = self.displayer.input("message", force_interactive=True) code, input_ = self.displayer.input("message", force_interactive=True)
@@ -115,7 +115,7 @@ class FileOutputDisplayTest(unittest.TestCase):
self.displayer.input, "msg", cli_flag="--flag") self.displayer.input, "msg", cli_flag="--flag")
def test_yesno(self): def test_yesno(self):
input_with_timeout = "certbot.display.util.input_with_timeout" input_with_timeout = "certbot._internal.display.util.input_with_timeout"
with mock.patch(input_with_timeout, return_value="Yes"): with mock.patch(input_with_timeout, return_value="Yes"):
self.assertTrue(self.displayer.yesno( self.assertTrue(self.displayer.yesno(
"message", force_interactive=True)) "message", force_interactive=True))
@@ -140,7 +140,7 @@ class FileOutputDisplayTest(unittest.TestCase):
self.assertTrue(self._force_noninteractive( self.assertTrue(self._force_noninteractive(
self.displayer.yesno, "message", default=True)) self.displayer.yesno, "message", default=True))
@mock.patch("certbot.display.util.input_with_timeout") @mock.patch("certbot._internal.display.util.input_with_timeout")
def test_checklist_valid(self, mock_input): def test_checklist_valid(self, mock_input):
mock_input.return_value = "2 1" mock_input.return_value = "2 1"
code, tag_list = self.displayer.checklist( code, tag_list = self.displayer.checklist(
@@ -148,21 +148,21 @@ class FileOutputDisplayTest(unittest.TestCase):
self.assertEqual( self.assertEqual(
(code, set(tag_list)), (display_util.OK, {"tag1", "tag2"})) (code, set(tag_list)), (display_util.OK, {"tag1", "tag2"}))
@mock.patch("certbot.display.util.input_with_timeout") @mock.patch("certbot._internal.display.util.input_with_timeout")
def test_checklist_empty(self, mock_input): def test_checklist_empty(self, mock_input):
mock_input.return_value = "" mock_input.return_value = ""
code, tag_list = self.displayer.checklist("msg", TAGS, force_interactive=True) code, tag_list = self.displayer.checklist("msg", TAGS, force_interactive=True)
self.assertEqual( self.assertEqual(
(code, set(tag_list)), (display_util.OK, {"tag1", "tag2", "tag3"})) (code, set(tag_list)), (display_util.OK, {"tag1", "tag2", "tag3"}))
@mock.patch("certbot.display.util.input_with_timeout") @mock.patch("certbot._internal.display.util.input_with_timeout")
def test_checklist_miss_valid(self, mock_input): def test_checklist_miss_valid(self, mock_input):
mock_input.side_effect = ["10", "tag1 please", "1"] mock_input.side_effect = ["10", "tag1 please", "1"]
ret = self.displayer.checklist("msg", TAGS, force_interactive=True) ret = self.displayer.checklist("msg", TAGS, force_interactive=True)
self.assertEqual(ret, (display_util.OK, ["tag1"])) self.assertEqual(ret, (display_util.OK, ["tag1"]))
@mock.patch("certbot.display.util.input_with_timeout") @mock.patch("certbot._internal.display.util.input_with_timeout")
def test_checklist_miss_quit(self, mock_input): def test_checklist_miss_quit(self, mock_input):
mock_input.side_effect = ["10", "c"] mock_input.side_effect = ["10", "c"]
@@ -194,7 +194,7 @@ class FileOutputDisplayTest(unittest.TestCase):
self.displayer._scrub_checklist_input(list_, TAGS)) self.displayer._scrub_checklist_input(list_, TAGS))
self.assertEqual(set_tags, exp[i]) self.assertEqual(set_tags, exp[i])
@mock.patch("certbot.display.util.input_with_timeout") @mock.patch("certbot._internal.display.util.input_with_timeout")
def test_directory_select(self, mock_input): def test_directory_select(self, mock_input):
args = ["msg", "/var/www/html", "--flag", True] args = ["msg", "/var/www/html", "--flag", True]
user_input = "/var/www/html" user_input = "/var/www/html"
@@ -214,7 +214,7 @@ class FileOutputDisplayTest(unittest.TestCase):
def _force_noninteractive(self, func, *args, **kwargs): def _force_noninteractive(self, func, *args, **kwargs):
skipped_interaction = self.displayer.skipped_interaction skipped_interaction = self.displayer.skipped_interaction
with mock.patch("certbot.display.util.sys.stdin") as mock_stdin: with mock.patch("certbot._internal.display.obj.sys.stdin") as mock_stdin:
mock_stdin.isatty.return_value = False mock_stdin.isatty.return_value = False
with mock.patch("certbot._internal.display.obj.logger") as mock_logger: with mock.patch("certbot._internal.display.obj.logger") as mock_logger:
result = func(*args, **kwargs) result = func(*args, **kwargs)
@@ -245,19 +245,9 @@ class FileOutputDisplayTest(unittest.TestCase):
self.displayer._print_menu("msg", CHOICES) self.displayer._print_menu("msg", CHOICES)
self.displayer._print_menu("msg", TAGS) self.displayer._print_menu("msg", TAGS)
def test_wrap_lines(self):
# pylint: disable=protected-access
msg = ("This is just a weak test{0}"
"This function is only meant to be for easy viewing{0}"
"Test a really really really really really really really really "
"really really really really long line...".format('\n'))
text = display_obj._wrap_lines(msg)
self.assertEqual(text.count('\n'), 3)
def test_get_valid_int_ans_valid(self): def test_get_valid_int_ans_valid(self):
# pylint: disable=protected-access # pylint: disable=protected-access
input_with_timeout = "certbot.display.util.input_with_timeout" input_with_timeout = "certbot._internal.display.util.input_with_timeout"
with mock.patch(input_with_timeout, return_value="1"): with mock.patch(input_with_timeout, return_value="1"):
self.assertEqual( self.assertEqual(
self.displayer._get_valid_int_ans(1), (display_util.OK, 1)) self.displayer._get_valid_int_ans(1), (display_util.OK, 1))
@@ -274,7 +264,7 @@ class FileOutputDisplayTest(unittest.TestCase):
["4", "one", "C"], ["4", "one", "C"],
["c"], ["c"],
] ]
input_with_timeout = "certbot.display.util.input_with_timeout" input_with_timeout = "certbot._internal.display.util.input_with_timeout"
for ans in answers: for ans in answers:
with mock.patch(input_with_timeout, side_effect=ans): with mock.patch(input_with_timeout, side_effect=ans):
self.assertEqual( self.assertEqual(
@@ -359,15 +349,5 @@ class NoninteractiveDisplayTest(unittest.TestCase):
self.assertIsNotNone(result) self.assertIsNotNone(result)
class PlaceParensTest(unittest.TestCase): if __name__ == "__main__":
@classmethod unittest.main() # pragma: no cover
def _call(cls, label): # pylint: disable=protected-access
from certbot._internal.display.obj import _parens_around_char
return _parens_around_char(label)
def test_single_letter(self):
self.assertEqual("(a)", self._call("a"))
def test_multiple(self):
self.assertEqual("(L)abel", self._call("Label"))
self.assertEqual("(y)es please", self._call("yes please"))
+1 -1
View File
@@ -304,7 +304,7 @@ class ChooseNamesTest(unittest.TestCase):
self.assertEqual(_choose_names_manually(), []) self.assertEqual(_choose_names_manually(), [])
# IDN exception with previous mocks # IDN exception with previous mocks
with mock.patch( with mock.patch(
"certbot.display.ops.display_util.separate_list_input" "certbot.display.ops.internal_display_util.separate_list_input"
) as mock_sli: ) as mock_sli:
unicode_error = UnicodeEncodeError('mock', u'', 0, 1, 'mock') unicode_error = UnicodeEncodeError('mock', u'', 0, 1, 'mock')
mock_sli.side_effect = unicode_error mock_sli.side_effect = unicode_error
-86
View File
@@ -98,91 +98,5 @@ class DirectorySelectTest(unittest.TestCase):
) )
class InputWithTimeoutTest(unittest.TestCase):
"""Tests for certbot.display.util.input_with_timeout."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.display.util import input_with_timeout
return input_with_timeout(*args, **kwargs)
def test_eof(self):
with tempfile.TemporaryFile("r+") as f:
with mock.patch("certbot.display.util.sys.stdin", new=f):
self.assertRaises(EOFError, self._call)
def test_input(self, prompt=None):
expected = "foo bar"
stdin = io.StringIO(expected + "\n")
with mock.patch("certbot.compat.misc.select.select") as mock_select:
mock_select.return_value = ([stdin], [], [],)
self.assertEqual(self._call(prompt), expected)
@mock.patch("certbot.display.util.sys.stdout")
def test_input_with_prompt(self, mock_stdout):
prompt = "test prompt: "
self.test_input(prompt)
mock_stdout.write.assert_called_once_with(prompt)
mock_stdout.flush.assert_called_once_with()
def test_timeout(self):
stdin = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
stdin.bind(('', 0))
stdin.listen(1)
with mock.patch("certbot.display.util.sys.stdin", stdin):
self.assertRaises(errors.Error, self._call, timeout=0.001)
stdin.close()
class SeparateListInputTest(unittest.TestCase):
"""Test Module functions."""
def setUp(self):
self.exp = ["a", "b", "c", "test"]
@classmethod
def _call(cls, input_):
from certbot.display.util import separate_list_input
return separate_list_input(input_)
def test_commas(self):
self.assertEqual(self._call("a,b,c,test"), self.exp)
def test_spaces(self):
self.assertEqual(self._call("a b c test"), self.exp)
def test_both(self):
self.assertEqual(self._call("a, b, c, test"), self.exp)
def test_mess(self):
actual = [
self._call(" a , b c \t test"),
self._call(",a, ,, , b c test "),
self._call(",,,,, , a b,,, , c,test"),
]
for act in actual:
self.assertEqual(act, self.exp)
class SummarizeDomainListTest(unittest.TestCase):
@classmethod
def _call(cls, domains):
from certbot.display.util import summarize_domain_list
return summarize_domain_list(domains)
def test_single_domain(self):
self.assertEqual("example.com", self._call(["example.com"]))
def test_two_domains(self):
self.assertEqual("example.com and example.org",
self._call(["example.com", "example.org"]))
def test_many_domains(self):
self.assertEqual("example.com and 2 more domains",
self._call(["example.com", "example.org", "a.example.com"]))
def test_empty_domains(self):
self.assertEqual("", self._call([]))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() # pragma: no cover unittest.main() # pragma: no cover