Repository: JonathonReinhart/scuba
Pull request #30 (2016-01-17 20:19:16)

additional unit tests


Patch:
diff --git a/.travis.yml b/.travis.yml
index 29490b2..77625b4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,9 +13,6 @@ matrix:
   allow_failures:
     - python: "nightly"
 
-install:
-  - "pip install -r requirements.txt"
-
 script:
   - ./run_tests.sh
 
diff --git a/scuba/__main__.py b/scuba/__main__.py
index 7091803..3a7901a 100755
--- a/scuba/__main__.py
+++ b/scuba/__main__.py
@@ -30,23 +30,13 @@ def verbose_msg(fmt, *args):
     if g_verbose:
         appmsg(fmt, *args)
 
-def process_command(config, command):
-    aliases = config.get('aliases', {})
-
-    if command:
-        rep = aliases.get(command[0])
-        if rep:
-            command.pop(0)
-            command = shlex.split(rep) + command
-
-    return command
 
 
 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, basestring):
+        if isinstance(options, str):
             options = (options,)
         vol += ':' + ','.join(options)
     return vol
@@ -63,7 +53,7 @@ def get_native_opts():
         f.write(line + '\n')
 
     # /etc/passwd
-    with NamedTemporaryFile(prefix='scuba', delete=False) as f:
+    with NamedTemporaryFile(mode='wt', prefix='scuba', delete=False) as f:
         filecleanup.register(f.name)
         opts.append(make_vol_opt(f.name, '/etc/passwd', 'z'))
 
@@ -88,7 +78,7 @@ def get_native_opts():
             ))
 
     # /etc/group
-    with NamedTemporaryFile(prefix='scuba', delete=False) as f:
+    with NamedTemporaryFile(mode='wt', prefix='scuba', delete=False) as f:
         filecleanup.register(f.name)
         opts.append(make_vol_opt(f.name, '/etc/group', 'z'))
 
@@ -105,7 +95,7 @@ def get_native_opts():
             ))
 
     # /etc/shadow
-    with NamedTemporaryFile(prefix='scuba', delete=False) as f:
+    with NamedTemporaryFile(mode='wt', prefix='scuba', delete=False) as f:
         filecleanup.register(f.name)
         opts.append(make_vol_opt(f.name, '/etc/shadow', 'z'))
 
@@ -148,7 +138,7 @@ def main(argv=None):
         sys.exit(128)
 
     # Process any aliases
-    usercmd = process_command(config, args.command)
+    usercmd = config.process_command(args.command)
 
     # Determine if Docker is running locally or remotely
     if 'DOCKER_HOST' in os.environ:
@@ -203,7 +193,7 @@ def main(argv=None):
     ] + docker_opts
 
     # Docker image
-    run_args.append(config['image'])
+    run_args.append(config.image)
 
     # Command to run in container
     run_args += docker_cmd
diff --git a/scuba/config.py b/scuba/config.py
index bbcd14c..d49f207 100644
--- a/scuba/config.py
+++ b/scuba/config.py
@@ -100,31 +100,56 @@ def find_config():
 
     raise ConfigError('{0} not found here or any parent directories'.format(SCUBA_YML))
 
+
+
+class ScubaConfig(object):
+    def __init__(self, **data):
+        required_nodes = ('image',)
+        optional_nodes = ('aliases',)
+
+        # Check for missing required nodes
+        missing = [n for n in required_nodes if not n in data]
+        if missing:
+            raise ConfigError('{0}: Required node{1} missing: {2}'.format(SCUBA_YML,
+                    's' if len(missing) > 1 else '', ', '.join(missing)))
+
+        # Check for unrecognized nodes
+        extra = [n for n in data if not n in required_nodes + optional_nodes]
+        if extra:
+            raise ConfigError('{0}: Unrecognized node{1}: {2}'.format(SCUBA_YML,
+                    's' if len(extra) > 1 else '', ', '.join(extra)))
+
+        self._image = data['image']
+
+        self._aliases = {}
+        for alias, cmdstr in data.get('aliases', {}).items():
+            self._aliases[alias] = shlex_split(cmdstr)
+
+    @property
+    def image(self):
+        return self._image
+
+    @property
+    def aliases(self):
+        return self._aliases
+
+    def process_command(self, command):
+        if command:
+            rep = self.aliases.get(command[0])
+            if rep:
+                command.pop(0)
+                command = rep + command
+
+        return command
+
+
 def load_config(path):
     try:
         with open(path) as f:
-            config = yaml.load(f, Loader)
+            data = yaml.load(f, Loader)
     except IOError as e:
-        raise ConfigError('Error opening {0}: {1}', SCUBA_YML, e)
+        raise ConfigError('Error opening {0}: {1}'.format(SCUBA_YML, e))
     except yaml.YAMLError as e:
-        raise ConfigError('Error loading {0}: {1}', SCUBA_YML, e)
-
-    if config == None:
-        config = {}
-
-    required_nodes = ('image',)
-    optional_nodes = ('aliases',)
-
-    # Check for missing required nodes
-    missing = [n for n in required_nodes if not n in config]
-    if missing:
-        raise ConfigError('{0}: Required node{1} missing: {2}', SCUBA_YML,
-                's' if len(missing) > 1 else '', ', '.join(missing))
-
-    # Check for unrecognized nodes
-    extra = [n for n in config if not n in required_nodes + optional_nodes]
-    if extra:
-        raise ConfigError('{0}: Unrecognized node{1}: {2}', SCUBA_YML,
-                's' if len(extra) > 1 else '', ', '.join(extra))
+        raise ConfigError('Error loading {0}: {1}'.format(SCUBA_YML, e))
 
-    return config
+    return ScubaConfig(**(data or {}))


Test patch:
diff --git a/run_tests.sh b/run_tests.sh
index 058d51e..5e05d58 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -1,2 +1,2 @@
 #!/bin/bash
-nosetests -v --with-coverage --cover-inclusive --cover-package=scuba
+nosetests -v --with-coverage --cover-inclusive --cover-package=scuba $@
diff --git a/tests/test_config.py b/tests/test_config.py
index 1f6ecc0..e2b712b 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,3 +1,4 @@
+ # coding=utf-8
 from __future__ import print_function
 
 from nose.tools import *
@@ -14,6 +15,9 @@ import scuba.config
 def assert_paths_equal(a, b):
     assert_equals(normpath(a), normpath(b))
 
+def assert_seq_equal(a, b):
+    assert_equals(list(a), list(b))
+
 class TestConfig(TestCase):
     def setUp(self):
         self.orig_path = os.getcwd()
@@ -84,12 +88,19 @@ class TestConfig(TestCase):
 
         assert_raises(scuba.config.ConfigError, scuba.config.load_config, '.scuba.yml')
 
+    def test_load_unexpected_node(self):
+        with open('.scuba.yml', 'w') as f:
+            f.write('image: busybox\n')
+            f.write('unexpected_node_123456: value\n')
+
+        assert_raises(scuba.config.ConfigError, scuba.config.load_config, '.scuba.yml')
+
     def test_load_config_minimal(self):
         with open('.scuba.yml', 'w') as f:
             f.write('image: busybox\n')
 
         config = scuba.config.load_config('.scuba.yml')
-        assert_equals(config['image'], 'busybox')
+        assert_equals(config.image, 'busybox')
 
     def test_load_config_with_aliases(self):
         with open('.scuba.yml', 'w') as f:
@@ -99,10 +110,11 @@ class TestConfig(TestCase):
             f.write('  snap: crackle pop\n')
 
         config = scuba.config.load_config('.scuba.yml')
-        assert_equals(config['image'], 'busybox')
-        assert_equals(len(config['aliases']), 2)
-        assert_equals(config['aliases']['foo'], 'bar')
-        assert_equals(config['aliases']['snap'], 'crackle pop')
+        assert_equals(config.image, 'busybox')
+        assert_equals(len(config.aliases), 2)
+        assert_seq_equal(config.aliases['foo'], ['bar'])
+        assert_seq_equal(config.aliases['snap'], ['crackle', 'pop'])
+
 
 
     def test_load_config_image_from_yaml(self):
@@ -113,7 +125,7 @@ class TestConfig(TestCase):
             f.write('image: !from_yaml .gitlab.yml image\n')
 
         config = scuba.config.load_config('.scuba.yml')
-        assert_equals(config['image'], 'debian:8.2')
+        assert_equals(config.image, 'debian:8.2')
 
     def test_load_config_image_from_yaml_nested_keys(self):
         with open('.gitlab.yml', 'w') as f:
@@ -125,7 +137,7 @@ class TestConfig(TestCase):
             f.write('image: !from_yaml .gitlab.yml somewhere.down.here\n')
 
         config = scuba.config.load_config('.scuba.yml')
-        assert_equals(config['image'], 'debian:8.2')
+        assert_equals(config.image, 'debian:8.2')
 
     def test_load_config_image_from_yaml_nested_key_missing(self):
         with open('.gitlab.yml', 'w') as f:
@@ -143,6 +155,12 @@ class TestConfig(TestCase):
 
         assert_raises(scuba.config.ConfigError, scuba.config.load_config, '.scuba.yml')
 
+    def test_load_config_image_from_yaml_unicode_args(self):
+        with open('.scuba.yml', 'w') as f:
+            f.write('image: !from_yaml .NONEXISTANT.yml ½\n')
+
+        assert_raises(scuba.config.ConfigError, scuba.config.load_config, '.scuba.yml')
+
     def test_load_config_image_from_yaml_missing_arg(self):
         with open('.gitlab.yml', 'w') as f:
             f.write('image: debian:8.2\n')
@@ -151,3 +169,46 @@ class TestConfig(TestCase):
             f.write('image: !from_yaml .gitlab.yml\n')
 
         assert_raises(scuba.config.ConfigError, scuba.config.load_config, '.scuba.yml')
+
+    ######################################################################
+    # process_command
+
+    def test_process_command_no_aliases(self):
+        cfg = scuba.config.ScubaConfig(
+                image = 'na',
+                )
+        result = cfg.process_command(['cmd', 'arg1', 'arg2'])
+        assert_equal(result, ['cmd', 'arg1', 'arg2'])
+
+    def test_process_command_aliases_unused(self):
+        cfg = scuba.config.ScubaConfig(
+                image = 'na',
+                aliases = dict(
+                    apple = 'banana',
+                    cat = 'dog',
+                    ),
+                )
+        result = cfg.process_command(['cmd', 'arg1', 'arg2'])
+        assert_equal(result, ['cmd', 'arg1', 'arg2'])
+
+    def test_process_command_aliases_used_noargs(self):
+        cfg = scuba.config.ScubaConfig(
+                image = 'na',
+                aliases = dict(
+                    apple = 'banana',
+                    cat = 'dog',
+                    ),
+                )
+        result = cfg.process_command(['apple', 'arg1', 'arg2'])
+        assert_equal(result, ['banana', 'arg1', 'arg2'])
+
+    def test_process_command_aliases_used_withargs(self):
+        cfg = scuba.config.ScubaConfig(
+                image = 'na',
+                aliases = dict(
+                    apple = 'banana cherry "pie is good"',
+                    cat = 'dog',
+                    ),
+                )
+        result = cfg.process_command(['apple', 'arg1', 'arg2'])
+        assert_equal(result, ['banana', 'cherry', 'pie is good', 'arg1', 'arg2'])
diff --git a/tests/test_etcfiles.py b/tests/test_etcfiles.py
new file mode 100644
index 0000000..df3463b
--- /dev/null
+++ b/tests/test_etcfiles.py
@@ -0,0 +1,44 @@
+from __future__ import print_function
+
+from nose.tools import *
+from unittest import TestCase
+
+import logging
+
+import scuba.etcfiles
+
+class TestEtcfiles(TestCase):
+
+    def test_passwd_entry(self):
+        ret = scuba.etcfiles.passwd_entry(
+            username = 'nobody',
+            password = 'x',
+            uid = 99,
+            gid = 99,
+            gecos = 'Nobody',
+            homedir = '/',
+            shell = '/sbin/nologin')
+
+        assert_equal('nobody:x:99:99:Nobody:/:/sbin/nologin', ret)
+
+    def test_group_entry(self):
+        ret = scuba.etcfiles.group_entry(
+            groupname = 'nobody',
+            password = 'x',
+            gid = 99,
+            users=['nope', 'uhuh', 'neep'])
+
+        assert_equal('nobody:x:99:nope,uhuh,neep', ret)
+
+    def test_shadow_entry(self):
+        ret = scuba.etcfiles.shadow_entry(
+            username = 'nobody',
+            password = '*',
+            lstchg = 12345,
+            minchg = 0,
+            maxchg = 99999,
+            warn = 7,
+            )
+
+        assert_equal('nobody:*:12345:0:99999:7:::', ret)
+