# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# 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 Affero General Public License
# along with this program. If not, see .
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import time
import shutil
import datetime
import pytest
from rhodecode.lib.str_utils import safe_bytes
from rhodecode.lib.vcs.backends import get_backend
from rhodecode.lib.vcs.backends.base import Config
from rhodecode.lib.vcs.nodes import FileNode
from rhodecode.tests import get_new_dir
from rhodecode.tests.utils import check_skip_backends, check_xfail_backends
@pytest.fixture()
def vcs_repository_support(request, backend_alias, baseapp, _vcs_repo_container):
"""
Provide a test repository for the test run.
Depending on the value of `recreate_repo_per_test` a new repo for each
test will be created.
The parameter `--backends` can be used to limit this fixture to specific
backend implementations.
"""
cls = request.cls
check_skip_backends(request.node, backend_alias)
check_xfail_backends(request.node, backend_alias)
if _should_create_repo_per_test(cls):
_vcs_repo_container = _create_vcs_repo_container(request)
repo = _vcs_repo_container.get_repo(cls, backend_alias=backend_alias)
# TODO: johbo: Supporting old test class api, think about removing this
cls.repo = repo
cls.repo_path = repo.path
cls.default_branch = repo.DEFAULT_BRANCH_NAME
cls.Backend = cls.backend_class = repo.__class__
cls.imc = repo.in_memory_commit
return backend_alias, repo
@pytest.fixture(scope="class")
def _vcs_repo_container(request):
"""
Internal fixture intended to help support class based scoping on demand.
"""
return _create_vcs_repo_container(request)
def _create_vcs_repo_container(request):
repo_container = VcsRepoContainer()
if not request.config.getoption("--keep-tmp-path"):
request.addfinalizer(repo_container.cleanup)
return repo_container
class VcsRepoContainer(object):
def __init__(self):
self._cleanup_paths = []
self._repos = {}
def get_repo(self, test_class, backend_alias):
if backend_alias not in self._repos:
repo = _create_empty_repository(test_class, backend_alias)
self._cleanup_paths.append(repo.path)
self._repos[backend_alias] = repo
return self._repos[backend_alias]
def cleanup(self):
for repo_path in reversed(self._cleanup_paths):
shutil.rmtree(repo_path)
def _should_create_repo_per_test(cls):
return getattr(cls, "recreate_repo_per_test", False)
def _create_empty_repository(cls, backend_alias=None):
Backend = get_backend(backend_alias or cls.backend_alias)
repo_path = get_new_dir(str(time.time()))
repo = Backend(repo_path, create=True)
if hasattr(cls, "_get_commits"):
commits = cls._get_commits()
cls.tip = _add_commits_to_repo(repo, commits)
return repo
@pytest.fixture()
def config():
"""
Instance of a repository config.
The instance contains only one value:
- Section: "section-a"
- Key: "a-1"
- Value: "value-a-1"
The intended usage is for cases where a config instance is needed but no
specific content is required.
"""
config = Config()
config.set("section-a", "a-1", "value-a-1")
return config
def _add_commits_to_repo(repo, commits):
imc = repo.in_memory_commit
tip = None
for commit in commits:
for node in commit.get("added", []):
if not isinstance(node, FileNode):
node = FileNode(safe_bytes(node.path), content=node.content)
imc.add(node)
for node in commit.get("changed", []):
if not isinstance(node, FileNode):
node = FileNode(safe_bytes(node.path), content=node.content)
imc.change(node)
for node in commit.get("removed", []):
imc.remove(FileNode(safe_bytes(node.path)))
tip = imc.commit(
message=str(commit["message"]),
author=str(commit["author"]),
date=commit["date"],
branch=commit.get("branch"),
)
return tip
@pytest.fixture()
def vcs_repo(request, backend_alias):
Backend = get_backend(backend_alias)
repo_path = get_new_dir(str(time.time()))
repo = Backend(repo_path, create=True)
@request.addfinalizer
def cleanup():
shutil.rmtree(repo_path)
return repo
@pytest.fixture()
def generate_repo_with_commits(vcs_repo):
"""
Creates a fabric to generate N commits with some file nodes on a randomly
generated repository
"""
def commit_generator(num):
start_date = datetime.datetime(2010, 1, 1, 20)
for x in range(num):
yield {
"message": "Commit %d" % x,
"author": "Joe Doe ",
"date": start_date + datetime.timedelta(hours=12 * x),
"added": [
FileNode(b"file_%d.txt" % x, content=b"Foobar %d" % x),
],
"modified": [
FileNode(b"file_%d.txt" % x, content=b"Foobar %d modified" % (x - 1)),
],
}
def commit_maker(num=5):
_add_commits_to_repo(vcs_repo, commit_generator(num))
return vcs_repo
return commit_maker
@pytest.fixture()
def hg_repo(request, vcs_repo):
repo = vcs_repo
commits = repo._get_commits()
_add_commits_to_repo(repo, commits)
return repo
@pytest.fixture()
def hg_commit(hg_repo):
return hg_repo.get_commit()
class BackendTestMixin(object):
"""
This is a backend independent test case class which should be created
with ``type`` method.
It is required to set following attributes at subclass:
- ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
- ``repo_path``: path to the repository which would be created for set of
tests
- ``recreate_repo_per_test``: If set to ``False``, repo would NOT be
created
before every single test. Defaults to ``True``.
"""
recreate_repo_per_test = True
@classmethod
def _get_commits(cls):
commits = [
{
"message": "Initial commit",
"author": "Joe Doe ",
"date": datetime.datetime(2010, 1, 1, 20),
"added": [
FileNode(b"foobar", content=b"Foobar"),
FileNode(b"foobar2", content=b"Foobar II"),
FileNode(b"foo/bar/baz", content=b"baz here!"),
],
},
{
"message": "Changes...",
"author": "Jane Doe ",
"date": datetime.datetime(2010, 1, 1, 21),
"added": [
FileNode(b"some/new.txt", content=b"news..."),
],
"changed": [
FileNode(b"foobar", b"Foobar I"),
],
"removed": [],
},
]
return commits