Merge "Add initial tests for strategies and searchers"
This commit is contained in:
@@ -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]
|
||||
|
||||
237
git_upstream/tests/test_searchers.py
Normal file
237
git_upstream/tests/test_searchers.py
Normal 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)
|
||||
187
git_upstream/tests/test_strategies.py
Normal file
187
git_upstream/tests/test_strategies.py
Normal 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)
|
||||
Reference in New Issue
Block a user