From 084fbef8f6c16dff8f52c11d8f05dbaf7cc0ef58 Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 21 2017 15:59:35 +0000 Subject: [PATCH 1/9] Remove deprecated modules used in koji * ssl_login has already been migrated to python-requests. It does not make sense to import ssl from koji to detect failure. Instead, koji introduces method is_requests_cert_error to check if the failure SSL login is due to revoked or expired certificate. This method is what rpkg needs to replace original koji.ssl. * For krb_login, gssapi is the default way to login user and krbV is still used as a fallback mode when gssapi fails. So, in rpkg, not referencing koji.krbV to catch error will decouple rpkg from krbV and whatever gssapi or krbV is used in krb_login, it is transparent for rpkg and there is no dependency to krbV anymore. Signed-off-by: Chenxiong Qi --- diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py index 6e9d301..e22e87e 100644 --- a/pyrpkg/__init__.py +++ b/pyrpkg/__init__.py @@ -25,7 +25,6 @@ import shutil import six import sys import tempfile -import koji.ssl.SSLCommon import subprocess from six.moves import configparser @@ -340,13 +339,10 @@ class Commands(object): koji_config['ca'], koji_config['serverca'], proxyuser=self.runas) - except koji.ssl.SSLCommon.SSL.Error as error: - for (_, _, ssl_reason) in error.message: - # Use heuristic. Some OpenSSL libs doesn't store error - # codes - if 'certificate revoked' in ssl_reason or 'certificate expired' in ssl_reason: - self.log.info("Certificate is revoked or expired.") - raise rpkgAuthError('Could not auth with koji. Login failed: %s' % error) + except Exception as e: + if koji.is_requests_cert_error(e): + self.log.info("Certificate is revoked or expired.") + raise rpkgAuthError('Could not auth with koji. Login failed: %s' % e) # Or try password auth elif authtype == 'password' or self.password and authtype is None: @@ -364,8 +360,8 @@ class Commands(object): if self._load_krb_user(): try: session.krb_login(proxyuser=self.runas) - except koji.krbV.Krb5Error as e: - self.log.error('Kerberos authentication fails: %s', e.args[1]) + except Exception as e: + self.log.error('Kerberos authentication fails: %s', e) else: self.log.warning('Kerberos authentication is used, but you do not have a ' 'valid credential.') diff --git a/tests/test_commands.py b/tests/test_commands.py index 7c5fdb6..19d1dbd 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -8,6 +8,7 @@ import git import rpm from mock import patch from mock import Mock +from mock import PropertyMock from pyrpkg import rpkgError @@ -567,3 +568,93 @@ class TestLoadModuleNameFromSpecialPushURL(CommandTestCase): def test_load_module_name(self): cmd = self.make_commands(path=self.case_repo) self.assertEqual(os.path.basename(self.repo_path), cmd.module_name) + + +class TestLoginKojiSession(CommandTestCase): + """Test login_koji_session""" + + def setUp(self): + super(TestLoginKojiSession, self).setUp() + + self.cmd = self.make_commands() + self.cmd.log = Mock() + self.koji_config = { + 'authtype': 'ssl', + 'server': 'http://localhost/kojihub', + 'cert': '/path/to/cert', + 'ca': '/path/to/ca', + 'serverca': '/path/to/serverca', + } + self.session = Mock() + + @patch('pyrpkg.koji.is_requests_cert_error', return_value=True) + def test_ssl_login_cert_revoked_or_expired(self, is_requests_cert_error): + self.session.ssl_login.side_effect = Exception + + self.koji_config['authtype'] = 'ssl' + + self.assertRaises(rpkgError, + self.cmd.login_koji_session, + self.koji_config, self.session) + self.cmd.log.info.assert_called_once_with( + 'Certificate is revoked or expired.') + + def test_ssl_login(self): + self.koji_config['authtype'] = 'ssl' + + self.cmd.login_koji_session(self.koji_config, self.session) + + self.session.ssl_login.assert_called_once_with( + self.koji_config['cert'], + self.koji_config['ca'], + self.koji_config['serverca'], + proxyuser=None, + ) + + def test_runas_option_cannot_be_set_for_password_auth(self): + self.koji_config['authtype'] = 'password' + self.cmd.runas = 'user' + self.assertRaises(rpkgError, + self.cmd.login_koji_session, + self.koji_config, self.session) + + @patch('pyrpkg.Commands.user', new_callable=PropertyMock) + def test_password_login(self, user): + user.return_value = 'tester' + self.session.opts = {} + self.koji_config['authtype'] = 'password' + + self.cmd.login_koji_session(self.koji_config, self.session) + + self.assertEqual({'user': 'tester', 'password': None}, + self.session.opts) + self.session.login.assert_called_once() + + @patch('pyrpkg.Commands._load_krb_user', return_value=False) + def test_krb_login_fails_if_no_valid_credential(self, _load_krb_user): + self.koji_config['authtype'] = 'kerberos' + self.cmd.realms = ['FEDORAPROJECT.ORG'] + + self.cmd.login_koji_session(self.koji_config, self.session) + + self.session.krb_login.assert_not_called() + self.assertEqual(2, self.cmd.log.warning.call_count) + + @patch('pyrpkg.Commands._load_krb_user', return_value=True) + def test_krb_login_fails(self, _load_krb_user): + self.koji_config['authtype'] = 'kerberos' + # Simulate ClientSession.krb_login fails and error is raised. + self.session.krb_login.side_effect = Exception + + self.cmd.login_koji_session(self.koji_config, self.session) + + self.session.krb_login.assert_called_once_with(proxyuser=None) + self.cmd.log.error.assert_called_once() + + @patch('pyrpkg.Commands._load_krb_user', return_value=True) + def test_successful_krb_login(self, _load_krb_user): + self.koji_config['authtype'] = 'kerberos' + + self.cmd.login_koji_session(self.koji_config, self.session) + + self.session.krb_login.assert_called_once_with(proxyuser=None) From 5a900a01a81700e59efd88ee21f24b9d7c81f1d4 Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 22 2017 04:01:10 +0000 Subject: [PATCH 2/9] Do not actually run git-diff in tests It is unnecessary to run git-diff in tests as no output is caught to assert. Meanwhile, this can also avoid "terminal not fully functional" in some cases. Signed-off-by: Chenxiong Qi --- diff --git a/tests/test_cli.py b/tests/test_cli.py index 816002c..ab27b34 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -597,20 +597,30 @@ class TestDiff(CliTestCase): self.make_changes() - def test_diff(self): + @patch('pyrpkg.Commands._run_command') + @patch('pyrpkg.os.chdir') + def test_diff(self, chdir, _run_command): cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'diff'] with patch('sys.argv', new=cli_cmd): cli = self.new_cli() cli.diff() - def test_diff_cached(self): + self.assertEqual(2, chdir.call_count) + _run_command.assert_called_once_with(['git', 'diff']) + + @patch('pyrpkg.Commands._run_command') + @patch('pyrpkg.os.chdir') + def test_diff_cached(self, chdir, _run_command): cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'diff', '--cached'] with patch('sys.argv', new=cli_cmd): cli = self.new_cli() cli.diff() + self.assertEqual(2, chdir.call_count) + _run_command.assert_called_once_with(['git', 'diff', '--cached']) + class TestGimmeSpec(CliTestCase): From 5e2f2176888f4a09783f5b5b2e910981b09fdae5 Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 22 2017 05:11:15 +0000 Subject: [PATCH 3/9] Do not build srpm in test Just mock _run_command to avoid building srpm in test. Signed-off-by: Chenxiong Qi --- diff --git a/tests/test_cli.py b/tests/test_cli.py index ab27b34..ea69147 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -280,16 +280,19 @@ class TestPull(CliTestCase): class TestSrpm(CliTestCase): + """Test srpm command""" - def test_srpm(self): + @patch('pyrpkg.Commands._run_command') + def test_srpm(self, _run_command): cli_cmd = ['rpkg', '--path', self.cloned_repo_path, '--release', 'rhel-6', 'srpm'] with patch('sys.argv', new=cli_cmd): cli = self.new_cli() cli.srpm() - self.assertTrue(os.path.exists(os.path.join(self.cloned_repo_path, - 'docpkg-1.2-2.el6.src.rpm'))) + expected_cmd = ['rpmbuild'] + cli.cmd.rpmdefines + \ + ['--nodeps', '-bs', os.path.join(cli.cmd.path, cli.cmd.spec)] + _run_command.assert_called_once_with(expected_cmd, shell=True) class TestCompile(CliTestCase): From a2fe7540955c565562faed444d16b728d30b22ed Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 23 2017 10:02:24 +0000 Subject: [PATCH 4/9] Add requirements files Required requirements are separated into several files. Not all required packages can be installed from PyPI, including Python modules and underlying command line tool invoked in various commands. See also README.rst. Signed-off-by: Chenxiong Qi --- diff --git a/requirements/README.rst b/requirements/README.rst new file mode 100644 index 0000000..7dfe220 --- /dev/null +++ b/requirements/README.rst @@ -0,0 +1,15 @@ +Requirements +============ + +* fedora-py2.txt: contains Python 2 packages that are required to run rpkg and + tests, those are needed to be installed via package manager. + +* fedora-py3.txt: contains Python 3 packages that are required to run rpkg and + tests, those are needed to be installed via package manager. + +* pypi.txt: contains Python packages that can be installed from PyPI via + ``pip``. Some of required packages are not available in PyPI as of writing + this README file. They has to be installed from package manager too. + +* fedora-cli-tools.txt: contains command line tools that rpkg executes them in + various commands, e.g. rpmlint, rpmbuild and mock. \ No newline at end of file diff --git a/requirements/fedora-cli-tools.txt b/requirements/fedora-cli-tools.txt new file mode 100644 index 0000000..7922817 --- /dev/null +++ b/requirements/fedora-cli-tools.txt @@ -0,0 +1,6 @@ +# Command line tools needed for running specific commands + +copr-cli +mock +rpm-build +rpmlint diff --git a/requirements/fedora-py2.txt b/requirements/fedora-py2.txt new file mode 100644 index 0000000..83cbf3e --- /dev/null +++ b/requirements/fedora-py2.txt @@ -0,0 +1,13 @@ +python2-cccolutils +python2-GitPython +python2-koji +python2-pycurl +python-six +python2-rpm # rpm-python originally + +# For running tests +python2-coverage +python2-flake8 +python2-mock +python2-nose +python2-rpmfluff diff --git a/requirements/fedora-py3.txt b/requirements/fedora-py3.txt new file mode 100644 index 0000000..d03efa8 --- /dev/null +++ b/requirements/fedora-py3.txt @@ -0,0 +1,13 @@ +python3-cccolutils +python3-GitPython +python3-koji +python3-pycurl +python3-six +python3-rpm # rpm-python originally + +# For running tests +python3-coverage +python3-flake8 +python3-mock +python3-nose +python3-rpmfluff diff --git a/requirements/pypi.txt b/requirements/pypi.txt new file mode 100644 index 0000000..bc1e56a --- /dev/null +++ b/requirements/pypi.txt @@ -0,0 +1,27 @@ +# List of Python packages that can be installed from PyPI. + +cccolutils >= 1.4 +GitPython >= 0.2.0 +pycurl >= 7.43 +six >= 1.9.0 + +# Only required for <= Python 2.6 +argparse == 1.4.0 + +# For running tests +coverage == 4.4.1 +flake8 >= 2.5.5 +mock >= 2.0.0 +nose >= 1.3.7 +git+https://pagure.io/rpmfluff.git@0.5.1#egg=rpmfluff + +# Several package that are not available in PyPI are also listed here. +# If rpkg runs from a Python virtualenv, you may need --site-packages to create +# the environment so that following Python modules can be imported. +# +# Please see also fedora-py2.txt or fedora-py3.txt to install them in your +# system. +# +# koji +# mock +# rpm-python diff --git a/requirements/tests.txt b/requirements/tests.txt new file mode 100644 index 0000000..e7573f1 --- /dev/null +++ b/requirements/tests.txt @@ -0,0 +1,4 @@ +python2-nose +python2-mock +python2-rpmfluff +python2-coverage From 5da63cdb25b19236acb06b96aea98e09e708769a Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 23 2017 10:02:24 +0000 Subject: [PATCH 5/9] Make tests and covered code compatible with Py3 Signed-off-by: Chenxiong Qi --- diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py index e22e87e..def57f4 100644 --- a/pyrpkg/__init__.py +++ b/pyrpkg/__init__.py @@ -1015,7 +1015,10 @@ class Commands(object): archlist = [pkg.header['arch'] for pkg in hdr.packages] if not archlist: raise rpkgError('No compatible build arches found in %s' % spec) - return archlist + if six.PY3: + return [str(arch, encoding='utf-8') for arch in archlist] + else: + return archlist def _get_build_arches_from_srpm(self, srpm, arches): """Given the path to an srpm, determine the possible build arches @@ -1100,6 +1103,10 @@ class Commands(object): hdr = koji.get_rpm_header(srpm) name = hdr[rpm.RPMTAG_NAME] contents = hdr[rpm.RPMTAG_FILENAMES] + if six.PY3: + name = str(name, encoding='utf-8') + contents = [str(filename, encoding='utf-8') + for filename in contents] except Exception as e: raise rpkgError('Error querying srpm: {0}'.format(str(e))) @@ -1448,8 +1455,10 @@ class Commands(object): # We need something better for epel cmd = ['git', 'ls-remote', url, 'refs/heads/%s' % branch] try: - proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, - stdout=subprocess.PIPE) + proc = subprocess.Popen(cmd, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + universal_newlines=True) output, error = proc.communicate() except OSError as e: raise rpkgError(e) @@ -2009,7 +2018,8 @@ class Commands(object): cmd = ['rpm'] + self.rpmdefines + ['-q', '--qf', '"%{CHANGELOGTEXT}\n"', '--specfile', '"%s"' % spec_file] proc = subprocess.Popen(' '.join(cmd), shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) stdout, stderr = proc.communicate() if proc.returncode > 0: raise rpkgError(stderr.strip()) @@ -2456,14 +2466,17 @@ class Commands(object): # Get the content of spec into memory for fast searching with open(os.path.join(self.path, self.spec), 'r') as f: data = f.read() - try: - spec = data.decode('UTF-8') - except UnicodeDecodeError as error: - # when can't decode file, ignore chars and show warning - spec = data.decode('UTF-8', 'ignore') - line, offset = self._byte_offset_to_line_number(spec, error.start) - self.log.warning("'%s' codec can't decode byte in position %d:%d : %s", - error.encoding, line, offset, error.reason) + if six.PY2: + try: + spec = data.decode('UTF-8') + except UnicodeDecodeError as error: + # when can't decode file, ignore chars and show warning + spec = data.decode('UTF-8', 'ignore') + line, offset = self._byte_offset_to_line_number(spec, error.start) + self.log.warning("'%s' codec can't decode byte in position %d:%d : %s", + error.encoding, line, offset, error.reason) + else: + spec = data # Replace %{name} with the package name spec = spec.replace("%{name}", self.module_name) # Replace %{version} with the package version diff --git a/tests/test_cli.py b/tests/test_cli.py index ea69147..d4517cb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -6,6 +6,7 @@ import logging import os import rpmfluff import shutil +import six import sys import tempfile @@ -64,8 +65,8 @@ class CliTestCase(CommandTestCase): if not untracked and commit: cmds.append(['git', 'commit', '-m', 'Add new file {0}'.format(_filename)]) - if cmds: - map(lambda cmd: self.run_cmd(cmd, cwd=repo_path), cmds) + for cmd in cmds: + self.run_cmd(cmd, cwd=repo_path) class TestModuleNameOption(CliTestCase): @@ -163,7 +164,7 @@ class TestCommit(CliTestCase): def get_last_commit_message(self): repo = git.Repo(self.cloned_repo_path) - return repo.iter_commits().next().message.strip() + return six.next(repo.iter_commits()).message.strip() def cli_commit(self): """Run commit command""" @@ -255,8 +256,8 @@ class TestPull(CliTestCase): cli = self.new_cli() cli.pull() - origin_last_commit = str(git.Repo(self.repo_path).iter_commits().next()) - cloned_last_commit = str(cli.cmd.repo.iter_commits().next()) + origin_last_commit = str(six.next(git.Repo(self.repo_path).iter_commits())) + cloned_last_commit = str(six.next(cli.cmd.repo.iter_commits())) self.assertEqual(origin_last_commit, cloned_last_commit) def test_pull_rebase(self): @@ -264,7 +265,7 @@ class TestPull(CliTestCase): self.make_changes(repo=self.cloned_repo_path, commit=True, filename='README.rst', content='Hello teseting.') - origin_last_commit = str(git.Repo(self.repo_path).iter_commits().next()) + origin_last_commit = str(six.next(git.Repo(self.repo_path).iter_commits())) cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'pull', '--rebase'] @@ -273,8 +274,8 @@ class TestPull(CliTestCase): cli.pull() commits = cli.cmd.repo.iter_commits() - commits.next() - fetched_commit = str(commits.next()) + six.next(commits) + fetched_commit = str(six.next(commits)) self.assertEqual(origin_last_commit, fetched_commit) self.assertEqual('', cli.cmd.repo.git.log('--merges')) @@ -530,11 +531,11 @@ class TestSwitchBranch(CliTestCase): self.assertEqual('eng-rhel-6', repo.active_branch.name) def test_fail_on_dirty_repo(self): - self.make_changes() - repo = git.Repo(self.cloned_repo_path) self.checkout_branch(repo, 'eng-rhel-6') + self.make_changes() + cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'switch-branch', 'master'] with patch('sys.argv', new=cli_cmd): @@ -547,8 +548,6 @@ class TestSwitchBranch(CliTestCase): else: self.fail('switch branch on dirty repo should fail.') - self.assertEqual('eng-rhel-6', repo.active_branch.name) - def test_fail_switch_unknown_remote_branch(self): cli_cmd = ['rpkg', '--path', self.cloned_repo_path, 'switch-branch', 'unknown-remote-branch'] @@ -572,7 +571,8 @@ class TestUnusedPatches(CliTestCase): os.path.join(self.cloned_repo_path, '0001-add-new-feature.patch'), os.path.join(self.cloned_repo_path, '0002-hotfix.patch'), ) - map(self.write_file, self.patches) + for patch_file in self.patches: + self.write_file(patch_file) git.Repo(self.cloned_repo_path).index.add(self.patches) @patch('sys.stdout', new=StringIO()) @@ -710,7 +710,7 @@ class TestGitUrl(CliTestCase): cli = self.new_cli() cli.giturl() - last_commit = str(cli.cmd.repo.iter_commits().next()) + last_commit = str(six.next(cli.cmd.repo.iter_commits())) expected_giturl = '{0}?#{1}'.format( cli.cmd.anongiturl % {'module': os.path.basename(self.repo_path)}, last_commit) @@ -793,7 +793,10 @@ class LookasideCacheMock(object): def hash_file(self, filename): md5 = hashlib.md5() with open(filename, 'r') as f: - md5.update(f.read()) + content = f.read() + if six.PY3: + content = content.encode('utf-8') + md5.update(content) return md5.hexdigest() def assertFilesUploaded(self, filenames): @@ -948,7 +951,7 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase): # Gzip file that will be added into the SRPM self.docpkg_gz = os.path.join(self.cloned_repo_path, 'docpkg.gz') gzf = gzip.open(self.docpkg_gz, 'w') - gzf.write('file content of docpkg') + gzf.write(b'file content of docpkg') gzf.close() # Build the SRPM @@ -956,8 +959,10 @@ class TestImportSrpm(LookasideCacheMock, CliTestCase): self.build.add_changelog_entry('- New release 0.2-1', version='0.2', release='1', nameStr='tester ') self.build.add_simple_payload_file() - self.build.add_source(rpmfluff.SourceFile('docpkg.gz', - gzip.open(self.docpkg_gz, 'r').read())) + content = gzip.open(self.docpkg_gz, 'r').read() + if six.PY3: + content = str(content, encoding='utf-8') + self.build.add_source(rpmfluff.SourceFile('docpkg.gz', content)) self.build.make() self.srpm_file = self.build.get_built_srpm() diff --git a/tests/test_commands.py b/tests/test_commands.py index 19d1dbd..fe0f963 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -2,6 +2,7 @@ import os import shutil +import six import tempfile import git @@ -275,7 +276,7 @@ class CheckRepoWithOrWithoutDistOptionCase(CommandTestCase): cloned_repo = git.Repo(self.cloned_repo_path) cloned_repo.git.pull() cloned_repo.git.checkout('-b', private_branch, 'origin/%s' % private_branch) - for i in xrange(3): + for i in range(3): self.make_a_dummy_commit(cloned_repo) cloned_repo.git.push() @@ -388,7 +389,7 @@ class TestProperties(CommandTestCase): def test_commithash(self): cmd = self.make_commands(path=self.cloned_repo_path) repo = git.Repo(self.cloned_repo_path) - expected_commit_hash = str(repo.iter_commits().next()) + expected_commit_hash = str(six.next(repo.iter_commits())) self.assertEqual(expected_commit_hash, cmd.commithash) def test_dist(self): @@ -507,7 +508,7 @@ class TestGetLatestCommit(CommandTestCase): cmd.anongiturl = '/tmp/%(module)s' cmd.distgit_namespaced = False - self.assertEqual(str(git.Repo(self.repo_path).iter_commits().next()), + self.assertEqual(str(six.next(git.Repo(self.repo_path).iter_commits())), cmd.get_latest_commit(os.path.basename(self.repo_path), 'eng-rhel-6')) diff --git a/tests/test_lookaside.py b/tests/test_lookaside.py index 6cc4c04..5b4a3a6 100644 --- a/tests/test_lookaside.py +++ b/tests/test_lookaside.py @@ -95,7 +95,7 @@ class CGILookasideCacheTestCase(unittest.TestCase): lc = CGILookasideCache('sha512', 'http://example.com', '_') lc.download(name, filename, hash, outfile, hashtype='sha512') self.assertEqual(curl.perform.call_count, 1) - self.assertEqual(curlopts[pycurl.URL], full_url) + self.assertEqual(curlopts[pycurl.URL].decode('utf-8'), full_url) self.assertEqual(os.path.getmtime(outfile), 0) with open(outfile) as f: @@ -150,7 +150,7 @@ class CGILookasideCacheTestCase(unittest.TestCase): lc.download(name, filename, hash, outfile, hashtype='sha512', branch=branch) self.assertEqual(curl.perform.call_count, 1) - self.assertEqual(curlopts[pycurl.URL], full_url) + self.assertEqual(curlopts[pycurl.URL].decode('utf-8'), full_url) @mock.patch('pyrpkg.lookaside.pycurl.Curl') def test_download_corrupted(self, mock_curl): From b6623cc433247a53045b2cfd034da4130a3f4c1b Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 23 2017 10:39:16 +0000 Subject: [PATCH 6/9] Run tests in both Python 2 and 3 with tox Run all tests as a whole: tox Run tests in selected Python version: tox -e py35 Because part of dependencies are not available in PyPI, before running tests, install them in your system in advance, those packages are listed in pypi.txt. Signed-off-by: Chenxiong Qi --- diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..8dcb2b6 --- /dev/null +++ b/tox.ini @@ -0,0 +1,12 @@ +[tox] +envlist = py27,py35,flake8 + +[testenv] +sitepackages = True +deps = -r{toxinidir}/requirements/pypi.txt +commands = nosetests {posargs} + +[testenv:flake8] +basepython = python3 +deps = flake8 >= 2.5.5 +commands = flake8 pyrpkg/ tests/ From 6cc06d7f004e288a42d9984ec22ac961509b66a4 Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 23 2017 13:16:40 +0000 Subject: [PATCH 7/9] Replace unicode with six.text_type Signed-off-by: Chenxiong Qi --- diff --git a/pyrpkg/errors.py b/pyrpkg/errors.py index 62924a9..1378bf2 100644 --- a/pyrpkg/errors.py +++ b/pyrpkg/errors.py @@ -9,6 +9,8 @@ """Custom error classes""" +import six + class rpkgError(Exception): """Our base error class""" @@ -59,4 +61,4 @@ class UploadError(rpkgError): return str(self.message) def __unicode__(self): - return unicode(self.message) + return six.text_type(self.message) diff --git a/pyrpkg/lookaside.py b/pyrpkg/lookaside.py index 21beaa4..4d28b38 100644 --- a/pyrpkg/lookaside.py +++ b/pyrpkg/lookaside.py @@ -217,7 +217,7 @@ class CGILookasideCache(object): # type it would explode with "unsupported second type in tuple". Let's # convert to str just to be sure. # https://bugzilla.redhat.com/show_bug.cgi?id=1241059 - if six.PY2 and isinstance(filename, unicode): + if six.PY2 and isinstance(filename, six.text_type): filename = filename.encode('utf-8') post_data = [('name', name), @@ -288,9 +288,9 @@ class CGILookasideCache(object): # As in remote_file_exists, we need to convert unicode strings to str if six.PY2: - if isinstance(name, unicode): + if isinstance(name, six.text_type): name = name.encode('utf-8') - if isinstance(filepath, unicode): + if isinstance(filepath, six.text_type): filepath = filepath.encode('utf-8') if self.remote_file_exists(name, filename, hash): From 5f33873b29e32a53806a2df42ee24d83e0524824 Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 23 2017 16:45:58 +0000 Subject: [PATCH 8/9] Declare Python 3 versions to support in setup.py Signed-off-by: Chenxiong Qi --- diff --git a/setup.py b/setup.py index a8c3413..b907c42 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,8 @@ setup( 'Programming Language :: Python :: 2 :: Only', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Build Tools', 'Topic :: Software Development :: Libraries :: Python Modules', ), From a66d1e5a118cb0879c2381476f0e6ed848b2c72a Mon Sep 17 00:00:00 2001 From: Chenxiong Qi Date: Jul 24 2017 12:48:30 +0000 Subject: [PATCH 9/9] Add commands to whitelist_externals This can help to suppress warning of "not installed in virtualenv" if those commands are not installed in a virtualenv. Signed-off-by: Chenxiong Qi --- diff --git a/tox.ini b/tox.ini index 8dcb2b6..9729792 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,9 @@ envlist = py27,py35,flake8 [testenv] sitepackages = True deps = -r{toxinidir}/requirements/pypi.txt +whitelist_externals = + flake8 + nosetests commands = nosetests {posargs} [testenv:flake8]