Begin implementing explicit control of commit support
Start implementation of supporting more explicitly defined git tree graph that allows for controlling individual commit settings such as specific files, message contents and author details through providing basic input data format detection and validation Change-Id: I6075aecd869eb58aa276d313a1f0ebd878e99ff9
This commit is contained in:
parent
bb831cb4c4
commit
9808e8f28e
29
fixtures_git/exc.py
Normal file
29
fixtures_git/exc.py
Normal file
@ -0,0 +1,29 @@
|
||||
#
|
||||
# Copyright (c) 2018 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
class FixturesGitException(Exception):
|
||||
"""Base exception for this package"""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidGraphFormat(FixturesGitException):
|
||||
"""Specified graph does not match any schema"""
|
||||
pass
|
||||
|
||||
|
||||
class BadGraphDefinition(FixturesGitException):
|
||||
"""Problem in walking the graph definition"""
|
||||
pass
|
@ -13,13 +13,16 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import faker
|
||||
import git
|
||||
import voluptuous
|
||||
|
||||
from fixtures_git import _utils
|
||||
from fixtures_git import exc
|
||||
|
||||
|
||||
class GitTree(object):
|
||||
@ -70,7 +73,24 @@ class GitTree(object):
|
||||
.. _git-upstream: https://pypi.python.org/pypi/git-upstream
|
||||
"""
|
||||
|
||||
explicit_schema_validator = voluptuous.Schema({
|
||||
voluptuous.Required('name'): str,
|
||||
'message': str,
|
||||
'files': dict,
|
||||
'author': str,
|
||||
'parents': list,
|
||||
})
|
||||
|
||||
simple_schema_validator = voluptuous.Schema(
|
||||
voluptuous.ExactSequence(
|
||||
[str, voluptuous.Schema([str])]
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, repo, tree, branches):
|
||||
self.logger = logging.getLogger(
|
||||
"%s.%s" % (__name__, self.__class__.__name__)
|
||||
)
|
||||
self.graph = {}
|
||||
self.repo = repo
|
||||
|
||||
@ -144,10 +164,45 @@ class GitTree(object):
|
||||
if branches is None:
|
||||
branches = []
|
||||
|
||||
def validate_graph(validator, err_msg):
|
||||
try:
|
||||
for node in graph_def:
|
||||
validator(node)
|
||||
except (voluptuous.Invalid, voluptuous.MultipleInvalid) as err:
|
||||
self.logger.error(
|
||||
"Validation of '%s' failed with error '%s'", node, err
|
||||
)
|
||||
raise exc.InvalidGraphFormat(
|
||||
"Invalid graph structure: %s" % err_msg
|
||||
)
|
||||
|
||||
# validate graph definition
|
||||
node1 = graph_def[0]
|
||||
if isinstance(node1, dict):
|
||||
validate_graph(
|
||||
self.explicit_schema_validator,
|
||||
"Does not conform to explicit style format."
|
||||
)
|
||||
# need to generate a suitable graph_def to walk
|
||||
raise RuntimeError("Explicit schema not yet handled")
|
||||
|
||||
elif isinstance(node1, list):
|
||||
validate_graph(
|
||||
self.simple_schema_validator,
|
||||
"Does not conform to simple style format."
|
||||
)
|
||||
# Or convert simple format to explicit here
|
||||
else:
|
||||
raise exc.InvalidGraphFormat(
|
||||
"Unknown graph format or unable to guess from '%s'" % node1
|
||||
)
|
||||
|
||||
# require that graphs must have at least 1 node with no
|
||||
# parents, which is a root commit in git
|
||||
if not any([True for _, parents in graph_def if not parents]):
|
||||
assert "No root commit defined in test graph"
|
||||
raise exc.BadGraphDefinition(
|
||||
"No root commit defined in test graph"
|
||||
)
|
||||
|
||||
for node, parents in _utils.reverse_toposort(graph_def):
|
||||
if not parents:
|
||||
@ -171,7 +226,20 @@ class GitTree(object):
|
||||
self._commit(node)
|
||||
self.graph[node] = self.repo.commit()
|
||||
|
||||
for name, node in branches:
|
||||
# handle branches being specified in explicit or simple
|
||||
if isinstance(branches, dict):
|
||||
# explicit format
|
||||
list_branches = branches.items()
|
||||
else:
|
||||
# simple format
|
||||
list_branches = branches
|
||||
for name, node in list_branches:
|
||||
if isinstance(node, list):
|
||||
name, node = node
|
||||
else:
|
||||
node = node
|
||||
if node in branches:
|
||||
node = branches[node]
|
||||
self.repo.git.branch(name, str(self.graph[node]), f=True)
|
||||
|
||||
# return to master
|
||||
|
@ -1,3 +1,4 @@
|
||||
faker>=0.8.16
|
||||
fixtures>=3.0.0
|
||||
GitPython>=1.0.1
|
||||
voluptuous==0.11.5
|
||||
|
@ -1,28 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2018 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
import fixtures
|
||||
import testtools
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
@ -17,10 +17,10 @@
|
||||
from testtools import matchers
|
||||
|
||||
from fixtures_git.gitfixture import GitFixture
|
||||
from tests import acceptance
|
||||
from tests import base
|
||||
|
||||
|
||||
class TestGitFixture(acceptance.BaseTestCase):
|
||||
class TestGitFixture(base.BaseTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
gitfixture = self.useFixture(
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2018 Hewlett Packard Enterprise Development LP
|
||||
# Copyright (c) 2018-2019 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -15,6 +15,73 @@
|
||||
#
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import fixtures
|
||||
import testtools
|
||||
|
||||
|
||||
class ChannelFixture(fixtures.Fixture):
|
||||
|
||||
def __init__(self, channel='stdout', object=None):
|
||||
|
||||
self._channel = channel
|
||||
if object is None:
|
||||
self._object = 'sys.%s' % channel
|
||||
else:
|
||||
self._object = object
|
||||
|
||||
def _setUp(self):
|
||||
string_fixture = self.useFixture(fixtures.StringStream(self._channel))
|
||||
self.stream = string_fixture.stream
|
||||
self.useFixture(
|
||||
fixtures.MonkeyPatch(self._object, self.stream)
|
||||
)
|
||||
|
||||
def getvalue(self):
|
||||
self.stream.seek(0)
|
||||
return self.stream.read()
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
self.logger = logging.getLogger(
|
||||
"%s.%s" % (__name__, self.__class__.__name__)
|
||||
)
|
||||
|
||||
# capture stdout/stderr for tests to inspect easily
|
||||
self.stdout = self.useFixture(ChannelFixture('stdout'))
|
||||
self.stderr = self.useFixture(ChannelFixture('stderr'))
|
||||
self.logger_output = self.useFixture(
|
||||
fixtures.FakeLogger(
|
||||
level=logging.DEBUG,
|
||||
format="%(levelname)s:%(name)s:%(message)s"
|
||||
)
|
||||
)
|
||||
|
||||
def get_testfile(self, ext):
|
||||
|
||||
*path_parts, clsname, testname = self.id().split('.')
|
||||
testname = testname.replace("test_", '', 1)
|
||||
possible_test_fixtures = (
|
||||
"%s.%s.%s.%s" % (path_parts[-1], clsname, testname, ext),
|
||||
"%s.%s.%s" % (path_parts[-1], clsname, ext),
|
||||
"%s.%s" % (path_parts[-1], ext),
|
||||
)
|
||||
for fpath in possible_test_fixtures:
|
||||
testdatafile = os.path.join(*(path_parts[:-1]), "fixtures", fpath)
|
||||
if os.path.exists(testdatafile):
|
||||
return testdatafile
|
||||
else:
|
||||
self.logger.warn(
|
||||
"No file with test data found from patterns (%s) for test "
|
||||
"id %s" % (", ".join(possible_test_fixtures), self.id())
|
||||
)
|
||||
|
||||
|
||||
class IsOrderedSubsetOfMismatch(object):
|
||||
def __init__(self, subset, set):
|
||||
self.subset = list(subset)
|
||||
|
29
tests/unit/fixtures/test_gittree.TestGitTree.tree_graph.yaml
Normal file
29
tests/unit/fixtures/test_gittree.TestGitTree.tree_graph.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
#
|
||||
# (c) Copyright 2019 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
---
|
||||
branches:
|
||||
master: E
|
||||
upstream/master: G
|
||||
somebranch: master
|
||||
|
||||
tree:
|
||||
- [A, []]
|
||||
- [B, []]
|
||||
- [C, [A, B]]
|
||||
- [D, [C]]
|
||||
- [E, [D]]
|
||||
- [F, [A]]
|
||||
- [G, [F]]
|
@ -0,0 +1,28 @@
|
||||
#
|
||||
# (c) Copyright 2019 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
---
|
||||
branches:
|
||||
head: [master, E]
|
||||
upstream: [upstream/master, G]
|
||||
|
||||
tree:
|
||||
- [A, []]
|
||||
- [B, []]
|
||||
- [C, [A, B]]
|
||||
- [D, [C]]
|
||||
- [E, [D]]
|
||||
- [F, [A]]
|
||||
- [G, [F]]
|
88
tests/unit/test_gittree.py
Normal file
88
tests/unit/test_gittree.py
Normal file
@ -0,0 +1,88 @@
|
||||
# Copyright (c) 2018 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
import fixtures
|
||||
import git
|
||||
import yaml
|
||||
|
||||
from tests import base
|
||||
|
||||
from fixtures_git import gittree
|
||||
|
||||
|
||||
class TestGitTree(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestGitTree, self).setUp()
|
||||
|
||||
tempdir = self.useFixture(fixtures.TempDir())
|
||||
self.addCleanup(shutil.rmtree, tempdir.path)
|
||||
self.path = os.path.join(tempdir.path, 'git')
|
||||
|
||||
os.mkdir(self.path)
|
||||
g = git.Git(self.path)
|
||||
g.init()
|
||||
|
||||
user = {
|
||||
'name': 'Example User',
|
||||
'email': 'user@example.com',
|
||||
}
|
||||
|
||||
self.repo = git.Repo(self.path)
|
||||
self.repo.git.config('user.email', user['email'])
|
||||
self.repo.git.config('user.name', user['name'])
|
||||
|
||||
testdatafile = self.get_testfile('yaml')
|
||||
if testdatafile:
|
||||
with open(testdatafile) as yfile:
|
||||
self.data = yaml.load(yfile, Loader=yaml.SafeLoader)
|
||||
else:
|
||||
self.data = {}
|
||||
self.repo.git.commit(m="Initialize empty repo", allow_empty=True)
|
||||
|
||||
def test_tree_graph(self):
|
||||
"""
|
||||
Test providing a basic tree with simple nodes can be specified
|
||||
"""
|
||||
|
||||
tree = gittree.GitTree(
|
||||
self.repo, self.data['tree'], self.data['branches']
|
||||
)
|
||||
# test that master points to the right commit
|
||||
self.assertEqual(
|
||||
tree.repo.branches['master'].commit,
|
||||
tree.graph['E'],
|
||||
)
|
||||
# make sure that somebranch resolved to the same commit as master
|
||||
self.assertEqual(
|
||||
tree.repo.branches['somebranch'].commit,
|
||||
tree.graph['E'],
|
||||
)
|
||||
|
||||
def test_tree_simple_style_graph(self):
|
||||
"""
|
||||
Make sure the simple format for specifying branches remains working
|
||||
"""
|
||||
tree = gittree.GitTree(
|
||||
self.repo, self.data['tree'], self.data['branches']
|
||||
)
|
||||
self.assertEqual(
|
||||
tree.repo.branches['master'].commit,
|
||||
tree.graph['E'],
|
||||
)
|
Loading…
Reference in New Issue
Block a user