rpm-ch: add unit tests for the command line tool
authorMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Tue, 25 Feb 2014 10:20:24 +0000 (12:20 +0200)
committerMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Thu, 5 Jun 2014 11:20:08 +0000 (14:20 +0300)
Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
tests/component/rpm/data
tests/component/rpm/test_rpm_ch.py [new file with mode: 0644]

index b505e80..e59a3ef 160000 (submodule)
@@ -1 +1 @@
-Subproject commit b505e8016f5a5003fdfa12619600df91e1a8ae97
+Subproject commit e59a3efb43993c24c5d7c2e2f354806ec419ed90
diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py
new file mode 100644 (file)
index 0000000..968cfd4
--- /dev/null
@@ -0,0 +1,401 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 Intel Corporation <markus.lehtonen@linux.intel.com>
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+"""Tests for the git-rpm-ch tool"""
+
+import os
+import re
+from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
+
+from gbp.scripts.rpm_ch import main as rpm_ch
+from gbp.git import GitRepository
+
+from tests.component.rpm import RpmRepoTestBase
+
+# Disable "Method could be a function warning"
+# pylint: disable=R0201
+
+
+def mock_ch(args):
+    """Wrapper for git-rpm-ch"""
+
+    return rpm_ch(['arg0', '--packaging-branch=master',
+                   '--spawn-editor=never'] + args)
+
+class TestRpmCh(RpmRepoTestBase):
+    """Basic tests for git-rpm-ch"""
+
+    def setup(self):
+        """Test case setup"""
+        super(TestRpmCh, self).setup()
+        # Set environment so that commits succeed without git config
+        os.environ['GIT_AUTHOR_NAME'] = 'My Name'
+        os.environ['GIT_COMMITTER_NAME'] = 'My Name'
+        os.environ['EMAIL'] = 'me@example.com'
+
+    @staticmethod
+    def read_file(filename):
+        """Read file to a list"""
+        with open(filename) as fobj:
+            return fobj.readlines()
+
+    def test_invalid_args(self):
+        """See that git-rpm-ch fails gracefully when called with invalid args"""
+        GitRepository.create('.')
+
+        with assert_raises(SystemExit):
+            mock_ch(['--invalid-opt'])
+
+    def test_import_outside_repo(self):
+        """Run git-rpm-ch when not in a git repository"""
+        eq_(mock_ch([]), 1)
+        self._check_log(0, 'gbp:error: No Git repository at ')
+
+    def test_invalid_config_file(self):
+        """Test invalid config file"""
+        # Create dummy invalid config file and run git-rpm-ch
+        GitRepository.create('.')
+        with open('.gbp.conf', 'w') as conffd:
+            conffd.write('foobar\n')
+        eq_(mock_ch([]), 1)
+        self._check_log(0, 'gbp:error: invalid config file: File contains no '
+                           'section headers.')
+
+    def test_update_spec_changelog(self):
+        """Test updating changelog in spec"""
+        repo = self.init_test_repo('gbp-test')
+        eq_(mock_ch([]), 0)
+        eq_(repo.status(), {' M': ['gbp-test.spec']})
+
+    def test_update_changes_file(self):
+        """Test updating a separate changes file"""
+        repo = self.init_test_repo('gbp-test-native')
+        eq_(mock_ch([]), 0)
+        eq_(repo.status(), {' M': ['packaging/gbp-test-native.changes']})
+
+    def test_create_spec_changelog(self):
+        """Test creating changelog in spec file"""
+        repo = self.init_test_repo('gbp-test2')
+        orig_content = self.read_file('packaging/gbp-test2.spec')
+
+        # Fails if no starting point is given
+        eq_(mock_ch([]), 1)
+        self._check_log(-1, "gbp:error: Couldn't determine starting point")
+
+        # Give starting point
+        eq_(mock_ch(['--since=HEAD^']), 0)
+        eq_(repo.status(), {' M': ['packaging/gbp-test2.spec']})
+        content = self.read_file('packaging/gbp-test2.spec')
+        # Should contain 4 lines (%changelog, header, 1 entry and an empty line)
+        eq_(len(content), len(orig_content) + 4)
+
+    def test_create_changes_file(self):
+        """Test creating a separate changes file"""
+        repo = self.init_test_repo('gbp-test2')
+
+        # Fails if no starting point is given
+        eq_(mock_ch(['--changelog-file=CHANGES']), 1)
+        self._check_log(-1, "gbp:error: Couldn't determine starting point")
+
+        # Give starting point
+        eq_(mock_ch(['--since=HEAD^', '--changelog-file=CHANGES']), 0)
+        eq_(repo.status(), {'??': ['packaging/gbp-test2.changes']})
+        content = self.read_file('packaging/gbp-test2.changes')
+        # Should contain 3 lines (header, 1 entry and an empty line)
+        eq_(len(content), 3)
+
+    def test_option_all(self):
+        """Test the --all cmdline option"""
+        repo = self.init_test_repo('gbp-test2')
+
+        eq_(mock_ch(['--changelog-file=CHANGES', '--all']), 0)
+        content = self.read_file('packaging/gbp-test2.changes')
+        # Should contain N+2 lines (header, N commits and an empty line)
+        commit_cnt = len(repo.get_commits(since=None, until='master'))
+        eq_(len(content), commit_cnt + 2)
+
+    def test_option_changelog_file(self):
+        """Test the --changelog-file cmdline option"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        # Guess changelog file
+        eq_(mock_ch(['--changelog-file=CHANGES']), 0)
+        eq_(repo.status(), {' M': ['packaging/gbp-test-native.changes']})
+
+        # Use spec file as changelog
+        eq_(mock_ch(['--changelog-file=SPEC', '--since=HEAD^']), 0)
+        eq_(repo.status(), {' M': ['packaging/gbp-test-native.changes',
+                                   'packaging/gbp-test-native.spec']})
+
+        # Arbitrary name
+        eq_(mock_ch(['--changelog-file=foo.changes', '--since=HEAD^']), 0)
+        eq_(repo.status(), {' M': ['packaging/gbp-test-native.changes',
+                                   'packaging/gbp-test-native.spec'],
+                            '??': ['foo.changes']})
+
+    def test_option_spec_file(self):
+        """Test the --spec-file cmdline option"""
+        repo = self.init_test_repo('gbp-test2')
+
+        eq_(mock_ch(['--spec-file=foo.spec']), 1)
+        self._check_log(-1, "gbp:error: Unable to read spec file")
+
+        eq_(mock_ch(['--spec-file=auto']), 1)
+        self._check_log(-1, "gbp:error: Multiple spec files found")
+
+        eq_(mock_ch(['--spec-file=packaging/gbp-test2.spec', '--since=HEAD^']),
+            0)
+        eq_(repo.status(), {' M': ['packaging/gbp-test2.spec']})
+
+    def test_option_packaging_dir(self):
+        """Test the --packaging-dir cmdline option"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        eq_(mock_ch(['--packaging-dir=foo']), 1)
+        self._check_log(-1, "gbp:error: No spec file found")
+
+        # Packaging dir should be taken from spec file if it is defined
+        eq_(mock_ch(['--packaging-dir', 'foo', '--spec-file',
+                     'packaging/gbp-test-native.spec']), 0)
+        eq_(repo.status(), {' M': ['packaging/gbp-test-native.changes']})
+
+    def test_branch_options(self):
+        """Test the --packaging-branch and --ignore-branch cmdline options"""
+        self.init_test_repo('gbp-test-native')
+
+        eq_(mock_ch(['--packaging-branch=foo']), 1)
+        self._check_log(-2, "gbp:error: You are not on branch 'foo'")
+
+        eq_(mock_ch(['--packaging-branch=foo', '--ignore-branch']), 0)
+
+    def test_option_no_release(self):
+        """Test the --no-release cmdline option"""
+        self.init_test_repo('gbp-test-native')
+        orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+        eq_(mock_ch(['--no-release']), 0)
+        content = self.read_file('packaging/gbp-test-native.changes')
+        # Only one line (entry) added
+        eq_(len(content), len(orig_content) + 1)
+
+    def test_author(self):
+        """Test determining the author name/email"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        # Test taking email address from env
+        os.environ['EMAIL'] = 'user@host.com'
+        eq_(mock_ch([]), 0)
+        header = self.read_file('packaging/gbp-test-native.changes')[0]
+        ok_(re.match(r'.+ <user@host\.com> .+', header))
+
+        # Missing git config setting should not cause a failure
+        del os.environ['EMAIL']
+        del os.environ['GIT_AUTHOR_NAME']
+        os.environ['GIT_CONFIG_NOSYSTEM'] = '1'
+        os.environ['HOME'] = os.path.abspath('.')
+        eq_(mock_ch(['--git-author', '--since=HEAD^1']), 0)
+
+        # Test the --git-author option
+        with open(os.path.join(repo.git_dir, 'config'), 'a') as fobj:
+            fobj.write('[user]\n  name=John Doe\n  email=jd@host.com\n')
+        eq_(mock_ch(['--git-author', '--since=HEAD^']), 0)
+        header = self.read_file('packaging/gbp-test-native.changes')[0]
+        ok_(re.match(r'.+ John Doe <jd@host\.com> .+', header), header)
+
+    def test_option_full(self):
+        """Test the --full cmdline option"""
+        repo = self.init_test_repo('gbp-test-native')
+        orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+        eq_(mock_ch(['--full', '--since=HEAD^']), 0)
+        commit_msg_body = repo.get_commit_info('HEAD')['body']
+        full_msg = [line for line in commit_msg_body.splitlines() if line]
+        content = self.read_file('packaging/gbp-test-native.changes')
+        # New lines: header, 1 entry "header", entry "body" from commit message
+        # and one empty line
+        eq_(len(content), len(orig_content) + 3 + len(full_msg))
+
+    def test_option_ignore_regex(self):
+        """Test the --ignore-regex cmdline option"""
+        repo = self.init_test_repo('gbp-test-native')
+        orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+        eq_(mock_ch(['--full', '--since', 'HEAD^', '--ignore-regex',
+                     'Signed-off-by:.*']), 0)
+        commit_msg_body = repo.get_commit_info('HEAD')['body']
+        full_msg = [line for line in commit_msg_body.splitlines() if
+                        (line and not line.startswith('Signed-off-by:'))]
+        content = self.read_file('packaging/gbp-test-native.changes')
+        # New lines: header, 1 entry "header", filtered entry "body" from
+        # commit message and one empty line
+        eq_(len(content), len(orig_content) + 3 + len(full_msg))
+
+    def test_option_id_len(self):
+        """Test the --id-len cmdline option"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        eq_(mock_ch(['--id-len=10']), 0)
+        commit_id = repo.rev_parse('HEAD', 10)
+        content = self.read_file('packaging/gbp-test-native.changes')
+        ok_(content[1].startswith('- [%s] ' % commit_id))
+
+    def test_option_changelog_revision(self):
+        """Test the --id-len cmdline option"""
+        self.init_test_repo('gbp-test-native')
+
+        # Test invalid format (unknown field)
+        eq_(mock_ch(['--changelog-revision=%(unknown_field)s']), 1)
+        self._check_log(-1, 'gbp:error: Unable to construct revision field')
+
+        # Test acceptable format
+        eq_(mock_ch(['--changelog-revision=foobar']), 0)
+        header = self.read_file('packaging/gbp-test-native.changes')[0]
+        ok_(re.match(r'.+ foobar$', header))
+
+    def test_tagging(self):
+        """Test commiting/tagging"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        # Update and commit+tag
+        eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 0)
+        ok_(repo.has_tag('new-tag'))
+        sha = repo.rev_parse('HEAD')
+        eq_(sha, repo.rev_parse('new-tag^0'))
+
+        # Should fail if the tag already exists
+        eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 1)
+
+        # Update and commit+tag
+        eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^',
+                     '--retag']), 0)
+        ok_(repo.has_tag('new-tag'))
+        sha2 = repo.rev_parse('HEAD')
+        ok_(sha2 != sha)
+        eq_(sha2, repo.rev_parse('new-tag^0'))
+
+    def test_tagging2(self):
+        """Test commiting/tagging spec file"""
+        repo = self.init_test_repo('gbp-test2')
+
+        # Check unclean repo
+        with open('untracked-file', 'w') as fobj:
+            fobj.write('this file is not tracked\n')
+        with open('README', 'a') as fobj:
+            fobj.write('some new content\n')
+
+        # Unstaged file (README) -> failure
+        eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 1)
+        self._check_log(-1, 'gbp:error: Please commit or stage your changes')
+
+        # Add file, update and commit+tag, untracked file should be ignored
+        repo.add_files('README')
+        eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 0)
+        ok_(repo.has_tag('new-tag'))
+        sha = repo.rev_parse('HEAD')
+        eq_(sha, repo.rev_parse('new-tag^0'))
+
+    def test_option_editor_cmd(self):
+        """Test the --editor-cmd and --spawn-editor cmdline options"""
+        repo = self.init_test_repo('gbp-test-native')
+        eq_(mock_ch(['--spawn-editor=release', '--editor-cmd=rm']), 0)
+        eq_(repo.status(), {' D': ['packaging/gbp-test-native.changes']})
+
+        repo.force_head('HEAD', hard=True)
+        ok_(repo.is_clean())
+
+        os.environ['EDITOR'] = 'rm'
+        eq_(mock_ch(['--spawn-editor=always', '--editor-cmd=']),
+            0)
+
+    def test_option_message(self):
+        """Test the --message cmdline option"""
+        self.init_test_repo('gbp-test-native')
+        orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+        eq_(mock_ch(['--message', 'my entry\nanother entry']), 0)
+        content = self.read_file('packaging/gbp-test-native.changes')
+        # Added header, two entries and a blank line
+        eq_(len(content), len(orig_content) + 4)
+        eq_(content[2], '- another entry\n')
+
+    def test_user_customizations(self):
+        """Test the user customizations"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        # Non-existent customization file
+        eq_(mock_ch(['--customizations=customizations.py']), 1)
+
+        # Create user customizations file
+        with open('customizations.py', 'w') as fobj:
+            fobj.write("class ChangelogEntryFormatter(object):\n")
+            fobj.write("    @classmethod\n")
+            fobj.write("    def compose(cls, commit_info, **kwargs):\n")
+            fobj.write("        return ['- %s' % commit_info['id']]\n")
+
+        eq_(mock_ch(['--customizations=customizations.py']), 0)
+        entry = self.read_file('packaging/gbp-test-native.changes')[1]
+        sha = repo.rev_parse('HEAD')
+        eq_(entry, '- %s\n' % sha)
+
+    def test_paths(self):
+        """Test tracking of certain paths only"""
+        repo = self.init_test_repo('gbp-test-native')
+        orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+        # Add new commit with known content
+        with open('new-file.txt', 'w') as fobj:
+            fobj.write('this is new content\n')
+        repo.add_files('new-file.txt')
+        repo.commit_staged('Add new file')
+
+        # Only track a non-existent file
+        eq_(mock_ch(['--since=HEAD^', 'non-existent-path']), 0)
+        content = self.read_file('packaging/gbp-test-native.changes')
+        # New lines: header and one empty line, no entries
+        eq_(len(content), len(orig_content) + 2)
+
+        # Track existing file
+        repo.force_head('HEAD', hard=True)
+        eq_(mock_ch(['--since=HEAD^', 'new-file.txt']), 0)
+        content = self.read_file('packaging/gbp-test-native.changes')
+        # New lines: header, one entry line and one empty line
+        eq_(len(content), len(orig_content) + 3)
+
+    def test_commit_guessing(self):
+        """Basic tests for guessing the starting point"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        # Check 'tagname' that is not found
+        eq_(mock_ch(['--changelog-revision=%(tagname)s']), 0)
+        self._check_log(0, 'gbp:warning: Changelog points to tagname')
+
+        # Check 'upstreamversion' and 'release' fields
+        repo.force_head('HEAD', hard=True)
+        eq_(mock_ch(['--changelog-revision=%(upstreamversion)s-%(release)s']),
+            0)
+
+    def test_commit_guessing_fail(self):
+        """Test for failure of start commit guessing"""
+        repo = self.init_test_repo('gbp-test-native')
+
+        # Add "very old" header to changelog
+        with open('packaging/gbp-test-native.changes', 'w') as ch_fp:
+            ch_fp.write('* Sat Jan 01 2000 User <user@host.com> 123\n- foo\n')
+        # rpm-ch should fail by not being able to find any commits before the
+        # last changelog section
+        eq_(mock_ch([]), 1)
+        self._check_log(-1, "gbp:error: Couldn't determine starting point")
+