Repository: certbot/certbot

Issue:
Interactivity glitch in git master
```
root@isnot:/etc/apache2/sites-enabled# lel --config-dir /tmp/zzz --test-cert --agree-t --regi
Saving debug log to /var/log/letsencrypt/letsencrypt.log

Which names would you like to activate HTTPS for?
-------------------------------------------------------------------------------
1: goxogle.com
2: isnot.org
3: is.isnot.org
4: tweedle.isnot.org
5: www.isnot.org
6: demo.reworld.org
-------------------------------------------------------------------------------
An unexpected error occurred:
AssertionError: Invalid IDisplay call for this prompt:
Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown
Please see the logfiles in /var/log/letsencrypt for more details.
```


Discussion:
Full log is:
```
2016-12-20 19:29:07,802:DEBUG:certbot.main:Root logging level set at 20
2016-12-20 19:29:07,803:INFO:certbot.main:Saving debug log to /var/log/letsencrypt/letsencrypt.log
2016-12-20 19:29:07,803:DEBUG:certbot.main:certbot version: 0.10.0.dev0
2016-12-20 19:29:07,803:DEBUG:certbot.main:Arguments: ['--config-dir', '/tmp/zzz', '--test-cert', '--agree-t', '--regi']
2016-12-20 19:29:07,803:DEBUG:certbot.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#script,PluginEntryPoint#standalone,PluginEntryPoint#manual,PluginEntryPoint#webroot,PluginEntryPoint#nginx,PluginEntryPoint#apache,PluginEntryPoint#null)
2016-12-20 19:29:07,803:DEBUG:certbot.plugins.selection:Requested authenticator None and installer None
2016-12-20 19:29:08,407:DEBUG:certbot.plugins.selection:Single candidate plugin: * apache
Description: Apache Web Server plugin - Beta
Interfaces: IAuthenticator, IInstaller, IPlugin
Entry point: apache = certbot_apache.configurator:ApacheConfigurator
Initialized: <certbot_apache.configurator.ApacheConfigurator object at 0x7fdf74e84310>
Prep: True
2016-12-20 19:29:08,408:DEBUG:certbot.plugins.selection:Selected authenticator <certbot_apache.configurator.ApacheConfigurator object at 0x7fdf74e84310> and installer <certbot_apache.configurator.ApacheConfigurator object at 0x7fdf74e84310>
2016-12-20 19:29:08,410:DEBUG:certbot.main:Exiting abnormally:
Traceback (most recent call last):
  File "venv/bin/certbot", line 11, in <module>
    load_entry_point('certbot', 'console_scripts', 'certbot')()
  File "/home/ubuntu/letsencrypt/certbot/main.py", line 831, in main
    return config.func(config, plugins)
  File "/home/ubuntu/letsencrypt/certbot/main.py", line 561, in run
    domains, certname = _find_domains_or_certname(config, installer)
  File "/home/ubuntu/letsencrypt/certbot/main.py", line 290, in _find_domains_or_certname
    domains = display_ops.choose_names(installer)
  File "/home/ubuntu/letsencrypt/certbot/display/ops.py", line 111, in choose_names
    code, names = _filter_names(names)
  File "/home/ubuntu/letsencrypt/certbot/display/ops.py", line 161, in _filter_names
    tags=sorted_names, cli_flag="--domains", force_interactive=True)
  File "/home/ubuntu/letsencrypt/certbot/display/util.py", line 224, in checklist
    code, ans = self.input("Select the appropriate numbers separated "
  File "/home/ubuntu/letsencrypt/certbot/display/util.py", line 141, in input
    if self._return_default(message, default, cli_flag, force_interactive):
  File "/home/ubuntu/letsencrypt/certbot/display/util.py", line 259, in _return_default
    assert default is not None or force_interactive, msg
AssertionError: Invalid IDisplay call for this prompt:
Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown
```

Resolving patch:
diff --git a/certbot/display/util.py b/certbot/display/util.py
--- a/certbot/display/util.py
+++ b/certbot/display/util.py
@@ -223,7 +223,8 @@ def checklist(self, message, tags, default_status=True, default=None,
 
             code, ans = self.input("Select the appropriate numbers separated "
                                    "by commas and/or spaces, or leave input "
-                                   "blank to select all options shown")
+                                   "blank to select all options shown",
+                                   force_interactive=True)
 
             if code == OK:
                 if len(ans.strip()) == 0:


Test patch:
diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py
--- a/certbot/tests/display/util_test.py
+++ b/certbot/tests/display/util_test.py
@@ -43,6 +43,19 @@ def test_notification_noninteractive(self):
         string = self.mock_stdout.write.call_args[0][0]
         self.assertTrue("message" in string)
 
+    def test_notification_noninteractive2(self):
+        # The main purpose of this test is to make sure we only call
+        # logger.warning once which _force_noninteractive checks internally
+        self._force_noninteractive(self.displayer.notification, "message")
+        string = self.mock_stdout.write.call_args[0][0]
+        self.assertTrue("message" in string)
+
+        self.assertTrue(self.displayer.skipped_interaction)
+
+        self._force_noninteractive(self.displayer.notification, "message2")
+        string = self.mock_stdout.write.call_args[0][0]
+        self.assertTrue("message2" in string)
+
     @mock.patch("certbot.display.util."
                 "FileDisplay._get_valid_int_ans")
     def test_menu(self, mock_ans):
@@ -50,6 +63,12 @@ def test_menu(self, mock_ans):
         ret = self.displayer.menu("message", CHOICES, force_interactive=True)
         self.assertEqual(ret, (display_util.OK, 0))
 
+    def test_menu_noninteractive(self):
+        default = 0
+        result = self._force_noninteractive(
+            self.displayer.menu, "msg", CHOICES, default=default)
+        self.assertEqual(result, (display_util.OK, default))
+
     def test_input_cancel(self):
         with mock.patch("six.moves.input", return_value="c"):
             code, _ = self.displayer.input("message", force_interactive=True)
@@ -100,38 +119,32 @@ def test_yesno_noninteractive(self):
         self.assertTrue(self._force_noninteractive(
             self.displayer.yesno, "message", default=True))
 
-    @mock.patch("certbot.display.util.FileDisplay.input")
+    @mock.patch("certbot.display.util.six.moves.input")
     def test_checklist_valid(self, mock_input):
-        mock_input.return_value = (display_util.OK, "2 1")
+        mock_input.return_value = "2 1"
         code, tag_list = self.displayer.checklist(
             "msg", TAGS, force_interactive=True)
         self.assertEqual(
             (code, set(tag_list)), (display_util.OK, set(["tag1", "tag2"])))
 
-    @mock.patch("certbot.display.util.FileDisplay.input")
+    @mock.patch("certbot.display.util.six.moves.input")
     def test_checklist_empty(self, mock_input):
-        mock_input.return_value = (display_util.OK, "")
+        mock_input.return_value = ""
         code, tag_list = self.displayer.checklist("msg", TAGS, force_interactive=True)
         self.assertEqual(
             (code, set(tag_list)), (display_util.OK, set(["tag1", "tag2", "tag3"])))
 
-    @mock.patch("certbot.display.util.FileDisplay.input")
+    @mock.patch("certbot.display.util.six.moves.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")
-        ]
+        mock_input.side_effect = ["10", "tag1 please", "1"]
 
         ret = self.displayer.checklist("msg", TAGS, force_interactive=True)
         self.assertEqual(ret, (display_util.OK, ["tag1"]))
 
-    @mock.patch("certbot.display.util.FileDisplay.input")
+    @mock.patch("certbot.display.util.six.moves.input")
     def test_checklist_miss_quit(self, mock_input):
-        mock_input.side_effect = [
-            (display_util.OK, "10"),
-            (display_util.CANCEL, "1")
-        ]
+        mock_input.side_effect = ["10", "c"]
+
         ret = self.displayer.checklist("msg", TAGS, force_interactive=True)
         self.assertEqual(ret, (display_util.CANCEL, []))
 
@@ -160,15 +173,15 @@ def test_scrub_checklist_input_valid(self):
                 self.displayer._scrub_checklist_input(list_, TAGS))
             self.assertEqual(set_tags, exp[i])
 
-    @mock.patch("certbot.display.util.FileDisplay.input")
+    @mock.patch("certbot.display.util.six.moves.input")
     def test_directory_select(self, mock_input):
         # pylint: disable=star-args
         args = ["msg", "/var/www/html", "--flag", True]
-        result = (display_util.OK, "/var/www/html")
-        mock_input.return_value = result
+        user_input = "/var/www/html"
+        mock_input.return_value = user_input
 
-        self.assertEqual(self.displayer.directory_select(*args), result)
-        mock_input.assert_called_once_with(*args)
+        returned = self.displayer.directory_select(*args)
+        self.assertEqual(returned, (display_util.OK, user_input))
 
     def test_directory_select_noninteractive(self):
         default = "/var/www/html"