Merge "Add initial tests for strategies and searchers"

This commit is contained in:
Jenkins
2014-09-12 08:14:44 +00:00
committed by Gerrit Code Review
3 changed files with 523 additions and 1 deletions

View File

@@ -115,7 +115,7 @@ class GitRepo(fixtures.Fixture):
message = message + "\n\nChange-Id: %s" % change_id
self.repo.git.commit(m=message)
def add_commits(self, num=1, ref="HEAD", change_ids=None):
def add_commits(self, num=1, ref="HEAD", change_ids=[]):
"""Create the given number of commits using generated files"""
if ref != "HEAD":
self.repo.git.checkout(ref)
@@ -137,3 +137,101 @@ class BaseTestCase(testtools.TestCase):
repo_path = self.testrepo.path
self.useFixture(DiveDir(repo_path))
self.repo = self.testrepo.repo
self.git = self.repo.git
def _build_git_tree(self, graph_def, branches=[]):
"""Helper function to build a git repository from a graph definition
of nodes and their parent nodes. A list of branches may be provided
where each element has two members corresponding to the name and the
target node it references.
Root commits can specified by an empty list as the second member:
('NodeA', [])
Merge commits are specified by multiple nodes:
('NodeMerge', ['Node1', 'Node2'])
As the import subcommand to git-upstream supports a special merge
commit that ignores all previous history from the other tree being
merged in using the 'ours' strategy. You specify this by defining
a parent node as '=<Node>'. The resulting merge commit contains just
the contents of the tree from the specified parent while still
recording the parents.
Following will result in a merge commit 'C', with parents 'P1' and
'P2', but will have the same tree as 'P1'.
('C', ['=P1', 'P2'])
Current code requires that the graph defintion defines each node
before subsequently referencing it as a parent.
This works:
[('A', []), ('B', ['A']), ('C', ['B'])]
This will not:
[('A', []), ('C', ['B']), ('B', ['A'])]
"""
self._graph = {}
# first commit is special, assume root commit and repo has 1 commit
node, parents = graph_def[0]
if not parents:
assert("First commit in graph def must be a root commit")
self._graph[node] = self.repo.commit()
# uses the fact that you can create commits in detached head mode
# and then create branches after the fact
for node, parents in graph_def[1:]:
# other root commits
if not parents:
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")
# 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]])
if len(parents) > 1:
# merge commits
parent_nodes = [p.strip("=") for p in parents]
commits = [str(self._graph[p]) for p in parent_nodes[1:]]
if any([True for p in parents if p.startswith("=")]):
# special merge commit using inverse of 'ours'
self.git.merge(*commits, s="ours", no_commit=True)
use = str(self._graph[
next(p.strip("=") for p in parents
if p.startswith("="))])
self.git.read_tree(use, u=True, reset=True)
self.git.commit(m="Merging %s into %s" %
(",".join(parent_nodes), node))
else:
# standard merge
self.git.merge(*commits, no_edit=True)
else:
# standard commit
self.testrepo.add_commits(1, ref="HEAD")
self._graph[node] = self.repo.commit()
for name, node in branches:
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]

View File

@@ -0,0 +1,237 @@
# Copyright (c) 2014 Hewlett-Packard 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.
#
from base import BaseTestCase
from git_upstream.lib.searchers import UpstreamMergeBaseSearcher
class TestUpstreamMergeBaseSearcher(BaseTestCase):
def _verify_expected(self, tree, branches, expected_nodes):
self._build_git_tree(tree, branches.values())
searcher = UpstreamMergeBaseSearcher(pattern=branches['upstream'][0],
repo=self.repo)
self.assertEquals(self._commits_from_nodes(reversed(expected_nodes)),
searcher.list())
def test_search_basic(self):
"""Construct a basic repo layout and validate that locate changes
walker can find the expected changes.
Repository layout being tested
B master
/
A---C---D upstream/master
"""
tree = [
('A', []),
('B', ['A']),
('C', ['A']),
('D', ['C'])
]
branches = {
'head': ('master', 'B'),
'upstream': ('upstream/master', 'D'),
}
expected_changes = ["B"]
self._verify_expected(tree, branches, expected_changes)
def test_search_additional_branch(self):
"""Construct a repo layout where previously an additional branch has
been included and validate that locate changes walker can find the
expected changes
Repository layout being tested
B example/packaging
\
C---D---E master
/
A---F---G upstream/master
"""
tree = [
('A', []),
('B', []),
('C', ['A', 'B']),
('D', ['C']),
('E', ['D']),
('F', ['A']),
('G', ['F'])
]
branches = {
'head': ('master', 'E'),
'upstream': ('upstream/master', 'G'),
}
expected_changes = ["C", "D", "E"]
self._verify_expected(tree, branches, expected_changes)
def test_search_additional_branch_multiple_imports(self):
"""Construct a repo layout where previously an additional branch has
been included and validate that locate changes walker can find the
right changes after an additional import
Repository layout being tested
B-- example/packaging
\ \
C---D---E-------I---J---K master
/ \ /
/ --H---D1--E1 import/next
/ /
A---F---G---L---M upstream/master
"""
tree = [
('A', []),
('B', []),
('C', ['A', 'B']),
('D', ['C']),
('E', ['D']),
('F', ['A']),
('G', ['F']),
('H', ['G', 'B']),
('D1', ['H']),
('E1', ['D1']),
('I', ['E', '=E1']),
('J', ['I']),
('K', ['J']),
('L', ['G']),
('M', ['L'])
]
branches = {
'head': ('master', 'K'),
'upstream': ('upstream/master', 'M'),
}
expected_changes = ["H", "D1", "E1", "I", "J", "K"]
self._verify_expected(tree, branches, expected_changes)
def test_search_changes_upload_prior_to_import(self):
"""Construct a repo layout where using a complex layout involving
additional branches having been included, and a previous import from
upstream having been completed, test that if a change was created on
another branch before the previous import was created, and merged to
the target branch after the previous import, can we find it correctly.
i.e. will the strategy also include commit 'O' in the diagram below.
Repository layout being tested
B--
\ \
\ \ O----------------
\ \ / \
C---D---E-------I---J---K---P---Q master
/ \ /
/ --H---D1--E1
/ /
A---F---G---L---M upstream/master
"""
tree = [
('A', []),
('B', []),
('C', ['A', 'B']),
('D', ['C']),
('E', ['D']),
('F', ['A']),
('G', ['F']),
('H', ['G', 'B']),
('D1', ['H']),
('E1', ['D1']),
('I', ['E', '=E1']),
('J', ['I']),
('K', ['J']),
('L', ['G']),
('M', ['L']),
('O', ['E']),
('P', ['K', 'O']),
('Q', ['P'])
]
branches = {
'head': ('master', 'Q'),
'upstream': ('upstream/master', 'M'),
}
expected_changes = ["H", "D1", "E1", "I", "J", "K", "O", "P", "Q"]
self.expectFailure(
"Should fail to find change 'O'",
self._verify_expected, tree, branches, expected_changes)
def test_search_multi_changes_upload_prior_to_import(self):
"""Construct a repo layout where using a complex layout involving
additional branches having been included, and a previous import from
upstream having been completed, test that if a change was created on
another branch before the previous import was created, and merged to
the target branch after the previous import, can we find it correctly.
i.e. will the strategy also include commit 'O' in the diagram below.
Repository layout being tested
B-- L-------------
\ \ / \
\ \ / J--------- \
\ \ / / \ \
C---D---E-------I---K---M---O---P master
/ \ /
/ --H---D1--E1
/ /
A---F---G---Q---R upstream/master
"""
tree = [
('A', []),
('B', []),
('C', ['A', 'B']),
('D', ['C']),
('E', ['D']),
('F', ['A']),
('G', ['F']),
('H', ['G', 'B']),
('D1', ['H']),
('E1', ['D1']),
('I', ['E', '=E1']),
('J', ['E']),
('K', ['I', 'J']),
('L', ['D']),
('M', ['K', 'L']),
('O', ['M']),
('P', ['O']),
('Q', ['G']),
('R', ['Q'])
]
branches = {
'head': ('master', 'P'),
'upstream': ('upstream/master', 'R'),
}
expected_changes = ["H", "D1", "E1", "I", "J", "K", "L", "M", "O", "P"]
self.expectFailure(
"Should fail to find changes 'J' and 'L'",
self._verify_expected, tree, branches, expected_changes)

View File

@@ -0,0 +1,187 @@
#
# Copyright (c) 2014 Hewlett-Packard 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.
#
from base import BaseTestCase
import_command = __import__("git_upstream.commands.import", globals(),
locals(), ['LocateChangesWalk'], -1)
LocateChangesWalk = import_command.LocateChangesWalk
class TestStrategies(BaseTestCase):
def _verify_expected(self, tree, branches, expected_nodes):
self._build_git_tree(tree, branches.values())
strategy = LocateChangesWalk(branch=branches['head'][0],
search_ref=branches['upstream'][0])
self.assertEquals(self._commits_from_nodes(expected_nodes),
[c for c in strategy.filtered_iter()])
def test_locate_changes_walk_basic(self):
"""Construct a basic repo layout and validate that locate changes
walker can find the expected changes.
Repository layout being tested
B master
/
A---C---D upstream/master
"""
tree = [
('A', []),
('B', ['A']),
('C', ['A']),
('D', ['C'])
]
branches = {
'head': ('master', 'B'),
'upstream': ('upstream/master', 'D'),
}
expected_changes = ["B"]
self._verify_expected(tree, branches, expected_changes)
def test_locate_changes_walk_additional_branch(self):
"""Construct a repo layout where previously an additional branch has
been included and validate that locate changes walker can find the
expected changes
Repository layout being tested
B example/packaging
\
C---D---E master
/
A---F---G upstream/master
"""
tree = [
('A', []),
('B', []),
('C', ['A', 'B']),
('D', ['C']),
('E', ['D']),
('F', ['A']),
('G', ['F'])
]
branches = {
'head': ('master', 'E'),
'upstream': ('upstream/master', 'G'),
}
expected_changes = ["D", "E"]
self._verify_expected(tree, branches, expected_changes)
def test_locate_changes_walk_additional_branch_multiple_imports(self):
"""Construct a repo layout where previously an additional branch has
been included and validate that locate changes walker can find the
right changes after an additional import
Repository layout being tested
B-- example/packaging
\ \
C---D---E-------I---J---K master
/ \ /
/ --H---D1--E1 import/next
/ /
A---F---G---L---M upstream/master
"""
tree = [
('A', []),
('B', []),
('C', ['A', 'B']),
('D', ['C']),
('E', ['D']),
('F', ['A']),
('G', ['F']),
('H', ['G', 'B']),
('D1', ['H']),
('E1', ['D1']),
('I', ['E', '=E1']),
('J', ['I']),
('K', ['J']),
('L', ['G']),
('M', ['L'])
]
branches = {
'head': ('master', 'K'),
'upstream': ('upstream/master', 'M'),
}
expected_changes = ["D1", "E1", "J", "K"]
self._verify_expected(tree, branches, expected_changes)
def test_locate_changes_walk_changes_prior_to_import(self):
"""Construct a repo layout where using a complex layout involving
additional branches having been included, and a previous import from
upstream having been completed, test that if a change was created on
another branch before the previous import was created, and merged to
the target branch after the previous import, can we find it correctly.
i.e. will the strategy also include commit 'O' in the diagram below.
Repository layout being tested
B-- example/packaging
\ \
\ \ O----------------
\ \ / \
C---D---E-------I---J---K---P---Q master
/ \ /
/ --H---D1--E1 import/next
/ /
A---F---G---L---M upstream/master
"""
tree = [
('A', []),
('B', []),
('C', ['A', 'B']),
('D', ['C']),
('E', ['D']),
('F', ['A']),
('G', ['F']),
('H', ['G', 'B']),
('D1', ['H']),
('E1', ['D1']),
('I', ['E', '=E1']),
('J', ['I']),
('K', ['J']),
('L', ['G']),
('M', ['L']),
('O', ['E']),
('P', ['K', 'O']),
('Q', ['P'])
]
branches = {
'head': ('master', 'Q'),
'upstream': ('upstream/master', 'M'),
}
expected_changes = ["D1", "E1", "J", "K", "O", "Q"]
self.expectFailure(
"Should fail to find change 'O'",
self._verify_expected, tree, branches, expected_changes)