From c852c2e2bdd9e3b2306dc9f7b3e11d948b98930b Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Mar 21 2017 16:39:23 +0000
Subject: more runroot tests


---

diff --git a/Makefile b/Makefile
index 9a0bd34..bc8767f 100644
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,7 @@ git-clean:
 
 test:
 	coverage erase
-	PYTHONPATH=hub/.:plugins/hub/. nosetests --with-coverage --cover-package .
+	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. nosetests --with-coverage --cover-package .
 	coverage html
 	@echo Coverage report in htmlcov/index.html
 
diff --git a/plugins/builder/runroot.py b/plugins/builder/runroot.py
index 6b90ee2..fe23bd0 100644
--- a/plugins/builder/runroot.py
+++ b/plugins/builder/runroot.py
@@ -246,7 +246,7 @@ class RunRootTask(tasks.BaseTaskHandler):
         self.logger.info('New runroot')
         self.logger.info("Runroot mounts: %s" % mounts)
         fn = '%s/tmp/runroot_mounts' % rootdir
-        fslog = file(fn, 'a')
+        fslog = open(fn, 'a')
         logfile = "%s/do_mounts.log" % self.workdir
         uploadpath = self.getUploadDir()
         error = None
@@ -264,9 +264,6 @@ class RunRootTask(tasks.BaseTaskHandler):
                     error = koji.GenericError("No such directory or mount: %s" % dev)
                     break
                 type = 'none'
-                if path is None:
-                    #shorthand for "same path"
-                    path = dev
             if 'bg' in opts:
                 error = koji.GenericError("bad config: background mount not allowed")
                 break
@@ -294,8 +291,8 @@ class RunRootTask(tasks.BaseTaskHandler):
         mounts = {}
         fn = '%s/tmp/runroot_mounts' % rootdir
         if os.path.exists(fn):
-            fslog = file(fn, 'r')
-            for line in fslog:
+            fslog = open(fn, 'r')
+            for line in fslog.readlines():
                 mounts.setdefault(line.strip(), 1)
             fslog.close()
         #also, check /proc/mounts just in case
diff --git a/tests/test_plugins/test_runroot_builder.py b/tests/test_plugins/test_runroot_builder.py
new file mode 100644
index 0000000..e841d98
--- /dev/null
+++ b/tests/test_plugins/test_runroot_builder.py
@@ -0,0 +1,202 @@
+import unittest
+import mock
+import ConfigParser
+
+# inject builder data
+from tests.test_builder.loadkojid import kojid
+import __main__
+__main__.BuildRoot = kojid.BuildRoot
+
+import koji
+from runroot import RunRootTask
+
+class FakeConfigParser(object):
+    def __init__(self):
+        self.CONFIG = {
+            'paths': {
+                'default_mounts': '/mnt/archive,/mnt/workdir',
+                'safe_roots': '/mnt/workdir/tmp',
+                'path_subs':
+                '/mnt/archive/prehistory/,/mnt/prehistoric_disk/archive/prehistory',
+            },
+            'path0': {
+                'mountpoint': '/mnt/archive',
+                'path': 'archive.org:/vol/archive',
+                'fstype': 'nfs',
+                'options': 'ro,hard,intr,nosuid,nodev,noatime,tcp',
+            },
+        }
+
+    def read(self, path):
+        return
+
+    def has_option(self, section, key):
+        return section in self.CONFIG and key in self.CONFIG[section]
+
+    def has_section(self, section):
+        return section in self.CONFIG
+
+    def get(self, section, key):
+        try:
+            return self.CONFIG[section][key]
+        except KeyError:
+            raise ConfigParser.NoOptionError(section, key)
+
+
+class TestRunrootConfig(unittest.TestCase):
+    @mock.patch('ConfigParser.SafeConfigParser')
+    def test_bad_config_paths0(self, safe_config_parser):
+        cp = FakeConfigParser()
+        del cp.CONFIG['path0']['mountpoint']
+        safe_config_parser.return_value = cp
+        session = mock.MagicMock()
+        options = mock.MagicMock()
+        options.workdir = '/tmp/nonexistentdirectory'
+        with self.assertRaises(koji.GenericError) as cm:
+            RunRootTask(123, 'runroot', {}, session, options)
+        self.assertEqual(cm.exception.message,
+            "bad config: missing options in path0 section")
+
+    @mock.patch('ConfigParser.SafeConfigParser')
+    def test_bad_config_absolute_path(self, safe_config_parser):
+        cp = FakeConfigParser()
+        cp.CONFIG['paths']['default_mounts'] = ''
+        safe_config_parser.return_value = cp
+        session = mock.MagicMock()
+        options = mock.MagicMock()
+        options.workdir = '/tmp/nonexistentdirectory'
+        with self.assertRaises(koji.GenericError) as cm:
+            RunRootTask(123, 'runroot', {}, session, options)
+        self.assertEqual(cm.exception.message,
+            "bad config: all paths (default_mounts, safe_roots, path_subs) needs to be absolute: ")
+
+    @mock.patch('ConfigParser.SafeConfigParser')
+    def test_valid_config(self, safe_config_parser):
+        safe_config_parser.return_value = FakeConfigParser()
+        session = mock.MagicMock()
+        options = mock.MagicMock()
+        options.workdir = '/tmp/nonexistentdirectory'
+        RunRootTask(123, 'runroot', {}, session, options)
+
+class TestMounts(unittest.TestCase):
+    @mock.patch('ConfigParser.SafeConfigParser')
+    def setUp(self, safe_config_parser):
+        safe_config_parser.return_value = FakeConfigParser()
+        self.session = mock.MagicMock()
+        options = mock.MagicMock()
+        options.workdir = '/tmp/nonexistentdirectory'
+        self.t = RunRootTask(123, 'runroot', {}, self.session, options)
+
+    def test_get_path_params(self):
+        # non-existent item
+        with self.assertRaises(koji.GenericError):
+            self.t._get_path_params('nonexistent_dir')
+
+        # valid item
+        self.assertEqual(self.t._get_path_params('/mnt/archive', 'rw'),
+            ('archive.org:/vol/archive/', '/mnt/archive', 'nfs', 'rw,hard,intr,nosuid,nodev,noatime,tcp'))
+
+    @mock.patch('os.path.isdir')
+    @mock.patch('runroot.open')
+    @mock.patch('runroot.log_output')
+    def test_do_mounts(self, log_output, file_mock, is_dir):
+        log_output.return_value = 0 # successful mount
+
+        # no mounts, don't do anything
+        self.t.logger = mock.MagicMock()
+        self.t.do_mounts('rootdir', [])
+        self.t.logger.assert_not_called()
+
+        # mountpoint has no absolute_path
+        with self.assertRaises(koji.GenericError) as cm:
+            self.t.do_mounts('rootdir', [('nfs:nfs', 'relative_path', 'nfs', '')])
+        self.assertEqual(cm.exception.message,
+                "invalid mount point: relative_path")
+
+        # cover missing opts
+        self.t.do_mounts('rootdir', [('nfs:nfs', '/mnt/archive', 'nfs', None)])
+
+        # standard
+        log_output.reset_mock()
+        mounts = [self.t._get_path_params('/mnt/archive')]
+        self.t.do_mounts('rootdir', mounts)
+        log_output.assert_called_once_with(self.session, 'mount',
+                ['mount', '-t', 'nfs', '-o', 'ro,hard,intr,nosuid,nodev,noatime,tcp',
+                'archive.org:/vol/archive/', 'rootdir/mnt/archive'],
+                '/tmp/nonexistentdirectory/tasks/123/123/do_mounts.log',
+                'tasks/123/123', append=True, logerror=True)
+
+        # mount command failed
+        log_output.reset_mock()
+        log_output.return_value = 1
+        #self.t.undo_mounts = mock.MagicMock()
+        mounts = [self.t._get_path_params('/mnt/archive')]
+        with self.assertRaises(koji.GenericError) as cm:
+            self.t.do_mounts('rootdir', mounts)
+        self.assertEqual(cm.exception.message,
+            'Unable to mount rootdir/mnt/archive: mount -t nfs -o'
+            ' ro,hard,intr,nosuid,nodev,noatime,tcp archive.org:/vol/archive/'
+            ' rootdir/mnt/archive was killed by signal 1')
+
+        # bind ok
+        log_output.return_value = 0
+        log_output.reset_mock()
+        mount = list(self.t._get_path_params('/mnt/archive'))
+        mount[3] += ',bind'
+        is_dir.return_value = True
+        self.t.do_mounts('rootdir', [mount])
+        log_output.assert_called_once_with(self.session, 'mount',
+                ['mount', '-t', 'none', '-o', 'ro,hard,intr,nosuid,nodev,noatime,tcp,bind',
+                'archive.org:/vol/archive/', 'rootdir/mnt/archive'],
+                '/tmp/nonexistentdirectory/tasks/123/123/do_mounts.log',
+                'tasks/123/123', append=True, logerror=True)
+
+        # bind - target doesn't exist
+        mount = list(self.t._get_path_params('/mnt/archive'))
+        mount[3] += ',bind'
+        is_dir.return_value = False
+        with self.assertRaises(koji.GenericError) as cm:
+            self.t.do_mounts('rootdir', [mount])
+        self.assertEqual(cm.exception.message,
+            "No such directory or mount: archive.org:/vol/archive/")
+
+        # bg option forbidden
+        log_output.reset_mock()
+        mount = list(self.t._get_path_params('/mnt/archive'))
+        mount[3] += ',bg'
+        is_dir.return_value = False
+        with self.assertRaises(koji.GenericError) as cm:
+            self.t.do_mounts('rootdir', [mount])
+        self.assertEqual(cm.exception.message,
+            "bad config: background mount not allowed")
+
+    @mock.patch('os.unlink')
+    @mock.patch('commands.getstatusoutput')
+    @mock.patch('os.path.exists')
+    def test_undo_mounts(self, path_exists, getstatusoutput, os_unlink):
+        self.t.logger = mock.MagicMock()
+
+        # correct
+        getstatusoutput.return_value = (0, 'ok')
+        path_exists.return_value = True
+        with mock.patch('runroot.open', mock.mock_open(read_data = 'mountpoint')):
+            self.t.undo_mounts('rootdir')
+        self.t.logger.assert_has_calls([
+            mock.call.debug('Unmounting runroot mounts'),
+            mock.call.info("Unmounting (runroot): ['mountpoint']"),
+        ])
+        os_unlink.assert_called_once_with('rootdir/tmp/runroot_mounts')
+
+        # fail
+        os_unlink.reset_mock()
+        getstatusoutput.return_value = (1, 'error')
+        path_exists.return_value = True
+        with mock.patch('runroot.open', mock.mock_open(read_data = 'mountpoint')):
+            with self.assertRaises(koji.GenericError) as cm:
+                self.t.undo_mounts('rootdir')
+            self.assertEqual(cm.exception.message, 'Unable to unmount: mountpoint: error')
+        os_unlink.assert_not_called()
+
+class TestHandler(unittest.TestCase):
+    # TODO
+    pass
diff --git a/tests/test_plugins/test_runroot_hub.py b/tests/test_plugins/test_runroot_hub.py
index dd13289..79b5696 100644
--- a/tests/test_plugins/test_runroot_hub.py
+++ b/tests/test_plugins/test_runroot_hub.py
@@ -1,6 +1,7 @@
 import unittest
 import mock
 
+import koji
 import runroot_hub
 
 
@@ -21,3 +22,125 @@ class TestRunrootHub(unittest.TestCase):
             arch='x86_64',
             channel='runroot',
         )
+
+    @mock.patch('kojihub.get_tag')
+    @mock.patch('runroot_hub.context')
+    def test_noarch_wrong_tag(self, context, get_tag):
+        context.session.assertPerm = mock.MagicMock()
+        get_tag.return_value = {'name': 'some_tag', 'arches': ''}
+        with self.assertRaises(koji.GenericError):
+            runroot_hub.runroot(
+                tagInfo='some_tag',
+                arch='noarch',
+                command='ls',
+            )
+        get_tag.assert_called_once_with('some_tag')
+
+    @mock.patch('kojihub.make_task')
+    @mock.patch('kojihub.get_all_arches')
+    @mock.patch('kojihub.get_tag')
+    @mock.patch('runroot_hub.context')
+    def test_noarch_good_tag(self, context, get_tag, get_all_arches, make_task):
+        context.session.assertPerm = mock.MagicMock()
+        context.handlers = mock.MagicMock()
+        context.handlers.call = mock.MagicMock()
+        context.handlers.call.side_effect = [
+            {'id': 2, 'name': 'runroot'}, # getChannel
+            [ # listHosts
+                {
+                    'arches': 'i386 x86_64',
+                    'capacity': 20.0,
+                    'comment': '',
+                    'description': '',
+                    'enabled': True,
+                    'id': 1,
+                    'name': 'builder.example.com',
+                    'ready': True,
+                    'task_load': 0.0,
+                    'user_id': 1
+                }
+            ]
+        ]
+        get_tag.return_value = {
+            'arches': 's390 x86_64',
+            'extra': {},
+            'id': 123456,
+            'locked': False,
+            'maven_include_all': False,
+            'maven_support': False,
+            'name': 'some_tag',
+            'perm': None,
+            'perm_id': None
+        }
+        get_all_arches.return_value = ['s390', 's390x', 'x86_64']
+        runroot_hub.runroot(
+            tagInfo='some_tag',
+            arch='noarch',
+            command='ls',
+        )
+
+        # check results
+        get_tag.assert_called_once_with('some_tag')
+        context.handlers.call.assert_has_calls([
+            mock.call('getChannel', 'runroot', strict=True),
+            mock.call('listHosts', channelID=2, enabled=True),
+        ])
+        make_task.assert_called_once_with(
+            'runroot',
+            ('some_tag', 'noarch', 'ls'),
+            priority=15,
+            arch='x86_64',
+            channel='runroot',
+        )
+
+    @mock.patch('kojihub.make_task')
+    @mock.patch('kojihub.get_all_arches')
+    @mock.patch('kojihub.get_tag')
+    @mock.patch('runroot_hub.context')
+    def test_noarch_good_tag_missing_arch(self, context, get_tag, get_all_arches, make_task):
+        context.session.assertPerm = mock.MagicMock()
+        context.handlers = mock.MagicMock()
+        context.handlers.call = mock.MagicMock()
+        context.handlers.call.side_effect = [
+            {'id': 2, 'name': 'runroot'}, # getChannel
+            [ # listHosts
+                {
+                    'arches': 'i386 x86_64',
+                    'capacity': 20.0,
+                    'comment': '',
+                    'description': '',
+                    'enabled': True,
+                    'id': 1,
+                    'name': 'builder.example.com',
+                    'ready': True,
+                    'task_load': 0.0,
+                    'user_id': 1
+                }
+            ]
+        ]
+        get_tag.return_value = {
+            'arches': 's390',
+            'extra': {},
+            'id': 123456,
+            'locked': False,
+            'maven_include_all': False,
+            'maven_support': False,
+            'name': 'some_tag',
+            'perm': None,
+            'perm_id': None
+        }
+        get_all_arches.return_value = ['s390x']
+        with self.assertRaises(koji.GenericError):
+            runroot_hub.runroot(
+                tagInfo='some_tag',
+                arch='noarch',
+                command='ls',
+            )
+
+        # check results
+        get_tag.assert_called_once_with('some_tag')
+        context.handlers.call.assert_has_calls([
+            mock.call('getChannel', 'runroot', strict=True),
+            mock.call('listHosts', channelID=2, enabled=True),
+        ])
+        make_task.assert_not_called()

