Add unit tests for koji commands

This commit is contained in:
Franz Chih-Ping Hsieh 2017-11-20 11:24:22 -05:00 committed by Mike McLean
parent d331fdfa11
commit f16f26be63
17 changed files with 3502 additions and 16 deletions

View file

@ -0,0 +1,92 @@
from __future__ import absolute_import
import mock
import six
import unittest
from koji_cli.commands import handle_add_tag
from . import utils
class TestAddTag(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s add-tag [options] name
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_add_tag(
self,
activate_session_mock,
stdout):
"""Test handle_add_tag function"""
session = mock.MagicMock()
options = mock.MagicMock()
# Case 1. no argument error
expected = self.format_error_message(
"Please specify a name for the tag")
self.assert_system_exit(
handle_add_tag,
options,
session,
[],
stderr=expected,
activate_session=None)
# Case 2. not admin account
expected = "This action requires admin privileges\n"
session.hasPerm.return_value = None
handle_add_tag(options, session, ['test-tag'])
self.assert_console_message(stdout, expected)
# Case 3. options test
arguments = ['test-tag',
'--parent', 'parent',
'--arch', 'x86_64',
'--maven-support', '--include-all']
# extra fields,
arguments += ['--extra', 'mock.package_manager=dnf',
'--extra', 'mock.new_chroot=0']
opts = {
'parent': 'parent',
'arches': 'x86_64',
'maven_support': True,
'maven_include_all': True,
'extra':
{
'mock.package_manager': 'dnf',
'mock.new_chroot': 0,
}
}
session.hasPerm.return_value = True
handle_add_tag(options, session, arguments)
session.createTag.assert_called_with('test-tag', **opts)
def test_handle_add_tag_help(self):
self.assert_help(
handle_add_tag,
"""Usage: %s add-tag [options] name
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--parent=PARENT Specify parent
--arches=ARCHES Specify arches
--maven-support Enable creation of Maven repos for this tag
--include-all Include all packages in this tag when generating Maven
repos
-x key=value, --extra=key=value
Set tag extra option
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,118 @@
from __future__ import absolute_import
import mock
import six
import unittest
from mock import call
from koji_cli.commands import handle_disable_host
from . import utils
class TestDisableHost(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_disable_host(
self,
activate_session_mock,
stdout):
"""Test %s function""" % handle_disable_host.__name__
arguments = []
options = mock.MagicMock()
session = mock.MagicMock()
session.getHost.return_value = None
session.disableHost.return_value = True
session.editHost.return_value = True
#
# session.multiCall returns:
#
# [[{'comment': None,
# 'capacity': 2.0,
# 'name': 'kbuilder01',
# 'enabled': True,
# 'arches': 'x86_64',
# 'task_load': 0.0,
# 'ready': False,
# 'user_id': 4,
# 'id': 2, 'description': None}],
# [{'comment': None,
# 'capacity': 2.0,
# 'name': 'kbuilder02' ...}]
#
session.multiCall.return_value = [[None], [None]]
arguments = ['host1', 'host2']
self.assertEqual(1, handle_disable_host(options, session, arguments))
activate_session_mock.assert_called_once()
session.getHost.assert_has_calls([call('host1'), call('host2')])
session.multiCall.assert_called_once()
session.disableHost.assert_not_called()
session.editHost.assert_not_called()
expect = ''
for host in arguments:
expect += "Host %s does not exist\n" % host
expect += "No changes made. Please correct the command line.\n"
self.assert_console_message(stdout, expect)
# reset session mocks
activate_session_mock.reset_mock()
session.multiCall.reset_mock()
session.disableHost.reset_mock()
session.editHost.reset_mock()
session.multiCall.return_value = [
[{'id': 1, 'name': 'host1'}], [{'id': 2, 'name': 'host2'}]
]
arguments = ['host1', 'host2', '--comment', 'disable host test']
handle_disable_host(options, session, arguments)
activate_session_mock.assert_called_once()
session.getHost.assert_has_calls([call('host1'), call('host2')])
self.assertEqual(2, session.multiCall.call_count)
session.disableHost.assert_has_calls([call('host1'), call('host2')])
session.editHost.assert_has_calls(
[call('host1', comment='disable host test'),
call('host2', comment='disable host test')])
self.assert_console_message(stdout, '')
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_disable_host_no_argument(self, activate_session_mock, stdout):
"""Test %s function without arguments""" % handle_disable_host.__name__
options = mock.MagicMock()
session = mock.MagicMock()
session.getHost.return_value = None
session.multiCall.return_value = [[None]]
session.disableHost.return_value = True
session.editHost.return_value = True
handle_disable_host(options, session, [])
activate_session_mock.assert_called_once()
session.getHost.assert_not_called()
session.multiCall.assert_called()
session.disableHost.assert_not_called()
session.editHost.assert_not_called()
self.assert_console_message(stdout, '')
def test_handle_disable_host_help(self):
"""Test %s help message""" % handle_disable_host.__name__
self.assert_help(
handle_disable_host,
"""Usage: %s disable-host [options] hostname ...
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--comment=COMMENT Comment indicating why the host(s) are being disabled
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,118 @@
from __future__ import absolute_import
import mock
import six
import unittest
from mock import call
from koji_cli.commands import handle_enable_host
from . import utils
class TestEnableHost(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_enable_host(
self,
activate_session_mock,
stdout):
"""Test %s function""" % handle_enable_host.__name__
arguments = []
options = mock.MagicMock()
session = mock.MagicMock()
session.getHost.return_value = None
session.enableHost.return_value = True
session.editHost.return_value = True
#
# session.multiCall returns:
#
# [[{'comment': None,
# 'capacity': 2.0,
# 'name': 'kbuilder01',
# 'enabled': True,
# 'arches': 'x86_64',
# 'task_load': 0.0,
# 'ready': False,
# 'user_id': 4,
# 'id': 2, 'description': None}],
# [{'comment': None,
# 'capacity': 2.0,
# 'name': 'kbuilder02' ...}]
#
session.multiCall.return_value = [[None], [None]]
arguments = ['host1', 'host2']
self.assertEqual(1, handle_enable_host(options, session, arguments))
activate_session_mock.assert_called_once()
session.getHost.assert_has_calls([call('host1'), call('host2')])
session.multiCall.assert_called_once()
session.enableHost.assert_not_called()
session.editHost.assert_not_called()
expect = ''
for host in arguments:
expect += "Host %s does not exist\n" % host
expect += "No changes made. Please correct the command line.\n"
self.assert_console_message(stdout, expect)
# reset session mocks
activate_session_mock.reset_mock()
session.multiCall.reset_mock()
session.disableHost.reset_mock()
session.editHost.reset_mock()
session.multiCall.return_value = [
[{'id': 1, 'name': 'host1'}], [{'id': 2, 'name': 'host2'}]
]
arguments = ['host1', 'host2', '--comment', 'disable host test']
handle_enable_host(options, session, arguments)
activate_session_mock.assert_called_once()
session.getHost.assert_has_calls([call('host1'), call('host2')])
self.assertEqual(2, session.multiCall.call_count)
session.enableHost.assert_has_calls([call('host1'), call('host2')])
session.editHost.assert_has_calls(
[call('host1', comment='disable host test'),
call('host2', comment='disable host test')])
self.assert_console_message(stdout, '')
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_enable_host_no_argument(self, activate_session_mock, stdout):
"""Test %s function without arguments""" % handle_enable_host.__name__
options = mock.MagicMock()
session = mock.MagicMock()
session.getHost.return_value = None
session.multiCall.return_value = [[None]]
session.enableHost.return_value = True
session.editHost.return_value = True
handle_enable_host(options, session, [])
activate_session_mock.assert_called_once()
session.getHost.assert_not_called()
session.multiCall.assert_called()
session.enableHost.assert_not_called()
session.editHost.assert_not_called()
self.assert_console_message(stdout, '')
def test_handle_enable_host_help(self):
"""Test %s help message""" % handle_enable_host.__name__
self.assert_help(
handle_enable_host,
"""Usage: %s enable-host [options] hostname ...
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--comment=COMMENT Comment indicating why the host(s) are being enabled
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,690 @@
from __future__ import absolute_import
import mock
import six
import unittest
import koji
import os
from koji_cli.commands import handle_import
from . import utils
class TestImport(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.huburl = "https://%s.local/%shub" % (self.progname, self.progname)
self.md5 = '00112233445566778899aabbccddeeff'
self.fake_srv_dir = '/path/to/server/import'
#
# RPM header example (bash-4.4.12-5.fc26.x86_64.rpm):
# {
# 'sourcepackage': None,
# 'name': 'bash',
# 'sigmd5': 'J\x15\xca\x94\xb1\xacY\xd5\xef\x9f\xc6\xd5\n\xd7?>',
# 'epoch': None,
# 'version': '4.4.12',
# 'release': '5.fc26',
# 'sourcerpm': 'bash-4.4.12-5.fc26.src.rpm',
# 'arch': 'x86_64'
# }
#
# SRPM header example (bash-4.4.12-5.fc26.src.rpm):
# {
# 'sourcepackage': 1,
# 'name': 'bash',
# 'sigmd5': '\x8a\x17\x05\xe8k\xef\x15\x16V[\x02\x9cs\xab\x7f\xdd',
# 'epoch': None,
# 'version': '4.4.12',
# 'release': '5.fc26',
# 'sourcerpm': None,
# 'arch': 'x86_64'
# }
self.srpm_header = {
'sourcepackage': 1,
'name': 'bash',
'sigmd5': bytearray.fromhex(self.md5),
'epoch': None,
'version': '4.4.12',
'release': '5.fc26',
'sourcerpm': None,
'arch': 'x86_64'
}
self.rpm_header = {
'sourcepackage': None,
'name': 'bash',
'sigmd5': bytearray.fromhex(self.md5),
'epoch': None,
'version': '4.4.12',
'release': '5.fc26',
'sourcerpm': 'bash-4.4.12-5.fc26.src.rpm',
'arch': 'x86_64'
}
# koji.BUILD_STATES
self.bstate = {
'BUILDING': 0,
'COMPLETE': 1,
'DELETED': 2,
'FAILED': 3,
'CANCELED': 4,
}
self.error_format = """Usage: %s import [options] package [package...]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
def __do_import_test(self, options, session, arguments, **kwargs):
expected = kwargs.get('expected', None)
rpm_header = kwargs.get('rpm_header', {})
fake_srv_path = kwargs.get('srv_path', '/path/to/server/import')
upload_rpm_mock = kwargs.get('upload_rpm_mock', session.uploadWrapper)
with mock.patch('koji.get_header_fields') as get_header_fields_mock, \
mock.patch('koji_cli.commands._unique_path') as unique_path_mock, \
mock.patch('koji_cli.commands.activate_session') as activate_session_mock, \
mock.patch('sys.stdout', new_callable=six.StringIO) as stdout, \
upload_rpm_mock:
get_header_fields_mock.return_value = rpm_header
unique_path_mock.return_value = fake_srv_path
handle_import(options, session, arguments)
# check output message
self.assert_console_message(stdout, expected)
# check mock calls
activate_session_mock.assert_called_with(session, options)
get_header_fields_mock.assert_called_with(
arguments[0],
('name', 'version', 'release', 'epoch',
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
)
session.getRPM.assert_called_with(
dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name'])
)
unique_path_mock.assert_called_with('cli-import')
upload_rpm_mock.assert_called_with(arguments[0], self.fake_srv_dir)
session.importRPM.assert_called_with(
self.fake_srv_dir, os.path.basename(arguments[0]))
# reset for next test
activate_session_mock.reset_mock()
get_header_fields_mock.reset_mock()
unique_path_mock.reset_mock()
upload_rpm_mock.reset_mock()
session.getRPM.reset_mock()
session.importRPM.reset_mock()
def __skip_import_test(self, options, session, arguments, **kwargs):
expected = kwargs.get('expected', None)
rpm_header = kwargs.get('rpm_header', {})
with mock.patch('koji.get_header_fields') as get_header_fields_mock, \
mock.patch('koji_cli.commands.activate_session') as activate_session_mock, \
mock.patch('sys.stdout', new_callable=six.StringIO) as stdout:
get_header_fields_mock.return_value = rpm_header
handle_import(options, session, arguments)
# check output message
self.assert_console_message(stdout, expected)
# check mock calls
activate_session_mock.assert_called_with(session, options)
get_header_fields_mock.assert_called_with(
arguments[0],
('name', 'version', 'release', 'epoch',
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
)
session.getRPM.assert_called_with(
dict((k, rpm_header.get(k, ''))
for k in ['release', 'version', 'arch', 'name'])
)
session.uploadWrapper.assert_not_called()
session.importRPM.assert_not_called()
def test_handle_import_src_rpm_import_with_no_exist_build(self):
"""Test handle_import source RPM import.
No build is on the server case,
this tests are focusing on do_import() function coverage.
"""
arguments = ['/path/to/bash-4.4.12-5.fc26.src.rpm', '--src-epoch', 'None']
options = mock.MagicMock()
session = mock.MagicMock()
# No exist build test case
# import general case
session.getBuild.return_value = None
session.getRPM.return_value = None
expected = "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
self.__do_import_test(
options, session, arguments,
rpm_header=self.srpm_header, expected=expected)
# import error case
session.importRPM.side_effect = koji.GenericError('fake-import-error')
expected = "uploading %s... done\n" % arguments[0]
expected += "importing %s... \n" % arguments[0]
expected += "Error importing: fake-import-error\n"
self.__do_import_test(
options, session, arguments,
rpm_header=self.srpm_header, expected=expected)
# import with --link (hardlink) option
session.importRPM.side_effect = None
expected = "importing %s... done\n" % arguments[0]
self.__do_import_test(
options, session, arguments + ['--link'],
upload_rpm_mock=mock.patch('koji_cli.commands.linked_upload').start(),
rpm_header=self.srpm_header, expected=expected)
session.uploadWrapper.assert_not_called()
def test_handle_import_src_rpm_import_with_exist_build(self):
"""Test handle_import source RPM import with exist build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.src.rpm']
options = mock.MagicMock()
session = mock.MagicMock()
false_md5 = 'ffeeddccbbaa99887766554433221100'
#
# getBuild return example (bash-4.4.12-5.fc26.src.rpm)
# {
# 'package_name': 'bash',
# 'extra': None,
# 'creation_time': '2017-11-08 18:56:31.359440',
# 'completion_time': '2017-11-08 18:56:31.359440',
# 'package_id': 1,
# 'id': 1,
# 'build_id': 1,
# 'state': 1,
# 'source': None,
# 'epoch': None,
# 'version': '4.4.12',
# 'completion_ts': 1510167391.35944,
# 'owner_id': 1,
# 'owner_name': 'kojiadmin',
# 'nvr': 'bash-4.4.12-5.fc26',
# 'start_time': '2017-11-08 18:56:31.359440',
# 'creation_event_id': 81,
# 'start_ts': 1510167391.35944,
# 'volume_id': 0,
# 'creation_ts': 1510167391.35944,
# 'name': 'bash',
# 'task_id': None,
# 'volume_name': 'DEFAULT',
# 'release': '5.fc26'
# }
#
#
# getRPM return example:
# {
# 'build_id': 1,
# 'name': 'bash',
# 'extra': None,
# 'external_repo_id': 0,
# 'buildtime': 1496119944,
# 'id': 1,
# 'epoch': None,
# 'version': '4.4.12',
# 'buildroot_id': None,
# 'metadata_only': False,
# 'release': '5.fc26',
# 'arch': 'src',
# 'payloadhash': '8a1705e86bef1516565b029c73ab7fdd',
# 'external_repo_name': 'INTERNAL',
# 'size': 9462614
# }
#
# Case 1: build exists and status is 'COMPLETE', md5 matched
# reseult: import skipped
session.getBuild.return_value = {'state': self.bstate['COMPLETE']}
session.getRPM.return_value = {
'external_repo_id': 0,
'external_repo_name': 'INTERNAL',
'payloadhash': self.md5
}
expected = "RPM already imported: %s\n" % arguments[0]
expected += "Skipping import\n"
self.__skip_import_test(
options, session, arguments,
rpm_header=self.srpm_header,
expected=expected)
# Case 2: build exists and status is 'COMPLETE', md5 mismatched
# reseult: import skipped
session.getRPM.return_value['payloadhash'] = false_md5
expected = "WARNING: md5sum mismatch for %s\n" % arguments[0]
expected += " A different rpm with the same name has already been imported\n"
expected += " Existing sigmd5 is %r, your import has %r\n" % (false_md5, self.md5)
expected += "Skipping import\n"
self.__skip_import_test(
options, session, arguments,
rpm_header=self.srpm_header,
expected=expected)
# Case 3: build exists and status is 'COMPLETE', has external_repo_id
# reseult: import will be performed
session.getRPM.return_value['external_repo_id'] = 1
expected = "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
self.__do_import_test(
options, session, arguments,
rpm_header=self.srpm_header,
expected=expected)
nvr = '%(name)s-%(version)s-%(release)s' % self.srpm_header
# Case 4: build exists and the status is FAILED or CANCELED
# without --create-build option.
# result: import skipped
session.getBuild.return_value = {
'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['FAILED']}
session.getRPM.return_value = {
'external_repo_name': 'INTERNAL',
'payloadhash': self.md5
}
expected = "Build %s state is %s. Skipping import\n" % (nvr, 'FAILED')
with mock.patch('koji.get_header_fields') as get_header_fields_mock, \
mock.patch('koji_cli.commands.activate_session') as activate_session_mock, \
mock.patch('sys.stdout', new_callable=six.StringIO) as stdout:
get_header_fields_mock.return_value = self.srpm_header
handle_import(options, session, arguments)
activate_session_mock.assert_called_with(session, options)
self.assert_console_message(stdout, expected)
# Case 5: build exists and the status is FAILED or CANCELED
# with --create-build option.
# result: empty build will be created,
# import will be performed.
expected = "Creating empty build: %s\n" % nvr
expected += "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
session.getBuild.side_effect = [
# simulate build fail case
{'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['FAILED']},
# after calling createEmptyBuild, getBuild should return:
{'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['COMPLETE']},
]
# no RPM info is returned
session.getRPM.return_value = {}
self.__do_import_test(
options, session, arguments + ['--create-build'],
rpm_header=self.srpm_header,
expected=expected)
kwarg = dict((k, self.rpm_header.get(k, None))
for k in ['name', 'version', 'release', 'epoch'])
session.createEmptyBuild.assert_called_with(**kwarg)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji.get_header_fields')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_binary_rpm_import_with_no_build_exist(
self,
activate_session_mock,
get_header_fields_mock,
stderr,
stdout):
"""Test handle_import binary RPM import with no exist build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm']
options = mock.MagicMock()
session = mock.MagicMock()
get_header_fields_mock.return_value = self.rpm_header
nvr = '%(name)s-%(version)s-%(release)s' % get_header_fields_mock.return_value
# Case 1. without exist build on server
# result: abort
session.getBuild.return_value = None
handle_import(options, session, arguments)
expected = "Missing build or srpm: %s\n" % nvr
expected += "Aborting import\n"
self.assert_console_message(stdout, expected)
# Case 2. without exist build on server,
# with --create-build option
# result: import will be performed
session.getBuild.return_value = None
session.getRPM.return_value = None
expected = "Missing build or srpm: %s\n" % nvr
expected += "Creating empty build: %s\n" % nvr
expected += "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
self.__do_import_test(
options, session, arguments + ['--create-build'],
rpm_header=self.rpm_header,
expected=expected)
kwarg = dict((k, self.rpm_header.get(k, None))
for k in ['name', 'version', 'release', 'epoch'])
session.createEmptyBuild.assert_called_with(**kwarg)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_binary_rpm_with_exist_build(
self,
activate_session_mock,
stderr,
stdout):
"""Test handle_import binary RPM import with exist build case."""
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm']
options = mock.MagicMock()
session = mock.MagicMock()
nvr = '%(name)s-%(version)s-%(release)s' % self.rpm_header
# Case 1. have exist build on server, build status is COMPLETE case.
# result: skip import
session.getBuild.return_value = {'state': self.bstate['COMPLETE']}
session.getRPM.return_value = {
'external_repo_name': 'INTERNAL',
'payloadhash': self.md5,
}
expected = "RPM already imported: %s\n" % arguments[0]
expected += "Skipping import\n"
self.__skip_import_test(
options, session, arguments,
rpm_header=self.rpm_header,
expected=expected)
# Case 2. have exist build on server, build status is failed,
# without --create-build case.
# result: import will be skipped
session.getBuild.return_value = {'state': self.bstate['FAILED']}
expected = "Build %s state is %s. Skipping import\n" % (nvr, 'FAILED')
self.__skip_import_test(
options, session, arguments,
rpm_header=self.rpm_header,
expected=expected)
# Case 3. have exist build on server, build status is failed,
# with --create-build,
# result:
# empty build will be created,
# import will be performed.
expected = "Creating empty build: %s\n" % nvr
expected += "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
session.getBuild.side_effect = [
# binary rpm case,
# getBuild is called to check build.
{'state': self.bstate['FAILED']},
# simulate build fail case
{'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['FAILED']},
# after calling createEmptyBuild, getBuild should return:
{'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['COMPLETE']},
]
# no RPM info is returned
session.getRPM.return_value = {}
self.__do_import_test(
options, session, arguments + ['--create-build'],
rpm_header=self.rpm_header,
expected=expected)
kwarg = dict((k, self.rpm_header.get(k, None))
for k in ['name', 'version', 'release', 'epoch'])
session.createEmptyBuild.assert_called_with(**kwarg)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji.get_header_fields')
@mock.patch('koji_cli.commands._unique_path')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_with_test_option(
self,
activate_session_mock,
unique_path_mock,
get_header_fields_mock,
stdout):
"""Test handle_import RPM with --test option"""
options = mock.MagicMock()
session = mock.MagicMock()
nvr = lambda: '%(name)s-%(version)s-%(release)s' % get_header_fields_mock.return_value
# Case 1. SRPM case
# result: skip because of --test
get_header_fields_mock.return_value = self.srpm_header
session.getBuild.return_value = {}
session.getRPM.return_value = {}
arguments = ['/path/to/bash-4.4.12-5.fc26.src.rpm', '--test']
expected = "Test mode -- skipping import for %s\n" % arguments[0]
handle_import(options, session, arguments)
self.assert_console_message(stdout, expected)
unique_path_mock.assert_not_called()
session.importRPM.assert_not_called()
# Case 2. Binary RPM case (need --create-build option)
# result: skip because of --test
session.getBuild.side_effect = [
# binary rpm case,
# getBuild is called to check build.
{'state': self.bstate['FAILED']},
# simulate build fail case
{'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['FAILED']},
]
get_header_fields_mock.return_value = self.rpm_header
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm', '--test', '--create-build']
expected = "Test mode -- would have created empty build: %s\n" % nvr()
expected += "Test mode -- skipping import for %s\n" % arguments[0]
with mock.patch('koji_cli.commands._unique_path') as unique_path_mock:
handle_import(options, session, arguments)
self.assert_console_message(stdout, expected)
unique_path_mock.assert_not_called()
session.createEmptyBuild.assert_not_called()
session.importRPM.assert_not_called()
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji.get_header_fields')
@mock.patch('koji_cli.commands._unique_path')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_with_epoch_option(
self,
activate_session_mock,
unique_path_mock,
get_header_fields_mock,
stdout):
"""Test handle_import RPM with --src-epoch option"""
options = mock.MagicMock()
session = mock.MagicMock()
epoch = "1"
nvr = '%(name)s-%(version)s-%(release)s' % self.rpm_header
# Binary RPM case (need --create-build option)
# result: skip because of --test
arguments = ['/path/to/bash-4.4.12-5.fc26.rpm',
'--create-build',
'--src-epoch', epoch]
expected = "Creating empty build: %s\n" % nvr
expected += "uploading %s... done\n" % arguments[0]
expected += "importing %s... done\n" % arguments[0]
session.getBuild.side_effect = [
# binary rpm case,
# getBuild is called to check build.
{'state': self.bstate['FAILED']},
# simulate build fail case
{'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['FAILED']},
# after calling createEmptyBuild, getBuild should return:
{'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'state': self.bstate['COMPLETE']},
]
# no RPM info is returned
session.getRPM.return_value = {}
self.__do_import_test(
options, session, arguments + ['--create-build'],
rpm_header=self.rpm_header,
expected=expected)
kwarg = dict((k, self.rpm_header.get(k, None))
for k in ['name', 'version', 'release', 'epoch'])
kwarg['epoch'] = int(epoch)
session.createEmptyBuild.assert_called_with(**kwarg)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji.get_header_fields')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_has_build_states_test(
self,
activate_session_mock,
get_header_fields_mock,
stderr,
stdout):
"""Test handle_import RPM with existing build but status not COMPLETE case"""
arguments = ['/path/to/bash-4.4.12-5.fc26.src.rpm']
options = mock.MagicMock()
session = mock.MagicMock()
get_header_fields_mock.return_value = self.srpm_header
test_cases = [
{
'state': 'FAILED',
'msg': "Build %s state is %s. Skipping import"
},
{
'state': 'CANCELED',
'msg': "Build %s state is %s. Skipping import"
},
{
'state': 'DELETED',
'msg': 'Build %s exists with state=%s. Skipping import'
},
{
'state': 'BUILDING',
'msg': 'Build %s exists with state=%s. Skipping import'
}
]
nvr = '%(name)s-%(version)s-%(release)s' % \
get_header_fields_mock.return_value
# build failed cases
for case in test_cases:
session.getBuild.return_value = {
'state': self.bstate[case['state']]}
handle_import(options, session, arguments)
expected = case['msg'] % (nvr, case['state']) + "\n"
self.assert_console_message(stdout, expected)
get_header_fields_mock.assert_called_with(
arguments[0],
('name', 'version', 'release', 'epoch',
'arch', 'sigmd5', 'sourcepackage', 'sourcerpm')
)
activate_session_mock.assert_called_with(session, options)
session.getRPM.assert_not_called()
session.importRPM.assert_not_called()
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_argument_error(
self,
activate_session_mock,
stderr):
"""Test handle_import function with error arguments"""
options = mock.MagicMock()
session = mock.MagicMock()
epoch = 'zzz'
test_cases = [
# empty argument error
{
'arguments': [],
'error': "At least one package must be specified"
},
# invalid epoch number test (epoch must be integer)
{
'arguments': ['pkg', '--src-epoch', epoch],
'error': "Invalid value for epoch: %s" % epoch
}
]
for case in test_cases:
expect = self.format_error_message(case['error'])
self.assert_system_exit(
handle_import,
options,
session,
case['arguments'],
stderr=expect,
activate_session=None)
activate_session_mock.assert_not_called()
def test_handle_import_help(self):
"""Test handle_import function help message"""
self.assert_help(
handle_import,
"""Usage: %s import [options] package [package...]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--link Attempt to hardlink instead of uploading
--test Don't actually import
--create-build Auto-create builds as needed
--src-epoch=SRC_EPOCH
When auto-creating builds, use this epoch
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,220 @@
from __future__ import absolute_import
import mock
import six
import unittest
from mock import call
from koji_cli.commands import handle_import_cg
from . import utils
import os
class TestImportCG(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def mock_os_path_exists(self, filepath):
if filepath in self.custom_os_path_exists:
return self.custom_os_path_exists[filepath]
return self.os_path_exists(filepath)
def setUp(self):
self.custom_os_path_exists = {}
self.os_path_exists = os.path.exists
self.error_format = """Usage: %s import-cg [options] metadata_file files_dir
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands._progress_callback')
@mock.patch('koji_cli.commands._unique_path')
@mock.patch('koji_cli.commands._running_in_bg', return_value=False)
@mock.patch('koji_cli.commands.linked_upload')
@mock.patch('koji_cli.commands.activate_session')
@mock.patch('koji_cli.commands.json')
def test_handle_import_cg(
self,
json_mock,
activate_session_mock,
linked_upload_mock,
running_in_bg_mock,
unique_path_mock,
progress_callback_mock,
stdout):
"""Test handle_import_cg function"""
arguments = ['fake-metafile', '/path/to/files/']
options = mock.MagicMock()
session = mock.MagicMock()
expected = ''
fake_srv_path = '/path/to/server/cli-import'
metadata = {
'output': [
{'relpath': '/real/path', 'filename': 'file-1'},
{'relpath': '/real/path', 'filename': 'file-2'}
]
}
#
# we just need to change original os.path.exists behavior, if the input
# is matched return the value we expected.
self.custom_os_path_exists = dict(('%(relpath)s/%(filename)s' % v, True)
for v in metadata['output'])
# setup and start os.path.exists patch
os_path_exists_patch = mock.patch('os.path.exists',
new=self.mock_os_path_exists)
os_path_exists_patch.start()
def gen_value(fmt, callback):
calls, expect = [], ''
for item in metadata['output']:
filepath = "%(relpath)s/%(filename)s" % item
calls.append(call(filepath,
item['relpath'],
callback=callback))
expect += fmt % filepath + "\n"
return calls, expect
json_mock.load.return_value = metadata
unique_path_mock.return_value = fake_srv_path
# Case 1, running in fg, progress on
with mock.patch(utils.get_builtin_open()):
handle_import_cg(options, session, arguments)
calls, expected = gen_value("Uploading %s\n", progress_callback_mock)
self.assert_console_message(stdout, expected)
linked_upload_mock.assert_not_called()
session.uploadWrapper.assert_has_calls(calls)
session.CGImport.assert_called_with(metadata, fake_srv_path)
# Case 2, running in fg, progress off
with mock.patch(utils.get_builtin_open()):
handle_import_cg(options, session, arguments + ['--noprogress'])
calls, expected = gen_value("Uploading %s", None)
self.assert_console_message(stdout, expected)
linked_upload_mock.assert_not_called()
session.uploadWrapper.assert_has_calls(calls)
session.CGImport.assert_called_with(metadata, fake_srv_path)
# reset mocks
linked_upload_mock.reset_mock()
session.uploadWrapper.reset_mock()
session.CGImport.reset_mock()
# Case 3, --test option
with mock.patch(utils.get_builtin_open()):
handle_import_cg(options, session, arguments + ['--test'])
linked_upload_mock.assert_not_called()
session.uploadWrapper.assert_not_called()
session.CGImport.assert_not_called()
calls = [call("%(relpath)s/%(filename)s" % item, item['relpath'])
for item in metadata['output']]
# Case 4, --link option
with mock.patch(utils.get_builtin_open()):
handle_import_cg(options, session, arguments + ['--link'])
linked_upload_mock.assert_has_calls(calls)
session.uploadWrapper.assert_not_called()
session.CGImport.assert_called_with(metadata, fake_srv_path)
# make sure there is no message on output
self.assert_console_message(stdout, '')
# stop os.path.exists patch
os_path_exists_patch.stop()
def test_handle_import_argument_test(self):
"""Test handle_import_cg function without arguments"""
arguments = ['fake-metafile', '/path/to/files/']
options = mock.MagicMock()
session = mock.MagicMock()
# Case 1. empty argument
expected = self.format_error_message(
"Please specify metadata files directory")
self.assert_system_exit(
handle_import_cg,
options,
session,
[],
stderr=expected,
activate_session=None)
# Case 2. JSON module does not exist
expected = self.format_error_message('Unable to find json module')
with mock.patch('koji_cli.commands.json', new=None):
self.assert_system_exit(
handle_import_cg,
options,
session,
arguments,
stderr=expected,
activate_session=None)
metadata = {
'output': [
{'metadata_only': True},
{'relpath': '/real/path', 'filename': 'filename'}
]
}
#
# we just need to change original os.path.exists behavior, if the input
# is matched return the value we expected.
self.custom_os_path_exists['/real/path/filename'] = False
with mock.patch(utils.get_builtin_open()), \
mock.patch('os.path.exists', new=self.mock_os_path_exists), \
mock.patch('koji_cli.commands.json') as json_mock:
# Case 3. metafile doesn't have output section
json_mock.load.return_value = {}
expected = "Metadata contains no output\n"
self.assert_system_exit(
handle_import_cg,
options,
session,
arguments,
stdout=expected,
exit_code=1)
# Case 4. path not exist
file_path = '%(relpath)s/%(filename)s' % metadata['output'][1]
json_mock.load.return_value = metadata
expected = self.format_error_message(
"No such file: %s" % file_path)
self.assert_system_exit(
handle_import_cg,
options,
session,
arguments,
stderr=expected)
def test_handle_import_cg_help(self):
"""Test handle_import_cg help message"""
self.assert_help(
handle_import_cg,
"""Usage: %s import-cg [options] metadata_file files_dir
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--noprogress Do not display progress of the upload
--link Attempt to hardlink instead of uploading
--test Don't actually import
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,284 @@
from __future__ import absolute_import
import mock
import six
import unittest
import random
import hashlib
import copy
import base64
from mock import call
from koji_cli.commands import handle_import_sig
from . import utils
import os
class TestImportSIG(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def md5sum(self, message):
md5 = hashlib.md5()
md5.update(message.encode('utf-8'))
return md5.hexdigest()
def mock_os_path_exists(self, filepath):
if filepath in self.custom_os_path_exists:
return self.custom_os_path_exists[filepath]
return self.os_path_exists(filepath)
def setUp(self):
self.custom_os_path_exists = {}
self.os_path_exists = os.path.exists
self.rpm_headers = [
{
'sourcepackage': 1,
'name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'arch': 'x86_64',
'siggpg': None,
'sigpgp': None,
},
{
'sourcepackage': 1,
'name': 'less',
'version': '487',
'release': '3.fc26',
'arch': 'x86_64',
'siggpg': None,
'sigpgp': None,
},
{
'sourcepackage': 1,
'name': 'sed',
'version': '4.4',
'release': '1.fc26',
'arch': 'x86_64',
'siggpg': None,
'sigpgp': None,
}
]
self.error_format = """Usage: %s import-sig [options] package [package...]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji.rip_rpm_sighdr')
@mock.patch('koji.get_sigpacket_key_id')
@mock.patch('koji.get_header_fields')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_import_sig(
self,
activate_session_mock,
get_header_fields_mock,
get_sigpacket_key_id_mock,
rip_rpm_sighdr_mock,
stdout):
"""Test handle_import_sig function"""
arguments = ['/path/to/bash', '/path/to/less', '/path/to/sed']
options = mock.MagicMock()
session = mock.MagicMock()
expected = ''
fake_sigkey = '00112233445566778899aAbBcCdDeEfF'
#
# we just need to change original os.path.exists behavior, if the input
# is matched return the value we expected.
self.custom_os_path_exists = dict((f, True) for f in arguments)
# setup and start os.path.exists patch
os_path_exists_patch = mock.patch('os.path.exists',
new=self.mock_os_path_exists)
os_path_exists_patch.start()
# Case 1, Unsigned pkg test (without ----with-unsigned)
# result: import skipped
for pkgfile in arguments:
expected += "Skipping unsigned package: %s" % pkgfile + "\n"
get_header_fields_mock.side_effect = copy.deepcopy(self.rpm_headers)
# Run
handle_import_sig(options, session, arguments)
self.assert_console_message(stdout, expected)
activate_session_mock.assert_called_once()
rip_rpm_sighdr_mock.assert_not_called()
session.getRPM.assert_not_called()
# Case 2, No RPM in system
# result: import skipped
expected = ''
for data in self.rpm_headers:
data['siggpg'] = fake_sigkey
data['sigpgp'] = fake_sigkey
tmp = data.copy()
tmp['arch'] = 'src' if tmp['sourcepackage'] else tmp['arch']
expected += "No such rpm in system: %(name)s-%(version)s-%(release)s.%(arch)s" % \
tmp + "\n"
get_header_fields_mock.side_effect = copy.deepcopy(self.rpm_headers)
get_sigpacket_key_id_mock.return_value = fake_sigkey
session.getRPM.return_value = {}
# Run
handle_import_sig(options, session, arguments)
self.assert_console_message(stdout, expected)
rip_rpm_sighdr_mock.assert_not_called()
session.queryRPMSigs.assert_not_called()
# Case 3, Find external repo RPM
# result: import skipped
ext_repos = ['ext-repo1.net', 'ext-repo2.net', 'ext-repo3.net']
expected = ''
rinfo = copy.deepcopy(self.rpm_headers)
for data in rinfo:
rid = random.randint(0, 999) % 3
data['external_repo_id'] = rid + 1
data['external_repo_name'] = ext_repos[rid]
data['arch'] = 'src' if data['sourcepackage'] else data['arch']
expected += "Skipping external rpm: %(name)s-%(version)s-%(release)s.%(arch)s@%(external_repo_name)s" % data + "\n"
get_header_fields_mock.side_effect = rinfo
session.getRPM.side_effect = rinfo
# Run
handle_import_sig(options, session, arguments)
self.assert_console_message(stdout, expected)
rip_rpm_sighdr_mock.assert_not_called()
session.queryRPMSigs.assert_not_called()
# Case 4, has previous RPM signature
# result: import skipped
# show match or mismatch message
expected = ''
# session.queryRPMSigs return example:
# [{'sigkey': '64dab85d', 'sighash': '7141c84f059d2f0722ff545051b2981d', 'rpm_id': 1}]
#
sighdr, sigRpm = [], []
for i, pkg in enumerate(arguments):
signature = 'sighdr-%s' % pkg
sighdr.append(signature.encode('utf-8'))
if i < 2:
sigRpm.append([{'sighash': self.md5sum(signature)}])
expected += "Signature already imported: %s" % pkg + "\n"
else:
sigRpm.append([{'sighash': self.md5sum('wrong-sig-case')}])
expected += "Warning: signature mismatch: %s" % pkg + "\n"
expected += " The system already has a signature for this rpm with key %s" % fake_sigkey + "\n"
expected += " The two signature headers are not the same" + "\n"
rinfo = copy.deepcopy(self.rpm_headers)
for i, data in enumerate(rinfo):
data['external_repo_id'] = 0
data['id'] = i + 1
get_header_fields_mock.side_effect = copy.deepcopy(rinfo)
session.getRPM.side_effect = rinfo
rip_rpm_sighdr_mock.side_effect = sighdr
session.queryRPMSigs.side_effect = sigRpm
# Run
handle_import_sig(options, session, arguments)
self.assert_console_message(stdout, expected)
# Case 5, --test options test
# result: everything works fine and addRPMSig/writeSignedRPM should
# not be called.
expected = ''
for pkg in arguments:
expected += "Importing signature [key %s] from %s..." % (fake_sigkey, pkg) + "\n"
expected += "Writing signed copy" + "\n"
get_header_fields_mock.side_effect = copy.deepcopy(rinfo)
session.getRPM.side_effect = rinfo
rip_rpm_sighdr_mock.side_effect = sighdr
session.queryRPMSigs.side_effect = None
session.queryRPMSigs.return_value = []
# Run
handle_import_sig(options, session, arguments + ['--test'])
self.assert_console_message(stdout, expected)
session.addRPMSig.assert_not_called()
session.writeSignedRPM.assert_not_called()
# Case 6, normal import
# result: addRPMSig/writeSignedRPM should be called
get_header_fields_mock.side_effect = copy.deepcopy(rinfo)
session.getRPM.side_effect = rinfo
rip_rpm_sighdr_mock.side_effect = sighdr
add_sig_calls, write_sig_calls = [], []
for i in range(0, 3):
add_sig_calls.append(call(rinfo[i]['id'], base64.encodestring(sighdr[i])))
write_sig_calls.append(call(rinfo[i]['id'], fake_sigkey))
# Run
handle_import_sig(options, session, arguments)
self.assert_console_message(stdout, expected)
session.addRPMSig.assert_has_calls(add_sig_calls)
session.writeSignedRPM.assert_has_calls(write_sig_calls)
# restore os.path.exists patch
os_path_exists_patch.stop()
def test_handle_import_sig_argument_test(self):
"""Test handle_import_sig function without arguments"""
options = mock.MagicMock()
session = mock.MagicMock()
# Case 1. empty argument
expected = self.format_error_message(
"At least one package must be specified")
self.assert_system_exit(
handle_import_sig,
options,
session,
[],
stderr=expected,
activate_session=None)
# Case 2. File not exists test
arguments = ['/bin/ls', '/tmp', '/path/to/file1', '/path/to/file2']
expected = self.format_error_message(
"No such file: %s" % arguments[2])
self.assert_system_exit(
handle_import_sig,
options,
session,
arguments,
stderr=expected,
activate_session=None)
def test_handle_import_sig_help(self):
"""Test handle_import_sig help message"""
self.assert_help(
handle_import_sig,
"""Usage: %s import-sig [options] package [package...]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--with-unsigned Also import unsigned sig headers
--write Also write the signed copies
--test Test mode -- don't actually import
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -1,8 +1,12 @@
import mock
import six
import unittest
import koji
from koji_cli.lib import _list_tasks
from koji_cli.commands import handle_list_tasks
from . import utils
class TestListTasks(unittest.TestCase):
def setUp(self):
@ -29,7 +33,7 @@ class TestListTasks(unittest.TestCase):
'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')],
'decode': True,
'owner': 1,
}, {'order' : 'priority,create_time'})
}, {'order': 'priority,create_time'})
# invalid me
session.getLoggedInUser.return_value = None
@ -50,7 +54,7 @@ class TestListTasks(unittest.TestCase):
'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')],
'decode': True,
'owner': 2,
}, {'order' : 'priority,create_time'})
}, {'order': 'priority,create_time'})
# invalid user
session.getUser.return_value = None
@ -66,7 +70,7 @@ class TestListTasks(unittest.TestCase):
'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')],
'decode': True,
'arch': ['x86_64', 'i386'],
}, {'order' : 'priority,create_time'})
}, {'order': 'priority,create_time'})
# only method
session.listTasks.reset_mock()
@ -77,7 +81,7 @@ class TestListTasks(unittest.TestCase):
'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')],
'decode': True,
'method': 'method',
}, {'order' : 'priority,create_time'})
}, {'order': 'priority,create_time'})
# only channel
session.listTasks.reset_mock()
@ -89,7 +93,7 @@ class TestListTasks(unittest.TestCase):
'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')],
'decode': True,
'channel_id': 123,
}, {'order' : 'priority,create_time'})
}, {'order': 'priority,create_time'})
session.getChannel.assert_called_once_with('channel')
# invalid channel
@ -107,7 +111,7 @@ class TestListTasks(unittest.TestCase):
'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')],
'decode': True,
'host_id': 234,
}, {'order' : 'priority,create_time'})
}, {'order': 'priority,create_time'})
session.getHost.assert_called_once_with('host')
# invalid host
@ -127,7 +131,7 @@ class TestListTasks(unittest.TestCase):
{
'children': [
{
'children': [ {'id': 3, 'parent': 2, 'sub': True}],
'children': [{'id': 3, 'parent': 2, 'sub': True}],
'id': 2,
'parent': 1,
'sub': True
@ -148,3 +152,110 @@ class TestListTasks(unittest.TestCase):
'sub': True}
])
class TestCliListTasks(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s list-tasks [options]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands._list_tasks')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_list_tasks(
self,
activate_session_mock,
list_tasks_mock,
stdout):
"""Test handle_list_tasks function"""
session = mock.MagicMock()
options = mock.MagicMock(quiet=False)
tasks = [
{
'id': 5,
'sub': True,
'state': 0,
'owner_name': 'kojiadmin',
'method': 'createrepo',
'parent': 3,
'priority': 14,
'arch': 'noarch'
},
{
'children':
[
{
'id': 5,
'sub': True,
'state': 0,
'owner_name': 'kojiadmin',
'method': 'createrepo',
'parent': 3,
'priority': 14,
'arch': 'noarch'
},
],
'id': 3,
'state': 0,
'owner_name': 'kojiadmin',
'method': 'newRepo',
'priority': 15,
'arch': 'noarch',
}
]
header = \
"ID Pri Owner State Arch Name\n"
task_output = \
"3 15 kojiadmin FREE noarch newRepo (noarch)\n" + \
"5 14 kojiadmin FREE noarch +createrepo (noarch)\n"
expected = self.format_error_message(
"This command takes no arguments")
# Case 1, argument error test.
self.assert_system_exit(
handle_list_tasks,
options,
session,
['test'],
stderr=expected,
activate_session=None)
# Case 2, no tasks
list_tasks_mock.return_value = None
handle_list_tasks(options, session, [])
self.assert_console_message(stdout, '(no tasks)\n')
# Case 3, show tasks with header
list_tasks_mock.return_value = tasks
handle_list_tasks(options, session, [])
self.assert_console_message(stdout, header + task_output)
# Case 4. show task without header
handle_list_tasks(options, session, ['--quiet'])
self.assert_console_message(stdout, task_output)
def test_handle_list_tasks_help(self):
self.assert_help(
handle_list_tasks,
"""Usage: %s list-tasks [options]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--mine Just print your tasks
--user=USER Only tasks for this user
--arch=ARCH Only tasks for this architecture
--method=METHOD Only tasks of this method
--channel=CHANNEL Only tasks in this channel
--host=HOST Only tasks for this host
--quiet Do not display the column headers
""" % self.progname)

View file

@ -0,0 +1,368 @@
from __future__ import absolute_import
import mock
import six
import unittest
from koji_cli.commands import anon_handle_mock_config
from . import utils
class TestMockConfig(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.common_args = [
'--distribution', 'fedora',
'--topdir', '/top-dir',
'--topurl', '/top-url',
'--yum-proxy', '/yum-proxy'
]
self.common_opts = {
'distribution': 'fedora',
'mockdir': '/var/lib/mock',
'topdir': '/top-dir',
'topurl': '/top-url',
'yum_proxy': '/yum-proxy',
}
self.mock_output = """# Auto-generated by the Koji build system
config_opts['chroot_setup_cmd'] = 'groupinstall build'
config_opts['use_host_resolv'] = False
config_opts['root'] = 'fedora26-build-repo_1'
config_opts['yum.conf'] = '[main]\ncachedir=/var/cache/yum\ndebuglevel=1\nlogfile=/var/log/yum.log\nreposdir=/dev/null\nretries=20\nobsoletes=1\ngpgcheck=0\nassumeyes=1\nkeepcache=1\ninstall_weak_deps=0\nstrict=1\n\n# repos\n\n[build]\nname=build\nbaseurl=https://fedora.local/kojifiles/repos/fedora26-build/1/x86_64\n'
config_opts['rpmbuild_timeout'] = 86400
config_opts['chroothome'] = '/builddir'
config_opts['target_arch'] = 'x86_64'
config_opts['basedir'] = '/var/lib/mock'
config_opts['plugin_conf']['yum_cache_enable'] = False
config_opts['plugin_conf']['root_cache_enable'] = False
config_opts['plugin_conf']['ccache_enable'] = False
config_opts['macros']['%_rpmfilename'] = '%%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm'
config_opts['macros']['%_topdir'] = '/builddir/build'
config_opts['macros']['%packager'] = 'Koji'
config_opts['macros']['%_host'] = 'x86_64-koji-linux-gnu'
config_opts['macros']['%_host_cpu'] = 'x86_64'
config_opts['macros']['%vendor'] = 'Koji'
config_opts['macros']['%distribution'] = 'Koji Testing'
"""
self.error_format = """Usage: %s mock-config [options]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji.genMockConfig')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_mock_config_buildroot_option(
self, activate_session_mock, gen_config_mock, stdout, stderr):
"""Test anon_handle_mock_config buildroot options"""
arguments = []
options = mock.MagicMock()
buildroot_info = {
'repo_id': 101,
'tag_name': 'tag_name',
'arch': 'x86_64'
}
# Mock out the xmlrpc server
session = mock.MagicMock()
session.getBuildroot.return_value = buildroot_info
# Mock config
gen_config_mock.return_value = self.mock_output
# buildroot check
arguments = ['--buildroot', 'root', self.progname]
expected = self.format_error_message("Buildroot id must be an integer")
self.assert_system_exit(
anon_handle_mock_config,
options,
session,
arguments,
stderr=expected)
arguments = self.common_args + ['--buildroot', '1',
'--name', self.progname]
opts = self.common_opts.copy()
opts.update({
'repoid': buildroot_info['repo_id'],
'tag_name': buildroot_info['tag_name']
})
anon_handle_mock_config(options, session, arguments)
self.assert_console_message(
stdout, "%s\n" % gen_config_mock.return_value)
gen_config_mock.assert_called_with(
self.progname, buildroot_info['arch'], **opts)
arguments = self.common_args + ['--buildroot', '1',
'--name', self.progname,
'--latest']
opts['repoid'] = 'latest'
anon_handle_mock_config(options, session, arguments)
self.assert_console_message(
stdout, "%s\n" % gen_config_mock.return_value)
gen_config_mock.assert_called_with(
self.progname, buildroot_info['arch'], **opts)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji.genMockConfig')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_mock_config_task_option(
self, activate_session_mock, gen_config_mock, stdout, stderr):
"""Test anon_handle_mock_config task options"""
arguments = []
task_id = 1001
options = mock.MagicMock()
session = mock.MagicMock()
session.listBuildroots.return_value = ''
# Mock config
gen_config_mock.return_value = ''
arguments = ['--task', 'task']
expected = self.format_error_message("Task id must be an integer")
self.assert_system_exit(
anon_handle_mock_config,
options,
session,
arguments,
stderr=expected)
arguments = ['--task', str(task_id)]
expected = "No buildroots for task %s (or no such task)\n" % str(task_id)
self.assertEqual(1, anon_handle_mock_config(options, session, arguments))
self.assert_console_message(stdout, expected)
multi_broots = [
{'id': 1101, 'repo_id': 101, 'tag_name': 'tag_101', 'arch': 'x86_64'},
{'id': 1111, 'repo_id': 111, 'tag_name': 'tag_111', 'arch': 'x86_64'},
{'id': 1121, 'repo_id': 121, 'tag_name': 'tag_121', 'arch': 'x86_64'}
]
session.listBuildroots.return_value = multi_broots
anon_handle_mock_config(options, session, arguments)
expected = "Multiple buildroots found: %s" % [br['id'] for br in multi_broots]
self.assert_console_message(stdout, "%s\n\n" % expected)
opts = self.common_opts.copy()
opts.update({
'repoid': 'latest',
'tag_name': multi_broots[0]['tag_name']
})
arguments = self.common_args + ['--task', str(task_id),
'--name', self.progname,
'--latest']
session.listBuildroots.return_value = [multi_broots[0]]
gen_config_mock.return_value = self.mock_output
anon_handle_mock_config(options, session, arguments)
self.assert_console_message(
stdout, "%s\n" % gen_config_mock.return_value)
gen_config_mock.assert_called_with(
self.progname, multi_broots[0]['arch'], **opts)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji.genMockConfig')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_mock_config_tag_option(
self, activate_session_mock, gen_config_mock, stdout, stderr):
"""Test anon_handle_mock_config with tag option"""
arguments = []
tag = 'tag'
tag = {'id': 201, 'name': 'tag', 'arch': 'x86_64'}
options = mock.MagicMock()
# Mock out the xmlrpc server
session = mock.MagicMock()
session.getTag.return_value = None
session.getBuildConfig.return_value = None
session.getRepo.return_value = None
arguments = ['--tag', tag['name']]
expected = "Please specify an arch\n"
self.assertEqual(1, anon_handle_mock_config(options, session, arguments))
self.assert_console_message(stdout, expected)
arguments = ['--tag', tag['name'], '--arch', tag['arch']]
expected = self.format_error_message("Invalid tag: %s" % tag['name'])
self.assert_system_exit(
anon_handle_mock_config,
options,
session,
arguments,
stderr=expected)
# return tag info
session.getTag.return_value = tag
expected = "Could not get config info for tag: %(name)s\n" % tag
self.assertEqual(1, anon_handle_mock_config(options, session, arguments))
self.assert_console_message(stdout, expected)
# return build config
session.getBuildConfig.return_value = {'id': 301}
expected = "Could not get a repo for tag: %(name)s\n" % tag
self.assertEqual(1, anon_handle_mock_config(options, session, arguments))
self.assert_console_message(stdout, expected)
# return repo
session.getRepo.return_value = {'id': 101}
gen_config_mock.return_value = self.mock_output
anon_handle_mock_config(options, session, arguments)
self.assert_console_message(
stdout, "%s\n" % gen_config_mock.return_value)
arguments = self.common_args + ['--tag', tag['name'],
'--arch', tag['arch'],
'--name', self.progname,
'--latest']
opts = self.common_opts.copy()
opts.update({
'repoid': 'latest',
'tag_name': tag['name'],
})
anon_handle_mock_config(options, session, arguments)
self.assert_console_message(
stdout, "%s\n" % gen_config_mock.return_value)
gen_config_mock.assert_called_with(
self.progname, tag['arch'], **opts)
@mock.patch('sys.stderr', new_callable=six.StringIO)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji.genMockConfig')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_mock_config_target_option(
self, activate_session_mock, gen_config_mock, stdout, stderr):
"""Test anon_handle_mock_config with target option"""
arguments = []
arch = "x86_64"
target = {'id': 1,
'name': 'target',
'dest_tag': 1,
'build_tag': 2,
'build_tag_name': 'target-build',
'dest_tag_name': 'target'}
options = mock.MagicMock()
# Mock out the xmlrpc server
session = mock.MagicMock()
session.getBuildTarget.return_value = None
session.getRepo.return_value = None
arguments = ['--target', target['name']]
expected = "Please specify an arch\n"
self.assertEqual(1, anon_handle_mock_config(options, session, arguments))
self.assert_console_message(stdout, expected)
arguments = ['--target', target['name'],
'--arch', arch]
expected = self.format_error_message(
"Invalid target: %s" % target['name'])
self.assert_system_exit(
anon_handle_mock_config,
options,
session,
arguments,
stderr=expected)
session.getBuildTarget.return_value = target
expected = "Could not get a repo for tag: %s\n" % target['build_tag_name']
self.assertEqual(1, anon_handle_mock_config(options, session, arguments))
self.assert_console_message(stdout, expected)
arguments = self.common_args + ['--target', target['name'],
'--arch', arch,
'--name', self.progname]
opts = self.common_opts.copy()
opts.update({
'repoid': 101,
'tag_name': target['build_tag_name']
})
session.getRepo.return_value = {'id': 101}
gen_config_mock.return_value = self.mock_output
anon_handle_mock_config(options, session, arguments)
self.assert_console_message(
stdout, "%s\n" % gen_config_mock.return_value)
gen_config_mock.assert_called_with(
self.progname, arch, **opts)
# --latest and -o (output) test
opts['repoid'] = 'latest'
arguments.extend(['--latest', '-o', '/tmp/mock.out'])
with mock.patch('koji_cli.commands.open', create=True) as openf_mock:
anon_handle_mock_config(options, session, arguments)
openf_mock.assert_called_with('/tmp/mock.out', 'w')
handle = openf_mock()
handle.write.assert_called_once_with(self.mock_output)
gen_config_mock.assert_called_with(
self.progname, arch, **opts)
@mock.patch('sys.stderr', new_callable=six.StringIO)
def test_handle_mock_config_errors(self, stderr):
"""Test anon_handle_mock_config general error messages"""
arguments = []
options = mock.MagicMock()
# Mock out the xmlrpc server
session = mock.MagicMock()
# Run it and check immediate output
# argument is empty
expected = self.format_error_message(
"Please specify one of: --tag, --target, --task, --buildroot")
self.assert_system_exit(
anon_handle_mock_config,
options,
session,
arguments,
stderr=expected)
# name is specified twice case
arguments = [self.progname, '--name', 'name']
expected = self.format_error_message(
"Name already specified via option")
self.assert_system_exit(
anon_handle_mock_config,
options,
session,
arguments,
stderr=expected)
def test_handle_mock_config_help(self):
"""Test anon_handle_mock_config help message full output"""
self.assert_help(
anon_handle_mock_config,
"""Usage: %s mock-config [options]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
-a ARCH, --arch=ARCH Specify the arch
-n NAME, --name=NAME Specify the name for the buildroot
--tag=TAG Create a mock config for a tag
--target=TARGET Create a mock config for a build target
--task=TASK Duplicate the mock config of a previous task
--latest use the latest redirect url
--buildroot=BUILDROOT
Duplicate the mock config for the specified buildroot
id
--mockdir=DIR Specify mockdir
--topdir=DIR Specify topdir
--topurl=URL URL under which Koji files are accessible
--distribution=DISTRIBUTION
Change the distribution macro
--yum-proxy=YUM_PROXY
Specify a yum proxy
-o FILE Output to a file
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,198 @@
from __future__ import absolute_import
import mock
import six
import unittest
import koji
from koji_cli.commands import handle_restart_hosts
from . import utils
class TestRestartHosts(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.task_id = 101
@mock.patch('koji_cli.commands.watch_tasks')
@mock.patch('koji_cli.commands._running_in_bg')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_restart_hosts_force_options(
self, activate_session_mock, running_in_bg_mock, watch_tasks_mock):
"""Test %s function with --force option""" % handle_restart_hosts.__name__
arguments = ['--force']
options = mock.MagicMock(quiet=None, poll_interval=3)
session = mock.MagicMock()
# set running in foreground
running_in_bg_mock.return_value = False
session.getHost.return_value = None
session.restartHosts.return_value = self.task_id
session.logout.return_value = None
# has other restart tasks are running case
session.listTasks.return_value = [{'id': 1}, {'id': 2}, {'id': 3}]
handle_restart_hosts(options, session, arguments)
activate_session_mock.assert_called_once()
session.listTasks.assert_not_called()
session.restartHosts.assert_called_with()
session.logout.assert_called_once()
watch_tasks_mock.assert_called_with(
session, [self.task_id], quiet=None, poll_interval=3)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.watch_tasks')
@mock.patch('koji_cli.commands._running_in_bg')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_restart_hosts_has_other_tasks(
self,
activate_session_mock,
running_in_bg_mock,
watch_tasks_mock,
stdout):
"""Test %s function when there has other restart tasks exist""" % handle_restart_hosts.__name__
options = mock.MagicMock()
session = mock.MagicMock()
# set running in foreground
running_in_bg_mock.return_value = False
session.getHost.return_value = None
session.restartHosts.return_value = True
session.logout.return_value = None
#
# session.listTasks returns:
#
# [{'weight': 0.1,
# 'awaited': None,
# 'completion_time': None,
# 'create_time': '2017-11-02 18:30:08.933753',
# 'result': None,
# 'owner': 1,
# 'id': 11,
# 'state': 1,
# 'label': None,
# 'priority': 5,
# 'waiting': True,
# 'completion_ts': None,
# 'method': 'restartHosts',
# 'owner_name': 'kojiadmin',
# 'parent': None,
# 'start_time': '2017-11-02 18:30:28.028843',
# 'start_ts': 1509647428.02884,
# 'create_ts': 1509647408.93375,
# 'host_id': 1, 'arch': 'noarch',
# 'request': "<?xml version='1.0'?>\n<methodCall>\n<methodName>restartHosts</methodName>\n<params>\n</params>\n</methodCall>\n",
# 'channel_id': 1,
# 'owner_type': 0}]
#
# has other restart tasks are running case
session.listTasks.return_value = [{'id': 1}, {'id': 2}, {'id': 3}]
self.assertEqual(1, handle_restart_hosts(options, session, []))
activate_session_mock.assert_called_once()
query_opt = {
'method': 'restartHosts',
'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')]
}
session.listTasks.assert_called_with(query_opt)
session.restartHosts.assert_not_called()
session.logout.assert_not_called()
expect = "Found other restartHosts tasks running.\n"
expect += "Task ids: %r\n" % \
[t['id'] for t in session.listTasks.return_value]
expect += "Use --force to run anyway\n"
self.assert_console_message(stdout, expect)
@mock.patch('koji_cli.commands.watch_tasks')
@mock.patch('koji_cli.commands._running_in_bg')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_restart_hosts_wait_option(
self, activate_session_mock, running_in_bg_mock, watch_tasks_mock):
"""Test %s function with --force option""" % handle_restart_hosts.__name__
arguments = ['--wait']
options = mock.MagicMock(quiet=None, poll_interval=3)
session = mock.MagicMock()
# --wait is specified, running_in_bg() should not matter.
running_in_bg_mock.return_value = True
session.getHost.return_value = None
session.restartHosts.return_value = self.task_id
session.logout.return_value = None
# has other restart tasks are running case
session.listTasks.return_value = []
handle_restart_hosts(options, session, arguments)
activate_session_mock.assert_called_once()
session.listTasks.assert_called_once()
session.restartHosts.assert_called_with()
session.logout.assert_called_once()
watch_tasks_mock.assert_called_with(
session, [self.task_id], quiet=None, poll_interval=3)
@mock.patch('koji_cli.commands.watch_tasks')
@mock.patch('koji_cli.commands._running_in_bg')
@mock.patch('koji_cli.commands.activate_session')
def test_handle_restart_hosts_other_options(
self, activate_session_mock, running_in_bg_mock, watch_tasks_mock):
"""Test %s function with --force option""" % handle_restart_hosts.__name__
arguments = ['--nowait',
'--channel', 'createrepo',
'--arch', 'x86_64',
'--timeout', '10']
options = mock.MagicMock(quiet=None, poll_interval=3)
session = mock.MagicMock()
# --no-wait is specified, running_in_bg() should not matter.
running_in_bg_mock.return_value = True
session.getHost.return_value = None
session.restartHosts.return_value = 101
session.logout.return_value = None
# has other restart tasks are running case
session.listTasks.return_value = []
handle_restart_hosts(options, session, arguments)
activate_session_mock.assert_called_once()
session.listTasks.assert_called_once()
session.restartHosts.assert_called_with(
options={'arches': ['x86_64'], 'timeout': 10, 'channel': 'createrepo'})
session.logout.assert_not_called()
watch_tasks_mock.assert_not_called()
def test_handle_restart_hosts_help(self):
self.assert_help(
handle_restart_hosts,
"""Usage: %s restart-hosts [options]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--wait Wait on the task, even if running in the background
--nowait Don't wait on task
--quiet Do not print the task information
--force Ignore checks and force operation
--channel=CHANNEL Only hosts in this channel
-a ARCH, --arch=ARCH Limit to hosts of this architecture (can be given
multiple times)
--timeout=N Time out after N seconds
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,92 @@
from __future__ import absolute_import
import mock
import six
import unittest
from koji_cli.commands import anon_handle_search
from . import utils
class TestSearch(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s search [options] search_type pattern
Available search types: package, build, tag, target, user, host, rpm, maven, win
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
def test_anon_handle_search(
self,
stdout):
"""Test anon_handle_search function"""
session = mock.MagicMock()
options = mock.MagicMock()
s_type, s_pattern = 'build', 'fedora'
arguments = [s_type, s_pattern]
search_results = [
{'id': 166, 'name': 'f25'},
{'id': 177, 'name': 'f26'},
{'id': 202, 'name': 'f27'}
]
session.search.return_value = search_results
expected = ''.join('%s\n' % x['name'] for x in search_results)
# Case 1. normal search
anon_handle_search(options, session, arguments)
self.assert_console_message(stdout, expected)
session.search.assert_called_with(s_pattern, s_type, 'glob')
# Case 2. exact match
anon_handle_search(options, session, arguments + ['--exact'])
self.assert_console_message(stdout, expected)
session.search.assert_called_with(s_pattern, s_type, 'exact')
# Case 3. regex match
anon_handle_search(options, session, arguments + ['-r'])
self.assert_console_message(stdout, expected)
session.search.assert_called_with(s_pattern, s_type, 'regexp')
def test_anon_handle_search_argument_error(self):
"""Test anon_handle_search function with argument error"""
s_type, s_patt = 'unknown', 'unknown'
cases = [
{'argument': [], 'error': 'Please specify search type'},
{'argument': [s_type], 'error': 'Please specify search pattern'},
{'argument': [s_type, s_patt],
'error': 'Unknown search type: %s' % s_type}
]
for case in cases:
expected = self.format_error_message(case['error'])
self.assert_system_exit(
anon_handle_search,
mock.MagicMock(),
mock.MagicMock(),
case['argument'],
stderr=expected,
activate_session=None)
def test_anon_handle_search_help(self):
self.assert_help(
anon_handle_search,
"""Usage: %s search [options] search_type pattern
Available search types: package, build, tag, target, user, host, rpm, maven, win
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
-r, --regex treat pattern as regex
--exact exact matches only
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,66 @@
from __future__ import absolute_import
import mock
import six
import unittest
from koji_cli.commands import handle_set_pkg_arches
from . import utils
class TestSetPkgArches(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s set-pkg-arches [options] arches tag package [package2 ...]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_set_pkg_arches(
self,
activate_session_mock,
stdout):
"""Test handle_set_pkg_arches function"""
session = mock.MagicMock()
options = mock.MagicMock()
arguments = ['x86_64', 'tag', '--force', 'bash', 'less', 'sed']
expected = self.format_error_message(
"Please specify an archlist, a tag, and at least one package")
# Case 1. argument error
self.assert_system_exit(
handle_set_pkg_arches,
options,
session,
[],
stderr=expected,
activate_session=None)
activate_session_mock.assert_not_called()
# Case 2. run set arch to x86_64
calls = [mock.call('tag', pkg, 'x86_64', force=True) for pkg in arguments[3:]]
handle_set_pkg_arches(options, session, arguments)
activate_session_mock.assert_called_with(session, options)
session.packageListSetArches.assert_has_calls(calls)
self.assert_console_message(stdout, '')
def test_handle_set_pkg_arches_help(self):
self.assert_help(
handle_set_pkg_arches,
"""Usage: %s set-pkg-arches [options] arches tag package [package2 ...]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--force Force operation
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,66 @@
from __future__ import absolute_import
import mock
import six
import unittest
from koji_cli.commands import handle_set_pkg_owner
from . import utils
class TestSetPkgOwner(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s set-pkg-owner [options] owner tag package [package2 ...]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_set_pkg_owner(
self,
activate_session_mock,
stdout):
"""Test handle_set_pkg_owner function"""
session = mock.MagicMock()
options = mock.MagicMock()
arguments = ['owner', 'tag', '--force', 'bash', 'less', 'sed']
expected = self.format_error_message(
"Please specify an owner, a tag, and at least one package")
# Case 1. argument error
self.assert_system_exit(
handle_set_pkg_owner,
options,
session,
[],
stderr=expected,
activate_session=None)
activate_session_mock.assert_not_called()
# Case 2. run set owner
calls = [mock.call('tag', pkg, 'owner', force=True) for pkg in arguments[3:]]
handle_set_pkg_owner(options, session, arguments)
activate_session_mock.assert_called_with(session, options)
session.packageListSetOwner.assert_has_calls(calls)
self.assert_console_message(stdout, '')
def test_handle_set_pkg_owner_help(self):
self.assert_help(
handle_set_pkg_owner,
"""Usage: %s set-pkg-owner [options] owner tag package [package2 ...]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--force Force operation
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,93 @@
from __future__ import absolute_import
import mock
import six
import unittest
from koji_cli.commands import handle_set_task_priority
from . import utils
class TestSetTaskPriority(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s set-task-priority [options] --priority=<priority> <task-id> [task-id]...
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_set_task_priority(
self,
activate_session_mock,
stdout):
"""Test handle_set_task_priority function"""
session = mock.MagicMock()
options = mock.MagicMock()
arguments = ['--priority', '10', '1', '11', '121', '1331']
# Case 1. no task id error
expected = self.format_error_message(
"You must specify at least one task id")
self.assert_system_exit(
handle_set_task_priority,
options,
session,
[],
stderr=expected,
activate_session=None)
activate_session_mock.assert_not_called()
# Case 2. no --priority is specified
expected = self.format_error_message(
"You must specify --priority")
self.assert_system_exit(
handle_set_task_priority,
options,
session,
['1'],
stderr=expected,
activate_session=None)
activate_session_mock.assert_not_called()
# Case 3 . Wrong task id (not integer format)
for case in ['1.0', '0.1', 'abc']:
expected = self.format_error_message(
"Task numbers must be integers")
self.assert_system_exit(
handle_set_task_priority,
options,
session,
[case, '--priority', '10'],
stderr=expected,
activate_session=None)
activate_session_mock.assert_not_called()
calls = [mock.call(int(tid), 10, False) for tid in arguments[2:]]
handle_set_task_priority(options, session, arguments)
activate_session_mock.assert_called_with(session, options)
session.setTaskPriority.assert_has_calls(calls)
self.assert_console_message(stdout, '')
def test_handle_set_task_priority_help(self):
self.assert_help(
handle_set_task_priority,
"""Usage: %s set-task-priority [options] --priority=<priority> <task-id> [task-id]...
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--priority=PRIORITY New priority
--recurse Change priority of child tasks as well
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,648 @@
from __future__ import absolute_import, print_function
import mock
import six
import unittest
import koji
import collections
import time
from koji_cli.commands import anon_handle_taskinfo, \
_printTaskInfo, _parseTaskParams
from . import utils
class TestParseTaskParams(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.session = mock.MagicMock()
self.build_templ = {
'package_name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'epoch': None,
'nvr': 'bash-4.4.12-5.fc26',
'build_id': 1,
}
def __run_parseTask_test(self, method, params, expect):
self.session.getTaskRequest.return_value = params
lines = _parseTaskParams(self.session, method, 1, '/mnt/koji')
self.assertEquals(lines, expect)
def test_error_with_param(self):
params = []
expect = ['Unable to parse task parameters']
with mock.patch('koji_cli.commands.logging.getLogger', create=True) as get_logger_mock:
h = get_logger_mock()
h.isEnabledFor.return_value = True
self.__run_parseTask_test('buildSRPMFromCVS', params, expect)
logger = get_logger_mock()
logger.isEnabledFor.assert_called_once()
logger.debug.assert_called_once()
def test_buildSRPMFrom(self):
# from CVS case
params = ['cvs.src.org']
expect = ["CVS URL: %s" % params[0]]
self.__run_parseTask_test('buildSRPMFromCVS', params, expect)
# from SCM case
params = ['git.github.com']
expect = ["SCM URL: %s" % params[0]]
self.__run_parseTask_test('buildSRPMFromSCM', params, expect)
def test_buildArch(self):
name = 'TEST'
topdir = '/mnt/koji'
self.session.getTag.return_value = {'name': name}
params = ['path/to/bash-4.4.12-5.fc26.src.rpm', 2, 'x86_64', True, {'repo_id': 1}]
expect = ["SRPM: %s/work/%s" % (topdir, params[0])]
expect.append("Build Tag: %s" % name)
expect.append("Build Arch: %s" % params[2])
expect.append("SRPM Kept: %r" % params[3])
expect.append("Options:")
expect.append(" repo_id: 1")
self.__run_parseTask_test('buildArch', params, expect)
def test_tagBuild(self):
params = [1, 1, False, None, True]
self.session.getTag.return_value = {'name': 'fedora'}
self.session.getBuild.return_value = self.build_templ
expect = ["Destination Tag: fedora"]
expect.append("Build: bash-4.4.12-5.fc26")
self.__run_parseTask_test('tagBuild', params, expect)
def test_buildNotification(self):
params = [['r1', 'r2'],
self.build_templ,
{'name': 'fedora'},
'fedoraproject.org']
expect = ["Recipients: %s" % (", ".join(params[0]))]
expect.append("Build: %s" % self.build_templ['nvr'])
expect.append("Build Target: %s" % params[2]['name'])
expect.append("Web URL: %s" % params[3])
self.__run_parseTask_test('buildNotification', params, expect)
def test_build(self):
params = ['path/to/bash-4.4.12-5.fc26.src.rpm',
'fedora26-build',
{'build-test': True}]
expect = ["Source: %s" % params[0]]
expect.append("Build Target: %s" % params[1])
expect.append("Options:")
expect.append(" build-test: True")
self.__run_parseTask_test('build', params, expect)
def test_maven(self):
params = ['scm.maven.org', 'maven-target', {'maven-test': True}]
expect = ["SCM URL: %s" % params[0]]
expect.append("Build Target: %s" % params[1])
expect.append("Options:")
expect.append(" maven-test: True")
self.__run_parseTask_test('maven', params, expect)
def test_buildMaven(self):
params = ['scm.maven.org', {'name': 'maven-tag'}, {'build-test': True}]
expect = ["SCM URL: %s" % params[0]]
expect.append("Build Tag: %s" % params[1]['name'])
expect.append("Options:")
expect.append(" build-test: True")
self.__run_parseTask_test('buildMaven', params, expect)
def test_wrapperRPM(self):
target = 'test-target'
params = ['http://path.to/pkg.spec', {'name': 'build-tag'},
self.build_templ,
{
'id': 1,
'method': 'wrapperRPM',
'arch': 'x86_64',
'request': [1, {'name': target}, self.build_templ]
},
{'wrapRPM-test': True}]
expect = ["Spec File URL: %s" % params[0]]
expect.append("Build Tag: %s" % params[1]['name'])
expect.append("Build: %s" % self.build_templ['nvr'])
task_info = "wrapperRPM (%s, %s)" % (target, self.build_templ['nvr'])
expect.append("Task: %s %s" % (params[3]['id'], task_info))
expect.append("Options:")
expect.append(" wrapRPM-test: True")
self.__run_parseTask_test('wrapperRPM', params, expect)
def test_chainmaven(self):
params = [{
'maven-pkg-1': {'build-opt': '--test'},
'maven-pkg-2': {'build-opt': '-O2'},
},
'build-target',
{'chainmaven-test': True}]
expect = ["Builds:"]
for pkg, opt in params[0].items():
expect.append(" %s" % pkg)
for k, v in opt.items():
expect.append(" %s: %s" % (k, v))
expect.append("Build Target: %s" % params[1])
expect.append("Options:")
expect.append(" chainmaven-test: True")
self.__run_parseTask_test('chainmaven', params, expect)
def test_winbuild(self):
params = ['vm-builder', 'github.com', 'target',
{'winver': 10}]
expect = ["VM: %s" % params[0]]
expect.append("SCM URL: %s" % params[1])
expect.append("Build Target: %s" % params[2])
expect.append("Options:")
expect.append(" winver: 10")
self.__run_parseTask_test('winbuild', params, expect)
def test_vmExec(self):
params = ['vm-name',
['x86_64', {'cpu': 'GenuineIntel'}],
{'optimize': '-O3'}]
expect = ["VM: %s" % params[0]]
expect.append("Exec Params:")
expect.append(" x86_64")
expect.append(" cpu: GenuineIntel")
expect.append("Options:")
expect.append(" optimize: -O3")
self.__run_parseTask_test('vmExec', params, expect)
def test_createXXX(self):
params = ['name', '1.0', '1', 'x86_64', 'target_info',
'build-tag', 'Repo', 'kickstart.ks', {}]
fields = ['Name', 'Version', 'Release', 'Arch', 'Target Info',
'Build Tag', 'Repo', 'Kickstart File']
template = list()
for n, v in zip(fields, params):
template.append("%s: %s" % (n, v))
for method in ('createLiveCD', 'createAppliance', 'createLiveMedia'):
params[-1] = {"extra": "test-%s" % method}
expect = list(template)
expect.append("Options:")
expect.append(" extra: test-%s" % method)
self.__run_parseTask_test(method, params, expect)
def test_appliance_livecd_livemedia(self):
params = ['name', '1.0', 'x86_64, ppc64', 'target_info',
'kickstart.ks', {}]
fields = ['Name', 'Version', 'Arches', 'Target', 'Kickstart']
template = list()
for n, v in zip(fields, params):
template.append("%s: %s" % (n, v))
for method in ('appliance', 'livecd', 'livemedia'):
params[-1] = {"extra": "test-%s" % method}
expect = list(template)
expect.append("Options:")
expect.append(" extra: test-%s" % method)
self.__run_parseTask_test(method, params, expect)
def test_newRepo(self):
params = [0]
self.session.getTag.return_value = {'name': 'f26'}
expect = ['Tag: f26']
self.__run_parseTask_test('newRepo', params, expect)
def test_prepRepo(self):
params = [{'name': 'f26'}]
expect = ['Tag: f26']
self.__run_parseTask_test('prepRepo', params, expect)
def test_createRepo(self):
params = [1, 'x86_64', {'id': 1, 'creation_time': '1970-1-1 0:0:0'},
[{'external_repo_name': 'fedoraproject.net'},
{'external_repo_name': 'centos.org'}]]
expect = ["Repo ID: %i" % params[0]]
expect.append("Arch: %s" % params[1])
expect.append("Old Repo ID: %i" % params[2]['id'])
expect.append("Old Repo Creation: Thu, 01 Jan 1970")
expect.append("External Repos: %s" % ', '.join(
[ext['external_repo_name'] for ext in params[3]]))
with mock.patch('koji.formatTimeLong',
return_value='Thu, 01 Jan 1970'):
self.__run_parseTask_test('createrepo', params, expect)
def test_tagNotification(self):
params = [['kojiadmin', 'user'], # recipients
True, # successful
1, # dest tag id
2, # src tag id
3, # build id
0, # user id
False, # Ignoresuccess
'no error'] # Failure message
destTag, srcTag = {'name': 'dest-tag-1'}, {'name': 'src-tag-2'}
user = {'name': 'kojiadmin'}
self.session.getTag.side_effect = [destTag, srcTag]
self.session.getBuild.return_value = self.build_templ
self.session.getUser.return_value = user
expect = ["Recipients: %s" % ", ".join(params[0])]
expect.append("Successful?: %s" % (params[1] and 'yes' or 'no'))
expect.append("Tagged Into: %s" % destTag['name'])
expect.append("Moved From: %s" % srcTag['name'])
expect.append("Build: %s" % self.build_templ['nvr'])
expect.append("Tagged By: %s" % user['name'])
expect.append("Ignore Success?: %s" % (params[6] and 'yes' or 'no'))
expect.append("Failure Message: %s" % params[7])
self.__run_parseTask_test('tagNotification', params, expect)
def test_dependantTask(self):
params = [
[1, 2, 3, 4], # dependant task ids
[
['buildSRPMFromSCM', ['param1', 'param2'], {'scm': 'github.com'}],
['build', ['param1', 'param2'], {'arch': 'x86_64'}],
['tagBuild', ['tagname', 'param2'], {}]
]
]
expect = ["Dependant Tasks: %s" % ", ".join([str(dep) for dep in params[0]])]
expect.append("Subtasks:")
for subtask in params[1]:
expect.append(" Method: %s" % subtask[0])
expect.append(" Parameters: %s" % ", ".join(
[str(subparam) for subparam in subtask[1]]))
if subtask[2]:
expect.append(' Options:')
for k, v in subtask[2].items():
expect.append(' %s: %s' % (k, v))
expect.append('')
self.__run_parseTask_test('dependantTask', params, expect)
def test_chainbuild(self):
params = [[
['base-grp', 'desktop-grp'],
['base-grp', 'devel-grp'],
],
'f26',
{'extra': 'f26-pre-release'}]
expect = ["Build Groups:"]
for i, grp in enumerate(params[0]):
expect.append(' %i: %s' % (i+1, ', '.join(grp)))
expect.append("Build Target: %s" % params[1])
expect.append("Options:")
expect.append(" extra: f26-pre-release")
self.__run_parseTask_test('chainbuild', params, expect)
def test_waitrepo(self):
params = ['build-taget', True, ['bash-4.4.12-5.fc26']]
expect = ["Build Target: %s" % params[0]]
expect.append("Newer Than: %s" % params[1])
expect.append("NVRs: %s" % ', '.join(params[2]))
self.__run_parseTask_test('waitrepo', params, expect)
class TestPrintTaskInfo(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.host_info = {
"comment": None,
"arches": "x86_64",
"task_load": 0.0,
"capacity": 2.0,
"name": "kojibuilder",
"ready": True,
"user_id": 3,
"enabled": True,
"id": 1,
"description": None
}
self.task_info_templ = {
'weight': 0.1,
'parent': None,
'create_ts': 1000,
'start_ts': 2000,
'completion_ts': 3000,
'state': 2,
'awaited': None,
'label': None,
'priority': 15,
'channel_id': 2,
'waiting': False,
'id': 1,
'owner': 1,
'host_id': 1,
'arch': 'noarch',
'method': 'newRepo'
}
self.tag_info = {
'maven_support': False,
'locked': False,
'name': 'fedora26-build',
'extra': {},
'perm': None,
'id': 2,
'arches': 'x86_64',
'maven_include_all': False,
'perm_id': None
}
self.user_info = {
'status': 0,
'usertype': 0,
'id': 1,
'name': 'kojiadmin',
'krb_principal': None
}
@mock.patch('koji_cli.commands.list_task_output_all_volumes')
def test_printTaskInfo_create_repo(self, list_task_output_mock):
session = mock.MagicMock()
parent = self.task_info_templ.copy()
parent.update({
'method': 'newRepo',
'host_id': None,
})
children = self.task_info_templ.copy()
children.update({
'id': 2,
'parent': 1,
'request': [1, 'x86_64', None],
'label': 'x86_64',
'channel_id': 2,
'host_id': None,
'method': 'createrepo'
})
session.getTaskInfo.side_effect = [parent, children]
session.listBuildroots.return_value = {}
session.listBuilds.return_value = {}
session.getTag.return_value = self.tag_info
session.getUser.return_value = self.user_info
session.getTaskRequest.return_value = [1, 'x86_64', None]
session.getTaskChildren.side_effect = [[children], []]
list_task_output_mock.side_effect = [
{},
{
'mergerepos.log': ['DEFAULT'],
'createrepo.log': ['DEFAULT']
}
]
expected = """\
Task: 1
Type: newRepo
Request Parameters:
Tag: fedora26-build
Owner: kojiadmin
State: closed
Created: Thu Jan 1 00:16:40 1970
Started: Thu Jan 1 00:33:20 1970
Finished: Thu Jan 1 00:50:00 1970
Task: 2
Type: createrepo
Request Parameters:
Repo ID: 1
Arch: x86_64
Owner: kojiadmin
State: closed
Created: Thu Jan 1 00:16:40 1970
Started: Thu Jan 1 00:33:20 1970
Finished: Thu Jan 1 00:50:00 1970
Log Files:
/mnt/koji/work/tasks/2/2/mergerepos.log
/mnt/koji/work/tasks/2/2/createrepo.log
"""
with mock.patch('sys.stdout', new_callable=six.StringIO) as stdout, \
mock.patch('time.localtime', new=time.gmtime):
_printTaskInfo(session, 1, '/mnt/koji')
self.assert_console_message(stdout, expected)
@mock.patch('koji_cli.commands.list_task_output_all_volumes')
def test_printTaskInfo_build_srpm(self, list_task_output_mock):
session = mock.MagicMock()
parent = self.task_info_templ.copy()
parent.update({
'method': 'build',
})
child_build = parent.copy()
child_build.update({
'id': 2,
'parent': 1,
'request': ['path/to/bash.src.rpm', 2, 'x86_64', True, {'repo_id': 1}],
'method': 'buildArch',
})
child_tag = parent.copy()
child_tag.update({
'id': 3,
'parent': 1,
'request': [1, 1, False, None, True],
'arch': 'noarch',
'method': 'tagBuild'
})
buildroot_info = [{
'id': 1,
'repo_id': 1,
'tag_name': 'fedora26-build',
'host_name': 'kojibuilder',
}]
build_info = [{
'package_name': 'bash',
'version': '4.4.12',
'release': '5.fc26',
'epoch': None,
'nvr': 'bash-4.4.12-5.fc26',
'build_id': 1,
}]
files = {
'bash-debuginfo-4.4.12-5.fc26.x86_64.rpm': ['DEFAULT'],
'hw_info.log': ['DEFAULT'],
'build.log': ['DEFAULT'],
'bash-4.4.12-5.fc26.src.rpm': ['DEFAULT'],
'root.log': ['DEFAULT'],
'state.log': ['DEFAULT'],
'mock_output.log': ['DEFAULT'],
'bash-4.4.12-5.fc26.x86_64.rpm': ['DEFAULT'],
'installed_pkgs.log': ['DEFAULT'],
'bash-doc-4.4.12-5.fc26.x86_64.rpm': ['DEFAULT']
}
# need ordered dict to get same results
files = collections.OrderedDict(sorted(files.items(),
key=lambda t: t[0]))
list_task_output_mock.side_effect = [[], files, {}]
session.getTaskInfo.side_effect = [parent, child_build, child_tag]
session.listBuildroots.side_effect = [[], buildroot_info, []]
session.listBuilds.side_effect = [build_info, [], []]
session.getTag.return_value = self.tag_info
session.getUser.return_value = self.user_info
session.getBuild.return_value = build_info[0]
session.getHost.return_value = self.host_info
session.getTaskRequest.side_effect = [
['path/to/bash-4.4.12-5.fc26.src.rpm', 'fedora26-build', {}],
['path/to/bash-4.4.12-5.fc26.src.rpm', 2, 'x86_64', True, {'repo_id': 1}],
[1, 1, False, None, True]
]
session.getTaskChildren.side_effect = [[child_build, child_tag], [], []]
expected = """\
Task: 1
Type: build
Request Parameters:
Source: path/to/bash-4.4.12-5.fc26.src.rpm
Build Target: fedora26-build
Owner: kojiadmin
State: closed
Created: Thu Jan 1 00:16:40 1970
Started: Thu Jan 1 00:33:20 1970
Finished: Thu Jan 1 00:50:00 1970
Host: kojibuilder
Build: bash-4.4.12-5.fc26 (1)
Task: 2
Type: buildArch
Request Parameters:
SRPM: /mnt/koji/work/path/to/bash-4.4.12-5.fc26.src.rpm
Build Tag: fedora26-build
Build Arch: x86_64
SRPM Kept: True
Options:
repo_id: 1
Owner: kojiadmin
State: closed
Created: Thu Jan 1 00:16:40 1970
Started: Thu Jan 1 00:33:20 1970
Finished: Thu Jan 1 00:50:00 1970
Host: kojibuilder
Buildroots:
/var/lib/mock/fedora26-build-1-1/
Log Files:
/mnt/koji/work/tasks/2/2/build.log
/mnt/koji/work/tasks/2/2/hw_info.log
/mnt/koji/work/tasks/2/2/installed_pkgs.log
/mnt/koji/work/tasks/2/2/mock_output.log
/mnt/koji/work/tasks/2/2/root.log
/mnt/koji/work/tasks/2/2/state.log
Output:
/mnt/koji/work/tasks/2/2/bash-4.4.12-5.fc26.src.rpm
/mnt/koji/work/tasks/2/2/bash-4.4.12-5.fc26.x86_64.rpm
/mnt/koji/work/tasks/2/2/bash-debuginfo-4.4.12-5.fc26.x86_64.rpm
/mnt/koji/work/tasks/2/2/bash-doc-4.4.12-5.fc26.x86_64.rpm
Task: 3
Type: tagBuild
Request Parameters:
Destination Tag: fedora26-build
Build: bash-4.4.12-5.fc26
Owner: kojiadmin
State: closed
Created: Thu Jan 1 00:16:40 1970
Started: Thu Jan 1 00:33:20 1970
Finished: Thu Jan 1 00:50:00 1970
Host: kojibuilder
"""
with mock.patch('sys.stdout', new_callable=six.StringIO) as stdout, \
mock.patch('time.localtime', new=time.gmtime):
_printTaskInfo(session, 1, '/mnt/koji')
self.assert_console_message(stdout, expected)
def test_printTaskInfo_no_task(self):
"""Test _printTaskInfo with no task found"""
session = mock.MagicMock()
task_id = 1
session.getTaskInfo.return_value = None
with self.assertRaises(koji.GenericError) as cm:
_printTaskInfo(session, task_id, '/')
self.assertEquals(str(cm.exception), "No such task: %d" % task_id)
class TestTaskInfo(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s taskinfo [options] taskID [taskID...]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_anon_handle_taskinfo(
self,
activate_session_mock,
stdout):
"""Test anon_handle_taskinfo function"""
session = mock.MagicMock()
options = mock.MagicMock()
# Case 1. no task id error
expected = self.format_error_message(
"You must specify at least one task ID")
self.assert_system_exit(
anon_handle_taskinfo,
options,
session,
[],
stderr=expected,
activate_session=None)
activate_session_mock.assert_not_called()
# Case 2. show task info
task_output = """Task: 1
Type: newRepo
Owner: kojiadmin
State: closed
Created: Thu Nov 16 17:34:29 2017
Started: Thu Nov 16 17:51:07 2017
Finished: Thu Nov 16 17:54:55 2017
Host: kojibuilder
"""
def print_task(*args, **kwargs):
print(task_output, end='')
with mock.patch('koji_cli.commands._printTaskInfo', new=print_task):
anon_handle_taskinfo(options, session, ['1'])
self.assert_console_message(stdout, task_output)
def test_anon_handle_taskinfo_help(self):
self.assert_help(
anon_handle_taskinfo,
"""Usage: %s taskinfo [options] taskID [taskID...]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
-r, --recurse Show children of this task as well
-v, --verbose Be verbose
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,65 @@
from __future__ import absolute_import
import mock
import six
import unittest
from koji_cli.commands import handle_unblock_pkg
from . import utils
class TestUnblockPkg(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def setUp(self):
self.error_format = """Usage: %s unblock-pkg [options] tag package [package2 ...]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_unblock_pkg(
self,
activate_session_mock,
stdout):
"""Test handle_unblock_pkg function"""
session = mock.MagicMock()
options = mock.MagicMock()
arguments = ['tag', 'bash', 'less', 'sed']
expected = self.format_error_message(
"Please specify a tag and at least one package")
# Case 1. argument error
self.assert_system_exit(
handle_unblock_pkg,
options,
session,
[],
stderr=expected,
activate_session=None)
activate_session_mock.assert_not_called()
# Case 2. run unlock
calls = [mock.call('tag', pkg) for pkg in arguments[1:]]
handle_unblock_pkg(options, session, arguments)
activate_session_mock.assert_called_with(session, options)
session.packageListUnblock.assert_has_calls(calls)
self.assert_console_message(stdout, '')
def test_handle_unblock_pkg_help(self):
self.assert_help(
handle_unblock_pkg,
"""Usage: %s unblock-pkg [options] tag package [package2 ...]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,249 @@
from __future__ import absolute_import
import mock
import six
import unittest
import koji
import hashlib
from mock import call
from koji_cli.commands import handle_write_signed_rpm
from . import utils
import os
QUERY_RPM_RESULTS = [
{'sigkey': '64dab85d', 'sighash': '7141c84f059d2f0722ff545051b2981d', 'rpm_id': 1},
{'sigkey': '64dab85d', 'sighash': '65e5dc6e8690fc7a9a5453029d33f5b6', 'rpm_id': 2},
{'sigkey': '64dab85d', 'sighash': '522d23faed3fe55866caa9a7e72c2c94', 'rpm_id': 3},
{'sigkey': '64dab85d', 'sighash': 'e9e7c107379cff28a0732a6304a5741e', 'rpm_id': 4}
]
GET_RPM_RESULTS = [
{
'build_id': 1,
'name': 'bash',
'extra': None,
'external_repo_id': 0,
'buildtime': 1496119944,
'id': 1,
'epoch': None,
'version': '4.4.12',
'buildroot_id': None,
'metadata_only': False,
'release': '5.fc26',
'arch': 'src',
'payloadhash': '8a1705e86bef1516565b029c73ab7fdd',
'external_repo_name': 'INTERNAL',
'size': 9462614
}, {
'build_id': 2,
'name': 'less',
'extra': None,
'external_repo_id': 0,
'buildtime': 1494946131,
'id': 2, 'epoch': None,
'version': '487',
'buildroot_id': None,
'metadata_only': False,
'release': '3.fc26',
'arch': 'src',
'payloadhash': '860aa8152af63ab3868af3596764d46e',
'external_repo_name': 'INTERNAL',
'size': 358835
}, {
'build_id': 3,
'name': 'sed',
'extra': None,
'external_repo_id': 0,
'buildtime': 1486654285,
'id': 3,
'epoch': None,
'version': '4.4',
'buildroot_id': None,
'metadata_only': False,
'release': '1.fc26',
'arch': 'src',
'payloadhash': 'beb5ac98593fa1047941ad771eb88497',
'external_repo_name': 'INTERNAL',
'size': 1262814
}, {
'build_id': 1,
'name': 'bash',
'extra': None,
'external_repo_id': 0,
'buildtime': 1496120170,
'id': 4,
'epoch': None,
'version': '4.4.12',
'buildroot_id': None,
'metadata_only': False,
'release': '5.fc26',
'arch': 'x86_64',
'payloadhash': '4a15ca94b1ac59d5ef9fc6d50ad73f3e',
'external_repo_name': 'INTERNAL',
'size': 1611874
}
]
class TestWriteSignedRPM(utils.CliTestCase):
# Show long diffs in error output...
maxDiff = None
def md5sum(self, message):
md5 = hashlib.md5()
md5.update(message.encode('utf-8'))
return md5.hexdigest()
def mock_os_path_exists(self, filepath):
if filepath in self.custom_os_path_exists:
return self.custom_os_path_exists[filepath]
return self.os_path_exists(filepath)
def setUp(self):
self.custom_os_path_exists = {}
self.os_path_exists = os.path.exists
self.error_format = """Usage: %s write-signed-rpm [options] <signature-key> n-v-r [n-v-r...]
(Specify the --help global option for a list of other help options)
%s: error: {message}
""" % (self.progname, self.progname)
@mock.patch('sys.stdout', new_callable=six.StringIO)
@mock.patch('koji_cli.commands.activate_session')
def test_handle_write_signed_rpm(
self,
activate_session_mock,
stdout):
"""Test handle_write_signed_rpm function"""
fake_sigkey = '64dab85d'
arguments = [fake_sigkey]
options = mock.MagicMock()
session = mock.MagicMock()
def get_expect_data(rpm_data):
expected = ''
calls = []
for i, data in enumerate(rpm_data):
nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % data
expected += "[%d/%d] %s" % (i+1, len(rpm_data), nvra) + "\n"
calls.append(call(data['id'], fake_sigkey))
return expected, calls
# Case 1, specifies N-V-R-A or N-V-R format RPM
# result: write sigkey to specified RPMs
rpm_data = [GET_RPM_RESULTS[0], GET_RPM_RESULTS[3]]
session.getRPM.side_effect = [
rpm_data[0], # bash-4.4.12-5.fc26.src
koji.GenericError # bash-4.4.12-5.fc26
]
session.getBuild.return_value = {
'package_name': 'bash',
'id': 1,
'version': '4.4.12',
'nvr': 'bash-4.4.12-5.fc26',
'name': 'bash',
'release': '5.fc26'
}
session.listRPMs.return_value = [rpm_data[1]] # bash-4.4.12-5.fc26
args = arguments + ['bash-4.4.12-5.fc26.src', 'bash-4.4.12-5.fc26']
expect_msg, expect_calls = get_expect_data(rpm_data)
handle_write_signed_rpm(options, session, args)
self.assert_console_message(stdout, expect_msg)
session.writeSignedRPM.assert_has_calls(expect_calls)
session.queryRPMSigs.assert_not_called()
# Case 2, with --all option
# result: write sigkey to all RPMS
session.queryRPMSigs.return_value = QUERY_RPM_RESULTS
session.getRPM.side_effect = GET_RPM_RESULTS
expect_msg, expect_calls = get_expect_data(GET_RPM_RESULTS)
handle_write_signed_rpm(options, session, arguments + ['--all'])
self.assert_console_message(stdout, expect_msg)
session.writeSignedRPM.assert_has_calls(expect_calls)
session.queryRPMSigs.assert_called_with(sigkey=fake_sigkey)
session.queryRPMSigs.reset_mock()
# Case 3, with --buildid option
# result: write sigkey to specified build id RPM
rpm_data = [
GET_RPM_RESULTS[0], # build_id = 1
GET_RPM_RESULTS[3]] # build_id = 1
session.listRPMs.return_value = rpm_data
expect_msg, expect_calls = get_expect_data(rpm_data)
handle_write_signed_rpm(options, session, arguments + ['--buildid', '1'])
self.assert_console_message(stdout, expect_msg)
session.listRPMs.assert_called_with(1)
session.queryRPMSigs.assert_not_called()
session.writeSignedRPM.assert_has_calls(expect_calls)
session.listRPM.reset_mock()
session.writeSignedRPM.reset_mock()
# Case 4, RPM not exist
# result: raise koji.GenericError
session.getRPM.side_effect = koji.GenericError('fake-get-rpm-error')
session.getBuild.return_value = None
args = arguments + ['gawk-4.1.4-3.fc26.x86_64']
with self.assertRaises(koji.GenericError) as cm:
handle_write_signed_rpm(options, session, args)
self.assertEqual(
str(cm.exception),
'No such rpm or build: %s' % args[1])
session.listRPM.assert_not_called()
session.queryRPMSigs.assert_not_called()
session.writeSignedRPM.assert_not_called()
def test_handle_write_signed_rpm_argument_test(self):
"""Test handle_write_signed_rpm function without arguments"""
options = mock.MagicMock()
session = mock.MagicMock()
# Case 1. empty argument
expected = self.format_error_message(
"A signature key must be specified")
self.assert_system_exit(
handle_write_signed_rpm,
options,
session,
[],
stderr=expected,
activate_session=None)
# Case 2. no RPM package is specified
arguments = ['fake-signature-key']
expected = self.format_error_message(
"At least one RPM must be specified")
self.assert_system_exit(
handle_write_signed_rpm,
options,
session,
arguments,
stderr=expected,
activate_session=None)
def test_handle_write_signed_rpm_help(self):
"""Test handle_write_signed_rpm help message"""
self.assert_help(
handle_write_signed_rpm,
"""Usage: %s write-signed-rpm [options] <signature-key> n-v-r [n-v-r...]
(Specify the --help global option for a list of other help options)
Options:
-h, --help show this help message and exit
--all Write out all RPMs signed with this key
--buildid=BUILDID Specify a build id rather than an n-v-r
""" % self.progname)
if __name__ == '__main__':
unittest.main()

View file

@ -1,3 +1,4 @@
from __future__ import print_function
import os
import sys
import six
@ -35,6 +36,8 @@ class CliTestCase(unittest.TestCase):
# public attribute
progname = os.path.basename(sys.argv[0]) or 'koji'
error_format = None
STDOUT = sys.stdout
STDERR = sys.stderr
#
# private methods
@ -51,8 +54,16 @@ class CliTestCase(unittest.TestCase):
return self.error_format.format(message=error_message) \
if self.error_format else error_message
def print_message(self, *args, **kwargs):
"""Print message on sys.stdout
This function will not be influenced when sys.stdout is mocked.
"""
kwargs['file'] = self.STDOUT
print(" ".join(map(str, args)), **kwargs)
def assert_function_wrapper(self, callableObj, *args, **kwargs):
"""wrapper func with anonymous funtion without argument"""
"""Wrapper func with anonymous funtion without argument"""
self.__assert_callable(callableObj)
return lambda: callableObj(*args, **kwargs)
@ -188,11 +199,8 @@ class CliTestCase(unittest.TestCase):
activate_session_mock.assert_not_called()
#
# Open /dev/stdout and write message directly.
# This function is useful when sys.stdout is redirected
#
def debug_print(message):
with open('/dev/stdout', 'w') as stdout:
stdout.write(message)
stdout.write('\n')
def get_builtin_open():
if six.PY2:
return '__builtin__.open'
else:
return 'builtins.open'