diff --git a/.gitignore b/.gitignore index be9576b..94bb9db 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ pip-delete-this-directory.txt .tox/ .coverage .cache +.git-test-trees nosetests.xml coverage.xml diff --git a/git_upstream/tests/base.py b/git_upstream/tests/base.py index 4085d8e..809a526 100644 --- a/git_upstream/tests/base.py +++ b/git_upstream/tests/base.py @@ -1,5 +1,5 @@ # Copyright 2010-2011 OpenStack Foundation -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013-2016 Hewlett-Packard Enterprise Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -129,10 +129,14 @@ class DiveDir(fixtures.Fixture): class GitRepo(fixtures.Fixture): """Create an empty git repo in which to operate.""" + def __init__(self, path=None): + self.path = path + def _setUp(self): self._file_list = set() - tempdir = self.useFixture(fixtures.TempDir()) - self.path = os.path.join(tempdir.path, 'git') + if not self.path: + tempdir = self.useFixture(fixtures.TempDir()) + self.path = os.path.join(tempdir.path, 'git') os.mkdir(self.path) g = git.Git(self.path) @@ -186,55 +190,34 @@ class GitRepo(fixtures.Fixture): self._create_file_commit(ids[x], message_prefix=message_prefix) -class BaseTestCase(testtools.TestCase): - """Base Test Case for all tests.""" +class BuildTree(object): - logging.basicConfig() - - def setUp(self): - super(BaseTestCase, self).setUp() - - self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) - self.testrepo = self.useFixture(GitRepo()) - repo_path = self.testrepo.path - self.useFixture(DiveDir(repo_path)) - self.repo = self.testrepo.repo - self.git = self.repo.git - - self._graph = {} - self.addOnException(self.attach_graph_info) - - # _testMethodDoc is a hidden attribute containing the docstring for - # the given test, useful for some tests where description is not - # yet defined. - if getattr(self, '_testMethodDoc', None): - self.addDetail('description', text_content(self._testMethodDoc)) - - if hasattr(self, 'tree'): - self._build_git_tree(self.tree, self.branches.values()) - - if hasattr(self, 'pre-script'): - self.run_pre_script() + def __init__(self, gitrepo, tree, branches): + self.graph = {} + self.gitrepo = gitrepo + self.repo = gitrepo.repo + self.git = self.gitrepo.repo.git + self._build_git_tree(tree, branches) def _commit(self, node): p_node = _get_node_to_pick(node) if p_node: - self.git.cherry_pick(self._graph[p_node]) + self.git.cherry_pick(self.graph[p_node]) else: # standard commit - self.testrepo.add_commits(1, ref="HEAD", - message_prefix="[%s]" % node) + self.gitrepo.add_commits(1, ref="HEAD", + message_prefix="[%s]" % node) def _merge_commit(self, node, parents): # merge commits parent_nodes = [p.lstrip("=") for p in parents] - commits = [str(self._graph[p]) for p in parent_nodes[1:]] + commits = [str(self.graph[p]) for p in parent_nodes[1:]] if any([p.startswith("=") for p in parents]): # special merge commit using inverse of 'ours' by # emptying the current index and then reading in any # trees of the nodes prefixed with '=' - use = [str(self._graph[p.lstrip("=")]) + use = [str(self.graph[p.lstrip("=")]) for p in parents if p.startswith("=")] self.git.merge(*commits, s="ours", no_commit=True) self.git.read_tree(empty=True) @@ -306,49 +289,64 @@ class BaseTestCase(testtools.TestCase): self.git.symbolic_ref("HEAD", "refs/heads/%s" % node) self.git.rm(".", r=True, cached=True) self.git.clean(f=True, d=True, x=True) - self.testrepo.add_commits(1, ref="HEAD", - message_prefix="[%s]" % node) + self.gitrepo.add_commits(1, ref="HEAD", + message_prefix="[%s]" % node) # only explicitly listed branches should exist afterwards self.git.checkout(self.repo.commit()) self.git.branch(node, D=True) else: # checkout the dependent node - self.git.checkout(self._graph[parents[0].lstrip('=')]) + self.git.checkout(self.graph[parents[0].lstrip('=')]) if len(parents) > 1: # merge commits self._merge_commit(node, parents) else: self._commit(node) - self._graph[node] = self.repo.commit() + self.graph[node] = self.repo.commit() for name, node in branches: - self.git.branch(name, str(self._graph[node]), f=True) + self.git.branch(name, str(self.graph[node]), f=True) # return to master self.git.checkout("master") def _commits_from_nodes(self, nodes=[]): - return [self._graph[n] for n in nodes] + return [self.graph[n] for n in nodes] - def attach_graph_info(self, exc_info): - # appears to be an odd bug where this method is called twice - # if registered with addOnException so make sure to guard - # that the path actually exists before running - if not (hasattr(self.testrepo, 'path') and - os.path.exists(self.testrepo.path)): - return - if not self._graph: - return - self.addDetail('graph-dict', text_content(pformat(self._graph))) +class BaseTestCase(testtools.TestCase): + """Base Test Case for all tests.""" - self.addDetail( - 'git-log-with-graph', - text_content(self.repo.git.log(graph=True, oneline=True, - decorate=True, all=True, - parents=True))) + logging.basicConfig() + + def setUp(self): + super(BaseTestCase, self).setUp() + + self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) + self.testrepo = self.useFixture(GitRepo()) + repo_path = self.testrepo.path + self.useFixture(DiveDir(repo_path)) + + self.repo = self.testrepo.repo + self.git = self.repo.git + + self.addOnException(self.attach_graph_info) + + # _testMethodDoc is a hidden attribute containing the docstring for + # the given test, useful for some tests where description is not + # yet defined. + if getattr(self, '_testMethodDoc', None): + self.addDetail('description', text_content(self._testMethodDoc)) + + self.gittree = None + if hasattr(self, 'tree'): + self.gittree = BuildTree( + self.testrepo, self.tree, self.branches.values()) + + if hasattr(self, 'pre-script'): + self.run_pre_script() def run_pre_script(self): """ @@ -371,3 +369,21 @@ class BaseTestCase(testtools.TestCase): self.addDetail('pre-script-output', text_content(output.decode('utf-8'))) + + def attach_graph_info(self, exc_info): + # appears to be an odd bug where this method is called twice + # if registered with addOnException so make sure to guard + # that the path actually exists before running + if not (hasattr(self.testrepo, 'path') and + os.path.exists(self.testrepo.path)): + return + + if not self.gittree or not self.gittree.graph: + return + self.addDetail('graph-dict', text_content(pformat(self.gittree.graph))) + + self.addDetail( + 'git-log-with-graph', + text_content(self.repo.git.log(graph=True, oneline=True, + decorate=True, all=True, + parents=True))) diff --git a/git_upstream/tests/build-test-tree.py b/git_upstream/tests/build-test-tree.py new file mode 100644 index 0000000..d3129f4 --- /dev/null +++ b/git_upstream/tests/build-test-tree.py @@ -0,0 +1,79 @@ +# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# simple tool to read in a scenario in yaml and reproduce the test +# tree to allow for experimentation and investigation. + +import io +import os +import shutil +import subprocess +import sys + +import yaml + +from git_upstream.tests import base + +TREE_DIR = ".git-test-trees" + + +def build_test_tree(basedir, filename): + data = {} + with io.open(filename, 'r', encoding='utf-8') as yaml_file: + data = yaml.load(yaml_file)[0] + data['name'] = os.path.splitext(os.path.basename(filename))[0] + test_dir = os.path.join(basedir, data['name']) + if os.path.exists(test_dir): + print("Removing previous test tree: '%s'" % test_dir) + shutil.rmtree(test_dir) + + with base.GitRepo(path=test_dir) as testrepo: + with base.DiveDir(testrepo.path): + + if 'tree' in data: + base.BuildTree(testrepo, data['tree'], + data['branches'].values()) + + if 'pre-script' in data: + output = subprocess.check_output(getattr(data, 'pre-script'), + stderr=subprocess.STDOUT, + shell=True) + print("pre-script output:\n%s" % output) + + return data + + +def main(): + + if len(sys.argv[1:]) < 1: + print("Error: must pass the path of the yaml file containing the " + "test scenario tree to be constructed") + sys.exit(1) + + scenario_files = sys.argv[1:] + + for f in scenario_files: + if not os.path.exists(f): + print("'%s' not found" % f) + + if not os.path.exists(TREE_DIR): + os.mkdir(TREE_DIR) + + for filename in scenario_files: + t = build_test_tree(TREE_DIR, filename) + print("Created test tree: %s" % + os.path.join(TREE_DIR, t['name'])) + +if __name__ == '__main__': + main() diff --git a/git_upstream/tests/commands/import/test_import.py b/git_upstream/tests/commands/import/test_import.py index 2fbc5fe..70bb914 100644 --- a/git_upstream/tests/commands/import/test_import.py +++ b/git_upstream/tests/commands/import/test_import.py @@ -75,7 +75,7 @@ class TestImportCommand(TestWithScenarios, BaseTestCase): if node == "MERGE": continue subject = commit.message.splitlines()[0] - node_subject = self._graph[node].message.splitlines()[0] + node_subject = self.gittree.graph[node].message.splitlines()[0] self.assertThat(subject, Equals(node_subject), "subject '%s' of commit '%s' does not match " "subject '%s' of node '%s'" % ( @@ -101,8 +101,8 @@ class TestImportCommand(TestWithScenarios, BaseTestCase): "--finish option failed to merge correctly") commit = self.git.rev_list('master', parents=True, max_count=1).split() parents = commit[1:] - self.assertThat(parents, Equals([self._graph['D'].hexsha, - self._graph['D1'].hexsha]), + self.assertThat(parents, Equals([self.gittree.graph['D'].hexsha, + self.gittree.graph['D1'].hexsha]), "import --finish merge does contain the correct " "parents") diff --git a/git_upstream/tests/searchers/test_searchers.py b/git_upstream/tests/searchers/test_searchers.py index d97a34c..8e4e441 100644 --- a/git_upstream/tests/searchers/test_searchers.py +++ b/git_upstream/tests/searchers/test_searchers.py @@ -39,7 +39,7 @@ class TestUpstreamMergeBaseSearcher(TestWithScenarios, BaseTestCase): # need the tree built at this point self.addDetail('expected-changes', text_content(pformat( - list((c, self._graph[c].hexsha) + list((c, self.gittree.graph[c].hexsha) for c in self.expected_changes)))) def test_search_changes(self): @@ -51,5 +51,5 @@ class TestUpstreamMergeBaseSearcher(TestWithScenarios, BaseTestCase): searcher = UpstreamMergeBaseSearcher(branch=self.branches['head'][0], patterns=pattern, repo=self.repo) self.assertEqual( - self._commits_from_nodes(reversed(self.expected_changes)), + self.gittree._commits_from_nodes(reversed(self.expected_changes)), searcher.list()) diff --git a/git_upstream/tests/strategies/test_strategies.py b/git_upstream/tests/strategies/test_strategies.py index a2ca004..9ea83ee 100644 --- a/git_upstream/tests/strategies/test_strategies.py +++ b/git_upstream/tests/strategies/test_strategies.py @@ -44,7 +44,7 @@ class TestStrategies(TestWithScenarios, BaseTestCase): # need the tree built at this point self.addDetail('expected-changes', text_content(pformat( - list((c, self._graph[c].hexsha) + list((c, self.gittree.graph[c].hexsha) for c in self.expected_changes)))) def test_search_changes(self): @@ -53,5 +53,6 @@ class TestStrategies(TestWithScenarios, BaseTestCase): branch=self.branches['head'][0], search_refs=[self.branches['upstream'][0]]) - self.assertEqual(self._commits_from_nodes(self.expected_changes), - [c for c in strategy.filtered_iter()]) + self.assertEqual( + self.gittree._commits_from_nodes(self.expected_changes), + [c for c in strategy.filtered_iter()]) diff --git a/git_upstream/tests/test_import.py b/git_upstream/tests/test_import.py index b0f033a..c31879d 100644 --- a/git_upstream/tests/test_import.py +++ b/git_upstream/tests/test_import.py @@ -15,14 +15,14 @@ """Tests for the 'import' module""" -from git_upstream.tests.base import BaseTestCase +from git_upstream.tests import base import_command = __import__("git_upstream.commands.import", globals(), locals(), ['ImportUpstream']) ImportUpstream = import_command.ImportUpstream -class TestImport(BaseTestCase): +class TestImport(base.BaseTestCase): def test_import_finish_merge_clean(self): """Test that after finishing the import merge that the users working @@ -57,7 +57,7 @@ class TestImport(BaseTestCase): 'import': ('import', 'C1') } - self._build_git_tree(tree, branches.values()) + self.gittree = base.BuildTree(self.testrepo, tree, branches.values()) iu = ImportUpstream("master", "upstream/master", "import") iu.finish() self.assertEqual("", self.git.status(porcelain=True), @@ -94,7 +94,7 @@ class TestImport(BaseTestCase): 'import': ('import', 'C1') } - self._build_git_tree(tree, branches.values()) + self.gittree = base.BuildTree(self.testrepo, tree, branches.values()) iu = ImportUpstream("master", "upstream/master", "import") # create a dummy file open('dummy-file', 'a').close() diff --git a/tox.ini b/tox.ini index 7112d42..7bb767a 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,9 @@ commands = python setup.py build_manpage [testenv:venv] commands = {posargs} +[testenv:build-tree] +commands = python git_upstream/tests/build-test-tree.py {posargs} + [flake8] ignore=H236,H40 show-source = True