Repository: JonathonReinhart/scuba
Pull request #56 (2016-05-13 01:45:05)

Improve test coverage


Patch:
diff --git a/scuba/__main__.py b/scuba/__main__.py
index 3ea935a..94a9409 100644
--- a/scuba/__main__.py
+++ b/scuba/__main__.py
@@ -12,13 +12,13 @@ import itertools
 import argparse
 from tempfile import NamedTemporaryFile
 import atexit
-import json
 
 from .constants import *
 from .config import find_config, load_config, ConfigError
 from .filecleanup import FileCleanup
 from .utils import *
 from .version import __version__
+from .dockerutil import *
 
 def appmsg(fmt, *args):
     print('scuba: ' + fmt.format(*args), file=sys.stderr)
@@ -27,39 +27,6 @@ def verbose_msg(fmt, *args):
     if g_verbose:
         appmsg(fmt, *args)
 
-
-
-def make_vol_opt(hostdir, contdir, options=None):
-    '''Generate a docker volume option'''
-    vol = '--volume={0}:{1}'.format(hostdir, contdir)
-    if options != None:
-        if isinstance(options, str):
-            options = (options,)
-        vol += ':' + ','.join(options)
-    return vol
-
-def shell_quote(s):
-    # http://stackoverflow.com/a/847800/119527
-    return pipes.quote(s)
-
-def get_image_command(image):
-    '''Gets the default command for an image'''
-    args = ['docker', 'inspect', image]
-    try:
-        p = subprocess.Popen(args, stdout = subprocess.PIPE)
-    except OSError as e:
-        if e.errno == errno.ENOENT:
-            appmsg('Failed to execute docker. Is it installed?')
-            sys.exit(2)
-
-    stdout, _ = p.communicate()
-    if not p.returncode == 0:
-        appmsg('Failed to inspect image')
-        sys.exit(2)
-
-    info = json.loads(stdout)[0]
-    return info['Config']['Cmd']
-
 def get_umask():
     # Same logic as bash/builtins/umask.def
     val = os.umask(0o22)
@@ -97,7 +64,11 @@ def get_native_opts(config, scuba_args, usercmd):
     if len(usercmd) == 0:
         # No user-provided command; we want to run the image's default command
         verbose_msg('No user command; getting command from image')
-        usercmd = get_image_command(config.image)
+        try:
+            usercmd = get_image_command(config.image)
+        except DockerError as e:
+            appmsg(str(e))
+            sys.exit(128)
         verbose_msg('{0} Cmd: "{1}"'.format(config.image, usercmd))
 
     return opts, usercmd
diff --git a/scuba/dockerutil.py b/scuba/dockerutil.py
new file mode 100644
index 0000000..92c4aa4
--- /dev/null
+++ b/scuba/dockerutil.py
@@ -0,0 +1,34 @@
+import subprocess
+import errno
+import json
+
+class DockerError(Exception):
+    pass
+
+def get_image_command(image):
+    '''Gets the default command for an image'''
+    args = ['docker', 'inspect', image]
+    try:
+        p = subprocess.Popen(args, stdout = subprocess.PIPE)
+    except OSError as e:
+        if e.errno == errno.ENOENT:
+            raise DockerError('Failed to execute docker. Is it installed?')
+        raise
+
+    stdout, _ = p.communicate()
+    if not p.returncode == 0:
+        raise DockerError('Failed to inspect image')
+
+    info = json.loads(stdout.decode('utf-8'))[0]
+    return info['Config']['Cmd']
+
+
+def make_vol_opt(hostdir, contdir, options=None):
+    '''Generate a docker volume option'''
+    vol = '--volume={0}:{1}'.format(hostdir, contdir)
+    if options != None:
+        if isinstance(options, str):
+            options = (options,)
+        vol += ':' + ','.join(options)
+    return vol
+


Test patch:
diff --git a/tests/test_dockerutil.py b/tests/test_dockerutil.py
new file mode 100644
index 0000000..1f5b9b6
--- /dev/null
+++ b/tests/test_dockerutil.py
@@ -0,0 +1,55 @@
+ # coding=utf-8
+from __future__ import print_function
+
+from nose.tools import *
+from .utils import *
+from unittest import TestCase
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+import subprocess
+
+import scuba.dockerutil as uut
+
+class TestDockerutil(TestCase):
+    def test_get_image_command_success(self):
+        '''get_image_command works'''
+        assert_true(uut.get_image_command('debian:8.2'))
+
+    def test_get_image_command_bad_image(self):
+        '''get_image_command raises an exception for a bad image name'''
+        assert_raises(uut.DockerError, uut.get_image_command, 'nosuchimageZZZZZZZZ')
+
+    def test_get_image_no_docker(self):
+        '''get_image_command raises an exception if docker is not installed'''
+
+        real_Popen = subprocess.Popen
+        def mocked_popen(popen_args, *args, **kw):
+            assert_equal(popen_args[0], 'docker')
+            popen_args[0] = 'dockerZZZZ'
+            return real_Popen(popen_args, *args, **kw)
+
+        with mock.patch('subprocess.Popen', side_effect=mocked_popen) as popen_mock:
+            assert_raises(uut.DockerError, uut.get_image_command, 'n/a')
+
+
+
+    def test_make_vol_opt_no_opts(self):
+        assert_equal(
+                uut.make_vol_opt('/hostdir', '/contdir'),
+                '--volume=/hostdir:/contdir'
+                )
+
+    def test_make_vol_opt_one_opt(self):
+        assert_equal(
+                uut.make_vol_opt('/hostdir', '/contdir', 'ro'),
+                '--volume=/hostdir:/contdir:ro'
+                )
+
+    def test_make_vol_opt_multi_opts(self):
+        assert_equal(
+                uut.make_vol_opt('/hostdir', '/contdir', ['ro', 'z']),
+                '--volume=/hostdir:/contdir:ro,z'
+                )
diff --git a/tests/test_main.py b/tests/test_main.py
index 053358a..fe82250 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -13,9 +13,11 @@ import os
 import sys
 from tempfile import mkdtemp, TemporaryFile, NamedTemporaryFile
 from shutil import rmtree
+import subprocess
 
 import scuba.__main__ as main
 import scuba.constants
+import scuba.dockerutil
 import scuba
 
 DOCKER_IMAGE = 'debian:8.2'
@@ -110,6 +112,44 @@ class TestMain(TestCase):
 
         assert_str_equalish('my output', out)
 
+    def test_no_cmd(self):
+        '''Verify scuba works with no given command'''
+
+        with open('.scuba.yml', 'w') as f:
+            f.write('image: {0}\n'.format(DOCKER_IMAGE))
+
+        with TemporaryFile(prefix='scubatest-stdin', mode='w+t') as stdin:
+            stdin.write('echo okay')
+            stdin.seek(0)
+
+            # This mock exists to pass an extra stdin= arg
+            real_subprocess_call = subprocess.call
+            def mocked_subprocess_call(*args, **kw):
+                assert_false('stdin' in kw)
+                kw['stdin'] = stdin
+                return real_subprocess_call(*args, **kw)
+
+            with mock.patch('subprocess.call', side_effect=mocked_subprocess_call):
+                args = []
+                out, _ = self.run_scuba(args)
+
+        assert_str_equalish('okay', out)
+
+    def test_handle_get_image_command_error(self):
+        '''Verify scuba handles a get_image_command error'''
+
+        with open('.scuba.yml', 'w') as f:
+            f.write('image: {0}\n'.format(DOCKER_IMAGE))
+
+        def mocked_gic(*args, **kw):
+            raise scuba.dockerutil.DockerError('mock error')
+
+        # http://alexmarandon.com/articles/python_mock_gotchas/#patching-in-the-wrong-place
+        # http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch
+        with mock.patch('scuba.__main__.get_image_command', side_effect=mocked_gic):
+            # DockerError -> exit(128)
+            self.run_scuba([], 128)
+
 
     def test_config_error(self):
         '''Verify config errors are handled gracefully'''