Unittests for display_util

This commit is contained in:
James Kasten 2015-02-08 21:02:23 -08:00
parent 168a70c273
commit 01899f387e
5 changed files with 292 additions and 415 deletions

View file

@ -1,311 +0,0 @@
"""Lets Encrypt display."""
import os
import textwrap
import dialog
import zope.interface
from letsencrypt.client import interfaces
WIDTH = 72
HEIGHT = 20
class NcursesDisplay(object):
"""Ncurses-based display."""
zope.interface.implements(interfaces.IDisplay)
def __init__(self, width=WIDTH, height=HEIGHT):
super(NcursesDisplay, self).__init__()
self.dialog = dialog.Dialog()
self.width = width
self.height = height
def notification(self, message, height=10):
"""Display a notification to the user and wait for user acceptance.
:param str message: Message to display
:param int height: Height of the dialog box
"""
self.dialog.msgbox(message, height, width=self.width)
def menu(self, message, choices, unused_input_text="",
ok_label="OK", help_label=""):
"""Display a menu.
:param str message: title of menu
:param choices: menu lines
:type choices: list of tuples (tag, item) or
list of items (tags will be enumerated)
:returns: tuple of the form (code, tag) where
code is a display exit code
tag is the tag string corresponding to the item chosen
:rtype: tuple
"""
if help_label:
help_button = True
else:
help_button = False
# Can accept either tuples or just the actual choices
if choices and isinstance(choices[0], tuple):
code, selection = self.dialog.menu(
message, choices=choices, ok_label=ok_label,
help_button=help_button, help_label=help_label,
width=self.width, height=self.height)
return code, str(selection)
else:
choices = list(enumerate(choices, 1))
code, tag = self.dialog.menu(
message, choices=choices, ok_label=ok_label,
help_button=help_button, help_label=help_label,
width=self.width, height=self.height)
return code, int(tag) - 1
def input(self, message):
"""Display an input box to the user.
:param str message: Message to display that asks for input.
:returns: tuple of the form (code, string) where
code is a display exit code
string is the input entered by the user
"""
return self.dialog.inputbox(message)
def yesno(self, message, yes_label="Yes", no_label="No"):
"""Display a Yes/No dialog box
: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
:returns: if yes_label was selected
:rtype: bool
"""
return self.dialog.DIALOG_OK == self.dialog.yesno(
message, self.height, self.width,
yes_label=yes_label, no_label=no_label)
def filter_names(self, names):
"""Determine which names the user would like to select from a list.
:param list names: domain names
:returns: tuple of the form (code, names) where
code is a display exit code
names is a list of names selected
:rtype: tuple
"""
choices = [(n, "", 0) for n in names]
code, names = self.dialog.checklist(
"Which names would you like to activate HTTPS for?",
choices=choices)
return code, [str(s) for s in names]
def success_installation(self, domains):
"""Display a box confirming the installation of HTTPS.
:param list domains: domain names which were enabled
"""
self.dialog.msgbox(
"\nCongratulations! You have successfully enabled "
+ gen_https_names(domains) + "!", width=self.width)
class FileDisplay(object):
"""File-based display."""
zope.interface.implements(interfaces.IDisplay)
def __init__(self, outfile):
super(FileDisplay, self).__init__()
self.outfile = outfile
def notification(self, message, unused_height):
"""Displays a notification and waits for user acceptance.
:param str message: Message to display
"""
side_frame = "-" * 79
lines = message.splitlines()
fixed_l = []
for line in lines:
fixed_l.append(textwrap.fill(line, 80))
self.outfile.write(
"{0}{1}{0}{2}{0}{1}{0}".format(
os.linesep, side_frame, os.linesep.join(fixed_l)))
raw_input("Press Enter to Continue")
def menu(self, message, choices, input_text="",
unused_ok_label = "", unused_help_label=""):
"""Display a menu.
:param str message: title of menu
:param choices: Menu lines
:type choices: list of tuples (tag, item) or
list of items (tags will be enumerated)
:returns: tuple of the form (code, tag) where
code is a display exit code
tag is the tag string corresponding to the item chosen
:rtype: tuple
"""
# Can take either tuples or single items in choices list
if choices and isinstance(choices[0], tuple):
choices = ["%s - %s" % (c[0], c[1]) for c in choices]
self.outfile.write("\n%s\n" % message)
side_frame = "-" * 79
self.outfile.write("%s\n" % side_frame)
for i, choice in enumerate(choices, 1):
self.outfile.write(textwrap.fill(
"%d: %s" % (i, choice), 80) + "\n")
self.outfile.write("%s\n" % side_frame)
code, selection = self._get_valid_int_ans(
"%s (c to cancel): " % input_text)
return code, (selection - 1)
def input(self, message):
# pylint: disable=no-self-use
"""Accept input from the user
:param str message: message to display to the user
:returns: tuple of (code, input) where
code is a display exit code
input is a str of the user's input
:rtype: tuple
"""
ans = raw_input("%s (Enter c to cancel)\n" % message)
if ans == "c" or ans == "C":
return CANCEL, "-1"
else:
return OK, ans
def yesno(self, message, unused_yes_label="", unused_no_label=""):
"""Query the user with a yes/no question.
:param str message: question for the user
:returns: True for "Yes", False for "No"
:rtype: bool
"""
self.outfile.write("\n%s\n" % textwrap.fill(message, 80))
ans = raw_input("y/n: ")
return ans.startswith("y") or ans.startswith("Y")
def filter_names(self, names):
"""Determine which names the user would like to select from a list.
:param list names: domain names
:returns: tuple of the form (code, names) where
code is a display exit code
names is a list of names selected
:rtype: tuple
"""
code, tag = self.menu(
"Choose the names would you like to upgrade to HTTPS?",
names, "Select the number of the name: ")
# Make sure to return a list...
return code, [names[tag]]
def success_installation(self, domains):
"""Display a box confirming the installation of HTTPS.
:param list domains: domain names which were enabled
"""
side_frame = '*' * 79
msg = textwrap.fill("Congratulations! You have successfully "
"enabled %s!" % gen_https_names(domains))
self.outfile.write("%s\n%s\n%s\n" % (side_frame, msg, side_frame))
def _get_valid_int_ans(self, input_string):
"""Get a numerical selection.
:param str input_string: Instructions for the user to make a selection.
:returns: tuple of the form (code, selection) where
code is a display exit code
selection is the user"s int selection
:rtype: tuple
"""
valid_ans = False
e_msg = "Make a selection by inputting the appropriate number.\n"
while not valid_ans:
ans = raw_input(input_string)
if ans.startswith("c") or ans.startswith("C"):
code = CANCEL
selection = -1
valid_ans = True
else:
try:
selection = int(ans)
# TODO add check to make sure it is less than max
if selection < 0:
self.outfile.write(e_msg)
continue
code = OK
valid_ans = True
except ValueError:
self.outfile.write(e_msg)
return code, selection
# Display exit codes
OK = "ok"
"""Display exit code indicating user acceptance"""
CANCEL = "cancel"
"""Display exit code for a user canceling the display"""
HELP = "help"
"""Display exit code when for when the user requests more help."""
def gen_https_names(domains):
"""Returns a string of the https domains.
Domains are formatted nicely with https:// prepended to each.
.. todo:: This should not use +=, rewrite this with unittests
"""
result = ""
if len(domains) > 2:
for i in range(len(domains)-1):
result = result + "https://" + domains[i] + ", "
result = result + "and "
if len(domains) == 2:
return "https://" + domains[0] + " and https://" + domains[1]
if domains:
result = result + "https://" + domains[len(domains)-1]
return result

View file

@ -5,6 +5,7 @@ import textwrap
import dialog
import zope.interface
from letsencrypt.client import errors
from letsencrypt.client import interfaces
@ -49,8 +50,8 @@ class NcursesDisplay(object):
:param str message: title of menu
:param choices: menu lines
:type choices: list of tuples (tag, item) or
: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
@ -58,7 +59,7 @@ class NcursesDisplay(object):
:returns: tuple of the form (`code`, `tag`) where
`code` - `str` display_util exit code
`tag` - `str` or `int` index corresponding to the item chosen
`tag` - `int` index corresponding to the item chosen
:rtype: tuple
"""
@ -75,7 +76,13 @@ class NcursesDisplay(object):
help_button=help_button, help_label=help_label,
width=self.width, height=self.height)
return code, str(selection)
# Return the selection index
for i, choice in enumerate(choices):
if choice[0] == selection:
return code, i
return code, -1
else:
choices = [
(str(i), choice) for i, choice in enumerate(choices, 1)
@ -103,6 +110,8 @@ class NcursesDisplay(object):
def yesno(self, message, yes_label="Yes", no_label="No"):
"""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
@ -120,6 +129,7 @@ class NcursesDisplay(object):
:param message: Message to display before choices
:param list tags: where each is of type :class:`str`
len(tags) > 0
:returns: tuple of the form (code, list_tags) where
`code` - int display exit code
@ -161,7 +171,7 @@ class FileDisplay(object):
"""Display a menu.
:param str message: title of menu
:param choices: Menu lines
:param choices: Menu lines, len must be > 0
:type choices: list of tuples (tag, item) or
list of descriptions (tags will be enumerated)
@ -175,7 +185,7 @@ class FileDisplay(object):
code, selection = self._get_valid_int_ans(len(choices))
return code, str(selection - 1)
return code, selection - 1
def input(self, message):
# pylint: disable=no-self-use
@ -190,7 +200,7 @@ class FileDisplay(object):
"""
ans = raw_input(
textwrap.fill("%s (Enter c to cancel): " % message, 80))
textwrap.fill("%s (Enter 'c' to cancel): " % message, 80))
if ans == "c" or ans == "C":
return CANCEL, "-1"
@ -200,6 +210,8 @@ class FileDisplay(object):
def yesno(self, message, yes_label="Yes", no_label="No"):
"""Query the user with a yes/no question.
Yes and No label must begin with different letters
:param str message: question for the user
:param str yes_label: Label of the "Yes" parameter
:param str no_label: Label of the "No" parameter
@ -215,10 +227,9 @@ class FileDisplay(object):
self.outfile.write("{0}{frame}{msg}{0}{frame}".format(
os.linesep, frame=side_frame, msg=message))
yes_label = _parens_around_char(yes_label)
no_label = _parens_around_char(no_label)
ans = raw_input("{yes}/{no}: ".format(yes=yes_label, no=no_label))
ans = raw_input("{yes}/{no}: ".format(
yes=_parens_around_char(yes_label),
no=_parens_around_char(no_label)))
return (ans.startswith(yes_label[0].lower()) or
ans.startswith(yes_label[0].upper()))
@ -227,7 +238,7 @@ class FileDisplay(object):
"""Display a checklist.
:param str message: Message to display to user
:param list tags: `str` tags to select
:param list tags: `str` tags to select, len(tags) > 0
:returns: tuple of (`code`, `tags`) where
`code` - str display exit code
@ -255,7 +266,7 @@ class FileDisplay(object):
def _scrub_checklist_input(self, indices, tags):
"""Validate input and transform indices to appropriate tags.
:param list indices: Checklist input
:param list indices: input
:param list tags: Original tags of the checklist
:returns: tags the user selected
@ -265,7 +276,7 @@ class FileDisplay(object):
# They should all be of type int
try:
indices = [int(index) for index in indices]
except TypeError:
except ValueError:
return []
# Remove duplicates
@ -292,14 +303,16 @@ class FileDisplay(object):
if choices and isinstance(choices[0], tuple):
choices = ["%s - %s" % (c[0], c[1]) for c in choices]
# Write out the message to the user
self.outfile.write(
"{new}{msg}{new}".format(new=os.linesep, msg=message))
side_frame = ("-" * 79) + os.linesep
self.outfile.write(side_frame)
for i, tag in enumerate(choices, 1):
# Write out the menu choices
for i, desc in enumerate(choices, 1):
self.outfile.write(
textwrap.fill("{num}: {tag}".format(num=i, tag=tag), 80))
textwrap.fill("{num}: {desc}".format(num=i, desc=desc), 80))
# Keep this outside of the textwrap
self.outfile.write(os.linesep)
@ -311,7 +324,7 @@ class FileDisplay(object):
:param str msg: Original message
:returns: Formatted message
:returns: Formatted message respecting newlines in message
:rtype: str
"""
@ -325,7 +338,7 @@ class FileDisplay(object):
def _get_valid_int_ans(self, max):
"""Get a numerical selection.
:param int max: The maximum entry (len of choices)
:param int max: The maximum entry (len of choices), must be positive
:returns: tuple of the form (`code`, `selection`) where
`code` - str display exit code ('ok' or cancel')
@ -337,10 +350,10 @@ class FileDisplay(object):
if max > 1:
input_msg = ("Select the appropriate number "
"[1-{max}] then [enter] (press 'c' to "
"cancel){end}".format(max=max, end=os.linesep))
"cancel): ".format(max=max))
else:
input_msg = ("Press 1 [enter] to confirm the selection "
"(press 'c' to cancel){0}".format(os.linesep))
"(press 'c' to cancel): ")
while selection < 1:
ans = raw_input(input_msg)
if ans.startswith("c") or ans.startswith("C"):
@ -348,6 +361,7 @@ class FileDisplay(object):
try:
selection = int(ans)
if selection < 1 or selection > max:
selection = -1
raise ValueError
except ValueError:

View file

@ -1,67 +0,0 @@
"""Let's Encrypt Enhancement Display"""
import logging
import zope.component
from letsencrypt.client import errors
from letsencrypt.client import interfaces
class EnhanceDisplay(object):
"""Class used to display various enhancements.
.. note::This is not a subclass of Display. It merely uses Display as a
component.
:ivar displayer: Display singleton
:type displayer: :class:`letsencrypt.client.interfaces.IDisplay
:ivar dict dispatch: Dict mapping enhancements to functions
"""
def __init__(self):
self.displayer = zope.component.getUtility(interfaces.IDisplay)
self.dispatch = {
"redirect": self.redirect_by_default,
}
def ask(self, enhancement):
"""Display the enhancement to the user.
:param str enhancement: One of the
:class:`letsencrypt.client.CONFIG.ENHANCEMENTS` enhancements
:returns: True if feature is desired, False otherwise
:rtype: bool
:raises :class:`letsencrypt.client.errors.LetsEncryptClientError`: If
the enhancement provided is not supported.
"""
try:
return self.dispatch[enhancement]
except KeyError:
logging.error("Unsupported enhancement given to ask()")
raise errors.LetsEncryptClientError("Unsupported Enhancement")
def redirect_by_default(self):
"""Determines whether the user would like to redirect to HTTPS.
:returns: True if redirect is desired, False otherwise
:rtype: bool
"""
choices = [
("Easy", "Allow both HTTP and HTTPS access to these sites"),
("Secure", "Make all requests redirect to secure HTTPS access")]
result = self.displayer.menu(
"Please choose whether HTTPS access is required or optional.",
choices, "Please enter the appropriate number")
if result[0] != OK:
return False
# different answer for each type of display
return str(result[1]) == "Secure" or result[1] == 1

View file

@ -141,15 +141,22 @@ class IDisplay(zope.interface.Interface):
"""
def menu(message, choices, input_text="", ok_label="OK", help_label=""):
def menu(message, choices,
ok_label="OK", cancel_label="Cancel", help_label=""):
"""Displays a generic menu.
:param str message: message to display
:param choices: choices
:type choices: :class:`list` of :func:`tuple`
:type choices: :class:`list` of :func:`tuple` or :class:`str`
:param str input_text: instructions on how to make a selection
:param str ok_label: label for OK button
:param str cancel_label: label for Cancel button
:param str help_label: label for Help button
:returns: tuple of (`code`, `index`) where
`code` - str display exit code
`index` - int index of the user's selection
"""
@ -168,6 +175,8 @@ class IDisplay(zope.interface.Interface):
def yesno(message, yes_label="Yes", no_label="No"):
"""Query the user with a yes/no question.
Yes and No label must begin with different letters.
:param str message: question for the user
:returns: True for "Yes", False for "No"
@ -175,13 +184,13 @@ class IDisplay(zope.interface.Interface):
"""
def checkbox(message, choices):
def checklist(message, choices):
"""Allow for multiple selections from a menu.
:param str message: message to display to the user
:param choices: :param choices: choices
:type choices: :class:`list` of :func:`tuple`
:param tags: tags
:type tags: :class:`list` of :class:`str`
"""

View file

@ -1,4 +1,5 @@
import sys
import contextlib
import os
import unittest
import mock
@ -10,6 +11,7 @@ class DisplayT(unittest.TestCase):
def setUp(self):
self.choices = [("First", "Description1"), ("Second", "Description2")]
self.tags = ["tag1", "tag2", "tag3"]
self.tags_choices = [("1", "tag1"), ("2", "tag2"), ("3", "tag3")]
def test_visual(displayer, choices):
@ -21,13 +23,21 @@ def test_visual(displayer, choices):
ok_label="O", cancel_label="Can", help_label="??")
displayer.input("Input Message")
displayer.yesno(
"Yes/No Message", yes_label="Yessir", no_label="Nosir")
"YesNo Message", yes_label="Yessir", no_label="Nosir")
displayer.checklist(
"Checklist Message", [choice[0] for choice in choices])
class NcursesDisplayTest(DisplayT):
"""Test ncurses display."""
"""Test ncurses display.
Since this is mostly a wrapper, it might be more helpful to test the actual
dialog boxes. The test_visual function will actually display the various
boxes but requires the user to do the verification. If something seems amiss
please use the test_visual function to debug it, the automatic tests rely
on too much mocking.
"""
def setUp(self):
super(NcursesDisplayTest, self).setUp()
self.displayer = display_util.NcursesDisplay()
@ -39,21 +49,233 @@ class NcursesDisplayTest(DisplayT):
self.assertEqual(mock_msgbox.call_count, 1)
@mock.patch("letsencrypt.client.display.display_util.dialog.Dialog.menu")
def test_menu(self, mock_menu):
pass
def test_menu_tag_and_desc(self, mock_menu):
mock_menu.return_value = (display_util.OK, "First")
def test_visual(self):
test_visual(self.displayer, self.choices)
ret = self.displayer.menu("Message", self.choices)
mock_menu.assert_called_with(
"Message", choices=self.choices, ok_label="OK",
cancel_label="Cancel",
help_button=False, help_label="",
width=display_util.WIDTH, height=display_util.HEIGHT)
self.assertEqual(ret, (display_util.OK, 0))
@mock.patch("letsencrypt.client.display.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", self.choices)
mock_menu.assert_called_with(
"Message", choices=self.choices, ok_label="OK",
cancel_label="Cancel",
help_button=False, help_label="",
width=display_util.WIDTH, height=display_util.HEIGHT)
self.assertEqual(ret, (display_util.CANCEL, -1))
@mock.patch("letsencrypt.client.display.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", self.tags, help_label="More Info")
mock_menu.assert_called_with(
"Message", choices=self.tags_choices, ok_label="OK",
cancel_label="Cancel",
help_button=True, help_label="More Info",
width=display_util.WIDTH, height=display_util.HEIGHT)
self.assertEqual(ret, (display_util.OK, 0))
@mock.patch("letsencrypt.client.display.display_util."
"dialog.Dialog.inputbox")
def test_input(self, mock_input):
self.displayer.input("message")
mock_input.assert_called_with("message")
@mock.patch("letsencrypt.client.display.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", display_util.HEIGHT, display_util.WIDTH,
yes_label="Yes", no_label="No")
@mock.patch("letsencrypt.client.display.display_util."
"dialog.Dialog.checklist")
def test_checklist(self, mock_checklist):
self.displayer.checklist("message", self.tags)
choices = [
(self.tags[0], "", False),
(self.tags[1], "", False),
(self.tags[2], "", False)
]
mock_checklist.assert_called_with(
"message", width=display_util.WIDTH, height=display_util.HEIGHT,
choices=choices)
# def test_visual(self):
# test_visual(self.displayer, self.choices)
class FileOutputDisplayTest(DisplayT):
"""Test stdout display."""
"""Test stdout display.
Most of this class has to deal with visual output. In order to test how the
functions look to a user, uncomment the test_visual function.
"""
def setUp(self):
super(FileOutputDisplayTest, self).setUp()
self.displayer = display_util.FileDisplay(sys.stdout)
self.mock_stdout = mock.MagicMock()
self.displayer = display_util.FileDisplay(self.mock_stdout)
def test_visual(self):
test_visual(self.displayer, self.choices)
def test_notification_no_pause(self):
self.displayer.notification("message", 10, False)
string = self.mock_stdout.write.call_args[0][0]
self.assertTrue("message" in string)
def test_notification_pause(self):
# Attempt to mock raw_input
with mock_raw_input(["enter"]):
self.displayer.notification("message")
self.assertTrue("message" in self.mock_stdout.write.call_args[0][0])
@mock.patch("letsencrypt.client.display.display_util."
"FileDisplay._get_valid_int_ans")
def test_menu(self, mock_ans):
mock_ans.return_value = (display_util.OK, 1)
ret = self.displayer.menu("message", self.choices)
self.assertEqual(ret, (display_util.OK, 0))
def test_input_cancel(self):
# Attempt to mock raw_input
with mock_raw_input(["c"]):
code, _ = self.displayer.input("message")
self.assertTrue(code, display_util.CANCEL)
def test_input_normal(self):
with mock_raw_input(["domain.com"]):
code, input_ = self.displayer.input("message")
self.assertEqual(code, display_util.OK)
self.assertEqual(input_, "domain.com")
def test_yesno(self):
with mock_raw_input(["Yes"]):
self.assertTrue(self.displayer.yesno("message"))
with mock_raw_input(["y"]):
self.assertTrue(self.displayer.yesno("message"))
with mock_raw_input(["cancel"]):
self.assertFalse(self.displayer.yesno("message"))
with mock_raw_input(["a"]):
self.assertTrue(self.displayer.yesno("msg", yes_label="Agree"))
@mock.patch("letsencrypt.client.display.display_util.FileDisplay.input")
def test_checklist_valid(self, mock_input):
mock_input.return_value = (display_util.OK, "2 1")
code, tag_list = self.displayer.checklist("msg", self.tags)
self.assertEqual(
(code, set(tag_list)), (display_util.OK, set(["tag1", "tag2"])))
@mock.patch("letsencrypt.client.display.display_util.FileDisplay.input")
def test_checklist_miss_valid(self, mock_input):
mock_input.side_effect = [
(display_util.OK, "10"),
(display_util.OK, "tag1 please"),
(display_util.OK, "1")
]
ret = self.displayer.checklist("msg", self.tags)
self.assertEqual(ret, (display_util.OK, ["tag1"]))
@mock.patch("letsencrypt.client.display.display_util.FileDisplay.input")
def test_checklist_miss_quit(self, mock_input):
mock_input.side_effect = [
(display_util.OK, "10"),
(display_util.CANCEL, "1")
]
ret = self.displayer.checklist("msg", self.tags)
self.assertEqual(ret, (display_util.CANCEL, []))
def test_scrub_checklist_input_valid(self):
indices = [
["1"],
["1", "2", "1"],
["2", "3"],
]
exp = [
set(["tag1"]),
set(["tag1", "tag2"]),
set(["tag2", "tag3"]),
]
for i, list_ in enumerate(indices):
set_tags = set(
self.displayer._scrub_checklist_input(list_, self.tags))
self.assertEqual(set_tags, exp[i])
def test_scrub_checklist_input_invalid(self):
indices = [
["0"],
["4"],
["tag1"],
["1", "tag1"],
["2", "o"]
]
for list_ in indices:
self.assertEqual(
self.displayer._scrub_checklist_input(list_, self.tags), [])
def test_print_menu(self):
# This is purely cosmetic... just make sure there aren't any exceptions
self.displayer._print_menu("msg", self.choices)
self.displayer._print_menu("msg", self.tags)
def test_wrap_lines(self):
msg = ("This is just a weak test\n"
"This function is only meant to be for easy viewing\n"
"Test a really really really really really really really really "
"really really really really really long line...")
text = self.displayer._wrap_lines(msg)
self.assertEqual(text.count(os.linesep), 3)
def test_get_valid_int_ans_valid(self):
with mock_raw_input(["1"]):
self.assertEqual(
self.displayer._get_valid_int_ans(1), (display_util.OK, 1))
ans = "2"
with mock_raw_input([ans]):
self.assertEqual(
self.displayer._get_valid_int_ans(3),
(display_util.OK, int(ans)))
def test_get_valid_int_ans_invalid(self):
answers = [
["0", "c"],
["4", "one", "C"],
["c"],
]
for ans in answers:
with mock_raw_input(ans):
self.assertEqual(
self.displayer._get_valid_int_ans(3),
(display_util.CANCEL, -1))
# def test_visual(self):
# self.displayer = display_util.FileDisplay(sys.stdout)
# test_visual(self.displayer, self.choices)
class SeparateListInputTest(unittest.TestCase):
@ -101,5 +323,15 @@ class PlaceParensTest(unittest.TestCase):
self.assertEqual("(L)abel", ret)
# https://stackoverflow.com/a/25275926
@contextlib.contextmanager
def mock_raw_input(values):
func = mock.MagicMock(side_effect=values)
original_raw_input = __builtins__.raw_input
__builtins__.raw_input = func
yield
__builtins__.raw_input = original_raw_input
if __name__ == "__main__":
unittest.main()