From 6222912c8aa2488f65bf36acea29fb646c5861f4 Mon Sep 17 00:00:00 2001
From: Yuming Zhu <yzhu@redhat.com>
Date: Nov 08 2016 04:16:52 +0000
Subject: [PATCH 1/4] ut: cli - test_maven_build


---

diff --git a/cli/koji b/cli/koji
index a171ac7..67900b7 100755
--- a/cli/koji
+++ b/cli/koji
@@ -831,7 +831,7 @@ def handle_block_pkg(options, session, args):
         return ret
     session.multicall = True
     for package in args[1:]:
-        session.packageListBlock(tag,package)
+        session.packageListBlock(tag, package)
     session.multiCall(strict=True)
 
 def handle_remove_pkg(options, session, args):
@@ -1066,11 +1066,12 @@ def handle_chain_build(options, session, args):
         return
     else:
         session.logout()
-        return watch_tasks(session, [task_id], quiet=options.quiet)
+        return watch_tasks(session, [task_id], quiet=build_opts.quiet)
 
 def handle_maven_build(options, session, args):
     "[build] Build a Maven package from source"
     usage = _("usage: %prog maven-build [options] target URL")
+    usage += _("\n       %prog maven-build --ini=CONFIG... [options] target")
     usage += _("\n(Specify the --help global option for a list of other help options)")
     parser = OptionParser(usage=usage)
     parser.add_option("--patches", action="store", metavar="URL",
@@ -1111,13 +1112,13 @@ def handle_maven_build(options, session, args):
                       help=_("Perform a scratch build"))
     parser.add_option("--nowait", action="store_true",
                       help=_("Don't wait on build"))
-    parser.add_option("--noprogress", action="store_true",
-                      help=_("Do not display progress of the upload"))
+    parser.add_option("--quiet", action="store_true",
+                      help=_("Do not print the task information"), default=options.quiet)
     parser.add_option("--background", action="store_true",
                       help=_("Run the build at a lower priority"))
     (build_opts, args) = parser.parse_args(args)
     if build_opts.inis:
-        if len(args)!= 1:
+        if len(args) != 1:
             parser.error(_("Exactly one argument (a build target) is required"))
     else:
         if len(args) != 2:
@@ -1144,9 +1145,9 @@ def handle_maven_build(options, session, args):
         source = opts.pop('scmurl')
     else:
         source = args[1]
-        if '://' not in source:
-            parser.error(_("Invalid SCM URL: %s" % source))
         opts = koji.util.maven_opts(build_opts, scratch=build_opts.scratch)
+    if '://' not in source:
+        parser.error(_("Invalid SCM URL: %s" % source))
     if build_opts.debug:
         opts.setdefault('maven_options', []).append('--debug')
     if build_opts.skip_tag:
@@ -1156,13 +1157,14 @@ def handle_maven_build(options, session, args):
         #relative to koji.PRIO_DEFAULT
         priority = 5
     task_id = session.mavenBuild(source, target, opts, priority=priority)
-    print "Created task:", task_id
-    print "Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)
+    if not build_opts.quiet:
+        print "Created task:", task_id
+        print "Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)
     if _running_in_bg() or build_opts.nowait:
         return
     else:
         session.logout()
-        return watch_tasks(session,[task_id],quiet=options.quiet)
+        return watch_tasks(session, [task_id], quiet=build_opts.quiet)
 
 def handle_wrapper_rpm(options, session, args):
     """[build] Build wrapper rpms for any archives associated with a build."""
diff --git a/tests/test_cli/test_chain_build.py b/tests/test_cli/test_chain_build.py
index 188b59c..a981bc2 100644
--- a/tests/test_cli/test_chain_build.py
+++ b/tests/test_cli/test_chain_build.py
@@ -203,7 +203,7 @@ Options:
         self.session.getBuildTarget.return_value = target_info
         self.session.getTag.return_value = dest_tag_info
         # Run it and check immediate output
-        # args: target, target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3
+        # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3
         # expected: failed, dest_tag is locked
         with self.assertRaises(SystemExit) as cm:
             cli.handle_chain_build(self.options, self.session, args)
diff --git a/tests/test_cli/test_maven_build.py b/tests/test_cli/test_maven_build.py
new file mode 100644
index 0000000..53f7737
--- /dev/null
+++ b/tests/test_cli/test_maven_build.py
@@ -0,0 +1,778 @@
+import unittest
+
+import StringIO as stringio
+
+import os
+
+import sys
+
+import mock
+
+import loadcli
+import optparse
+
+cli = loadcli.cli
+
+EMPTY_BUILD_OPTS = {
+    'specfile': None,
+    'nowait': None,
+    'patches': None,
+    'envs': [],
+    'scratch': None,
+    'section': None,
+    'quiet': None,
+    'profiles': [],
+    'skip_tag': None,
+    'jvm_options': [],
+    'goals': [],
+    'background': None,
+    'maven_options': [],
+    'debug': None,
+    'packages': [],
+    'properties': [],
+    'inis': []}
+
+
+class TestMavenBuild(unittest.TestCase):
+    # Show long diffs in error output...
+    maxDiff = None
+
+    def setUp(self):
+        # Mock out the options parsed in main
+        self.options = mock.MagicMock()
+        self.options.quiet = None
+        self.options.weburl = 'weburl'
+        # Mock out the xmlrpc server
+        self.session = mock.MagicMock()
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build(self, watch_tasks_mock, running_in_bg_mock,
+                                activate_session_mock, stdout):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': dest_tag_id, 'name': dest_tag, 'locked': False}
+        source = 'http://scm'
+        task_id = 1
+        args = [target, source]
+        opts = {}
+        priority = None
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        self.session.mavenBuild.return_value = task_id
+        # Run it and check immediate output
+        # args: target http://scm
+        # expected: success
+        rv = cli.handle_maven_build(self.options, self.session, args)
+        actual = stdout.getvalue()
+        expected = """Created task: 1
+Task info: weburl/taskinfo?taskID=1
+"""
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        self.session.mavenBuild.assert_called_once_with(
+            source, target, opts, priority=priority)
+        running_in_bg_mock.assert_called_once()
+        self.session.logout.assert_called()
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
+        self.assertEqual(rv, 0)
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build_no_arg(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
+        args = []
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        # Run it and check immediate output
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual_stdout = stdout.getvalue()
+        actual_stderr = stderr.getvalue()
+        expected_stdout = ''
+        expected_stderr = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: Exactly two arguments (a build target and a SCM URL) are required
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual_stdout, expected_stdout)
+        self.assertMultiLineEqual(actual_stderr, expected_stderr)
+
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_not_called()
+        self.session.getBuildTarget.assert_not_called()
+        self.session.getTag.assert_not_called()
+        running_in_bg_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build_no_arg_with_ini(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
+        args = ['--ini=config.ini']
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        # Run it and check immediate output
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual_stdout = stdout.getvalue()
+        actual_stderr = stderr.getvalue()
+        expected_stdout = ''
+        expected_stderr = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: Exactly one argument (a build target) is required
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual_stdout, expected_stdout)
+        self.assertMultiLineEqual(actual_stderr, expected_stderr)
+
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_not_called()
+        self.session.getBuildTarget.assert_not_called()
+        self.session.getTag.assert_not_called()
+        running_in_bg_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build_help(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
+        args = ['--help']
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        # Run it and check immediate output
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual_stdout = stdout.getvalue()
+        actual_stderr = stderr.getvalue()
+        expected_stdout = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+Options:
+  -h, --help            show this help message and exit
+  --patches=URL         SCM URL of a directory containing patches to apply to
+                        the sources before building
+  -G GOAL, --goal=GOAL  Additional goal to run before "deploy"
+  -P PROFILE, --profile=PROFILE
+                        Enable a profile for the Maven build
+  -D NAME=VALUE, --property=NAME=VALUE
+                        Pass a system property to the Maven build
+  -E NAME=VALUE, --env=NAME=VALUE
+                        Set an environment variable
+  -p PACKAGE, --package=PACKAGE
+                        Install an additional package into the buildroot
+  -J OPTION, --jvm-option=OPTION
+                        Pass a command-line option to the JVM
+  -M OPTION, --maven-option=OPTION
+                        Pass a command-line option to Maven
+  --ini=CONFIG          Pass build parameters via a .ini file
+  -s SECTION, --section=SECTION
+                        Get build parameters from this section of the .ini
+  --debug               Run Maven build in debug mode
+  --specfile=URL        SCM URL of a spec file fragment to use to generate
+                        wrapper RPMs
+  --skip-tag            Do not attempt to tag package
+  --scratch             Perform a scratch build
+  --nowait              Don't wait on build
+  --quiet               Do not print the task information
+  --background          Run the build at a lower priority
+""" % (progname, progname)
+        expected_stderr = ''
+        self.assertMultiLineEqual(actual_stdout, expected_stdout)
+        self.assertMultiLineEqual(actual_stderr, expected_stderr)
+
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_not_called()
+        self.session.getBuildTarget.assert_not_called()
+        self.session.getTag.assert_not_called()
+
+        running_in_bg_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertEqual(cm.exception.code, 0)
+
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build_target_not_found(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr):
+        target = 'target'
+        target_info = None
+        source = 'http://scm'
+        args = [target, source]
+
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        self.session.getBuildTarget.return_value = target_info
+        # Run it and check immediate output
+        # args: target http://scm
+        # expected: failed, target not found
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual = stderr.getvalue()
+        expected = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: Unknown build target: target
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_not_called()
+        running_in_bg_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_build_dest_tag_not_found(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = None
+        source = 'http://scm'
+        args = [target, source]
+
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        # Run it and check immediate output
+        # args: target http://scm
+        # expected: failed, dest_tag not found
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual = stderr.getvalue()
+        expected = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: Unknown destination tag: dest_tag
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        running_in_bg_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_build_dest_tag_locked(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': True}
+        source = 'http://scm'
+        args = [target, source]
+
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        # Run it and check immediate output
+        # args: target http://scm
+        # expected: failed, dest_tag is locked
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual = stderr.getvalue()
+        expected = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: Destination tag dest_tag is locked
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        running_in_bg_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch(
+        'koji.util.parse_maven_param',
+        return_value={
+            'section': {
+                'scmurl': 'http://iniscmurl',
+                'packages': [
+                    'pkg1',
+                    'pkg2']}})
+    @mock.patch('koji.util.maven_opts')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_build_inis(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            maven_opts_mock,
+            parse_maven_param_mock,
+            activate_session_mock,
+            stdout,
+            stderr):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
+        source = 'http://iniscmurl'
+        section = 'section'
+        args = [
+            '--ini=config1.ini',
+            '--ini=config2.ini',
+            '--section=' +
+            section,
+            target]
+        scratch = None
+        build_opts = EMPTY_BUILD_OPTS.copy()
+        build_opts['section'] = section
+        build_opts['inis'] = ['config1.ini', 'config2.ini']
+        build_opts = optparse.Values(build_opts)
+        opts = {'packages': ['pkg1', 'pkg2']}
+        task_id = 1
+        priority = None
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        self.session.mavenBuild.return_value = task_id
+        # Run it and check immediate output
+        # args: --ini=config1.ini --ini=config2.ini --section=section target
+        # expected: success
+        rv = cli.handle_maven_build(self.options, self.session, args)
+        actual = stdout.getvalue()
+        expected = """Created task: 1
+Task info: weburl/taskinfo?taskID=1
+"""
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        parse_maven_param_mock.assert_called_once_with(
+            build_opts.inis, scratch=scratch, section=section)
+        maven_opts_mock.assert_not_called()
+        self.session.mavenBuild.assert_called_once_with(
+            source, target, opts, priority=priority)
+        running_in_bg_mock.assert_called_once()
+        self.session.logout.assert_called_once()
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=build_opts.quiet)
+        self.assertEqual(rv, 0)
+
+        stdout.seek(0)
+        stdout.truncate()
+        self.options.reset_mock()
+        parse_maven_param_mock.reset_mock()
+        parse_maven_param_mock.return_value = {
+            'section': {
+                'type': 'other',
+                'scmurl': 'http://iniscmurl',
+                'packages': [
+                    'pkg1',
+                    'pkg2']}}
+        self.session.reset_mock()
+        # Run it and check immediate output
+        # args: --ini=config1.ini --ini=config2.ini --section=section target
+        # expected: failed, no type == 'maven' found
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual = stderr.getvalue()
+        expected = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: Section section does not contain a maven-build config
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual, expected)
+        self.assertMultiLineEqual(stdout.getvalue(), '')
+        parse_maven_param_mock.assert_called_once_with(
+            build_opts.inis, scratch=scratch, section=section)
+        maven_opts_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+        stdout.seek(0)
+        stdout.truncate()
+        stderr.seek(0)
+        stderr.truncate()
+        self.options.reset_mock()
+        parse_maven_param_mock.reset_mock()
+        parse_maven_param_mock.side_effect = ValueError('errormsg')
+        self.session.reset_mock()
+        # Run it and check immediate output
+        # args: --ini=config1.ini --ini=config2.ini --section=section target
+        # expected: failed, ValueError raised when parsing .ini files
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual = stderr.getvalue()
+        expected = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: errormsg
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual, expected)
+        self.assertMultiLineEqual(stdout.getvalue(), '')
+        parse_maven_param_mock.assert_called_once_with(
+            build_opts.inis, scratch=scratch, section=section)
+        maven_opts_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+    @mock.patch('sys.stderr', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji.util.parse_maven_param')
+    @mock.patch('koji.util.maven_opts')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_build_invalid_scm(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            maven_opts_mock,
+            parse_maven_param_mock,
+            activate_session_mock,
+            stderr):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
+        source = 'badscm'
+        args = [target, source]
+        scratch = None
+        build_opts = EMPTY_BUILD_OPTS.copy()
+        progname = os.path.basename(sys.argv[0]) or 'koji'
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        # Run it and check immediate output
+        # args: target badscm
+        # expected: failed, scm is invalid
+        with self.assertRaises(SystemExit) as cm:
+            cli.handle_maven_build(self.options, self.session, args)
+        actual = stderr.getvalue()
+        expected = """Usage: %s maven-build [options] target URL
+       %s maven-build --ini=CONFIG... [options] target
+(Specify the --help global option for a list of other help options)
+
+%s: error: Invalid SCM URL: badscm
+""" % (progname, progname, progname)
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        parse_maven_param_mock.assert_not_called()
+        maven_opts_mock.assert_called_once_with(build_opts, scratch=scratch)
+        running_in_bg_mock.assert_not_called()
+        self.session.mavenBuild.assert_not_called()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertEqual(cm.exception.code, 2)
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji.util.parse_maven_param')
+    @mock.patch('koji.util.maven_opts', return_value={})
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_build_other_params(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            maven_opts_mock,
+            parse_maven_param_mock,
+            activate_session_mock,
+            stdout):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
+        source = 'http://scm'
+        args = ['--debug', '--skip-tag', '--background', target, source]
+        scratch = None
+        priority = 5
+        build_opts = EMPTY_BUILD_OPTS.copy()
+        build_opts['debug'] = True
+        build_opts['skip_tag'] = True
+        build_opts['background'] = True
+        opts = {'maven_options': ['--debug'], 'skip_tag': True}
+
+        task_id = 1
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        self.session.mavenBuild.return_value = task_id
+        # Run it and check immediate output
+        # args: --debug --skip-tag --background target http://scm
+        # expected: success
+        rv = cli.handle_maven_build(self.options, self.session, args)
+        actual = stdout.getvalue()
+        expected = """Created task: 1
+Task info: weburl/taskinfo?taskID=1
+"""
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        parse_maven_param_mock.assert_not_called()
+        maven_opts_mock.assert_called_once_with(build_opts, scratch=scratch)
+        running_in_bg_mock.assert_called_once()
+        self.session.mavenBuild.assert_called_once_with(
+            source, target, opts, priority=priority)
+        self.session.logout.assert_called_once()
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=build_opts['quiet'])
+        self.assertEqual(rv, 0)
+
+        stdout.seek(0)
+        stdout.truncate()
+        self.options.reset_mock()
+        maven_opts_mock.reset_mock()
+        maven_opts_mock.return_value = {'maven_options': ['test', 'test2=val']}
+        self.session.reset_mock()
+        args = [
+            '--debug',
+            '--skip-tag',
+            '--background',
+            '-Mtest',
+            '-Mtest2=val',
+            target,
+            source]
+        build_opts['maven_options'] = ['test', 'test2=val']
+        opts['maven_options'] = ['test', 'test2=val', '--debug']
+        # Run it and check immediate output
+        # args: --debug --skip-tag --background -Mtest -Mtest2=val target http://scm
+        # expected: success
+        cli.handle_maven_build(self.options, self.session, args)
+        self.assertMultiLineEqual(actual, expected)
+        maven_opts_mock.assert_called_once_with(build_opts, scratch=scratch)
+        self.session.mavenBuild.assert_called_once_with(
+            source, target, opts, priority=priority)
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build_quiet(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stdout):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': dest_tag_id, 'name': dest_tag, 'locked': False}
+        source = 'http://scm'
+        task_id = 1
+        args = ['--quiet', target, source]
+        opts = {}
+        priority = None
+        self.options.quiet = True
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        self.session.mavenBuild.return_value = task_id
+        # Run it and check immediate output
+        # args: --quiet target http://scm
+        # expected: success
+        rv = cli.handle_maven_build(self.options, self.session, args)
+        actual = stdout.getvalue()
+        expected = ''
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        self.session.mavenBuild.assert_called_once_with(
+            source, target, opts, priority=priority)
+        running_in_bg_mock.assert_called_once()
+        self.session.logout.assert_called()
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
+        self.assertEqual(rv, 0)
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji_cli._running_in_bg', return_value=True)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build_quiet(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stdout):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': dest_tag_id, 'name': dest_tag, 'locked': False}
+        source = 'http://scm'
+        task_id = 1
+        args = [target, source]
+        opts = {}
+        priority = None
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        self.session.mavenBuild.return_value = task_id
+        # Run it and check immediate output
+        # args: target http://scm
+        # expected: success
+        rv = cli.handle_maven_build(self.options, self.session, args)
+        actual = stdout.getvalue()
+        expected = """Created task: 1
+Task info: weburl/taskinfo?taskID=1
+"""
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        self.session.mavenBuild.assert_called_once_with(
+            source, target, opts, priority=priority)
+        running_in_bg_mock.assert_called_once()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertIsNone(rv)
+
+    @mock.patch('sys.stdout', new_callable=stringio.StringIO)
+    @mock.patch('koji_cli.activate_session')
+    @mock.patch('koji.util.parse_maven_param')
+    @mock.patch('koji.util.maven_opts', return_value={})
+    @mock.patch('koji_cli._running_in_bg', return_value=False)
+    @mock.patch('koji_cli.watch_tasks', return_value=0)
+    def test_handle_maven_build_nowait(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            maven_opts_mock,
+            parse_maven_param_mock,
+            activate_session_mock,
+            stdout):
+        target = 'target'
+        dest_tag = 'dest_tag'
+        dest_tag_id = 2
+        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag}
+        dest_tag_info = {'id': dest_tag_id, 'name': dest_tag, 'locked': False}
+        source = 'http://scm'
+        task_id = 1
+        args = ['--nowait', target, source]
+        build_opts = EMPTY_BUILD_OPTS.copy()
+        build_opts['nowait'] = True
+        opts = {}
+        priority = None
+        scratch = None
+
+        self.session.getBuildTarget.return_value = target_info
+        self.session.getTag.return_value = dest_tag_info
+        self.session.mavenBuild.return_value = task_id
+        # Run it and check immediate output
+        # args: target http://scm
+        # expected: success
+        rv = cli.handle_maven_build(self.options, self.session, args)
+        actual = stdout.getvalue()
+        expected = """Created task: 1
+Task info: weburl/taskinfo?taskID=1
+"""
+        self.assertMultiLineEqual(actual, expected)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_called_once_with(self.session)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.getTag.assert_called_once_with(dest_tag_id)
+        parse_maven_param_mock.assert_not_called()
+        maven_opts_mock.assert_called_once_with(build_opts, scratch=scratch)
+        self.session.mavenBuild.assert_called_once_with(
+            source, target, opts, priority=priority)
+        running_in_bg_mock.assert_called_once()
+        self.session.logout.assert_not_called()
+        watch_tasks_mock.assert_not_called()
+        self.assertIsNone(rv)
+
+
+if __name__ == '__main__':
+    unittest.main()

From 020493f32f3ea78dfdc05da2195db3880ae6707b Mon Sep 17 00:00:00 2001
From: Yuming Zhu <yzhu@redhat.com>
Date: Nov 08 2016 04:18:01 +0000
Subject: [PATCH 2/4] ut: format testcases


---

diff --git a/tests/test_cli/test_build.py b/tests/test_cli/test_build.py
index 7582f80..1afe54e 100644
--- a/tests/test_cli/test_build.py
+++ b/tests/test_cli/test_build.py
@@ -30,8 +30,13 @@ class TestBuild(unittest.TestCase):
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_from_srpm(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock, activate_session_mock,
-                                    stdout):
+    def test_handle_build_from_srpm(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -62,10 +67,13 @@ Task info: weburl/taskinfo?taskID=1
         self.session.getTag.assert_called_once_with(dest_tag)
         unique_path_mock.assert_called_once_with('cli-build')
         self.assertEqual(running_in_bg_mock.call_count, 2)
-        self.session.uploadWrapper.assert_called_once_with(source, 'random_path', callback=cli._progress_callback)
-        self.session.build.assert_called_once_with('random_path/' + source, target, opts, priority=priority)
+        self.session.uploadWrapper.assert_called_once_with(
+            source, 'random_path', callback=cli._progress_callback)
+        self.session.build.assert_called_once_with(
+            'random_path/' + source, target, opts, priority=priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -73,8 +81,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_from_scm(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                   activate_session_mock, stdout):
+    def test_handle_build_from_scm(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -104,9 +117,11 @@ Task info: weburl/taskinfo?taskID=1
         unique_path_mock.assert_not_called()
         running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
-        self.session.build.assert_called_once_with(source, target, opts, priority=priority)
+        self.session.build.assert_called_once_with(
+            source, target, opts, priority=priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -116,7 +131,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
     def test_handle_build_no_arg(
-            self, watch_tasks_mock, running_in_bg_mock, unique_path_mock, activate_session_mock, stderr, stdout):
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
         args = []
         progname = os.path.basename(sys.argv[0]) or 'koji'
 
@@ -153,7 +174,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
     def test_handle_build_help(
-            self, watch_tasks_mock, running_in_bg_mock, unique_path_mock, activate_session_mock, stderr, stdout):
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
         args = ['--help']
         progname = os.path.basename(sys.argv[0]) or 'koji'
 
@@ -201,7 +228,13 @@ Options:
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
     def test_handle_build_arch_override_denied(
-            self, watch_tasks_mock, running_in_bg_mock, unique_path_mock, activate_session_mock, stderr, stdout):
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
         target = 'target'
         source = 'http://scm'
         arch_override = 'somearch'
@@ -239,8 +272,13 @@ Options:
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_none_tag(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                   activate_session_mock, stdout):
+    def test_handle_build_none_tag(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'nOne'
         source = 'http://scm'
         task_id = 1
@@ -267,9 +305,11 @@ Task info: weburl/taskinfo?taskID=1
         running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
         # target==None, repo_id==2, skip_tag==True
-        self.session.build.assert_called_once_with(source, None, opts, priority=priority)
+        self.session.build.assert_called_once_with(
+            source, None, opts, priority=priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stderr', new_callable=stringio.StringIO)
@@ -277,8 +317,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_target_not_found(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                           activate_session_mock, stderr):
+    def test_handle_build_target_not_found(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stderr):
         target = 'target'
         target_info = None
         source = 'http://scm'
@@ -316,8 +361,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_dest_tag_not_found(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                             activate_session_mock, stderr):
+    def test_handle_build_dest_tag_not_found(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stderr):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_name = 'dest_tag_name'
@@ -359,8 +409,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_dest_tag_locked(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                          activate_session_mock, stderr):
+    def test_handle_build_dest_tag_locked(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stderr):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_name = 'dest_tag_name'
@@ -402,8 +457,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_arch_override(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                        activate_session_mock, stdout):
+    def test_handle_build_arch_override(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -411,7 +471,12 @@ Task info: weburl/taskinfo?taskID=1
         source = 'http://scm'
         task_id = 1
         arch_override = 'somearch'
-        args = ['--arch-override=' + arch_override, '--scratch', target, source]
+        args = [
+            '--arch-override=' +
+            arch_override,
+            '--scratch',
+            target,
+            source]
         opts = {'arch_override': arch_override, 'scratch': True}
         priority = None
 
@@ -435,9 +500,11 @@ Task info: weburl/taskinfo?taskID=1
         running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
         # arch-override=='somearch', scratch==True
-        self.session.build.assert_called_once_with(source, target, opts, priority=priority)
+        self.session.build.assert_called_once_with(
+            source, target, opts, priority=priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -445,8 +512,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_background(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                     activate_session_mock, stdout):
+    def test_handle_build_background(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -476,9 +548,11 @@ Task info: weburl/taskinfo?taskID=1
         unique_path_mock.assert_not_called()
         running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
-        self.session.build.assert_called_once_with(source, target, opts, priority=priority)
+        self.session.build.assert_called_once_with(
+            source, target, opts, priority=priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -486,8 +560,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=True)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_running_in_bg(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                        activate_session_mock, stdout):
+    def test_handle_build_running_in_bg(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -519,8 +598,10 @@ Task info: weburl/taskinfo?taskID=1
         unique_path_mock.assert_called_once_with('cli-build')
         self.assertEqual(running_in_bg_mock.call_count, 2)
         # callback==None
-        self.session.uploadWrapper.assert_called_once_with(source, 'random_path', callback=None)
-        self.session.build.assert_called_once_with('random_path/' + source, target, opts, priority=priority)
+        self.session.uploadWrapper.assert_called_once_with(
+            source, 'random_path', callback=None)
+        self.session.build.assert_called_once_with(
+            'random_path/' + source, target, opts, priority=priority)
         self.session.logout.assert_not_called()
         watch_tasks_mock.assert_not_called()
         self.assertIsNone(rv)
@@ -530,8 +611,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_noprogress(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                     activate_session_mock, stdout):
+    def test_handle_build_noprogress(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -563,10 +649,13 @@ Task info: weburl/taskinfo?taskID=1
         unique_path_mock.assert_called_once_with('cli-build')
         self.assertEqual(running_in_bg_mock.call_count, 2)
         # callback==None
-        self.session.uploadWrapper.assert_called_once_with(source, 'random_path', callback=None)
-        self.session.build.assert_called_once_with('random_path/' + source, target, opts, priority=priority)
+        self.session.uploadWrapper.assert_called_once_with(
+            source, 'random_path', callback=None)
+        self.session.build.assert_called_once_with(
+            'random_path/' + source, target, opts, priority=priority)
         self.session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -574,8 +663,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_quiet(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                activate_session_mock, stdout):
+    def test_handle_build_quiet(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -604,10 +698,13 @@ Task info: weburl/taskinfo?taskID=1
         unique_path_mock.assert_called_once_with('cli-build')
         self.assertEqual(running_in_bg_mock.call_count, 2)
         # callback==None
-        self.session.uploadWrapper.assert_called_once_with(source, 'random_path', callback=None)
-        self.session.build.assert_called_once_with('random_path/' + source, target, opts, priority=priority)
+        self.session.uploadWrapper.assert_called_once_with(
+            source, 'random_path', callback=None)
+        self.session.build.assert_called_once_with(
+            'random_path/' + source, target, opts, priority=priority)
         self.session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -615,8 +712,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_wait(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                               activate_session_mock, stdout):
+    def test_handle_build_wait(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -649,10 +751,13 @@ Task info: weburl/taskinfo?taskID=1
         unique_path_mock.assert_called_once_with('cli-build')
         # the second one won't be executed when wait==False
         self.assertEqual(running_in_bg_mock.call_count, 1)
-        self.session.uploadWrapper.assert_called_once_with(source, 'random_path', callback=cli._progress_callback)
-        self.session.build.assert_called_once_with('random_path/' + source, target, opts, priority=priority)
+        self.session.uploadWrapper.assert_called_once_with(
+            source, 'random_path', callback=cli._progress_callback)
+        self.session.build.assert_called_once_with(
+            'random_path/' + source, target, opts, priority=priority)
         self.session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -660,8 +765,13 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._unique_path', return_value='random_path')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_nowait(self, watch_tasks_mock, running_in_bg_mock, unique_path_mock,
-                                 activate_session_mock, stdout):
+    def test_handle_build_nowait(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            unique_path_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         target_info = {'dest_tag': dest_tag}
@@ -693,8 +803,10 @@ Task info: weburl/taskinfo?taskID=1
         unique_path_mock.assert_called_once_with('cli-build')
         # the second one won't be executed when wait==False
         self.assertEqual(running_in_bg_mock.call_count, 1)
-        self.session.uploadWrapper.assert_called_once_with(source, 'random_path', callback=cli._progress_callback)
-        self.session.build.assert_called_once_with('random_path/' + source, target, opts, priority=priority)
+        self.session.uploadWrapper.assert_called_once_with(
+            source, 'random_path', callback=cli._progress_callback)
+        self.session.build.assert_called_once_with(
+            'random_path/' + source, target, opts, priority=priority)
         self.session.logout.assert_not_called()
         watch_tasks_mock.assert_not_called()
         self.assertIsNone(rv)
diff --git a/tests/test_cli/test_chain_build.py b/tests/test_cli/test_chain_build.py
index a981bc2..55699ca 100644
--- a/tests/test_cli/test_chain_build.py
+++ b/tests/test_cli/test_chain_build.py
@@ -36,12 +36,24 @@ class TestChainBuild(unittest.TestCase):
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
         tag_tree = [{'parent_id': 2}, {'parent_id': 4}, {'parent_id': 5}]
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
-        sources = [['http://scm1'], ['http://scm2', 'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
+        sources = [['http://scm1'], ['http://scm2',
+                                     'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
         task_id = 1
         args = [target] + source_args
         priority = None
@@ -64,10 +76,12 @@ Task info: weburl/taskinfo?taskID=1
         self.session.getBuildTarget.assert_called_once_with(target)
         self.session.getTag.assert_called_once_with(dest_tag_id, strict=True)
         self.session.getFullInheritance.assert_called_once_with(build_tag_id)
-        self.session.chainBuild.assert_called_once_with(sources, target, priority=priority)
+        self.session.chainBuild.assert_called_once_with(
+            sources, target, priority=priority)
         running_in_bg_mock.assert_called_once()
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
@@ -76,7 +90,12 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
     def test_handle_chain_build_no_arg(
-            self, watch_tasks_mock, running_in_bg_mock, activate_session_mock, stderr, stdout):
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
         args = []
         progname = os.path.basename(sys.argv[0]) or 'koji'
 
@@ -111,7 +130,12 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
     def test_handle_chain_build_help(
-            self, watch_tasks_mock, running_in_bg_mock, activate_session_mock, stderr, stdout):
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr,
+            stdout):
         args = ['--help']
         progname = os.path.basename(sys.argv[0]) or 'koji'
 
@@ -148,11 +172,23 @@ Options:
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_chain_build_target_not_found(self, watch_tasks_mock, running_in_bg_mock,
-                                                 activate_session_mock, stderr):
+    def test_handle_chain_build_target_not_found(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr):
         target = 'target'
         target_info = None
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
         args = [target] + source_args
 
         progname = os.path.basename(sys.argv[0]) or 'koji'
@@ -185,17 +221,32 @@ Options:
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_dest_tag_locked(self, watch_tasks_mock, running_in_bg_mock,
-                                          activate_session_mock, stderr):
+    def test_handle_build_dest_tag_locked(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stderr):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': True}
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
         args = [target] + source_args
 
         progname = os.path.basename(sys.argv[0]) or 'koji'
@@ -229,18 +280,30 @@ Options:
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_build_dest_tag_not_inherited_by_build_tag(self, watch_tasks_mock, running_in_bg_mock,
-                                                              activate_session_mock, stdout):
+    def test_handle_build_dest_tag_not_inherited_by_build_tag(
+            self, watch_tasks_mock, running_in_bg_mock, activate_session_mock, stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'name': target, 'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'name': target,
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
         tag_tree = [{'parent_id': 4}, {'parent_id': 5}]
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
         args = [target] + source_args
 
         self.session.getBuildTarget.return_value = target_info
@@ -269,18 +332,32 @@ Target target is not usable for a chain-build
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_chain_build_invalidated_src(self, watch_tasks_mock, running_in_bg_mock,
-                                                activate_session_mock):
+    def test_handle_chain_build_invalidated_src(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
         tag_tree = [{'parent_id': 2}, {'parent_id': 4}, {'parent_id': 5}]
-        source_args = ['badnvr', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
+        source_args = [
+            'badnvr',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
         args = [target] + source_args
 
         self.session.getBuildTarget.return_value = target_info
@@ -297,8 +374,10 @@ Target target is not usable for a chain-build
             # Finally, assert that things were called as we expected.
             activate_session_mock.assert_called_once_with(self.session)
             self.session.getBuildTarget.assert_called_once_with(target)
-            self.session.getTag.assert_called_once_with(dest_tag_id, strict=True)
-            self.session.getFullInheritance.assert_called_once_with(build_tag_id)
+            self.session.getTag.assert_called_once_with(
+                dest_tag_id, strict=True)
+            self.session.getFullInheritance.assert_called_once_with(
+                build_tag_id)
             self.session.chainBuild.assert_not_called()
             running_in_bg_mock.assert_not_called()
             self.session.logout.assert_not_called()
@@ -306,7 +385,15 @@ Target target is not usable for a chain-build
             self.assertEqual(rv, 1)
 
         with mock.patch('sys.stdout', new_callable=stringio.StringIO) as stdout:
-            source_args = ['path/n-v-r', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
+            source_args = [
+                'path/n-v-r',
+                ':',
+                'http://scm2',
+                'http://scm3',
+                'n-v-r-1',
+                ':',
+                'n-v-r-2',
+                'n-v-r-3']
             args = [target] + source_args
             # args: target path/n-v-r : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3
             # expected: failed
@@ -316,7 +403,15 @@ Target target is not usable for a chain-build
             self.assertMultiLineEqual(actual, expected)
 
         with mock.patch('sys.stdout', new_callable=stringio.StringIO) as stdout:
-            source_args = ['badn-vr', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
+            source_args = [
+                'badn-vr',
+                ':',
+                'http://scm2',
+                'http://scm3',
+                'n-v-r-1',
+                ':',
+                'n-v-r-2',
+                'n-v-r-3']
             args = [target] + source_args
             # args: target badn-vr : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3
             # expected: failed
@@ -326,7 +421,15 @@ Target target is not usable for a chain-build
             self.assertMultiLineEqual(actual, expected)
 
         with mock.patch('sys.stdout', new_callable=stringio.StringIO) as stdout:
-            source_args = ['badn-v-r.rpm', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
+            source_args = [
+                'badn-v-r.rpm',
+                ':',
+                'http://scm2',
+                'http://scm3',
+                'n-v-r-1',
+                ':',
+                'n-v-r-2',
+                'n-v-r-3']
             args = [target] + source_args
             # args: target badn-v-r.rpm : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3
             # expected: failed
@@ -359,19 +462,35 @@ If there are no dependencies, use the build command instead
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_chain_build_background(self, watch_tasks_mock, running_in_bg_mock,
-                                           activate_session_mock, stdout):
+    def test_handle_chain_build_background(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
         tag_tree = [{'parent_id': 2}, {'parent_id': 4}, {'parent_id': 5}]
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
-        sources = [['http://scm1'], ['http://scm2', 'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
+        sources = [['http://scm1'], ['http://scm2',
+                                     'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
         task_id = 1
         args = ['--background', target] + source_args
         priority = 5
@@ -394,29 +513,47 @@ Task info: weburl/taskinfo?taskID=1
         self.session.getBuildTarget.assert_called_once_with(target)
         self.session.getTag.assert_called_once_with(dest_tag_id, strict=True)
         self.session.getFullInheritance.assert_called_once_with(build_tag_id)
-        self.session.chainBuild.assert_called_once_with(sources, target, priority=priority)
+        self.session.chainBuild.assert_called_once_with(
+            sources, target, priority=priority)
         running_in_bg_mock.assert_called_once()
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_chain_build_quiet(self, watch_tasks_mock, running_in_bg_mock,
-                                      activate_session_mock, stdout):
+    def test_handle_chain_build_quiet(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
         tag_tree = [{'parent_id': 2}, {'parent_id': 4}, {'parent_id': 5}]
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
-        sources = [['http://scm1'], ['http://scm2', 'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
+        sources = [['http://scm1'], ['http://scm2',
+                                     'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
         task_id = 1
         self.options.quiet = True
         args = ['--quiet', target] + source_args
@@ -438,29 +575,47 @@ Task info: weburl/taskinfo?taskID=1
         self.session.getBuildTarget.assert_called_once_with(target)
         self.session.getTag.assert_called_once_with(dest_tag_id, strict=True)
         self.session.getFullInheritance.assert_called_once_with(build_tag_id)
-        self.session.chainBuild.assert_called_once_with(sources, target, priority=priority)
+        self.session.chainBuild.assert_called_once_with(
+            sources, target, priority=priority)
         running_in_bg_mock.assert_called_once()
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(self.session, [task_id], quiet=self.options.quiet)
+        watch_tasks_mock.assert_called_once_with(
+            self.session, [task_id], quiet=self.options.quiet)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=stringio.StringIO)
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=True)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_chain_build_running_in_bg(self, watch_tasks_mock, running_in_bg_mock,
-                                              activate_session_mock, stdout):
+    def test_handle_chain_build_running_in_bg(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
         tag_tree = [{'parent_id': 2}, {'parent_id': 4}, {'parent_id': 5}]
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
-        sources = [['http://scm1'], ['http://scm2', 'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
+        sources = [['http://scm1'], ['http://scm2',
+                                     'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
         task_id = 1
         args = [target] + source_args
         priority = None
@@ -483,7 +638,8 @@ Task info: weburl/taskinfo?taskID=1
         self.session.getBuildTarget.assert_called_once_with(target)
         self.session.getTag.assert_called_once_with(dest_tag_id, strict=True)
         self.session.getFullInheritance.assert_called_once_with(build_tag_id)
-        self.session.chainBuild.assert_called_once_with(sources, target, priority=priority)
+        self.session.chainBuild.assert_called_once_with(
+            sources, target, priority=priority)
         running_in_bg_mock.assert_called_once()
         self.session.logout.assert_not_called()
         watch_tasks_mock.assert_not_called()
@@ -493,19 +649,35 @@ Task info: weburl/taskinfo?taskID=1
     @mock.patch('koji_cli.activate_session')
     @mock.patch('koji_cli._running_in_bg', return_value=False)
     @mock.patch('koji_cli.watch_tasks', return_value=0)
-    def test_handle_chain_build_nowait(self, watch_tasks_mock, running_in_bg_mock,
-                                       activate_session_mock, stdout):
+    def test_handle_chain_build_nowait(
+            self,
+            watch_tasks_mock,
+            running_in_bg_mock,
+            activate_session_mock,
+            stdout):
         target = 'target'
         dest_tag = 'dest_tag'
         dest_tag_id = 2
         build_tag = 'build_tag'
         build_tag_id = 3
-        target_info = {'dest_tag': dest_tag_id, 'dest_tag_name': dest_tag, 'build_tag': build_tag_id,
-                       'build_tag_name': build_tag}
+        target_info = {
+            'dest_tag': dest_tag_id,
+            'dest_tag_name': dest_tag,
+            'build_tag': build_tag_id,
+            'build_tag_name': build_tag}
         dest_tag_info = {'id': 2, 'name': dest_tag, 'locked': False}
         tag_tree = [{'parent_id': 2}, {'parent_id': 4}, {'parent_id': 5}]
-        source_args = ['http://scm1', ':', 'http://scm2', 'http://scm3', 'n-v-r-1', ':', 'n-v-r-2', 'n-v-r-3']
-        sources = [['http://scm1'], ['http://scm2', 'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
+        source_args = [
+            'http://scm1',
+            ':',
+            'http://scm2',
+            'http://scm3',
+            'n-v-r-1',
+            ':',
+            'n-v-r-2',
+            'n-v-r-3']
+        sources = [['http://scm1'], ['http://scm2',
+                                     'http://scm3', 'n-v-r-1'], ['n-v-r-2', 'n-v-r-3']]
         task_id = 1
         args = ['--nowait', target] + source_args
         priority = None
@@ -528,7 +700,8 @@ Task info: weburl/taskinfo?taskID=1
         self.session.getBuildTarget.assert_called_once_with(target)
         self.session.getTag.assert_called_once_with(dest_tag_id, strict=True)
         self.session.getFullInheritance.assert_called_once_with(build_tag_id)
-        self.session.chainBuild.assert_called_once_with(sources, target, priority=priority)
+        self.session.chainBuild.assert_called_once_with(
+            sources, target, priority=priority)
         running_in_bg_mock.assert_called_once()
         self.session.logout.assert_not_called()
         watch_tasks_mock.assert_not_called()
diff --git a/tests/test_cli/test_import_comps.py b/tests/test_cli/test_import_comps.py
index 85a1773..c1c70b0 100644
--- a/tests/test_cli/test_import_comps.py
+++ b/tests/test_cli/test_import_comps.py
@@ -351,12 +351,20 @@ def generate_out_calls():
     comps_file = path + '/data/comps-example.xml'
     stdout_file = path + '/data/comps-example.yumcomps.out'
     calls_file = path + '/data/comps-example.yumcomps.calls'
-    _generate_out_calls(cli._import_comps_alt, comps_file, stdout_file, calls_file)
+    _generate_out_calls(
+        cli._import_comps_alt,
+        comps_file,
+        stdout_file,
+        calls_file)
 
     comps_file = path + '/data/comps-sample.xml'
     stdout_file = path + '/data/comps-sample.yumcomps.out'
     calls_file = path + '/data/comps-sample.yumcomps.calls'
-    _generate_out_calls(cli._import_comps_alt, comps_file, stdout_file, calls_file)
+    _generate_out_calls(
+        cli._import_comps_alt,
+        comps_file,
+        stdout_file,
+        calls_file)
 
 
 if __name__ == '__main__':
diff --git a/tests/test_cli/test_running_in_bg.py b/tests/test_cli/test_running_in_bg.py
index ec669e7..db5bc3f 100644
--- a/tests/test_cli/test_running_in_bg.py
+++ b/tests/test_cli/test_running_in_bg.py
@@ -8,6 +8,7 @@ cli = loadcli.cli
 
 
 class TestRunningInBg(unittest.TestCase):
+
     @mock.patch('koji_cli.os')
     def test_running_in_bg(self, os_mock):
         os_mock.isatty.return_value = False
diff --git a/tests/test_cli/test_unique_path.py b/tests/test_cli/test_unique_path.py
index 528b882..f203581 100644
--- a/tests/test_cli/test_unique_path.py
+++ b/tests/test_cli/test_unique_path.py
@@ -9,8 +9,12 @@ class TestUniquePath(unittest.TestCase):
 
     def test_unique_path(self):
         for i in range(1000):
-            self.assertNotEqual(cli._unique_path('prefix'), cli._unique_path('prefix'))
-            self.assertRegexpMatches(cli._unique_path('prefix'), '^prefix/\d{10}\.\d{1,6}\.[a-zA-Z]{8}$')
+            self.assertNotEqual(
+                cli._unique_path('prefix'),
+                cli._unique_path('prefix'))
+            self.assertRegexpMatches(
+                cli._unique_path('prefix'),
+                '^prefix/\d{10}\.\d{1,6}\.[a-zA-Z]{8}$')
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_cli/test_upload_progress_callback.py b/tests/test_cli/test_upload_progress_callback.py
index f0e1fe7..f330dd7 100644
--- a/tests/test_cli/test_upload_progress_callback.py
+++ b/tests/test_cli/test_upload_progress_callback.py
@@ -35,10 +35,11 @@ class TestUploadProgressCallBack(unittest.TestCase):
         cli._progress_callback(12300, 234000, 5670, 80, 900)
         cli._progress_callback(45600, 234000, 5670, 0, 900)
         cli._progress_callback(234000, 234000, 5670, 80, 900)
-        self.assertMultiLineEqual(stdout.getvalue(),
-                                  '[=                                   ]  05% 00:15:00  12.01 KiB    70.88 B/sec\r'
-                                  '[=======                             ]  19% 00:15:00  44.53 KiB        - B/sec\r'
-                                  '[====================================] 100% 00:15:00 228.52 KiB   260.00 B/sec\r')
+        self.assertMultiLineEqual(
+            stdout.getvalue(),
+            '[=                                   ]  05% 00:15:00  12.01 KiB    70.88 B/sec\r'
+            '[=======                             ]  19% 00:15:00  44.53 KiB        - B/sec\r'
+            '[====================================] 100% 00:15:00 228.52 KiB   260.00 B/sec\r')
 
 
 if __name__ == '__main__':

From 096a6b9ea0af4c35a0bf5340afaac1bb4668f2aa Mon Sep 17 00:00:00 2001
From: Yuming Zhu <yzhu@redhat.com>
Date: Nov 14 2016 03:17:34 +0000
Subject: [PATCH 3/4] ut: koji.utils - cases for related Maven build utils


---

diff --git a/koji/util.py b/koji/util.py
index 61599d9..4d47d55 100644
--- a/koji/util.py
+++ b/koji/util.py
@@ -572,7 +572,6 @@ def parse_maven_params(confs, chain=False, scratch=False):
         conf_fd.close()
     builds = {}
     for package in config.sections():
-        params = {}
         buildtype = 'maven'
         if config.has_option(package, 'type'):
             buildtype = config.get(package, 'type')
diff --git a/tests/data/maven/bad_empty_config.ini b/tests/data/maven/bad_empty_config.ini
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/data/maven/bad_empty_config.ini
diff --git a/tests/data/maven/bad_scmurl_config.ini b/tests/data/maven/bad_scmurl_config.ini
new file mode 100644
index 0000000..5e8bb9c
--- /dev/null
+++ b/tests/data/maven/bad_scmurl_config.ini
@@ -0,0 +1,16 @@
+[pkg]
+type=maven
+patches=patchurl
+specfile=specfile
+goals=goal1 goal2
+profiles=profile1 profile2
+packages=pkg1 pkg2
+jvm_options=--opt1 --opt2=val
+maven_options=--opt1 --opt2=val
+properties: p1=1
+            p2
+            p3=ppp3
+envs:e1=1
+     e2=2
+buildrequires=r1 r2
+otheropts=others
\ No newline at end of file
diff --git a/tests/data/maven/bad_type_config.ini b/tests/data/maven/bad_type_config.ini
new file mode 100644
index 0000000..a26f990
--- /dev/null
+++ b/tests/data/maven/bad_type_config.ini
@@ -0,0 +1,17 @@
+[pkg]
+type=other
+scmurl=scmurl
+patches=patchurl
+specfile=specfile
+goals=goal1 goal2
+profiles=profile1 profile2
+packages=pkg1 pkg2
+jvm_options=--opt1 --opt2=val
+maven_options=--opt1 --opt2=val
+properties: p1=1
+            p2
+            p3=ppp3
+envs:e1=1
+     e2=2
+buildrequires=r1 r2
+otheropts=others
\ No newline at end of file
diff --git a/tests/data/maven/bad_wrapper_config.ini b/tests/data/maven/bad_wrapper_config.ini
new file mode 100644
index 0000000..7bd8050
--- /dev/null
+++ b/tests/data/maven/bad_wrapper_config.ini
@@ -0,0 +1,17 @@
+[pkg]
+type=wrapper
+scmurl=scmurl
+patches=patchurl
+specfile=specfile
+goals=goal1 goal2
+profiles=profile1 profile2
+packages=pkg1 pkg2
+jvm_options=--opt1 --opt2=val
+maven_options=--opt1 --opt2=val
+properties: p1=1
+            p2
+            p3=ppp3
+envs:e1=1
+     e2=2
+buildrequires=r1 r2
+otheropts=others
\ No newline at end of file
diff --git a/tests/data/maven/config.ini b/tests/data/maven/config.ini
new file mode 100644
index 0000000..1bc3511
--- /dev/null
+++ b/tests/data/maven/config.ini
@@ -0,0 +1,52 @@
+[pkg1]
+scmurl=scmurl
+patches=patchurl
+specfile=specfile
+goals=goal1 goal2
+profiles=profile1 profile2
+packages=pkg1 pkg2
+jvm_options=--opt1 --opt2=val
+maven_options=--opt1 --opt2=val
+properties: p1=1
+            p2
+            p3=ppp3
+envs:e1=1
+     e2=2
+buildrequires=r1 r2
+otheropts=others
+
+[pkg2]
+type=maven
+scmurl=scmurl
+patches=patchurl
+specfile=specfile
+goals=goal1 goal2
+profiles=profile1 profile2
+packages=pkg1 pkg2
+jvm_options=--opt1 --opt2=val
+maven_options=--opt1 --opt2=val
+properties: p1=1
+            p2
+            p3=ppp3
+envs:e1=1
+     e2=2
+buildrequires=r1 r2
+otheropts=others
+
+[pkg3]
+type=wrapper
+scmurl=scmurl
+patches=patchurl
+specfile=specfile
+goals=goal1 goal2
+profiles=profile1 profile2
+packages=pkg1 pkg2
+jvm_options=--opt1 --opt2=val
+maven_options=--opt1 --opt2=val
+properties: p1=1
+            p2
+            p3=ppp3
+envs:e1=1
+     e2=2
+buildrequires=r1
+otheropts=others
diff --git a/tests/data/maven/good_config.ini b/tests/data/maven/good_config.ini
new file mode 100644
index 0000000..e45861c
--- /dev/null
+++ b/tests/data/maven/good_config.ini
@@ -0,0 +1,16 @@
+[pkg4]
+scmurl=scmurl
+patches=patchurl
+specfile=specfile
+goals=goal1 goal2
+profiles=profile1 profile2
+packages=pkg1 pkg2
+jvm_options=--opt1 --opt2=val
+maven_options=--opt1 --opt2=val
+properties: p1=1
+            p2
+            p3=ppp3
+envs:e1=1
+     e2=2
+buildrequires=r1 r2
+otheropts=others
\ No newline at end of file
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 68bec02..1db7eb9 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,6 +1,10 @@
 import mock
 import unittest
+from mock import call
 
+import os
+import optparse
+import ConfigParser
 import koji
 import koji.util
 
@@ -77,7 +81,6 @@ class MiscFunctionTestCase(unittest.TestCase):
         islink.assert_called_once_with(dst)
         move.assert_not_called()
 
-
     @mock.patch('urllib2.urlopen')
     @mock.patch('tempfile.TemporaryFile')
     @mock.patch('shutil.copyfileobj')
@@ -92,7 +95,7 @@ class MiscFunctionTestCase(unittest.TestCase):
         path = 'relative/file/path'
         url = 'http://example.com/koji/relative/file/path'
 
-        #using topurl, no tempfile
+        # using topurl, no tempfile
         fo = koji.openRemoteFile(path, topurl)
         m_urlopen.assert_called_once_with(url)
         m_urlopen.return_value.close.assert_called_once()
@@ -104,7 +107,7 @@ class MiscFunctionTestCase(unittest.TestCase):
         for m in mocks:
             m.reset_mock()
 
-        #using topurl + tempfile
+        # using topurl + tempfile
         tempdir = '/tmp/koji/1234'
         fo = koji.openRemoteFile(path, topurl, tempdir=tempdir)
         m_urlopen.assert_called_once_with(url)
@@ -117,7 +120,7 @@ class MiscFunctionTestCase(unittest.TestCase):
         for m in mocks:
             m.reset_mock()
 
-        #using topdir
+        # using topdir
         topdir = '/mnt/mykojidir'
         filename = '/mnt/mykojidir/relative/file/path'
         fo = koji.openRemoteFile(path, topdir=topdir)
@@ -135,3 +138,308 @@ class MiscFunctionTestCase(unittest.TestCase):
             koji.openRemoteFile(path)
         for m in mocks:
             m.assert_not_called()
+
+
+class MavenUtilTestCase(unittest.TestCase):
+    """Test maven relative functions"""
+    maxDiff = None
+
+    def test_maven_config_opt_adapter(self):
+        """Test class MavenConfigOptAdapter"""
+        conf = mock.MagicMock()
+        section = 'section'
+        adapter = koji.util.MavenConfigOptAdapter(conf, section)
+        self.assertIs(adapter._conf, conf)
+        self.assertIs(adapter._section, section)
+        conf.has_option.return_value = True
+        adapter.goals
+        adapter.properties
+        adapter.someattr
+        conf.has_option.return_value = False
+        with self.assertRaises(AttributeError) as cm:
+            adapter.noexistsattr
+        self.assertEquals(cm.exception.args[0], 'noexistsattr')
+        self.assertEquals(conf.mock_calls, [call.has_option(section, 'goals'),
+                                            call.get(section, 'goals'),
+                                            call.get().split(),
+                                            call.has_option(section, 'properties'),
+                                            call.get(section, 'properties'),
+                                            call.get().splitlines(),
+                                            call.has_option(section, 'someattr'),
+                                            call.get('section', 'someattr'),
+                                            call.has_option(section, 'noexistsattr')])
+
+    def test_maven_opts(self):
+        """Test maven_opts function"""
+        values = optparse.Values({
+            'scmurl': 'scmurl',
+            'patches': 'patchurl',
+            'specfile': 'specfile',
+            'goals': ['goal1', 'goal2'],
+            'profiles': ['profile1', 'profile2'],
+            'packages': ['pkg1', 'pkg2'],
+            'jvm_options': ['--opt1', '--opt2=val'],
+            'maven_options': ['--opt1', '--opt2=val'],
+            'properties': ['p1=1', 'p2', 'p3=ppp3'],
+            'envs': ['e1=1', 'e2=2'],
+            'buildrequires': ['r1', 'r2'],
+            'otheropts': 'others'})
+        self.assertEqual(koji.util.maven_opts(values), {
+            'scmurl': 'scmurl',
+            'patches': 'patchurl',
+            'specfile': 'specfile',
+            'goals': ['goal1', 'goal2'],
+            'profiles': ['profile1', 'profile2'],
+            'packages': ['pkg1', 'pkg2'],
+            'jvm_options': ['--opt1', '--opt2=val'],
+            'maven_options': ['--opt1', '--opt2=val'],
+            'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+            'envs': {'e1': '1', 'e2': '2'}})
+        self.assertEqual(koji.util.maven_opts(values, chain=True, scratch=True), {
+            'scmurl': 'scmurl',
+            'patches': 'patchurl',
+            'specfile': 'specfile',
+            'goals': ['goal1', 'goal2'],
+            'profiles': ['profile1', 'profile2'],
+            'packages': ['pkg1', 'pkg2'],
+            'jvm_options': ['--opt1', '--opt2=val'],
+            'maven_options': ['--opt1', '--opt2=val'],
+            'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+            'envs': {'e1': '1', 'e2': '2'},
+            'buildrequires': ['r1', 'r2']})
+        self.assertEqual(koji.util.maven_opts(values, chain=False, scratch=True), {
+            'scmurl': 'scmurl',
+            'patches': 'patchurl',
+            'specfile': 'specfile',
+            'goals': ['goal1', 'goal2'],
+            'profiles': ['profile1', 'profile2'],
+            'packages': ['pkg1', 'pkg2'],
+            'jvm_options': ['--opt1', '--opt2=val'],
+            'maven_options': ['--opt1', '--opt2=val'],
+            'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+            'envs': {'e1': '1', 'e2': '2'},
+            'scratch': True})
+        values = optparse.Values({'envs': ['e1']})
+        with self.assertRaises(ValueError) as cm:
+            koji.util.maven_opts(values)
+        self.assertEqual(
+            cm.exception.args[0],
+            "Environment variables must be in NAME=VALUE format")
+
+    def test_maven_params(self):
+        """Test maven_params function"""
+        config = self._read_conf('/data/maven/config.ini')
+        self.assertEqual(koji.util.maven_params(config, 'pkg1'), {
+            'scmurl': 'scmurl',
+            'patches': 'patchurl',
+            'specfile': 'specfile',
+            'goals': ['goal1', 'goal2'],
+            'profiles': ['profile1', 'profile2'],
+            'packages': ['pkg1', 'pkg2'],
+            'jvm_options': ['--opt1', '--opt2=val'],
+            'maven_options': ['--opt1', '--opt2=val'],
+            'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+            'envs': {'e1': '1', 'e2': '2'}})
+
+    def test_wrapper_params(self):
+        """Test wrapper_params function"""
+        config = self._read_conf('/data/maven/config.ini')
+        self.assertEqual(koji.util.wrapper_params(config, 'pkg2'), {
+            'type': 'maven',
+            'scmurl': 'scmurl',
+            'buildrequires': ['r1', 'r2'],
+            'create_build': True})
+        self.assertEqual(koji.util.wrapper_params(config, 'pkg2', scratch=True), {
+            'type': 'maven',
+            'scmurl': 'scmurl',
+            'buildrequires': ['r1', 'r2']})
+
+    def test_parse_maven_params(self):
+        """Test parse_maven_params function"""
+        path = os.path.dirname(__file__)
+        # single conf file, and chain=False, scratch=False
+        confs = path + '/data/maven/config.ini'
+        self.assertEqual(koji.util.parse_maven_params(confs), {
+            'pkg1': {
+                'scmurl': 'scmurl',
+                'patches': 'patchurl',
+                'specfile': 'specfile',
+                'goals': ['goal1', 'goal2'],
+                'profiles': ['profile1', 'profile2'],
+                'packages': ['pkg1', 'pkg2'],
+                'jvm_options': ['--opt1', '--opt2=val'],
+                'maven_options': ['--opt1', '--opt2=val'],
+                'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+                'envs': {'e1': '1', 'e2': '2'}},
+            'pkg2': {
+                'scmurl': 'scmurl',
+                'patches': 'patchurl',
+                'specfile': 'specfile',
+                'goals': ['goal1', 'goal2'],
+                'profiles': ['profile1', 'profile2'],
+                'packages': ['pkg1', 'pkg2'],
+                'jvm_options': ['--opt1', '--opt2=val'],
+                'maven_options': ['--opt1', '--opt2=val'],
+                'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+                'envs': {'e1': '1', 'e2': '2'}},
+            'pkg3': {
+                'type': 'wrapper',
+                'scmurl': 'scmurl',
+                'buildrequires': ['r1'],
+                'create_build': True}})
+
+        # multiple conf file, and chain=True, scratch=False
+        confs = [confs, path + '/data/maven/good_config.ini']
+        self.assertEqual(koji.util.parse_maven_params(confs, chain=True), {
+            'pkg1': {
+                'scmurl': 'scmurl',
+                'patches': 'patchurl',
+                'specfile': 'specfile',
+                'goals': ['goal1', 'goal2'],
+                'profiles': ['profile1', 'profile2'],
+                'packages': ['pkg1', 'pkg2'],
+                'jvm_options': ['--opt1', '--opt2=val'],
+                'maven_options': ['--opt1', '--opt2=val'],
+                'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+                'envs': {'e1': '1', 'e2': '2'},
+                'buildrequires': ['r1', 'r2']},
+            'pkg2': {
+                'scmurl': 'scmurl',
+                'patches': 'patchurl',
+                'specfile': 'specfile',
+                'goals': ['goal1', 'goal2'],
+                'profiles': ['profile1', 'profile2'],
+                'packages': ['pkg1', 'pkg2'],
+                'jvm_options': ['--opt1', '--opt2=val'],
+                'maven_options': ['--opt1', '--opt2=val'],
+                'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+                'envs': {'e1': '1', 'e2': '2'},
+                'buildrequires': ['r1', 'r2']},
+            'pkg3': {
+                'type': 'wrapper',
+                'scmurl': 'scmurl',
+                'buildrequires': ['r1'],
+                'create_build': True},
+            'pkg4': {
+                'scmurl': 'scmurl',
+                'patches': 'patchurl',
+                'specfile': 'specfile',
+                'goals': ['goal1', 'goal2'],
+                'profiles': ['profile1', 'profile2'],
+                'packages': ['pkg1', 'pkg2'],
+                'jvm_options': ['--opt1', '--opt2=val'],
+                'maven_options': ['--opt1', '--opt2=val'],
+                'properties': {'p2': None, 'p3': 'ppp3', 'p1': '1'},
+                'envs': {'e1': '1', 'e2': '2'},
+                'buildrequires': ['r1', 'r2']},
+        })
+
+        # bad conf file - type=wrapper and len(params.get('buildrequires')!=1)
+        confs = path + '/data/maven/bad_wrapper_config.ini'
+        with self.assertRaises(ValueError) as cm:
+            koji.util.parse_maven_params(confs)
+        self.assertEqual(
+            cm.exception.args[0],
+            'A wrapper-rpm must depend on exactly one package')
+
+        # bad conf file - type is neither 'maven' nor 'wrapper')
+        confs = path + '/data/maven/bad_type_config.ini'
+        with self.assertRaises(ValueError) as cm:
+            koji.util.parse_maven_params(confs)
+        self.assertEqual(cm.exception.args[0], 'Unsupported build type: other')
+
+        # bad conf file - no scmurl param
+        confs = path + '/data/maven/bad_scmurl_config.ini'
+        with self.assertRaises(ValueError) as cm:
+            koji.util.parse_maven_params(confs)
+        self.assertEqual(
+            cm.exception.args[0],
+            'pkg is missing the scmurl parameter')
+
+        # bad conf file - empty dict returned
+        confs = path + '/data/maven/bad_empty_config.ini'
+        with self.assertRaises(ValueError) as cm:
+            koji.util.parse_maven_params(confs)
+        self.assertEqual(
+            cm.exception.args[0],
+            'No sections found in: %s' %
+            confs)
+
+    def test_parse_maven_param(self):
+        """Test parse_maven_param function"""
+        path = os.path.dirname(__file__)
+        # single conf file, and chain=False, scratch=False
+        confs = path + '/data/maven/config.ini'
+        with mock.patch('koji.util.parse_maven_params',
+                        return_value={
+                            'pkg1': {'sth': 'pkg1'},
+                            'pkg2': {'sth': 'pkg2'},
+                            'pkg3': {'sth': 'pkg3'}}):
+            self.assertEqual(
+                koji.util.parse_maven_param(
+                    confs, section='pkg1'), {
+                    'pkg1': {
+                        'sth': 'pkg1'}})
+            with self.assertRaises(ValueError) as cm:
+                koji.util.parse_maven_param(confs, section='pkg4')
+            self.assertEqual(
+                cm.exception.args[0],
+                'Section pkg4 does not exist in: %s' %
+                confs)
+            with self.assertRaises(ValueError) as cm:
+                koji.util.parse_maven_param(confs)
+            self.assertEqual(
+                cm.exception.args[0],
+                'Multiple sections in: %s, you must specify the section' %
+                confs)
+        with mock.patch('koji.util.parse_maven_params', return_value={
+                'pkg': {'sth': 'pkg'}}):
+            self.assertEqual(koji.util.parse_maven_param(confs),
+                             {'pkg': {'sth': 'pkg'}})
+
+    def test_parse_maven_chain(self):
+        """Test parse_maven_chain function"""
+        path = os.path.dirname(__file__)
+        confs = path + '/data/maven/config.ini'
+        with mock.patch('koji.util.parse_maven_params',
+                        return_value={
+                            'pkg1': {'buildrequires': ['pkg2', 'pkg3']},
+                            'pkg2': {'buildrequires': ['pkg3']},
+                            'pkg3': {'sth': 'sth'}}):
+            self.assertEqual(koji.util.parse_maven_chain(confs),
+                             {'pkg1': {'buildrequires': ['pkg2', 'pkg3']},
+                              'pkg2': {'buildrequires': ['pkg3']},
+                              'pkg3': {'sth': 'sth'}})
+        # circular deps
+        with mock.patch('koji.util.parse_maven_params',
+                        return_value={
+                            'pkg1': {'buildrequires': ['pkg2', 'pkg3']},
+                            'pkg2': {'buildrequires': ['pkg3']},
+                            'pkg3': {'buildrequires': ['pkg1']}}):
+            with self.assertRaises(ValueError) as cm:
+                koji.util.parse_maven_chain(confs)
+            self.assertEqual(
+                cm.exception.args[0],
+                'No possible build order, missing/circular dependencies')
+        # missing deps
+        with mock.patch('koji.util.parse_maven_params',
+                        return_value={
+                            'pkg1': {'buildrequires': ['pkg2', 'pkg3']},
+                            'pkg2': {'buildrequires': ['pkg3']},
+                            'pkg3': {'buildrequires': ['pkg4']}}):
+            with self.assertRaises(ValueError) as cm:
+                koji.util.parse_maven_chain(confs)
+            self.assertEqual(
+                cm.exception.args[0],
+                'No possible build order, missing/circular dependencies')
+
+    def _read_conf(self, cfile):
+        config = ConfigParser.ConfigParser()
+        path = os.path.dirname(__file__)
+        with open(path + cfile, 'r') as conf_file:
+            config.readfp(conf_file)
+        return config
+
+
+if __name__ == '__main__':
+    unittest.main()

From 1f48b8cc2749adcca2fe41f609887ef1f5dab50e Mon Sep 17 00:00:00 2001
From: Yuming Zhu <yzhu@redhat.com>
Date: Nov 14 2016 07:03:11 +0000
Subject: [PATCH 4/4] ut: test case for koji.util.tsort()


---

diff --git a/tests/test_utils.py b/tests/test_utils.py
index 1db7eb9..88cd1aa 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -433,6 +433,45 @@ class MavenUtilTestCase(unittest.TestCase):
                 cm.exception.args[0],
                 'No possible build order, missing/circular dependencies')
 
+    def test_tsort(self):
+        # success, one path
+        parts = {
+            'p1': {'p2', 'p3'},
+            'p2': {'p3'},
+            'p3': set()
+        }
+        self.assertEqual(koji.util.tsort(parts),
+                         [{'p3'}, {'p2'}, {'p1'}])
+        # success, multi-path
+        parts = {
+            'p1': {'p2'},
+            'p2': {'p4'},
+            'p3': {'p4'},
+            'p4': set(),
+            'p5': set()
+        }
+        self.assertEqual(koji.util.tsort(parts),
+                         [{'p4', 'p5'}, {'p2', 'p3'}, {'p1'}])
+        # failed, missing child 'p4'
+        parts = {
+            'p1': {'p2'},
+            'p2': {'p3'},
+            'p3': {'p4'}
+        }
+        with self.assertRaises(ValueError) as cm:
+            koji.util.tsort(parts)
+        self.assertEqual(cm.exception.args[0], 'total ordering not possible')
+
+        # failed, circular
+        parts = {
+            'p1': {'p2'},
+            'p2': {'p3'},
+            'p3': {'p1'}
+        }
+        with self.assertRaises(ValueError) as cm:
+            koji.util.tsort(parts)
+        self.assertEqual(cm.exception.args[0], 'total ordering not possible')
+
     def _read_conf(self, cfile):
         config = ConfigParser.ConfigParser()
         path = os.path.dirname(__file__)

