Repository: cdent/gabbi

Issue:
In 'live' testing scenarios argument passing to build_tests is convoluted and SSL may not work
If you want to use `build_tests` to create real TestCases against a live server it's likely you know the URL and that would be most convenient thing to pass instead of having to parse out the host, port and prefix (script_name) and then pass those.

In addition, if you have a URL you know if your server is SSL but the tests may not have been written to do SSL (with an `ssl: true` entry). Because of the test building process this is a bit awkward at the moment. It would be better to be able to say "yeah, this is SSL"  for the whole run.

Discussion:
cdent: @jd and @EmilienM, this good for you guys?
EmilienM: :+1: 

Patch:
diff --git a/gabbi/driver.py b/gabbi/driver.py
index 33c0a98..49088fa 100644
--- a/gabbi/driver.py
+++ b/gabbi/driver.py
@@ -39,7 +39,8 @@ from gabbi import utils
 
 def build_tests(path, loader, host=None, port=8001, intercept=None,
                 test_loader_name=None, fixture_module=None,
-                response_handlers=None, prefix='', require_ssl=False):
+                response_handlers=None, prefix='', require_ssl=False,
+                url=None):
     """Read YAML files from a directory to create tests.
 
     Each YAML file represents an ordered sequence of HTTP requests.
@@ -54,6 +55,7 @@ def build_tests(path, loader, host=None, port=8001, intercept=None,
     :param response_handers: ResponseHandler classes.
     :type response_handlers: List of ResponseHandler classes.
     :param prefix: A URL prefix for all URLs that are not fully qualified.
+    :param url: A full URL to test against. Replaces host, port and prefix.
     :param require_ssl: If ``True``, make all tests default to using SSL.
     :rtype: TestSuite containing multiple TestSuites (one for each YAML file).
     """
@@ -63,6 +65,12 @@ def build_tests(path, loader, host=None, port=8001, intercept=None,
     if not bool(host) ^ bool(intercept):
         raise AssertionError('must specify exactly one of host or intercept')
 
+    # If url is being used, reset host, port and prefix.
+    if url:
+        host, port, prefix, force_ssl = utils.host_info_from_target(url)
+        if force_ssl and not require_ssl:
+            require_ssl = force_ssl
+
     if test_loader_name is None:
         test_loader_name = inspect.stack()[1]
         test_loader_name = os.path.splitext(os.path.basename(
@@ -97,7 +105,7 @@ def build_tests(path, loader, host=None, port=8001, intercept=None,
 def py_test_generator(test_dir, host=None, port=8001, intercept=None,
                       prefix=None, test_loader_name=None,
                       fixture_module=None, response_handlers=None,
-                      require_ssl=False):
+                      require_ssl=False, url=None):
     """Generate tests cases for py.test
 
     This uses build_tests to create TestCases and then yields them in
@@ -110,7 +118,8 @@ def py_test_generator(test_dir, host=None, port=8001, intercept=None,
                         test_loader_name=test_loader_name,
                         fixture_module=fixture_module,
                         response_handlers=response_handlers,
-                        prefix=prefix, require_ssl=require_ssl)
+                        prefix=prefix, require_ssl=require_ssl,
+                        url=url)
 
     for test in tests:
         if hasattr(test, '_tests'):
diff --git a/gabbi/runner.py b/gabbi/runner.py
index 3411dbe..d4e79d5 100644
--- a/gabbi/runner.py
+++ b/gabbi/runner.py
@@ -17,8 +17,6 @@ from importlib import import_module
 import sys
 import unittest
 
-from six.moves.urllib import parse as urlparse
-
 from gabbi import case
 from gabbi import handlers
 from gabbi.reporter import ConciseTestRunner
@@ -93,7 +91,7 @@ def run():
     )
 
     args = parser.parse_args()
-    host, port, prefix, force_ssl = process_target_args(
+    host, port, prefix, force_ssl = utils.host_info_from_target(
         args.target, args.prefix)
 
     # Initialize response handlers.
@@ -113,31 +111,6 @@ def run():
     sys.exit(not result.wasSuccessful())
 
 
-def process_target_args(target, prefix):
-    """Turn the argparse args into a host, port and prefix."""
-    force_ssl = False
-    split_url = urlparse.urlparse(target)
-
-    if split_url.scheme:
-        if split_url.scheme == 'https':
-            force_ssl = True
-        return split_url.hostname, split_url.port, split_url.path, force_ssl
-    else:
-        target = target
-        prefix = prefix
-
-    if ':' in target and '[' not in target:
-        host, port = target.rsplit(':', 1)
-    elif ']:' in target:
-        host, port = target.rsplit(':', 1)
-    else:
-        host = target
-        port = None
-    host = host.replace('[', '').replace(']', '')
-
-    return host, port, prefix, force_ssl
-
-
 def initialize_handlers(response_handlers):
     custom_response_handlers = []
     for import_path in response_handlers or []:
diff --git a/gabbi/utils.py b/gabbi/utils.py
index 3de040d..172b4bf 100644
--- a/gabbi/utils.py
+++ b/gabbi/utils.py
@@ -126,6 +126,31 @@ def not_binary(content_type):
             content_type.startswith('application/json'))
 
 
+def host_info_from_target(target, prefix=None):
+    """Turn url or host:port and target into test destination."""
+    force_ssl = False
+    split_url = urlparse.urlparse(target)
+
+    if split_url.scheme:
+        if split_url.scheme == 'https':
+            force_ssl = True
+        return split_url.hostname, split_url.port, split_url.path, force_ssl
+    else:
+        target = target
+        prefix = prefix
+
+    if ':' in target and '[' not in target:
+        host, port = target.rsplit(':', 1)
+    elif ']:' in target:
+        host, port = target.rsplit(':', 1)
+    else:
+        host = target
+        port = None
+    host = host.replace('[', '').replace(']', '')
+
+    return host, port, prefix, force_ssl
+
+
 def _colorize(color, message):
     """Add a color to the message."""
     try:


Test patch:
diff --git a/gabbi/tests/test_driver.py b/gabbi/tests/test_driver.py
index 0b2ce0a..8f6bca0 100644
--- a/gabbi/tests/test_driver.py
+++ b/gabbi/tests/test_driver.py
@@ -70,3 +70,20 @@ class DriverTest(unittest.TestCase):
         first_test = suite._tests[0]._tests[0]
         full_url = first_test._parse_url(first_test.test_data['url'])
         self.assertEqual('http://localhost:8001/', full_url)
+
+    def test_build_url_target(self):
+        suite = driver.build_tests(self.test_dir, self.loader,
+                                   host='localhost', port='999',
+                                   url='https://example.com:1024/theend')
+        first_test = suite._tests[0]._tests[0]
+        full_url = first_test._parse_url(first_test.test_data['url'])
+        self.assertEqual('https://example.com:1024/theend/', full_url)
+
+    def test_build_url_target_forced_ssl(self):
+        suite = driver.build_tests(self.test_dir, self.loader,
+                                   host='localhost', port='999',
+                                   url='http://example.com:1024/theend',
+                                   require_ssl=True)
+        first_test = suite._tests[0]._tests[0]
+        full_url = first_test._parse_url(first_test.test_data['url'])
+        self.assertEqual('https://example.com:1024/theend/', full_url)
diff --git a/gabbi/tests/test_runner.py b/gabbi/tests/test_runner.py
index 3c132b1..a854cf9 100644
--- a/gabbi/tests/test_runner.py
+++ b/gabbi/tests/test_runner.py
@@ -229,93 +229,6 @@ class RunnerTest(unittest.TestCase):
         self._stderr.write(sys.stderr.read())
 
 
-class RunnerHostArgParse(unittest.TestCase):
-
-    def _test_hostport(self, url_or_host, expected_host,
-                       provided_prefix=None, expected_port=None,
-                       expected_prefix=None, expected_ssl=False):
-        host, port, prefix, ssl = runner.process_target_args(
-            url_or_host, provided_prefix)
-
-        # normalize hosts, they are case insensitive
-        self.assertEqual(expected_host.lower(), host.lower())
-        # port can be a string or int depending on the inputs
-        self.assertEqual(expected_port, port)
-        self.assertEqual(expected_prefix, prefix)
-        self.assertEqual(expected_ssl, ssl)
-
-    def test_plain_url_no_port(self):
-        self._test_hostport('http://foobar.com/news',
-                            'foobar.com',
-                            expected_port=None,
-                            expected_prefix='/news')
-
-    def test_plain_url_with_port(self):
-        self._test_hostport('http://foobar.com:80/news',
-                            'foobar.com',
-                            expected_port=80,
-                            expected_prefix='/news')
-
-    def test_ssl_url(self):
-        self._test_hostport('https://foobar.com/news',
-                            'foobar.com',
-                            expected_prefix='/news',
-                            expected_ssl=True)
-
-    def test_ssl_port80_url(self):
-        self._test_hostport('https://foobar.com:80/news',
-                            'foobar.com',
-                            expected_prefix='/news',
-                            expected_port=80,
-                            expected_ssl=True)
-
-    def test_ssl_port_url(self):
-        self._test_hostport('https://foobar.com:999/news',
-                            'foobar.com',
-                            expected_prefix='/news',
-                            expected_port=999,
-                            expected_ssl=True)
-
-    def test_simple_hostport(self):
-        self._test_hostport('foobar.com:999',
-                            'foobar.com',
-                            expected_port='999')
-
-    def test_simple_hostport_with_prefix(self):
-        self._test_hostport('foobar.com:999',
-                            'foobar.com',
-                            provided_prefix='/news',
-                            expected_port='999',
-                            expected_prefix='/news')
-
-    def test_ipv6_url_long(self):
-        self._test_hostport(
-            'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:999/news',
-            'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210',
-            expected_port=999,
-            expected_prefix='/news')
-
-    def test_ipv6_url_localhost(self):
-        self._test_hostport(
-            'http://[::1]:999/news',
-            '::1',
-            expected_port=999,
-            expected_prefix='/news')
-
-    def test_ipv6_host_localhost(self):
-        # If a user wants to use the hostport form, then they need
-        # to hack it with the brackets.
-        self._test_hostport(
-            '[::1]',
-            '::1')
-
-    def test_ipv6_hostport_localhost(self):
-        self._test_hostport(
-            '[::1]:999',
-            '::1',
-            expected_port='999')
-
-
 class HTMLResponseHandler(handlers.ResponseHandler):
 
     test_key_suffix = 'html'
diff --git a/gabbi/tests/test_utils.py b/gabbi/tests/test_utils.py
index 1754dad..d5b8b50 100644
--- a/gabbi/tests/test_utils.py
+++ b/gabbi/tests/test_utils.py
@@ -158,3 +158,90 @@ class CreateURLTest(unittest.TestCase):
             '/foo', 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', port=999)
         self.assertEqual(
             'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:999/foo', url)
+
+
+class UtilsHostInfoFromTarget(unittest.TestCase):
+
+    def _test_hostport(self, url_or_host, expected_host,
+                       provided_prefix=None, expected_port=None,
+                       expected_prefix=None, expected_ssl=False):
+        host, port, prefix, ssl = utils.host_info_from_target(
+            url_or_host, provided_prefix)
+
+        # normalize hosts, they are case insensitive
+        self.assertEqual(expected_host.lower(), host.lower())
+        # port can be a string or int depending on the inputs
+        self.assertEqual(expected_port, port)
+        self.assertEqual(expected_prefix, prefix)
+        self.assertEqual(expected_ssl, ssl)
+
+    def test_plain_url_no_port(self):
+        self._test_hostport('http://foobar.com/news',
+                            'foobar.com',
+                            expected_port=None,
+                            expected_prefix='/news')
+
+    def test_plain_url_with_port(self):
+        self._test_hostport('http://foobar.com:80/news',
+                            'foobar.com',
+                            expected_port=80,
+                            expected_prefix='/news')
+
+    def test_ssl_url(self):
+        self._test_hostport('https://foobar.com/news',
+                            'foobar.com',
+                            expected_prefix='/news',
+                            expected_ssl=True)
+
+    def test_ssl_port80_url(self):
+        self._test_hostport('https://foobar.com:80/news',
+                            'foobar.com',
+                            expected_prefix='/news',
+                            expected_port=80,
+                            expected_ssl=True)
+
+    def test_ssl_port_url(self):
+        self._test_hostport('https://foobar.com:999/news',
+                            'foobar.com',
+                            expected_prefix='/news',
+                            expected_port=999,
+                            expected_ssl=True)
+
+    def test_simple_hostport(self):
+        self._test_hostport('foobar.com:999',
+                            'foobar.com',
+                            expected_port='999')
+
+    def test_simple_hostport_with_prefix(self):
+        self._test_hostport('foobar.com:999',
+                            'foobar.com',
+                            provided_prefix='/news',
+                            expected_port='999',
+                            expected_prefix='/news')
+
+    def test_ipv6_url_long(self):
+        self._test_hostport(
+            'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:999/news',
+            'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210',
+            expected_port=999,
+            expected_prefix='/news')
+
+    def test_ipv6_url_localhost(self):
+        self._test_hostport(
+            'http://[::1]:999/news',
+            '::1',
+            expected_port=999,
+            expected_prefix='/news')
+
+    def test_ipv6_host_localhost(self):
+        # If a user wants to use the hostport form, then they need
+        # to hack it with the brackets.
+        self._test_hostport(
+            '[::1]',
+            '::1')
+
+    def test_ipv6_hostport_localhost(self):
+        self._test_hostport(
+            '[::1]:999',
+            '::1',
+            expected_port='999')