diff --git a/conftest.py b/conftest.py index f731dbc6..0ac472d3 100644 --- a/conftest.py +++ b/conftest.py @@ -27,8 +27,11 @@ from rhodecode.tests.conftest_common import ( # noqa pytest_plugins = [ - "rhodecode.tests.fixture_mods.fixture_pyramid", - "rhodecode.tests.fixture_mods.fixture_utils", + "rhodecode.tests.fixtures.fixture_pyramid", + "rhodecode.tests.fixtures.fixture_utils", + "rhodecode.tests.fixtures.function_scoped_baseapp", + "rhodecode.tests.fixtures.module_scoped_baseapp", + "rhodecode.tests.fixtures.rcextensions_fixtures", ] diff --git a/pyproject.toml b/pyproject.toml index 84e5acbd..599ef4f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,8 +65,7 @@ dependencies = {file = ["requirements.txt"]} optional-dependencies.tests = {file = ["requirements_test.txt"]} [tool.ruff] - -select = [ +lint.select = [ # Pyflakes "F", # Pycodestyle @@ -75,16 +74,13 @@ select = [ # isort "I001" ] - -ignore = [ +lint.ignore = [ "E501", # line too long, handled by black ] - # Same as Black. line-length = 120 -[tool.ruff.isort] - +[tool.ruff.lint.isort] known-first-party = ["rhodecode"] [tool.ruff.format] diff --git a/pytest.ini b/pytest.ini index e1bd2486..6afc1d2f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,8 +4,10 @@ norecursedirs = rhodecode/public rhodecode/templates tests/scripts cache_dir = /tmp/.pytest_cache pyramid_config = rhodecode/tests/rhodecode.ini -vcsserver_protocol = http -vcsserver_config_http = rhodecode/tests/vcsserver_http.ini + +vcsserver_config = rhodecode/tests/vcsserver_http.ini +rhodecode_config = rhodecode/tests/rhodecode.ini +celery_config = rhodecode/tests/rhodecode.ini addopts = --pdbcls=IPython.terminal.debugger:TerminalPdb diff --git a/rhodecode/api/tests/test_add_field_to_repo.py b/rhodecode/api/tests/test_add_field_to_repo.py index c12ae440..3b9f1fc8 100644 --- a/rhodecode/api/tests/test_add_field_to_repo.py +++ b/rhodecode/api/tests/test_add_field_to_repo.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 diff --git a/rhodecode/api/tests/test_create_gist.py b/rhodecode/api/tests/test_create_gist.py index 41b4bf77..c440fb9e 100644 --- a/rhodecode/api/tests/test_create_gist.py +++ b/rhodecode/api/tests/test_create_gist.py @@ -24,7 +24,7 @@ from rhodecode.model.db import Gist from rhodecode.model.gist import GistModel from rhodecode.api.tests.utils import ( build_data, api_call, assert_error, assert_ok, crash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture @pytest.mark.usefixtures("testuser_api", "app") diff --git a/rhodecode/api/tests/test_create_repo.py b/rhodecode/api/tests/test_create_repo.py index be373a42..872d62e7 100644 --- a/rhodecode/api/tests/test_create_repo.py +++ b/rhodecode/api/tests/test_create_repo.py @@ -27,7 +27,7 @@ from rhodecode.model.user import UserModel from rhodecode.tests import TEST_USER_ADMIN_LOGIN from rhodecode.api.tests.utils import ( build_data, api_call, assert_ok, assert_error, crash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.lib.ext_json import json from rhodecode.lib.str_utils import safe_str diff --git a/rhodecode/api/tests/test_create_repo_group.py b/rhodecode/api/tests/test_create_repo_group.py index 72e7ee56..a72b2180 100644 --- a/rhodecode/api/tests/test_create_repo_group.py +++ b/rhodecode/api/tests/test_create_repo_group.py @@ -26,7 +26,7 @@ from rhodecode.model.user import UserModel from rhodecode.tests import TEST_USER_ADMIN_LOGIN from rhodecode.api.tests.utils import ( build_data, api_call, assert_ok, assert_error, crash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture fixture = Fixture() diff --git a/rhodecode/api/tests/test_create_user.py b/rhodecode/api/tests/test_create_user.py index 9b1e6797..6b612232 100644 --- a/rhodecode/api/tests/test_create_user.py +++ b/rhodecode/api/tests/test_create_user.py @@ -26,7 +26,7 @@ from rhodecode.tests import ( TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL) from rhodecode.api.tests.utils import ( build_data, api_call, assert_ok, assert_error, jsonify, crash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.model.db import RepoGroup diff --git a/rhodecode/api/tests/test_create_user_group.py b/rhodecode/api/tests/test_create_user_group.py index 14035458..fb101f0d 100644 --- a/rhodecode/api/tests/test_create_user_group.py +++ b/rhodecode/api/tests/test_create_user_group.py @@ -25,7 +25,7 @@ from rhodecode.model.user import UserModel from rhodecode.model.user_group import UserGroupModel from rhodecode.api.tests.utils import ( build_data, api_call, assert_error, assert_ok, crash, jsonify) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture @pytest.mark.usefixtures("testuser_api", "app") diff --git a/rhodecode/api/tests/test_fork_repo.py b/rhodecode/api/tests/test_fork_repo.py index f24b1a90..bb4cb554 100644 --- a/rhodecode/api/tests/test_fork_repo.py +++ b/rhodecode/api/tests/test_fork_repo.py @@ -28,7 +28,7 @@ from rhodecode.model.user import UserModel from rhodecode.tests import TEST_USER_ADMIN_LOGIN from rhodecode.api.tests.utils import ( build_data, api_call, assert_error, assert_ok, crash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture fixture = Fixture() diff --git a/rhodecode/api/tests/test_update_repo.py b/rhodecode/api/tests/test_update_repo.py index f978b9aa..64f98f7c 100644 --- a/rhodecode/api/tests/test_update_repo.py +++ b/rhodecode/api/tests/test_update_repo.py @@ -25,8 +25,8 @@ from rhodecode.model.scm import ScmModel from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN from rhodecode.api.tests.utils import ( build_data, api_call, assert_error, assert_ok, crash, jsonify) -from rhodecode.tests.fixture import Fixture -from rhodecode.tests.fixture_mods.fixture_utils import plain_http_host_only_stub +from rhodecode.tests.fixtures.rc_fixture import Fixture +from rhodecode.tests.fixtures.fixture_utils import plain_http_host_only_stub fixture = Fixture() diff --git a/rhodecode/apps/admin/tests/test_admin_audit_logs.py b/rhodecode/apps/admin/tests/test_admin_audit_logs.py index da127e3d..fe1475c3 100644 --- a/rhodecode/apps/admin/tests/test_admin_audit_logs.py +++ b/rhodecode/apps/admin/tests/test_admin_audit_logs.py @@ -26,7 +26,7 @@ import pytest from rhodecode.lib.str_utils import safe_str from rhodecode.tests import * from rhodecode.tests.routes import route_path -from rhodecode.tests.fixture import FIXTURES +from rhodecode.tests.fixtures.rc_fixture import FIXTURES from rhodecode.model.db import UserLog from rhodecode.model.meta import Session diff --git a/rhodecode/apps/admin/tests/test_admin_main_views.py b/rhodecode/apps/admin/tests/test_admin_main_views.py index b0b75eab..1520cc51 100644 --- a/rhodecode/apps/admin/tests/test_admin_main_views.py +++ b/rhodecode/apps/admin/tests/test_admin_main_views.py @@ -20,7 +20,7 @@ import pytest from rhodecode.tests import TestController -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/admin/tests/test_admin_repos.py b/rhodecode/apps/admin/tests/test_admin_repos.py index 014a79b7..ab09b219 100644 --- a/rhodecode/apps/admin/tests/test_admin_repos.py +++ b/rhodecode/apps/admin/tests/test_admin_repos.py @@ -37,7 +37,7 @@ from rhodecode.model.user import UserModel from rhodecode.tests import ( login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) -from rhodecode.tests.fixture import Fixture, error_function +from rhodecode.tests.fixtures.rc_fixture import Fixture, error_function from rhodecode.tests.utils import repo_on_filesystem from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/admin/tests/test_admin_repository_groups.py b/rhodecode/apps/admin/tests/test_admin_repository_groups.py index 1f94bce2..9d168f39 100644 --- a/rhodecode/apps/admin/tests/test_admin_repository_groups.py +++ b/rhodecode/apps/admin/tests/test_admin_repository_groups.py @@ -27,7 +27,7 @@ from rhodecode.model.meta import Session from rhodecode.model.repo_group import RepoGroupModel from rhodecode.tests import ( assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/admin/tests/test_admin_user_groups.py b/rhodecode/apps/admin/tests/test_admin_user_groups.py index c6dbe1d0..61d37d10 100644 --- a/rhodecode/apps/admin/tests/test_admin_user_groups.py +++ b/rhodecode/apps/admin/tests/test_admin_user_groups.py @@ -24,7 +24,7 @@ from rhodecode.model.meta import Session from rhodecode.tests import ( TestController, assert_session_flash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/admin/tests/test_admin_users.py b/rhodecode/apps/admin/tests/test_admin_users.py index 39b83e21..095d84a0 100644 --- a/rhodecode/apps/admin/tests/test_admin_users.py +++ b/rhodecode/apps/admin/tests/test_admin_users.py @@ -28,7 +28,7 @@ from rhodecode.model.user import UserModel from rhodecode.tests import ( TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py b/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py index 1d1e09b3..33518b74 100644 --- a/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py +++ b/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py @@ -22,7 +22,7 @@ import pytest from rhodecode.model.db import User, UserSshKeys from rhodecode.tests import TestController, assert_session_flash -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/home/tests/test_get_goto_switched_data.py b/rhodecode/apps/home/tests/test_get_goto_switched_data.py index b7d6e269..551fcbe9 100644 --- a/rhodecode/apps/home/tests/test_get_goto_switched_data.py +++ b/rhodecode/apps/home/tests/test_get_goto_switched_data.py @@ -27,7 +27,7 @@ from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.db import Session, Repository, RepoGroup from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/home/tests/test_get_repo_list_data.py b/rhodecode/apps/home/tests/test_get_repo_list_data.py index 52139aed..bf6b7d31 100644 --- a/rhodecode/apps/home/tests/test_get_repo_list_data.py +++ b/rhodecode/apps/home/tests/test_get_repo_list_data.py @@ -22,7 +22,7 @@ from rhodecode.model.db import Repository from rhodecode.lib.ext_json import json from rhodecode.tests import TestController -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/home/tests/test_get_user_data.py b/rhodecode/apps/home/tests/test_get_user_data.py index d1128c5c..2113b360 100644 --- a/rhodecode/apps/home/tests/test_get_user_data.py +++ b/rhodecode/apps/home/tests/test_get_user_data.py @@ -20,7 +20,7 @@ import pytest from rhodecode.lib.ext_json import json from rhodecode.tests import TestController -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/home/tests/test_get_user_group_data.py b/rhodecode/apps/home/tests/test_get_user_group_data.py index dea6573f..9fdb613c 100644 --- a/rhodecode/apps/home/tests/test_get_user_group_data.py +++ b/rhodecode/apps/home/tests/test_get_user_group_data.py @@ -40,7 +40,7 @@ import pytest from rhodecode.lib.ext_json import json from rhodecode.tests import TestController -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/home/tests/test_home.py b/rhodecode/apps/home/tests/test_home.py index 9b855285..fdcfb634 100644 --- a/rhodecode/apps/home/tests/test_home.py +++ b/rhodecode/apps/home/tests/test_home.py @@ -24,7 +24,7 @@ from rhodecode.model.db import Repository, RepoGroup, User from rhodecode.model.meta import Session from rhodecode.model.settings import SettingsModel from rhodecode.tests import TestController -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/login/tests/test_2fa.py b/rhodecode/apps/login/tests/test_2fa.py index 3c3ec102..0e193253 100644 --- a/rhodecode/apps/login/tests/test_2fa.py +++ b/rhodecode/apps/login/tests/test_2fa.py @@ -3,7 +3,7 @@ import mock from rhodecode.lib.type_utils import AttributeDict from rhodecode.model.meta import Session -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path from rhodecode.model.settings import SettingsModel diff --git a/rhodecode/apps/login/tests/test_login.py b/rhodecode/apps/login/tests/test_login.py index 424cfabf..22c9ac8e 100644 --- a/rhodecode/apps/login/tests/test_login.py +++ b/rhodecode/apps/login/tests/test_login.py @@ -31,7 +31,7 @@ from rhodecode.model.meta import Session from rhodecode.tests import ( assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN, no_newline_id_generator) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/login/tests/test_password_reset.py b/rhodecode/apps/login/tests/test_password_reset.py index 3d95cc2d..d4d26b4f 100644 --- a/rhodecode/apps/login/tests/test_password_reset.py +++ b/rhodecode/apps/login/tests/test_password_reset.py @@ -22,7 +22,7 @@ from rhodecode.lib import helpers as h from rhodecode.tests import ( TestController, clear_cache_regions, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.utils import AssertResponse from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py index 5923c751..162221dc 100644 --- a/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py +++ b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py @@ -22,7 +22,7 @@ from rhodecode.apps._base import ADMIN_PREFIX from rhodecode.model.db import User from rhodecode.tests import ( TestController, assert_session_flash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/my_account/tests/test_my_account_emails.py b/rhodecode/apps/my_account/tests/test_my_account_emails.py index ba4f542b..4ca87b60 100644 --- a/rhodecode/apps/my_account/tests/test_my_account_emails.py +++ b/rhodecode/apps/my_account/tests/test_my_account_emails.py @@ -23,7 +23,7 @@ from rhodecode.model.db import User, UserEmailMap from rhodecode.tests import ( TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL, assert_session_flash, TEST_USER_REGULAR_PASS) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/my_account/tests/test_my_account_notifications.py b/rhodecode/apps/my_account/tests/test_my_account_notifications.py index f6746159..a8259dd7 100644 --- a/rhodecode/apps/my_account/tests/test_my_account_notifications.py +++ b/rhodecode/apps/my_account/tests/test_my_account_notifications.py @@ -21,7 +21,7 @@ import pytest from rhodecode.tests import ( TestController, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path from rhodecode.model.db import Notification, User diff --git a/rhodecode/apps/my_account/tests/test_my_account_password.py b/rhodecode/apps/my_account/tests/test_my_account_password.py index a1841e1b..23f5bdfa 100644 --- a/rhodecode/apps/my_account/tests/test_my_account_password.py +++ b/rhodecode/apps/my_account/tests/test_my_account_password.py @@ -24,7 +24,7 @@ from rhodecode.lib.auth import check_password from rhodecode.model.meta import Session from rhodecode.model.user import UserModel from rhodecode.tests import assert_session_flash, TestController -from rhodecode.tests.fixture import Fixture, error_function +from rhodecode.tests.fixtures.rc_fixture import Fixture, error_function from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/my_account/tests/test_my_account_profile.py b/rhodecode/apps/my_account/tests/test_my_account_profile.py index 79117835..fe8d9ac8 100644 --- a/rhodecode/apps/my_account/tests/test_my_account_profile.py +++ b/rhodecode/apps/my_account/tests/test_my_account_profile.py @@ -20,7 +20,7 @@ from rhodecode.tests import ( TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/my_account/tests/test_my_account_simple_views.py b/rhodecode/apps/my_account/tests/test_my_account_simple_views.py index 154f4768..1fb74f8c 100644 --- a/rhodecode/apps/my_account/tests/test_my_account_simple_views.py +++ b/rhodecode/apps/my_account/tests/test_my_account_simple_views.py @@ -19,7 +19,7 @@ from rhodecode.model.db import User, Repository, UserFollowing from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py b/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py index 7e769eda..cf853330 100644 --- a/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py +++ b/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py @@ -21,7 +21,7 @@ from rhodecode.model.db import User, UserSshKeys from rhodecode.tests import TestController, assert_session_flash -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py b/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py index 8ba459eb..2d81aaad 100644 --- a/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py +++ b/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py @@ -22,7 +22,7 @@ import pytest from rhodecode.apps.repository.tests.test_repo_compare import ComparePage from rhodecode.lib.vcs import nodes from rhodecode.lib.vcs.backends.base import EmptyCommit -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.utils import commit_change from rhodecode.tests.routes import route_path @@ -166,14 +166,15 @@ class TestSideBySideDiff(object): response.mustcontain('Collapse 2 commits') response.mustcontain('123 file changed') - response.mustcontain( - 'r%s:%s...r%s:%s' % ( - commit1.idx, commit1.short_id, commit2.idx, commit2.short_id)) + response.mustcontain(f'r{commit1.idx}:{commit1.short_id}...r{commit2.idx}:{commit2.short_id}') response.mustcontain(f_path) - @pytest.mark.xfail(reason='GIT does not handle empty commit compare correct (missing 1 commit)') + #@pytest.mark.xfail(reason='GIT does not handle empty commit compare correct (missing 1 commit)') def test_diff_side_by_side_from_0_commit_with_file_filter(self, app, backend, backend_stub): + if backend.alias == 'git': + pytest.skip('GIT does not handle empty commit compare correct (missing 1 commit)') + f_path = b'test_sidebyside_file.py' commit1_content = b'content-25d7e49c18b159446c\n' commit2_content = b'content-603d6c72c46d953420\n' @@ -200,9 +201,7 @@ class TestSideBySideDiff(object): response.mustcontain('Collapse 2 commits') response.mustcontain('1 file changed') - response.mustcontain( - 'r%s:%s...r%s:%s' % ( - commit1.idx, commit1.short_id, commit2.idx, commit2.short_id)) + response.mustcontain(f'r{commit1.idx}:{commit1.short_id}...r{commit2.idx}:{commit2.short_id}') response.mustcontain(f_path) diff --git a/rhodecode/apps/repository/tests/test_repo_files.py b/rhodecode/apps/repository/tests/test_repo_files.py index 01301c8f..c59aed27 100644 --- a/rhodecode/apps/repository/tests/test_repo_files.py +++ b/rhodecode/apps/repository/tests/test_repo_files.py @@ -33,7 +33,7 @@ from rhodecode.lib.vcs.conf import settings from rhodecode.model.db import Session, Repository from rhodecode.tests import assert_session_flash -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/repository/tests/test_repo_forks.py b/rhodecode/apps/repository/tests/test_repo_forks.py index 978b86ff..e52dc880 100644 --- a/rhodecode/apps/repository/tests/test_repo_forks.py +++ b/rhodecode/apps/repository/tests/test_repo_forks.py @@ -21,7 +21,7 @@ import pytest from rhodecode.tests import TestController, assert_session_flash, HG_FORK, GIT_FORK -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.lib import helpers as h from rhodecode.model.db import Repository diff --git a/rhodecode/apps/repository/tests/test_repo_maintainance.py b/rhodecode/apps/repository/tests/test_repo_maintainance.py index 8b812084..86e0ad49 100644 --- a/rhodecode/apps/repository/tests/test_repo_maintainance.py +++ b/rhodecode/apps/repository/tests/test_repo_maintainance.py @@ -21,7 +21,7 @@ import pytest from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/repository/tests/test_repo_pullrequests.py b/rhodecode/apps/repository/tests/test_repo_pullrequests.py index 1df532a3..ad41a2cb 100644 --- a/rhodecode/apps/repository/tests/test_repo_pullrequests.py +++ b/rhodecode/apps/repository/tests/test_repo_pullrequests.py @@ -15,6 +15,9 @@ # 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 logging +import os + import mock import pytest @@ -41,7 +44,7 @@ from rhodecode.tests import ( TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, ) -from rhodecode.tests.fixture_mods.fixture_utils import PRTestUtility +from rhodecode.tests.fixtures.fixture_utils import PRTestUtility from rhodecode.tests.routes import route_path @@ -1050,7 +1053,6 @@ class TestPullrequestsView(object): ) assert len(notifications.all()) == 2 - @pytest.mark.xfail(reason="unable to fix this test after python3 migration") def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token): commits = [ { @@ -1125,20 +1127,38 @@ class TestPullrequestsView(object): response.mustcontain(no=["content_of_ancestor-child"]) response.mustcontain("content_of_change") - def test_merge_pull_request_enabled(self, pr_util, csrf_token): - # Clear any previous calls to rcextensions - rhodecode.EXTENSIONS.calls.clear() + def test_merge_pull_request_enabled(self, pr_util, csrf_token, rcextensions_modification): pull_request = pr_util.create_pull_request(approved=True, mergeable=True) pull_request_id = pull_request.pull_request_id - repo_name = (pull_request.target_repo.scm_instance().name,) + repo_name = pull_request.target_repo.scm_instance().name url = route_path( "pullrequest_merge", - repo_name=str(repo_name[0]), + repo_name=repo_name, pull_request_id=pull_request_id, ) - response = self.app.post(url, params={"csrf_token": csrf_token}).follow() + + rcstack_location = os.path.dirname(self.app._pyramid_registry.settings['__file__']) + rc_ext_location = os.path.join(rcstack_location, 'rcextension-output.txt') + + + mods = [ + ('_push_hook', + f""" + import os + action = kwargs['action'] + commit_ids = kwargs['commit_ids'] + with open('{rc_ext_location}', 'w') as f: + f.write('test-execution'+os.linesep) + f.write(f'{{action}}'+os.linesep) + f.write(f'{{commit_ids}}'+os.linesep) + return HookResponse(0, 'HOOK_TEST') + """) + ] + # Add the hook + with rcextensions_modification(rcstack_location, mods, create_if_missing=True, force_create=True): + response = self.app.post(url, params={"csrf_token": csrf_token}).follow() pull_request = PullRequest.get(pull_request_id) @@ -1162,12 +1182,39 @@ class TestPullrequestsView(object): assert actions[-1].action == "user.push" assert actions[-1].action_data["commit_ids"] == pr_commit_ids - # Check post_push rcextension was really executed - push_calls = rhodecode.EXTENSIONS.calls["_push_hook"] - assert len(push_calls) == 1 - unused_last_call_args, last_call_kwargs = push_calls[0] - assert last_call_kwargs["action"] == "push" - assert last_call_kwargs["commit_ids"] == pr_commit_ids + with open(rc_ext_location) as f: + f_data = f.read() + assert 'test-execution' in f_data + for commit_id in pr_commit_ids: + assert f'{commit_id}' in f_data + + def test_merge_pull_request_forbidden_by_pre_push_hook(self, pr_util, csrf_token, rcextensions_modification, caplog): + caplog.set_level(logging.WARNING, logger="rhodecode.model.pull_request") + + pull_request = pr_util.create_pull_request(approved=True, mergeable=True) + pull_request_id = pull_request.pull_request_id + repo_name = pull_request.target_repo.scm_instance().name + + url = route_path( + "pullrequest_merge", + repo_name=repo_name, + pull_request_id=pull_request_id, + ) + + rcstack_location = os.path.dirname(self.app._pyramid_registry.settings['__file__']) + + mods = [ + ('_pre_push_hook', + f""" + return HookResponse(1, 'HOOK_TEST_FORBIDDEN') + """) + ] + # Add the hook + with rcextensions_modification(rcstack_location, mods, create_if_missing=True, force_create=True): + self.app.post(url, params={"csrf_token": csrf_token}) + + assert 'Merge failed, not updating the pull request.' in [r[2] for r in caplog.record_tuples] + def test_merge_pull_request_disabled(self, pr_util, csrf_token): pull_request = pr_util.create_pull_request(mergeable=False) @@ -1523,7 +1570,6 @@ class TestPullrequestsView(object): assert pull_request.revisions == [commit_ids["change-rebased"]] - def test_remove_pull_request_branch(self, backend_git, csrf_token): branch_name = "development" commits = [ diff --git a/rhodecode/apps/repository/tests/test_repo_settings.py b/rhodecode/apps/repository/tests/test_repo_settings.py index 14b7281c..d20fe3cb 100644 --- a/rhodecode/apps/repository/tests/test_repo_settings.py +++ b/rhodecode/apps/repository/tests/test_repo_settings.py @@ -26,7 +26,7 @@ from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User from rhodecode.model.meta import Session from rhodecode.tests import ( TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/repository/tests/test_repo_settings_advanced.py b/rhodecode/apps/repository/tests/test_repo_settings_advanced.py index 1d3cdc80..dc8e8c6a 100644 --- a/rhodecode/apps/repository/tests/test_repo_settings_advanced.py +++ b/rhodecode/apps/repository/tests/test_repo_settings_advanced.py @@ -24,7 +24,7 @@ from rhodecode.model.db import Repository from rhodecode.model.repo import RepoModel from rhodecode.tests import ( HG_REPO, GIT_REPO, assert_session_flash, no_newline_id_generator) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.utils import repo_on_filesystem from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/repository/tests/test_repo_summary.py b/rhodecode/apps/repository/tests/test_repo_summary.py index 8fb75b34..a2a3ab92 100644 --- a/rhodecode/apps/repository/tests/test_repo_summary.py +++ b/rhodecode/apps/repository/tests/test_repo_summary.py @@ -31,7 +31,7 @@ from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel from rhodecode.model.scm import ScmModel from rhodecode.tests import assert_session_flash -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.utils import AssertResponse, repo_on_filesystem from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/repository/tests/test_repo_vcs_settings.py b/rhodecode/apps/repository/tests/test_repo_vcs_settings.py index cd41d528..63accea0 100644 --- a/rhodecode/apps/repository/tests/test_repo_vcs_settings.py +++ b/rhodecode/apps/repository/tests/test_repo_vcs_settings.py @@ -30,7 +30,7 @@ from rhodecode.model.user import UserModel from rhodecode.tests import ( login_user_session, logout_user_session, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.utils import AssertResponse from rhodecode.tests.routes import route_path diff --git a/rhodecode/apps/repository/tests/test_vcs_settings.py b/rhodecode/apps/repository/tests/test_vcs_settings.py index 72d9a07c..0b05c64a 100644 --- a/rhodecode/apps/repository/tests/test_vcs_settings.py +++ b/rhodecode/apps/repository/tests/test_vcs_settings.py @@ -32,16 +32,13 @@ class TestAdminRepoVcsSettings(object): @pytest.mark.parametrize('setting_name, setting_backends', [ ('hg_use_rebase_for_merging', ['hg']), ]) - def test_labs_settings_visible_if_enabled( - self, setting_name, setting_backends, backend): + def test_labs_settings_visible_if_enabled(self, setting_name, setting_backends, backend): if backend.alias not in setting_backends: pytest.skip('Setting not available for backend {}'.format(backend)) - vcs_settings_url = route_path( - 'edit_repo_vcs', repo_name=backend.repo.repo_name) + vcs_settings_url = route_path('edit_repo_vcs', repo_name=backend.repo.repo_name) - with mock.patch.dict( - rhodecode.CONFIG, {'labs_settings_active': 'true'}): + with mock.patch.dict(rhodecode.CONFIG, {'labs_settings_active': 'true'}): response = self.app.get(vcs_settings_url) assertr = response.assert_response() diff --git a/rhodecode/apps/ssh_support/lib/backends/base.py b/rhodecode/apps/ssh_support/lib/backends/base.py index ede5ad3a..639a810e 100644 --- a/rhodecode/apps/ssh_support/lib/backends/base.py +++ b/rhodecode/apps/ssh_support/lib/backends/base.py @@ -20,7 +20,7 @@ import os import sys import logging -from rhodecode.lib.hook_daemon.base import prepare_callback_daemon +from rhodecode.lib.hook_daemon.utils import prepare_callback_daemon from rhodecode.lib.ext_json import sjson as json from rhodecode.lib.vcs.conf import settings as vcs_settings from rhodecode.lib.api_utils import call_service_api @@ -162,9 +162,7 @@ class SshVcsServer(object): extras = {} extras.update(tunnel_extras) - callback_daemon, extras = prepare_callback_daemon( - extras, protocol=self.hooks_protocol, - host=vcs_settings.HOOKS_HOST) + callback_daemon, extras = prepare_callback_daemon(extras, protocol=self.hooks_protocol) with callback_daemon: try: diff --git a/rhodecode/apps/ssh_support/tests/test_server_git.py b/rhodecode/apps/ssh_support/tests/test_server_git.py index 843bec16..fc721340 100644 --- a/rhodecode/apps/ssh_support/tests/test_server_git.py +++ b/rhodecode/apps/ssh_support/tests/test_server_git.py @@ -33,19 +33,24 @@ class GitServerCreator(object): 'app:main': { 'ssh.executable.git': git_path, 'vcs.hooks.protocol.v2': 'celery', + 'app.service_api.host': 'http://localhost', + 'app.service_api.token': 'secret4', + 'rhodecode.api.url': '/_admin/api', } } repo_name = 'test_git' repo_mode = 'receive-pack' user = plain_dummy_user() - def __init__(self): - pass + def __init__(self, service_api_url, ini_file): + self.service_api_url = service_api_url + self.ini_file = ini_file def create(self, **kwargs): + self.config_data['app:main']['app.service_api.host'] = self.service_api_url parameters = { 'store': self.root, - 'ini_path': '', + 'ini_path': self.ini_file, 'user': self.user, 'repo_name': self.repo_name, 'repo_mode': self.repo_mode, @@ -60,12 +65,30 @@ class GitServerCreator(object): return server -@pytest.fixture() -def git_server(app): - return GitServerCreator() +@pytest.fixture(scope='module') +def git_server(request, module_app, rhodecode_factory, available_port_factory): + ini_file = module_app._pyramid_settings['__file__'] + vcsserver_host = module_app._pyramid_settings['vcs.server'] + + store_dir = os.path.dirname(ini_file) + + # start rhodecode for service API + rc = rhodecode_factory( + request, + store_dir=store_dir, + port=available_port_factory(), + overrides=( + {'handler_console': {'level': 'DEBUG'}}, + {'app:main': {'vcs.server': vcsserver_host}}, + {'app:main': {'repo_store.path': store_dir}} + )) + + service_api_url = f'http://{rc.bind_addr}' + + return GitServerCreator(service_api_url, ini_file) -class TestGitServer(object): +class TestGitServer: def test_command(self, git_server): server = git_server.create() @@ -102,14 +125,14 @@ class TestGitServer(object): assert result is value def test_run_returns_executes_command(self, git_server): - server = git_server.create() from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper + server = git_server.create() os.environ['SSH_CLIENT'] = '127.0.0.1' with mock.patch.object(GitTunnelWrapper, 'create_hooks_env') as _patch: _patch.return_value = 0 with mock.patch.object(GitTunnelWrapper, 'command', return_value='date'): - exit_code = server.run() + exit_code = server.run(tunnel_extras={'config': server.ini_path}) assert exit_code == (0, False) @@ -135,7 +158,7 @@ class TestGitServer(object): 'action': action, 'ip': '10.10.10.10', 'locked_by': [None, None], - 'config': '', + 'config': git_server.ini_file, 'repo_store': store, 'server_url': None, 'hooks': ['push', 'pull'], diff --git a/rhodecode/apps/ssh_support/tests/test_server_hg.py b/rhodecode/apps/ssh_support/tests/test_server_hg.py index 9765eb52..e9585eb5 100644 --- a/rhodecode/apps/ssh_support/tests/test_server_hg.py +++ b/rhodecode/apps/ssh_support/tests/test_server_hg.py @@ -17,6 +17,7 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ import os + import mock import pytest @@ -32,22 +33,27 @@ class MercurialServerCreator(object): 'app:main': { 'ssh.executable.hg': hg_path, 'vcs.hooks.protocol.v2': 'celery', + 'app.service_api.host': 'http://localhost', + 'app.service_api.token': 'secret4', + 'rhodecode.api.url': '/_admin/api', } } repo_name = 'test_hg' user = plain_dummy_user() - def __init__(self): - pass + def __init__(self, service_api_url, ini_file): + self.service_api_url = service_api_url + self.ini_file = ini_file def create(self, **kwargs): + self.config_data['app:main']['app.service_api.host'] = self.service_api_url parameters = { 'store': self.root, - 'ini_path': '', + 'ini_path': self.ini_file, 'user': self.user, 'repo_name': self.repo_name, 'user_permissions': { - 'test_hg': 'repository.admin' + self.repo_name: 'repository.admin' }, 'settings': self.config_data['app:main'], 'env': plain_dummy_env() @@ -57,12 +63,30 @@ class MercurialServerCreator(object): return server -@pytest.fixture() -def hg_server(app): - return MercurialServerCreator() +@pytest.fixture(scope='module') +def hg_server(request, module_app, rhodecode_factory, available_port_factory): + ini_file = module_app._pyramid_settings['__file__'] + vcsserver_host = module_app._pyramid_settings['vcs.server'] + + store_dir = os.path.dirname(ini_file) + + # start rhodecode for service API + rc = rhodecode_factory( + request, + store_dir=store_dir, + port=available_port_factory(), + overrides=( + {'handler_console': {'level': 'DEBUG'}}, + {'app:main': {'vcs.server': vcsserver_host}}, + {'app:main': {'repo_store.path': store_dir}} + )) + + service_api_url = f'http://{rc.bind_addr}' + + return MercurialServerCreator(service_api_url, ini_file) -class TestMercurialServer(object): +class TestMercurialServer: def test_command(self, hg_server, tmpdir): server = hg_server.create() @@ -107,7 +131,7 @@ class TestMercurialServer(object): with mock.patch.object(MercurialTunnelWrapper, 'create_hooks_env') as _patch: _patch.return_value = 0 with mock.patch.object(MercurialTunnelWrapper, 'command', return_value='date'): - exit_code = server.run() + exit_code = server.run(tunnel_extras={'config': server.ini_path}) assert exit_code == (0, False) diff --git a/rhodecode/apps/ssh_support/tests/test_server_svn.py b/rhodecode/apps/ssh_support/tests/test_server_svn.py index d8e1d1f0..e78d3149 100644 --- a/rhodecode/apps/ssh_support/tests/test_server_svn.py +++ b/rhodecode/apps/ssh_support/tests/test_server_svn.py @@ -15,7 +15,9 @@ # 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 os + import mock import pytest @@ -26,39 +28,62 @@ from rhodecode.apps.ssh_support.tests.conftest import plain_dummy_env, plain_dum class SubversionServerCreator(object): root = '/tmp/repo/path/' svn_path = '/usr/local/bin/svnserve' + config_data = { 'app:main': { 'ssh.executable.svn': svn_path, 'vcs.hooks.protocol.v2': 'celery', + 'app.service_api.host': 'http://localhost', + 'app.service_api.token': 'secret4', + 'rhodecode.api.url': '/_admin/api', } } repo_name = 'test-svn' user = plain_dummy_user() - def __init__(self): - pass + def __init__(self, service_api_url, ini_file): + self.service_api_url = service_api_url + self.ini_file = ini_file def create(self, **kwargs): + self.config_data['app:main']['app.service_api.host'] = self.service_api_url parameters = { 'store': self.root, - 'repo_name': self.repo_name, - 'ini_path': '', + 'ini_path': self.ini_file, 'user': self.user, + 'repo_name': self.repo_name, 'user_permissions': { self.repo_name: 'repository.admin' }, 'settings': self.config_data['app:main'], 'env': plain_dummy_env() } - parameters.update(kwargs) server = SubversionServer(**parameters) return server -@pytest.fixture() -def svn_server(app): - return SubversionServerCreator() +@pytest.fixture(scope='module') +def svn_server(request, module_app, rhodecode_factory, available_port_factory): + ini_file = module_app._pyramid_settings['__file__'] + vcsserver_host = module_app._pyramid_settings['vcs.server'] + + store_dir = os.path.dirname(ini_file) + + # start rhodecode for service API + rc = rhodecode_factory( + request, + store_dir=store_dir, + port=available_port_factory(), + overrides=( + {'handler_console': {'level': 'DEBUG'}}, + {'app:main': {'vcs.server': vcsserver_host}}, + {'app:main': {'repo_store.path': store_dir}} + )) + + service_api_url = f'http://{rc.bind_addr}' + + return SubversionServerCreator(service_api_url, ini_file) class TestSubversionServer(object): @@ -168,8 +193,9 @@ class TestSubversionServer(object): assert repo_name == expected_match def test_run_returns_executes_command(self, svn_server): - server = svn_server.create() from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper + + server = svn_server.create() os.environ['SSH_CLIENT'] = '127.0.0.1' with mock.patch.object( SubversionTunnelWrapper, 'get_first_client_response', @@ -184,20 +210,18 @@ class TestSubversionServer(object): SubversionTunnelWrapper, 'command', return_value=['date']): - exit_code = server.run() + exit_code = server.run(tunnel_extras={'config': server.ini_path}) # SVN has this differently configured, and we get in our mock env # None as return code assert exit_code == (None, False) def test_run_returns_executes_command_that_cannot_extract_repo_name(self, svn_server): - server = svn_server.create() from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper - with mock.patch.object( - SubversionTunnelWrapper, 'command', - return_value=['date']): - with mock.patch.object( - SubversionTunnelWrapper, 'get_first_client_response', + + server = svn_server.create() + with mock.patch.object(SubversionTunnelWrapper, 'command', return_value=['date']): + with mock.patch.object(SubversionTunnelWrapper, 'get_first_client_response', return_value=None): - exit_code = server.run() + exit_code = server.run(tunnel_extras={'config': server.ini_path}) assert exit_code == (1, False) diff --git a/rhodecode/apps/user_group/tests/test_user_groups.py b/rhodecode/apps/user_group/tests/test_user_groups.py index b2b5cae4..5d5fd018 100644 --- a/rhodecode/apps/user_group/tests/test_user_groups.py +++ b/rhodecode/apps/user_group/tests/test_user_groups.py @@ -22,7 +22,7 @@ from rhodecode.tests import ( TestController, assert_session_flash, TEST_USER_ADMIN_LOGIN) from rhodecode.model.db import UserGroup from rhodecode.model.meta import Session -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/user_group_profile/tests/test_user_group.py b/rhodecode/apps/user_group_profile/tests/test_user_group.py index 51096e70..4740375f 100644 --- a/rhodecode/apps/user_group_profile/tests/test_user_group.py +++ b/rhodecode/apps/user_group_profile/tests/test_user_group.py @@ -18,7 +18,7 @@ from rhodecode.model.user_group import UserGroupModel from rhodecode.tests import ( TestController, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path fixture = Fixture() diff --git a/rhodecode/apps/user_profile/tests/test_users.py b/rhodecode/apps/user_profile/tests/test_users.py index c471adef..79b348c7 100644 --- a/rhodecode/apps/user_profile/tests/test_users.py +++ b/rhodecode/apps/user_profile/tests/test_users.py @@ -22,7 +22,7 @@ from rhodecode.model.db import User from rhodecode.tests import ( TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.utils import AssertResponse from rhodecode.tests.routes import route_path diff --git a/rhodecode/config/environment.py b/rhodecode/config/environment.py index 1c699e65..acc2a76f 100644 --- a/rhodecode/config/environment.py +++ b/rhodecode/config/environment.py @@ -30,7 +30,7 @@ from rhodecode.lib.vcs import connect_vcs log = logging.getLogger(__name__) -def propagate_rhodecode_config(global_config, settings, config): +def propagate_rhodecode_config(global_config, settings, config, full=True): # Store the settings to make them available to other modules. settings_merged = global_config.copy() settings_merged.update(settings) @@ -40,7 +40,7 @@ def propagate_rhodecode_config(global_config, settings, config): rhodecode.PYRAMID_SETTINGS = settings_merged rhodecode.CONFIG = settings_merged - if 'default_user_id' not in rhodecode.CONFIG: + if full and 'default_user_id' not in rhodecode.CONFIG: rhodecode.CONFIG['default_user_id'] = utils.get_default_user_id() log.debug('set rhodecode.CONFIG data') @@ -93,6 +93,7 @@ def load_pyramid_environment(global_config, settings): # first run, to store data... propagate_rhodecode_config(global_config, settings, {}) + if vcs_server_enabled: connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings)) else: diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py index c82395cd..8e34137c 100644 --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -101,6 +101,9 @@ def make_pyramid_app(global_config, **settings): patches.inspect_getargspec() patches.repoze_sendmail_lf_fix() + # first init, so load_pyramid_enviroment, can access some critical data, like __file__ + propagate_rhodecode_config(global_config, {}, {}, full=False) + load_pyramid_environment(global_config, settings) # Static file view comes first diff --git a/rhodecode/config/rcextensions/__init__.py b/rhodecode/config/rcextensions/__init__.py index a4b661c4..ddb69559 100644 --- a/rhodecode/config/rcextensions/__init__.py +++ b/rhodecode/config/rcextensions/__init__.py @@ -17,7 +17,7 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ """ -rcextensions module, please edit `hooks.py` to over write hooks logic +rcextensions module, please edit `hooks.py` to over-write hooks logic """ from .hooks import ( diff --git a/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py b/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py index 1631291c..fbf1d72e 100644 --- a/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py +++ b/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py @@ -85,7 +85,7 @@ def _pre_push_hook(*args, **kwargs): # check files names if forbidden_files: - reason = 'File {} is forbidden to be pushed'.format(file_name) + reason = f'File {file_name} is forbidden to be pushed' for forbidden_pattern in forbid_files: # here we can also filter for operation, e.g if check for only ADDED files # if operation == 'A': diff --git a/rhodecode/config/rcextensions/helpers/extra_fields.py b/rhodecode/config/rcextensions/helpers/extra_fields.py index 093de20d..3bf746d2 100644 --- a/rhodecode/config/rcextensions/helpers/extra_fields.py +++ b/rhodecode/config/rcextensions/helpers/extra_fields.py @@ -1,4 +1,3 @@ - # Copyright (C) 2016-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -55,7 +54,7 @@ def run(*args, **kwargs): return fields -class _Undefined(object): +class _Undefined: pass @@ -67,7 +66,7 @@ def get_field(extra_fields_data, key, default=_Undefined(), convert_type=True): if key not in extra_fields_data: if isinstance(default, _Undefined): - raise ValueError('key {} not present in extra_fields'.format(key)) + raise ValueError(f'key {key} not present in extra_fields') return default # NOTE(dan): from metadata we get field_label, field_value, field_desc, field_type diff --git a/rhodecode/config/rcextensions/helpers/extract_post_commits.py b/rhodecode/config/rcextensions/helpers/extract_post_commits.py index 84ca7e69..9b2d0d9f 100644 --- a/rhodecode/config/rcextensions/helpers/extract_post_commits.py +++ b/rhodecode/config/rcextensions/helpers/extract_post_commits.py @@ -1,4 +1,3 @@ - # Copyright (C) 2016-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify diff --git a/rhodecode/config/rcextensions/helpers/extract_pre_commits.py b/rhodecode/config/rcextensions/helpers/extract_pre_commits.py index b2c1df2b..40702027 100644 --- a/rhodecode/config/rcextensions/helpers/extract_pre_commits.py +++ b/rhodecode/config/rcextensions/helpers/extract_pre_commits.py @@ -1,4 +1,3 @@ - # Copyright (C) 2016-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -52,7 +51,7 @@ def get_git_commits(repo, refs): cmd = [ 'log', '--pretty=format:{"commit_id": "%H", "author": "%aN <%aE>", "date": "%ad", "message": "%s"}', - '{}...{}'.format(old_rev, new_rev) + f'{old_rev}...{new_rev}' ] stdout, stderr = repo.run_git_command(cmd, extra_env=git_env) @@ -80,12 +79,12 @@ def run(*args, **kwargs): if vcs_type == 'git': for rev_data in kwargs['commit_ids']: - new_environ = dict((k, v) for k, v in rev_data['git_env']) + new_environ = {k: v for k, v in rev_data['git_env']} commits = get_git_commits(vcs_repo, kwargs['commit_ids']) if vcs_type == 'hg': for rev_data in kwargs['commit_ids']: - new_environ = dict((k, v) for k, v in rev_data['hg_env']) + new_environ = {k: v for k, v in rev_data['hg_env']} commits = get_hg_commits(vcs_repo, kwargs['commit_ids']) return commits diff --git a/rhodecode/config/rcextensions/helpers/extract_pre_files.py b/rhodecode/config/rcextensions/helpers/extract_pre_files.py index 3d517165..9eb625c8 100644 --- a/rhodecode/config/rcextensions/helpers/extract_pre_files.py +++ b/rhodecode/config/rcextensions/helpers/extract_pre_files.py @@ -1,4 +1,3 @@ - # Copyright (C) 2016-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -133,12 +132,12 @@ def run(*args, **kwargs): if vcs_type == 'git': for rev_data in kwargs['commit_ids']: - new_environ = dict((k, v) for k, v in rev_data['git_env']) + new_environ = {k: v for k, v in rev_data['git_env']} files = get_git_files(repo, vcs_repo, kwargs['commit_ids']) if vcs_type == 'hg': for rev_data in kwargs['commit_ids']: - new_environ = dict((k, v) for k, v in rev_data['hg_env']) + new_environ = {k: v for k, v in rev_data['hg_env']} files = get_hg_files(repo, vcs_repo, kwargs['commit_ids']) if vcs_type == 'svn': diff --git a/rhodecode/config/rcextensions/helpers/http_call.py b/rhodecode/config/rcextensions/helpers/http_call.py index 0bbe2434..b06dd9a0 100644 --- a/rhodecode/config/rcextensions/helpers/http_call.py +++ b/rhodecode/config/rcextensions/helpers/http_call.py @@ -1,4 +1,3 @@ - # Copyright (C) 2016-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify diff --git a/rhodecode/config/rcextensions/utils.py b/rhodecode/config/rcextensions/utils.py index 2f31a873..d7f4b48d 100644 --- a/rhodecode/config/rcextensions/utils.py +++ b/rhodecode/config/rcextensions/utils.py @@ -28,7 +28,7 @@ import urllib.error log = logging.getLogger('rhodecode.' + __name__) -class HookResponse(object): +class HookResponse: def __init__(self, status, output): self.status = status self.output = output @@ -44,6 +44,11 @@ class HookResponse(object): def __bool__(self): return self.status == 0 + def to_json(self): + return {'status': self.status, 'output': self.output} + + def __repr__(self): + return self.to_json().__repr__() class DotDict(dict): @@ -91,8 +96,8 @@ class DotDict(dict): def __repr__(self): keys = list(self.keys()) keys.sort() - args = ', '.join(['%s=%r' % (key, self[key]) for key in keys]) - return '%s(%s)' % (self.__class__.__name__, args) + args = ', '.join(['{}={!r}'.format(key, self[key]) for key in keys]) + return '{}({})'.format(self.__class__.__name__, args) @staticmethod def fromDict(d): @@ -110,7 +115,7 @@ def serialize(x): def unserialize(x): if isinstance(x, dict): - return dict((k, unserialize(v)) for k, v in x.items()) + return {k: unserialize(v) for k, v in x.items()} elif isinstance(x, (list, tuple)): return type(x)(unserialize(v) for v in x) else: @@ -161,7 +166,8 @@ def str2bool(_str) -> bool: string into boolean :param _str: string value to translate into boolean - :returns: bool from given string + :rtype: boolean + :returns: boolean from given string """ if _str is None: return False diff --git a/rhodecode/config/routing_links.py b/rhodecode/config/routing_links.py index 4a782b15..6a569383 100644 --- a/rhodecode/config/routing_links.py +++ b/rhodecode/config/routing_links.py @@ -49,22 +49,22 @@ link_config = [ { "name": "enterprise_docs", "target": "https://rhodecode.com/r1/enterprise/docs/", - "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/", + "external_target": "https://docs.rhodecode.com/4.x/rce/index.html", }, { "name": "enterprise_log_file_locations", "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/", - "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files", + "external_target": "https://docs.rhodecode.com/4.x/rce/admin/system-overview.html#log-files", }, { "name": "enterprise_issue_tracker_settings", "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/", - "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html", + "external_target": "https://docs.rhodecode.com/4.x/rce/issue-trackers/issue-trackers.html", }, { "name": "enterprise_svn_setup", "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/", - "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html", + "external_target": "https://docs.rhodecode.com/4.x/rce/admin/svn-http.html", }, { "name": "enterprise_license_convert_from_old", diff --git a/rhodecode/config/utils.py b/rhodecode/config/utils.py index 17236b25..e3d48d1c 100644 --- a/rhodecode/config/utils.py +++ b/rhodecode/config/utils.py @@ -19,6 +19,8 @@ import os import platform +from rhodecode.lib.type_utils import str2bool + DEFAULT_USER = 'default' @@ -48,28 +50,23 @@ def initialize_database(config): engine = engine_from_config(config, 'sqlalchemy.db1.') init_model(engine, encryption_key=get_encryption_key(config)) +def initialize_test_environment(settings): + skip_test_env = str2bool(os.environ.get('RC_NO_TEST_ENV')) + if skip_test_env: + return -def initialize_test_environment(settings, test_env=None): - if test_env is None: - test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0)) + repo_store_path = os.environ.get('RC_TEST_ENV_REPO_STORE') or settings['repo_store.path'] from rhodecode.lib.utils import ( create_test_directory, create_test_database, create_test_repositories, create_test_index) - from rhodecode.tests import TESTS_TMP_PATH - from rhodecode.lib.vcs.backends.hg import largefiles_store - from rhodecode.lib.vcs.backends.git import lfs_store + create_test_directory(repo_store_path) + + create_test_database(repo_store_path, settings) # test repos - if test_env: - create_test_directory(TESTS_TMP_PATH) - # large object stores - create_test_directory(largefiles_store(TESTS_TMP_PATH)) - create_test_directory(lfs_store(TESTS_TMP_PATH)) - - create_test_database(TESTS_TMP_PATH, settings) - create_test_repositories(TESTS_TMP_PATH, settings) - create_test_index(TESTS_TMP_PATH, settings) + create_test_repositories(repo_store_path, settings) + create_test_index(repo_store_path, settings) def get_vcs_server_protocol(config): diff --git a/rhodecode/lib/exceptions.py b/rhodecode/lib/exceptions.py index d4cd6c17..301e629f 100644 --- a/rhodecode/lib/exceptions.py +++ b/rhodecode/lib/exceptions.py @@ -20,8 +20,7 @@ Set of custom exceptions used in RhodeCode """ -from webob.exc import HTTPClientError -from pyramid.httpexceptions import HTTPBadGateway +from pyramid.httpexceptions import HTTPBadGateway, HTTPClientError class LdapUsernameError(Exception): @@ -102,12 +101,7 @@ class HTTPRequirementError(HTTPClientError): self.args = (message, ) -class ClientNotSupportedError(HTTPRequirementError): - title = explanation = 'Client Not Supported' - reason = None - - -class HTTPLockedRC(HTTPClientError): +class HTTPLockedRepo(HTTPClientError): """ Special Exception For locked Repos in RhodeCode, the return code can be overwritten by _code keyword argument passed into constructors @@ -131,14 +125,13 @@ class HTTPBranchProtected(HTTPClientError): Special Exception For Indicating that branch is protected in RhodeCode, the return code can be overwritten by _code keyword argument passed into constructors """ - code = 403 title = explanation = 'Branch Protected' reason = None - def __init__(self, message, *args, **kwargs): - self.title = self.explanation = message - super().__init__(*args, **kwargs) - self.args = (message, ) + +class ClientNotSupported(HTTPRequirementError): + title = explanation = 'Client Not Supported' + reason = None class IMCCommitError(Exception): diff --git a/rhodecode/lib/hook_daemon/base.py b/rhodecode/lib/hook_daemon/base.py index 705926f4..d0629edb 100644 --- a/rhodecode/lib/hook_daemon/base.py +++ b/rhodecode/lib/hook_daemon/base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -16,13 +16,14 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -import os -import time import logging +import traceback -from rhodecode.lib.config_utils import get_app_config_lightweight +from rhodecode.model import meta +from rhodecode.lib import hooks_base +from rhodecode.lib.utils2 import AttributeDict +from rhodecode.lib.exceptions import HTTPLockedRepo, HTTPBranchProtected -from rhodecode.lib.svn_txn_utils import get_txn_id_from_store log = logging.getLogger(__name__) @@ -42,53 +43,82 @@ class BaseHooksCallbackDaemon: log.debug('Exiting `%s` callback daemon', self.__class__.__name__) -class HooksModuleCallbackDaemon(BaseHooksCallbackDaemon): +class Hooks(object): + """ + Exposes the hooks module for calling them using the local HooksModuleCallbackDaemon + """ + def __init__(self, request=None, log_prefix=''): + self.log_prefix = log_prefix + self.request = request - def __init__(self, module): - super().__init__() - self.hooks_module = module + def repo_size(self, extras): + log.debug("%sCalled repo_size of %s object", self.log_prefix, self) + return self._call_hook(hooks_base.repo_size, extras) - def __repr__(self): - return f'HooksModuleCallbackDaemon(hooks_module={self.hooks_module})' + def pre_pull(self, extras): + log.debug("%sCalled pre_pull of %s object", self.log_prefix, self) + return self._call_hook(hooks_base.pre_pull, extras) + def post_pull(self, extras): + log.debug("%sCalled post_pull of %s object", self.log_prefix, self) + return self._call_hook(hooks_base.post_pull, extras) -def prepare_callback_daemon(extras, protocol, host, txn_id=None): + def pre_push(self, extras): + log.debug("%sCalled pre_push of %s object", self.log_prefix, self) + return self._call_hook(hooks_base.pre_push, extras) - match protocol: - case 'http': - from rhodecode.lib.hook_daemon.http_hooks_deamon import HttpHooksCallbackDaemon - port = 0 - if txn_id: - # read txn-id to re-use the PORT for callback daemon - repo_path = os.path.join(extras['repo_store'], extras['repository']) - txn_details = get_txn_id_from_store(repo_path, txn_id) - port = txn_details.get('port', 0) + def post_push(self, extras): + log.debug("%sCalled post_push of %s object", self.log_prefix, self) + return self._call_hook(hooks_base.post_push, extras) - callback_daemon = HttpHooksCallbackDaemon( - txn_id=txn_id, host=host, port=port) - case 'celery': - from rhodecode.lib.hook_daemon.celery_hooks_deamon import CeleryHooksCallbackDaemon + def _call_hook(self, hook, extras): + extras = AttributeDict(extras) + _server_url = extras['server_url'] - config = get_app_config_lightweight(extras['config']) - task_queue = config.get('celery.broker_url') - task_backend = config.get('celery.result_backend') + extras.request = self.request + try: + result = hook(extras) + if result is None: + raise Exception(f'Failed to obtain hook result from func: {hook}') + except HTTPBranchProtected as error: + # Those special cases don't need error reporting. It's a case of + # locked repo or protected branch + result = AttributeDict({ + 'status': error.code, + 'output': error.explanation + }) + except HTTPLockedRepo as error: + # Those special cases don't need error reporting. It's a case of + # locked repo or protected branch + result = AttributeDict({ + 'status': error.code, + 'output': error.explanation + }) + except Exception as error: + # locked needs different handling since we need to also + # handle PULL operations + log.exception('%sException when handling hook %s', self.log_prefix, hook) + exc_tb = traceback.format_exc() + error_args = error.args + return { + 'status': 128, + 'output': '', + 'exception': type(error).__name__, + 'exception_traceback': exc_tb, + 'exception_args': error_args, + } + finally: + meta.Session.remove() - callback_daemon = CeleryHooksCallbackDaemon(task_queue, task_backend) - case 'local': - from rhodecode.lib.hook_daemon.hook_module import Hooks - callback_daemon = HooksModuleCallbackDaemon(Hooks.__module__) - case _: - log.error('Unsupported callback daemon protocol "%s"', protocol) - raise Exception('Unsupported callback daemon protocol.') + log.debug('%sGot hook call response %s', self.log_prefix, result) + return { + 'status': result.status, + 'output': result.output, + } - extras['hooks_uri'] = getattr(callback_daemon, 'hooks_uri', '') - extras['task_queue'] = getattr(callback_daemon, 'task_queue', '') - extras['task_backend'] = getattr(callback_daemon, 'task_backend', '') - extras['hooks_protocol'] = protocol - extras['time'] = time.time() + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass - # register txn_id - extras['txn_id'] = txn_id - log.debug('Prepared a callback daemon: %s', - callback_daemon.__class__.__name__) - return callback_daemon, extras diff --git a/rhodecode/lib/hook_daemon/celery_hooks_deamon.py b/rhodecode/lib/hook_daemon/celery_hooks_deamon.py index 245a6bf3..fc67b3cb 100644 --- a/rhodecode/lib/hook_daemon/celery_hooks_deamon.py +++ b/rhodecode/lib/hook_daemon/celery_hooks_deamon.py @@ -22,14 +22,16 @@ from rhodecode.lib.hook_daemon.base import BaseHooksCallbackDaemon class CeleryHooksCallbackDaemon(BaseHooksCallbackDaemon): """ Context manger for achieving a compatibility with celery backend + It is calling a call to vcsserver, where it uses HooksCeleryClient to actually call a task from + + f'rhodecode.lib.celerylib.tasks.{method}' + """ - def __init__(self, task_queue, task_backend): - self.task_queue = task_queue - self.task_backend = task_backend + def __init__(self, broker_url, result_backend): + super().__init__() + self.broker_url = broker_url + self.result_backend = result_backend def __repr__(self): - return f'CeleryHooksCallbackDaemon(task_queue={self.task_queue}, task_backend={self.task_backend})' - - def __repr__(self): - return f'CeleryHooksCallbackDaemon(task_queue={self.task_queue}, task_backend={self.task_backend})' + return f'CeleryHooksCallbackDaemon(broker_url={self.broker_url}, result_backend={self.result_backend})' diff --git a/rhodecode/lib/hook_daemon/hook_module.py b/rhodecode/lib/hook_daemon/hook_module.py index c6e88c0b..1a7e90e0 100644 --- a/rhodecode/lib/hook_daemon/hook_module.py +++ b/rhodecode/lib/hook_daemon/hook_module.py @@ -17,88 +17,18 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ import logging -import traceback -from rhodecode.model import meta - -from rhodecode.lib import hooks_base -from rhodecode.lib.exceptions import HTTPLockedRC, HTTPBranchProtected -from rhodecode.lib.utils2 import AttributeDict +from rhodecode.lib.hook_daemon.base import BaseHooksCallbackDaemon log = logging.getLogger(__name__) -class Hooks(object): - """ - Exposes the hooks for remote callbacks - """ - def __init__(self, request=None, log_prefix=''): - self.log_prefix = log_prefix - self.request = request +class HooksModuleCallbackDaemon(BaseHooksCallbackDaemon): - def repo_size(self, extras): - log.debug("%sCalled repo_size of %s object", self.log_prefix, self) - return self._call_hook(hooks_base.repo_size, extras) + def __init__(self, module): + super().__init__() + self.hooks_module = module - def pre_pull(self, extras): - log.debug("%sCalled pre_pull of %s object", self.log_prefix, self) - return self._call_hook(hooks_base.pre_pull, extras) + def __repr__(self): + return f'HooksModuleCallbackDaemon(hooks_module={self.hooks_module})' - def post_pull(self, extras): - log.debug("%sCalled post_pull of %s object", self.log_prefix, self) - return self._call_hook(hooks_base.post_pull, extras) - - def pre_push(self, extras): - log.debug("%sCalled pre_push of %s object", self.log_prefix, self) - return self._call_hook(hooks_base.pre_push, extras) - - def post_push(self, extras): - log.debug("%sCalled post_push of %s object", self.log_prefix, self) - return self._call_hook(hooks_base.post_push, extras) - - def _call_hook(self, hook, extras): - extras = AttributeDict(extras) - _server_url = extras['server_url'] - - extras.request = self.request - - try: - result = hook(extras) - if result is None: - raise Exception(f'Failed to obtain hook result from func: {hook}') - except HTTPBranchProtected as error: - # Those special cases don't need error reporting. It's a case of - # locked repo or protected branch - result = AttributeDict({ - 'status': error.code, - 'output': error.explanation - }) - except (HTTPLockedRC, Exception) as error: - # locked needs different handling since we need to also - # handle PULL operations - exc_tb = '' - if not isinstance(error, HTTPLockedRC): - exc_tb = traceback.format_exc() - log.exception('%sException when handling hook %s', self.log_prefix, hook) - error_args = error.args - return { - 'status': 128, - 'output': '', - 'exception': type(error).__name__, - 'exception_traceback': exc_tb, - 'exception_args': error_args, - } - finally: - meta.Session.remove() - - log.debug('%sGot hook call response %s', self.log_prefix, result) - return { - 'status': result.status, - 'output': result.output, - } - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - pass diff --git a/rhodecode/lib/hook_daemon/http_hooks_deamon.py b/rhodecode/lib/hook_daemon/http_hooks_deamon.py deleted file mode 100644 index bc86d0cb..00000000 --- a/rhodecode/lib/hook_daemon/http_hooks_deamon.py +++ /dev/null @@ -1,287 +0,0 @@ -# Copyright (C) 2010-2023 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 os -import logging -import traceback -import threading -import socket -import msgpack -import gevent - -from http.server import BaseHTTPRequestHandler -from socketserver import TCPServer - -from rhodecode.model import meta -from rhodecode.lib.ext_json import json -from rhodecode.lib import rc_cache -from rhodecode.lib.svn_txn_utils import get_txn_id_data_key -from rhodecode.lib.hook_daemon.hook_module import Hooks - -log = logging.getLogger(__name__) - - -class HooksHttpHandler(BaseHTTPRequestHandler): - - JSON_HOOKS_PROTO = 'json.v1' - MSGPACK_HOOKS_PROTO = 'msgpack.v1' - # starting with RhodeCode 5.0.0 MsgPack is the default, prior it used json - DEFAULT_HOOKS_PROTO = MSGPACK_HOOKS_PROTO - - @classmethod - def serialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO): - if proto == cls.MSGPACK_HOOKS_PROTO: - return msgpack.packb(data) - return json.dumps(data) - - @classmethod - def deserialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO): - if proto == cls.MSGPACK_HOOKS_PROTO: - return msgpack.unpackb(data) - return json.loads(data) - - def do_POST(self): - hooks_proto, method, extras = self._read_request() - log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto) - - txn_id = getattr(self.server, 'txn_id', None) - if txn_id: - log.debug('Computing TXN_ID based on `%s`:`%s`', - extras['repository'], extras['txn_id']) - computed_txn_id = rc_cache.utils.compute_key_from_params( - extras['repository'], extras['txn_id']) - if txn_id != computed_txn_id: - raise Exception( - 'TXN ID fail: expected {} got {} instead'.format( - txn_id, computed_txn_id)) - - request = getattr(self.server, 'request', None) - try: - hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address)) - result = self._call_hook_method(hooks, method, extras) - - except Exception as e: - exc_tb = traceback.format_exc() - result = { - 'exception': e.__class__.__name__, - 'exception_traceback': exc_tb, - 'exception_args': e.args - } - self._write_response(hooks_proto, result) - - def _read_request(self): - length = int(self.headers['Content-Length']) - # respect sent headers, fallback to OLD proto for compatability - hooks_proto = self.headers.get('rc-hooks-protocol') or self.JSON_HOOKS_PROTO - if hooks_proto == self.MSGPACK_HOOKS_PROTO: - # support for new vcsserver msgpack based protocol hooks - body = self.rfile.read(length) - data = self.deserialize_data(body) - else: - body = self.rfile.read(length) - data = self.deserialize_data(body) - - return hooks_proto, data['method'], data['extras'] - - def _write_response(self, hooks_proto, result): - self.send_response(200) - if hooks_proto == self.MSGPACK_HOOKS_PROTO: - self.send_header("Content-type", "application/msgpack") - self.end_headers() - data = self.serialize_data(result) - self.wfile.write(data) - else: - self.send_header("Content-type", "text/json") - self.end_headers() - data = self.serialize_data(result) - self.wfile.write(data) - - def _call_hook_method(self, hooks, method, extras): - try: - result = getattr(hooks, method)(extras) - finally: - meta.Session.remove() - return result - - def log_message(self, format, *args): - """ - This is an overridden method of BaseHTTPRequestHandler which logs using - a logging library instead of writing directly to stderr. - """ - - message = format % args - - log.debug( - "HOOKS: client=%s - - [%s] %s", self.client_address, - self.log_date_time_string(), message) - - -class ThreadedHookCallbackDaemon(object): - - _callback_thread = None - _daemon = None - _done = False - use_gevent = False - - def __init__(self, txn_id=None, host=None, port=None): - self._prepare(txn_id=txn_id, host=host, port=port) - if self.use_gevent: - self._run_func = self._run_gevent - self._stop_func = self._stop_gevent - else: - self._run_func = self._run - self._stop_func = self._stop - - def __enter__(self): - log.debug('Running `%s` callback daemon', self.__class__.__name__) - self._run_func() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - log.debug('Exiting `%s` callback daemon', self.__class__.__name__) - self._stop_func() - - def _prepare(self, txn_id=None, host=None, port=None): - raise NotImplementedError() - - def _run(self): - raise NotImplementedError() - - def _stop(self): - raise NotImplementedError() - - def _run_gevent(self): - raise NotImplementedError() - - def _stop_gevent(self): - raise NotImplementedError() - - -class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon): - """ - Context manager which will run a callback daemon in a background thread. - """ - - hooks_uri = None - - # From Python docs: Polling reduces our responsiveness to a shutdown - # request and wastes cpu at all other times. - POLL_INTERVAL = 0.01 - - use_gevent = False - - def __repr__(self): - return f'HttpHooksCallbackDaemon(hooks_uri={self.hooks_uri})' - - @property - def _hook_prefix(self): - return f'HOOKS: {self.hooks_uri} ' - - def get_hostname(self): - return socket.gethostname() or '127.0.0.1' - - def get_available_port(self, min_port=20000, max_port=65535): - from rhodecode.lib.utils2 import get_available_port as _get_port - return _get_port(min_port, max_port) - - def _prepare(self, txn_id=None, host=None, port=None): - from pyramid.threadlocal import get_current_request - - if not host or host == "*": - host = self.get_hostname() - if not port: - port = self.get_available_port() - - server_address = (host, port) - self.hooks_uri = f'{host}:{port}' - self.txn_id = txn_id - self._done = False - - log.debug( - "%s Preparing HTTP callback daemon registering hook object: %s", - self._hook_prefix, HooksHttpHandler) - - self._daemon = TCPServer(server_address, HooksHttpHandler) - # inject transaction_id for later verification - self._daemon.txn_id = self.txn_id - - # pass the WEB app request into daemon - self._daemon.request = get_current_request() - - def _run(self): - log.debug("Running thread-based loop of callback daemon in background") - callback_thread = threading.Thread( - target=self._daemon.serve_forever, - kwargs={'poll_interval': self.POLL_INTERVAL}) - callback_thread.daemon = True - callback_thread.start() - self._callback_thread = callback_thread - - def _run_gevent(self): - log.debug("Running gevent-based loop of callback daemon in background") - # create a new greenlet for the daemon's serve_forever method - callback_greenlet = gevent.spawn( - self._daemon.serve_forever, - poll_interval=self.POLL_INTERVAL) - - # store reference to greenlet - self._callback_greenlet = callback_greenlet - - # switch to this greenlet - gevent.sleep(0.01) - - def _stop(self): - log.debug("Waiting for background thread to finish.") - self._daemon.shutdown() - self._callback_thread.join() - self._daemon = None - self._callback_thread = None - if self.txn_id: - #TODO: figure out the repo_path... - repo_path = '' - txn_id_file = get_txn_id_data_key(repo_path, self.txn_id) - log.debug('Cleaning up TXN ID %s', txn_id_file) - if os.path.isfile(txn_id_file): - os.remove(txn_id_file) - - log.debug("Background thread done.") - - def _stop_gevent(self): - log.debug("Waiting for background greenlet to finish.") - - # if greenlet exists and is running - if self._callback_greenlet and not self._callback_greenlet.dead: - # shutdown daemon if it exists - if self._daemon: - self._daemon.shutdown() - - # kill the greenlet - self._callback_greenlet.kill() - - self._daemon = None - self._callback_greenlet = None - - if self.txn_id: - #TODO: figure out the repo_path... - repo_path = '' - txn_id_file = get_txn_id_data_key(repo_path, self.txn_id) - log.debug('Cleaning up TXN ID %s', txn_id_file) - if os.path.isfile(txn_id_file): - os.remove(txn_id_file) - - log.debug("Background greenlet done.") diff --git a/rhodecode/lib/hook_daemon/utils.py b/rhodecode/lib/hook_daemon/utils.py new file mode 100644 index 00000000..b2938e21 --- /dev/null +++ b/rhodecode/lib/hook_daemon/utils.py @@ -0,0 +1,61 @@ +# 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 logging + +from rhodecode.lib.config_utils import get_app_config_lightweight + +from rhodecode.lib.hook_daemon.base import Hooks +from rhodecode.lib.hook_daemon.hook_module import HooksModuleCallbackDaemon +from rhodecode.lib.hook_daemon.celery_hooks_deamon import CeleryHooksCallbackDaemon +from rhodecode.lib.type_utils import str2bool + +log = logging.getLogger(__name__) + + + +def prepare_callback_daemon(extras, protocol: str, txn_id=None): + hooks_config = {} + match protocol: + case 'celery': + config = get_app_config_lightweight(extras['config']) + + broker_url = config.get('celery.broker_url') + result_backend = config.get('celery.result_backend') + + hooks_config = { + 'broker_url': broker_url, + 'result_backend': result_backend, + } + + callback_daemon = CeleryHooksCallbackDaemon(broker_url, result_backend) + case 'local': + callback_daemon = HooksModuleCallbackDaemon(Hooks.__module__) + case _: + log.error('Unsupported callback daemon protocol "%s"', protocol) + raise Exception('Unsupported callback daemon protocol.') + + extras['hooks_config'] = hooks_config + extras['hooks_protocol'] = protocol + extras['time'] = time.time() + + # register txn_id + extras['txn_id'] = txn_id + log.debug('Prepared a callback daemon: %s', callback_daemon.__class__.__name__) + return callback_daemon, extras diff --git a/rhodecode/lib/hooks_base.py b/rhodecode/lib/hooks_base.py index 795987cc..c44d5368 100644 --- a/rhodecode/lib/hooks_base.py +++ b/rhodecode/lib/hooks_base.py @@ -30,14 +30,14 @@ from rhodecode.lib import helpers as h from rhodecode.lib import audit_logger from rhodecode.lib.utils2 import safe_str, user_agent_normalizer from rhodecode.lib.exceptions import ( - HTTPLockedRC, HTTPBranchProtected, UserCreationError, ClientNotSupportedError) + HTTPLockedRepo, HTTPBranchProtected, UserCreationError, ClientNotSupported) from rhodecode.model.db import Repository, User from rhodecode.lib.statsd_client import StatsdClient log = logging.getLogger(__name__) -class HookResponse(object): +class HookResponse: def __init__(self, status, output): self.status = status self.output = output @@ -56,6 +56,8 @@ class HookResponse(object): def to_json(self): return {'status': self.status, 'output': self.output} + def __repr__(self): + return self.to_json().__repr__() def is_shadow_repo(extras): """ @@ -73,8 +75,69 @@ def check_vcs_client(extras): except ModuleNotFoundError: is_vcs_client_whitelisted = lambda *x: True backend = extras.get('scm') - if not is_vcs_client_whitelisted(extras.get('user_agent'), backend): - raise ClientNotSupportedError(f"Your {backend} client is forbidden") + user_agent = extras.get('user_agent') + if not is_vcs_client_whitelisted(user_agent, backend): + raise ClientNotSupported(f"Your {backend} client (version={user_agent}) is forbidden by security rules") + + +def check_locked_repo(extras, check_same_user=True): + user = User.get_by_username(extras.username) + output = '' + if extras.locked_by[0] and (not check_same_user or user.user_id != extras.locked_by[0]): + + locked_by = User.get(extras.locked_by[0]).username + reason = extras.locked_by[2] + # this exception is interpreted in git/hg middlewares and based + # on that proper return code is server to client + _http_ret = HTTPLockedRepo(_locked_by_explanation(extras.repository, locked_by, reason)) + if str(_http_ret.code).startswith('2'): + # 2xx Codes don't raise exceptions + output = _http_ret.title + else: + raise _http_ret + + return output + + +def check_branch_protected(extras): + if extras.commit_ids and extras.check_branch_perms: + user = User.get_by_username(extras.username) + auth_user = user.AuthUser() + repo = Repository.get_by_repo_name(extras.repository) + if not repo: + raise ValueError(f'Repo for {extras.repository} not found') + affected_branches = [] + if repo.repo_type == 'hg': + for entry in extras.commit_ids: + if entry['type'] == 'branch': + is_forced = bool(entry['multiple_heads']) + affected_branches.append([entry['name'], is_forced]) + elif repo.repo_type == 'git': + for entry in extras.commit_ids: + if entry['type'] == 'heads': + is_forced = bool(entry['pruned_sha']) + affected_branches.append([entry['name'], is_forced]) + + for branch_name, is_forced in affected_branches: + + rule, branch_perm = auth_user.get_rule_and_branch_permission(extras.repository, branch_name) + if not branch_perm: + # no branch permission found for this branch, just keep checking + continue + + if branch_perm == 'branch.push_force': + continue + elif branch_perm == 'branch.push' and is_forced is False: + continue + elif branch_perm == 'branch.push' and is_forced is True: + halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}. ' \ + f'FORCE PUSH FORBIDDEN.' + else: + halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}.' + + if halt_message: + _http_ret = HTTPBranchProtected(halt_message) + raise _http_ret def _get_scm_size(alias, root_path): @@ -109,116 +172,30 @@ def repo_size(extras): repo = Repository.get_by_repo_name(extras.repository) vcs_part = f'.{repo.repo_type}' size_vcs, size_root, size_total = _get_scm_size(vcs_part, repo.repo_full_path) - msg = (f'RhodeCode: `{repo.repo_name}` size summary {vcs_part}:{size_vcs} repo:{size_root} total:{size_total}\n') + msg = f'RhodeCode: `{repo.repo_name}` size summary {vcs_part}:{size_vcs} repo:{size_root} total:{size_total}\n' return HookResponse(0, msg) -def pre_push(extras): - """ - Hook executed before pushing code. - - It bans pushing when the repository is locked. - """ - - check_vcs_client(extras) - user = User.get_by_username(extras.username) - output = '' - if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]): - locked_by = User.get(extras.locked_by[0]).username - reason = extras.locked_by[2] - # this exception is interpreted in git/hg middlewares and based - # on that proper return code is server to client - _http_ret = HTTPLockedRC( - _locked_by_explanation(extras.repository, locked_by, reason)) - if str(_http_ret.code).startswith('2'): - # 2xx Codes don't raise exceptions - output = _http_ret.title - else: - raise _http_ret - - hook_response = '' - if not is_shadow_repo(extras): - - if extras.commit_ids and extras.check_branch_perms: - auth_user = user.AuthUser() - repo = Repository.get_by_repo_name(extras.repository) - if not repo: - raise ValueError(f'Repo for {extras.repository} not found') - affected_branches = [] - if repo.repo_type == 'hg': - for entry in extras.commit_ids: - if entry['type'] == 'branch': - is_forced = bool(entry['multiple_heads']) - affected_branches.append([entry['name'], is_forced]) - elif repo.repo_type == 'git': - for entry in extras.commit_ids: - if entry['type'] == 'heads': - is_forced = bool(entry['pruned_sha']) - affected_branches.append([entry['name'], is_forced]) - - for branch_name, is_forced in affected_branches: - - rule, branch_perm = auth_user.get_rule_and_branch_permission( - extras.repository, branch_name) - if not branch_perm: - # no branch permission found for this branch, just keep checking - continue - - if branch_perm == 'branch.push_force': - continue - elif branch_perm == 'branch.push' and is_forced is False: - continue - elif branch_perm == 'branch.push' and is_forced is True: - halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}. ' \ - f'FORCE PUSH FORBIDDEN.' - else: - halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}.' - - if halt_message: - _http_ret = HTTPBranchProtected(halt_message) - raise _http_ret - - # Propagate to external components. This is done after checking the - # lock, for consistent behavior. - hook_response = pre_push_extension( - repo_store_path=Repository.base_path(), **extras) - events.trigger(events.RepoPrePushEvent( - repo_name=extras.repository, extras=extras)) - - return HookResponse(0, output) + hook_response - - def pre_pull(extras): """ Hook executed before pulling the code. It bans pulling when the repository is locked. + It bans pulling when incorrect client is used. """ - - check_vcs_client(extras) output = '' - if extras.locked_by[0]: - locked_by = User.get(extras.locked_by[0]).username - reason = extras.locked_by[2] - # this exception is interpreted in git/hg middlewares and based - # on that proper return code is server to client - _http_ret = HTTPLockedRC( - _locked_by_explanation(extras.repository, locked_by, reason)) - if str(_http_ret.code).startswith('2'): - # 2xx Codes don't raise exceptions - output = _http_ret.title - else: - raise _http_ret + check_vcs_client(extras) + + # locking repo can, but not have to stop the operation it can also just produce output + output += check_locked_repo(extras, check_same_user=False) # Propagate to external components. This is done after checking the # lock, for consistent behavior. hook_response = '' if not is_shadow_repo(extras): extras.hook_type = extras.hook_type or 'pre_pull' - hook_response = pre_pull_extension( - repo_store_path=Repository.base_path(), **extras) - events.trigger(events.RepoPrePullEvent( - repo_name=extras.repository, extras=extras)) + hook_response = pre_pull_extension(repo_store_path=Repository.base_path(), **extras) + events.trigger(events.RepoPrePullEvent(repo_name=extras.repository, extras=extras)) return HookResponse(0, output) + hook_response @@ -239,6 +216,7 @@ def post_pull(extras): statsd.incr('rhodecode_pull_total', tags=[ f'user-agent:{user_agent_normalizer(extras.user_agent)}', ]) + output = '' # make lock is a tri state False, True, None. We only make lock on True if extras.make_lock is True and not is_shadow_repo(extras): @@ -246,18 +224,9 @@ def post_pull(extras): Repository.lock(Repository.get_by_repo_name(extras.repository), user.user_id, lock_reason=Repository.LOCK_PULL) - msg = 'Made lock on repo `{}`'.format(extras.repository) + msg = f'Made lock on repo `{extras.repository}`' output += msg - if extras.locked_by[0]: - locked_by = User.get(extras.locked_by[0]).username - reason = extras.locked_by[2] - _http_ret = HTTPLockedRC( - _locked_by_explanation(extras.repository, locked_by, reason)) - if str(_http_ret.code).startswith('2'): - # 2xx Codes don't raise exceptions - output += _http_ret.title - # Propagate to external components. hook_response = '' if not is_shadow_repo(extras): @@ -270,6 +239,33 @@ def post_pull(extras): return HookResponse(0, output) + hook_response +def pre_push(extras): + """ + Hook executed before pushing code. + + It bans pushing when the repository is locked. + It banks pushing when incorrect client is used. + It also checks for Branch protection + """ + output = '' + check_vcs_client(extras) + + # locking repo can, but not have to stop the operation it can also just produce output + output += check_locked_repo(extras) + + hook_response = '' + if not is_shadow_repo(extras): + + check_branch_protected(extras) + + # Propagate to external components. This is done after checking the + # lock, for consistent behavior. + hook_response = pre_push_extension(repo_store_path=Repository.base_path(), **extras) + events.trigger(events.RepoPrePushEvent(repo_name=extras.repository, extras=extras)) + + return HookResponse(0, output) + hook_response + + def post_push(extras): """Hook executed after user pushes to the repository.""" commit_ids = extras.commit_ids @@ -292,22 +288,13 @@ def post_push(extras): # Propagate to external components. output = '' + # make lock is a tri state False, True, None. We only release lock on False if extras.make_lock is False and not is_shadow_repo(extras): Repository.unlock(Repository.get_by_repo_name(extras.repository)) msg = f'Released lock on repo `{extras.repository}`\n' output += msg - if extras.locked_by[0]: - locked_by = User.get(extras.locked_by[0]).username - reason = extras.locked_by[2] - _http_ret = HTTPLockedRC( - _locked_by_explanation(extras.repository, locked_by, reason)) - # TODO: johbo: if not? - if str(_http_ret.code).startswith('2'): - # 2xx Codes don't raise exceptions - output += _http_ret.title - if extras.new_refs: tmpl = '{}/{}/pull-request/new?{{ref_type}}={{ref_name}}'.format( safe_str(extras.server_url), safe_str(extras.repository)) @@ -322,11 +309,8 @@ def post_push(extras): hook_response = '' if not is_shadow_repo(extras): - hook_response = post_push_extension( - repo_store_path=Repository.base_path(), - **extras) - events.trigger(events.RepoPushEvent( - repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras)) + hook_response = post_push_extension(repo_store_path=Repository.base_path(), **extras) + events.trigger(events.RepoPushEvent(repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras)) output += 'RhodeCode: push completed\n' return HookResponse(0, output) + hook_response @@ -380,12 +364,20 @@ class ExtensionCallback(object): # with older rcextensions that require api_key present if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']: kwargs_to_pass['api_key'] = '_DEPRECATED_' - return callback(**kwargs_to_pass) + result = callback(**kwargs_to_pass) + log.debug('got rcextensions result: %s', result) + return result def is_active(self): return hasattr(rhodecode.EXTENSIONS, self._hook_name) def _get_callback(self): + if rhodecode.is_test: + log.debug('In test mode, reloading rcextensions...') + # NOTE: for test re-load rcextensions always so we can dynamically change them for testing purposes + from rhodecode.lib.utils import load_rcextensions + load_rcextensions(root_path=os.path.dirname(rhodecode.CONFIG['__file__'])) + return getattr(rhodecode.EXTENSIONS, self._hook_name, None) return getattr(rhodecode.EXTENSIONS, self._hook_name, None) diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py index f709a646..9580f827 100644 --- a/rhodecode/lib/middleware/simplegit.py +++ b/rhodecode/lib/middleware/simplegit.py @@ -40,16 +40,6 @@ GIT_PROTO_PAT = re.compile( GIT_LFS_PROTO_PAT = re.compile(r'^/(.+)/(info/lfs/(.+))') -def default_lfs_store(): - """ - Default lfs store location, it's consistent with Mercurials large file - store which is in .cache/largefiles - """ - from rhodecode.lib.vcs.backends.git import lfs_store - user_home = os.path.expanduser("~") - return lfs_store(user_home) - - class SimpleGit(simplevcs.SimpleVCS): SCM = 'git' @@ -151,6 +141,6 @@ class SimpleGit(simplevcs.SimpleVCS): extras['git_lfs_enabled'] = utils2.str2bool( config.get('vcs_git_lfs', 'enabled')) - extras['git_lfs_store_path'] = custom_store or default_lfs_store() + extras['git_lfs_store_path'] = custom_store extras['git_lfs_http_scheme'] = scheme return extras diff --git a/rhodecode/lib/middleware/simplevcs.py b/rhodecode/lib/middleware/simplevcs.py index b3f7c230..bd6d8c16 100644 --- a/rhodecode/lib/middleware/simplevcs.py +++ b/rhodecode/lib/middleware/simplevcs.py @@ -1,5 +1,3 @@ - - # Copyright (C) 2014-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -32,8 +30,7 @@ from functools import wraps import time from paste.httpheaders import REMOTE_USER, AUTH_TYPE -from pyramid.httpexceptions import ( - HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError) +from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError from zope.cachedescriptors.property import Lazy as LazyProperty import rhodecode @@ -41,10 +38,9 @@ from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin from rhodecode.lib import rc_cache from rhodecode.lib.svn_txn_utils import store_txn_id_data from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware -from rhodecode.lib.base import ( - BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context) -from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError) -from rhodecode.lib.hook_daemon.base import prepare_callback_daemon +from rhodecode.lib.base import BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context +from rhodecode.lib.exceptions import UserCreationError, NotAllowedToCreateUserError +from rhodecode.lib.hook_daemon.utils import prepare_callback_daemon from rhodecode.lib.middleware import appenlight from rhodecode.lib.middleware.utils import scm_app_http from rhodecode.lib.str_utils import safe_bytes, safe_int @@ -78,17 +74,18 @@ def initialize_generator(factory): try: init = next(gen) except StopIteration: - raise ValueError('Generator must yield at least one element.') + raise ValueError("Generator must yield at least one element.") if init != "__init__": raise ValueError('First yielded element must be "__init__".') return gen + return wrapper class SimpleVCS(object): """Common functionality for SCM HTTP handlers.""" - SCM = 'unknown' + SCM = "unknown" acl_repo_name = None url_repo_name = None @@ -100,11 +97,11 @@ class SimpleVCS(object): # we use this regex which will match only on URLs pointing to shadow # repositories. shadow_repo_re = re.compile( - '(?P(?:{slug_pat}/)*)' # repo groups - '(?P{slug_pat})/' # target repo - 'pull-request/(?P\\d+)/' # pull request - 'repository$' # shadow repo - .format(slug_pat=SLUG_RE.pattern)) + "(?P(?:{slug_pat}/)*)" # repo groups + "(?P{slug_pat})/" # target repo + "pull-request/(?P\\d+)/" # pull request + "repository$".format(slug_pat=SLUG_RE.pattern) # shadow repo + ) def __init__(self, config, registry): self.registry = registry @@ -113,15 +110,14 @@ class SimpleVCS(object): self.repo_vcs_config = base.Config() rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False) - realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH' + realm = rc_settings.get("rhodecode_realm") or "RhodeCode AUTH" # authenticate this VCS request using authfunc - auth_ret_code_detection = \ - str2bool(self.config.get('auth_ret_code_detection', False)) + auth_ret_code_detection = str2bool(self.config.get("auth_ret_code_detection", False)) self.authenticate = BasicAuth( - '', authenticate, registry, config.get('auth_ret_code'), - auth_ret_code_detection, rc_realm=realm) - self.ip_addr = '0.0.0.0' + "", authenticate, registry, config.get("auth_ret_code"), auth_ret_code_detection, rc_realm=realm + ) + self.ip_addr = "0.0.0.0" @LazyProperty def global_vcs_config(self): @@ -132,10 +128,10 @@ class SimpleVCS(object): @property def base_path(self): - settings_path = self.config.get('repo_store.path') + settings_path = self.config.get("repo_store.path") if not settings_path: - raise ValueError('FATAL: repo_store.path is empty') + raise ValueError("FATAL: repo_store.path is empty") return settings_path def set_repo_names(self, environ): @@ -164,17 +160,16 @@ class SimpleVCS(object): match_dict = match.groupdict() # Build acl repo name from regex match. - acl_repo_name = safe_str('{groups}{target}'.format( - groups=match_dict['groups'] or '', - target=match_dict['target'])) + acl_repo_name = safe_str( + "{groups}{target}".format(groups=match_dict["groups"] or "", target=match_dict["target"]) + ) # Retrieve pull request instance by ID from regex match. - pull_request = PullRequest.get(match_dict['pr_id']) + pull_request = PullRequest.get(match_dict["pr_id"]) # Only proceed if we got a pull request and if acl repo name from # URL equals the target repo name of the pull request. if pull_request and (acl_repo_name == pull_request.target_repo.repo_name): - # Get file system path to shadow repository. workspace_id = PullRequestModel()._workspace_id(pull_request) vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id) @@ -184,21 +179,23 @@ class SimpleVCS(object): self.acl_repo_name = acl_repo_name self.is_shadow_repo = True - log.debug('Setting all VCS repository names: %s', { - 'acl_repo_name': self.acl_repo_name, - 'url_repo_name': self.url_repo_name, - 'vcs_repo_name': self.vcs_repo_name, - }) + log.debug( + "Setting all VCS repository names: %s", + { + "acl_repo_name": self.acl_repo_name, + "url_repo_name": self.url_repo_name, + "vcs_repo_name": self.vcs_repo_name, + }, + ) @property def scm_app(self): - custom_implementation = self.config['vcs.scm_app_implementation'] - if custom_implementation == 'http': - log.debug('Using HTTP implementation of scm app.') + custom_implementation = self.config["vcs.scm_app_implementation"] + if custom_implementation == "http": + log.debug("Using HTTP implementation of scm app.") scm_app_impl = scm_app_http else: - log.debug('Using custom implementation of scm_app: "{}"'.format( - custom_implementation)) + log.debug('Using custom implementation of scm_app: "{}"'.format(custom_implementation)) scm_app_impl = importlib.import_module(custom_implementation) return scm_app_impl @@ -208,17 +205,18 @@ class SimpleVCS(object): with a repository_name for support of _ non changeable urls """ - data = repo_name.split('/') + data = repo_name.split("/") if len(data) >= 2: from rhodecode.model.repo import RepoModel + by_id_match = RepoModel().get_repo_by_id(repo_name) if by_id_match: data[1] = by_id_match.repo_name # Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO # and we use this data - maybe_new_path = '/'.join(data) - return safe_bytes(maybe_new_path).decode('latin1') + maybe_new_path = "/".join(data) + return safe_bytes(maybe_new_path).decode("latin1") def _invalidate_cache(self, repo_name): """ @@ -231,21 +229,18 @@ class SimpleVCS(object): def is_valid_and_existing_repo(self, repo_name, base_path, scm_type): db_repo = Repository.get_by_repo_name(repo_name) if not db_repo: - log.debug('Repository `%s` not found inside the database.', - repo_name) + log.debug("Repository `%s` not found inside the database.", repo_name) return False if db_repo.repo_type != scm_type: log.warning( - 'Repository `%s` have incorrect scm_type, expected %s got %s', - repo_name, db_repo.repo_type, scm_type) + "Repository `%s` have incorrect scm_type, expected %s got %s", repo_name, db_repo.repo_type, scm_type + ) return False config = db_repo._config - config.set('extensions', 'largefiles', '') - return is_valid_repo( - repo_name, base_path, - explicit_scm=scm_type, expect_scm=scm_type, config=config) + config.set("extensions", "largefiles", "") + return is_valid_repo(repo_name, base_path, explicit_scm=scm_type, expect_scm=scm_type, config=config) def valid_and_active_user(self, user): """ @@ -267,8 +262,9 @@ class SimpleVCS(object): def is_shadow_repo_dir(self): return os.path.isdir(self.vcs_repo_name) - def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None, - plugin_id='', plugin_cache_active=False, cache_ttl=0): + def _check_permission( + self, action, user, auth_user, repo_name, ip_addr=None, plugin_id="", plugin_cache_active=False, cache_ttl=0 + ): """ Checks permissions using action (push/pull) user and repository name. If plugin_cache and ttl is set it will use the plugin which @@ -280,71 +276,67 @@ class SimpleVCS(object): :param repo_name: repository name """ - log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)', - plugin_id, plugin_cache_active, cache_ttl) + log.debug("AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)", plugin_id, plugin_cache_active, cache_ttl) user_id = user.user_id - cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}' - region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid) + cache_namespace_uid = f"cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}" + region = rc_cache.get_or_create_region("cache_perms", cache_namespace_uid) - @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, - expiration_time=cache_ttl, - condition=plugin_cache_active) - def compute_perm_vcs( - cache_name, plugin_id, action, user_id, repo_name, ip_addr): - - log.debug('auth: calculating permission access now for vcs operation: %s', action) + @region.conditional_cache_on_arguments( + namespace=cache_namespace_uid, expiration_time=cache_ttl, condition=plugin_cache_active + ) + def compute_perm_vcs(cache_name, plugin_id, action, user_id, repo_name, ip_addr): + log.debug("auth: calculating permission access now for vcs operation: %s", action) # check IP inherit = user.inherit_default_permissions - ip_allowed = AuthUser.check_ip_allowed( - user_id, ip_addr, inherit_from_default=inherit) + ip_allowed = AuthUser.check_ip_allowed(user_id, ip_addr, inherit_from_default=inherit) if ip_allowed: - log.info('Access for IP:%s allowed', ip_addr) + log.info("Access for IP:%s allowed", ip_addr) else: return False - if action == 'push': - perms = ('repository.write', 'repository.admin') + if action == "push": + perms = ("repository.write", "repository.admin") if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name): return False else: # any other action need at least read permission - perms = ( - 'repository.read', 'repository.write', 'repository.admin') + perms = ("repository.read", "repository.write", "repository.admin") if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name): return False return True start = time.time() - log.debug('Running plugin `%s` permissions check', plugin_id) + log.debug("Running plugin `%s` permissions check", plugin_id) # for environ based auth, password can be empty, but then the validation is # on the server that fills in the env data needed for authentication - perm_result = compute_perm_vcs( - 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr) + perm_result = compute_perm_vcs("vcs_permissions", plugin_id, action, user.user_id, repo_name, ip_addr) auth_time = time.time() - start - log.debug('Permissions for plugin `%s` completed in %.4fs, ' - 'expiration time of fetched cache %.1fs.', - plugin_id, auth_time, cache_ttl) + log.debug( + "Permissions for plugin `%s` completed in %.4fs, " "expiration time of fetched cache %.1fs.", + plugin_id, + auth_time, + cache_ttl, + ) return perm_result def _get_http_scheme(self, environ): try: - return environ['wsgi.url_scheme'] + return environ["wsgi.url_scheme"] except Exception: - log.exception('Failed to read http scheme') - return 'http' + log.exception("Failed to read http scheme") + return "http" def _get_default_cache_ttl(self): # take AUTH_CACHE_TTL from the `rhodecode` auth plugin - plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode') + plugin = loadplugin("egg:rhodecode-enterprise-ce#rhodecode") plugin_settings = plugin.get_settings() - plugin_cache_active, cache_ttl = plugin.get_ttl_cache( - plugin_settings) or (False, 0) + plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings) or (False, 0) return plugin_cache_active, cache_ttl def __call__(self, environ, start_response): @@ -359,17 +351,17 @@ class SimpleVCS(object): def _handle_request(self, environ, start_response): if not self.url_repo_name: - log.warning('Repository name is empty: %s', self.url_repo_name) + log.warning("Repository name is empty: %s", self.url_repo_name) # failed to get repo name, we fail now return HTTPNotFound()(environ, start_response) - log.debug('Extracted repo name is %s', self.url_repo_name) + log.debug("Extracted repo name is %s", self.url_repo_name) ip_addr = get_ip_addr(environ) user_agent = get_user_agent(environ) username = None # skip passing error to error controller - environ['pylons.status_code_redirect'] = True + environ["pylons.status_code_redirect"] = True # ====================================================================== # GET ACTION PULL or PUSH @@ -380,17 +372,15 @@ class SimpleVCS(object): # Check if this is a request to a shadow repository of a pull request. # In this case only pull action is allowed. # ====================================================================== - if self.is_shadow_repo and action != 'pull': - reason = 'Only pull action is allowed for shadow repositories.' - log.debug('User not allowed to proceed, %s', reason) + if self.is_shadow_repo and action != "pull": + reason = "Only pull action is allowed for shadow repositories." + log.debug("User not allowed to proceed, %s", reason) return HTTPNotAcceptable(reason)(environ, start_response) # Check if the shadow repo actually exists, in case someone refers # to it, and it has been deleted because of successful merge. if self.is_shadow_repo and not self.is_shadow_repo_dir: - log.debug( - 'Shadow repo detected, and shadow repo dir `%s` is missing', - self.is_shadow_repo_dir) + log.debug("Shadow repo detected, and shadow repo dir `%s` is missing", self.is_shadow_repo_dir) return HTTPNotFound()(environ, start_response) # ====================================================================== @@ -398,7 +388,7 @@ class SimpleVCS(object): # ====================================================================== detect_force_push = False check_branch_perms = False - if action in ['pull', 'push']: + if action in ["pull", "push"]: user_obj = anonymous_user = User.get_default_user() auth_user = user_obj.AuthUser() username = anonymous_user.username @@ -406,8 +396,12 @@ class SimpleVCS(object): plugin_cache_active, cache_ttl = self._get_default_cache_ttl() # ONLY check permissions if the user is activated anonymous_perm = self._check_permission( - action, anonymous_user, auth_user, self.acl_repo_name, ip_addr, - plugin_id='anonymous_access', + action, + anonymous_user, + auth_user, + self.acl_repo_name, + ip_addr, + plugin_id="anonymous_access", plugin_cache_active=plugin_cache_active, cache_ttl=cache_ttl, ) @@ -416,12 +410,13 @@ class SimpleVCS(object): if not anonymous_user.active or not anonymous_perm: if not anonymous_user.active: - log.debug('Anonymous access is disabled, running ' - 'authentication') + log.debug("Anonymous access is disabled, running " "authentication") if not anonymous_perm: - log.debug('Not enough credentials to access repo: `%s` ' - 'repository as anonymous user', self.acl_repo_name) + log.debug( + "Not enough credentials to access repo: `%s` " "repository as anonymous user", + self.acl_repo_name, + ) username = None # ============================================================== @@ -430,19 +425,18 @@ class SimpleVCS(object): # ============================================================== # try to auth based on environ, container auth methods - log.debug('Running PRE-AUTH for container|headers based authentication') + log.debug("Running PRE-AUTH for container|headers based authentication") # headers auth, by just reading special headers and bypass the auth with user/passwd pre_auth = authenticate( - '', '', environ, VCS_TYPE, registry=self.registry, - acl_repo_name=self.acl_repo_name) + "", "", environ, VCS_TYPE, registry=self.registry, acl_repo_name=self.acl_repo_name + ) - if pre_auth and pre_auth.get('username'): - username = pre_auth['username'] - log.debug('PRE-AUTH got `%s` as username', username) + if pre_auth and pre_auth.get("username"): + username = pre_auth["username"] + log.debug("PRE-AUTH got `%s` as username", username) if pre_auth: - log.debug('PRE-AUTH successful from %s', - pre_auth.get('auth_data', {}).get('_plugin')) + log.debug("PRE-AUTH successful from %s", pre_auth.get("auth_data", {}).get("_plugin")) # If not authenticated by the container, running basic auth # before inject the calling repo_name for special scope checks @@ -463,16 +457,16 @@ class SimpleVCS(object): return HTTPNotAcceptable(reason)(environ, start_response) if isinstance(auth_result, dict): - AUTH_TYPE.update(environ, 'basic') - REMOTE_USER.update(environ, auth_result['username']) - username = auth_result['username'] - plugin = auth_result.get('auth_data', {}).get('_plugin') - log.info( - 'MAIN-AUTH successful for user `%s` from %s plugin', - username, plugin) + AUTH_TYPE.update(environ, "basic") + REMOTE_USER.update(environ, auth_result["username"]) + username = auth_result["username"] + plugin = auth_result.get("auth_data", {}).get("_plugin") + log.info("MAIN-AUTH successful for user `%s` from %s plugin", username, plugin) - plugin_cache_active, cache_ttl = auth_result.get( - 'auth_data', {}).get('_ttl_cache') or (False, 0) + plugin_cache_active, cache_ttl = auth_result.get("auth_data", {}).get("_ttl_cache") or ( + False, + 0, + ) else: return auth_result.wsgi_application(environ, start_response) @@ -488,21 +482,24 @@ class SimpleVCS(object): # check user attributes for password change flag user_obj = user auth_user = user_obj.AuthUser() - if user_obj and user_obj.username != User.DEFAULT_USER and \ - user_obj.user_data.get('force_password_change'): - reason = 'password change required' - log.debug('User not allowed to authenticate, %s', reason) + if ( + user_obj + and user_obj.username != User.DEFAULT_USER + and user_obj.user_data.get("force_password_change") + ): + reason = "password change required" + log.debug("User not allowed to authenticate, %s", reason) return HTTPNotAcceptable(reason)(environ, start_response) # check permissions for this repository perm = self._check_permission( - action, user, auth_user, self.acl_repo_name, ip_addr, - plugin, plugin_cache_active, cache_ttl) + action, user, auth_user, self.acl_repo_name, ip_addr, plugin, plugin_cache_active, cache_ttl + ) if not perm: return HTTPForbidden()(environ, start_response) - environ['rc_auth_user_id'] = str(user_id) + environ["rc_auth_user_id"] = str(user_id) - if action == 'push': + if action == "push": perms = auth_user.get_branch_permissions(self.acl_repo_name) if perms: check_branch_perms = True @@ -510,41 +507,48 @@ class SimpleVCS(object): # extras are injected into UI object and later available # in hooks executed by RhodeCode - check_locking = _should_check_locking(environ.get('QUERY_STRING')) + check_locking = _should_check_locking(environ.get("QUERY_STRING")) extras = vcs_operation_context( - environ, repo_name=self.acl_repo_name, username=username, - action=action, scm=self.SCM, check_locking=check_locking, - is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms, - detect_force_push=detect_force_push + environ, + repo_name=self.acl_repo_name, + username=username, + action=action, + scm=self.SCM, + check_locking=check_locking, + is_shadow_repo=self.is_shadow_repo, + check_branch_perms=check_branch_perms, + detect_force_push=detect_force_push, ) # ====================================================================== # REQUEST HANDLING # ====================================================================== - repo_path = os.path.join( - safe_str(self.base_path), safe_str(self.vcs_repo_name)) - log.debug('Repository path is %s', repo_path) + repo_path = os.path.join(safe_str(self.base_path), safe_str(self.vcs_repo_name)) + log.debug("Repository path is %s", repo_path) fix_PATH() log.info( '%s action on %s repo "%s" by "%s" from %s %s', - action, self.SCM, safe_str(self.url_repo_name), - safe_str(username), ip_addr, user_agent) + action, + self.SCM, + safe_str(self.url_repo_name), + safe_str(username), + ip_addr, + user_agent, + ) - return self._generate_vcs_response( - environ, start_response, repo_path, extras, action) + return self._generate_vcs_response(environ, start_response, repo_path, extras, action) def _get_txn_id(self, environ): - - for k in ['RAW_URI', 'HTTP_DESTINATION']: + for k in ["RAW_URI", "HTTP_DESTINATION"]: url = environ.get(k) if not url: continue # regex to search for svn-txn-id - pattern = r'/!svn/txr/([^/]+)/' + pattern = r"/!svn/txr/([^/]+)/" # Search for the pattern in the URL match = re.search(pattern, url) @@ -555,8 +559,7 @@ class SimpleVCS(object): return txn_id @initialize_generator - def _generate_vcs_response( - self, environ, start_response, repo_path, extras, action): + def _generate_vcs_response(self, environ, start_response, repo_path, extras, action): """ Returns a generator for the response content. @@ -565,24 +568,20 @@ class SimpleVCS(object): also handles the locking exceptions which will be triggered when the first chunk is produced by the underlying WSGI application. """ - svn_txn_id = '' - if action == 'push': + svn_txn_id = "" + if action == "push": svn_txn_id = self._get_txn_id(environ) - callback_daemon, extras = self._prepare_callback_daemon( - extras, environ, action, txn_id=svn_txn_id) + callback_daemon, extras = self._prepare_callback_daemon(extras, environ, action, txn_id=svn_txn_id) if svn_txn_id: - - port = safe_int(extras['hooks_uri'].split(':')[-1]) txn_id_data = extras.copy() - txn_id_data.update({'port': port}) - txn_id_data.update({'req_method': environ['REQUEST_METHOD']}) + txn_id_data.update({"req_method": environ["REQUEST_METHOD"]}) full_repo_path = repo_path store_txn_id_data(full_repo_path, svn_txn_id, txn_id_data) - log.debug('HOOKS extras is %s', extras) + log.debug("HOOKS extras is %s", extras) http_scheme = self._get_http_scheme(environ) @@ -609,7 +608,7 @@ class SimpleVCS(object): try: # invalidate cache on push - if action == 'push': + if action == "push": self._invalidate_cache(self.url_repo_name) finally: meta.Session.remove() @@ -632,12 +631,12 @@ class SimpleVCS(object): """Return the WSGI app that will finally handle the request.""" raise NotImplementedError() - def _create_config(self, extras, repo_name, scheme='http'): + def _create_config(self, extras, repo_name, scheme="http"): """Create a safe config representation.""" raise NotImplementedError() def _should_use_callback_daemon(self, extras, environ, action): - if extras.get('is_shadow_repo'): + if extras.get("is_shadow_repo"): # we don't want to execute hooks, and callback daemon for shadow repos return False return True @@ -647,11 +646,9 @@ class SimpleVCS(object): if not self._should_use_callback_daemon(extras, environ, action): # disable callback daemon for actions that don't require it - protocol = 'local' + protocol = "local" - return prepare_callback_daemon( - extras, protocol=protocol, - host=vcs_settings.HOOKS_HOST, txn_id=txn_id) + return prepare_callback_daemon(extras, protocol=protocol, txn_id=txn_id) def _should_check_locking(query_string): @@ -659,4 +656,4 @@ def _should_check_locking(query_string): # server see all operation on commit; bookmarks, phases and # obsolescence marker in different transaction, we don't want to check # locking on those - return query_string not in ['cmd=listkeys'] + return query_string not in ["cmd=listkeys"] diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py index 11331c06..ea5ada46 100644 --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -21,6 +21,7 @@ Utilities library for RhodeCode """ import datetime +import importlib import decorator import logging @@ -42,8 +43,9 @@ from webhelpers2.text import collapse, strip_tags, convert_accented_entities, co from mako import exceptions +import rhodecode from rhodecode import ConfigGet -from rhodecode.lib.exceptions import HTTPBranchProtected, HTTPLockedRC +from rhodecode.lib.exceptions import HTTPBranchProtected, HTTPLockedRepo, ClientNotSupported from rhodecode.lib.hash_utils import sha256_safe, md5, sha1 from rhodecode.lib.type_utils import AttributeDict from rhodecode.lib.str_utils import safe_bytes, safe_str @@ -86,6 +88,7 @@ def adopt_for_celery(func): @wraps(func) def wrapper(extras): extras = AttributeDict(extras) + try: # HooksResponse implements to_json method which must be used there. return func(extras).to_json() @@ -100,7 +103,18 @@ def adopt_for_celery(func): 'exception_args': error_args, 'exception_traceback': '', } - except HTTPLockedRC as error: + except ClientNotSupported as error: + # Those special cases don't need error reporting. It's a case of + # locked repo or protected branch + error_args = error.args + return { + 'status': error.code, + 'output': error.explanation, + 'exception': type(error).__name__, + 'exception_args': error_args, + 'exception_traceback': '', + } + except HTTPLockedRepo as error: # Those special cases don't need error reporting. It's a case of # locked repo or protected branch error_args = error.args @@ -117,7 +131,7 @@ def adopt_for_celery(func): 'output': '', 'exception': type(e).__name__, 'exception_args': e.args, - 'exception_traceback': '', + 'exception_traceback': traceback.format_exc(), } return wrapper @@ -411,6 +425,10 @@ def prepare_config_data(clear_session=True, repo=None): ('web', 'push_ssl', 'false'), ] for setting in ui_settings: + # skip certain deprecated keys that might be still in DB + if f"{setting.section}_{setting.key}" in ['extensions_hgsubversion']: + continue + # Todo: remove this section once transition to *.ini files will be completed if setting.section in ('largefiles', 'vcs_git_lfs'): if setting.key != 'enabled': @@ -686,22 +704,41 @@ def repo2db_mapper(initial_repo_list, remove_obsolete=False, force_hooks_rebuild return added, removed +def deep_reload_package(package_name): + """ + Deeply reload a package by removing it and its submodules from sys.modules, + then re-importing it. + """ + # Remove the package and its submodules from sys.modules + to_reload = [name for name in sys.modules if name == package_name or name.startswith(package_name + ".")] + for module_name in to_reload: + del sys.modules[module_name] + log.debug(f"Removed module from cache: {module_name}") + + # Re-import the package + package = importlib.import_module(package_name) + log.debug(f"Re-imported package: {package_name}") + + return package def load_rcextensions(root_path): import rhodecode from rhodecode.config import conf path = os.path.join(root_path) - sys.path.append(path) + deep_reload = path in sys.path + sys.path.insert(0, path) try: - rcextensions = __import__('rcextensions') + rcextensions = __import__('rcextensions', fromlist=['']) except ImportError: if os.path.isdir(os.path.join(path, 'rcextensions')): log.warning('Unable to load rcextensions from %s', path) rcextensions = None if rcextensions: + if deep_reload: + rcextensions = deep_reload_package('rcextensions') log.info('Loaded rcextensions from %s...', rcextensions) rhodecode.EXTENSIONS = rcextensions @@ -741,6 +778,7 @@ def create_test_index(repo_location, config): except ImportError: raise ImportError('Failed to import rc_testdata, ' 'please make sure this package is installed from requirements_test.txt') + rc_testdata.extract_search_index( 'vcs_search_index', os.path.dirname(config['search.location'])) @@ -785,22 +823,15 @@ def create_test_repositories(test_path, config): Creates test repositories in the temporary directory. Repositories are extracted from archives within the rc_testdata package. """ - import rc_testdata + try: + import rc_testdata + except ImportError: + raise ImportError('Failed to import rc_testdata, ' + 'please make sure this package is installed from requirements_test.txt') + from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO - log.debug('making test vcs repositories') - - idx_path = config['search.location'] - data_path = config['cache_dir'] - - # clean index and data - if idx_path and os.path.exists(idx_path): - log.debug('remove %s', idx_path) - shutil.rmtree(idx_path) - - if data_path and os.path.exists(data_path): - log.debug('remove %s', data_path) - shutil.rmtree(data_path) + log.debug('making test vcs repositories at %s', test_path) rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO)) rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO)) diff --git a/rhodecode/lib/vcs/__init__.py b/rhodecode/lib/vcs/__init__.py index da7de79b..bce7c095 100644 --- a/rhodecode/lib/vcs/__init__.py +++ b/rhodecode/lib/vcs/__init__.py @@ -140,7 +140,7 @@ class CurlSession(object): try: curl.perform() except pycurl.error as exc: - log.error('Failed to call endpoint url: {} using pycurl'.format(url)) + log.error('Failed to call endpoint url: %s using pycurl', url) raise status_code = curl.getinfo(pycurl.HTTP_CODE) diff --git a/rhodecode/lib/vcs/backends/git/__init__.py b/rhodecode/lib/vcs/backends/git/__init__.py index a2632c07..e08cd427 100644 --- a/rhodecode/lib/vcs/backends/git/__init__.py +++ b/rhodecode/lib/vcs/backends/git/__init__.py @@ -45,10 +45,3 @@ def discover_git_version(raise_on_exc=False): if raise_on_exc: raise return '' - - -def lfs_store(base_location): - """ - Return a lfs store relative to base_location - """ - return os.path.join(base_location, '.cache', 'lfs_store') diff --git a/rhodecode/lib/vcs/backends/hg/__init__.py b/rhodecode/lib/vcs/backends/hg/__init__.py index 60ebd19b..cefbe54a 100644 --- a/rhodecode/lib/vcs/backends/hg/__init__.py +++ b/rhodecode/lib/vcs/backends/hg/__init__.py @@ -45,10 +45,3 @@ def discover_hg_version(raise_on_exc=False): if raise_on_exc: raise return '' - - -def largefiles_store(base_location): - """ - Return a largefile store relative to base_location - """ - return os.path.join(base_location, '.cache', 'largefiles') diff --git a/rhodecode/lib/vcs/client_http.py b/rhodecode/lib/vcs/client_http.py index b331b167..498a0eb6 100644 --- a/rhodecode/lib/vcs/client_http.py +++ b/rhodecode/lib/vcs/client_http.py @@ -216,7 +216,7 @@ class RemoteRepo(object): self._cache_region, self._cache_namespace = \ remote_maker.init_cache_region(cache_repo_id) - with_wire = with_wire or {} + with_wire = with_wire or {"cache": False} repo_state_uid = with_wire.get('repo_state_uid') or 'state' diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py index 075c7efd..8b44a156 100644 --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -373,6 +373,7 @@ class CommentsModel(BaseModel): Session().add(comment) Session().flush() + kwargs = { 'user': user, 'renderer_type': renderer, @@ -387,8 +388,7 @@ class CommentsModel(BaseModel): } if commit_obj: - recipients = ChangesetComment.get_users( - revision=commit_obj.raw_id) + recipients = ChangesetComment.get_users(revision=commit_obj.raw_id) # add commit author if it's in RhodeCode system cs_author = User.get_from_cs_author(commit_obj.author) if not cs_author: @@ -397,16 +397,13 @@ class CommentsModel(BaseModel): recipients += [cs_author] commit_comment_url = self.get_url(comment, request=request) - commit_comment_reply_url = self.get_url( - comment, request=request, - anchor=f'comment-{comment.comment_id}/?/ReplyToComment') + commit_comment_reply_url = self.get_url(comment, request=request, anchor=f'comment-{comment.comment_id}/?/ReplyToComment') target_repo_url = h.link_to( repo.repo_name, h.route_url('repo_summary', repo_name=repo.repo_name)) - commit_url = h.route_url('repo_commit', repo_name=repo.repo_name, - commit_id=commit_id) + commit_url = h.route_url('repo_commit', repo_name=repo.repo_name, commit_id=commit_id) # commit specifics kwargs.update({ @@ -489,7 +486,6 @@ class CommentsModel(BaseModel): if not is_draft: comment_data = comment.get_api_data() - self._log_audit_action( action, {'data': comment_data}, auth_user, comment) diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py index 0848a826..e99fad38 100644 --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -38,7 +38,7 @@ from rhodecode.translation import lazy_ugettext from rhodecode.lib import helpers as h, hooks_utils, diffs from rhodecode.lib import audit_logger from collections import OrderedDict -from rhodecode.lib.hook_daemon.base import prepare_callback_daemon +from rhodecode.lib.hook_daemon.utils import prepare_callback_daemon from rhodecode.lib.ext_json import sjson as json from rhodecode.lib.markup_renderer import ( DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) @@ -980,9 +980,7 @@ class PullRequestModel(BaseModel): target_ref = self._refresh_reference( pull_request.target_ref_parts, target_vcs) - callback_daemon, extras = prepare_callback_daemon( - extras, protocol=vcs_settings.HOOKS_PROTOCOL, - host=vcs_settings.HOOKS_HOST) + callback_daemon, extras = prepare_callback_daemon(extras, protocol=vcs_settings.HOOKS_PROTOCOL) with callback_daemon: # TODO: johbo: Implement a clean way to run a config_override diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py index 275cd868..23433839 100644 --- a/rhodecode/model/settings.py +++ b/rhodecode/model/settings.py @@ -862,27 +862,3 @@ class VcsSettingsModel(object): raise ValueError( f'The given data does not contain {data_key} key') return data_keys - - def create_largeobjects_dirs_if_needed(self, repo_store_path): - """ - This is subscribed to the `pyramid.events.ApplicationCreated` event. It - does a repository scan if enabled in the settings. - """ - - from rhodecode.lib.vcs.backends.hg import largefiles_store - from rhodecode.lib.vcs.backends.git import lfs_store - - paths = [ - largefiles_store(repo_store_path), - lfs_store(repo_store_path)] - - for path in paths: - if os.path.isdir(path): - continue - if os.path.isfile(path): - continue - # not a file nor dir, we try to create it - try: - os.makedirs(path) - except Exception: - log.warning('Failed to create largefiles dir:%s', path) diff --git a/rhodecode/tests/__init__.py b/rhodecode/tests/__init__.py index 3d8d082d..f2f2a9a2 100644 --- a/rhodecode/tests/__init__.py +++ b/rhodecode/tests/__init__.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -38,7 +37,7 @@ from rhodecode.lib.hash_utils import sha1_safe log = logging.getLogger(__name__) __all__ = [ - 'get_new_dir', 'TestController', + 'get_new_dir', 'TestController', 'console_printer', 'clear_cache_regions', 'assert_session_flash', 'login_user', 'no_newline_id_generator', 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO', @@ -244,3 +243,11 @@ def no_newline_id_generator(test_name): return test_name or 'test-with-empty-name' +def console_printer(*msg): + print_func = print + try: + from rich import print as print_func + except ImportError: + pass + + print_func(*msg) diff --git a/rhodecode/tests/auth_external_test.py b/rhodecode/tests/auth_external_test.py index 07010731..ab4ff946 100644 --- a/rhodecode/tests/auth_external_test.py +++ b/rhodecode/tests/auth_external_test.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -90,7 +89,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin): 'firstname': firstname, 'lastname': lastname, 'groups': [], - 'email': '%s@rhodecode.com' % username, + 'email': f'{username}@rhodecode.com', 'admin': admin, 'active': active, "active_from_extern": None, diff --git a/rhodecode/tests/config/test_routing_links.py b/rhodecode/tests/config/test_routing_links.py index a8b7612a..bb9ada63 100644 --- a/rhodecode/tests/config/test_routing_links.py +++ b/rhodecode/tests/config/test_routing_links.py @@ -20,14 +20,14 @@ import pytest import requests from rhodecode.config import routing_links - +from rhodecode.tests import console_printer def check_connection(): try: response = requests.get('https://rhodecode.com') return response.status_code == 200 except Exception as e: - print(e) + console_printer(e) return False diff --git a/rhodecode/tests/conftest.py b/rhodecode/tests/conftest.py index e6cdc829..da5ca2af 100644 --- a/rhodecode/tests/conftest.py +++ b/rhodecode/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -16,23 +16,10 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -""" -py.test config for test suite for making push/pull operations. - -.. important:: - - You must have git >= 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. -""" - -import pytest +import pytest # noqa import logging - -from rhodecode.authentication import AuthenticationPluginRegistry -from rhodecode.model.db import Permission, User -from rhodecode.model.meta import Session -from rhodecode.model.settings import SettingsModel -from rhodecode.model.user import UserModel +import collections +import rhodecode log = logging.getLogger(__name__) @@ -40,99 +27,3 @@ log = logging.getLogger(__name__) # Docker image running httpbin... HTTPBIN_DOMAIN = 'http://httpbin' HTTPBIN_POST = HTTPBIN_DOMAIN + '/post' - - -@pytest.fixture() -def enable_auth_plugins(request, baseapp, csrf_token): - """ - Return a factory object that when called, allows to control which - authentication plugins are enabled. - """ - - class AuthPluginManager(object): - - def cleanup(self): - self._enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode']) - - def enable(self, plugins_list, override=None): - return self._enable_plugins(plugins_list, override) - - def _enable_plugins(self, plugins_list, override=None): - override = override or {} - params = { - 'auth_plugins': ','.join(plugins_list), - } - - # helper translate some names to others, to fix settings code - name_map = { - 'token': 'authtoken' - } - log.debug('enable_auth_plugins: enabling following auth-plugins: %s', plugins_list) - - for module in plugins_list: - plugin_name = module.partition('#')[-1] - if plugin_name in name_map: - plugin_name = name_map[plugin_name] - enabled_plugin = f'auth_{plugin_name}_enabled' - cache_ttl = f'auth_{plugin_name}_cache_ttl' - - # default params that are needed for each plugin, - # `enabled` and `cache_ttl` - params.update({ - enabled_plugin: True, - cache_ttl: 0 - }) - if override.get: - params.update(override.get(module, {})) - - validated_params = params - - for k, v in validated_params.items(): - setting = SettingsModel().create_or_update_setting(k, v) - Session().add(setting) - Session().commit() - - AuthenticationPluginRegistry.invalidate_auth_plugins_cache(hard=True) - - enabled_plugins = SettingsModel().get_auth_plugins() - assert plugins_list == enabled_plugins - - enabler = AuthPluginManager() - request.addfinalizer(enabler.cleanup) - - return enabler - - -@pytest.fixture() -def test_user_factory(request, baseapp): - - def user_factory(username='test_user', password='qweqwe', first_name='John', last_name='Testing', **kwargs): - usr = UserModel().create_or_update( - username=username, - password=password, - email=f'{username}@rhodecode.org', - firstname=first_name, lastname=last_name) - Session().commit() - - for k, v in kwargs.items(): - setattr(usr, k, v) - Session().add(usr) - - new_usr = User.get_by_username(username) - new_usr_id = new_usr.user_id - assert new_usr == usr - - @request.addfinalizer - def cleanup(): - if User.get(new_usr_id) is None: - return - - perm = Permission.query().all() - for p in perm: - UserModel().revoke_perm(usr, p) - - UserModel().delete(new_usr_id) - Session().commit() - return usr - - return user_factory diff --git a/rhodecode/tests/conftest_common.py b/rhodecode/tests/conftest_common.py index a773c0fb..20d044a5 100644 --- a/rhodecode/tests/conftest_common.py +++ b/rhodecode/tests/conftest_common.py @@ -1,4 +1,4 @@ -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -98,16 +98,16 @@ def pytest_addoption(parser): 'pyramid_config', "Set up a Pyramid environment with the specified config file.") + parser.addini('rhodecode_config', 'rhodecode config ini for tests') + parser.addini('celery_config', 'celery config ini for tests') + parser.addini('vcsserver_config', 'vcsserver config ini for tests') + vcsgroup = parser.getgroup('vcs') + vcsgroup.addoption( '--without-vcsserver', dest='with_vcsserver', action='store_false', help="Do not start the VCSServer in a background process.") - vcsgroup.addoption( - '--with-vcsserver-http', dest='vcsserver_config_http', - help="Start the HTTP VCSServer with the specified config file.") - vcsgroup.addoption( - '--vcsserver-protocol', dest='vcsserver_protocol', - help="Start the VCSServer with HTTP protocol support.") + vcsgroup.addoption( '--vcsserver-config-override', action='store', type=_parse_json, default=None, dest='vcsserver_config_override', help=( @@ -122,12 +122,6 @@ def pytest_addoption(parser): "Allows to set the port of the vcsserver. Useful when testing " "against an already running server and random ports cause " "trouble.")) - parser.addini( - 'vcsserver_config_http', - "Start the HTTP VCSServer with the specified config file.") - parser.addini( - 'vcsserver_protocol', - "Start the VCSServer with HTTP protocol support.") @pytest.hookimpl(tryfirst=True, hookwrapper=True) diff --git a/rhodecode/tests/database/__init__.py b/rhodecode/tests/database/__init__.py new file mode 100644 index 00000000..c4d86aeb --- /dev/null +++ b/rhodecode/tests/database/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2010-2023 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/ diff --git a/rhodecode/tests/database/conftest.py b/rhodecode/tests/database/conftest.py index 0bd2c0b3..528b1dbb 100644 --- a/rhodecode/tests/database/conftest.py +++ b/rhodecode/tests/database/conftest.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -17,7 +16,7 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -from subprocess import Popen, PIPE +import subprocess import os import sys import tempfile @@ -26,87 +25,71 @@ import pytest from sqlalchemy.engine import url from rhodecode.lib.str_utils import safe_str, safe_bytes -from rhodecode.tests.fixture import TestINI +from rhodecode.tests.fixtures.rc_fixture import TestINI def _get_dbs_from_metafunc(metafunc): - dbs_mark = metafunc.definition.get_closest_marker('dbs') + dbs_mark = metafunc.definition.get_closest_marker("dbs") if dbs_mark: # Supported backends by this test function, created from pytest.mark.dbs backends = dbs_mark.args else: - backends = metafunc.config.getoption('--dbs') + backends = metafunc.config.getoption("--dbs") return backends def pytest_generate_tests(metafunc): # Support test generation based on --dbs parameter - if 'db_backend' in metafunc.fixturenames: - requested_backends = set(metafunc.config.getoption('--dbs')) + if "db_backend" in metafunc.fixturenames: + requested_backends = set(metafunc.config.getoption("--dbs")) backends = _get_dbs_from_metafunc(metafunc) backends = requested_backends.intersection(backends) # TODO: johbo: Disabling a backend did not work out with # parametrization, find better way to achieve this. if not backends: metafunc.function._skip = True - metafunc.parametrize('db_backend_name', backends) + metafunc.parametrize("db_backend_name", backends) def pytest_collection_modifyitems(session, config, items): - remaining = [ - i for i in items if not getattr(i.obj, '_skip', False)] + remaining = [i for i in items if not getattr(i.obj, "_skip", False)] items[:] = remaining @pytest.fixture() -def db_backend( - request, db_backend_name, ini_config, tmpdir_factory): +def db_backend(request, db_backend_name, ini_config, tmpdir_factory): basetemp = tmpdir_factory.getbasetemp().strpath klass = _get_backend(db_backend_name) - option_name = '--{}-connection-string'.format(db_backend_name) + option_name = "--{}-connection-string".format(db_backend_name) connection_string = request.config.getoption(option_name) or None - return klass( - config_file=ini_config, basetemp=basetemp, - connection_string=connection_string) + return klass(config_file=ini_config, basetemp=basetemp, connection_string=connection_string) def _get_backend(backend_type): - return { - 'sqlite': SQLiteDBBackend, - 'postgres': PostgresDBBackend, - 'mysql': MySQLDBBackend, - '': EmptyDBBackend - }[backend_type] + return {"sqlite": SQLiteDBBackend, "postgres": PostgresDBBackend, "mysql": MySQLDBBackend, "": EmptyDBBackend}[ + backend_type + ] class DBBackend(object): _store = os.path.dirname(os.path.abspath(__file__)) _type = None - _base_ini_config = [{'app:main': {'vcs.start_server': 'false', - 'startup.import_repos': 'false'}}] - _db_url = [{'app:main': {'sqlalchemy.db1.url': ''}}] - _base_db_name = 'rhodecode_test_db_backend' - std_env = {'RC_TEST': '0'} - - def __init__( - self, config_file, db_name=None, basetemp=None, - connection_string=None): - - from rhodecode.lib.vcs.backends.hg import largefiles_store - from rhodecode.lib.vcs.backends.git import lfs_store + _base_ini_config = [{"app:main": {"vcs.start_server": "false", "startup.import_repos": "false"}}] + _db_url = [{"app:main": {"sqlalchemy.db1.url": ""}}] + _base_db_name = "rhodecode_test_db_backend" + std_env = {"RC_TEST": "0"} + def __init__(self, config_file, db_name=None, basetemp=None, connection_string=None): self.fixture_store = os.path.join(self._store, self._type) self.db_name = db_name or self._base_db_name self._base_ini_file = config_file - self.stderr = '' - self.stdout = '' + self.stderr = "" + self.stdout = "" self._basetemp = basetemp or tempfile.gettempdir() - self._repos_location = os.path.join(self._basetemp, 'rc_test_repos') - self._repos_hg_largefiles_store = largefiles_store(self._basetemp) - self._repos_git_lfs_store = lfs_store(self._basetemp) + self._repos_location = os.path.join(self._basetemp, "rc_test_repos") self.connection_string = connection_string @property @@ -118,8 +101,7 @@ class DBBackend(object): if not new_connection_string: new_connection_string = self.get_default_connection_string() else: - new_connection_string = new_connection_string.format( - db_name=self.db_name) + new_connection_string = new_connection_string.format(db_name=self.db_name) url_parts = url.make_url(new_connection_string) self._connection_string = new_connection_string self.user = url_parts.username @@ -127,73 +109,67 @@ class DBBackend(object): self.host = url_parts.host def get_default_connection_string(self): - raise NotImplementedError('default connection_string is required.') + raise NotImplementedError("default connection_string is required.") def execute(self, cmd, env=None, *args): """ Runs command on the system with given ``args``. """ - command = cmd + ' ' + ' '.join(args) - sys.stdout.write(f'CMD: {command}') + command = cmd + " " + " ".join(args) + sys.stdout.write(f"CMD: {command}") # Tell Python to use UTF-8 encoding out stdout _env = os.environ.copy() - _env['PYTHONIOENCODING'] = 'UTF-8' + _env["PYTHONIOENCODING"] = "UTF-8" _env.update(self.std_env) if env: _env.update(env) - self.p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, env=_env) + self.p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=_env) self.stdout, self.stderr = self.p.communicate() stdout_str = safe_str(self.stdout) - sys.stdout.write(f'COMMAND:{command}\n') + sys.stdout.write(f"COMMAND:{command}\n") sys.stdout.write(stdout_str) return self.stdout, self.stderr def assert_returncode_success(self): from rich import print as pprint + if not self.p.returncode == 0: pprint(safe_str(self.stderr)) - raise AssertionError(f'non 0 retcode:{self.p.returncode}') + raise AssertionError(f"non 0 retcode:{self.p.returncode}") def assert_correct_output(self, stdout, version): - assert b'UPGRADE FOR STEP %b COMPLETED' % safe_bytes(version) in stdout + assert b"UPGRADE FOR STEP %b COMPLETED" % safe_bytes(version) in stdout def setup_rhodecode_db(self, ini_params=None, env=None): if not ini_params: ini_params = self._base_ini_config ini_params.extend(self._db_url) - with TestINI(self._base_ini_file, ini_params, - self._type, destroy=True) as _ini_file: - + with TestINI(self._base_ini_file, ini_params, self._type, destroy=True) as _ini_file: if not os.path.isdir(self._repos_location): os.makedirs(self._repos_location) - if not os.path.isdir(self._repos_hg_largefiles_store): - os.makedirs(self._repos_hg_largefiles_store) - if not os.path.isdir(self._repos_git_lfs_store): - os.makedirs(self._repos_git_lfs_store) return self.execute( "rc-setup-app {0} --user=marcink " "--email=marcin@rhodeocode.com --password={1} " - "--repos={2} --force-yes".format( - _ini_file, 'qweqwe', self._repos_location), env=env) + "--repos={2} --force-yes".format(_ini_file, "qweqwe", self._repos_location), + env=env, + ) def upgrade_database(self, ini_params=None): if not ini_params: ini_params = self._base_ini_config ini_params.extend(self._db_url) - test_ini = TestINI( - self._base_ini_file, ini_params, self._type, destroy=True) + test_ini = TestINI(self._base_ini_file, ini_params, self._type, destroy=True) with test_ini as ini_file: if not os.path.isdir(self._repos_location): os.makedirs(self._repos_location) - return self.execute( - "rc-upgrade-db {0} --force-yes".format(ini_file)) + return self.execute("rc-upgrade-db {0} --force-yes".format(ini_file)) def setup_db(self): raise NotImplementedError @@ -206,7 +182,7 @@ class DBBackend(object): class EmptyDBBackend(DBBackend): - _type = '' + _type = "" def setup_db(self): pass @@ -222,21 +198,20 @@ class EmptyDBBackend(DBBackend): class SQLiteDBBackend(DBBackend): - _type = 'sqlite' + _type = "sqlite" def get_default_connection_string(self): - return 'sqlite:///{}/{}.sqlite'.format(self._basetemp, self.db_name) + return "sqlite:///{}/{}.sqlite".format(self._basetemp, self.db_name) def setup_db(self): # dump schema for tests # cp -v $TEST_DB_NAME - self._db_url = [{'app:main': { - 'sqlalchemy.db1.url': self.connection_string}}] + self._db_url = [{"app:main": {"sqlalchemy.db1.url": self.connection_string}}] def import_dump(self, dumpname): dump = os.path.join(self.fixture_store, dumpname) - target = os.path.join(self._basetemp, '{0.db_name}.sqlite'.format(self)) - return self.execute(f'cp -v {dump} {target}') + target = os.path.join(self._basetemp, "{0.db_name}.sqlite".format(self)) + return self.execute(f"cp -v {dump} {target}") def teardown_db(self): target_db = os.path.join(self._basetemp, self.db_name) @@ -244,39 +219,39 @@ class SQLiteDBBackend(DBBackend): class MySQLDBBackend(DBBackend): - _type = 'mysql' + _type = "mysql" def get_default_connection_string(self): - return 'mysql://root:qweqwe@127.0.0.1/{}'.format(self.db_name) + return "mysql://root:qweqwe@127.0.0.1/{}".format(self.db_name) def setup_db(self): # dump schema for tests # mysqldump -uroot -pqweqwe $TEST_DB_NAME - self._db_url = [{'app:main': { - 'sqlalchemy.db1.url': self.connection_string}}] - return self.execute("mysql -v -u{} -p{} -e 'create database '{}';'".format( - self.user, self.password, self.db_name)) + self._db_url = [{"app:main": {"sqlalchemy.db1.url": self.connection_string}}] + return self.execute( + "mysql -v -u{} -p{} -e 'create database '{}';'".format(self.user, self.password, self.db_name) + ) def import_dump(self, dumpname): dump = os.path.join(self.fixture_store, dumpname) - return self.execute("mysql -u{} -p{} {} < {}".format( - self.user, self.password, self.db_name, dump)) + return self.execute("mysql -u{} -p{} {} < {}".format(self.user, self.password, self.db_name, dump)) def teardown_db(self): - return self.execute("mysql -v -u{} -p{} -e 'drop database '{}';'".format( - self.user, self.password, self.db_name)) + return self.execute( + "mysql -v -u{} -p{} -e 'drop database '{}';'".format(self.user, self.password, self.db_name) + ) class PostgresDBBackend(DBBackend): - _type = 'postgres' + _type = "postgres" def get_default_connection_string(self): - return 'postgresql://postgres:qweqwe@localhost/{}'.format(self.db_name) + return "postgresql://postgres:qweqwe@localhost/{}".format(self.db_name) def setup_db(self): # dump schema for tests # pg_dump -U postgres -h localhost $TEST_DB_NAME - self._db_url = [{'app:main': {'sqlalchemy.db1.url': self.connection_string}}] + self._db_url = [{"app:main": {"sqlalchemy.db1.url": self.connection_string}}] cmd = f"PGPASSWORD={self.password} psql -U {self.user} -h localhost -c 'create database '{self.db_name}';'" return self.execute(cmd) diff --git a/rhodecode/tests/database/test_creation.py b/rhodecode/tests/database/test_creation.py index 23e13086..25ef7a98 100755 --- a/rhodecode/tests/database/test_creation.py +++ b/rhodecode/tests/database/test_creation.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify diff --git a/rhodecode/tests/database/test_migration.py b/rhodecode/tests/database/test_migration.py index f0d90748..9a199bdf 100644 --- a/rhodecode/tests/database/test_migration.py +++ b/rhodecode/tests/database/test_migration.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -21,33 +20,42 @@ import pytest @pytest.mark.dbs("postgres") -@pytest.mark.parametrize("dumpname", [ - '1.4.4.sql', - '1.5.0.sql', - '1.6.0.sql', - '1.6.0_no_repo_name_index.sql', -]) +@pytest.mark.parametrize( + "dumpname", + [ + "1.4.4.sql", + "1.5.0.sql", + "1.6.0.sql", + "1.6.0_no_repo_name_index.sql", + ], +) def test_migrate_postgres_db(db_backend, dumpname): _run_migration_test(db_backend, dumpname) @pytest.mark.dbs("sqlite") -@pytest.mark.parametrize("dumpname", [ - 'rhodecode.1.4.4.sqlite', - 'rhodecode.1.4.4_with_groups.sqlite', - 'rhodecode.1.4.4_with_ldap_active.sqlite', -]) +@pytest.mark.parametrize( + "dumpname", + [ + "rhodecode.1.4.4.sqlite", + "rhodecode.1.4.4_with_groups.sqlite", + "rhodecode.1.4.4_with_ldap_active.sqlite", + ], +) def test_migrate_sqlite_db(db_backend, dumpname): _run_migration_test(db_backend, dumpname) @pytest.mark.dbs("mysql") -@pytest.mark.parametrize("dumpname", [ - '1.4.4.sql', - '1.5.0.sql', - '1.6.0.sql', - '1.6.0_no_repo_name_index.sql', -]) +@pytest.mark.parametrize( + "dumpname", + [ + "1.4.4.sql", + "1.5.0.sql", + "1.6.0.sql", + "1.6.0_no_repo_name_index.sql", + ], +) def test_migrate_mysql_db(db_backend, dumpname): _run_migration_test(db_backend, dumpname) @@ -60,5 +68,5 @@ def _run_migration_test(db_backend, dumpname): db_backend.import_dump(dumpname) stdout, stderr = db_backend.upgrade_database() - db_backend.assert_correct_output(stdout+stderr, version='16') + db_backend.assert_correct_output(stdout + stderr, version="16") db_backend.assert_returncode_success() diff --git a/rhodecode/tests/fixture_mods/fixture_pyramid.py b/rhodecode/tests/fixture_mods/fixture_pyramid.py deleted file mode 100644 index e90d7d5b..00000000 --- a/rhodecode/tests/fixture_mods/fixture_pyramid.py +++ /dev/null @@ -1,226 +0,0 @@ - -# Copyright (C) 2010-2023 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 pytest - -from rhodecode.lib.config_utils import get_app_config -from rhodecode.tests.fixture import TestINI -from rhodecode.tests import TESTS_TMP_PATH -from rhodecode.tests.server_utils import RcVCSServer - - -@pytest.fixture(scope='session') -def vcsserver(request, vcsserver_port, vcsserver_factory): - """ - Session scope VCSServer. - - Tests which need the VCSServer have to rely on this fixture in order - to ensure it will be running. - - For specific needs, the fixture vcsserver_factory can be used. It allows to - adjust the configuration file for the test run. - - Command line args: - - --without-vcsserver: Allows to switch this fixture off. You have to - manually start the server. - - --vcsserver-port: Will expect the VCSServer to listen on this port. - """ - - if not request.config.getoption('with_vcsserver'): - return None - - return vcsserver_factory( - request, vcsserver_port=vcsserver_port) - - -@pytest.fixture(scope='session') -def vcsserver_factory(tmpdir_factory): - """ - Use this if you need a running vcsserver with a special configuration. - """ - - def factory(request, overrides=(), vcsserver_port=None, - log_file=None, workers='3'): - - if vcsserver_port is None: - vcsserver_port = get_available_port() - - overrides = list(overrides) - overrides.append({'server:main': {'port': vcsserver_port}}) - - option_name = 'vcsserver_config_http' - override_option_name = 'vcsserver_config_override' - config_file = get_config( - request.config, option_name=option_name, - override_option_name=override_option_name, overrides=overrides, - basetemp=tmpdir_factory.getbasetemp().strpath, - prefix='test_vcs_') - - server = RcVCSServer(config_file, log_file, workers) - server.start() - - @request.addfinalizer - def cleanup(): - server.shutdown() - - server.wait_until_ready() - return server - - return factory - - -def _use_log_level(config): - level = config.getoption('test_loglevel') or 'critical' - return level.upper() - - -@pytest.fixture(scope='session') -def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port): - option_name = 'pyramid_config' - log_level = _use_log_level(request.config) - - overrides = [ - {'server:main': {'port': rcserver_port}}, - {'app:main': { - 'cache_dir': '%(here)s/rc-tests/rc_data', - 'vcs.server': f'localhost:{vcsserver_port}', - # johbo: We will always start the VCSServer on our own based on the - # fixtures of the test cases. For the test run it must always be - # off in the INI file. - 'vcs.start_server': 'false', - - 'vcs.server.protocol': 'http', - 'vcs.scm_app_implementation': 'http', - 'vcs.svn.proxy.enabled': 'true', - 'vcs.hooks.protocol.v2': 'celery', - 'vcs.hooks.host': '*', - 'repo_store.path': TESTS_TMP_PATH, - 'app.service_api.token': 'service_secret_token', - }}, - - {'handler_console': { - 'class': 'StreamHandler', - 'args': '(sys.stderr,)', - 'level': log_level, - }}, - - ] - - filename = get_config( - request.config, option_name=option_name, - override_option_name='{}_override'.format(option_name), - overrides=overrides, - basetemp=tmpdir_factory.getbasetemp().strpath, - prefix='test_rce_') - return filename - - -@pytest.fixture(scope='session') -def ini_settings(ini_config): - ini_path = ini_config - return get_app_config(ini_path) - - -def get_available_port(min_port=40000, max_port=55555): - from rhodecode.lib.utils2 import get_available_port as _get_port - return _get_port(min_port, max_port) - - -@pytest.fixture(scope='session') -def rcserver_port(request): - port = get_available_port() - print(f'Using rhodecode port {port}') - return port - - -@pytest.fixture(scope='session') -def vcsserver_port(request): - port = request.config.getoption('--vcsserver-port') - if port is None: - port = get_available_port() - print(f'Using vcsserver port {port}') - return port - - -@pytest.fixture(scope='session') -def available_port_factory() -> get_available_port: - """ - Returns a callable which returns free port numbers. - """ - return get_available_port - - -@pytest.fixture() -def available_port(available_port_factory): - """ - Gives you one free port for the current test. - - Uses "available_port_factory" to retrieve the port. - """ - return available_port_factory() - - -@pytest.fixture(scope='session') -def testini_factory(tmpdir_factory, ini_config): - """ - Factory to create an INI file based on TestINI. - - It will make sure to place the INI file in the correct directory. - """ - basetemp = tmpdir_factory.getbasetemp().strpath - return TestIniFactory(basetemp, ini_config) - - -class TestIniFactory(object): - - def __init__(self, basetemp, template_ini): - self._basetemp = basetemp - self._template_ini = template_ini - - def __call__(self, ini_params, new_file_prefix='test'): - ini_file = TestINI( - self._template_ini, ini_params=ini_params, - new_file_prefix=new_file_prefix, dir=self._basetemp) - result = ini_file.create() - return result - - -def get_config( - config, option_name, override_option_name, overrides=None, - basetemp=None, prefix='test'): - """ - Find a configuration file and apply overrides for the given `prefix`. - """ - config_file = ( - config.getoption(option_name) or config.getini(option_name)) - if not config_file: - pytest.exit( - "Configuration error, could not extract {}.".format(option_name)) - - overrides = overrides or [] - config_override = config.getoption(override_option_name) - if config_override: - overrides.append(config_override) - temp_ini_file = TestINI( - config_file, ini_params=overrides, new_file_prefix=prefix, - dir=basetemp) - - return temp_ini_file.create() diff --git a/rhodecode/tests/fixture_mods/__init__.py b/rhodecode/tests/fixtures/__init__.py similarity index 100% rename from rhodecode/tests/fixture_mods/__init__.py rename to rhodecode/tests/fixtures/__init__.py diff --git a/rhodecode/tests/fixtures/diff_with_diff_data.diff b/rhodecode/tests/fixtures/diff_fixtures/diff_with_diff_data.diff similarity index 100% rename from rhodecode/tests/fixtures/diff_with_diff_data.diff rename to rhodecode/tests/fixtures/diff_fixtures/diff_with_diff_data.diff diff --git a/rhodecode/tests/fixtures/git_diff_binary_and_normal.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_and_normal.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_binary_and_normal.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_and_normal.diff diff --git a/rhodecode/tests/fixtures/git_diff_binary_special_files.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_binary_special_files.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files.diff diff --git a/rhodecode/tests/fixtures/git_diff_binary_special_files_2.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files_2.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_binary_special_files_2.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files_2.diff diff --git a/rhodecode/tests/fixtures/git_diff_chmod.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_chmod.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_chmod.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_chmod.diff diff --git a/rhodecode/tests/fixtures/git_diff_js_chars.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_js_chars.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_js_chars.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_js_chars.diff diff --git a/rhodecode/tests/fixtures/git_diff_mod_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_mod_single_binary_file.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_mod_single_binary_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_mod_single_binary_file.diff diff --git a/rhodecode/tests/fixtures/git_diff_rename_file.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_rename_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file.diff diff --git a/rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file_with_spaces.diff similarity index 100% rename from rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file_with_spaces.diff diff --git a/rhodecode/tests/fixtures/git_node_history_response.json b/rhodecode/tests/fixtures/diff_fixtures/git_node_history_response.json similarity index 100% rename from rhodecode/tests/fixtures/git_node_history_response.json rename to rhodecode/tests/fixtures/diff_fixtures/git_node_history_response.json diff --git a/rhodecode/tests/fixtures/hg_diff_add_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_add_single_binary_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_add_single_binary_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_add_single_binary_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_binary_and_normal.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_binary_and_normal.diff diff --git a/rhodecode/tests/fixtures/hg_diff_chmod.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_chmod.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod.diff diff --git a/rhodecode/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod_and_mod_single_binary_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod_and_mod_single_binary_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_copy_and_chmod_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_chmod_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_copy_and_chmod_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_chmod_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_copy_and_modify_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_modify_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_copy_and_modify_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_modify_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_copy_chmod_and_edit_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_chmod_and_edit_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_copy_chmod_and_edit_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_chmod_and_edit_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_copy_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_copy_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file_with_spaces.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file_with_spaces.diff diff --git a/rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_del_single_binary_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_del_single_binary_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_double_file_change_double_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_double_newline.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_double_file_change_double_newline.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_double_newline.diff diff --git a/rhodecode/tests/fixtures/hg_diff_double_file_change_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_newline.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_double_file_change_newline.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_newline.diff diff --git a/rhodecode/tests/fixtures/hg_diff_four_file_change_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_four_file_change_newline.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_four_file_change_newline.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_four_file_change_newline.diff diff --git a/rhodecode/tests/fixtures/hg_diff_mixed_filename_encodings.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mixed_filename_encodings.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_mixed_filename_encodings.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mixed_filename_encodings.diff diff --git a/rhodecode/tests/fixtures/hg_diff_mod_file_and_rename.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_file_and_rename.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_mod_file_and_rename.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_file_and_rename.diff diff --git a/rhodecode/tests/fixtures/hg_diff_mod_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_binary_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_mod_single_binary_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_binary_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff diff --git a/rhodecode/tests/fixtures/hg_diff_no_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_no_newline.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_no_newline.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_no_newline.diff diff --git a/rhodecode/tests/fixtures/hg_diff_rename_and_chmod_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_and_chmod_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_rename_and_chmod_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_and_chmod_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_rename_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_rename_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file.diff diff --git a/rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file_with_spaces.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file_with_spaces.diff diff --git a/rhodecode/tests/fixtures/hg_diff_single_file_change_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_single_file_change_newline.diff similarity index 100% rename from rhodecode/tests/fixtures/hg_diff_single_file_change_newline.diff rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_single_file_change_newline.diff diff --git a/rhodecode/tests/fixtures/hg_node_history_response.json b/rhodecode/tests/fixtures/diff_fixtures/hg_node_history_response.json similarity index 100% rename from rhodecode/tests/fixtures/hg_node_history_response.json rename to rhodecode/tests/fixtures/diff_fixtures/hg_node_history_response.json diff --git a/rhodecode/tests/fixtures/journal_dump.csv b/rhodecode/tests/fixtures/diff_fixtures/journal_dump.csv similarity index 100% rename from rhodecode/tests/fixtures/journal_dump.csv rename to rhodecode/tests/fixtures/diff_fixtures/journal_dump.csv diff --git a/rhodecode/tests/fixtures/large_diff.diff b/rhodecode/tests/fixtures/diff_fixtures/large_diff.diff similarity index 100% rename from rhodecode/tests/fixtures/large_diff.diff rename to rhodecode/tests/fixtures/diff_fixtures/large_diff.diff diff --git a/rhodecode/tests/fixtures/svn_diff_binary_add_file.diff b/rhodecode/tests/fixtures/diff_fixtures/svn_diff_binary_add_file.diff similarity index 100% rename from rhodecode/tests/fixtures/svn_diff_binary_add_file.diff rename to rhodecode/tests/fixtures/diff_fixtures/svn_diff_binary_add_file.diff diff --git a/rhodecode/tests/fixtures/svn_diff_multiple_changes.diff b/rhodecode/tests/fixtures/diff_fixtures/svn_diff_multiple_changes.diff similarity index 100% rename from rhodecode/tests/fixtures/svn_diff_multiple_changes.diff rename to rhodecode/tests/fixtures/diff_fixtures/svn_diff_multiple_changes.diff diff --git a/rhodecode/tests/fixtures/svn_node_history_branches.json b/rhodecode/tests/fixtures/diff_fixtures/svn_node_history_branches.json similarity index 100% rename from rhodecode/tests/fixtures/svn_node_history_branches.json rename to rhodecode/tests/fixtures/diff_fixtures/svn_node_history_branches.json diff --git a/rhodecode/tests/fixtures/svn_node_history_response.json b/rhodecode/tests/fixtures/diff_fixtures/svn_node_history_response.json similarity index 100% rename from rhodecode/tests/fixtures/svn_node_history_response.json rename to rhodecode/tests/fixtures/diff_fixtures/svn_node_history_response.json diff --git a/rhodecode/tests/fixtures/fixture_pyramid.py b/rhodecode/tests/fixtures/fixture_pyramid.py new file mode 100644 index 00000000..26935325 --- /dev/null +++ b/rhodecode/tests/fixtures/fixture_pyramid.py @@ -0,0 +1,308 @@ +# 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 pytest + +from rhodecode.lib.config_utils import get_app_config +from rhodecode.tests.fixtures.rc_fixture import TestINI +from rhodecode.tests import TESTS_TMP_PATH +from rhodecode.tests.server_utils import RcVCSServer +from rhodecode.tests.server_utils import RcWebServer +from rhodecode.tests.server_utils import CeleryServer + + +@pytest.fixture(scope="session") +def vcsserver_factory(): + """ + Use this if you need a running vcsserver with a special configuration. + """ + + def factory(request, store_dir, overrides=(), config_file=None, port=None, log_file=None, workers="3", env=None, info_prefix=""): + env = env or {"RC_NO_TEST_ENV": "1"} + vcsserver_port = port + if port is None: + vcsserver_port = get_available_port() + + overrides = list(overrides) + overrides.append({"server:main": {"port": vcsserver_port}}) + + if getattr(request, 'param', None): + config_overrides = [request.param] + overrides.extend(config_overrides) + + option_name = "vcsserver_config" + override_option_name = None + if not config_file: + config_file = get_config( + request.config, + option_name=option_name, + override_option_name=override_option_name, + overrides=overrides, + basetemp=store_dir, + prefix=f"{info_prefix}test_vcsserver_ini_", + ) + server = RcVCSServer(config_file, log_file, workers, env=env, info_prefix=info_prefix) + server.start() + + @request.addfinalizer + def cleanup(): + server.shutdown() + + server.wait_until_ready() + return server + + return factory + + +@pytest.fixture(scope="session") +def rhodecode_factory(): + def factory(request, store_dir, overrides=(), config_file=None, port=None, log_file=None, workers="3", env=None, info_prefix=""): + env = env or {"RC_NO_TEST_ENV": "1"} + rhodecode_port = port + if port is None: + rhodecode_port = get_available_port() + + overrides = list(overrides) + overrides.append({"server:main": {"port": rhodecode_port}}) + overrides.append({"app:main": {"use_celery": "true"}}) + overrides.append({"app:main": {"celery.task_always_eager": "false"}}) + + if getattr(request, 'param', None): + config_overrides = [request.param] + overrides.extend(config_overrides) + + + option_name = "rhodecode_config" + override_option_name = None + if not config_file: + config_file = get_config( + request.config, + option_name=option_name, + override_option_name=override_option_name, + overrides=overrides, + basetemp=store_dir, + prefix=f"{info_prefix}test_rhodecode_ini", + ) + + server = RcWebServer(config_file, log_file, workers, env, info_prefix=info_prefix) + server.start() + + @request.addfinalizer + def cleanup(): + server.shutdown() + + server.wait_until_ready() + return server + + return factory + + +@pytest.fixture(scope="session") +def celery_factory(): + def factory(request, store_dir, overrides=(), config_file=None, port=None, log_file=None, workers="3", env=None, info_prefix=""): + env = env or {"RC_NO_TEST_ENV": "1"} + rhodecode_port = port + + overrides = list(overrides) + overrides.append({"app:main": {"use_celery": "true"}}) + overrides.append({"app:main": {"celery.task_always_eager": "false"}}) + config_overrides = None + + if getattr(request, 'param', None): + config_overrides = [request.param] + overrides.extend(config_overrides) + + option_name = "celery_config" + override_option_name = None + + if not config_file: + config_file = get_config( + request.config, + option_name=option_name, + override_option_name=override_option_name, + overrides=overrides, + basetemp=store_dir, + prefix=f"{info_prefix}test_celery_ini_", + ) + + server = CeleryServer(config_file, log_file, workers, env, info_prefix=info_prefix) + server.start() + + @request.addfinalizer + def cleanup(): + server.shutdown() + + server.wait_until_ready() + return server + + return factory + + +def _use_log_level(config): + level = config.getoption("test_loglevel") or "critical" + return level.upper() + + +def _ini_config_factory(request, base_dir, rcserver_port, vcsserver_port): + option_name = "pyramid_config" + log_level = _use_log_level(request.config) + + overrides = [ + {"server:main": {"port": rcserver_port}}, + { + "app:main": { + #'cache_dir': '%(here)s/rc-tests/rc_data', + "vcs.server": f"localhost:{vcsserver_port}", + # johbo: We will always start the VCSServer on our own based on the + # fixtures of the test cases. For the test run it must always be + # off in the INI file. + "vcs.start_server": "false", + "vcs.server.protocol": "http", + "vcs.scm_app_implementation": "http", + "vcs.svn.proxy.enabled": "true", + "vcs.hooks.protocol.v2": "celery", + "vcs.hooks.host": "*", + "repo_store.path": TESTS_TMP_PATH, + "app.service_api.token": "service_secret_token", + } + }, + { + "handler_console": { + "class": "StreamHandler", + "args": "(sys.stderr,)", + "level": log_level, + } + }, + ] + + filename = get_config( + request.config, + option_name=option_name, + override_option_name=f"{option_name}_override", + overrides=overrides, + basetemp=base_dir, + prefix="test_rce_", + ) + return filename + + +@pytest.fixture(scope="session") +def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port): + base_dir = tmpdir_factory.getbasetemp().strpath + return _ini_config_factory(request, base_dir, rcserver_port, vcsserver_port) + + +@pytest.fixture(scope="session") +def ini_config_factory(request, tmpdir_factory, rcserver_port, vcsserver_port): + def _factory(ini_config_basedir, overrides=()): + return _ini_config_factory(request, ini_config_basedir, rcserver_port, vcsserver_port) + + return _factory + + +@pytest.fixture(scope="session") +def ini_settings(ini_config): + ini_path = ini_config + return get_app_config(ini_path) + + +def get_available_port(min_port=40000, max_port=55555): + from rhodecode.lib.utils2 import get_available_port as _get_port + + return _get_port(min_port, max_port) + + +@pytest.fixture(scope="session") +def rcserver_port(request): + port = get_available_port() + return port + + +@pytest.fixture(scope="session") +def vcsserver_port(request): + port = request.config.getoption("--vcsserver-port") + if port is None: + port = get_available_port() + return port + + +@pytest.fixture(scope="session") +def available_port_factory() -> get_available_port: + """ + Returns a callable which returns free port numbers. + """ + return get_available_port + + +@pytest.fixture() +def available_port(available_port_factory): + """ + Gives you one free port for the current test. + + Uses "available_port_factory" to retrieve the port. + """ + return available_port_factory() + + +@pytest.fixture(scope="session") +def testini_factory(tmpdir_factory, ini_config): + """ + Factory to create an INI file based on TestINI. + + It will make sure to place the INI file in the correct directory. + """ + basetemp = tmpdir_factory.getbasetemp().strpath + return TestIniFactory(basetemp, ini_config) + + +class TestIniFactory(object): + def __init__(self, ini_store_dir, template_ini): + self._ini_store_dir = ini_store_dir + self._template_ini = template_ini + + def __call__(self, ini_params, new_file_prefix="test"): + ini_file = TestINI( + self._template_ini, ini_params=ini_params, new_file_prefix=new_file_prefix, dir=self._ini_store_dir + ) + result = ini_file.create() + return result + + +def get_config(config, option_name, override_option_name, overrides=None, basetemp=None, prefix="test"): + """ + Find a configuration file and apply overrides for the given `prefix`. + """ + try: + config_file = config.getoption(option_name) + except ValueError: + config_file = None + + if not config_file: + config_file = config.getini(option_name) + + if not config_file: + pytest.exit(f"Configuration error, could not extract {option_name}.") + + overrides = overrides or [] + if override_option_name: + config_override = config.getoption(override_option_name) + if config_override: + overrides.append(config_override) + temp_ini_file = TestINI(config_file, ini_params=overrides, new_file_prefix=prefix, dir=basetemp) + + return temp_ini_file.create() diff --git a/rhodecode/tests/fixture_mods/fixture_utils.py b/rhodecode/tests/fixtures/fixture_utils.py similarity index 69% rename from rhodecode/tests/fixture_mods/fixture_utils.py rename to rhodecode/tests/fixtures/fixture_utils.py index 9b95ceeb..5caedd3f 100644 --- a/rhodecode/tests/fixture_mods/fixture_utils.py +++ b/rhodecode/tests/fixtures/fixture_utils.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -30,6 +29,7 @@ import uuid import dateutil.tz import logging import functools +import textwrap import mock import pyramid.testing @@ -43,8 +43,17 @@ import rhodecode.lib from rhodecode.model.changeset_status import ChangesetStatusModel from rhodecode.model.comment import CommentsModel from rhodecode.model.db import ( - PullRequest, PullRequestReviewers, Repository, RhodeCodeSetting, ChangesetStatus, - RepoGroup, UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi) + PullRequest, + PullRequestReviewers, + Repository, + RhodeCodeSetting, + ChangesetStatus, + RepoGroup, + UserGroup, + RepoRhodeCodeUi, + RepoRhodeCodeSetting, + RhodeCodeUi, +) from rhodecode.model.meta import Session from rhodecode.model.pull_request import PullRequestModel from rhodecode.model.repo import RepoModel @@ -60,12 +69,20 @@ from rhodecode.lib.str_utils import safe_bytes from rhodecode.lib.hash_utils import sha1_safe from rhodecode.lib.vcs.backends import get_backend from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.base import bootstrap_config from rhodecode.tests import ( - login_user_session, get_new_dir, utils, TESTS_TMP_PATH, - TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN, - TEST_USER_REGULAR_PASS) -from rhodecode.tests.utils import CustomTestApp, set_anonymous_access -from rhodecode.tests.fixture import Fixture + login_user_session, + get_new_dir, + utils, + TESTS_TMP_PATH, + TEST_USER_ADMIN_LOGIN, + TEST_USER_REGULAR_LOGIN, + TEST_USER_REGULAR2_LOGIN, + TEST_USER_REGULAR_PASS, + console_printer, +) +from rhodecode.tests.utils import set_anonymous_access +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.config import utils as config_utils log = logging.getLogger(__name__) @@ -76,36 +93,7 @@ def cmp(a, b): return (a > b) - (a < b) -@pytest.fixture(scope='session', autouse=True) -def activate_example_rcextensions(request): - """ - Patch in an example rcextensions module which verifies passed in kwargs. - """ - from rhodecode.config import rcextensions - - old_extensions = rhodecode.EXTENSIONS - rhodecode.EXTENSIONS = rcextensions - rhodecode.EXTENSIONS.calls = collections.defaultdict(list) - - @request.addfinalizer - def cleanup(): - rhodecode.EXTENSIONS = old_extensions - - -@pytest.fixture() -def capture_rcextensions(): - """ - Returns the recorded calls to entry points in rcextensions. - """ - calls = rhodecode.EXTENSIONS.calls - calls.clear() - # Note: At this moment, it is still the empty dict, but that will - # be filled during the test run and since it is a reference this - # is enough to make it work. - return calls - - -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def http_environ_session(): """ Allow to use "http_environ" in session scope. @@ -117,7 +105,31 @@ def plain_http_host_stub(): """ Value of HTTP_HOST in the test run. """ - return 'example.com:80' + return "example.com:80" + + +def plain_config_stub(request, request_stub): + """ + Set up pyramid.testing and return the Configurator. + """ + + config = bootstrap_config(request=request_stub) + + @request.addfinalizer + def cleanup(): + pyramid.testing.tearDown() + + return config + + +def plain_request_stub(): + """ + Stub request object. + """ + from rhodecode.lib.base import bootstrap_request + + _request = bootstrap_request(scheme="https") + return _request @pytest.fixture() @@ -132,7 +144,7 @@ def plain_http_host_only_stub(): """ Value of HTTP_HOST in the test run. """ - return plain_http_host_stub().split(':')[0] + return plain_http_host_stub().split(":")[0] @pytest.fixture() @@ -147,33 +159,21 @@ def plain_http_environ(): """ HTTP extra environ keys. - User by the test application and as well for setting up the pylons + Used by the test application and as well for setting up the pylons environment. In the case of the fixture "app" it should be possible to override this for a specific test case. """ return { - 'SERVER_NAME': plain_http_host_only_stub(), - 'SERVER_PORT': plain_http_host_stub().split(':')[1], - 'HTTP_HOST': plain_http_host_stub(), - 'HTTP_USER_AGENT': 'rc-test-agent', - 'REQUEST_METHOD': 'GET' + "SERVER_NAME": plain_http_host_only_stub(), + "SERVER_PORT": plain_http_host_stub().split(":")[1], + "HTTP_HOST": plain_http_host_stub(), + "HTTP_USER_AGENT": "rc-test-agent", + "REQUEST_METHOD": "GET", } -@pytest.fixture() -def http_environ(): - """ - HTTP extra environ keys. - - User by the test application and as well for setting up the pylons - environment. In the case of the fixture "app" it should be possible - to override this for a specific test case. - """ - return plain_http_environ() - - -@pytest.fixture(scope='session') -def baseapp(ini_config, vcsserver, http_environ_session): +@pytest.fixture(scope="session") +def baseapp(request, ini_config, http_environ_session, available_port_factory, vcsserver_factory, celery_factory): from rhodecode.lib.config_utils import get_app_config from rhodecode.config.middleware import make_pyramid_app @@ -181,22 +181,41 @@ def baseapp(ini_config, vcsserver, http_environ_session): pyramid.paster.setup_logging(ini_config) settings = get_app_config(ini_config) - app = make_pyramid_app({'__file__': ini_config}, **settings) + store_dir = os.path.dirname(ini_config) - return app + # start vcsserver + _vcsserver_port = available_port_factory() + vcsserver_instance = vcsserver_factory( + request, + store_dir=store_dir, + port=_vcsserver_port, + info_prefix="base-app-" + ) + + settings["vcs.server"] = vcsserver_instance.bind_addr + + # we skip setting store_dir for baseapp, it's internally set via testing rhodecode.ini + # settings['repo_store.path'] = str(store_dir) + console_printer(f' :warning: [green]pytest-setup[/green] Starting base pyramid-app: {ini_config}') + pyramid_baseapp = make_pyramid_app({"__file__": ini_config}, **settings) + + # start celery + celery_factory( + request, + store_dir=store_dir, + port=None, + info_prefix="base-app-", + overrides=( + {'handler_console': {'level': 'DEBUG'}}, + {'app:main': {'vcs.server': vcsserver_instance.bind_addr}}, + {'app:main': {'repo_store.path': store_dir}} + ) + ) + + return pyramid_baseapp -@pytest.fixture(scope='function') -def app(request, config_stub, baseapp, http_environ): - app = CustomTestApp( - baseapp, - extra_environ=http_environ) - if request.cls: - request.cls.app = app - return app - - -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def app_settings(baseapp, ini_config): """ Settings dictionary used to create the app. @@ -207,19 +226,19 @@ def app_settings(baseapp, ini_config): return baseapp.config.get_settings() -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def db_connection(ini_settings): # Initialize the database connection. config_utils.initialize_database(ini_settings) -LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user')) +LoginData = collections.namedtuple("LoginData", ("csrf_token", "user")) def _autologin_user(app, *args): session = login_user_session(app, *args) csrf_token = rhodecode.lib.auth.get_csrf_token(session) - return LoginData(csrf_token, session['rhodecode_user']) + return LoginData(csrf_token, session["rhodecode_user"]) @pytest.fixture() @@ -235,18 +254,17 @@ def autologin_regular_user(app): """ Utility fixture which makes sure that the regular user is logged in """ - return _autologin_user( - app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) + return _autologin_user(app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def csrf_token(request, autologin_user): return autologin_user.csrf_token -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def xhr_header(request): - return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + return {"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"} @pytest.fixture() @@ -257,18 +275,18 @@ def real_crypto_backend(monkeypatch): During the test run the crypto backend is replaced with a faster implementation based on the MD5 algorithm. """ - monkeypatch.setattr(rhodecode, 'is_test', False) + monkeypatch.setattr(rhodecode, "is_test", False) -@pytest.fixture(scope='class') +@pytest.fixture(scope="class") def index_location(request, baseapp): - index_location = baseapp.config.get_settings()['search.location'] + index_location = baseapp.config.get_settings()["search.location"] if request.cls: request.cls.index_location = index_location return index_location -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def tests_tmp_path(request): """ Create temporary directory to be used during the test session. @@ -276,7 +294,8 @@ def tests_tmp_path(request): if not os.path.exists(TESTS_TMP_PATH): os.makedirs(TESTS_TMP_PATH) - if not request.config.getoption('--keep-tmp-path'): + if not request.config.getoption("--keep-tmp-path"): + @request.addfinalizer def remove_tmp_path(): shutil.rmtree(TESTS_TMP_PATH) @@ -291,7 +310,7 @@ def test_repo_group(request): usage automatically """ fixture = Fixture() - repogroupid = 'test_repo_group_%s' % str(time.time()).replace('.', '') + repogroupid = "test_repo_group_%s" % str(time.time()).replace(".", "") repo_group = fixture.create_repo_group(repogroupid) def _cleanup(): @@ -308,7 +327,7 @@ def test_user_group(request): usage automatically """ fixture = Fixture() - usergroupid = 'test_user_group_%s' % str(time.time()).replace('.', '') + usergroupid = "test_user_group_%s" % str(time.time()).replace(".", "") user_group = fixture.create_user_group(usergroupid) def _cleanup(): @@ -318,7 +337,7 @@ def test_user_group(request): return user_group -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def test_repo(request): container = TestRepoContainer() request.addfinalizer(container._cleanup) @@ -340,9 +359,9 @@ class TestRepoContainer(object): """ dump_extractors = { - 'git': utils.extract_git_repo_from_dump, - 'hg': utils.extract_hg_repo_from_dump, - 'svn': utils.extract_svn_repo_from_dump, + "git": utils.extract_git_repo_from_dump, + "hg": utils.extract_hg_repo_from_dump, + "svn": utils.extract_svn_repo_from_dump, } def __init__(self): @@ -358,7 +377,7 @@ class TestRepoContainer(object): return Repository.get(self._repos[key]) def _create_repo(self, dump_name, backend_alias, config): - repo_name = f'{backend_alias}-{dump_name}' + repo_name = f"{backend_alias}-{dump_name}" backend = get_backend(backend_alias) dump_extractor = self.dump_extractors[backend_alias] repo_path = dump_extractor(dump_name, repo_name) @@ -375,19 +394,17 @@ class TestRepoContainer(object): self._fixture.destroy_repo(repo_name) -def backend_base(request, backend_alias, baseapp, test_repo): - if backend_alias not in request.config.getoption('--backends'): - pytest.skip("Backend %s not selected." % (backend_alias, )) +def backend_base(request, backend_alias, test_repo): + if backend_alias not in request.config.getoption("--backends"): + pytest.skip(f"Backend {backend_alias} not selected.") utils.check_xfail_backends(request.node, backend_alias) utils.check_skip_backends(request.node, backend_alias) - repo_name = 'vcs_test_%s' % (backend_alias, ) + repo_name = "vcs_test_%s" % (backend_alias,) backend = Backend( - alias=backend_alias, - repo_name=repo_name, - test_name=request.node.name, - test_repo_container=test_repo) + alias=backend_alias, repo_name=repo_name, test_name=request.node.name, test_repo_container=test_repo + ) request.addfinalizer(backend.cleanup) return backend @@ -404,22 +421,22 @@ def backend(request, backend_alias, baseapp, test_repo): for specific backends. This is intended as a utility for incremental development of a new backend implementation. """ - return backend_base(request, backend_alias, baseapp, test_repo) + return backend_base(request, backend_alias, test_repo) @pytest.fixture() def backend_git(request, baseapp, test_repo): - return backend_base(request, 'git', baseapp, test_repo) + return backend_base(request, "git", test_repo) @pytest.fixture() def backend_hg(request, baseapp, test_repo): - return backend_base(request, 'hg', baseapp, test_repo) + return backend_base(request, "hg", test_repo) @pytest.fixture() def backend_svn(request, baseapp, test_repo): - return backend_base(request, 'svn', baseapp, test_repo) + return backend_base(request, "svn", test_repo) @pytest.fixture() @@ -467,9 +484,9 @@ class Backend(object): session. """ - invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+') + invalid_repo_name = re.compile(r"[^0-9a-zA-Z]+") _master_repo = None - _master_repo_path = '' + _master_repo_path = "" _commit_ids = {} def __init__(self, alias, repo_name, test_name, test_repo_container): @@ -500,6 +517,7 @@ class Backend(object): last repo which has been created with `create_repo`. """ from rhodecode.model.db import Repository + return Repository.get_by_repo_name(self.repo_name) @property @@ -517,9 +535,7 @@ class Backend(object): which can serve as the base to create a new commit on top of it. """ vcsrepo = self.repo.scm_instance() - head_id = ( - vcsrepo.DEFAULT_BRANCH_NAME or - vcsrepo.commit_ids[-1]) + head_id = vcsrepo.DEFAULT_BRANCH_NAME or vcsrepo.commit_ids[-1] return head_id @property @@ -543,9 +559,7 @@ class Backend(object): return self._commit_ids - def create_repo( - self, commits=None, number_of_commits=0, heads=None, - name_suffix='', bare=False, **kwargs): + def create_repo(self, commits=None, number_of_commits=0, heads=None, name_suffix="", bare=False, **kwargs): """ Create a repository and record it for later cleanup. @@ -559,13 +573,10 @@ class Backend(object): :param bare: set a repo as bare (no checkout) """ self.repo_name = self._next_repo_name() + name_suffix - repo = self._fixture.create_repo( - self.repo_name, repo_type=self.alias, bare=bare, **kwargs) + repo = self._fixture.create_repo(self.repo_name, repo_type=self.alias, bare=bare, **kwargs) self._cleanup_repos.append(repo.repo_name) - commits = commits or [ - {'message': f'Commit {x} of {self.repo_name}'} - for x in range(number_of_commits)] + commits = commits or [{"message": f"Commit {x} of {self.repo_name}"} for x in range(number_of_commits)] vcs_repo = repo.scm_instance() vcs_repo.count() self._add_commits_to_repo(vcs_repo, commits) @@ -579,7 +590,7 @@ class Backend(object): Make sure that repo contains all commits mentioned in `heads` """ vcsrepo = repo.scm_instance() - vcsrepo.config.clear_section('hooks') + vcsrepo.config.clear_section("hooks") commit_ids = [self._commit_ids[h] for h in heads] if do_fetch: vcsrepo.fetch(self._master_repo_path, commit_ids=commit_ids) @@ -592,21 +603,22 @@ class Backend(object): self._cleanup_repos.append(self.repo_name) return repo - def new_repo_name(self, suffix=''): + def new_repo_name(self, suffix=""): self.repo_name = self._next_repo_name() + suffix self._cleanup_repos.append(self.repo_name) return self.repo_name def _next_repo_name(self): - return "%s_%s" % ( - self.invalid_repo_name.sub('_', self._test_name), len(self._cleanup_repos)) + return "%s_%s" % (self.invalid_repo_name.sub("_", self._test_name), len(self._cleanup_repos)) - def ensure_file(self, filename, content=b'Test content\n'): + def ensure_file(self, filename, content=b"Test content\n"): assert self._cleanup_repos, "Avoid writing into vcs_test repos" commits = [ - {'added': [ - FileNode(filename, content=content), - ]}, + { + "added": [ + FileNode(filename, content=content), + ] + }, ] self._add_commits_to_repo(self.repo.scm_instance(), commits) @@ -627,11 +639,11 @@ class Backend(object): self._commit_ids = commit_ids # Creating refs for Git to allow fetching them from remote repository - if self.alias == 'git': + if self.alias == "git": refs = {} for message in self._commit_ids: - cleanup_message = message.replace(' ', '') - ref_name = f'refs/test-refs/{cleanup_message}' + cleanup_message = message.replace(" ", "") + ref_name = f"refs/test-refs/{cleanup_message}" refs[ref_name] = self._commit_ids[message] self._create_refs(repo, refs) @@ -645,7 +657,7 @@ class VcsBackend(object): Represents the test configuration for one supported vcs backend. """ - invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+') + invalid_repo_name = re.compile(r"[^0-9a-zA-Z]+") def __init__(self, alias, repo_path, test_name, test_repo_container): self.alias = alias @@ -658,7 +670,7 @@ class VcsBackend(object): return self._test_repo_container(key, self.alias).scm_instance() def __repr__(self): - return f'{self.__class__.__name__}(alias={self.alias}, repo={self._repo_path})' + return f"{self.__class__.__name__}(alias={self.alias}, repo={self._repo_path})" @property def repo(self): @@ -676,8 +688,7 @@ class VcsBackend(object): """ return get_backend(self.alias) - def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None, - bare=False): + def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None, bare=False): repo_name = self._next_repo_name() self._repo_path = get_new_dir(repo_name) repo_class = get_backend(self.alias) @@ -687,9 +698,7 @@ class VcsBackend(object): repo = repo_class(self._repo_path, create=True, src_url=src_url, bare=bare) self._cleanup_repos.append(repo) - commits = commits or [ - {'message': 'Commit %s of %s' % (x, repo_name)} - for x in range(number_of_commits)] + commits = commits or [{"message": "Commit %s of %s" % (x, repo_name)} for x in range(number_of_commits)] _add_commits_to_repo(repo, commits) return repo @@ -706,38 +715,30 @@ class VcsBackend(object): return self._repo_path def _next_repo_name(self): + return "{}_{}".format(self.invalid_repo_name.sub("_", self._test_name), len(self._cleanup_repos)) - return "{}_{}".format( - self.invalid_repo_name.sub('_', self._test_name), - len(self._cleanup_repos) - ) - - def add_file(self, repo, filename, content='Test content\n'): + def add_file(self, repo, filename, content="Test content\n"): imc = repo.in_memory_commit imc.add(FileNode(safe_bytes(filename), content=safe_bytes(content))) - imc.commit( - message='Automatic commit from vcsbackend fixture', - author='Automatic ') + imc.commit(message="Automatic commit from vcsbackend fixture", author="Automatic ") - def ensure_file(self, filename, content='Test content\n'): + def ensure_file(self, filename, content="Test content\n"): assert self._cleanup_repos, "Avoid writing into vcs_test repos" self.add_file(self.repo, filename, content) def vcsbackend_base(request, backend_alias, tests_tmp_path, baseapp, test_repo) -> VcsBackend: - if backend_alias not in request.config.getoption('--backends'): - pytest.skip("Backend %s not selected." % (backend_alias, )) + if backend_alias not in request.config.getoption("--backends"): + pytest.skip("Backend %s not selected." % (backend_alias,)) utils.check_xfail_backends(request.node, backend_alias) utils.check_skip_backends(request.node, backend_alias) - repo_name = f'vcs_test_{backend_alias}' + repo_name = f"vcs_test_{backend_alias}" repo_path = os.path.join(tests_tmp_path, repo_name) backend = VcsBackend( - alias=backend_alias, - repo_path=repo_path, - test_name=request.node.name, - test_repo_container=test_repo) + alias=backend_alias, repo_path=repo_path, test_name=request.node.name, test_repo_container=test_repo + ) request.addfinalizer(backend.cleanup) return backend @@ -758,17 +759,17 @@ def vcsbackend(request, backend_alias, tests_tmp_path, baseapp, test_repo): @pytest.fixture() def vcsbackend_git(request, tests_tmp_path, baseapp, test_repo): - return vcsbackend_base(request, 'git', tests_tmp_path, baseapp, test_repo) + return vcsbackend_base(request, "git", tests_tmp_path, baseapp, test_repo) @pytest.fixture() def vcsbackend_hg(request, tests_tmp_path, baseapp, test_repo): - return vcsbackend_base(request, 'hg', tests_tmp_path, baseapp, test_repo) + return vcsbackend_base(request, "hg", tests_tmp_path, baseapp, test_repo) @pytest.fixture() def vcsbackend_svn(request, tests_tmp_path, baseapp, test_repo): - return vcsbackend_base(request, 'svn', tests_tmp_path, baseapp, test_repo) + return vcsbackend_base(request, "svn", tests_tmp_path, baseapp, test_repo) @pytest.fixture() @@ -789,29 +790,28 @@ def _add_commits_to_repo(vcs_repo, commits): imc = vcs_repo.in_memory_commit for idx, commit in enumerate(commits): - message = str(commit.get('message', f'Commit {idx}')) + message = str(commit.get("message", f"Commit {idx}")) - for node in commit.get('added', []): + for node in commit.get("added", []): imc.add(FileNode(safe_bytes(node.path), content=node.content)) - for node in commit.get('changed', []): + for node in commit.get("changed", []): imc.change(FileNode(safe_bytes(node.path), content=node.content)) - for node in commit.get('removed', []): + for node in commit.get("removed", []): imc.remove(FileNode(safe_bytes(node.path))) - parents = [ - vcs_repo.get_commit(commit_id=commit_ids[p]) - for p in commit.get('parents', [])] + parents = [vcs_repo.get_commit(commit_id=commit_ids[p]) for p in commit.get("parents", [])] - operations = ('added', 'changed', 'removed') + operations = ("added", "changed", "removed") if not any((commit.get(o) for o in operations)): - imc.add(FileNode(b'file_%b' % safe_bytes(str(idx)), content=safe_bytes(message))) + imc.add(FileNode(b"file_%b" % safe_bytes(str(idx)), content=safe_bytes(message))) commit = imc.commit( message=message, - author=str(commit.get('author', 'Automatic ')), - date=commit.get('date'), - branch=commit.get('branch'), - parents=parents) + author=str(commit.get("author", "Automatic ")), + date=commit.get("date"), + branch=commit.get("branch"), + parents=parents, + ) commit_ids[commit.message] = commit.raw_id @@ -842,14 +842,14 @@ class RepoServer(object): self._cleanup_servers = [] def serve(self, vcsrepo): - if vcsrepo.alias != 'svn': + if vcsrepo.alias != "svn": raise TypeError("Backend %s not supported" % vcsrepo.alias) proc = subprocess.Popen( - ['svnserve', '-d', '--foreground', '--listen-host', 'localhost', - '--root', vcsrepo.path]) + ["svnserve", "-d", "--foreground", "--listen-host", "localhost", "--root", vcsrepo.path] + ) self._cleanup_servers.append(proc) - self.url = 'svn://localhost' + self.url = "svn://localhost" def cleanup(self): for proc in self._cleanup_servers: @@ -874,7 +874,6 @@ def pr_util(backend, request, config_stub): class PRTestUtility(object): - pull_request = None pull_request_id = None mergeable_patcher = None @@ -886,48 +885,55 @@ class PRTestUtility(object): self.backend = backend def create_pull_request( - self, commits=None, target_head=None, source_head=None, - revisions=None, approved=False, author=None, mergeable=False, - enable_notifications=True, name_suffix='', reviewers=None, observers=None, - title="Test", description="Description"): + self, + commits=None, + target_head=None, + source_head=None, + revisions=None, + approved=False, + author=None, + mergeable=False, + enable_notifications=True, + name_suffix="", + reviewers=None, + observers=None, + title="Test", + description="Description", + ): self.set_mergeable(mergeable) if not enable_notifications: # mock notification side effect - self.notification_patcher = mock.patch( - 'rhodecode.model.notification.NotificationModel.create') + self.notification_patcher = mock.patch("rhodecode.model.notification.NotificationModel.create") self.notification_patcher.start() if not self.pull_request: if not commits: commits = [ - {'message': 'c1'}, - {'message': 'c2'}, - {'message': 'c3'}, + {"message": "c1"}, + {"message": "c2"}, + {"message": "c3"}, ] - target_head = 'c1' - source_head = 'c2' - revisions = ['c2'] + target_head = "c1" + source_head = "c2" + revisions = ["c2"] self.commit_ids = self.backend.create_master_repo(commits) - self.target_repository = self.backend.create_repo( - heads=[target_head], name_suffix=name_suffix) - self.source_repository = self.backend.create_repo( - heads=[source_head], name_suffix=name_suffix) - self.author = author or UserModel().get_by_username( - TEST_USER_ADMIN_LOGIN) + self.target_repository = self.backend.create_repo(heads=[target_head], name_suffix=name_suffix) + self.source_repository = self.backend.create_repo(heads=[source_head], name_suffix=name_suffix) + self.author = author or UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) model = PullRequestModel() self.create_parameters = { - 'created_by': self.author, - 'source_repo': self.source_repository.repo_name, - 'source_ref': self._default_branch_reference(source_head), - 'target_repo': self.target_repository.repo_name, - 'target_ref': self._default_branch_reference(target_head), - 'revisions': [self.commit_ids[r] for r in revisions], - 'reviewers': reviewers or self._get_reviewers(), - 'observers': observers or self._get_observers(), - 'title': title, - 'description': description, + "created_by": self.author, + "source_repo": self.source_repository.repo_name, + "source_ref": self._default_branch_reference(source_head), + "target_repo": self.target_repository.repo_name, + "target_ref": self._default_branch_reference(target_head), + "revisions": [self.commit_ids[r] for r in revisions], + "reviewers": reviewers or self._get_reviewers(), + "observers": observers or self._get_observers(), + "title": title, + "description": description, } self.pull_request = model.create(**self.create_parameters) assert model.get_versions(self.pull_request) == [] @@ -943,9 +949,7 @@ class PRTestUtility(object): return self.pull_request def approve(self): - self.create_status_votes( - ChangesetStatus.STATUS_APPROVED, - *self.pull_request.reviewers) + self.create_status_votes(ChangesetStatus.STATUS_APPROVED, *self.pull_request.reviewers) def close(self): PullRequestModel().close_pull_request(self.pull_request, self.author) @@ -953,28 +957,26 @@ class PRTestUtility(object): def _default_branch_reference(self, commit_message, branch: str = None) -> str: default_branch = branch or self.backend.default_branch_name message = self.commit_ids[commit_message] - reference = f'branch:{default_branch}:{message}' + reference = f"branch:{default_branch}:{message}" return reference def _get_reviewers(self): role = PullRequestReviewers.ROLE_REVIEWER return [ - (TEST_USER_REGULAR_LOGIN, ['default1'], False, role, []), - (TEST_USER_REGULAR2_LOGIN, ['default2'], False, role, []), + (TEST_USER_REGULAR_LOGIN, ["default1"], False, role, []), + (TEST_USER_REGULAR2_LOGIN, ["default2"], False, role, []), ] def _get_observers(self): - return [ - - ] + return [] def update_source_repository(self, head=None, do_fetch=False): - heads = [head or 'c3'] + heads = [head or "c3"] self.backend.pull_heads(self.source_repository, heads=heads, do_fetch=do_fetch) def update_target_repository(self, head=None, do_fetch=False): - heads = [head or 'c3'] + heads = [head or "c3"] self.backend.pull_heads(self.target_repository, heads=heads, do_fetch=do_fetch) def set_pr_target_ref(self, ref_type: str = "branch", ref_name: str = "branch", ref_commit_id: str = "") -> str: @@ -1004,7 +1006,7 @@ class PRTestUtility(object): # TODO: johbo: Git and Mercurial have an inconsistent vcs api here, # remove the if once that's sorted out. if self.backend.alias == "git": - kwargs = {'branch_name': self.backend.default_branch_name} + kwargs = {"branch_name": self.backend.default_branch_name} else: kwargs = {} source_vcs.strip(removed_commit_id, **kwargs) @@ -1015,10 +1017,8 @@ class PRTestUtility(object): def create_comment(self, linked_to=None): comment = CommentsModel().create( - text="Test comment", - repo=self.target_repository.repo_name, - user=self.author, - pull_request=self.pull_request) + text="Test comment", repo=self.target_repository.repo_name, user=self.author, pull_request=self.pull_request + ) assert comment.pull_request_version_id is None if linked_to: @@ -1026,15 +1026,15 @@ class PRTestUtility(object): return comment - def create_inline_comment( - self, linked_to=None, line_no='n1', file_path='file_1'): + def create_inline_comment(self, linked_to=None, line_no="n1", file_path="file_1"): comment = CommentsModel().create( text="Test comment", repo=self.target_repository.repo_name, user=self.author, line_no=line_no, f_path=file_path, - pull_request=self.pull_request) + pull_request=self.pull_request, + ) assert comment.pull_request_version_id is None if linked_to: @@ -1044,25 +1044,20 @@ class PRTestUtility(object): def create_version_of_pull_request(self): pull_request = self.create_pull_request() - version = PullRequestModel()._create_version_from_snapshot( - pull_request) + version = PullRequestModel()._create_version_from_snapshot(pull_request) return version def create_status_votes(self, status, *reviewers): for reviewer in reviewers: ChangesetStatusModel().set_status( - repo=self.pull_request.target_repo, - status=status, - user=reviewer.user_id, - pull_request=self.pull_request) + repo=self.pull_request.target_repo, status=status, user=reviewer.user_id, pull_request=self.pull_request + ) def set_mergeable(self, value): if not self.mergeable_patcher: - self.mergeable_patcher = mock.patch.object( - VcsSettingsModel, 'get_general_settings') + self.mergeable_patcher = mock.patch.object(VcsSettingsModel, "get_general_settings") self.mergeable_mock = self.mergeable_patcher.start() - self.mergeable_mock.return_value = { - 'rhodecode_pr_merge_enabled': value} + self.mergeable_mock.return_value = {"rhodecode_pr_merge_enabled": value} def cleanup(self): # In case the source repository is already cleaned up, the pull @@ -1109,7 +1104,6 @@ def user_util(request, db_connection): # TODO: johbo: Split this up into utilities per domain or something similar class UserUtility(object): - def __init__(self, test_name="test"): self._test_name = self._sanitize_name(test_name) self.fixture = Fixture() @@ -1126,37 +1120,29 @@ class UserUtility(object): self.user_permissions = [] def _sanitize_name(self, name): - for char in ['[', ']']: - name = name.replace(char, '_') + for char in ["[", "]"]: + name = name.replace(char, "_") return name - def create_repo_group( - self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True): - group_name = "{prefix}_repogroup_{count}".format( - prefix=self._test_name, - count=len(self.repo_group_ids)) - repo_group = self.fixture.create_repo_group( - group_name, cur_user=owner) + def create_repo_group(self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True): + group_name = "{prefix}_repogroup_{count}".format(prefix=self._test_name, count=len(self.repo_group_ids)) + repo_group = self.fixture.create_repo_group(group_name, cur_user=owner) if auto_cleanup: self.repo_group_ids.append(repo_group.group_id) return repo_group - def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None, - auto_cleanup=True, repo_type='hg', bare=False): - repo_name = "{prefix}_repository_{count}".format( - prefix=self._test_name, - count=len(self.repos_ids)) + def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None, auto_cleanup=True, repo_type="hg", bare=False): + repo_name = "{prefix}_repository_{count}".format(prefix=self._test_name, count=len(self.repos_ids)) repository = self.fixture.create_repo( - repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type, bare=bare) + repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type, bare=bare + ) if auto_cleanup: self.repos_ids.append(repository.repo_id) return repository def create_user(self, auto_cleanup=True, **kwargs): - user_name = "{prefix}_user_{count}".format( - prefix=self._test_name, - count=len(self.user_ids)) + user_name = "{prefix}_user_{count}".format(prefix=self._test_name, count=len(self.user_ids)) user = self.fixture.create_user(user_name, **kwargs) if auto_cleanup: self.user_ids.append(user.user_id) @@ -1171,13 +1157,9 @@ class UserUtility(object): user_group = self.create_user_group(members=[user]) return user, user_group - def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None, - auto_cleanup=True, **kwargs): - group_name = "{prefix}_usergroup_{count}".format( - prefix=self._test_name, - count=len(self.user_group_ids)) - user_group = self.fixture.create_user_group( - group_name, cur_user=owner, **kwargs) + def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None, auto_cleanup=True, **kwargs): + group_name = "{prefix}_usergroup_{count}".format(prefix=self._test_name, count=len(self.user_group_ids)) + user_group = self.fixture.create_user_group(group_name, cur_user=owner, **kwargs) if auto_cleanup: self.user_group_ids.append(user_group.users_group_id) @@ -1190,52 +1172,34 @@ class UserUtility(object): self.inherit_default_user_permissions(user_name, False) self.user_permissions.append((user_name, permission_name)) - def grant_user_permission_to_repo_group( - self, repo_group, user, permission_name): - permission = RepoGroupModel().grant_user_permission( - repo_group, user, permission_name) - self.user_repo_group_permission_ids.append( - (repo_group.group_id, user.user_id)) + def grant_user_permission_to_repo_group(self, repo_group, user, permission_name): + permission = RepoGroupModel().grant_user_permission(repo_group, user, permission_name) + self.user_repo_group_permission_ids.append((repo_group.group_id, user.user_id)) return permission - def grant_user_group_permission_to_repo_group( - self, repo_group, user_group, permission_name): - permission = RepoGroupModel().grant_user_group_permission( - repo_group, user_group, permission_name) - self.user_group_repo_group_permission_ids.append( - (repo_group.group_id, user_group.users_group_id)) + def grant_user_group_permission_to_repo_group(self, repo_group, user_group, permission_name): + permission = RepoGroupModel().grant_user_group_permission(repo_group, user_group, permission_name) + self.user_group_repo_group_permission_ids.append((repo_group.group_id, user_group.users_group_id)) return permission - def grant_user_permission_to_repo( - self, repo, user, permission_name): - permission = RepoModel().grant_user_permission( - repo, user, permission_name) - self.user_repo_permission_ids.append( - (repo.repo_id, user.user_id)) + def grant_user_permission_to_repo(self, repo, user, permission_name): + permission = RepoModel().grant_user_permission(repo, user, permission_name) + self.user_repo_permission_ids.append((repo.repo_id, user.user_id)) return permission - def grant_user_group_permission_to_repo( - self, repo, user_group, permission_name): - permission = RepoModel().grant_user_group_permission( - repo, user_group, permission_name) - self.user_group_repo_permission_ids.append( - (repo.repo_id, user_group.users_group_id)) + def grant_user_group_permission_to_repo(self, repo, user_group, permission_name): + permission = RepoModel().grant_user_group_permission(repo, user_group, permission_name) + self.user_group_repo_permission_ids.append((repo.repo_id, user_group.users_group_id)) return permission - def grant_user_permission_to_user_group( - self, target_user_group, user, permission_name): - permission = UserGroupModel().grant_user_permission( - target_user_group, user, permission_name) - self.user_user_group_permission_ids.append( - (target_user_group.users_group_id, user.user_id)) + def grant_user_permission_to_user_group(self, target_user_group, user, permission_name): + permission = UserGroupModel().grant_user_permission(target_user_group, user, permission_name) + self.user_user_group_permission_ids.append((target_user_group.users_group_id, user.user_id)) return permission - def grant_user_group_permission_to_user_group( - self, target_user_group, user_group, permission_name): - permission = UserGroupModel().grant_user_group_permission( - target_user_group, user_group, permission_name) - self.user_group_user_group_permission_ids.append( - (target_user_group.users_group_id, user_group.users_group_id)) + def grant_user_group_permission_to_user_group(self, target_user_group, user_group, permission_name): + permission = UserGroupModel().grant_user_group_permission(target_user_group, user_group, permission_name) + self.user_group_user_group_permission_ids.append((target_user_group.users_group_id, user_group.users_group_id)) return permission def revoke_user_permission(self, user_name, permission_name): @@ -1285,14 +1249,11 @@ class UserUtility(object): """ first_group = RepoGroup.get(first_group_id) second_group = RepoGroup.get(second_group_id) - first_group_parts = ( - len(first_group.group_name.split('/')) if first_group else 0) - second_group_parts = ( - len(second_group.group_name.split('/')) if second_group else 0) + first_group_parts = len(first_group.group_name.split("/")) if first_group else 0 + second_group_parts = len(second_group.group_name.split("/")) if second_group else 0 return cmp(second_group_parts, first_group_parts) - sorted_repo_group_ids = sorted( - self.repo_group_ids, key=functools.cmp_to_key(_repo_group_compare)) + sorted_repo_group_ids = sorted(self.repo_group_ids, key=functools.cmp_to_key(_repo_group_compare)) for repo_group_id in sorted_repo_group_ids: self.fixture.destroy_repo_group(repo_group_id) @@ -1308,16 +1269,11 @@ class UserUtility(object): """ first_group = UserGroup.get(first_group_id) second_group = UserGroup.get(second_group_id) - first_group_parts = ( - len(first_group.users_group_name.split('/')) - if first_group else 0) - second_group_parts = ( - len(second_group.users_group_name.split('/')) - if second_group else 0) + first_group_parts = len(first_group.users_group_name.split("/")) if first_group else 0 + second_group_parts = len(second_group.users_group_name.split("/")) if second_group else 0 return cmp(second_group_parts, first_group_parts) - sorted_user_group_ids = sorted( - self.user_group_ids, key=functools.cmp_to_key(_user_group_compare)) + sorted_user_group_ids = sorted(self.user_group_ids, key=functools.cmp_to_key(_user_group_compare)) for user_group_id in sorted_user_group_ids: self.fixture.destroy_user_group(user_group_id) @@ -1326,22 +1282,19 @@ class UserUtility(object): self.fixture.destroy_user(user_id) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def testrun(): return { - 'uuid': uuid.uuid4(), - 'start': datetime.datetime.utcnow().isoformat(), - 'timestamp': int(time.time()), + "uuid": uuid.uuid4(), + "start": datetime.datetime.utcnow().isoformat(), + "timestamp": int(time.time()), } class AppenlightClient(object): + url_template = "{url}?protocol_version=0.5" - url_template = '{url}?protocol_version=0.5' - - def __init__( - self, url, api_key, add_server=True, add_timestamp=True, - namespace=None, request=None, testrun=None): + def __init__(self, url, api_key, add_server=True, add_timestamp=True, namespace=None, request=None, testrun=None): self.url = self.url_template.format(url=url) self.api_key = api_key self.add_server = add_server @@ -1362,40 +1315,41 @@ class AppenlightClient(object): def collect(self, data): if self.add_server: - data.setdefault('server', self.server) + data.setdefault("server", self.server) if self.add_timestamp: - data.setdefault('date', datetime.datetime.utcnow().isoformat()) + data.setdefault("date", datetime.datetime.utcnow().isoformat()) if self.namespace: - data.setdefault('namespace', self.namespace) + data.setdefault("namespace", self.namespace) if self.request: - data.setdefault('request', self.request) + data.setdefault("request", self.request) self.stats.append(data) def send_stats(self): tags = [ - ('testrun', self.request), - ('testrun.start', self.testrun['start']), - ('testrun.timestamp', self.testrun['timestamp']), - ('test', self.namespace), + ("testrun", self.request), + ("testrun.start", self.testrun["start"]), + ("testrun.timestamp", self.testrun["timestamp"]), + ("test", self.namespace), ] for key, value in self.tags_before.items(): - tags.append((key + '.before', value)) + tags.append((key + ".before", value)) try: delta = self.tags_after[key] - value - tags.append((key + '.delta', delta)) + tags.append((key + ".delta", delta)) except Exception: pass for key, value in self.tags_after.items(): - tags.append((key + '.after', value)) - self.collect({ - 'message': "Collected tags", - 'tags': tags, - }) + tags.append((key + ".after", value)) + self.collect( + { + "message": "Collected tags", + "tags": tags, + } + ) response = requests.post( self.url, - headers={ - 'X-appenlight-api-key': self.api_key}, + headers={"X-appenlight-api-key": self.api_key}, json=self.stats, ) @@ -1403,7 +1357,7 @@ class AppenlightClient(object): pprint.pprint(self.stats) print(response.headers) print(response.text) - raise Exception('Sending to appenlight failed') + raise Exception("Sending to appenlight failed") @pytest.fixture() @@ -1454,9 +1408,8 @@ class SettingsUtility(object): self.repo_rhodecode_ui_ids = [] self.repo_rhodecode_setting_ids = [] - def create_repo_rhodecode_ui( - self, repo, section, value, key=None, active=True, cleanup=True): - key = key or sha1_safe(f'{section}{value}{repo.repo_id}') + def create_repo_rhodecode_ui(self, repo, section, value, key=None, active=True, cleanup=True): + key = key or sha1_safe(f"{section}{value}{repo.repo_id}") setting = RepoRhodeCodeUi() setting.repository_id = repo.repo_id @@ -1471,9 +1424,8 @@ class SettingsUtility(object): self.repo_rhodecode_ui_ids.append(setting.ui_id) return setting - def create_rhodecode_ui( - self, section, value, key=None, active=True, cleanup=True): - key = key or sha1_safe(f'{section}{value}') + def create_rhodecode_ui(self, section, value, key=None, active=True, cleanup=True): + key = key or sha1_safe(f"{section}{value}") setting = RhodeCodeUi() setting.ui_section = section @@ -1487,10 +1439,8 @@ class SettingsUtility(object): self.rhodecode_ui_ids.append(setting.ui_id) return setting - def create_repo_rhodecode_setting( - self, repo, name, value, type_, cleanup=True): - setting = RepoRhodeCodeSetting( - repo.repo_id, key=name, val=value, type=type_) + def create_repo_rhodecode_setting(self, repo, name, value, type_, cleanup=True): + setting = RepoRhodeCodeSetting(repo.repo_id, key=name, val=value, type=type_) Session().add(setting) Session().commit() @@ -1530,13 +1480,12 @@ class SettingsUtility(object): @pytest.fixture() def no_notifications(request): - notification_patcher = mock.patch( - 'rhodecode.model.notification.NotificationModel.create') + notification_patcher = mock.patch("rhodecode.model.notification.NotificationModel.create") notification_patcher.start() request.addfinalizer(notification_patcher.stop) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def repeat(request): """ The number of repetitions is based on this fixture. @@ -1544,7 +1493,7 @@ def repeat(request): Slower calls may divide it by 10 or 100. It is chosen in a way so that the tests are not too slow in our default test suite. """ - return request.config.getoption('--repeat') + return request.config.getoption("--repeat") @pytest.fixture() @@ -1561,43 +1510,18 @@ def context_stub(): return context -@pytest.fixture() -def request_stub(): - """ - Stub request object. - """ - from rhodecode.lib.base import bootstrap_request - request = bootstrap_request(scheme='https') - return request - - -@pytest.fixture() -def config_stub(request, request_stub): - """ - Set up pyramid.testing and return the Configurator. - """ - from rhodecode.lib.base import bootstrap_config - config = bootstrap_config(request=request_stub) - - @request.addfinalizer - def cleanup(): - pyramid.testing.tearDown() - - return config - - @pytest.fixture() def StubIntegrationType(): class _StubIntegrationType(IntegrationTypeBase): - """ Test integration type class """ + """Test integration type class""" - key = 'test' - display_name = 'Test integration type' - description = 'A test integration type for testing' + key = "test" + display_name = "Test integration type" + description = "A test integration type for testing" @classmethod def icon(cls): - return 'test_icon_html_image' + return "test_icon_html_image" def __init__(self, settings): super(_StubIntegrationType, self).__init__(settings) @@ -1611,14 +1535,14 @@ def StubIntegrationType(): test_string_field = colander.SchemaNode( colander.String(), missing=colander.required, - title='test string field', + title="test string field", ) test_int_field = colander.SchemaNode( colander.Int(), - title='some integer setting', + title="some integer setting", ) - return SettingsSchema() + return SettingsSchema() integration_type_registry.register_integration_type(_StubIntegrationType) return _StubIntegrationType @@ -1627,18 +1551,22 @@ def StubIntegrationType(): @pytest.fixture() def stub_integration_settings(): return { - 'test_string_field': 'some data', - 'test_int_field': 100, + "test_string_field": "some data", + "test_int_field": 100, } @pytest.fixture() -def repo_integration_stub(request, repo_stub, StubIntegrationType, - stub_integration_settings): +def repo_integration_stub(request, repo_stub, StubIntegrationType, stub_integration_settings): integration = IntegrationModel().create( - StubIntegrationType, settings=stub_integration_settings, enabled=True, - name='test repo integration', - repo=repo_stub, repo_group=None, child_repos_only=None) + StubIntegrationType, + settings=stub_integration_settings, + enabled=True, + name="test repo integration", + repo=repo_stub, + repo_group=None, + child_repos_only=None, + ) @request.addfinalizer def cleanup(): @@ -1648,12 +1576,16 @@ def repo_integration_stub(request, repo_stub, StubIntegrationType, @pytest.fixture() -def repogroup_integration_stub(request, test_repo_group, StubIntegrationType, - stub_integration_settings): +def repogroup_integration_stub(request, test_repo_group, StubIntegrationType, stub_integration_settings): integration = IntegrationModel().create( - StubIntegrationType, settings=stub_integration_settings, enabled=True, - name='test repogroup integration', - repo=None, repo_group=test_repo_group, child_repos_only=True) + StubIntegrationType, + settings=stub_integration_settings, + enabled=True, + name="test repogroup integration", + repo=None, + repo_group=test_repo_group, + child_repos_only=True, + ) @request.addfinalizer def cleanup(): @@ -1663,12 +1595,16 @@ def repogroup_integration_stub(request, test_repo_group, StubIntegrationType, @pytest.fixture() -def repogroup_recursive_integration_stub(request, test_repo_group, - StubIntegrationType, stub_integration_settings): +def repogroup_recursive_integration_stub(request, test_repo_group, StubIntegrationType, stub_integration_settings): integration = IntegrationModel().create( - StubIntegrationType, settings=stub_integration_settings, enabled=True, - name='test recursive repogroup integration', - repo=None, repo_group=test_repo_group, child_repos_only=False) + StubIntegrationType, + settings=stub_integration_settings, + enabled=True, + name="test recursive repogroup integration", + repo=None, + repo_group=test_repo_group, + child_repos_only=False, + ) @request.addfinalizer def cleanup(): @@ -1678,12 +1614,16 @@ def repogroup_recursive_integration_stub(request, test_repo_group, @pytest.fixture() -def global_integration_stub(request, StubIntegrationType, - stub_integration_settings): +def global_integration_stub(request, StubIntegrationType, stub_integration_settings): integration = IntegrationModel().create( - StubIntegrationType, settings=stub_integration_settings, enabled=True, - name='test global integration', - repo=None, repo_group=None, child_repos_only=None) + StubIntegrationType, + settings=stub_integration_settings, + enabled=True, + name="test global integration", + repo=None, + repo_group=None, + child_repos_only=None, + ) @request.addfinalizer def cleanup(): @@ -1693,12 +1633,16 @@ def global_integration_stub(request, StubIntegrationType, @pytest.fixture() -def root_repos_integration_stub(request, StubIntegrationType, - stub_integration_settings): +def root_repos_integration_stub(request, StubIntegrationType, stub_integration_settings): integration = IntegrationModel().create( - StubIntegrationType, settings=stub_integration_settings, enabled=True, - name='test global integration', - repo=None, repo_group=None, child_repos_only=True) + StubIntegrationType, + settings=stub_integration_settings, + enabled=True, + name="test global integration", + repo=None, + repo_group=None, + child_repos_only=True, + ) @request.addfinalizer def cleanup(): @@ -1710,8 +1654,8 @@ def root_repos_integration_stub(request, StubIntegrationType, @pytest.fixture() def local_dt_to_utc(): def _factory(dt): - return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone( - dateutil.tz.tzutc()).replace(tzinfo=None) + return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(dateutil.tz.tzutc()).replace(tzinfo=None) + return _factory @@ -1724,7 +1668,7 @@ def disable_anonymous_user(request, baseapp): set_anonymous_access(True) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def rc_fixture(request): return Fixture() @@ -1734,9 +1678,9 @@ def repo_groups(request): fixture = Fixture() session = Session() - zombie_group = fixture.create_repo_group('zombie') - parent_group = fixture.create_repo_group('parent') - child_group = fixture.create_repo_group('parent/child') + zombie_group = fixture.create_repo_group("zombie") + parent_group = fixture.create_repo_group("parent") + child_group = fixture.create_repo_group("parent/child") groups_in_db = session.query(RepoGroup).all() assert len(groups_in_db) == 3 assert child_group.group_parent_id == parent_group.group_id @@ -1748,3 +1692,4 @@ def repo_groups(request): fixture.destroy_repo_group(parent_group) return zombie_group, parent_group, child_group + diff --git a/rhodecode/tests/fixtures/function_scoped_baseapp.py b/rhodecode/tests/fixtures/function_scoped_baseapp.py new file mode 100644 index 00000000..b768258f --- /dev/null +++ b/rhodecode/tests/fixtures/function_scoped_baseapp.py @@ -0,0 +1,52 @@ +# 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 pytest +from rhodecode.tests.utils import CustomTestApp +from rhodecode.tests.fixtures.fixture_utils import plain_http_environ, plain_config_stub, plain_request_stub + + +@pytest.fixture(scope='function') +def request_stub(): + return plain_request_stub() + + +@pytest.fixture(scope='function') +def config_stub(request, request_stub): + return plain_config_stub(request, request_stub) + + +@pytest.fixture(scope='function') +def http_environ(): + """ + HTTP extra environ keys. + + Used by the test application and as well for setting up the pylons + environment. In the case of the fixture "app" it should be possible + to override this for a specific test case. + """ + return plain_http_environ() + + +@pytest.fixture(scope='function') +def app(request, config_stub, http_environ, baseapp): + app = CustomTestApp(baseapp, extra_environ=http_environ) + if request.cls: + # inject app into a class that uses this fixtures + request.cls.app = app + return app diff --git a/rhodecode/tests/fixtures/module_scoped_baseapp.py b/rhodecode/tests/fixtures/module_scoped_baseapp.py new file mode 100644 index 00000000..3478e714 --- /dev/null +++ b/rhodecode/tests/fixtures/module_scoped_baseapp.py @@ -0,0 +1,49 @@ +# 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 pytest +from rhodecode.tests.utils import CustomTestApp +from rhodecode.tests.fixtures.fixture_utils import plain_http_environ, plain_config_stub, plain_request_stub + + +@pytest.fixture(scope='module') +def module_request_stub(): + return plain_request_stub() + + +@pytest.fixture(scope='module') +def module_config_stub(request, module_request_stub): + return plain_config_stub(request, module_request_stub) + + +@pytest.fixture(scope='module') +def module_http_environ(): + """ + HTTP extra environ keys. + + Used by the test application and as well for setting up the pylons + environment. In the case of the fixture "app" it should be possible + to override this for a specific test case. + """ + return plain_http_environ() + + +@pytest.fixture(scope='module') +def module_app(request, module_config_stub, module_http_environ, baseapp): + app = CustomTestApp(baseapp, extra_environ=module_http_environ) + return app diff --git a/rhodecode/tests/fixture.py b/rhodecode/tests/fixtures/rc_fixture.py similarity index 68% rename from rhodecode/tests/fixture.py rename to rhodecode/tests/fixtures/rc_fixture.py index d7ca57bd..2fc5692a 100644 --- a/rhodecode/tests/fixture.py +++ b/rhodecode/tests/fixtures/rc_fixture.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -37,17 +36,16 @@ from rhodecode.model.user_group import UserGroupModel from rhodecode.model.gist import GistModel from rhodecode.model.auth_token import AuthTokenModel from rhodecode.model.scm import ScmModel -from rhodecode.authentication.plugins.auth_rhodecode import \ - RhodeCodeAuthPlugin +from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin from rhodecode.tests import TEST_USER_ADMIN_LOGIN dn = os.path.dirname -FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures') +FIXTURES = os.path.join(dn(os.path.abspath(__file__)), "diff_fixtures") def error_function(*args, **kwargs): - raise Exception('Total Crash !') + raise Exception("Total Crash !") class TestINI(object): @@ -59,8 +57,7 @@ class TestINI(object): print('paster server %s' % new_test_ini) """ - def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT', - destroy=True, dir=None): + def __init__(self, ini_file_path, ini_params, new_file_prefix="DEFAULT", destroy=True, dir=None): self.ini_file_path = ini_file_path self.ini_params = ini_params self.new_path = None @@ -85,9 +82,8 @@ class TestINI(object): parser[section][key] = str(val) with tempfile.NamedTemporaryFile( - mode='w', - prefix=self.new_path_prefix, suffix='.ini', dir=self._dir, - delete=False) as new_ini_file: + mode="w", prefix=self.new_path_prefix, suffix=".ini", dir=self._dir, delete=False + ) as new_ini_file: parser.write(new_ini_file) self.new_path = new_ini_file.name @@ -99,7 +95,6 @@ class TestINI(object): class Fixture(object): - def anon_access(self, status): """ Context process for disabling anonymous access. use like: @@ -139,22 +134,19 @@ class Fixture(object): class context(object): def _get_plugin(self): - plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_id = "egg:rhodecode-enterprise-ce#{}".format(RhodeCodeAuthPlugin.uid) plugin = RhodeCodeAuthPlugin(plugin_id) return plugin def __enter__(self): - plugin = self._get_plugin() - plugin.create_or_update_setting('auth_restriction', auth_restriction) + plugin.create_or_update_setting("auth_restriction", auth_restriction) Session().commit() SettingsModel().invalidate_settings_cache(hard=True) def __exit__(self, exc_type, exc_val, exc_tb): - plugin = self._get_plugin() - plugin.create_or_update_setting( - 'auth_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE) + plugin.create_or_update_setting("auth_restriction", RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE) Session().commit() SettingsModel().invalidate_settings_cache(hard=True) @@ -173,62 +165,61 @@ class Fixture(object): class context(object): def _get_plugin(self): - plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_id = "egg:rhodecode-enterprise-ce#{}".format(RhodeCodeAuthPlugin.uid) plugin = RhodeCodeAuthPlugin(plugin_id) return plugin def __enter__(self): plugin = self._get_plugin() - plugin.create_or_update_setting('scope_restriction', scope_restriction) + plugin.create_or_update_setting("scope_restriction", scope_restriction) Session().commit() SettingsModel().invalidate_settings_cache(hard=True) def __exit__(self, exc_type, exc_val, exc_tb): plugin = self._get_plugin() - plugin.create_or_update_setting( - 'scope_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL) + plugin.create_or_update_setting("scope_restriction", RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL) Session().commit() SettingsModel().invalidate_settings_cache(hard=True) return context() def _get_repo_create_params(self, **custom): - repo_type = custom.get('repo_type') or 'hg' + repo_type = custom.get("repo_type") or "hg" default_landing_ref, landing_ref_lbl = ScmModel.backend_landing_ref(repo_type) defs = { - 'repo_name': None, - 'repo_type': repo_type, - 'clone_uri': '', - 'push_uri': '', - 'repo_group': '-1', - 'repo_description': 'DESC', - 'repo_private': False, - 'repo_landing_commit_ref': default_landing_ref, - 'repo_copy_permissions': False, - 'repo_state': Repository.STATE_CREATED, + "repo_name": None, + "repo_type": repo_type, + "clone_uri": "", + "push_uri": "", + "repo_group": "-1", + "repo_description": "DESC", + "repo_private": False, + "repo_landing_commit_ref": default_landing_ref, + "repo_copy_permissions": False, + "repo_state": Repository.STATE_CREATED, } defs.update(custom) - if 'repo_name_full' not in custom: - defs.update({'repo_name_full': defs['repo_name']}) + if "repo_name_full" not in custom: + defs.update({"repo_name_full": defs["repo_name"]}) # fix the repo name if passed as repo_name_full - if defs['repo_name']: - defs['repo_name'] = defs['repo_name'].split('/')[-1] + if defs["repo_name"]: + defs["repo_name"] = defs["repo_name"].split("/")[-1] return defs def _get_group_create_params(self, **custom): defs = { - 'group_name': None, - 'group_description': 'DESC', - 'perm_updates': [], - 'perm_additions': [], - 'perm_deletions': [], - 'group_parent_id': -1, - 'enable_locking': False, - 'recursive': False, + "group_name": None, + "group_description": "DESC", + "perm_updates": [], + "perm_additions": [], + "perm_deletions": [], + "group_parent_id": -1, + "enable_locking": False, + "recursive": False, } defs.update(custom) @@ -236,16 +227,16 @@ class Fixture(object): def _get_user_create_params(self, name, **custom): defs = { - 'username': name, - 'password': 'qweqwe', - 'email': '%s+test@rhodecode.org' % name, - 'firstname': 'TestUser', - 'lastname': 'Test', - 'description': 'test description', - 'active': True, - 'admin': False, - 'extern_type': 'rhodecode', - 'extern_name': None, + "username": name, + "password": "qweqwe", + "email": "%s+test@rhodecode.org" % name, + "firstname": "TestUser", + "lastname": "Test", + "description": "test description", + "active": True, + "admin": False, + "extern_type": "rhodecode", + "extern_name": None, } defs.update(custom) @@ -253,30 +244,30 @@ class Fixture(object): def _get_user_group_create_params(self, name, **custom): defs = { - 'users_group_name': name, - 'user_group_description': 'DESC', - 'users_group_active': True, - 'user_group_data': {}, + "users_group_name": name, + "user_group_description": "DESC", + "users_group_active": True, + "user_group_data": {}, } defs.update(custom) return defs def create_repo(self, name, **kwargs): - repo_group = kwargs.get('repo_group') + repo_group = kwargs.get("repo_group") if isinstance(repo_group, RepoGroup): - kwargs['repo_group'] = repo_group.group_id + kwargs["repo_group"] = repo_group.group_id name = name.split(Repository.NAME_SEP)[-1] name = Repository.NAME_SEP.join((repo_group.group_name, name)) - if 'skip_if_exists' in kwargs: - del kwargs['skip_if_exists'] + if "skip_if_exists" in kwargs: + del kwargs["skip_if_exists"] r = Repository.get_by_repo_name(name) if r: return r form_data = self._get_repo_create_params(repo_name=name, **kwargs) - cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN) + cur_user = kwargs.get("cur_user", TEST_USER_ADMIN_LOGIN) RepoModel().create(form_data, cur_user) Session().commit() repo = Repository.get_by_repo_name(name) @@ -287,17 +278,15 @@ class Fixture(object): repo_to_fork = Repository.get_by_repo_name(repo_to_fork) form_data = self._get_repo_create_params( - repo_name=fork_name, - fork_parent_id=repo_to_fork.repo_id, - repo_type=repo_to_fork.repo_type, - **kwargs) + repo_name=fork_name, fork_parent_id=repo_to_fork.repo_id, repo_type=repo_to_fork.repo_type, **kwargs + ) # TODO: fix it !! - form_data['description'] = form_data['repo_description'] - form_data['private'] = form_data['repo_private'] - form_data['landing_rev'] = form_data['repo_landing_commit_ref'] + form_data["description"] = form_data["repo_description"] + form_data["private"] = form_data["repo_private"] + form_data["landing_rev"] = form_data["repo_landing_commit_ref"] - owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN) + owner = kwargs.get("cur_user", TEST_USER_ADMIN_LOGIN) RepoModel().create_fork(form_data, cur_user=owner) Session().commit() r = Repository.get_by_repo_name(fork_name) @@ -305,7 +294,7 @@ class Fixture(object): return r def destroy_repo(self, repo_name, **kwargs): - RepoModel().delete(repo_name, pull_requests='delete', artifacts='delete', **kwargs) + RepoModel().delete(repo_name, pull_requests="delete", artifacts="delete", **kwargs) Session().commit() def destroy_repo_on_filesystem(self, repo_name): @@ -314,17 +303,16 @@ class Fixture(object): shutil.rmtree(rm_path) def create_repo_group(self, name, **kwargs): - if 'skip_if_exists' in kwargs: - del kwargs['skip_if_exists'] + if "skip_if_exists" in kwargs: + del kwargs["skip_if_exists"] gr = RepoGroup.get_by_group_name(group_name=name) if gr: return gr form_data = self._get_group_create_params(group_name=name, **kwargs) - owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN) + owner = kwargs.get("cur_user", TEST_USER_ADMIN_LOGIN) gr = RepoGroupModel().create( - group_name=form_data['group_name'], - group_description=form_data['group_name'], - owner=owner) + group_name=form_data["group_name"], group_description=form_data["group_name"], owner=owner + ) Session().commit() gr = RepoGroup.get_by_group_name(gr.group_name) return gr @@ -334,8 +322,8 @@ class Fixture(object): Session().commit() def create_user(self, name, **kwargs): - if 'skip_if_exists' in kwargs: - del kwargs['skip_if_exists'] + if "skip_if_exists" in kwargs: + del kwargs["skip_if_exists"] user = User.get_by_username(name) if user: return user @@ -343,8 +331,7 @@ class Fixture(object): user = UserModel().create(form_data) # create token for user - AuthTokenModel().create( - user=user, description=u'TEST_USER_TOKEN') + AuthTokenModel().create(user=user, description="TEST_USER_TOKEN") Session().commit() user = User.get_by_username(user.username) @@ -368,22 +355,24 @@ class Fixture(object): Session().commit() def create_user_group(self, name, **kwargs): - if 'skip_if_exists' in kwargs: - del kwargs['skip_if_exists'] + if "skip_if_exists" in kwargs: + del kwargs["skip_if_exists"] gr = UserGroup.get_by_group_name(group_name=name) if gr: return gr # map active flag to the real attribute. For API consistency of fixtures - if 'active' in kwargs: - kwargs['users_group_active'] = kwargs['active'] - del kwargs['active'] + if "active" in kwargs: + kwargs["users_group_active"] = kwargs["active"] + del kwargs["active"] form_data = self._get_user_group_create_params(name, **kwargs) - owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN) + owner = kwargs.get("cur_user", TEST_USER_ADMIN_LOGIN) user_group = UserGroupModel().create( - name=form_data['users_group_name'], - description=form_data['user_group_description'], - owner=owner, active=form_data['users_group_active'], - group_data=form_data['user_group_data']) + name=form_data["users_group_name"], + description=form_data["user_group_description"], + owner=owner, + active=form_data["users_group_active"], + group_data=form_data["user_group_data"], + ) Session().commit() user_group = UserGroup.get_by_group_name(user_group.users_group_name) return user_group @@ -394,18 +383,23 @@ class Fixture(object): def create_gist(self, **kwargs): form_data = { - 'description': 'new-gist', - 'owner': TEST_USER_ADMIN_LOGIN, - 'gist_type': GistModel.cls.GIST_PUBLIC, - 'lifetime': -1, - 'acl_level': Gist.ACL_LEVEL_PUBLIC, - 'gist_mapping': {b'filename1.txt': {'content': b'hello world'},} + "description": "new-gist", + "owner": TEST_USER_ADMIN_LOGIN, + "gist_type": GistModel.cls.GIST_PUBLIC, + "lifetime": -1, + "acl_level": Gist.ACL_LEVEL_PUBLIC, + "gist_mapping": { + b"filename1.txt": {"content": b"hello world"}, + }, } form_data.update(kwargs) gist = GistModel().create( - description=form_data['description'], owner=form_data['owner'], - gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'], - lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level'] + description=form_data["description"], + owner=form_data["owner"], + gist_mapping=form_data["gist_mapping"], + gist_type=form_data["gist_type"], + lifetime=form_data["lifetime"], + gist_acl_level=form_data["acl_level"], ) Session().commit() return gist @@ -420,7 +414,7 @@ class Fixture(object): Session().commit() def load_resource(self, resource_name, strip=False): - with open(os.path.join(FIXTURES, resource_name), 'rb') as f: + with open(os.path.join(FIXTURES, resource_name), "rb") as f: source = f.read() if strip: source = source.strip() diff --git a/rhodecode/tests/fixtures/rcextensions_fixtures.py b/rhodecode/tests/fixtures/rcextensions_fixtures.py new file mode 100644 index 00000000..c8879912 --- /dev/null +++ b/rhodecode/tests/fixtures/rcextensions_fixtures.py @@ -0,0 +1,157 @@ +# 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 os +import shutil +import logging +import textwrap + +import pytest + +import rhodecode +import rhodecode.lib + +from rhodecode.tests import console_printer + +log = logging.getLogger(__name__) + + +def store_rcextensions(destination, force=False): + from rhodecode.config import rcextensions + package_path = rcextensions.__path__[0] + + # Note: rcextensions are looked up based on the path of the ini file + rcextensions_path = os.path.join(destination, 'rcextensions') + + if force: + shutil.rmtree(rcextensions_path, ignore_errors=True) + shutil.copytree(package_path, rcextensions_path) + + +@pytest.fixture(scope="module") +def rcextensions(request, tmp_storage_location): + """ + Installs a testing rcextensions pack to ensure they work as expected. + """ + + # Note: rcextensions are looked up based on the path of the ini file + rcextensions_path = os.path.join(tmp_storage_location, 'rcextensions') + + if os.path.exists(rcextensions_path): + pytest.fail( + f"Path for rcextensions already exists, please clean up before " + f"test run this path: {rcextensions_path}") + else: + store_rcextensions(tmp_storage_location) + + +@pytest.fixture(scope='function') +def rcextensions_present(request): + + class RcExtensionsPresent: + def __init__(self, rcextensions_location): + self.rcextensions_location = rcextensions_location + + def __enter__(self): + self.store() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.cleanup() + + def store(self): + store_rcextensions(self.rcextensions_location) + + def cleanup(self): + shutil.rmtree(os.path.join(self.rcextensions_location, 'rcextensions')) + + return RcExtensionsPresent + + +@pytest.fixture(scope='function') +def rcextensions_modification(request): + """ + example usage:: + + hook_name = '_pre_push_hook' + code = ''' + raise OSError('failed') + return HookResponse(1, 'FAILED') + ''' + mods = [ + (hook_name, code), + ] + # rhodecode.ini file location, where rcextensions needs to live + rcstack_location = os.path.dirname(rcstack.config_file) + with rcextensions_modification(rcstack_location, mods): + # do some stuff + """ + + class RcextensionsModification: + def __init__(self, rcextensions_location, mods, create_if_missing=False, force_create=False): + self.force_create = force_create + self.create_if_missing = create_if_missing + self.rcextensions_location = rcextensions_location + self.mods = mods + if not isinstance(mods, list): + raise ValueError('mods must be a list of modifications') + + def __enter__(self): + if self.create_if_missing: + store_rcextensions(self.rcextensions_location, force=self.force_create) + + for hook_name, method_body in self.mods: + self.modification(hook_name, method_body) + + def __exit__(self, exc_type, exc_val, exc_tb): + self.cleanup() + + def cleanup(self): + # reset rcextensions to "bare" state from the package + store_rcextensions(self.rcextensions_location, force=True) + + def modification(self, hook_name, method_body): + import ast + + rcextensions_path = os.path.join(self.rcextensions_location, 'rcextensions') + + # Load the code from hooks.py + hooks_filename = os.path.join(rcextensions_path, 'hooks.py') + with open(hooks_filename, "r") as file: + tree = ast.parse(file.read()) + + # Define new content for the function as a string + new_code = textwrap.dedent(method_body) + + # Parse the new code to add it to the function + new_body = ast.parse(new_code).body + + # Walk through the AST to find and modify the function + for node in tree.body: + if isinstance(node, ast.FunctionDef) and node.name == hook_name: + node.body = new_body # Replace the function body with the new body + + # Compile the modified AST back to code + compile(tree, hooks_filename, "exec") + + # Write the updated code back to hooks.py + with open(hooks_filename, "w") as file: + file.write(ast.unparse(tree)) # Requires Python 3.9+ + + console_printer(f" [green]rcextensions[/green] Updated the body of '{hooks_filename}' function '{hook_name}'") + + return RcextensionsModification diff --git a/rhodecode/tests/functional/test_delegated_admin.py b/rhodecode/tests/functional/test_delegated_admin.py index 8e65348a..e48cf835 100644 --- a/rhodecode/tests/functional/test_delegated_admin.py +++ b/rhodecode/tests/functional/test_delegated_admin.py @@ -21,7 +21,7 @@ import pytest from rhodecode.tests import TestController -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests.routes import route_path diff --git a/rhodecode/tests/integrations/__init__.py b/rhodecode/tests/integrations/__init__.py new file mode 100644 index 00000000..ae06165a --- /dev/null +++ b/rhodecode/tests/integrations/__init__.py @@ -0,0 +1,17 @@ +# 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/ diff --git a/rhodecode/tests/integrations/test_integration.py b/rhodecode/tests/integrations/test_integration.py index 1e320b67..8c76dbc5 100644 --- a/rhodecode/tests/integrations/test_integration.py +++ b/rhodecode/tests/integrations/test_integration.py @@ -20,7 +20,7 @@ import time import pytest from rhodecode import events -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.model.db import Session, Integration from rhodecode.model.integration import IntegrationModel diff --git a/rhodecode/tests/lib/auth_modules/__init__.py b/rhodecode/tests/lib/auth_modules/__init__.py new file mode 100644 index 00000000..ae06165a --- /dev/null +++ b/rhodecode/tests/lib/auth_modules/__init__.py @@ -0,0 +1,17 @@ +# 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/ diff --git a/rhodecode/tests/lib/middleware/test_simplehg.py b/rhodecode/tests/lib/middleware/test_simplehg.py index 69bbf833..608a245d 100644 --- a/rhodecode/tests/lib/middleware/test_simplehg.py +++ b/rhodecode/tests/lib/middleware/test_simplehg.py @@ -123,10 +123,14 @@ def test_get_config(user_util, baseapp, request_stub): ('web', 'allow_push', '*'), ('web', 'allow_archive', 'gz zip bz2'), ('web', 'baseurl', '/'), + + # largefiles data... ('vcs_git_lfs', 'store_location', hg_config_org.get('vcs_git_lfs', 'store_location')), + ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')), + ('vcs_svn_branch', '9aac1a38c3b8a0cdc4ae0f960a5f83332bc4fa5e', '/branches/*'), ('vcs_svn_branch', 'c7e6a611c87da06529fd0dd733308481d67c71a8', '/trunk'), - ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')), + ('hooks', 'preoutgoing.pre_pull', 'python:vcsserver.hooks.pre_pull'), ('hooks', 'prechangegroup.pre_push', 'python:vcsserver.hooks.pre_push'), ('hooks', 'outgoing.pull_logger', 'python:vcsserver.hooks.log_pull_action'), diff --git a/rhodecode/tests/lib/middleware/test_simplevcs.py b/rhodecode/tests/lib/middleware/test_simplevcs.py index c8129d24..2ed7cfde 100644 --- a/rhodecode/tests/lib/middleware/test_simplevcs.py +++ b/rhodecode/tests/lib/middleware/test_simplevcs.py @@ -22,7 +22,8 @@ import pytest from rhodecode.lib.str_utils import base64_to_str from rhodecode.lib.utils2 import AttributeDict -from rhodecode.tests.utils import CustomTestApp +from rhodecode.tests.fixtures.fixture_pyramid import ini_config +from rhodecode.tests.utils import CustomTestApp, AuthPluginManager from rhodecode.lib.caching_query import FromCache from rhodecode.lib.middleware import simplevcs @@ -34,6 +35,57 @@ from rhodecode.tests import ( HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS) from rhodecode.tests.lib.middleware import mock_scm_app +from rhodecode.model.db import Permission, User +from rhodecode.model.meta import Session +from rhodecode.model.user import UserModel + + +@pytest.fixture() +def enable_auth_plugins(request, app): + """ + Return a factory object that when called, allows to control which + authentication plugins are enabled. + """ + + enabler = AuthPluginManager() + request.addfinalizer(enabler.cleanup) + + return enabler + + +@pytest.fixture() +def test_user_factory(request, baseapp): + + def user_factory(username='test_user', password='qweqwe', first_name='John', last_name='Testing', **kwargs): + usr = UserModel().create_or_update( + username=username, + password=password, + email=f'{username}@rhodecode.org', + firstname=first_name, lastname=last_name) + Session().commit() + + for k, v in kwargs.items(): + setattr(usr, k, v) + Session().add(usr) + + new_usr = User.get_by_username(username) + new_usr_id = new_usr.user_id + assert new_usr == usr + + @request.addfinalizer + def cleanup(): + if User.get(new_usr_id) is None: + return + + perm = Permission.query().all() + for p in perm: + UserModel().revoke_perm(usr, p) + + UserModel().delete(new_usr_id) + Session().commit() + return usr + + return user_factory class StubVCSController(simplevcs.SimpleVCS): @@ -107,8 +159,7 @@ def _remove_default_user_from_query_cache(): Session().expire(user) -def test_handles_exceptions_during_permissions_checks( - vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory): +def test_handles_exceptions_during_permissions_checks(vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory): test_password = 'qweqwe' test_user = test_user_factory(password=test_password, extern_type='headers', extern_name='headers') @@ -373,29 +424,30 @@ class TestShadowRepoExposure(object): controller.vcs_repo_name) -@pytest.mark.usefixtures('baseapp') class TestGenerateVcsResponse(object): - def test_ensures_that_start_response_is_called_early_enough(self): - self.call_controller_with_response_body(iter(['a', 'b'])) + def test_ensures_that_start_response_is_called_early_enough(self, baseapp): + app_ini_config = baseapp.config.registry.settings['__file__'] + self.call_controller_with_response_body(app_ini_config, iter(['a', 'b'])) assert self.start_response.called - def test_invalidates_cache_after_body_is_consumed(self): - result = self.call_controller_with_response_body(iter(['a', 'b'])) + def test_invalidates_cache_after_body_is_consumed(self, baseapp): + app_ini_config = baseapp.config.registry.settings['__file__'] + result = self.call_controller_with_response_body(app_ini_config, iter(['a', 'b'])) assert not self.was_cache_invalidated() # Consume the result list(result) assert self.was_cache_invalidated() - def test_raises_unknown_exceptions(self): - result = self.call_controller_with_response_body( - self.raise_result_iter(vcs_kind='unknown')) + def test_raises_unknown_exceptions(self, baseapp): + app_ini_config = baseapp.config.registry.settings['__file__'] + result = self.call_controller_with_response_body(app_ini_config, self.raise_result_iter(vcs_kind='unknown')) with pytest.raises(Exception): list(result) - def call_controller_with_response_body(self, response_body): + def call_controller_with_response_body(self, ini_config, response_body): + settings = { - 'base_path': 'fake_base_path', 'vcs.hooks.protocol.v2': 'celery', 'vcs.hooks.direct_calls': False, } @@ -407,7 +459,7 @@ class TestGenerateVcsResponse(object): result = controller._generate_vcs_response( environ={}, start_response=self.start_response, repo_path='fake_repo_path', - extras={}, action='push') + extras={'config': ini_config}, action='push') self.controller = controller return result diff --git a/rhodecode/tests/lib/middleware/utils/test_scm_app_http.py b/rhodecode/tests/lib/middleware/utils/test_scm_app_http.py index 3f9b7b75..6530618c 100644 --- a/rhodecode/tests/lib/middleware/utils/test_scm_app_http.py +++ b/rhodecode/tests/lib/middleware/utils/test_scm_app_http.py @@ -19,6 +19,7 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ import pytest +import tempfile from rhodecode.tests.utils import CustomTestApp from rhodecode.lib.middleware.utils import scm_app_http, scm_app @@ -41,10 +42,13 @@ def vcsserver_http_echo_app(request, vcsserver_factory): """ A running VCSServer with the EchoApp activated via HTTP. """ - vcsserver = vcsserver_factory( + store_dir = tempfile.gettempdir() + + vcsserver_instance = vcsserver_factory( request=request, + store_dir=store_dir, overrides=[{'app:main': {'dev.use_echo_app': 'true'}}]) - return vcsserver + return vcsserver_instance @pytest.fixture(scope='session') diff --git a/rhodecode/tests/lib/test_auth.py b/rhodecode/tests/lib/test_auth.py index 63955c53..1b8f98bc 100644 --- a/rhodecode/tests/lib/test_auth.py +++ b/rhodecode/tests/lib/test_auth.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 diff --git a/rhodecode/tests/lib/test_diffs.py b/rhodecode/tests/lib/test_diffs.py index e543d78f..c38c7bb7 100644 --- a/rhodecode/tests/lib/test_diffs.py +++ b/rhodecode/tests/lib/test_diffs.py @@ -30,7 +30,7 @@ from rhodecode.lib.diffs import ( from rhodecode.lib.utils2 import AttributeDict from rhodecode.lib.vcs.backends.git import GitCommit -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.tests import no_newline_id_generator from rhodecode.lib.vcs.backends.git.repository import GitDiff from rhodecode.lib.vcs.backends.hg.repository import MercurialDiff diff --git a/rhodecode/tests/lib/test_hooks_daemon.py b/rhodecode/tests/lib/test_hooks_daemon.py index cf6b6500..1e4e079e 100644 --- a/rhodecode/tests/lib/test_hooks_daemon.py +++ b/rhodecode/tests/lib/test_hooks_daemon.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -18,305 +17,71 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ import logging -import io import mock -import msgpack import pytest import tempfile -from rhodecode.lib.hook_daemon import http_hooks_deamon from rhodecode.lib.hook_daemon import celery_hooks_deamon -from rhodecode.lib.hook_daemon import hook_module +from rhodecode.lib.hook_daemon import utils as hooks_utils from rhodecode.lib.hook_daemon import base as hook_base -from rhodecode.lib.str_utils import safe_bytes -from rhodecode.tests.utils import assert_message_in_log -from rhodecode.lib.ext_json import json -test_proto = http_hooks_deamon.HooksHttpHandler.MSGPACK_HOOKS_PROTO +from rhodecode.tests.utils import assert_message_in_log class TestHooks(object): def test_hooks_can_be_used_as_a_context_processor(self): - hooks = hook_module.Hooks() + hooks = hook_base.Hooks() with hooks as return_value: pass assert hooks == return_value - -class TestHooksHttpHandler(object): - def test_read_request_parses_method_name_and_arguments(self): - data = { - 'method': 'test', - 'extras': { - 'param1': 1, - 'param2': 'a' - } - } - request = self._generate_post_request(data) - hooks_patcher = mock.patch.object( - hook_module.Hooks, data['method'], create=True, return_value=1) - - with hooks_patcher as hooks_mock: - handler = http_hooks_deamon.HooksHttpHandler - handler.DEFAULT_HOOKS_PROTO = test_proto - handler.wbufsize = 10240 - MockServer(handler, request) - - hooks_mock.assert_called_once_with(data['extras']) - - def test_hooks_serialized_result_is_returned(self): - request = self._generate_post_request({}) - rpc_method = 'test' - hook_result = { - 'first': 'one', - 'second': 2 - } - extras = {} - - # patching our _read to return test method and proto used - read_patcher = mock.patch.object( - http_hooks_deamon.HooksHttpHandler, '_read_request', - return_value=(test_proto, rpc_method, extras)) - - # patch Hooks instance to return hook_result data on 'test' call - hooks_patcher = mock.patch.object( - hook_module.Hooks, rpc_method, create=True, - return_value=hook_result) - - with read_patcher, hooks_patcher: - handler = http_hooks_deamon.HooksHttpHandler - handler.DEFAULT_HOOKS_PROTO = test_proto - handler.wbufsize = 10240 - server = MockServer(handler, request) - - expected_result = http_hooks_deamon.HooksHttpHandler.serialize_data(hook_result) - - server.request.output_stream.seek(0) - assert server.request.output_stream.readlines()[-1] == expected_result - - def test_exception_is_returned_in_response(self): - request = self._generate_post_request({}) - rpc_method = 'test' - - read_patcher = mock.patch.object( - http_hooks_deamon.HooksHttpHandler, '_read_request', - return_value=(test_proto, rpc_method, {})) - - hooks_patcher = mock.patch.object( - hook_module.Hooks, rpc_method, create=True, - side_effect=Exception('Test exception')) - - with read_patcher, hooks_patcher: - handler = http_hooks_deamon.HooksHttpHandler - handler.DEFAULT_HOOKS_PROTO = test_proto - handler.wbufsize = 10240 - server = MockServer(handler, request) - - server.request.output_stream.seek(0) - data = server.request.output_stream.readlines() - msgpack_data = b''.join(data[5:]) - org_exc = http_hooks_deamon.HooksHttpHandler.deserialize_data(msgpack_data) - expected_result = { - 'exception': 'Exception', - 'exception_traceback': org_exc['exception_traceback'], - 'exception_args': ['Test exception'] - } - assert org_exc == expected_result - - def test_log_message_writes_to_debug_log(self, caplog): - ip_port = ('0.0.0.0', 8888) - handler = http_hooks_deamon.HooksHttpHandler(MockRequest('POST /'), ip_port, mock.Mock()) - fake_date = '1/Nov/2015 00:00:00' - date_patcher = mock.patch.object( - handler, 'log_date_time_string', return_value=fake_date) - - with date_patcher, caplog.at_level(logging.DEBUG): - handler.log_message('Some message %d, %s', 123, 'string') - - expected_message = f"HOOKS: client={ip_port} - - [{fake_date}] Some message 123, string" - - assert_message_in_log( - caplog.records, expected_message, - levelno=logging.DEBUG, module='http_hooks_deamon') - - def _generate_post_request(self, data, proto=test_proto): - if proto == http_hooks_deamon.HooksHttpHandler.MSGPACK_HOOKS_PROTO: - payload = msgpack.packb(data) - else: - payload = json.dumps(data) - - return b'POST / HTTP/1.0\nContent-Length: %d\n\n%b' % ( - len(payload), payload) - - -class ThreadedHookCallbackDaemon(object): - def test_constructor_calls_prepare(self): - prepare_daemon_patcher = mock.patch.object( - http_hooks_deamon.ThreadedHookCallbackDaemon, '_prepare') - with prepare_daemon_patcher as prepare_daemon_mock: - http_hooks_deamon.ThreadedHookCallbackDaemon() - prepare_daemon_mock.assert_called_once_with() - - def test_run_is_called_on_context_start(self): - patchers = mock.patch.multiple( - http_hooks_deamon.ThreadedHookCallbackDaemon, - _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT) - - with patchers as mocks: - daemon = http_hooks_deamon.ThreadedHookCallbackDaemon() - with daemon as daemon_context: - pass - mocks['_run'].assert_called_once_with() - assert daemon_context == daemon - - def test_stop_is_called_on_context_exit(self): - patchers = mock.patch.multiple( - http_hooks_deamon.ThreadedHookCallbackDaemon, - _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT) - - with patchers as mocks: - daemon = http_hooks_deamon.ThreadedHookCallbackDaemon() - with daemon as daemon_context: - assert mocks['_stop'].call_count == 0 - - mocks['_stop'].assert_called_once_with() - assert daemon_context == daemon - - -class TestHttpHooksCallbackDaemon(object): - def test_hooks_callback_generates_new_port(self, caplog): - with caplog.at_level(logging.DEBUG): - daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881) - assert daemon._daemon.server_address == ('127.0.0.1', 8881) - - with caplog.at_level(logging.DEBUG): - daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host=None, port=None) - assert daemon._daemon.server_address[1] in range(0, 66000) - assert daemon._daemon.server_address[0] != '127.0.0.1' - - def test_prepare_inits_daemon_variable(self, tcp_server, caplog): - with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG): - daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881) - assert daemon._daemon == tcp_server - - _, port = tcp_server.server_address - - msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \ - f"hook object: " - assert_message_in_log( - caplog.records, msg, levelno=logging.DEBUG, module='http_hooks_deamon') - - def test_prepare_inits_hooks_uri_and_logs_it( - self, tcp_server, caplog): - with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG): - daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881) - - _, port = tcp_server.server_address - expected_uri = '{}:{}'.format('127.0.0.1', port) - assert daemon.hooks_uri == expected_uri - - msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \ - f"hook object: " - - assert_message_in_log( - caplog.records, msg, - levelno=logging.DEBUG, module='http_hooks_deamon') - - def test_run_creates_a_thread(self, tcp_server): - thread = mock.Mock() - - with self._tcp_patcher(tcp_server): - daemon = http_hooks_deamon.HttpHooksCallbackDaemon() - - with self._thread_patcher(thread) as thread_mock: - daemon._run() - - thread_mock.assert_called_once_with( - target=tcp_server.serve_forever, - kwargs={'poll_interval': daemon.POLL_INTERVAL}) - assert thread.daemon is True - thread.start.assert_called_once_with() - - def test_run_logs(self, tcp_server, caplog): - - with self._tcp_patcher(tcp_server): - daemon = http_hooks_deamon.HttpHooksCallbackDaemon() - - with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG): - daemon._run() - - assert_message_in_log( - caplog.records, - 'Running thread-based loop of callback daemon in background', - levelno=logging.DEBUG, module='http_hooks_deamon') - - def test_stop_cleans_up_the_connection(self, tcp_server, caplog): - thread = mock.Mock() - - with self._tcp_patcher(tcp_server): - daemon = http_hooks_deamon.HttpHooksCallbackDaemon() - - with self._thread_patcher(thread), caplog.at_level(logging.DEBUG): - with daemon: - assert daemon._daemon == tcp_server - assert daemon._callback_thread == thread - - assert daemon._daemon is None - assert daemon._callback_thread is None - tcp_server.shutdown.assert_called_with() - thread.join.assert_called_once_with() - - assert_message_in_log( - caplog.records, 'Waiting for background thread to finish.', - levelno=logging.DEBUG, module='http_hooks_deamon') - - def _tcp_patcher(self, tcp_server): - return mock.patch.object( - http_hooks_deamon, 'TCPServer', return_value=tcp_server) - - def _thread_patcher(self, thread): - return mock.patch.object( - http_hooks_deamon.threading, 'Thread', return_value=thread) - - class TestPrepareHooksDaemon(object): @pytest.mark.parametrize('protocol', ('celery',)) - def test_returns_celery_hooks_callback_daemon_when_celery_protocol_specified( - self, protocol): + def test_returns_celery_hooks_callback_daemon_when_celery_protocol_specified(self, protocol): with tempfile.NamedTemporaryFile(mode='w') as temp_file: - temp_file.write("[app:main]\ncelery.broker_url = redis://redis/0\n" - "celery.result_backend = redis://redis/0") + temp_file.write( + "[app:main]\n" + "celery.broker_url = redis://redis/0\n" + "celery.result_backend = redis://redis/0\n" + ) temp_file.flush() expected_extras = {'config': temp_file.name} - callback, extras = hook_base.prepare_callback_daemon( - expected_extras, protocol=protocol, host='') + callback, extras = hooks_utils.prepare_callback_daemon(expected_extras, protocol=protocol) assert isinstance(callback, celery_hooks_deamon.CeleryHooksCallbackDaemon) @pytest.mark.parametrize('protocol, expected_class', ( - ('http', http_hooks_deamon.HttpHooksCallbackDaemon), + ('celery', celery_hooks_deamon.CeleryHooksCallbackDaemon), )) - def test_returns_real_hooks_callback_daemon_when_protocol_is_specified( - self, protocol, expected_class): - expected_extras = { - 'extra1': 'value1', - 'txn_id': 'txnid2', - 'hooks_protocol': protocol.lower(), - 'task_backend': '', - 'task_queue': '', - 'repo_store': '/var/opt/rhodecode_repo_store', - 'repository': 'rhodecode', - } - from rhodecode import CONFIG - CONFIG['vcs.svn.redis_conn'] = 'redis://redis:6379/0' - callback, extras = hook_base.prepare_callback_daemon( - expected_extras.copy(), protocol=protocol, host='127.0.0.1', - txn_id='txnid2') - assert isinstance(callback, expected_class) - extras.pop('hooks_uri') - expected_extras['time'] = extras['time'] - assert extras == expected_extras + def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(self, protocol, expected_class): + + with tempfile.NamedTemporaryFile(mode='w') as temp_file: + temp_file.write( + "[app:main]\n" + "celery.broker_url = redis://redis:6379/0\n" + "celery.result_backend = redis://redis:6379/0\n" + ) + temp_file.flush() + + expected_extras = { + 'extra1': 'value1', + 'txn_id': 'txnid2', + 'hooks_protocol': protocol.lower(), + 'hooks_config': { + 'broker_url': 'redis://redis:6379/0', + 'result_backend': 'redis://redis:6379/0', + }, + 'repo_store': '/var/opt/rhodecode_repo_store', + 'repository': 'rhodecode', + 'config': temp_file.name + } + from rhodecode import CONFIG + CONFIG['vcs.svn.redis_conn'] = 'redis://redis:6379/0' + callback, extras = hooks_utils.prepare_callback_daemon(expected_extras.copy(), protocol=protocol,txn_id='txnid2') + assert isinstance(callback, expected_class) + expected_extras['time'] = extras['time'] + assert extras == expected_extras @pytest.mark.parametrize('protocol', ( 'invalid', @@ -330,35 +95,4 @@ class TestPrepareHooksDaemon(object): 'hooks_protocol': protocol.lower() } with pytest.raises(Exception): - callback, extras = hook_base.prepare_callback_daemon( - expected_extras.copy(), - protocol=protocol, host='127.0.0.1') - - -class MockRequest(object): - - def __init__(self, request): - self.request = request - self.input_stream = io.BytesIO(safe_bytes(self.request)) - self.output_stream = io.BytesIO() # make it un-closable for testing invesitagion - self.output_stream.close = lambda: None - - def makefile(self, mode, *args, **kwargs): - return self.output_stream if mode == 'wb' else self.input_stream - - -class MockServer(object): - - def __init__(self, handler_cls, request): - ip_port = ('0.0.0.0', 8888) - self.request = MockRequest(request) - self.server_address = ip_port - self.handler = handler_cls(self.request, ip_port, self) - - -@pytest.fixture() -def tcp_server(): - server = mock.Mock() - server.server_address = ('127.0.0.1', 8881) - server.wbufsize = 1024 - return server + callback, extras = hooks_utils.prepare_callback_daemon(expected_extras.copy(), protocol=protocol) diff --git a/rhodecode/tests/lib/test_utils.py b/rhodecode/tests/lib/test_utils.py index 41a48c40..6f68b38e 100644 --- a/rhodecode/tests/lib/test_utils.py +++ b/rhodecode/tests/lib/test_utils.py @@ -33,7 +33,7 @@ from rhodecode.model import meta from rhodecode.model.repo import RepoModel from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.settings import UiSetting, SettingsModel -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode_tools.lib.hash_utils import md5_safe from rhodecode.lib.ext_json import json @@ -403,12 +403,9 @@ class TestPrepareConfigData(object): self._assert_repo_name_passed(model_mock, repo_name) - expected_result = [ - ('section1', 'option1', 'value1'), - ('section2', 'option2', 'value2'), - ] - # We have extra config items returned, so we're ignoring two last items - assert result[:2] == expected_result + assert ('section1', 'option1', 'value1') in result + assert ('section2', 'option2', 'value2') in result + assert ('section3', 'option3', 'value3') not in result def _assert_repo_name_passed(self, model_mock, repo_name): assert model_mock.call_count == 1 diff --git a/rhodecode/tests/load/http_performance.py b/rhodecode/tests/load/http_performance.py index 505e37d4..ce64121b 100644 --- a/rhodecode/tests/load/http_performance.py +++ b/rhodecode/tests/load/http_performance.py @@ -25,7 +25,7 @@ It works by replaying a group of commits to the repo. import argparse import collections -import ConfigParser +import configparser import functools import itertools import os @@ -294,7 +294,7 @@ class HgMixin(object): def add_remote(self, repo, remote_url, remote_name='upstream'): self.remove_remote(repo, remote_name) os.chdir(repo) - hgrc = ConfigParser.RawConfigParser() + hgrc = configparser.RawConfigParser() hgrc.read('.hg/hgrc') hgrc.set('paths', remote_name, remote_url) with open('.hg/hgrc', 'w') as f: @@ -303,7 +303,7 @@ class HgMixin(object): @keep_cwd def remove_remote(self, repo, remote_name='upstream'): os.chdir(repo) - hgrc = ConfigParser.RawConfigParser() + hgrc = configparser.RawConfigParser() hgrc.read('.hg/hgrc') hgrc.remove_option('paths', remote_name) with open('.hg/hgrc', 'w') as f: diff --git a/rhodecode/tests/load/profile-mem.py b/rhodecode/tests/load/profile-mem.py index 087fc644..96778bd1 100644 --- a/rhodecode/tests/load/profile-mem.py +++ b/rhodecode/tests/load/profile-mem.py @@ -59,16 +59,6 @@ def parse_options(): parser.add_argument( '--interval', '-i', type=float, default=5, help="Interval in secods.") - parser.add_argument( - '--appenlight', '--ae', action='store_true') - parser.add_argument( - '--appenlight-url', '--ae-url', - default='https://ae.rhodecode.com/api/logs', - help='URL of the Appenlight API endpoint, defaults to "%(default)s".') - parser.add_argument( - '--appenlight-api-key', '--ae-key', - help='API key to use when sending data to appenlight. This has to be ' - 'set if Appenlight is enabled.') return parser.parse_args() diff --git a/rhodecode/tests/load/profile.py b/rhodecode/tests/load/profile.py index f45567af..5a6a1f5f 100644 --- a/rhodecode/tests/load/profile.py +++ b/rhodecode/tests/load/profile.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify diff --git a/rhodecode/tests/load/vcs_performance.py b/rhodecode/tests/load/vcs_performance.py index e5ab98a7..f4ca7294 100644 --- a/rhodecode/tests/load/vcs_performance.py +++ b/rhodecode/tests/load/vcs_performance.py @@ -1,5 +1,3 @@ - - # Copyright (C) 2016-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify diff --git a/rhodecode/tests/models/common.py b/rhodecode/tests/models/common.py index f41c4aab..0c98304e 100644 --- a/rhodecode/tests/models/common.py +++ b/rhodecode/tests/models/common.py @@ -22,7 +22,7 @@ from rhodecode.model.meta import Session from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.repo import RepoModel from rhodecode.model.user import UserModel -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture fixture = Fixture() diff --git a/rhodecode/tests/models/test_notifications.py b/rhodecode/tests/models/test_notifications.py index 3b5b8715..fee49a4f 100644 --- a/rhodecode/tests/models/test_notifications.py +++ b/rhodecode/tests/models/test_notifications.py @@ -19,7 +19,7 @@ import pytest -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.model.db import User, Notification, UserNotification from rhodecode.model.meta import Session diff --git a/rhodecode/tests/models/test_permissions.py b/rhodecode/tests/models/test_permissions.py index 2efb6475..8722a774 100644 --- a/rhodecode/tests/models/test_permissions.py +++ b/rhodecode/tests/models/test_permissions.py @@ -29,7 +29,7 @@ from rhodecode.model.repo import RepoModel from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.user import UserModel from rhodecode.model.user_group import UserGroupModel -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture fixture = Fixture() diff --git a/rhodecode/tests/models/test_pullrequest.py b/rhodecode/tests/models/test_pullrequest.py index 0df90b8c..9a09c098 100644 --- a/rhodecode/tests/models/test_pullrequest.py +++ b/rhodecode/tests/models/test_pullrequest.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -16,6 +15,7 @@ # 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 os import mock import pytest @@ -23,8 +23,7 @@ import textwrap import rhodecode from rhodecode.lib.vcs.backends import get_backend -from rhodecode.lib.vcs.backends.base import ( - MergeResponse, MergeFailureReason, Reference) +from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason, Reference from rhodecode.lib.vcs.exceptions import RepositoryError from rhodecode.lib.vcs.nodes import FileNode from rhodecode.model.comment import CommentsModel @@ -39,54 +38,42 @@ pytestmark = [ ] -@pytest.mark.usefixtures('config_stub') +@pytest.mark.usefixtures("config_stub") class TestPullRequestModel(object): - @pytest.fixture() def pull_request(self, request, backend, pr_util): """ A pull request combined with multiples patches. """ BackendClass = get_backend(backend.alias) - merge_resp = MergeResponse( - False, False, None, MergeFailureReason.UNKNOWN, - metadata={'exception': 'MockError'}) - self.merge_patcher = mock.patch.object( - BackendClass, 'merge', return_value=merge_resp) - self.workspace_remove_patcher = mock.patch.object( - BackendClass, 'cleanup_merge_workspace') + merge_resp = MergeResponse(False, False, None, MergeFailureReason.UNKNOWN, metadata={"exception": "MockError"}) + self.merge_patcher = mock.patch.object(BackendClass, "merge", return_value=merge_resp) + self.workspace_remove_patcher = mock.patch.object(BackendClass, "cleanup_merge_workspace") self.workspace_remove_mock = self.workspace_remove_patcher.start() self.merge_mock = self.merge_patcher.start() - self.comment_patcher = mock.patch( - 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status') + self.comment_patcher = mock.patch("rhodecode.model.changeset_status.ChangesetStatusModel.set_status") self.comment_patcher.start() - self.notification_patcher = mock.patch( - 'rhodecode.model.notification.NotificationModel.create') + self.notification_patcher = mock.patch("rhodecode.model.notification.NotificationModel.create") self.notification_patcher.start() - self.helper_patcher = mock.patch( - 'rhodecode.lib.helpers.route_path') + self.helper_patcher = mock.patch("rhodecode.lib.helpers.route_path") self.helper_patcher.start() - self.hook_patcher = mock.patch.object(PullRequestModel, - 'trigger_pull_request_hook') + self.hook_patcher = mock.patch.object(PullRequestModel, "trigger_pull_request_hook") self.hook_mock = self.hook_patcher.start() - self.invalidation_patcher = mock.patch( - 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation') + self.invalidation_patcher = mock.patch("rhodecode.model.pull_request.ScmModel.mark_for_invalidation") self.invalidation_mock = self.invalidation_patcher.start() - self.pull_request = pr_util.create_pull_request( - mergeable=True, name_suffix=u'ąć') + self.pull_request = pr_util.create_pull_request(mergeable=True, name_suffix="ąć") self.source_commit = self.pull_request.source_ref_parts.commit_id self.target_commit = self.pull_request.target_ref_parts.commit_id - self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id + self.workspace_id = f"pr-{self.pull_request.pull_request_id}" self.repo_id = self.pull_request.target_repo.repo_id @request.addfinalizer def cleanup_pull_request(): - calls = [mock.call( - self.pull_request, self.pull_request.author, 'create')] + calls = [mock.call(self.pull_request, self.pull_request.author, "create")] self.hook_mock.assert_has_calls(calls) self.workspace_remove_patcher.stop() @@ -114,29 +101,30 @@ class TestPullRequestModel(object): assert len(prs) == 1 def test_count_awaiting_review(self, pull_request): - pr_count = PullRequestModel().count_awaiting_review( - pull_request.target_repo) + pr_count = PullRequestModel().count_awaiting_review(pull_request.target_repo) assert pr_count == 1 def test_get_awaiting_my_review(self, pull_request): PullRequestModel().update_reviewers( - pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])], - pull_request.author) + pull_request, [(pull_request.author, ["author"], False, "reviewer", [])], pull_request.author + ) Session().commit() prs = PullRequestModel().get_awaiting_my_review( - pull_request.target_repo.repo_name, user_id=pull_request.author.user_id) + pull_request.target_repo.repo_name, user_id=pull_request.author.user_id + ) assert isinstance(prs, list) assert len(prs) == 1 def test_count_awaiting_my_review(self, pull_request): PullRequestModel().update_reviewers( - pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])], - pull_request.author) + pull_request, [(pull_request.author, ["author"], False, "reviewer", [])], pull_request.author + ) Session().commit() pr_count = PullRequestModel().count_awaiting_my_review( - pull_request.target_repo.repo_name, user_id=pull_request.author.user_id) + pull_request.target_repo.repo_name, user_id=pull_request.author.user_id + ) assert pr_count == 1 def test_delete_calls_cleanup_merge(self, pull_request): @@ -144,24 +132,19 @@ class TestPullRequestModel(object): PullRequestModel().delete(pull_request, pull_request.author) Session().commit() - self.workspace_remove_mock.assert_called_once_with( - repo_id, self.workspace_id) + self.workspace_remove_mock.assert_called_once_with(repo_id, self.workspace_id) def test_close_calls_cleanup_and_hook(self, pull_request): - PullRequestModel().close_pull_request( - pull_request, pull_request.author) + PullRequestModel().close_pull_request(pull_request, pull_request.author) Session().commit() repo_id = pull_request.target_repo.repo_id - self.workspace_remove_mock.assert_called_once_with( - repo_id, self.workspace_id) - self.hook_mock.assert_called_with( - self.pull_request, self.pull_request.author, 'close') + self.workspace_remove_mock.assert_called_once_with(repo_id, self.workspace_id) + self.hook_mock.assert_called_with(self.pull_request, self.pull_request.author, "close") def test_merge_status(self, pull_request): - self.merge_mock.return_value = MergeResponse( - True, False, None, MergeFailureReason.NONE) + self.merge_mock.return_value = MergeResponse(True, False, None, MergeFailureReason.NONE) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None @@ -169,13 +152,17 @@ class TestPullRequestModel(object): merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is True - assert msg == 'This pull request can be automatically merged.' + assert msg == "This pull request can be automatically merged." self.merge_mock.assert_called_with( - self.repo_id, self.workspace_id, + self.repo_id, + self.workspace_id, pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), - pull_request.source_ref_parts, dry_run=True, - use_rebase=False, close_branch=False) + pull_request.source_ref_parts, + dry_run=True, + use_rebase=False, + close_branch=False, + ) assert pull_request._last_merge_source_rev == self.source_commit assert pull_request._last_merge_target_rev == self.target_commit @@ -184,13 +171,13 @@ class TestPullRequestModel(object): self.merge_mock.reset_mock() merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is True - assert msg == 'This pull request can be automatically merged.' + assert msg == "This pull request can be automatically merged." assert self.merge_mock.called is False def test_merge_status_known_failure(self, pull_request): self.merge_mock.return_value = MergeResponse( - False, False, None, MergeFailureReason.MERGE_FAILED, - metadata={'unresolved_files': 'file1'}) + False, False, None, MergeFailureReason.MERGE_FAILED, metadata={"unresolved_files": "file1"} + ) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None @@ -198,13 +185,17 @@ class TestPullRequestModel(object): merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is False - assert msg == 'This pull request cannot be merged because of merge conflicts. file1' + assert msg == "This pull request cannot be merged because of merge conflicts. file1" self.merge_mock.assert_called_with( - self.repo_id, self.workspace_id, + self.repo_id, + self.workspace_id, pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), - pull_request.source_ref_parts, dry_run=True, - use_rebase=False, close_branch=False) + pull_request.source_ref_parts, + dry_run=True, + use_rebase=False, + close_branch=False, + ) assert pull_request._last_merge_source_rev == self.source_commit assert pull_request._last_merge_target_rev == self.target_commit @@ -213,13 +204,13 @@ class TestPullRequestModel(object): self.merge_mock.reset_mock() merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is False - assert msg == 'This pull request cannot be merged because of merge conflicts. file1' + assert msg == "This pull request cannot be merged because of merge conflicts. file1" assert self.merge_mock.called is False def test_merge_status_unknown_failure(self, pull_request): self.merge_mock.return_value = MergeResponse( - False, False, None, MergeFailureReason.UNKNOWN, - metadata={'exception': 'MockError'}) + False, False, None, MergeFailureReason.UNKNOWN, metadata={"exception": "MockError"} + ) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None @@ -227,15 +218,17 @@ class TestPullRequestModel(object): merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is False - assert msg == ( - 'This pull request cannot be merged because of an unhandled exception. ' - 'MockError') + assert msg == "This pull request cannot be merged because of an unhandled exception. MockError" self.merge_mock.assert_called_with( - self.repo_id, self.workspace_id, + self.repo_id, + self.workspace_id, pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), - pull_request.source_ref_parts, dry_run=True, - use_rebase=False, close_branch=False) + pull_request.source_ref_parts, + dry_run=True, + use_rebase=False, + close_branch=False, + ) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None @@ -244,155 +237,136 @@ class TestPullRequestModel(object): self.merge_mock.reset_mock() merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is False - assert msg == ( - 'This pull request cannot be merged because of an unhandled exception. ' - 'MockError') + assert msg == "This pull request cannot be merged because of an unhandled exception. MockError" assert self.merge_mock.called is True def test_merge_status_when_target_is_locked(self, pull_request): - pull_request.target_repo.locked = [1, u'12345.50', 'lock_web'] + pull_request.target_repo.locked = [1, "12345.50", "lock_web"] merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is False - assert msg == ( - 'This pull request cannot be merged because the target repository ' - 'is locked by user:1.') + assert msg == "This pull request cannot be merged because the target repository is locked by user:1." def test_merge_status_requirements_check_target(self, pull_request): - def has_largefiles(self, repo): return repo == pull_request.source_repo - patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles) + patcher = mock.patch.object(PullRequestModel, "_has_largefiles", has_largefiles) with patcher: merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is False - assert msg == 'Target repository large files support is disabled.' + assert msg == "Target repository large files support is disabled." def test_merge_status_requirements_check_source(self, pull_request): - def has_largefiles(self, repo): return repo == pull_request.target_repo - patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles) + patcher = mock.patch.object(PullRequestModel, "_has_largefiles", has_largefiles) with patcher: merge_response, status, msg = PullRequestModel().merge_status(pull_request) assert status is False - assert msg == 'Source repository large files support is disabled.' + assert msg == "Source repository large files support is disabled." def test_merge(self, pull_request, merge_extras): user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) - merge_ref = Reference( - 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6') - self.merge_mock.return_value = MergeResponse( - True, True, merge_ref, MergeFailureReason.NONE) + merge_ref = Reference("type", "name", "6126b7bfcc82ad2d3deaee22af926b082ce54cc6") + self.merge_mock.return_value = MergeResponse(True, True, merge_ref, MergeFailureReason.NONE) - merge_extras['repository'] = pull_request.target_repo.repo_name - PullRequestModel().merge_repo( - pull_request, pull_request.author, extras=merge_extras) + merge_extras["repository"] = pull_request.target_repo.repo_name + PullRequestModel().merge_repo(pull_request, pull_request.author, extras=merge_extras) Session().commit() - message = ( - u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}' - u'\n\n {pr_title}'.format( - pr_id=pull_request.pull_request_id, - source_repo=safe_str( - pull_request.source_repo.scm_instance().name), - source_ref_name=pull_request.source_ref_parts.name, - pr_title=safe_str(pull_request.title) - ) + message = "Merge pull request !{pr_id} from {source_repo} {source_ref_name}" "\n\n {pr_title}".format( + pr_id=pull_request.pull_request_id, + source_repo=safe_str(pull_request.source_repo.scm_instance().name), + source_ref_name=pull_request.source_ref_parts.name, + pr_title=safe_str(pull_request.title), ) self.merge_mock.assert_called_with( - self.repo_id, self.workspace_id, + self.repo_id, + self.workspace_id, pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), pull_request.source_ref_parts, - user_name=user.short_contact, user_email=user.email, message=message, - use_rebase=False, close_branch=False + user_name=user.short_contact, + user_email=user.email, + message=message, + use_rebase=False, + close_branch=False, ) - self.invalidation_mock.assert_called_once_with( - pull_request.target_repo.repo_name) + self.invalidation_mock.assert_called_once_with(pull_request.target_repo.repo_name) - self.hook_mock.assert_called_with( - self.pull_request, self.pull_request.author, 'merge') + self.hook_mock.assert_called_with(self.pull_request, self.pull_request.author, "merge") pull_request = PullRequest.get(pull_request.pull_request_id) - assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6' + assert pull_request.merge_rev == "6126b7bfcc82ad2d3deaee22af926b082ce54cc6" def test_merge_with_status_lock(self, pull_request, merge_extras): user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) - merge_ref = Reference( - 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6') - self.merge_mock.return_value = MergeResponse( - True, True, merge_ref, MergeFailureReason.NONE) + merge_ref = Reference("type", "name", "6126b7bfcc82ad2d3deaee22af926b082ce54cc6") + self.merge_mock.return_value = MergeResponse(True, True, merge_ref, MergeFailureReason.NONE) - merge_extras['repository'] = pull_request.target_repo.repo_name + merge_extras["repository"] = pull_request.target_repo.repo_name with pull_request.set_state(PullRequest.STATE_UPDATING): assert pull_request.pull_request_state == PullRequest.STATE_UPDATING - PullRequestModel().merge_repo( - pull_request, pull_request.author, extras=merge_extras) + PullRequestModel().merge_repo(pull_request, pull_request.author, extras=merge_extras) Session().commit() assert pull_request.pull_request_state == PullRequest.STATE_CREATED - message = ( - u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}' - u'\n\n {pr_title}'.format( - pr_id=pull_request.pull_request_id, - source_repo=safe_str( - pull_request.source_repo.scm_instance().name), - source_ref_name=pull_request.source_ref_parts.name, - pr_title=safe_str(pull_request.title) - ) + message = "Merge pull request !{pr_id} from {source_repo} {source_ref_name}" "\n\n {pr_title}".format( + pr_id=pull_request.pull_request_id, + source_repo=safe_str(pull_request.source_repo.scm_instance().name), + source_ref_name=pull_request.source_ref_parts.name, + pr_title=safe_str(pull_request.title), ) self.merge_mock.assert_called_with( - self.repo_id, self.workspace_id, + self.repo_id, + self.workspace_id, pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), pull_request.source_ref_parts, - user_name=user.short_contact, user_email=user.email, message=message, - use_rebase=False, close_branch=False + user_name=user.short_contact, + user_email=user.email, + message=message, + use_rebase=False, + close_branch=False, ) - self.invalidation_mock.assert_called_once_with( - pull_request.target_repo.repo_name) + self.invalidation_mock.assert_called_once_with(pull_request.target_repo.repo_name) - self.hook_mock.assert_called_with( - self.pull_request, self.pull_request.author, 'merge') + self.hook_mock.assert_called_with(self.pull_request, self.pull_request.author, "merge") pull_request = PullRequest.get(pull_request.pull_request_id) - assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6' + assert pull_request.merge_rev == "6126b7bfcc82ad2d3deaee22af926b082ce54cc6" def test_merge_failed(self, pull_request, merge_extras): user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) - merge_ref = Reference( - 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6') - self.merge_mock.return_value = MergeResponse( - False, False, merge_ref, MergeFailureReason.MERGE_FAILED) + merge_ref = Reference("type", "name", "6126b7bfcc82ad2d3deaee22af926b082ce54cc6") + self.merge_mock.return_value = MergeResponse(False, False, merge_ref, MergeFailureReason.MERGE_FAILED) - merge_extras['repository'] = pull_request.target_repo.repo_name - PullRequestModel().merge_repo( - pull_request, pull_request.author, extras=merge_extras) + merge_extras["repository"] = pull_request.target_repo.repo_name + PullRequestModel().merge_repo(pull_request, pull_request.author, extras=merge_extras) Session().commit() - message = ( - u'Merge pull request !{pr_id} from {source_repo} {source_ref_name}' - u'\n\n {pr_title}'.format( - pr_id=pull_request.pull_request_id, - source_repo=safe_str( - pull_request.source_repo.scm_instance().name), - source_ref_name=pull_request.source_ref_parts.name, - pr_title=safe_str(pull_request.title) - ) + message = "Merge pull request !{pr_id} from {source_repo} {source_ref_name}" "\n\n {pr_title}".format( + pr_id=pull_request.pull_request_id, + source_repo=safe_str(pull_request.source_repo.scm_instance().name), + source_ref_name=pull_request.source_ref_parts.name, + pr_title=safe_str(pull_request.title), ) self.merge_mock.assert_called_with( - self.repo_id, self.workspace_id, + self.repo_id, + self.workspace_id, pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), pull_request.source_ref_parts, - user_name=user.short_contact, user_email=user.email, message=message, - use_rebase=False, close_branch=False + user_name=user.short_contact, + user_email=user.email, + message=message, + use_rebase=False, + close_branch=False, ) pull_request = PullRequest.get(pull_request.pull_request_id) @@ -410,7 +384,7 @@ class TestPullRequestModel(object): assert commit_ids == pull_request.revisions # Merge revision is not in the revisions list - pull_request.merge_rev = 'f000' * 10 + pull_request.merge_rev = "f000" * 10 commit_ids = PullRequestModel()._get_commit_ids(pull_request) assert commit_ids == pull_request.revisions + [pull_request.merge_rev] @@ -419,147 +393,126 @@ class TestPullRequestModel(object): source_ref_id = pull_request.source_ref_parts.commit_id target_ref_id = pull_request.target_ref_parts.commit_id diff = PullRequestModel()._get_diff_from_pr_or_version( - source_repo, source_ref_id, target_ref_id, - hide_whitespace_changes=False, diff_context=6) - assert b'file_1' in diff.raw.tobytes() + source_repo, source_ref_id, target_ref_id, hide_whitespace_changes=False, diff_context=6 + ) + assert b"file_1" in diff.raw.tobytes() def test_generate_title_returns_unicode(self): title = PullRequestModel().generate_pullrequest_title( - source='source-dummy', - source_ref='source-ref-dummy', - target='target-dummy', + source="source-dummy", + source_ref="source-ref-dummy", + target="target-dummy", ) assert type(title) == str - @pytest.mark.parametrize('title, has_wip', [ - ('hello', False), - ('hello wip', False), - ('hello wip: xxx', False), - ('[wip] hello', True), - ('[wip] hello', True), - ('wip: hello', True), - ('wip hello', True), - - ]) + @pytest.mark.parametrize( + "title, has_wip", + [ + ("hello", False), + ("hello wip", False), + ("hello wip: xxx", False), + ("[wip] hello", True), + ("[wip] hello", True), + ("wip: hello", True), + ("wip hello", True), + ], + ) def test_wip_title_marker(self, pull_request, title, has_wip): pull_request.title = title assert pull_request.work_in_progress == has_wip -@pytest.mark.usefixtures('config_stub') +@pytest.mark.usefixtures("config_stub") class TestIntegrationMerge(object): - @pytest.mark.parametrize('extra_config', ( - {'vcs.hooks.protocol.v2': 'celery', 'vcs.hooks.direct_calls': False}, - )) - def test_merge_triggers_push_hooks( - self, pr_util, user_admin, capture_rcextensions, merge_extras, - extra_config): - pull_request = pr_util.create_pull_request( - approved=True, mergeable=True) - # TODO: johbo: Needed for sqlite, try to find an automatic way for it - merge_extras['repository'] = pull_request.target_repo.repo_name - Session().commit() - - with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False): - merge_state = PullRequestModel().merge_repo( - pull_request, user_admin, extras=merge_extras) - Session().commit() - - assert merge_state.executed - assert '_pre_push_hook' in capture_rcextensions - assert '_push_hook' in capture_rcextensions - - def test_merge_can_be_rejected_by_pre_push_hook( - self, pr_util, user_admin, capture_rcextensions, merge_extras): - pull_request = pr_util.create_pull_request( - approved=True, mergeable=True) - # TODO: johbo: Needed for sqlite, try to find an automatic way for it - merge_extras['repository'] = pull_request.target_repo.repo_name - Session().commit() - - with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull: - pre_pull.side_effect = RepositoryError("Disallow push!") - merge_status = PullRequestModel().merge_repo( - pull_request, user_admin, extras=merge_extras) - Session().commit() - - assert not merge_status.executed - assert 'pre_push' not in capture_rcextensions - assert 'post_push' not in capture_rcextensions - - def test_merge_fails_if_target_is_locked( - self, pr_util, user_regular, merge_extras): - pull_request = pr_util.create_pull_request( - approved=True, mergeable=True) - locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web'] + def test_merge_fails_if_target_is_locked(self, pr_util, user_regular, merge_extras): + pull_request = pr_util.create_pull_request(approved=True, mergeable=True) + locked_by = [user_regular.user_id + 1, 12345.50, "lock_web"] pull_request.target_repo.locked = locked_by # TODO: johbo: Check if this can work based on the database, currently # all data is pre-computed, that's why just updating the DB is not # enough. - merge_extras['locked_by'] = locked_by - merge_extras['repository'] = pull_request.target_repo.repo_name + merge_extras["locked_by"] = locked_by + merge_extras["repository"] = pull_request.target_repo.repo_name # TODO: johbo: Needed for sqlite, try to find an automatic way for it Session().commit() - merge_status = PullRequestModel().merge_repo( - pull_request, user_regular, extras=merge_extras) + merge_status = PullRequestModel().merge_repo(pull_request, user_regular, extras=merge_extras) Session().commit() assert not merge_status.executed -@pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [ - (False, 1, 0), - (True, 0, 1), -]) -def test_outdated_comments( - pr_util, use_outdated, inlines_count, outdated_count, config_stub): +@pytest.mark.parametrize( + "use_outdated, inlines_count, outdated_count", + [ + (False, 1, 0), + (True, 0, 1), + ], +) +def test_outdated_comments(pr_util, use_outdated, inlines_count, outdated_count, config_stub): pull_request = pr_util.create_pull_request() - pr_util.create_inline_comment(file_path='not_in_updated_diff') + pr_util.create_inline_comment(file_path="not_in_updated_diff") with outdated_comments_patcher(use_outdated) as outdated_comment_mock: pr_util.add_one_commit() - assert_inline_comments( - pull_request, visible=inlines_count, outdated=outdated_count) + assert_inline_comments(pull_request, visible=inlines_count, outdated=outdated_count) outdated_comment_mock.assert_called_with(pull_request) -@pytest.mark.parametrize('mr_type, expected_msg', [ - (MergeFailureReason.NONE, - 'This pull request can be automatically merged.'), - (MergeFailureReason.UNKNOWN, - 'This pull request cannot be merged because of an unhandled exception. CRASH'), - (MergeFailureReason.MERGE_FAILED, - 'This pull request cannot be merged because of merge conflicts. CONFLICT_FILE'), - (MergeFailureReason.PUSH_FAILED, - 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'), - (MergeFailureReason.TARGET_IS_NOT_HEAD, - 'This pull request cannot be merged because the target `ref_name` is not a head.'), - (MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES, - 'This pull request cannot be merged because the source contains more branches than the target.'), - (MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS, - 'This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.'), - (MergeFailureReason.TARGET_IS_LOCKED, - 'This pull request cannot be merged because the target repository is locked by user:123.'), - (MergeFailureReason.MISSING_TARGET_REF, - 'This pull request cannot be merged because the target reference `ref_name` is missing.'), - (MergeFailureReason.MISSING_SOURCE_REF, - 'This pull request cannot be merged because the source reference `ref_name` is missing.'), - (MergeFailureReason.SUBREPO_MERGE_FAILED, - 'This pull request cannot be merged because of conflicts related to sub repositories.'), - -]) +@pytest.mark.parametrize( + "mr_type, expected_msg", + [ + (MergeFailureReason.NONE, "This pull request can be automatically merged."), + (MergeFailureReason.UNKNOWN, "This pull request cannot be merged because of an unhandled exception. CRASH"), + ( + MergeFailureReason.MERGE_FAILED, + "This pull request cannot be merged because of merge conflicts. CONFLICT_FILE", + ), + ( + MergeFailureReason.PUSH_FAILED, + "This pull request could not be merged because push to target:`some-repo@merge_commit` failed.", + ), + ( + MergeFailureReason.TARGET_IS_NOT_HEAD, + "This pull request cannot be merged because the target `ref_name` is not a head.", + ), + ( + MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES, + "This pull request cannot be merged because the source contains more branches than the target.", + ), + ( + MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS, + "This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.", + ), + ( + MergeFailureReason.TARGET_IS_LOCKED, + "This pull request cannot be merged because the target repository is locked by user:123.", + ), + ( + MergeFailureReason.MISSING_TARGET_REF, + "This pull request cannot be merged because the target reference `ref_name` is missing.", + ), + ( + MergeFailureReason.MISSING_SOURCE_REF, + "This pull request cannot be merged because the source reference `ref_name` is missing.", + ), + ( + MergeFailureReason.SUBREPO_MERGE_FAILED, + "This pull request cannot be merged because of conflicts related to sub repositories.", + ), + ], +) def test_merge_response_message(mr_type, expected_msg): - merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6') + merge_ref = Reference("type", "ref_name", "6126b7bfcc82ad2d3deaee22af926b082ce54cc6") metadata = { - 'unresolved_files': 'CONFLICT_FILE', - 'exception': "CRASH", - 'target': 'some-repo', - 'merge_commit': 'merge_commit', - 'target_ref': merge_ref, - 'source_ref': merge_ref, - 'heads': ','.join(['a', 'b', 'c']), - 'locked_by': 'user:123' + "unresolved_files": "CONFLICT_FILE", + "exception": "CRASH", + "target": "some-repo", + "merge_commit": "merge_commit", + "target_ref": merge_ref, + "source_ref": merge_ref, + "heads": ",".join(["a", "b", "c"]), + "locked_by": "user:123", } merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata) @@ -573,30 +526,28 @@ def merge_extras(request, user_regular): """ extras = { - 'ip': '127.0.0.1', - 'username': user_regular.username, - 'user_id': user_regular.user_id, - 'action': 'push', - 'repository': 'fake_target_repo_name', - 'scm': 'git', - 'config': request.config.getini('pyramid_config'), - 'repo_store': '', - 'make_lock': None, - 'locked_by': [None, None, None], - 'server_url': 'http://test.example.com:5000', - 'hooks': ['push', 'pull'], - 'is_shadow_repo': False, + "ip": "127.0.0.1", + "username": user_regular.username, + "user_id": user_regular.user_id, + "action": "push", + "repository": "fake_target_repo_name", + "scm": "git", + "config": request.config.getini("pyramid_config"), + "repo_store": "", + "make_lock": None, + "locked_by": [None, None, None], + "server_url": "http://test.example.com:5000", + "hooks": ["push", "pull"], + "is_shadow_repo": False, } return extras -@pytest.mark.usefixtures('config_stub') +@pytest.mark.usefixtures("config_stub") class TestUpdateCommentHandling(object): - - @pytest.fixture(autouse=True, scope='class') + @pytest.fixture(autouse=True, scope="class") def enable_outdated_comments(self, request, baseapp): - config_patch = mock.patch.dict( - 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True}) + config_patch = mock.patch.dict("rhodecode.CONFIG", {"rhodecode_use_outdated_comments": True}) config_patch.start() @request.addfinalizer @@ -605,206 +556,194 @@ class TestUpdateCommentHandling(object): def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util): commits = [ - {'message': 'a'}, - {'message': 'b', 'added': [FileNode(b'file_b', b'test_content\n')]}, - {'message': 'c', 'added': [FileNode(b'file_c', b'test_content\n')]}, + {"message": "a"}, + {"message": "b", "added": [FileNode(b"file_b", b"test_content\n")]}, + {"message": "c", "added": [FileNode(b"file_c", b"test_content\n")]}, ] - pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b']) - pr_util.create_inline_comment(file_path='file_b') - pr_util.add_one_commit(head='c') + pull_request = pr_util.create_pull_request(commits=commits, target_head="a", source_head="b", revisions=["b"]) + pr_util.create_inline_comment(file_path="file_b") + pr_util.add_one_commit(head="c") assert_inline_comments(pull_request, visible=1, outdated=0) def test_comment_stays_unflagged_on_change_above(self, pr_util): - original_content = b''.join((b'line %d\n' % x for x in range(1, 11))) - updated_content = b'new_line_at_top\n' + original_content + original_content = b"".join((b"line %d\n" % x for x in range(1, 11))) + updated_content = b"new_line_at_top\n" + original_content commits = [ - {'message': 'a'}, - {'message': 'b', 'added': [FileNode(b'file_b', original_content)]}, - {'message': 'c', 'changed': [FileNode(b'file_b', updated_content)]}, + {"message": "a"}, + {"message": "b", "added": [FileNode(b"file_b", original_content)]}, + {"message": "c", "changed": [FileNode(b"file_b", updated_content)]}, ] - pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b']) + pull_request = pr_util.create_pull_request(commits=commits, target_head="a", source_head="b", revisions=["b"]) with outdated_comments_patcher(): - comment = pr_util.create_inline_comment( - line_no=u'n8', file_path='file_b') - pr_util.add_one_commit(head='c') + comment = pr_util.create_inline_comment(line_no="n8", file_path="file_b") + pr_util.add_one_commit(head="c") assert_inline_comments(pull_request, visible=1, outdated=0) - assert comment.line_no == u'n9' + assert comment.line_no == "n9" def test_comment_stays_unflagged_on_change_below(self, pr_util): - original_content = b''.join([b'line %d\n' % x for x in range(10)]) - updated_content = original_content + b'new_line_at_end\n' + original_content = b"".join([b"line %d\n" % x for x in range(10)]) + updated_content = original_content + b"new_line_at_end\n" commits = [ - {'message': 'a'}, - {'message': 'b', 'added': [FileNode(b'file_b', original_content)]}, - {'message': 'c', 'changed': [FileNode(b'file_b', updated_content)]}, + {"message": "a"}, + {"message": "b", "added": [FileNode(b"file_b", original_content)]}, + {"message": "c", "changed": [FileNode(b"file_b", updated_content)]}, ] - pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b']) - pr_util.create_inline_comment(file_path='file_b') - pr_util.add_one_commit(head='c') + pull_request = pr_util.create_pull_request(commits=commits, target_head="a", source_head="b", revisions=["b"]) + pr_util.create_inline_comment(file_path="file_b") + pr_util.add_one_commit(head="c") assert_inline_comments(pull_request, visible=1, outdated=0) - @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9']) + @pytest.mark.parametrize("line_no", ["n4", "o4", "n10", "o9"]) def test_comment_flagged_on_change_around_context(self, pr_util, line_no): - base_lines = [b'line %d\n' % x for x in range(1, 13)] + base_lines = [b"line %d\n" % x for x in range(1, 13)] change_lines = list(base_lines) - change_lines.insert(6, b'line 6a added\n') + change_lines.insert(6, b"line 6a added\n") # Changes on the last line of sight update_lines = list(change_lines) - update_lines[0] = b'line 1 changed\n' - update_lines[-1] = b'line 12 changed\n' + update_lines[0] = b"line 1 changed\n" + update_lines[-1] = b"line 12 changed\n" def file_b(lines): - return FileNode(b'file_b', b''.join(lines)) + return FileNode(b"file_b", b"".join(lines)) commits = [ - {'message': 'a', 'added': [file_b(base_lines)]}, - {'message': 'b', 'changed': [file_b(change_lines)]}, - {'message': 'c', 'changed': [file_b(update_lines)]}, + {"message": "a", "added": [file_b(base_lines)]}, + {"message": "b", "changed": [file_b(change_lines)]}, + {"message": "c", "changed": [file_b(update_lines)]}, ] - pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b']) - pr_util.create_inline_comment(line_no=line_no, file_path='file_b') + pull_request = pr_util.create_pull_request(commits=commits, target_head="a", source_head="b", revisions=["b"]) + pr_util.create_inline_comment(line_no=line_no, file_path="file_b") with outdated_comments_patcher(): - pr_util.add_one_commit(head='c') + pr_util.add_one_commit(head="c") assert_inline_comments(pull_request, visible=0, outdated=1) - @pytest.mark.parametrize("change, content", [ - ('changed', b'changed\n'), - ('removed', b''), - ], ids=['changed', b'removed']) + @pytest.mark.parametrize( + "change, content", + [ + ("changed", b"changed\n"), + ("removed", b""), + ], + ids=["changed", b"removed"], + ) def test_comment_flagged_on_change(self, pr_util, change, content): commits = [ - {'message': 'a'}, - {'message': 'b', 'added': [FileNode(b'file_b', b'test_content\n')]}, - {'message': 'c', change: [FileNode(b'file_b', content)]}, + {"message": "a"}, + {"message": "b", "added": [FileNode(b"file_b", b"test_content\n")]}, + {"message": "c", change: [FileNode(b"file_b", content)]}, ] - pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b']) - pr_util.create_inline_comment(file_path='file_b') + pull_request = pr_util.create_pull_request(commits=commits, target_head="a", source_head="b", revisions=["b"]) + pr_util.create_inline_comment(file_path="file_b") with outdated_comments_patcher(): - pr_util.add_one_commit(head='c') + pr_util.add_one_commit(head="c") assert_inline_comments(pull_request, visible=0, outdated=1) -@pytest.mark.usefixtures('config_stub') +@pytest.mark.usefixtures("config_stub") class TestUpdateChangedFiles(object): - def test_no_changes_on_unchanged_diff(self, pr_util): commits = [ - {'message': 'a'}, - {'message': 'b', - 'added': [FileNode(b'file_b', b'test_content b\n')]}, - {'message': 'c', - 'added': [FileNode(b'file_c', b'test_content c\n')]}, + {"message": "a"}, + {"message": "b", "added": [FileNode(b"file_b", b"test_content b\n")]}, + {"message": "c", "added": [FileNode(b"file_c", b"test_content c\n")]}, ] # open a PR from a to b, adding file_b pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b'], - name_suffix='per-file-review') + commits=commits, target_head="a", source_head="b", revisions=["b"], name_suffix="per-file-review" + ) # modify PR adding new file file_c - pr_util.add_one_commit(head='c') + pr_util.add_one_commit(head="c") - assert_pr_file_changes( - pull_request, - added=['file_c'], - modified=[], - removed=[]) + assert_pr_file_changes(pull_request, added=["file_c"], modified=[], removed=[]) def test_modify_and_undo_modification_diff(self, pr_util): commits = [ - {'message': 'a'}, - {'message': 'b', - 'added': [FileNode(b'file_b', b'test_content b\n')]}, - {'message': 'c', - 'changed': [FileNode(b'file_b', b'test_content b modified\n')]}, - {'message': 'd', - 'changed': [FileNode(b'file_b', b'test_content b\n')]}, + {"message": "a"}, + {"message": "b", "added": [FileNode(b"file_b", b"test_content b\n")]}, + {"message": "c", "changed": [FileNode(b"file_b", b"test_content b modified\n")]}, + {"message": "d", "changed": [FileNode(b"file_b", b"test_content b\n")]}, ] # open a PR from a to b, adding file_b pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b'], - name_suffix='per-file-review') + commits=commits, target_head="a", source_head="b", revisions=["b"], name_suffix="per-file-review" + ) # modify PR modifying file file_b - pr_util.add_one_commit(head='c') + pr_util.add_one_commit(head="c") - assert_pr_file_changes( - pull_request, - added=[], - modified=['file_b'], - removed=[]) + assert_pr_file_changes(pull_request, added=[], modified=["file_b"], removed=[]) # move the head again to d, which rollbacks change, # meaning we should indicate no changes - pr_util.add_one_commit(head='d') + pr_util.add_one_commit(head="d") - assert_pr_file_changes( - pull_request, - added=[], - modified=[], - removed=[]) + assert_pr_file_changes(pull_request, added=[], modified=[], removed=[]) def test_updated_all_files_in_pr(self, pr_util): commits = [ - {'message': 'a'}, - {'message': 'b', 'added': [ - FileNode(b'file_a', b'test_content a\n'), - FileNode(b'file_b', b'test_content b\n'), - FileNode(b'file_c', b'test_content c\n')]}, - {'message': 'c', 'changed': [ - FileNode(b'file_a', b'test_content a changed\n'), - FileNode(b'file_b', b'test_content b changed\n'), - FileNode(b'file_c', b'test_content c changed\n')]}, + {"message": "a"}, + { + "message": "b", + "added": [ + FileNode(b"file_a", b"test_content a\n"), + FileNode(b"file_b", b"test_content b\n"), + FileNode(b"file_c", b"test_content c\n"), + ], + }, + { + "message": "c", + "changed": [ + FileNode(b"file_a", b"test_content a changed\n"), + FileNode(b"file_b", b"test_content b changed\n"), + FileNode(b"file_c", b"test_content c changed\n"), + ], + }, ] # open a PR from a to b, changing 3 files pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b'], - name_suffix='per-file-review') + commits=commits, target_head="a", source_head="b", revisions=["b"], name_suffix="per-file-review" + ) - pr_util.add_one_commit(head='c') + pr_util.add_one_commit(head="c") - assert_pr_file_changes( - pull_request, - added=[], - modified=['file_a', 'file_b', 'file_c'], - removed=[]) + assert_pr_file_changes(pull_request, added=[], modified=["file_a", "file_b", "file_c"], removed=[]) def test_updated_and_removed_all_files_in_pr(self, pr_util): commits = [ - {'message': 'a'}, - {'message': 'b', 'added': [ - FileNode(b'file_a', b'test_content a\n'), - FileNode(b'file_b', b'test_content b\n'), - FileNode(b'file_c', b'test_content c\n')]}, - {'message': 'c', 'removed': [ - FileNode(b'file_a', b'test_content a changed\n'), - FileNode(b'file_b', b'test_content b changed\n'), - FileNode(b'file_c', b'test_content c changed\n')]}, + {"message": "a"}, + { + "message": "b", + "added": [ + FileNode(b"file_a", b"test_content a\n"), + FileNode(b"file_b", b"test_content b\n"), + FileNode(b"file_c", b"test_content c\n"), + ], + }, + { + "message": "c", + "removed": [ + FileNode(b"file_a", b"test_content a changed\n"), + FileNode(b"file_b", b"test_content b changed\n"), + FileNode(b"file_c", b"test_content c changed\n"), + ], + }, ] # open a PR from a to b, removing 3 files pull_request = pr_util.create_pull_request( - commits=commits, target_head='a', source_head='b', revisions=['b'], - name_suffix='per-file-review') + commits=commits, target_head="a", source_head="b", revisions=["b"], name_suffix="per-file-review" + ) - pr_util.add_one_commit(head='c') + pr_util.add_one_commit(head="c") - assert_pr_file_changes( - pull_request, - added=[], - modified=[], - removed=['file_a', 'file_b', 'file_c']) + assert_pr_file_changes(pull_request, added=[], modified=[], removed=["file_a", "file_b", "file_c"]) def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub): @@ -866,8 +805,7 @@ def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, con .. |under_review| replace:: *"Under Review"*""" ).format(commit_id[:12]) - pull_request_comments = sorted( - pull_request.comments, key=lambda c: c.modified_at) + pull_request_comments = sorted(pull_request.comments, key=lambda c: c.modified_at) update_comment = pull_request_comments[-1] assert update_comment.text == expected_message @@ -890,8 +828,8 @@ def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub): version = PullRequestModel()._create_version_from_snapshot(pull_request) # Check attributes - assert version.title == pr_util.create_parameters['title'] - assert version.description == pr_util.create_parameters['description'] + assert version.title == pr_util.create_parameters["title"] + assert version.description == pr_util.create_parameters["description"] assert version.status == PullRequest.STATUS_CLOSED # versions get updated created_on @@ -899,11 +837,11 @@ def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub): assert version.updated_on == updated_on assert version.user_id == pull_request.user_id - assert version.revisions == pr_util.create_parameters['revisions'] + assert version.revisions == pr_util.create_parameters["revisions"] assert version.source_repo == pr_util.source_repository - assert version.source_ref == pr_util.create_parameters['source_ref'] + assert version.source_ref == pr_util.create_parameters["source_ref"] assert version.target_repo == pr_util.target_repository - assert version.target_ref == pr_util.create_parameters['target_ref'] + assert version.target_ref == pr_util.create_parameters["target_ref"] assert version._last_merge_source_rev == pull_request._last_merge_source_rev assert version._last_merge_target_rev == pull_request._last_merge_target_rev assert version.last_merge_status == pull_request.last_merge_status @@ -921,15 +859,9 @@ def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config Session().commit() # Expect that only the new comment is linked to version2 - assert ( - comment_unlinked.pull_request_version_id == - version2.pull_request_version_id) - assert ( - comment_linked.pull_request_version_id == - version1.pull_request_version_id) - assert ( - comment_unlinked.pull_request_version_id != - comment_linked.pull_request_version_id) + assert comment_unlinked.pull_request_version_id == version2.pull_request_version_id + assert comment_linked.pull_request_version_id == version1.pull_request_version_id + assert comment_unlinked.pull_request_version_id != comment_linked.pull_request_version_id def test_calculate_commits(): @@ -945,35 +877,26 @@ def test_calculate_commits(): def assert_inline_comments(pull_request, visible=None, outdated=None): if visible is not None: inline_comments = CommentsModel().get_inline_comments( - pull_request.target_repo.repo_id, pull_request=pull_request) - inline_cnt = len(CommentsModel().get_inline_comments_as_list( - inline_comments)) + pull_request.target_repo.repo_id, pull_request=pull_request + ) + inline_cnt = len(CommentsModel().get_inline_comments_as_list(inline_comments)) assert inline_cnt == visible if outdated is not None: - outdated_comments = CommentsModel().get_outdated_comments( - pull_request.target_repo.repo_id, pull_request) + outdated_comments = CommentsModel().get_outdated_comments(pull_request.target_repo.repo_id, pull_request) assert len(outdated_comments) == outdated -def assert_pr_file_changes( - pull_request, added=None, modified=None, removed=None): +def assert_pr_file_changes(pull_request, added=None, modified=None, removed=None): pr_versions = PullRequestModel().get_versions(pull_request) # always use first version, ie original PR to calculate changes pull_request_version = pr_versions[0] - old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs( - pull_request, pull_request_version) - file_changes = PullRequestModel()._calculate_file_changes( - old_diff_data, new_diff_data) + old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(pull_request, pull_request_version) + file_changes = PullRequestModel()._calculate_file_changes(old_diff_data, new_diff_data) - assert added == file_changes.added, \ - 'expected added:%s vs value:%s' % (added, file_changes.added) - assert modified == file_changes.modified, \ - 'expected modified:%s vs value:%s' % (modified, file_changes.modified) - assert removed == file_changes.removed, \ - 'expected removed:%s vs value:%s' % (removed, file_changes.removed) + assert added == file_changes.added, "expected added:%s vs value:%s" % (added, file_changes.added) + assert modified == file_changes.modified, "expected modified:%s vs value:%s" % (modified, file_changes.modified) + assert removed == file_changes.removed, "expected removed:%s vs value:%s" % (removed, file_changes.removed) def outdated_comments_patcher(use_outdated=True): - return mock.patch.object( - CommentsModel, 'use_outdated_comments', - return_value=use_outdated) + return mock.patch.object(CommentsModel, "use_outdated_comments", return_value=use_outdated) diff --git a/rhodecode/tests/models/test_repo_groups.py b/rhodecode/tests/models/test_repo_groups.py index 91a88a75..dee2a806 100644 --- a/rhodecode/tests/models/test_repo_groups.py +++ b/rhodecode/tests/models/test_repo_groups.py @@ -23,7 +23,7 @@ from sqlalchemy.exc import IntegrityError import pytest from rhodecode.tests import TESTS_TMP_PATH -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.repo import RepoModel diff --git a/rhodecode/tests/models/test_user_group_permissions_on_repo_groups.py b/rhodecode/tests/models/test_user_group_permissions_on_repo_groups.py index c32f7beb..83279902 100644 --- a/rhodecode/tests/models/test_user_group_permissions_on_repo_groups.py +++ b/rhodecode/tests/models/test_user_group_permissions_on_repo_groups.py @@ -28,7 +28,7 @@ from rhodecode.model.user_group import UserGroupModel from rhodecode.tests.models.common import ( _create_project_tree, check_tree_perms, _get_perms, _check_expected_count, expected_count, _destroy_project_tree) -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture fixture = Fixture() diff --git a/rhodecode/tests/models/test_user_groups.py b/rhodecode/tests/models/test_user_groups.py index 6f27e775..c21b6a31 100644 --- a/rhodecode/tests/models/test_user_groups.py +++ b/rhodecode/tests/models/test_user_groups.py @@ -22,7 +22,7 @@ import pytest from rhodecode.model.db import User from rhodecode.tests import TEST_USER_REGULAR_LOGIN -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.model.user_group import UserGroupModel from rhodecode.model.meta import Session diff --git a/rhodecode/tests/models/test_users.py b/rhodecode/tests/models/test_users.py index 1c9184a0..c4851c75 100644 --- a/rhodecode/tests/models/test_users.py +++ b/rhodecode/tests/models/test_users.py @@ -27,7 +27,7 @@ from rhodecode.model.user import UserModel from rhodecode.model.user_group import UserGroupModel from rhodecode.model.repo import RepoModel from rhodecode.model.repo_group import RepoGroupModel -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture from rhodecode.lib.str_utils import safe_str diff --git a/rhodecode/tests/other/test_validators.py b/rhodecode/tests/other/test_validators.py index 6be345e6..d4fd4842 100644 --- a/rhodecode/tests/other/test_validators.py +++ b/rhodecode/tests/other/test_validators.py @@ -32,11 +32,11 @@ from rhodecode.model.meta import Session from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.db import ChangesetStatus, Repository from rhodecode.model.changeset_status import ChangesetStatusModel -from rhodecode.tests.fixture import Fixture +from rhodecode.tests.fixtures.rc_fixture import Fixture fixture = Fixture() -pytestmark = pytest.mark.usefixtures('baseapp') +pytestmark = pytest.mark.usefixtures("baseapp") @pytest.fixture() diff --git a/rhodecode/tests/rhodecode.ini b/rhodecode/tests/rhodecode.ini index ab57657f..c628eba7 100644 --- a/rhodecode/tests/rhodecode.ini +++ b/rhodecode/tests/rhodecode.ini @@ -111,7 +111,7 @@ app.base_url = http://rhodecode.local app.service_api.host = http://rhodecode.local:10020 ; Secret for Service API authentication. -app.service_api.token = +app.service_api.token = secret4 ; Unique application ID. Should be a random unique string for security. app_instance_uuid = rc-production @@ -351,7 +351,7 @@ archive_cache.objectstore.retry_attempts = 10 ; and served from the cache during subsequent requests for the same archive of ; the repository. This path is important to be shared across filesystems and with ; RhodeCode and vcsserver -archive_cache.filesystem.store_dir = %(here)s/rc-tests/archive_cache +archive_cache.filesystem.store_dir = %(here)s/.rc-test-data/archive_cache ; The limit in GB sets how much data we cache before recycling last used, defaults to 10 gb archive_cache.filesystem.cache_size_gb = 2 @@ -406,7 +406,7 @@ celery.task_store_eager_result = true ; Default cache dir for caches. Putting this into a ramdisk can boost performance. ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space -cache_dir = %(here)s/rc-test-data +cache_dir = %(here)s/.rc-test-data ; ********************************************* ; `sql_cache_short` cache for heavy SQL queries @@ -435,7 +435,7 @@ rc_cache.cache_repo_longterm.max_size = 10000 rc_cache.cache_general.backend = dogpile.cache.rc.file_namespace rc_cache.cache_general.expiration_time = 43200 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set -rc_cache.cache_general.arguments.filename = %(here)s/rc-tests/cache-backend/cache_general_db +rc_cache.cache_general.arguments.filename = %(here)s/.rc-test-data/cache-backend/cache_general_db ; alternative `cache_general` redis backend with distributed lock #rc_cache.cache_general.backend = dogpile.cache.rc.redis @@ -454,6 +454,10 @@ rc_cache.cache_general.arguments.filename = %(here)s/rc-tests/cache-backend/cach ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen #rc_cache.cache_general.arguments.lock_auto_renewal = true +; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key} +#rc_cache.cache_general.arguments.key_prefix = custom-prefix- + + ; ************************************************* ; `cache_perms` cache for permission tree, auth TTL ; for simplicity use rc.file_namespace backend, @@ -462,7 +466,7 @@ rc_cache.cache_general.arguments.filename = %(here)s/rc-tests/cache-backend/cach rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace rc_cache.cache_perms.expiration_time = 0 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set -rc_cache.cache_perms.arguments.filename = %(here)s/rc-tests/cache-backend/cache_perms_db +rc_cache.cache_perms.arguments.filename = %(here)s/.rc-test-data/cache-backend/cache_perms_db ; alternative `cache_perms` redis backend with distributed lock #rc_cache.cache_perms.backend = dogpile.cache.rc.redis @@ -481,6 +485,10 @@ rc_cache.cache_perms.arguments.filename = %(here)s/rc-tests/cache-backend/cache_ ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen #rc_cache.cache_perms.arguments.lock_auto_renewal = true +; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key} +#rc_cache.cache_perms.arguments.key_prefix = custom-prefix- + + ; *************************************************** ; `cache_repo` cache for file tree, Readme, RSS FEEDS ; for simplicity use rc.file_namespace backend, @@ -489,7 +497,7 @@ rc_cache.cache_perms.arguments.filename = %(here)s/rc-tests/cache-backend/cache_ rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace rc_cache.cache_repo.expiration_time = 2592000 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set -rc_cache.cache_repo.arguments.filename = %(here)s/rc-tests/cache-backend/cache_repo_db +rc_cache.cache_repo.arguments.filename = %(here)s/.rc-test-data/cache-backend/cache_repo_db ; alternative `cache_repo` redis backend with distributed lock #rc_cache.cache_repo.backend = dogpile.cache.rc.redis @@ -508,6 +516,10 @@ rc_cache.cache_repo.arguments.filename = %(here)s/rc-tests/cache-backend/cache_r ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen #rc_cache.cache_repo.arguments.lock_auto_renewal = true +; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key} +#rc_cache.cache_repo.arguments.key_prefix = custom-prefix- + + ; ############## ; BEAKER SESSION ; ############## @@ -516,7 +528,7 @@ rc_cache.cache_repo.arguments.filename = %(here)s/rc-tests/cache-backend/cache_r ; types are file, ext:redis, ext:database, ext:memcached ; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session beaker.session.type = file -beaker.session.data_dir = %(here)s/rc-tests/data/sessions +beaker.session.data_dir = %(here)s/.rc-test-data/data/sessions ; Redis based sessions #beaker.session.type = ext:redis @@ -532,7 +544,7 @@ beaker.session.data_dir = %(here)s/rc-tests/data/sessions beaker.session.key = rhodecode beaker.session.secret = test-rc-uytcxaz -beaker.session.lock_dir = %(here)s/rc-tests/data/sessions/lock +beaker.session.lock_dir = %(here)s/.rc-test-data/data/sessions/lock ; Secure encrypted cookie. Requires AES and AES python libraries ; you must disable beaker.session.secret to use this @@ -564,7 +576,7 @@ beaker.session.secure = false ; WHOOSH Backend, doesn't require additional services to run ; it works good with few dozen repos search.module = rhodecode.lib.index.whoosh -search.location = %(here)s/rc-tests/data/index +search.location = %(here)s/.rc-test-data/data/index ; #################### ; CHANNELSTREAM CONFIG @@ -584,7 +596,7 @@ channelstream.server = channelstream:9800 ; see Nginx/Apache configuration examples in our docs channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream channelstream.secret = ENV_GENERATED -channelstream.history.location = %(here)s/rc-tests/channelstream_history +channelstream.history.location = %(here)s/.rc-test-data/channelstream_history ; Internal application path that Javascript uses to connect into. ; If you use proxy-prefix the prefix should be added before /_channelstream @@ -601,7 +613,7 @@ channelstream.proxy_path = /_channelstream ; pymysql is an alternative driver for MySQL, use in case of problems with default one #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode -sqlalchemy.db1.url = sqlite:///%(here)s/rc-tests/rhodecode_test.db?timeout=30 +sqlalchemy.db1.url = sqlite:///%(here)s/.rc-test-data/rhodecode_test.db?timeout=30 ; see sqlalchemy docs for other advanced settings ; print the sql statements to output @@ -737,7 +749,7 @@ ssh.generate_authorized_keyfile = true ; Path to the authorized_keys file where the generate entries are placed. ; It is possible to have multiple key files specified in `sshd_config` e.g. ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode -ssh.authorized_keys_file_path = %(here)s/rc-tests/authorized_keys_rhodecode +ssh.authorized_keys_file_path = %(here)s/.rc-test-data/authorized_keys_rhodecode ; Command to execute the SSH wrapper. The binary is available in the ; RhodeCode installation directory. diff --git a/rhodecode/tests/run_full_tests.sh b/rhodecode/tests/run_full_tests.sh deleted file mode 100755 index d50eefca..00000000 --- a/rhodecode/tests/run_full_tests.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -set -e - -## MIGRATIONS AND DB TESTS ## -echo "DATABASE CREATION TESTS" -rhodecode/tests/database/test_creation.sh - -echo "DATABASE MIGRATIONS TESTS" -rhodecode/tests/database/test_migration.sh - -## TEST VCS OPERATIONS ## -echo "VCS FUNCTIONAL TESTS" -rhodecode/tests/test_vcs_operations.sh - -## TOX TESTS ## -echo "TOX TESTS" -tox -r --develop diff --git a/rhodecode/tests/server_utils.py b/rhodecode/tests/server_utils.py index 4b062a47..d5aadc4b 100644 --- a/rhodecode/tests/server_utils.py +++ b/rhodecode/tests/server_utils.py @@ -24,13 +24,13 @@ import tempfile import pytest import subprocess import logging -from urllib.request import urlopen -from urllib.error import URLError +import requests import configparser from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS from rhodecode.tests.utils import is_url_reachable +from rhodecode.tests import console_printer log = logging.getLogger(__name__) @@ -49,7 +49,7 @@ def get_host_url(pyramid_config): def assert_no_running_instance(url): if is_url_reachable(url): - print(f"Hint: Usually this means another instance of server " + console_printer(f"Hint: Usually this means another instance of server " f"is running in the background at {url}.") pytest.fail(f"Port is not free at {url}, cannot start server at") @@ -58,8 +58,9 @@ class ServerBase(object): _args = [] log_file_name = 'NOT_DEFINED.log' status_url_tmpl = 'http://{host}:{port}/_admin/ops/ping' + console_marker = " :warning: [green]pytest-setup[/green] " - def __init__(self, config_file, log_file): + def __init__(self, config_file, log_file, env): self.config_file = config_file config = configparser.ConfigParser() config.read(config_file) @@ -69,10 +70,10 @@ class ServerBase(object): self._args = [] self.log_file = log_file or os.path.join( tempfile.gettempdir(), self.log_file_name) + self.env = env self.process = None self.server_out = None - log.info("Using the {} configuration:{}".format( - self.__class__.__name__, config_file)) + log.info(f"Using the {self.__class__.__name__} configuration:{config_file}") if not os.path.isfile(config_file): raise RuntimeError(f'Failed to get config at {config_file}') @@ -110,18 +111,17 @@ class ServerBase(object): while time.time() - start < timeout: try: - urlopen(status_url) + requests.get(status_url) break - except URLError: + except requests.exceptions.ConnectionError: time.sleep(0.2) else: pytest.fail( - "Starting the {} failed or took more than {} " - "seconds. cmd: `{}`".format( - self.__class__.__name__, timeout, self.command)) + f"Starting the {self.__class__.__name__} failed or took more than {timeout} seconds." + f"cmd: `{self.command}`" + ) - log.info('Server of {} ready at url {}'.format( - self.__class__.__name__, status_url)) + log.info(f'Server of {self.__class__.__name__} ready at url {status_url}') def shutdown(self): self.process.kill() @@ -130,7 +130,7 @@ class ServerBase(object): def get_log_file_with_port(self): log_file = list(self.log_file.partition('.log')) - log_file.insert(1, get_port(self.config_file)) + log_file.insert(1, f'-{get_port(self.config_file)}') log_file = ''.join(log_file) return log_file @@ -140,11 +140,12 @@ class RcVCSServer(ServerBase): Represents a running VCSServer instance. """ - log_file_name = 'rc-vcsserver.log' + log_file_name = 'rhodecode-vcsserver.log' status_url_tmpl = 'http://{host}:{port}/status' - def __init__(self, config_file, log_file=None, workers='3'): - super(RcVCSServer, self).__init__(config_file, log_file) + def __init__(self, config_file, log_file=None, workers='3', env=None, info_prefix=''): + super(RcVCSServer, self).__init__(config_file, log_file, env) + self.info_prefix = info_prefix self._args = [ 'gunicorn', '--bind', self.bind_addr, @@ -164,9 +165,10 @@ class RcVCSServer(ServerBase): host_url = self.host_url() assert_no_running_instance(host_url) - print(f'rhodecode-vcsserver starting at: {host_url}') - print(f'rhodecode-vcsserver command: {self.command}') - print(f'rhodecode-vcsserver logfile: {self.log_file}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-vcsserver starting at: {host_url}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-vcsserver command: {self.command}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-vcsserver logfile: {self.log_file}') + console_printer() self.process = subprocess.Popen( self._args, bufsize=0, env=env, @@ -178,11 +180,12 @@ class RcWebServer(ServerBase): Represents a running RCE web server used as a test fixture. """ - log_file_name = 'rc-web.log' + log_file_name = 'rhodecode-ce.log' status_url_tmpl = 'http://{host}:{port}/_admin/ops/ping' - def __init__(self, config_file, log_file=None, workers='2'): - super(RcWebServer, self).__init__(config_file, log_file) + def __init__(self, config_file, log_file=None, workers='2', env=None, info_prefix=''): + super(RcWebServer, self).__init__(config_file, log_file, env) + self.info_prefix = info_prefix self._args = [ 'gunicorn', '--bind', self.bind_addr, @@ -195,7 +198,8 @@ class RcWebServer(ServerBase): def start(self): env = os.environ.copy() - env['RC_NO_TMP_PATH'] = '1' + if self.env: + env.update(self.env) self.log_file = self.get_log_file_with_port() self.server_out = open(self.log_file, 'w') @@ -203,9 +207,10 @@ class RcWebServer(ServerBase): host_url = self.host_url() assert_no_running_instance(host_url) - print(f'rhodecode-web starting at: {host_url}') - print(f'rhodecode-web command: {self.command}') - print(f'rhodecode-web logfile: {self.log_file}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-ce starting at: {host_url}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-ce command: {self.command}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-ce logfile: {self.log_file}') + console_printer() self.process = subprocess.Popen( self._args, bufsize=0, env=env, @@ -229,3 +234,44 @@ class RcWebServer(ServerBase): } params.update(**kwargs) return params['user'], params['passwd'] + +class CeleryServer(ServerBase): + log_file_name = 'rhodecode-celery.log' + status_url_tmpl = 'http://{host}:{port}/_admin/ops/ping' + + def __init__(self, config_file, log_file=None, workers='2', env=None, info_prefix=''): + super(CeleryServer, self).__init__(config_file, log_file, env) + self.info_prefix = info_prefix + self._args = \ + ['celery', + '--no-color', + '--app=rhodecode.lib.celerylib.loader', + 'worker', + '--autoscale=4,2', + '--max-tasks-per-child=30', + '--task-events', + '--loglevel=DEBUG', + '--ini=' + self.config_file] + + def start(self): + env = os.environ.copy() + env['RC_NO_TEST_ENV'] = '1' + + self.log_file = self.get_log_file_with_port() + self.server_out = open(self.log_file, 'w') + + host_url = "Celery" #self.host_url() + #assert_no_running_instance(host_url) + + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-celery starting at: {host_url}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-celery command: {self.command}') + console_printer(f'{self.console_marker}{self.info_prefix}rhodecode-celery logfile: {self.log_file}') + console_printer() + + self.process = subprocess.Popen( + self._args, bufsize=0, env=env, + stdout=self.server_out, stderr=self.server_out) + + + def wait_until_ready(self, timeout=30): + time.sleep(2) diff --git a/rhodecode/tests/test_vcs_operations.sh b/rhodecode/tests/test_vcs_operations.sh deleted file mode 100755 index 60009b23..00000000 --- a/rhodecode/tests/test_vcs_operations.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -set -e - -SQLITE_DB_PATH=/mnt/hgfs/marcink-shared/workspace-python/rhodecode -RC_LOG=/tmp/rc.log -INI_FILE=test.ini -TEST_DB_NAME=rhodecode_test - - -for databaseName in p m s; do - # set the different DBs - if [ "$databaseName" = "s" ]; then - echo "sqlite" - rhodecode-config --filename=$INI_FILE --update '[app:main]sqlalchemy.db1.url=sqlite:///'$SQLITE_DB_PATH/$TEST_DB_NAME'.sqlite' - elif [ "$databaseName" = "p" ]; then - echo "postgres" - rhodecode-config --filename=$INI_FILE --update '[app:main]sqlalchemy.db1.url=postgresql://postgres:qweqwe@localhost/'$TEST_DB_NAME'' - elif [ "$databaseName" = "m" ]; then - echo "mysql" - rhodecode-config --filename=$INI_FILE --update '[app:main]sqlalchemy.db1.url=mysql://root:qweqwe@localhost/'$TEST_DB_NAME'' - fi - - # running just VCS tests - RC_NO_TMP_PATH=1 py.test \ - rhodecode/tests/other/test_vcs_operations.py - -done diff --git a/rhodecode/tests/utils.py b/rhodecode/tests/utils.py index ec551d33..64a5d8f4 100644 --- a/rhodecode/tests/utils.py +++ b/rhodecode/tests/utils.py @@ -36,24 +36,29 @@ from webtest.app import TestResponse, TestApp import pytest -try: - import rc_testdata -except ImportError: - raise ImportError('Failed to import rc_testdata, ' - 'please make sure this package is installed from requirements_test.txt') - from rhodecode.model.db import User, Repository from rhodecode.model.meta import Session from rhodecode.model.scm import ScmModel from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository from rhodecode.lib.vcs.backends.base import EmptyCommit -from rhodecode.tests import login_user_session +from rhodecode.tests import login_user_session, console_printer +from rhodecode.authentication import AuthenticationPluginRegistry +from rhodecode.model.settings import SettingsModel log = logging.getLogger(__name__) -def print_to_func(value, print_to=sys.stderr): - print(value, file=print_to) +def console_printer_utils(msg): + console_printer(f" :white_check_mark: [green]test-utils[/green] {msg}") + + +def get_rc_testdata(): + try: + import rc_testdata + except ImportError: + raise ImportError('Failed to import rc_testdata, ' + 'please make sure this package is installed from requirements_test.txt') + return rc_testdata class CustomTestResponse(TestResponse): @@ -73,7 +78,6 @@ class CustomTestResponse(TestResponse): assert string in res """ print_body = kw.pop('print_body', False) - print_to = kw.pop('print_to', sys.stderr) if 'no' in kw: no = kw['no'] @@ -89,18 +93,18 @@ class CustomTestResponse(TestResponse): for s in strings: if s not in self: - print_to_func(f"Actual response (no {s!r}):", print_to=print_to) - print_to_func(f"body output saved as `{f}`", print_to=print_to) + console_printer_utils(f"Actual response (no {s!r}):") + console_printer_utils(f"body output saved as `{f}`") if print_body: - print_to_func(str(self), print_to=print_to) + console_printer_utils(str(self)) raise IndexError(f"Body does not contain string {s!r}, body output saved as {f}") for no_s in no: if no_s in self: - print_to_func(f"Actual response (has {no_s!r})", print_to=print_to) - print_to_func(f"body output saved as `{f}`", print_to=print_to) + console_printer_utils(f"Actual response (has {no_s!r})") + console_printer_utils(f"body output saved as `{f}`") if print_body: - print_to_func(str(self), print_to=print_to) + console_printer_utils(str(self)) raise IndexError(f"Body contains bad string {no_s!r}, body output saved as {f}") def assert_response(self): @@ -209,6 +213,7 @@ def extract_git_repo_from_dump(dump_name, repo_name): """Create git repo `repo_name` from dump `dump_name`.""" repos_path = ScmModel().repos_path target_path = os.path.join(repos_path, repo_name) + rc_testdata = get_rc_testdata() rc_testdata.extract_git_dump(dump_name, target_path) return target_path @@ -217,6 +222,7 @@ def extract_hg_repo_from_dump(dump_name, repo_name): """Create hg repo `repo_name` from dump `dump_name`.""" repos_path = ScmModel().repos_path target_path = os.path.join(repos_path, repo_name) + rc_testdata = get_rc_testdata() rc_testdata.extract_hg_dump(dump_name, target_path) return target_path @@ -245,6 +251,7 @@ def _load_svn_dump_into_repo(dump_name, repo_path): Currently the dumps are in rc_testdata. They might later on be integrated with the main repository once they stabilize more. """ + rc_testdata = get_rc_testdata() dump = rc_testdata.load_svn_dump(dump_name) load_dump = subprocess.Popen( ['svnadmin', 'load', repo_path], @@ -254,9 +261,7 @@ def _load_svn_dump_into_repo(dump_name, repo_path): if load_dump.returncode != 0: log.error("Output of load_dump command: %s", out) log.error("Error output of load_dump command: %s", err) - raise Exception( - 'Failed to load dump "%s" into repository at path "%s".' - % (dump_name, repo_path)) + raise Exception(f'Failed to load dump "{dump_name}" into repository at path "{repo_path}".') class AssertResponse(object): @@ -492,3 +497,54 @@ def permission_update_data_generator(csrf_token, default=None, grant=None, revok ('perm_del_member_type_{}'.format(obj_id), obj_type), ]) return form_data + + + +class AuthPluginManager: + + def cleanup(self): + self._enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode']) + + def enable(self, plugins_list, override=None): + return self._enable_plugins(plugins_list, override) + + @classmethod + def _enable_plugins(cls, plugins_list, override: object = None): + override = override or {} + params = { + 'auth_plugins': ','.join(plugins_list), + } + + # helper translate some names to others, to fix settings code + name_map = { + 'token': 'authtoken' + } + log.debug('enable_auth_plugins: enabling following auth-plugins: %s', plugins_list) + + for module in plugins_list: + plugin_name = module.partition('#')[-1] + if plugin_name in name_map: + plugin_name = name_map[plugin_name] + enabled_plugin = f'auth_{plugin_name}_enabled' + cache_ttl = f'auth_{plugin_name}_cache_ttl' + + # default params that are needed for each plugin, + # `enabled` and `cache_ttl` + params.update({ + enabled_plugin: True, + cache_ttl: 0 + }) + if override.get: + params.update(override.get(module, {})) + + validated_params = params + + for k, v in validated_params.items(): + setting = SettingsModel().create_or_update_setting(k, v) + Session().add(setting) + Session().commit() + + AuthenticationPluginRegistry.invalidate_auth_plugins_cache(hard=True) + + enabled_plugins = SettingsModel().get_auth_plugins() + assert plugins_list == enabled_plugins diff --git a/rhodecode/tests/vcs/__init__.py b/rhodecode/tests/vcs/__init__.py index bdc53285..c4d86aeb 100644 --- a/rhodecode/tests/vcs/__init__.py +++ b/rhodecode/tests/vcs/__init__.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify diff --git a/rhodecode/tests/vcs/conftest.py b/rhodecode/tests/vcs/conftest.py index 5d321182..7e0313f8 100644 --- a/rhodecode/tests/vcs/conftest.py +++ b/rhodecode/tests/vcs/conftest.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -32,8 +31,7 @@ from rhodecode.tests.utils import check_skip_backends, check_xfail_backends @pytest.fixture() -def vcs_repository_support( - request, backend_alias, baseapp, _vcs_repo_container): +def vcs_repository_support(request, backend_alias, baseapp, _vcs_repo_container): """ Provide a test repository for the test run. @@ -63,7 +61,7 @@ def vcs_repository_support( return backend_alias, repo -@pytest.fixture(scope='class') +@pytest.fixture(scope="class") def _vcs_repo_container(request): """ Internal fixture intended to help support class based scoping on demand. @@ -73,13 +71,12 @@ def _vcs_repo_container(request): def _create_vcs_repo_container(request): repo_container = VcsRepoContainer() - if not request.config.getoption('--keep-tmp-path'): + 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 = {} @@ -98,14 +95,14 @@ class VcsRepoContainer(object): def _should_create_repo_per_test(cls): - return getattr(cls, 'recreate_repo_per_test', False) + 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'): + if hasattr(cls, "_get_commits"): commits = cls._get_commits() cls.tip = _add_commits_to_repo(repo, commits) @@ -127,7 +124,7 @@ def config(): specific content is required. """ config = Config() - config.set('section-a', 'a-1', 'value-a-1') + config.set("section-a", "a-1", "value-a-1") return config @@ -136,24 +133,24 @@ def _add_commits_to_repo(repo, commits): tip = None for commit in commits: - for node in commit.get('added', []): + 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', []): + 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', []): + 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') + message=str(commit["message"]), + author=str(commit["author"]), + date=commit["date"], + branch=commit.get("branch"), ) return tip @@ -183,16 +180,15 @@ def generate_repo_with_commits(vcs_repo): 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), + "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)), ], - 'modified': [ - FileNode(b'file_%d.txt' % x, - content=b'Foobar %d modified' % (x-1)), - ] } def commit_maker(num=5): @@ -231,34 +227,33 @@ class BackendTestMixin(object): 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": "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...'), + "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'), + "changed": [ + FileNode(b"foobar", b"Foobar I"), ], - 'removed': [], + "removed": [], }, ] return commits - - diff --git a/rhodecode/tests/vcs/test_archives.py b/rhodecode/tests/vcs/test_archives.py index b9be4745..a22ff43d 100644 --- a/rhodecode/tests/vcs/test_archives.py +++ b/rhodecode/tests/vcs/test_archives.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -43,121 +42,120 @@ def d_cache_config(): @pytest.mark.usefixtures("vcs_repository_support") class TestArchives(BackendTestMixin): - @classmethod def _get_commits(cls): start_date = datetime.datetime(2010, 1, 1, 20) yield { - 'message': 'Initial Commit', - 'author': 'Joe Doe ', - 'date': start_date + datetime.timedelta(hours=12), - 'added': [ - FileNode(b'executable_0o100755', b'mode_755', mode=0o100755), - FileNode(b'executable_0o100500', b'mode_500', mode=0o100500), - FileNode(b'not_executable', b'mode_644', mode=0o100644), + "message": "Initial Commit", + "author": "Joe Doe ", + "date": start_date + datetime.timedelta(hours=12), + "added": [ + FileNode(b"executable_0o100755", b"mode_755", mode=0o100755), + FileNode(b"executable_0o100500", b"mode_500", mode=0o100500), + FileNode(b"not_executable", b"mode_644", mode=0o100644), ], } for x in range(5): yield { - 'message': 'Commit %d' % x, - 'author': 'Joe Doe ', - 'date': start_date + datetime.timedelta(hours=12 * x), - 'added': [ - FileNode(b'%d/file_%d.txt' % (x, x), content=b'Foobar %d' % x), + "message": "Commit %d" % x, + "author": "Joe Doe ", + "date": start_date + datetime.timedelta(hours=12 * x), + "added": [ + FileNode(b"%d/file_%d.txt" % (x, x), content=b"Foobar %d" % x), ], } - @pytest.mark.parametrize('compressor', ['gz', 'bz2']) + @pytest.mark.parametrize("compressor", ["gz", "bz2"]) def test_archive_tar(self, compressor, tmpdir, tmp_path, d_cache_config): - - archive_node = tmp_path / 'archive-node' + archive_node = tmp_path / "archive-node" archive_node.touch() archive_lnk = self.tip.archive_repo( - str(archive_node), kind=f't{compressor}', archive_dir_name='repo', cache_config=d_cache_config) + str(archive_node), kind=f"t{compressor}", archive_dir_name="repo", cache_config=d_cache_config + ) out_dir = tmpdir - out_file = tarfile.open(str(archive_lnk), f'r|{compressor}') + out_file = tarfile.open(str(archive_lnk), f"r|{compressor}") out_file.extractall(out_dir) out_file.close() for x in range(5): - node_path = '%d/file_%d.txt' % (x, x) - with open(os.path.join(out_dir, 'repo/' + node_path), 'rb') as f: + node_path = "%d/file_%d.txt" % (x, x) + with open(os.path.join(out_dir, "repo/" + node_path), "rb") as f: file_content = f.read() assert file_content == self.tip.get_node(node_path).content shutil.rmtree(out_dir) - @pytest.mark.parametrize('compressor', ['gz', 'bz2']) + @pytest.mark.parametrize("compressor", ["gz", "bz2"]) def test_archive_tar_symlink(self, compressor): - pytest.skip('Not supported') + pytest.skip("Not supported") - @pytest.mark.parametrize('compressor', ['gz', 'bz2']) + @pytest.mark.parametrize("compressor", ["gz", "bz2"]) def test_archive_tar_file_modes(self, compressor, tmpdir, tmp_path, d_cache_config): - archive_node = tmp_path / 'archive-node' + archive_node = tmp_path / "archive-node" archive_node.touch() archive_lnk = self.tip.archive_repo( - str(archive_node), kind='t{}'.format(compressor), archive_dir_name='repo', cache_config=d_cache_config) + str(archive_node), kind="t{}".format(compressor), archive_dir_name="repo", cache_config=d_cache_config + ) out_dir = tmpdir - out_file = tarfile.open(str(archive_lnk), 'r|{}'.format(compressor)) + out_file = tarfile.open(str(archive_lnk), "r|{}".format(compressor)) out_file.extractall(out_dir) out_file.close() def dest(inp): return os.path.join(out_dir, "repo/" + inp) - assert oct(os.stat(dest('not_executable')).st_mode) == '0o100644' + assert oct(os.stat(dest("not_executable")).st_mode) == "0o100644" def test_archive_zip(self, tmp_path, d_cache_config): - archive_node = tmp_path / 'archive-node' - archive_node.touch() - - archive_lnk = self.tip.archive_repo(str(archive_node), kind='zip', - archive_dir_name='repo', cache_config=d_cache_config) - zip_file = zipfile.ZipFile(str(archive_lnk)) - - for x in range(5): - node_path = '%d/file_%d.txt' % (x, x) - data = zip_file.read(f'repo/{node_path}') - - decompressed = io.BytesIO() - decompressed.write(data) - assert decompressed.getvalue() == \ - self.tip.get_node(node_path).content - decompressed.close() - - def test_archive_zip_with_metadata(self, tmp_path, d_cache_config): - archive_node = tmp_path / 'archive-node' + archive_node = tmp_path / "archive-node" archive_node.touch() archive_lnk = self.tip.archive_repo( - str(archive_node), kind='zip', - archive_dir_name='repo', write_metadata=True, cache_config=d_cache_config) - + str(archive_node), kind="zip", archive_dir_name="repo", cache_config=d_cache_config + ) zip_file = zipfile.ZipFile(str(archive_lnk)) - metafile = zip_file.read('repo/.archival.txt') - - raw_id = ascii_bytes(self.tip.raw_id) - assert b'commit_id:%b' % raw_id in metafile for x in range(5): - node_path = '%d/file_%d.txt' % (x, x) - data = zip_file.read(f'repo/{node_path}') + node_path = "%d/file_%d.txt" % (x, x) + data = zip_file.read(f"repo/{node_path}") + decompressed = io.BytesIO() decompressed.write(data) - assert decompressed.getvalue() == \ - self.tip.get_node(node_path).content + assert decompressed.getvalue() == self.tip.get_node(node_path).content + decompressed.close() + + def test_archive_zip_with_metadata(self, tmp_path, d_cache_config): + archive_node = tmp_path / "archive-node" + archive_node.touch() + + archive_lnk = self.tip.archive_repo( + str(archive_node), kind="zip", archive_dir_name="repo", write_metadata=True, cache_config=d_cache_config + ) + + zip_file = zipfile.ZipFile(str(archive_lnk)) + metafile = zip_file.read("repo/.archival.txt") + + raw_id = ascii_bytes(self.tip.raw_id) + assert b"commit_id:%b" % raw_id in metafile + + for x in range(5): + node_path = "%d/file_%d.txt" % (x, x) + data = zip_file.read(f"repo/{node_path}") + decompressed = io.BytesIO() + decompressed.write(data) + assert decompressed.getvalue() == self.tip.get_node(node_path).content decompressed.close() def test_archive_wrong_kind(self, tmp_path, d_cache_config): - archive_node = tmp_path / 'archive-node' + archive_node = tmp_path / "archive-node" archive_node.touch() with pytest.raises(ImproperArchiveTypeError): - self.tip.archive_repo(str(archive_node), kind='wrong kind', cache_config=d_cache_config) + self.tip.archive_repo(str(archive_node), kind="wrong kind", cache_config=d_cache_config) @pytest.fixture() @@ -167,8 +165,8 @@ def base_commit(): """ commit = base.BaseCommit() commit.repository = mock.Mock() - commit.repository.name = 'fake_repo' - commit.short_id = 'fake_id' + commit.repository.name = "fake_repo" + commit.short_id = "fake_id" return commit @@ -180,19 +178,17 @@ def test_validate_archive_prefix_enforces_non_ascii_as_prefix(base_commit): def test_validate_archive_prefix_empty_prefix(base_commit): # TODO: johbo: Should raise a ValueError here. with pytest.raises(VCSError): - base_commit._validate_archive_prefix('') + base_commit._validate_archive_prefix("") def test_validate_archive_prefix_with_leading_slash(base_commit): # TODO: johbo: Should raise a ValueError here. with pytest.raises(VCSError): - base_commit._validate_archive_prefix('/any') + base_commit._validate_archive_prefix("/any") def test_validate_archive_prefix_falls_back_to_repository_name(base_commit): prefix = base_commit._validate_archive_prefix(None) - expected_prefix = base_commit._ARCHIVE_PREFIX_TEMPLATE.format( - repo_name='fake_repo', - short_id='fake_id') + expected_prefix = base_commit._ARCHIVE_PREFIX_TEMPLATE.format(repo_name="fake_repo", short_id="fake_id") assert isinstance(prefix, str) assert prefix == expected_prefix diff --git a/rhodecode/tests/vcs/test_branches.py b/rhodecode/tests/vcs/test_branches.py index 2159c6f9..2cea7691 100644 --- a/rhodecode/tests/vcs/test_branches.py +++ b/rhodecode/tests/vcs/test_branches.py @@ -64,18 +64,14 @@ class TestBranches(BackendTestMixin): def test_new_head(self): tip = self.repo.get_commit() - self.imc.add( - FileNode(b"docs/index.txt", content=b"Documentation\n") - ) + self.imc.add(FileNode(b"docs/index.txt", content=b"Documentation\n")) foobar_tip = self.imc.commit( message="New branch: foobar", author="joe ", branch="foobar", parents=[tip], ) - self.imc.change( - FileNode(b"docs/index.txt", content=b"Documentation\nand more...\n") - ) + self.imc.change(FileNode(b"docs/index.txt", content=b"Documentation\nand more...\n")) assert foobar_tip.branch == "foobar" newtip = self.imc.commit( message="At foobar_tip branch", @@ -96,21 +92,15 @@ class TestBranches(BackendTestMixin): @pytest.mark.backends("git", "hg") def test_branch_with_slash_in_name(self): self.imc.add(FileNode(b"extrafile", content=b"Some data\n")) - self.imc.commit( - "Branch with a slash!", author="joe ", branch="issue/123" - ) + self.imc.commit("Branch with a slash!", author="joe ", branch="issue/123") assert "issue/123" in self.repo.branches @pytest.mark.backends("git", "hg") def test_branch_with_slash_in_name_and_similar_without(self): self.imc.add(FileNode(b"extrafile", content=b"Some data\n")) - self.imc.commit( - "Branch with a slash!", author="joe ", branch="issue/123" - ) + self.imc.commit("Branch with a slash!", author="joe ", branch="issue/123") self.imc.add(FileNode(b"extrafile II", content=b"Some data\n")) - self.imc.commit( - "Branch without a slash...", author="joe ", branch="123" - ) + self.imc.commit("Branch without a slash...", author="joe ", branch="123") assert "issue/123" in self.repo.branches assert "123" in self.repo.branches diff --git a/rhodecode/tests/vcs/test_client_http.py b/rhodecode/tests/vcs/test_client_http.py index eb7fe2bc..2618dd24 100644 --- a/rhodecode/tests/vcs/test_client_http.py +++ b/rhodecode/tests/vcs/test_client_http.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -28,9 +27,7 @@ from rhodecode.lib.vcs import client_http, exceptions def is_new_connection(logger, level, message): - return ( - logger == 'requests.packages.urllib3.connectionpool' and - message.startswith('Starting new HTTP')) + return logger == "requests.packages.urllib3.connectionpool" and message.startswith("Starting new HTTP") @pytest.fixture() @@ -54,7 +51,7 @@ def stub_fail_session(): """ session = mock.Mock() post = session.post() - post.content = msgpack.packb({'error': '500'}) + post.content = msgpack.packb({"error": "500"}) post.status_code = 500 session.reset_mock() @@ -89,44 +86,37 @@ def test_uses_persistent_http_connections(caplog, vcsbackend_hg): for x in range(5): remote_call(normal=True, closed=False) - new_connections = [ - r for r in caplog.record_tuples if is_new_connection(*r)] + new_connections = [r for r in caplog.record_tuples if is_new_connection(*r)] assert len(new_connections) <= 1 def test_repo_maker_uses_session_for_classmethods(stub_session_factory): - repo_maker = client_http.RemoteVCSMaker( - 'server_and_port', 'endpoint', 'test_dummy_scm', stub_session_factory) + repo_maker = client_http.RemoteVCSMaker("server_and_port", "endpoint", "test_dummy_scm", stub_session_factory) repo_maker.example_call() - stub_session_factory().post.assert_called_with( - 'http://server_and_port/endpoint', data=mock.ANY) + stub_session_factory().post.assert_called_with("http://server_and_port/endpoint", data=mock.ANY) -def test_repo_maker_uses_session_for_instance_methods( - stub_session_factory, config): - repo_maker = client_http.RemoteVCSMaker( - 'server_and_port', 'endpoint', 'test_dummy_scm', stub_session_factory) - repo = repo_maker('stub_path', 'stub_repo_id', config) +def test_repo_maker_uses_session_for_instance_methods(stub_session_factory, config): + repo_maker = client_http.RemoteVCSMaker("server_and_port", "endpoint", "test_dummy_scm", stub_session_factory) + repo = repo_maker("stub_path", "stub_repo_id", config) repo.example_call() - stub_session_factory().post.assert_called_with( - 'http://server_and_port/endpoint', data=mock.ANY) + stub_session_factory().post.assert_called_with("http://server_and_port/endpoint", data=mock.ANY) -@mock.patch('rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory') -@mock.patch('rhodecode.lib.vcs.connection') -def test_connect_passes_in_the_same_session( - connection, session_factory_class, stub_session): +@mock.patch("rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory") +@mock.patch("rhodecode.lib.vcs.connection") +def test_connect_passes_in_the_same_session(connection, session_factory_class, stub_session): session_factory = session_factory_class.return_value session_factory.return_value = stub_session - vcs.connect_http('server_and_port') + vcs.connect_http("server_and_port") -def test_repo_maker_uses_session_that_throws_error( - stub_session_failing_factory, config): +def test_repo_maker_uses_session_that_throws_error(stub_session_failing_factory, config): repo_maker = client_http.RemoteVCSMaker( - 'server_and_port', 'endpoint', 'test_dummy_scm', stub_session_failing_factory) - repo = repo_maker('stub_path', 'stub_repo_id', config) + "server_and_port", "endpoint", "test_dummy_scm", stub_session_failing_factory + ) + repo = repo_maker("stub_path", "stub_repo_id", config) with pytest.raises(exceptions.HttpVCSCommunicationError): repo.example_call() diff --git a/rhodecode/tests/vcs/test_commits.py b/rhodecode/tests/vcs/test_commits.py index 81fedd0d..df57ca1f 100644 --- a/rhodecode/tests/vcs/test_commits.py +++ b/rhodecode/tests/vcs/test_commits.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -23,27 +22,31 @@ import time import pytest from rhodecode.lib.str_utils import safe_bytes -from rhodecode.lib.vcs.backends.base import ( - CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit) +from rhodecode.lib.vcs.backends.base import CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit from rhodecode.lib.vcs.exceptions import ( - BranchDoesNotExistError, CommitDoesNotExistError, - RepositoryError, EmptyRepositoryError) + BranchDoesNotExistError, + CommitDoesNotExistError, + RepositoryError, + EmptyRepositoryError, +) from rhodecode.lib.vcs.nodes import ( - FileNode, AddedFileNodesGenerator, - ChangedFileNodesGenerator, RemovedFileNodesGenerator) + FileNode, + AddedFileNodesGenerator, + ChangedFileNodesGenerator, + RemovedFileNodesGenerator, +) from rhodecode.tests import get_new_dir from rhodecode.tests.vcs.conftest import BackendTestMixin class TestBaseChangeset(object): - def test_is_deprecated(self): from rhodecode.lib.vcs.backends.base import BaseChangeset + pytest.deprecated_call(BaseChangeset) class TestEmptyCommit(object): - def test_branch_without_alias_returns_none(self): commit = EmptyCommit() assert commit.branch is None @@ -58,29 +61,28 @@ class TestCommitsInNonEmptyRepo(BackendTestMixin): start_date = datetime.datetime(2010, 1, 1, 20) for x in range(5): 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), + "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), ], } def test_walk_returns_empty_list_in_case_of_file(self): - result = list(self.tip.walk('file_0.txt')) + result = list(self.tip.walk("file_0.txt")) assert result == [] @pytest.mark.backends("git", "hg") def test_new_branch(self): - self.imc.add(FileNode(b'docs/index.txt', content=b'Documentation\n')) + self.imc.add(FileNode(b"docs/index.txt", content=b"Documentation\n")) foobar_tip = self.imc.commit( - message='New branch: foobar', - author='joe ', - branch='foobar', + message="New branch: foobar", + author="joe ", + branch="foobar", ) - assert 'foobar' in self.repo.branches - assert foobar_tip.branch == 'foobar' + assert "foobar" in self.repo.branches + assert foobar_tip.branch == "foobar" # 'foobar' should be the only branch that contains the new commit branch = list(self.repo.branches.values()) assert branch[0] != branch[1] @@ -89,18 +91,14 @@ class TestCommitsInNonEmptyRepo(BackendTestMixin): def test_new_head_in_default_branch(self): tip = self.repo.get_commit() - self.imc.add( - FileNode(b"docs/index.txt", content=b"Documentation\n") - ) + self.imc.add(FileNode(b"docs/index.txt", content=b"Documentation\n")) foobar_tip = self.imc.commit( message="New branch: foobar", author="joe ", branch="foobar", parents=[tip], ) - self.imc.change( - FileNode(b"docs/index.txt", content=b"Documentation\nand more...\n") - ) + self.imc.change(FileNode(b"docs/index.txt", content=b"Documentation\nand more...\n")) assert foobar_tip.branch == "foobar" newtip = self.imc.commit( message="At foobar_tip branch", @@ -132,51 +130,55 @@ class TestCommitsInNonEmptyRepo(BackendTestMixin): :return: """ DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME - TEST_BRANCH = 'docs' + TEST_BRANCH = "docs" org_tip = self.repo.get_commit() - self.imc.add(FileNode(b'readme.txt', content=b'Document\n')) + self.imc.add(FileNode(b"readme.txt", content=b"Document\n")) initial = self.imc.commit( - message='Initial commit', - author='joe ', + message="Initial commit", + author="joe ", parents=[org_tip], - branch=DEFAULT_BRANCH,) + branch=DEFAULT_BRANCH, + ) - self.imc.add(FileNode(b'newdoc.txt', content=b'foobar\n')) + self.imc.add(FileNode(b"newdoc.txt", content=b"foobar\n")) docs_branch_commit1 = self.imc.commit( - message='New branch: docs', - author='joe ', + message="New branch: docs", + author="joe ", parents=[initial], - branch=TEST_BRANCH,) + branch=TEST_BRANCH, + ) - self.imc.add(FileNode(b'newdoc2.txt', content=b'foobar2\n')) + self.imc.add(FileNode(b"newdoc2.txt", content=b"foobar2\n")) docs_branch_commit2 = self.imc.commit( - message='New branch: docs2', - author='joe ', + message="New branch: docs2", + author="joe ", parents=[docs_branch_commit1], - branch=TEST_BRANCH,) + branch=TEST_BRANCH, + ) - self.imc.add(FileNode(b'newfile', content=b'hello world\n')) + self.imc.add(FileNode(b"newfile", content=b"hello world\n")) self.imc.commit( - message='Back in default branch', - author='joe ', + message="Back in default branch", + author="joe ", parents=[initial], - branch=DEFAULT_BRANCH,) + branch=DEFAULT_BRANCH, + ) default_branch_commits = self.repo.get_commits(branch_name=DEFAULT_BRANCH) assert docs_branch_commit1 not in list(default_branch_commits) assert docs_branch_commit2 not in list(default_branch_commits) docs_branch_commits = self.repo.get_commits( - start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1], - branch_name=TEST_BRANCH) + start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1], branch_name=TEST_BRANCH + ) assert docs_branch_commit1 in list(docs_branch_commits) assert docs_branch_commit2 in list(docs_branch_commits) @pytest.mark.backends("svn") def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn): - repo = vcsbackend_svn['svn-simple-layout'] - commits = repo.get_commits(branch_name='trunk') + repo = vcsbackend_svn["svn-simple-layout"] + commits = repo.get_commits(branch_name="trunk") commit_indexes = [c.idx for c in commits] assert commit_indexes == [1, 2, 3, 7, 12, 15] @@ -214,13 +216,10 @@ class TestCommits(BackendTestMixin): start_date = datetime.datetime(2010, 1, 1, 20) for x in range(5): 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) - ], + "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)], } def test_simple(self): @@ -231,11 +230,11 @@ class TestCommits(BackendTestMixin): tip = self.repo.get_commit() # json.dumps(tip) uses .__json__() method data = tip.__json__() - assert 'branch' in data - assert data['revision'] + assert "branch" in data + assert data["revision"] def test_retrieve_tip(self): - tip = self.repo.get_commit('tip') + tip = self.repo.get_commit("tip") assert tip == self.repo.get_commit() def test_invalid(self): @@ -259,34 +258,34 @@ class TestCommits(BackendTestMixin): def test_size(self): tip = self.repo.get_commit() - size = 5 * len('Foobar N') # Size of 5 files + size = 5 * len("Foobar N") # Size of 5 files assert tip.size == size def test_size_at_commit(self): tip = self.repo.get_commit() - size = 5 * len('Foobar N') # Size of 5 files + size = 5 * len("Foobar N") # Size of 5 files assert self.repo.size_at_commit(tip.raw_id) == size def test_size_at_first_commit(self): commit = self.repo[0] - size = len('Foobar N') # Size of 1 file + size = len("Foobar N") # Size of 1 file assert self.repo.size_at_commit(commit.raw_id) == size def test_author(self): tip = self.repo.get_commit() - assert_text_equal(tip.author, 'Joe Doe ') + assert_text_equal(tip.author, "Joe Doe ") def test_author_name(self): tip = self.repo.get_commit() - assert_text_equal(tip.author_name, 'Joe Doe') + assert_text_equal(tip.author_name, "Joe Doe") def test_author_email(self): tip = self.repo.get_commit() - assert_text_equal(tip.author_email, 'joe.doe@example.com') + assert_text_equal(tip.author_email, "joe.doe@example.com") def test_message(self): tip = self.repo.get_commit() - assert_text_equal(tip.message, 'Commit 4') + assert_text_equal(tip.message, "Commit 4") def test_diff(self): tip = self.repo.get_commit() @@ -296,7 +295,7 @@ class TestCommits(BackendTestMixin): def test_prev(self): tip = self.repo.get_commit() prev_commit = tip.prev() - assert prev_commit.message == 'Commit 3' + assert prev_commit.message == "Commit 3" def test_prev_raises_on_first_commit(self): commit = self.repo.get_commit(commit_idx=0) @@ -311,7 +310,7 @@ class TestCommits(BackendTestMixin): def test_next(self): commit = self.repo.get_commit(commit_idx=2) next_commit = commit.next() - assert next_commit.message == 'Commit 3' + assert next_commit.message == "Commit 3" def test_next_raises_on_tip(self): commit = self.repo.get_commit() @@ -320,36 +319,36 @@ class TestCommits(BackendTestMixin): def test_get_path_commit(self): commit = self.repo.get_commit() - commit.get_path_commit('file_4.txt') - assert commit.message == 'Commit 4' + commit.get_path_commit("file_4.txt") + assert commit.message == "Commit 4" def test_get_filenodes_generator(self): tip = self.repo.get_commit() filepaths = [node.path for node in tip.get_filenodes_generator()] - assert filepaths == ['file_%d.txt' % x for x in range(5)] + assert filepaths == ["file_%d.txt" % x for x in range(5)] def test_get_file_annotate(self): file_added_commit = self.repo.get_commit(commit_idx=3) - annotations = list(file_added_commit.get_file_annotate('file_3.txt')) + annotations = list(file_added_commit.get_file_annotate("file_3.txt")) line_no, commit_id, commit_loader, line = annotations[0] assert line_no == 1 assert commit_id == file_added_commit.raw_id assert commit_loader() == file_added_commit - assert b'Foobar 3' in line + assert b"Foobar 3" in line def test_get_file_annotate_does_not_exist(self): file_added_commit = self.repo.get_commit(commit_idx=2) # TODO: Should use a specific exception class here? with pytest.raises(Exception): - list(file_added_commit.get_file_annotate('file_3.txt')) + list(file_added_commit.get_file_annotate("file_3.txt")) def test_get_file_annotate_tip(self): tip = self.repo.get_commit() commit = self.repo.get_commit(commit_idx=3) - expected_values = list(commit.get_file_annotate('file_3.txt')) - annotations = list(tip.get_file_annotate('file_3.txt')) + expected_values = list(commit.get_file_annotate("file_3.txt")) + annotations = list(tip.get_file_annotate("file_3.txt")) # Note: Skip index 2 because the loader function is not the same for idx in (0, 1, 3): @@ -398,7 +397,7 @@ class TestCommits(BackendTestMixin): repo = self.Backend(repo_path, create=True) with pytest.raises(EmptyRepositoryError): - list(repo.get_commits(start_id='foobar')) + list(repo.get_commits(start_id="foobar")) def test_get_commits_respects_hidden(self): commits = self.repo.get_commits(show_hidden=True) @@ -424,8 +423,7 @@ class TestCommits(BackendTestMixin): def test_get_commits_respects_start_date_with_branch(self): start_date = datetime.datetime(2010, 1, 2) - commits = self.repo.get_commits( - start_date=start_date, branch_name=self.repo.DEFAULT_BRANCH_NAME) + commits = self.repo.get_commits(start_date=start_date, branch_name=self.repo.DEFAULT_BRANCH_NAME) assert isinstance(commits, CollectionGenerator) # Should be 4 commits after 2010-01-02 00:00:00 assert len(commits) == 4 @@ -435,8 +433,7 @@ class TestCommits(BackendTestMixin): def test_get_commits_respects_start_date_and_end_date(self): start_date = datetime.datetime(2010, 1, 2) end_date = datetime.datetime(2010, 1, 3) - commits = self.repo.get_commits(start_date=start_date, - end_date=end_date) + commits = self.repo.get_commits(start_date=start_date, end_date=end_date) assert isinstance(commits, CollectionGenerator) assert len(commits) == 2 for c in commits: @@ -459,23 +456,22 @@ class TestCommits(BackendTestMixin): assert list(commit_ids) == list(reversed(self.repo.commit_ids)) def test_get_commits_slice_generator(self): - commits = self.repo.get_commits( - branch_name=self.repo.DEFAULT_BRANCH_NAME) + commits = self.repo.get_commits(branch_name=self.repo.DEFAULT_BRANCH_NAME) assert isinstance(commits, CollectionGenerator) commit_slice = list(commits[1:3]) assert len(commit_slice) == 2 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self): with pytest.raises(CommitDoesNotExistError): - list(self.repo.get_commits(start_id='foobar')) + list(self.repo.get_commits(start_id="foobar")) def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self): with pytest.raises(CommitDoesNotExistError): - list(self.repo.get_commits(end_id='foobar')) + list(self.repo.get_commits(end_id="foobar")) def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self): with pytest.raises(BranchDoesNotExistError): - list(self.repo.get_commits(branch_name='foobar')) + list(self.repo.get_commits(branch_name="foobar")) def test_get_commits_raise_repositoryerror_for_wrong_start_end(self): start_id = self.repo.commit_ids[-1] @@ -498,13 +494,16 @@ class TestCommits(BackendTestMixin): assert commit1 is not None assert commit2 is not None assert 1 != commit1 - assert 'string' != commit1 + assert "string" != commit1 -@pytest.mark.parametrize("filename, expected", [ - ("README.rst", False), - ("README", True), -]) +@pytest.mark.parametrize( + "filename, expected", + [ + ("README.rst", False), + ("README", True), + ], +) def test_commit_is_link(vcsbackend, filename, expected): commit = vcsbackend.repo.get_commit() link_status = commit.is_link(filename) @@ -519,75 +518,74 @@ class TestCommitsChanges(BackendTestMixin): def _get_commits(cls): return [ { - 'message': 'Initial', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 20), - 'added': [ - FileNode(b'foo/bar', content=b'foo'), - FileNode(safe_bytes('foo/bał'), content=b'foo'), - FileNode(b'foobar', content=b'foo'), - FileNode(b'qwe', content=b'foo'), + "message": "Initial", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 20), + "added": [ + FileNode(b"foo/bar", content=b"foo"), + FileNode(safe_bytes("foo/bał"), content=b"foo"), + FileNode(b"foobar", content=b"foo"), + FileNode(b"qwe", content=b"foo"), ], }, { - 'message': 'Massive changes', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 22), - 'added': [FileNode(b'fallout', content=b'War never changes')], - 'changed': [ - FileNode(b'foo/bar', content=b'baz'), - FileNode(b'foobar', content=b'baz'), + "message": "Massive changes", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 22), + "added": [FileNode(b"fallout", content=b"War never changes")], + "changed": [ + FileNode(b"foo/bar", content=b"baz"), + FileNode(b"foobar", content=b"baz"), ], - 'removed': [FileNode(b'qwe')], + "removed": [FileNode(b"qwe")], }, ] def test_initial_commit(self, local_dt_to_utc): commit = self.repo.get_commit(commit_idx=0) assert set(commit.added) == { - commit.get_node('foo/bar'), - commit.get_node('foo/bał'), - commit.get_node('foobar'), - commit.get_node('qwe') + commit.get_node("foo/bar"), + commit.get_node("foo/bał"), + commit.get_node("foobar"), + commit.get_node("qwe"), } assert set(commit.changed) == set() assert set(commit.removed) == set() - assert set(commit.affected_files) == {'foo/bar', 'foo/bał', 'foobar', 'qwe'} - assert commit.date == local_dt_to_utc( - datetime.datetime(2010, 1, 1, 20, 0)) + assert set(commit.affected_files) == {"foo/bar", "foo/bał", "foobar", "qwe"} + assert commit.date == local_dt_to_utc(datetime.datetime(2010, 1, 1, 20, 0)) def test_head_added(self): commit = self.repo.get_commit() assert isinstance(commit.added, AddedFileNodesGenerator) - assert set(commit.added) == {commit.get_node('fallout')} + assert set(commit.added) == {commit.get_node("fallout")} assert isinstance(commit.changed, ChangedFileNodesGenerator) - assert set(commit.changed) == {commit.get_node('foo/bar'), commit.get_node('foobar')} + assert set(commit.changed) == {commit.get_node("foo/bar"), commit.get_node("foobar")} assert isinstance(commit.removed, RemovedFileNodesGenerator) assert len(commit.removed) == 1 - assert list(commit.removed)[0].path == 'qwe' + assert list(commit.removed)[0].path == "qwe" def test_get_filemode(self): commit = self.repo.get_commit() - assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar') + assert FILEMODE_DEFAULT == commit.get_file_mode("foo/bar") def test_get_filemode_non_ascii(self): commit = self.repo.get_commit() - assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał') - assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał') + assert FILEMODE_DEFAULT == commit.get_file_mode("foo/bał") + assert FILEMODE_DEFAULT == commit.get_file_mode("foo/bał") def test_get_path_history(self): commit = self.repo.get_commit() - history = commit.get_path_history('foo/bar') + history = commit.get_path_history("foo/bar") assert len(history) == 2 def test_get_path_history_with_limit(self): commit = self.repo.get_commit() - history = commit.get_path_history('foo/bar', limit=1) + history = commit.get_path_history("foo/bar", limit=1) assert len(history) == 1 def test_get_path_history_first_commit(self): commit = self.repo[0] - history = commit.get_path_history('foo/bar') + history = commit.get_path_history("foo/bar") assert len(history) == 1 diff --git a/rhodecode/tests/vcs/test_config.py b/rhodecode/tests/vcs/test_config.py index 9aac9d64..4ecea4a2 100644 --- a/rhodecode/tests/vcs/test_config.py +++ b/rhodecode/tests/vcs/test_config.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -21,14 +20,17 @@ import pytest def test_get_existing_value(config): - value = config.get('section-a', 'a-1') - assert value == 'value-a-1' + value = config.get("section-a", "a-1") + assert value == "value-a-1" -@pytest.mark.parametrize('section, option', [ - ('section-a', 'does-not-exist'), - ('does-not-exist', 'does-not-exist'), -]) +@pytest.mark.parametrize( + "section, option", + [ + ("section-a", "does-not-exist"), + ("does-not-exist", "does-not-exist"), + ], +) def test_get_unset_value_returns_none(config, section, option): value = config.get(section, option) assert value is None @@ -41,11 +43,11 @@ def test_allows_to_create_a_copy(config): def test_changes_in_the_copy_dont_affect_the_original(config): clone = config.copy() - clone.set('section-a', 'a-2', 'value-a-2') - assert set(config.serialize()) == {('section-a', 'a-1', 'value-a-1')} + clone.set("section-a", "a-2", "value-a-2") + assert set(config.serialize()) == {("section-a", "a-1", "value-a-1")} def test_changes_in_the_original_dont_affect_the_copy(config): clone = config.copy() - config.set('section-a', 'a-2', 'value-a-2') - assert set(clone.serialize()) == {('section-a', 'a-1', 'value-a-1')} + config.set("section-a", "a-2", "value-a-2") + assert set(clone.serialize()) == {("section-a", "a-1", "value-a-1")} diff --git a/rhodecode/tests/vcs/test_diff.py b/rhodecode/tests/vcs/test_diff.py index 190b22b0..38b4a3f4 100644 --- a/rhodecode/tests/vcs/test_diff.py +++ b/rhodecode/tests/vcs/test_diff.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -25,7 +24,6 @@ from rhodecode.tests.vcs.conftest import BackendTestMixin class TestGetDiffValidation: - def test_raises_on_string_input(self, vcsbackend): repo = vcsbackend.repo with pytest.raises(TypeError): @@ -54,63 +52,58 @@ class TestGetDiffValidation: def test_supports_path1_parameter(self, vcsbackend): repo = vcsbackend.repo commit = repo[1] - repo.get_diff( - repo.EMPTY_COMMIT, commit, - path='vcs/__init__.py', path1='vcs/__init__.py') + repo.get_diff(repo.EMPTY_COMMIT, commit, path="vcs/__init__.py", path1="vcs/__init__.py") @pytest.mark.backends("git", "hg") def test_raises_value_error_if_paths_not_supported(self, vcsbackend): repo = vcsbackend.repo commit = repo[1] with pytest.raises(ValueError): - repo.get_diff( - repo.EMPTY_COMMIT, commit, - path='trunk/example.py', path1='branches/argparse/example.py') + repo.get_diff(repo.EMPTY_COMMIT, commit, path="trunk/example.py", path1="branches/argparse/example.py") @pytest.mark.usefixtures("vcs_repository_support") class TestRepositoryGetDiff(BackendTestMixin): - recreate_repo_per_test = False @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'foobar2'), + "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"foobar2"), ], }, { - 'message': 'Changed foobar, added foobar3', - 'author': 'Jane Doe ', - 'date': datetime.datetime(2010, 1, 1, 21), - 'added': [ - FileNode(b'foobar3', content=b'foobar3'), + "message": "Changed foobar, added foobar3", + "author": "Jane Doe ", + "date": datetime.datetime(2010, 1, 1, 21), + "added": [ + FileNode(b"foobar3", content=b"foobar3"), ], - 'changed': [ - FileNode(b'foobar', b'FOOBAR'), + "changed": [ + FileNode(b"foobar", b"FOOBAR"), ], }, { - 'message': 'Removed foobar, changed foobar3', - 'author': 'Jane Doe ', - 'date': datetime.datetime(2010, 1, 1, 22), - 'changed': [ - FileNode(b'foobar3', content=b'FOOBAR\nFOOBAR\nFOOBAR\n'), + "message": "Removed foobar, changed foobar3", + "author": "Jane Doe ", + "date": datetime.datetime(2010, 1, 1, 22), + "changed": [ + FileNode(b"foobar3", content=b"FOOBAR\nFOOBAR\nFOOBAR\n"), ], - 'removed': [FileNode(b'foobar')], + "removed": [FileNode(b"foobar")], }, { - 'message': 'Whitespace changes', - 'author': 'Jane Doe ', - 'date': datetime.datetime(2010, 1, 1, 23), - 'changed': [ - FileNode(b'foobar3', content=b'FOOBAR \nFOOBAR\nFOOBAR\n'), + "message": "Whitespace changes", + "author": "Jane Doe ", + "date": datetime.datetime(2010, 1, 1, 23), + "changed": [ + FileNode(b"foobar3", content=b"FOOBAR \nFOOBAR\nFOOBAR\n"), ], }, ] @@ -130,28 +123,24 @@ class TestRepositoryGetDiff(BackendTestMixin): assert diff.raw.tobytes() == self.third_commit_diffs[self.repo.alias] def test_ignore_whitespace(self): - diff = self.repo.get_diff( - self.repo[2], self.repo[3], ignore_whitespace=True) - assert b'@@' not in diff.raw.tobytes() + diff = self.repo.get_diff(self.repo[2], self.repo[3], ignore_whitespace=True) + assert b"@@" not in diff.raw.tobytes() def test_only_one_file(self): - diff = self.repo.get_diff( - self.repo.EMPTY_COMMIT, self.repo[0], path='foobar') - assert b'foobar2' not in diff.raw.tobytes() + diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0], path="foobar") + assert b"foobar2" not in diff.raw.tobytes() def test_context_parameter(self): first_commit = self.repo.get_commit(commit_idx=0) - diff = self.repo.get_diff( - self.repo.EMPTY_COMMIT, first_commit, context=2) + diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, first_commit, context=2) assert diff.raw.tobytes() == self.first_commit_diffs[self.repo.alias] def test_context_only_one_file(self): - diff = self.repo.get_diff( - self.repo.EMPTY_COMMIT, self.repo[0], path='foobar', context=2) + diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0], path="foobar", context=2) assert diff.raw.tobytes() == self.first_commit_one_file[self.repo.alias] first_commit_diffs = { - 'git': br"""diff --git a/foobar b/foobar + "git": rb"""diff --git a/foobar b/foobar new file mode 100644 index 0000000..f6ea049 --- /dev/null @@ -168,7 +157,7 @@ index 0000000..e8c9d6b +foobar2 \ No newline at end of file """, - 'hg': br"""diff --git a/foobar b/foobar + "hg": rb"""diff --git a/foobar b/foobar new file mode 100644 --- /dev/null +++ b/foobar @@ -183,7 +172,7 @@ new file mode 100644 +foobar2 \ No newline at end of file """, - 'svn': b"""Index: foobar + "svn": b"""Index: foobar =================================================================== diff --git a/foobar b/foobar new file mode 10644 @@ -205,7 +194,7 @@ new file mode 10644 } second_commit_diffs = { - 'git': br"""diff --git a/foobar b/foobar + "git": rb"""diff --git a/foobar b/foobar index f6ea049..389865b 100644 --- a/foobar +++ b/foobar @@ -223,7 +212,7 @@ index 0000000..c11c37d +foobar3 \ No newline at end of file """, - 'hg': br"""diff --git a/foobar b/foobar + "hg": rb"""diff --git a/foobar b/foobar --- a/foobar +++ b/foobar @@ -1,1 +1,1 @@ @@ -239,7 +228,7 @@ new file mode 100644 +foobar3 \ No newline at end of file """, - 'svn': b"""Index: foobar + "svn": b"""Index: foobar =================================================================== diff --git a/foobar b/foobar --- a/foobar\t(revision 1) @@ -262,7 +251,7 @@ new file mode 10644 } third_commit_diffs = { - 'git': br"""diff --git a/foobar b/foobar + "git": rb"""diff --git a/foobar b/foobar deleted file mode 100644 index 389865b..0000000 --- a/foobar @@ -281,7 +270,7 @@ index c11c37d..f932447 100644 +FOOBAR +FOOBAR """, - 'hg': br"""diff --git a/foobar b/foobar + "hg": rb"""diff --git a/foobar b/foobar deleted file mode 100644 --- a/foobar +++ /dev/null @@ -298,7 +287,7 @@ diff --git a/foobar3 b/foobar3 +FOOBAR +FOOBAR """, - 'svn': b"""Index: foobar + "svn": b"""Index: foobar =================================================================== diff --git a/foobar b/foobar deleted file mode 10644 @@ -322,7 +311,7 @@ diff --git a/foobar3 b/foobar3 } first_commit_one_file = { - 'git': br"""diff --git a/foobar b/foobar + "git": rb"""diff --git a/foobar b/foobar new file mode 100644 index 0000000..f6ea049 --- /dev/null @@ -331,7 +320,7 @@ index 0000000..f6ea049 +foobar \ No newline at end of file """, - 'hg': br"""diff --git a/foobar b/foobar + "hg": rb"""diff --git a/foobar b/foobar new file mode 100644 --- /dev/null +++ b/foobar @@ -339,7 +328,7 @@ new file mode 100644 +foobar \ No newline at end of file """, - 'svn': b"""Index: foobar + "svn": b"""Index: foobar =================================================================== diff --git a/foobar b/foobar new file mode 10644 @@ -353,13 +342,11 @@ new file mode 10644 class TestSvnGetDiff(object): - - @pytest.mark.parametrize('path, path1', [ - ('trunk/example.py', 'tags/v0.2/example.py'), - ('trunk', 'tags/v0.2') - ], ids=['file', 'dir']) + @pytest.mark.parametrize( + "path, path1", [("trunk/example.py", "tags/v0.2/example.py"), ("trunk", "tags/v0.2")], ids=["file", "dir"] + ) def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1): - repo = vcsbackend_svn['svn-simple-layout'] + repo = vcsbackend_svn["svn-simple-layout"] commit1 = repo[-2] commit2 = repo[-1] diff = repo.get_diff(commit1, commit2, path=path, path1=path1) @@ -386,7 +373,7 @@ diff --git a/example.py b/example.py ''' def test_diff_of_moved_directory(self, vcsbackend_svn): - repo = vcsbackend_svn['svn-move-directory'] + repo = vcsbackend_svn["svn-move-directory"] diff = repo.get_diff(repo[0], repo[1]) # TODO: johbo: Think about supporting svn directory nodes # a little bit better, source is here like a file @@ -408,7 +395,6 @@ new file mode 10644 @pytest.mark.usefixtures("vcs_repository_support") class TestGetDiffBinary(BackendTestMixin): - recreate_repo_per_test = False # Note: "Fake" PNG files, has the correct magic as prefix @@ -419,26 +405,29 @@ class TestGetDiffBinary(BackendTestMixin): def _get_commits(): commits = [ { - 'message': 'Add binary file image.png', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 20), - 'added': [ - FileNode(b'image.png', content=TestGetDiffBinary.BINARY), - ]}, + "message": "Add binary file image.png", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 20), + "added": [ + FileNode(b"image.png", content=TestGetDiffBinary.BINARY), + ], + }, { - 'message': 'Modify image.png', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 21), - 'changed': [ - FileNode(b'image.png', content=TestGetDiffBinary.BINARY2), - ]}, + "message": "Modify image.png", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 21), + "changed": [ + FileNode(b"image.png", content=TestGetDiffBinary.BINARY2), + ], + }, { - 'message': 'Remove image.png', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 21), - 'removed': [ - FileNode(b'image.png'), - ]}, + "message": "Remove image.png", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 21), + "removed": [ + FileNode(b"image.png"), + ], + }, ] return commits @@ -446,7 +435,7 @@ class TestGetDiffBinary(BackendTestMixin): diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0]) expected = { - 'git': b"""diff --git a/image.png b/image.png + "git": b"""diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..28380fd4a25c58be1b68b523ba2a314f4459ee9c GIT binary patch @@ -457,7 +446,7 @@ literal 0 Hc$@', - 'date': datetime.datetime(2010, 1, 1, 20), - 'added': nodes, + "message": "Initial commit", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 20), + "added": nodes, }, ] return commits diff --git a/rhodecode/tests/vcs/test_getitem.py b/rhodecode/tests/vcs/test_getitem.py index 883646ce..d95c7bd8 100644 --- a/rhodecode/tests/vcs/test_getitem.py +++ b/rhodecode/tests/vcs/test_getitem.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -28,28 +27,30 @@ from rhodecode.tests.vcs.conftest import BackendTestMixin @pytest.mark.usefixtures("vcs_repository_support") class TestGetitem(BackendTestMixin): - @classmethod def _get_commits(cls): start_date = datetime.datetime(2010, 1, 1, 20) for x in range(5): yield { - 'message': 'Commit %d' % x, - 'author': 'Joe Doe ', - 'date': start_date + datetime.timedelta(hours=12 * x), - 'added': [ - FileNode(b'file_%d.txt' % x, content='Foobar %d' % x), + "message": "Commit %d" % x, + "author": "Joe Doe ", + "date": start_date + datetime.timedelta(hours=12 * x), + "added": [ + FileNode(b"file_%d.txt" % x, content="Foobar %d" % x), ], } def test_last_item_is_tip(self): assert self.repo[-1] == self.repo.get_commit() - @pytest.mark.parametrize("offset, message", [ - (-1, 'Commit 4'), - (-2, 'Commit 3'), - (-5, 'Commit 0'), - ]) + @pytest.mark.parametrize( + "offset, message", + [ + (-1, "Commit 4"), + (-2, "Commit 3"), + (-5, "Commit 0"), + ], + ) def test_negative_offset_fetches_correct_commit(self, offset, message): assert self.repo[offset].message == message diff --git a/rhodecode/tests/vcs/test_getslice.py b/rhodecode/tests/vcs/test_getslice.py index add11b3f..225aaf83 100644 --- a/rhodecode/tests/vcs/test_getslice.py +++ b/rhodecode/tests/vcs/test_getslice.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -25,17 +24,16 @@ from rhodecode.tests.vcs.conftest import BackendTestMixin @pytest.mark.usefixtures("vcs_repository_support") class TestGetslice(BackendTestMixin): - @classmethod def _get_commits(cls): start_date = datetime.datetime(2010, 1, 1, 20) for x in range(5): yield { - 'message': 'Commit %d' % x, - 'author': 'Joe Doe ', - 'date': start_date + datetime.timedelta(hours=12 * x), - 'added': [ - FileNode(b'file_%d.txt' % x, content='Foobar %d' % x), + "message": "Commit %d" % x, + "author": "Joe Doe ", + "date": start_date + datetime.timedelta(hours=12 * x), + "added": [ + FileNode(b"file_%d.txt" % x, content="Foobar %d" % x), ], } @@ -43,34 +41,24 @@ class TestGetslice(BackendTestMixin): assert list(self.repo[-1:])[0] == self.repo.get_commit() def test__getslice__respects_start_index(self): - assert list(self.repo[2:]) == \ - [self.repo.get_commit(commit_id) - for commit_id in self.repo.commit_ids[2:]] + assert list(self.repo[2:]) == [self.repo.get_commit(commit_id) for commit_id in self.repo.commit_ids[2:]] def test__getslice__respects_negative_start_index(self): - assert list(self.repo[-2:]) == \ - [self.repo.get_commit(commit_id) - for commit_id in self.repo.commit_ids[-2:]] + assert list(self.repo[-2:]) == [self.repo.get_commit(commit_id) for commit_id in self.repo.commit_ids[-2:]] def test__getslice__respects_end_index(self): - assert list(self.repo[:2]) == \ - [self.repo.get_commit(commit_id) - for commit_id in self.repo.commit_ids[:2]] + assert list(self.repo[:2]) == [self.repo.get_commit(commit_id) for commit_id in self.repo.commit_ids[:2]] def test__getslice__respects_negative_end_index(self): - assert list(self.repo[:-2]) == \ - [self.repo.get_commit(commit_id) - for commit_id in self.repo.commit_ids[:-2]] + assert list(self.repo[:-2]) == [self.repo.get_commit(commit_id) for commit_id in self.repo.commit_ids[:-2]] def test__getslice__start_grater_than_end(self): assert list(self.repo[10:0]) == [] def test__getslice__negative_iteration(self): - assert list(self.repo[::-1]) == \ - [self.repo.get_commit(commit_id) - for commit_id in self.repo.commit_ids[::-1]] + assert list(self.repo[::-1]) == [self.repo.get_commit(commit_id) for commit_id in self.repo.commit_ids[::-1]] def test__getslice__iterate_even(self): - assert list(self.repo[0:10:2]) == \ - [self.repo.get_commit(commit_id) - for commit_id in self.repo.commit_ids[0:10:2]] + assert list(self.repo[0:10:2]) == [ + self.repo.get_commit(commit_id) for commit_id in self.repo.commit_ids[0:10:2] + ] diff --git a/rhodecode/tests/vcs/test_git.py b/rhodecode/tests/vcs/test_git.py index 3bdd6c41..48e3c7fc 100644 --- a/rhodecode/tests/vcs/test_git.py +++ b/rhodecode/tests/vcs/test_git.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -27,12 +26,9 @@ import pytest from rhodecode.lib.utils import make_db_config from rhodecode.lib.vcs.backends.base import Reference -from rhodecode.lib.vcs.backends.git import ( - GitRepository, GitCommit, discover_git_version) -from rhodecode.lib.vcs.exceptions import ( - RepositoryError, VCSError, NodeDoesNotExistError) -from rhodecode.lib.vcs.nodes import ( - NodeKind, FileNode, DirNode, NodeState, SubModuleNode) +from rhodecode.lib.vcs.backends.git import GitRepository, GitCommit, discover_git_version +from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError +from rhodecode.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState, SubModuleNode from rhodecode.tests import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir from rhodecode.tests.vcs.conftest import BackendTestMixin @@ -40,7 +36,7 @@ from rhodecode.tests.vcs.conftest import BackendTestMixin pytestmark = pytest.mark.backends("git") -DIFF_FROM_REMOTE = br"""diff --git a/foobar b/foobar +DIFF_FROM_REMOTE = rb"""diff --git a/foobar b/foobar new file mode 100644 index 0000000..f6ea049 --- /dev/null @@ -64,7 +60,6 @@ def callable_get_diff(*args, **kwargs): class TestGitRepository(object): - @pytest.fixture(autouse=True) def prepare(self, request, baseapp): self.repo = GitRepository(TEST_GIT_REPO, bare=True) @@ -74,9 +69,8 @@ class TestGitRepository(object): """ Return a non bare clone of the base repo. """ - clone_path = str(tmpdir.join('clone-repo')) - repo_clone = GitRepository( - clone_path, create=True, src_url=self.repo.path, bare=False) + clone_path = str(tmpdir.join("clone-repo")) + repo_clone = GitRepository(clone_path, create=True, src_url=self.repo.path, bare=False) return repo_clone @@ -84,20 +78,18 @@ class TestGitRepository(object): """ Return a non bare empty repo. """ - clone_path = str(tmpdir.join('empty-repo')) + clone_path = str(tmpdir.join("empty-repo")) return GitRepository(clone_path, create=True, bare=bare) def test_wrong_repo_path(self): - wrong_repo_path = '/tmp/errorrepo_git' + wrong_repo_path = "/tmp/errorrepo_git" with pytest.raises(RepositoryError): GitRepository(wrong_repo_path) def test_repo_clone(self, tmp_path_factory): repo = GitRepository(TEST_GIT_REPO) - clone_path = '{}_{}'.format(tmp_path_factory.mktemp('_'), TEST_GIT_REPO_CLONE) - repo_clone = GitRepository( - clone_path, - src_url=TEST_GIT_REPO, create=True, do_workspace_checkout=True) + clone_path = f"{tmp_path_factory.mktemp('_')}_{TEST_GIT_REPO_CLONE}" + repo_clone = GitRepository(clone_path, src_url=TEST_GIT_REPO, create=True, do_workspace_checkout=True) assert len(repo.commit_ids) == len(repo_clone.commit_ids) # Checking hashes of commits should be enough @@ -107,48 +99,42 @@ class TestGitRepository(object): def test_repo_clone_without_create(self): with pytest.raises(RepositoryError): - GitRepository( - TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO) + GitRepository(TEST_GIT_REPO_CLONE + "_wo_create", src_url=TEST_GIT_REPO) def test_repo_clone_with_update(self, tmp_path_factory): repo = GitRepository(TEST_GIT_REPO) - clone_path = '{}_{}_update'.format(tmp_path_factory.mktemp('_'), TEST_GIT_REPO_CLONE) + clone_path = "{}_{}_update".format(tmp_path_factory.mktemp("_"), TEST_GIT_REPO_CLONE) - repo_clone = GitRepository( - clone_path, - create=True, src_url=TEST_GIT_REPO, do_workspace_checkout=True) + repo_clone = GitRepository(clone_path, create=True, src_url=TEST_GIT_REPO, do_workspace_checkout=True) assert len(repo.commit_ids) == len(repo_clone.commit_ids) # check if current workdir was updated - fpath = os.path.join(clone_path, 'MANIFEST.in') + fpath = os.path.join(clone_path, "MANIFEST.in") assert os.path.isfile(fpath) def test_repo_clone_without_update(self, tmp_path_factory): repo = GitRepository(TEST_GIT_REPO) - clone_path = '{}_{}_without_update'.format(tmp_path_factory.mktemp('_'), TEST_GIT_REPO_CLONE) - repo_clone = GitRepository( - clone_path, - create=True, src_url=TEST_GIT_REPO, do_workspace_checkout=False) + clone_path = "{}_{}_without_update".format(tmp_path_factory.mktemp("_"), TEST_GIT_REPO_CLONE) + repo_clone = GitRepository(clone_path, create=True, src_url=TEST_GIT_REPO, do_workspace_checkout=False) assert len(repo.commit_ids) == len(repo_clone.commit_ids) # check if current workdir was *NOT* updated - fpath = os.path.join(clone_path, 'MANIFEST.in') + fpath = os.path.join(clone_path, "MANIFEST.in") # Make sure it's not bare repo assert not repo_clone.bare assert not os.path.isfile(fpath) def test_repo_clone_into_bare_repo(self, tmp_path_factory): repo = GitRepository(TEST_GIT_REPO) - clone_path = '{}_{}_bare.git'.format(tmp_path_factory.mktemp('_'), TEST_GIT_REPO_CLONE) - repo_clone = GitRepository( - clone_path, create=True, src_url=repo.path, bare=True) + clone_path = "{}_{}_bare.git".format(tmp_path_factory.mktemp("_"), TEST_GIT_REPO_CLONE) + repo_clone = GitRepository(clone_path, create=True, src_url=repo.path, bare=True) assert repo_clone.bare def test_create_repo_is_not_bare_by_default(self): - repo = GitRepository(get_new_dir('not-bare-by-default'), create=True) + repo = GitRepository(get_new_dir("not-bare-by-default"), create=True) assert not repo.bare def test_create_bare_repo(self): - repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True) + repo = GitRepository(get_new_dir("bare-repo"), create=True, bare=True) assert repo.bare def test_update_server_info(self): @@ -167,37 +153,38 @@ class TestGitRepository(object): def test_commit_ids(self): # there are 112 commits (by now) # so we can assume they would be available from now on - subset = {'c1214f7e79e02fc37156ff215cd71275450cffc3', - '38b5fe81f109cb111f549bfe9bb6b267e10bc557', - 'fa6600f6848800641328adbf7811fd2372c02ab2', - '102607b09cdd60e2793929c4f90478be29f85a17', - '49d3fd156b6f7db46313fac355dca1a0b94a0017', - '2d1028c054665b962fa3d307adfc923ddd528038', - 'd7e0d30fbcae12c90680eb095a4f5f02505ce501', - 'ff7ca51e58c505fec0dd2491de52c622bb7a806b', - 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f', - '8430a588b43b5d6da365400117c89400326e7992', - 'd955cd312c17b02143c04fa1099a352b04368118', - 'f67b87e5c629c2ee0ba58f85197e423ff28d735b', - 'add63e382e4aabc9e1afdc4bdc24506c269b7618', - 'f298fe1189f1b69779a4423f40b48edf92a703fc', - 'bd9b619eb41994cac43d67cf4ccc8399c1125808', - '6e125e7c890379446e98980d8ed60fba87d0f6d1', - 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd', - '0b05e4ed56c802098dfc813cbe779b2f49e92500', - '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e', - '45223f8f114c64bf4d6f853e3c35a369a6305520', - 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e', - 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68', - '27d48942240f5b91dfda77accd2caac94708cc7d', - '622f0eb0bafd619d2560c26f80f09e3b0b0d78af', - 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'} + subset = { + "c1214f7e79e02fc37156ff215cd71275450cffc3", + "38b5fe81f109cb111f549bfe9bb6b267e10bc557", + "fa6600f6848800641328adbf7811fd2372c02ab2", + "102607b09cdd60e2793929c4f90478be29f85a17", + "49d3fd156b6f7db46313fac355dca1a0b94a0017", + "2d1028c054665b962fa3d307adfc923ddd528038", + "d7e0d30fbcae12c90680eb095a4f5f02505ce501", + "ff7ca51e58c505fec0dd2491de52c622bb7a806b", + "dd80b0f6cf5052f17cc738c2951c4f2070200d7f", + "8430a588b43b5d6da365400117c89400326e7992", + "d955cd312c17b02143c04fa1099a352b04368118", + "f67b87e5c629c2ee0ba58f85197e423ff28d735b", + "add63e382e4aabc9e1afdc4bdc24506c269b7618", + "f298fe1189f1b69779a4423f40b48edf92a703fc", + "bd9b619eb41994cac43d67cf4ccc8399c1125808", + "6e125e7c890379446e98980d8ed60fba87d0f6d1", + "d4a54db9f745dfeba6933bf5b1e79e15d0af20bd", + "0b05e4ed56c802098dfc813cbe779b2f49e92500", + "191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", + "45223f8f114c64bf4d6f853e3c35a369a6305520", + "ca1eb7957a54bce53b12d1a51b13452f95bc7c7e", + "f5ea29fc42ef67a2a5a7aecff10e1566699acd68", + "27d48942240f5b91dfda77accd2caac94708cc7d", + "622f0eb0bafd619d2560c26f80f09e3b0b0d78af", + "e686b958768ee96af8029fe19c6050b1a8dd3b2b", + } assert subset.issubset(set(self.repo.commit_ids)) def test_slicing(self): # 4 1 5 10 95 - for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5), - (10, 20, 10), (5, 100, 95)]: + for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5), (10, 20, 10), (5, 100, 95)]: commit_ids = list(self.repo[sfrom:sto]) assert len(commit_ids) == size assert commit_ids[0] == self.repo.get_commit(commit_idx=sfrom) @@ -214,8 +201,8 @@ class TestGitRepository(object): def test_tags(self): # TODO: Need more tests here - assert 'v0.1.1' in self.repo.tags - assert 'v0.1.2' in self.repo.tags + assert "v0.1.1" in self.repo.tags + assert "v0.1.2" in self.repo.tags for __, commit_id in self.repo.tags.items(): assert isinstance(self.repo.get_commit(commit_id), GitCommit) @@ -229,37 +216,34 @@ class TestGitRepository(object): init_commit = self.repo.get_commit(commit_id) init_author = init_commit.author - assert init_commit.message == 'initial import\n' - assert init_author == 'Marcin Kuzminski ' + assert init_commit.message == "initial import\n" + assert init_author == "Marcin Kuzminski " assert init_author == init_commit.committer - for path in ('vcs/__init__.py', - 'vcs/backends/BaseRepository.py', - 'vcs/backends/__init__.py'): + for path in ("vcs/__init__.py", "vcs/backends/BaseRepository.py", "vcs/backends/__init__.py"): assert isinstance(init_commit.get_node(path), FileNode) - for path in ('', 'vcs', 'vcs/backends'): + for path in ("", "vcs", "vcs/backends"): assert isinstance(init_commit.get_node(path), DirNode) with pytest.raises(NodeDoesNotExistError): - init_commit.get_node(path='foobar') + init_commit.get_node(path="foobar") - node = init_commit.get_node('vcs/') - assert hasattr(node, 'kind') + node = init_commit.get_node("vcs/") + assert hasattr(node, "kind") assert node.kind == NodeKind.DIR - node = init_commit.get_node('vcs') - assert hasattr(node, 'kind') + node = init_commit.get_node("vcs") + assert hasattr(node, "kind") assert node.kind == NodeKind.DIR - node = init_commit.get_node('vcs/__init__.py') - assert hasattr(node, 'kind') + node = init_commit.get_node("vcs/__init__.py") + assert hasattr(node, "kind") assert node.kind == NodeKind.FILE def test_not_existing_commit(self): with pytest.raises(RepositoryError): - self.repo.get_commit('f' * 40) + self.repo.get_commit("f" * 40) def test_commit10(self): - commit10 = self.repo.get_commit(self.repo.commit_ids[9]) README = """=== VCS @@ -273,7 +257,7 @@ Introduction TODO: To be written... """ - node = commit10.get_node('README.rst') + node = commit10.get_node("README.rst") assert node.kind == NodeKind.FILE assert node.str_content == README @@ -283,39 +267,39 @@ TODO: To be written... def test_checkout_with_create(self, tmpdir): repo_clone = self.get_clone_repo(tmpdir) - new_branch = 'new_branch' - assert repo_clone._current_branch() == 'master' - assert set(repo_clone.branches) == {'master'} + new_branch = "new_branch" + assert repo_clone._current_branch() == "master" + assert set(repo_clone.branches) == {"master"} repo_clone._checkout(new_branch, create=True) # Branches is a lazy property so we need to recrete the Repo object. repo_clone = GitRepository(repo_clone.path) - assert set(repo_clone.branches) == {'master', new_branch} + assert set(repo_clone.branches) == {"master", new_branch} assert repo_clone._current_branch() == new_branch def test_checkout(self, tmpdir): repo_clone = self.get_clone_repo(tmpdir) - repo_clone._checkout('new_branch', create=True) - repo_clone._checkout('master') + repo_clone._checkout("new_branch", create=True) + repo_clone._checkout("master") - assert repo_clone._current_branch() == 'master' + assert repo_clone._current_branch() == "master" def test_checkout_same_branch(self, tmpdir): repo_clone = self.get_clone_repo(tmpdir) - repo_clone._checkout('master') - assert repo_clone._current_branch() == 'master' + repo_clone._checkout("master") + assert repo_clone._current_branch() == "master" def test_checkout_branch_already_exists(self, tmpdir): repo_clone = self.get_clone_repo(tmpdir) with pytest.raises(RepositoryError): - repo_clone._checkout('master', create=True) + repo_clone._checkout("master", create=True) def test_checkout_bare_repo(self): with pytest.raises(RepositoryError): - self.repo._checkout('master') + self.repo._checkout("master") def test_current_branch_bare_repo(self): with pytest.raises(RepositoryError): @@ -326,8 +310,8 @@ TODO: To be written... assert repo._current_branch() is None def test_local_clone(self, tmp_path_factory): - clone_path = str(tmp_path_factory.mktemp('test-local-clone')) - self.repo._local_clone(clone_path, 'master') + clone_path = str(tmp_path_factory.mktemp("test-local-clone")) + self.repo._local_clone(clone_path, "master") repo_clone = GitRepository(clone_path) assert self.repo.commit_ids == repo_clone.commit_ids @@ -338,23 +322,23 @@ TODO: To be written... # Create a new branch in source repo new_branch_commit = source_repo.commit_ids[-3] source_repo._checkout(new_branch_commit) - source_repo._checkout('new_branch', create=True) + source_repo._checkout("new_branch", create=True) - clone_path = str(tmpdir.join('git-clone-path-1')) - source_repo._local_clone(clone_path, 'new_branch') + clone_path = str(tmpdir.join("git-clone-path-1")) + source_repo._local_clone(clone_path, "new_branch") repo_clone = GitRepository(clone_path) - assert source_repo.commit_ids[:-3 + 1] == repo_clone.commit_ids + assert source_repo.commit_ids[: -3 + 1] == repo_clone.commit_ids - clone_path = str(tmpdir.join('git-clone-path-2')) - source_repo._local_clone(clone_path, 'master') + clone_path = str(tmpdir.join("git-clone-path-2")) + source_repo._local_clone(clone_path, "master") repo_clone = GitRepository(clone_path) assert source_repo.commit_ids == repo_clone.commit_ids def test_local_clone_fails_if_target_exists(self): with pytest.raises(RepositoryError): - self.repo._local_clone(self.repo.path, 'master') + self.repo._local_clone(self.repo.path, "master") def test_local_fetch(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) @@ -364,30 +348,30 @@ TODO: To be written... master_commit = source_repo.commit_ids[-1] new_branch_commit = source_repo.commit_ids[-3] source_repo._checkout(new_branch_commit) - source_repo._checkout('new_branch', create=True) + source_repo._checkout("new_branch", create=True) - target_repo._local_fetch(source_repo.path, 'new_branch') + target_repo._local_fetch(source_repo.path, "new_branch") assert target_repo._last_fetch_heads() == [new_branch_commit] - target_repo._local_fetch(source_repo.path, 'master') + target_repo._local_fetch(source_repo.path, "master") assert target_repo._last_fetch_heads() == [master_commit] def test_local_fetch_from_bare_repo(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) - target_repo._local_fetch(self.repo.path, 'master') + target_repo._local_fetch(self.repo.path, "master") master_commit = self.repo.commit_ids[-1] assert target_repo._last_fetch_heads() == [master_commit] def test_local_fetch_from_same_repo(self): with pytest.raises(ValueError): - self.repo._local_fetch(self.repo.path, 'master') + self.repo._local_fetch(self.repo.path, "master") def test_local_fetch_branch_does_not_exist(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) with pytest.raises(RepositoryError): - target_repo._local_fetch(self.repo.path, 'new_branch') + target_repo._local_fetch(self.repo.path, "new_branch") def test_local_pull(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) @@ -397,19 +381,19 @@ TODO: To be written... master_commit = source_repo.commit_ids[-1] new_branch_commit = source_repo.commit_ids[-3] source_repo._checkout(new_branch_commit) - source_repo._checkout('new_branch', create=True) + source_repo._checkout("new_branch", create=True) - target_repo._local_pull(source_repo.path, 'new_branch') + target_repo._local_pull(source_repo.path, "new_branch") target_repo = GitRepository(target_repo.path) assert target_repo.head == new_branch_commit - target_repo._local_pull(source_repo.path, 'master') + target_repo._local_pull(source_repo.path, "master") target_repo = GitRepository(target_repo.path) assert target_repo.head == master_commit def test_local_pull_in_bare_repo(self): with pytest.raises(RepositoryError): - self.repo._local_pull(self.repo.path, 'master') + self.repo._local_pull(self.repo.path, "master") def test_local_merge(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) @@ -419,159 +403,144 @@ TODO: To be written... master_commit = source_repo.commit_ids[-1] new_branch_commit = source_repo.commit_ids[-3] source_repo._checkout(new_branch_commit) - source_repo._checkout('new_branch', create=True) + source_repo._checkout("new_branch", create=True) # This is required as one cannot do a -ff-only merge in an empty repo. - target_repo._local_pull(source_repo.path, 'new_branch') + target_repo._local_pull(source_repo.path, "new_branch") - target_repo._local_fetch(source_repo.path, 'master') - merge_message = 'Merge message\n\nDescription:...' - user_name = 'Albert Einstein' - user_email = 'albert@einstein.com' - target_repo._local_merge(merge_message, user_name, user_email, - target_repo._last_fetch_heads()) + target_repo._local_fetch(source_repo.path, "master") + merge_message = "Merge message\n\nDescription:..." + user_name = "Albert Einstein" + user_email = "albert@einstein.com" + target_repo._local_merge(merge_message, user_name, user_email, target_repo._last_fetch_heads()) target_repo = GitRepository(target_repo.path) assert target_repo.commit_ids[-2] == master_commit last_commit = target_repo.get_commit(target_repo.head) assert last_commit.message.strip() == merge_message - assert last_commit.author == '%s <%s>' % (user_name, user_email) + assert last_commit.author == "%s <%s>" % (user_name, user_email) - assert not os.path.exists( - os.path.join(target_repo.path, '.git', 'MERGE_HEAD')) + assert not os.path.exists(os.path.join(target_repo.path, ".git", "MERGE_HEAD")) def test_local_merge_raises_exception_on_conflict(self, vcsbackend_git): target_repo = vcsbackend_git.create_repo(number_of_commits=1) - vcsbackend_git.ensure_file(b'README', b'I will conflict with you!!!') + vcsbackend_git.ensure_file(b"README", b"I will conflict with you!!!") - target_repo._local_fetch(self.repo.path, 'master') + target_repo._local_fetch(self.repo.path, "master") with pytest.raises(RepositoryError): - target_repo._local_merge( - 'merge_message', 'user name', 'user@name.com', - target_repo._last_fetch_heads()) + target_repo._local_merge("merge_message", "user name", "user@name.com", target_repo._last_fetch_heads()) # Check we are not left in an intermediate merge state - assert not os.path.exists( - os.path.join(target_repo.path, '.git', 'MERGE_HEAD')) + assert not os.path.exists(os.path.join(target_repo.path, ".git", "MERGE_HEAD")) def test_local_merge_into_empty_repo(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) # This is required as one cannot do a -ff-only merge in an empty repo. - target_repo._local_fetch(self.repo.path, 'master') + target_repo._local_fetch(self.repo.path, "master") with pytest.raises(RepositoryError): - target_repo._local_merge( - 'merge_message', 'user name', 'user@name.com', - target_repo._last_fetch_heads()) + target_repo._local_merge("merge_message", "user name", "user@name.com", target_repo._last_fetch_heads()) def test_local_merge_in_bare_repo(self): with pytest.raises(RepositoryError): - self.repo._local_merge( - 'merge_message', 'user name', 'user@name.com', None) + self.repo._local_merge("merge_message", "user name", "user@name.com", None) def test_local_push_non_bare(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) - pushed_branch = 'pushed_branch' - self.repo._local_push('master', target_repo.path, pushed_branch) + pushed_branch = "pushed_branch" + self.repo._local_push("master", target_repo.path, pushed_branch) # Fix the HEAD of the target repo, or otherwise GitRepository won't # report any branches. - with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f: - f.write('ref: refs/heads/%s' % pushed_branch) + with open(os.path.join(target_repo.path, ".git", "HEAD"), "w") as f: + f.write("ref: refs/heads/%s" % pushed_branch) target_repo = GitRepository(target_repo.path) - assert (target_repo.branches[pushed_branch] == - self.repo.branches['master']) + assert target_repo.branches[pushed_branch] == self.repo.branches["master"] def test_local_push_bare(self, tmpdir): target_repo = self.get_empty_repo(tmpdir, bare=True) - pushed_branch = 'pushed_branch' - self.repo._local_push('master', target_repo.path, pushed_branch) + pushed_branch = "pushed_branch" + self.repo._local_push("master", target_repo.path, pushed_branch) # Fix the HEAD of the target repo, or otherwise GitRepository won't # report any branches. - with open(os.path.join(target_repo.path, 'HEAD'), 'w') as f: - f.write('ref: refs/heads/%s' % pushed_branch) + with open(os.path.join(target_repo.path, "HEAD"), "w") as f: + f.write("ref: refs/heads/%s" % pushed_branch) target_repo = GitRepository(target_repo.path) - assert (target_repo.branches[pushed_branch] == - self.repo.branches['master']) + assert target_repo.branches[pushed_branch] == self.repo.branches["master"] def test_local_push_non_bare_target_branch_is_checked_out(self, tmpdir): target_repo = self.get_clone_repo(tmpdir) - pushed_branch = 'pushed_branch' + pushed_branch = "pushed_branch" # Create a new branch in source repo new_branch_commit = target_repo.commit_ids[-3] target_repo._checkout(new_branch_commit) target_repo._checkout(pushed_branch, create=True) - self.repo._local_push('master', target_repo.path, pushed_branch) + self.repo._local_push("master", target_repo.path, pushed_branch) target_repo = GitRepository(target_repo.path) - assert (target_repo.branches[pushed_branch] == - self.repo.branches['master']) + assert target_repo.branches[pushed_branch] == self.repo.branches["master"] def test_local_push_raises_exception_on_conflict(self, vcsbackend_git): target_repo = vcsbackend_git.create_repo(number_of_commits=1) with pytest.raises(RepositoryError): - self.repo._local_push('master', target_repo.path, 'master') + self.repo._local_push("master", target_repo.path, "master") def test_hooks_can_be_enabled_via_env_variable_for_local_push(self, tmpdir): target_repo = self.get_empty_repo(tmpdir, bare=True) - with mock.patch.object(self.repo, 'run_git_command') as run_mock: - self.repo._local_push( - 'master', target_repo.path, 'master', enable_hooks=True) - env = run_mock.call_args[1]['extra_env'] - assert 'RC_SKIP_HOOKS' not in env + with mock.patch.object(self.repo, "run_git_command") as run_mock: + self.repo._local_push("master", target_repo.path, "master", enable_hooks=True) + env = run_mock.call_args[1]["extra_env"] + assert "RC_SKIP_HOOKS" not in env def _add_failing_hook(self, repo_path, hook_name, bare=False): - path_components = ( - ['hooks', hook_name] if bare else ['.git', 'hooks', hook_name]) + path_components = ["hooks", hook_name] if bare else [".git", "hooks", hook_name] hook_path = os.path.join(repo_path, *path_components) - with open(hook_path, 'w') as f: + with open(hook_path, "w") as f: script_lines = [ - '#!%s' % sys.executable, - 'import os', - 'import sys', + "#!%s" % sys.executable, + "import os", + "import sys", 'if os.environ.get("RC_SKIP_HOOKS"):', - ' sys.exit(0)', - 'sys.exit(1)', + " sys.exit(0)", + "sys.exit(1)", ] - f.write('\n'.join(script_lines)) + f.write("\n".join(script_lines)) os.chmod(hook_path, 0o755) def test_local_push_does_not_execute_hook(self, tmpdir): target_repo = self.get_empty_repo(tmpdir) - pushed_branch = 'pushed_branch' - self._add_failing_hook(target_repo.path, 'pre-receive') - self.repo._local_push('master', target_repo.path, pushed_branch) + pushed_branch = "pushed_branch" + self._add_failing_hook(target_repo.path, "pre-receive") + self.repo._local_push("master", target_repo.path, pushed_branch) # Fix the HEAD of the target repo, or otherwise GitRepository won't # report any branches. - with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f: - f.write('ref: refs/heads/%s' % pushed_branch) + with open(os.path.join(target_repo.path, ".git", "HEAD"), "w") as f: + f.write("ref: refs/heads/%s" % pushed_branch) target_repo = GitRepository(target_repo.path) - assert (target_repo.branches[pushed_branch] == - self.repo.branches['master']) + assert target_repo.branches[pushed_branch] == self.repo.branches["master"] def test_local_push_executes_hook(self, tmpdir): target_repo = self.get_empty_repo(tmpdir, bare=True) - self._add_failing_hook(target_repo.path, 'pre-receive', bare=True) + self._add_failing_hook(target_repo.path, "pre-receive", bare=True) with pytest.raises(RepositoryError): - self.repo._local_push( - 'master', target_repo.path, 'master', enable_hooks=True) + self.repo._local_push("master", target_repo.path, "master", enable_hooks=True) def test_maybe_prepare_merge_workspace(self): workspace = self.repo._maybe_prepare_merge_workspace( - 2, 'pr2', Reference('branch', 'master', 'unused'), - Reference('branch', 'master', 'unused')) + 2, "pr2", Reference("branch", "master", "unused"), Reference("branch", "master", "unused") + ) assert os.path.isdir(workspace) workspace_repo = GitRepository(workspace) @@ -579,14 +548,14 @@ TODO: To be written... # Calling it a second time should also succeed workspace = self.repo._maybe_prepare_merge_workspace( - 2, 'pr2', Reference('branch', 'master', 'unused'), - Reference('branch', 'master', 'unused')) + 2, "pr2", Reference("branch", "master", "unused"), Reference("branch", "master", "unused") + ) assert os.path.isdir(workspace) def test_maybe_prepare_merge_workspace_different_refs(self): workspace = self.repo._maybe_prepare_merge_workspace( - 2, 'pr2', Reference('branch', 'master', 'unused'), - Reference('branch', 'develop', 'unused')) + 2, "pr2", Reference("branch", "master", "unused"), Reference("branch", "develop", "unused") + ) assert os.path.isdir(workspace) workspace_repo = GitRepository(workspace) @@ -594,48 +563,47 @@ TODO: To be written... # Calling it a second time should also succeed workspace = self.repo._maybe_prepare_merge_workspace( - 2, 'pr2', Reference('branch', 'master', 'unused'), - Reference('branch', 'develop', 'unused')) + 2, "pr2", Reference("branch", "master", "unused"), Reference("branch", "develop", "unused") + ) assert os.path.isdir(workspace) def test_cleanup_merge_workspace(self): workspace = self.repo._maybe_prepare_merge_workspace( - 2, 'pr3', Reference('branch', 'master', 'unused'), - Reference('branch', 'master', 'unused')) - self.repo.cleanup_merge_workspace(2, 'pr3') + 2, "pr3", Reference("branch", "master", "unused"), Reference("branch", "master", "unused") + ) + self.repo.cleanup_merge_workspace(2, "pr3") assert not os.path.exists(workspace) def test_cleanup_merge_workspace_invalid_workspace_id(self): # No assert: because in case of an inexistent workspace this function # should still succeed. - self.repo.cleanup_merge_workspace(1, 'pr4') + self.repo.cleanup_merge_workspace(1, "pr4") def test_set_refs(self): - test_ref = 'refs/test-refs/abcde' - test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623' + test_ref = "refs/test-refs/abcde" + test_commit_id = "ecb86e1f424f2608262b130db174a7dfd25a6623" self.repo.set_refs(test_ref, test_commit_id) - stdout, _ = self.repo.run_git_command(['show-ref']) + stdout, _ = self.repo.run_git_command(["show-ref"]) assert test_ref in stdout assert test_commit_id in stdout def test_remove_ref(self): - test_ref = 'refs/test-refs/abcde' - test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623' + test_ref = "refs/test-refs/abcde" + test_commit_id = "ecb86e1f424f2608262b130db174a7dfd25a6623" self.repo.set_refs(test_ref, test_commit_id) - stdout, _ = self.repo.run_git_command(['show-ref']) + stdout, _ = self.repo.run_git_command(["show-ref"]) assert test_ref in stdout assert test_commit_id in stdout self.repo.remove_ref(test_ref) - stdout, _ = self.repo.run_git_command(['show-ref']) + stdout, _ = self.repo.run_git_command(["show-ref"]) assert test_ref not in stdout assert test_commit_id not in stdout class TestGitCommit(object): - @pytest.fixture(autouse=True) def prepare(self): self.repo = GitRepository(TEST_GIT_REPO) @@ -643,11 +611,11 @@ class TestGitCommit(object): def test_default_commit(self): tip = self.repo.get_commit() assert tip == self.repo.get_commit(None) - assert tip == self.repo.get_commit('tip') + assert tip == self.repo.get_commit("tip") def test_root_node(self): tip = self.repo.get_commit() - assert tip.root is tip.get_node('') + assert tip.root is tip.get_node("") def test_lazy_fetch(self): """ @@ -655,7 +623,7 @@ class TestGitCommit(object): the commit. This test is somewhat hard to write as order of tests is a key here. Written by running command after command in a shell. """ - commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc' + commit_id = "2a13f185e4525f9d4b59882791a2d397b90d5ddc" assert commit_id in self.repo.commit_ids commit = self.repo.get_commit(commit_id) assert len(commit.nodes) == 0 @@ -665,31 +633,29 @@ class TestGitCommit(object): # accessing root.nodes updates commit.nodes assert len(commit.nodes) == 9 - docs = root.get_node('docs') + docs = root.get_node("docs") # we haven't yet accessed anything new as docs dir was already cached assert len(commit.nodes) == 9 assert len(docs.nodes) == 8 # accessing docs.nodes updates commit.nodes assert len(commit.nodes) == 17 - assert docs is commit.get_node('docs') + assert docs is commit.get_node("docs") assert docs is root.nodes[0] assert docs is root.dirs[0] - assert docs is commit.get_node('docs') + assert docs is commit.get_node("docs") def test_nodes_with_commit(self): - commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc' + commit_id = "2a13f185e4525f9d4b59882791a2d397b90d5ddc" commit = self.repo.get_commit(commit_id) root = commit.root - docs = root.get_node('docs') - assert docs is commit.get_node('docs') - api = docs.get_node('api') - assert api is commit.get_node('docs/api') - index = api.get_node('index.rst') - assert index is commit.get_node('docs/api/index.rst') - assert index is commit.get_node('docs')\ - .get_node('api')\ - .get_node('index.rst') + docs = root.get_node("docs") + assert docs is commit.get_node("docs") + api = docs.get_node("api") + assert api is commit.get_node("docs/api") + index = api.get_node("index.rst") + assert index is commit.get_node("docs/api/index.rst") + assert index is commit.get_node("docs").get_node("api").get_node("index.rst") def test_branch_and_tags(self): """ @@ -716,19 +682,12 @@ class TestGitCommit(object): def test_file_size(self): to_check = ( - ('c1214f7e79e02fc37156ff215cd71275450cffc3', - 'vcs/backends/BaseRepository.py', 502), - ('d7e0d30fbcae12c90680eb095a4f5f02505ce501', - 'vcs/backends/hg.py', 854), - ('6e125e7c890379446e98980d8ed60fba87d0f6d1', - 'setup.py', 1068), - - ('d955cd312c17b02143c04fa1099a352b04368118', - 'vcs/backends/base.py', 2921), - ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e', - 'vcs/backends/base.py', 3936), - ('f50f42baeed5af6518ef4b0cb2f1423f3851a941', - 'vcs/backends/base.py', 6189), + ("c1214f7e79e02fc37156ff215cd71275450cffc3", "vcs/backends/BaseRepository.py", 502), + ("d7e0d30fbcae12c90680eb095a4f5f02505ce501", "vcs/backends/hg.py", 854), + ("6e125e7c890379446e98980d8ed60fba87d0f6d1", "setup.py", 1068), + ("d955cd312c17b02143c04fa1099a352b04368118", "vcs/backends/base.py", 2921), + ("ca1eb7957a54bce53b12d1a51b13452f95bc7c7e", "vcs/backends/base.py", 3936), + ("f50f42baeed5af6518ef4b0cb2f1423f3851a941", "vcs/backends/base.py", 6189), ) for commit_id, path, size in to_check: node = self.repo.get_commit(commit_id).get_node(path) @@ -736,80 +695,77 @@ class TestGitCommit(object): assert node.size == size def test_file_history_from_commits(self): - node = self.repo[10].get_node('setup.py') + node = self.repo[10].get_node("setup.py") commit_ids = [commit.raw_id for commit in node.history] - assert ['ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == commit_ids + assert ["ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == commit_ids - node = self.repo[20].get_node('setup.py') + node = self.repo[20].get_node("setup.py") node_ids = [commit.raw_id for commit in node.history] - assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e', - 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids + assert ["191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == node_ids # special case we check history from commit that has this particular # file changed this means we check if it's included as well - node = self.repo.get_commit('191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e') \ - .get_node('setup.py') + node = self.repo.get_commit("191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e").get_node("setup.py") node_ids = [commit.raw_id for commit in node.history] - assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e', - 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids + assert ["191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == node_ids def test_file_history(self): # we can only check if those commits are present in the history # as we cannot update this test every time file is changed files = { - 'setup.py': [ - '54386793436c938cff89326944d4c2702340037d', - '51d254f0ecf5df2ce50c0b115741f4cf13985dab', - '998ed409c795fec2012b1c0ca054d99888b22090', - '5e0eb4c47f56564395f76333f319d26c79e2fb09', - '0115510b70c7229dbc5dc49036b32e7d91d23acd', - '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e', - '2a13f185e4525f9d4b59882791a2d397b90d5ddc', - '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e', - 'ff7ca51e58c505fec0dd2491de52c622bb7a806b', + "setup.py": [ + "54386793436c938cff89326944d4c2702340037d", + "51d254f0ecf5df2ce50c0b115741f4cf13985dab", + "998ed409c795fec2012b1c0ca054d99888b22090", + "5e0eb4c47f56564395f76333f319d26c79e2fb09", + "0115510b70c7229dbc5dc49036b32e7d91d23acd", + "7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e", + "2a13f185e4525f9d4b59882791a2d397b90d5ddc", + "191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", + "ff7ca51e58c505fec0dd2491de52c622bb7a806b", ], - 'vcs/nodes.py': [ - '33fa3223355104431402a888fa77a4e9956feb3e', - 'fa014c12c26d10ba682fadb78f2a11c24c8118e1', - 'e686b958768ee96af8029fe19c6050b1a8dd3b2b', - 'ab5721ca0a081f26bf43d9051e615af2cc99952f', - 'c877b68d18e792a66b7f4c529ea02c8f80801542', - '4313566d2e417cb382948f8d9d7c765330356054', - '6c2303a793671e807d1cfc70134c9ca0767d98c2', - '54386793436c938cff89326944d4c2702340037d', - '54000345d2e78b03a99d561399e8e548de3f3203', - '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b', - '2d03ca750a44440fb5ea8b751176d1f36f8e8f46', - '2a08b128c206db48c2f0b8f70df060e6db0ae4f8', - '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b', - 'ac71e9503c2ca95542839af0ce7b64011b72ea7c', - '12669288fd13adba2a9b7dd5b870cc23ffab92d2', - '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382', - '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5', - '5eab1222a7cd4bfcbabc218ca6d04276d4e27378', - 'f50f42baeed5af6518ef4b0cb2f1423f3851a941', - 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25', - 'f15c21f97864b4f071cddfbf2750ec2e23859414', - 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade', - 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b', - '84dec09632a4458f79f50ddbbd155506c460b4f9', - '0115510b70c7229dbc5dc49036b32e7d91d23acd', - '2a13f185e4525f9d4b59882791a2d397b90d5ddc', - '3bf1c5868e570e39569d094f922d33ced2fa3b2b', - 'b8d04012574729d2c29886e53b1a43ef16dd00a1', - '6970b057cffe4aab0a792aa634c89f4bebf01441', - 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f', - 'ff7ca51e58c505fec0dd2491de52c622bb7a806b', + "vcs/nodes.py": [ + "33fa3223355104431402a888fa77a4e9956feb3e", + "fa014c12c26d10ba682fadb78f2a11c24c8118e1", + "e686b958768ee96af8029fe19c6050b1a8dd3b2b", + "ab5721ca0a081f26bf43d9051e615af2cc99952f", + "c877b68d18e792a66b7f4c529ea02c8f80801542", + "4313566d2e417cb382948f8d9d7c765330356054", + "6c2303a793671e807d1cfc70134c9ca0767d98c2", + "54386793436c938cff89326944d4c2702340037d", + "54000345d2e78b03a99d561399e8e548de3f3203", + "1c6b3677b37ea064cb4b51714d8f7498f93f4b2b", + "2d03ca750a44440fb5ea8b751176d1f36f8e8f46", + "2a08b128c206db48c2f0b8f70df060e6db0ae4f8", + "30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b", + "ac71e9503c2ca95542839af0ce7b64011b72ea7c", + "12669288fd13adba2a9b7dd5b870cc23ffab92d2", + "5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382", + "12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5", + "5eab1222a7cd4bfcbabc218ca6d04276d4e27378", + "f50f42baeed5af6518ef4b0cb2f1423f3851a941", + "d7e390a45f6aa96f04f5e7f583ad4f867431aa25", + "f15c21f97864b4f071cddfbf2750ec2e23859414", + "e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade", + "ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b", + "84dec09632a4458f79f50ddbbd155506c460b4f9", + "0115510b70c7229dbc5dc49036b32e7d91d23acd", + "2a13f185e4525f9d4b59882791a2d397b90d5ddc", + "3bf1c5868e570e39569d094f922d33ced2fa3b2b", + "b8d04012574729d2c29886e53b1a43ef16dd00a1", + "6970b057cffe4aab0a792aa634c89f4bebf01441", + "dd80b0f6cf5052f17cc738c2951c4f2070200d7f", + "ff7ca51e58c505fec0dd2491de52c622bb7a806b", ], - 'vcs/backends/git.py': [ - '4cf116ad5a457530381135e2f4c453e68a1b0105', - '9a751d84d8e9408e736329767387f41b36935153', - 'cb681fb539c3faaedbcdf5ca71ca413425c18f01', - '428f81bb652bcba8d631bce926e8834ff49bdcc6', - '180ab15aebf26f98f714d8c68715e0f05fa6e1c7', - '2b8e07312a2e89e92b90426ab97f349f4bce2a3a', - '50e08c506174d8645a4bb517dd122ac946a0f3bf', - '54000345d2e78b03a99d561399e8e548de3f3203', + "vcs/backends/git.py": [ + "4cf116ad5a457530381135e2f4c453e68a1b0105", + "9a751d84d8e9408e736329767387f41b36935153", + "cb681fb539c3faaedbcdf5ca71ca413425c18f01", + "428f81bb652bcba8d631bce926e8834ff49bdcc6", + "180ab15aebf26f98f714d8c68715e0f05fa6e1c7", + "2b8e07312a2e89e92b90426ab97f349f4bce2a3a", + "50e08c506174d8645a4bb517dd122ac946a0f3bf", + "54000345d2e78b03a99d561399e8e548de3f3203", ], } for path, commit_ids in files.items(): @@ -817,79 +773,79 @@ class TestGitCommit(object): node_ids = [commit.raw_id for commit in node.history] assert set(commit_ids).issubset(set(node_ids)), ( "We assumed that %s is subset of commit_ids for which file %s " - "has been changed, and history of that node returned: %s" - % (commit_ids, path, node_ids)) + "has been changed, and history of that node returned: %s" % (commit_ids, path, node_ids) + ) def test_file_annotate(self): files = { - 'vcs/backends/__init__.py': { - 'c1214f7e79e02fc37156ff215cd71275450cffc3': { - 'lines_no': 1, - 'commits': [ - 'c1214f7e79e02fc37156ff215cd71275450cffc3', + "vcs/backends/__init__.py": { + "c1214f7e79e02fc37156ff215cd71275450cffc3": { + "lines_no": 1, + "commits": [ + "c1214f7e79e02fc37156ff215cd71275450cffc3", ], }, - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': { - 'lines_no': 21, - 'commits': [ - '49d3fd156b6f7db46313fac355dca1a0b94a0017', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647": { + "lines_no": 21, + "commits": [ + "49d3fd156b6f7db46313fac355dca1a0b94a0017", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", ], }, - 'e29b67bd158580fc90fc5e9111240b90e6e86064': { - 'lines_no': 32, - 'commits': [ - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '5eab1222a7cd4bfcbabc218ca6d04276d4e27378', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '54000345d2e78b03a99d561399e8e548de3f3203', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '78c3f0c23b7ee935ec276acb8b8212444c33c396', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '2a13f185e4525f9d4b59882791a2d397b90d5ddc', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '78c3f0c23b7ee935ec276acb8b8212444c33c396', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '992f38217b979d0b0987d0bae3cc26dac85d9b19', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', - '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + "e29b67bd158580fc90fc5e9111240b90e6e86064": { + "lines_no": 32, + "commits": [ + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "5eab1222a7cd4bfcbabc218ca6d04276d4e27378", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "54000345d2e78b03a99d561399e8e548de3f3203", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "78c3f0c23b7ee935ec276acb8b8212444c33c396", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "2a13f185e4525f9d4b59882791a2d397b90d5ddc", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "78c3f0c23b7ee935ec276acb8b8212444c33c396", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "992f38217b979d0b0987d0bae3cc26dac85d9b19", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", + "16fba1ae9334d79b66d7afed2c2dfbfa2ae53647", ], }, }, @@ -903,37 +859,32 @@ class TestGitCommit(object): l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)] assert l1_1 == l1_2 l1 = l1_1 - l2 = files[fname][commit_id]['commits'] + l2 = files[fname][commit_id]["commits"] assert l1 == l2, ( "The lists of commit_ids for %s@commit_id %s" "from annotation list should match each other, " - "got \n%s \nvs \n%s " % (fname, commit_id, l1, l2)) + "got \n%s \nvs \n%s " % (fname, commit_id, l1, l2) + ) def test_files_state(self): """ Tests state of FileNodes. """ - node = self.repo\ - .get_commit('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\ - .get_node('vcs/utils/diffs.py') + node = self.repo.get_commit("e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0").get_node("vcs/utils/diffs.py") assert node.state, NodeState.ADDED assert node.added assert not node.changed assert not node.not_changed assert not node.removed - node = self.repo\ - .get_commit('33fa3223355104431402a888fa77a4e9956feb3e')\ - .get_node('.hgignore') + node = self.repo.get_commit("33fa3223355104431402a888fa77a4e9956feb3e").get_node(".hgignore") assert node.state, NodeState.CHANGED assert not node.added assert node.changed assert not node.not_changed assert not node.removed - node = self.repo\ - .get_commit('e29b67bd158580fc90fc5e9111240b90e6e86064')\ - .get_node('setup.py') + node = self.repo.get_commit("e29b67bd158580fc90fc5e9111240b90e6e86064").get_node("setup.py") assert node.state, NodeState.NOT_CHANGED assert not node.added assert not node.changed @@ -942,48 +893,38 @@ class TestGitCommit(object): # If node has REMOVED state then trying to fetch it would raise # CommitError exception - commit = self.repo.get_commit( - 'fa6600f6848800641328adbf7811fd2372c02ab2') - path = 'vcs/backends/BaseRepository.py' + commit = self.repo.get_commit("fa6600f6848800641328adbf7811fd2372c02ab2") + path = "vcs/backends/BaseRepository.py" with pytest.raises(NodeDoesNotExistError): commit.get_node(path) # but it would be one of ``removed`` (commit's attribute) assert path in [rf.path for rf in commit.removed] - commit = self.repo.get_commit( - '54386793436c938cff89326944d4c2702340037d') - changed = [ - 'setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py', - 'vcs/nodes.py'] + commit = self.repo.get_commit("54386793436c938cff89326944d4c2702340037d") + changed = ["setup.py", "tests/test_nodes.py", "vcs/backends/hg.py", "vcs/nodes.py"] assert set(changed) == set([f.path for f in commit.changed]) def test_unicode_branch_refs(self): unicode_branches = { - 'refs/heads/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b', - u'refs/heads/uniçö∂e': 'ürl', + "refs/heads/unicode": "6c0ce52b229aa978889e91b38777f800e85f330b", + "refs/heads/uniçö∂e": "ürl", } - with mock.patch( - ("rhodecode.lib.vcs.backends.git.repository" - ".GitRepository._refs"), - unicode_branches): + with mock.patch(("rhodecode.lib.vcs.backends.git.repository" ".GitRepository._refs"), unicode_branches): branches = self.repo.branches - assert 'unicode' in branches - assert 'uniçö∂e' in branches + assert "unicode" in branches + assert "uniçö∂e" in branches def test_unicode_tag_refs(self): unicode_tags = { - 'refs/tags/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b', - u'refs/tags/uniçö∂e': '6c0ce52b229aa978889e91b38777f800e85f330b', + "refs/tags/unicode": "6c0ce52b229aa978889e91b38777f800e85f330b", + "refs/tags/uniçö∂e": "6c0ce52b229aa978889e91b38777f800e85f330b", } - with mock.patch( - ("rhodecode.lib.vcs.backends.git.repository" - ".GitRepository._refs"), - unicode_tags): + with mock.patch(("rhodecode.lib.vcs.backends.git.repository" ".GitRepository._refs"), unicode_tags): tags = self.repo.tags - assert 'unicode' in tags - assert 'uniçö∂e' in tags + assert "unicode" in tags + assert "uniçö∂e" in tags def test_commit_message_is_unicode(self): for commit in self.repo: @@ -995,190 +936,186 @@ class TestGitCommit(object): def test_repo_files_content_types(self): commit = self.repo.get_commit() - for node in commit.get_node('/'): + for node in commit.get_node("/"): if node.is_file(): assert type(node.content) == bytes assert type(node.str_content) == str def test_wrong_path(self): # There is 'setup.py' in the root dir but not there: - path = 'foo/bar/setup.py' + path = "foo/bar/setup.py" tip = self.repo.get_commit() with pytest.raises(VCSError): tip.get_node(path) - @pytest.mark.parametrize("author_email, commit_id", [ - ('marcin@python-blog.com', 'c1214f7e79e02fc37156ff215cd71275450cffc3'), - ('lukasz.balcerzak@python-center.pl', - 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'), - ('none@none', '8430a588b43b5d6da365400117c89400326e7992'), - ]) + @pytest.mark.parametrize( + "author_email, commit_id", + [ + ("marcin@python-blog.com", "c1214f7e79e02fc37156ff215cd71275450cffc3"), + ("lukasz.balcerzak@python-center.pl", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"), + ("none@none", "8430a588b43b5d6da365400117c89400326e7992"), + ], + ) def test_author_email(self, author_email, commit_id): commit = self.repo.get_commit(commit_id) assert author_email == commit.author_email - @pytest.mark.parametrize("author, commit_id", [ - ('Marcin Kuzminski', 'c1214f7e79e02fc37156ff215cd71275450cffc3'), - ('Lukasz Balcerzak', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'), - ('marcink', '8430a588b43b5d6da365400117c89400326e7992'), - ]) + @pytest.mark.parametrize( + "author, commit_id", + [ + ("Marcin Kuzminski", "c1214f7e79e02fc37156ff215cd71275450cffc3"), + ("Lukasz Balcerzak", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"), + ("marcink", "8430a588b43b5d6da365400117c89400326e7992"), + ], + ) def test_author_username(self, author, commit_id): commit = self.repo.get_commit(commit_id) assert author == commit.author_name class TestLargeFileRepo(object): - def test_large_file(self, backend_git): conf = make_db_config() - repo = backend_git.create_test_repo('largefiles', conf) + git_largefiles_store = conf.get("vcs_git_lfs", "store_location") + + repo = backend_git.create_test_repo("largefiles", conf) tip = repo.scm_instance().get_commit() + node = tip.get_node("1MB.zip") + # extract stored LF node into the origin cache - lfs_store = os.path.join(repo.repo_path, repo.repo_name, 'lfs_store') + repo_lfs_store: str = os.path.join(repo.repo_path, repo.repo_name, "lfs_store") - oid = '7b331c02e313c7599d5a90212e17e6d3cb729bd2e1c9b873c302a63c95a2f9bf' - oid_path = os.path.join(lfs_store, oid) - # Todo: oid path depends on LFSOidStorage.store_suffix. Once it will be changed update below line accordingly - oid_destination = os.path.join( - conf.get('vcs_git_lfs', 'store_location'), f'objects/{oid[:2]}/{oid[2:4]}/{oid}') + oid: str = "7b331c02e313c7599d5a90212e17e6d3cb729bd2e1c9b873c302a63c95a2f9bf" + # where the OID actually is INSIDE the repo... + oid_path = os.path.join(repo_lfs_store, oid) + + # Note: oid path depends on LFSOidStore.store_suffix. Once it will be changed update below line accordingly + oid_destination = os.path.join(git_largefiles_store, f"objects/{oid[:2]}/{oid[2:4]}/{oid}") - os.makedirs(os.path.dirname(oid_destination)) shutil.copy(oid_path, oid_destination) - node = tip.get_node('1MB.zip') - lf_node = node.get_largefile_node() assert lf_node.is_largefile() is True assert lf_node.size == 1024000 - assert lf_node.name == '1MB.zip' + assert lf_node.name == "1MB.zip" @pytest.mark.usefixtures("vcs_repository_support") class TestGitSpecificWithRepo(BackendTestMixin): - @classmethod def _get_commits(cls): return [ { - 'message': 'Initial', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 20), - 'added': [ - FileNode(b'foobar/static/js/admin/base.js', content=b'base'), - FileNode(b'foobar/static/admin', content=b'admin', mode=0o120000), # this is a link - FileNode(b'foo', content=b'foo'), + "message": "Initial", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 20), + "added": [ + FileNode(b"foobar/static/js/admin/base.js", content=b"base"), + FileNode(b"foobar/static/admin", content=b"admin", mode=0o120000), # this is a link + FileNode(b"foo", content=b"foo"), ], }, { - 'message': 'Second', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 22), - 'added': [ - FileNode(b'foo2', content=b'foo2'), + "message": "Second", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 22), + "added": [ + FileNode(b"foo2", content=b"foo2"), ], }, ] def test_paths_slow_traversing(self): commit = self.repo.get_commit() - assert commit.get_node('foobar').get_node('static').get_node('js')\ - .get_node('admin').get_node('base.js').content == b'base' + assert ( + commit.get_node("foobar").get_node("static").get_node("js").get_node("admin").get_node("base.js").content + == b"base" + ) def test_paths_fast_traversing(self): commit = self.repo.get_commit() - assert commit.get_node('foobar/static/js/admin/base.js').content == b'base' + assert commit.get_node("foobar/static/js/admin/base.js").content == b"base" def test_get_diff_runs_git_command_with_hashes(self): comm1 = self.repo[0] comm2 = self.repo[1] - with mock.patch.object(self.repo, '_remote', return_value=mock.Mock()) as remote_mock: + with mock.patch.object(self.repo, "_remote", return_value=mock.Mock()) as remote_mock: remote_mock.diff = mock.MagicMock(side_effect=callable_get_diff) self.repo.get_diff(comm1, comm2) remote_mock.diff.assert_called_once_with( - comm1.raw_id, comm2.raw_id, - file_filter=None, opt_ignorews=False, context=3) + comm1.raw_id, comm2.raw_id, file_filter=None, opt_ignorews=False, context=3 + ) def test_get_diff_runs_git_command_with_str_hashes(self): comm2 = self.repo[1] - with mock.patch.object(self.repo, '_remote', return_value=mock.Mock()) as remote_mock: + with mock.patch.object(self.repo, "_remote", return_value=mock.Mock()) as remote_mock: remote_mock.diff = mock.MagicMock(side_effect=callable_get_diff) self.repo.get_diff(self.repo.EMPTY_COMMIT, comm2) remote_mock.diff.assert_called_once_with( - self.repo.EMPTY_COMMIT.raw_id, comm2.raw_id, - file_filter=None, opt_ignorews=False, context=3) + self.repo.EMPTY_COMMIT.raw_id, comm2.raw_id, file_filter=None, opt_ignorews=False, context=3 + ) def test_get_diff_runs_git_command_with_path_if_its_given(self): comm1 = self.repo[0] comm2 = self.repo[1] - with mock.patch.object(self.repo, '_remote', return_value=mock.Mock()) as remote_mock: + with mock.patch.object(self.repo, "_remote", return_value=mock.Mock()) as remote_mock: remote_mock.diff = mock.MagicMock(side_effect=callable_get_diff) - self.repo.get_diff(comm1, comm2, 'foo') + self.repo.get_diff(comm1, comm2, "foo") remote_mock.diff.assert_called_once_with( - self.repo._lookup_commit(0), comm2.raw_id, - file_filter='foo', opt_ignorews=False, context=3) + self.repo._lookup_commit(0), comm2.raw_id, file_filter="foo", opt_ignorews=False, context=3 + ) @pytest.mark.usefixtures("vcs_repository_support") class TestGitRegression(BackendTestMixin): - @classmethod def _get_commits(cls): return [ { - 'message': 'Initial', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 20), - 'added': [ - FileNode(b'bot/__init__.py', content=b'base'), - FileNode(b'bot/templates/404.html', content=b'base'), - FileNode(b'bot/templates/500.html', content=b'base'), + "message": "Initial", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 20), + "added": [ + FileNode(b"bot/__init__.py", content=b"base"), + FileNode(b"bot/templates/404.html", content=b"base"), + FileNode(b"bot/templates/500.html", content=b"base"), ], }, { - 'message': 'Second', - 'author': 'Joe Doe ', - 'date': datetime.datetime(2010, 1, 1, 22), - 'added': [ - FileNode(b'bot/build/migrations/1.py', content=b'foo2'), - FileNode(b'bot/build/migrations/2.py', content=b'foo2'), - FileNode(b'bot/build/static/templates/f.html', content=b'foo2'), - FileNode(b'bot/build/static/templates/f1.html', content=b'foo2'), - FileNode(b'bot/build/templates/err.html', content=b'foo2'), - FileNode(b'bot/build/templates/err2.html', content=b'foo2'), + "message": "Second", + "author": "Joe Doe ", + "date": datetime.datetime(2010, 1, 1, 22), + "added": [ + FileNode(b"bot/build/migrations/1.py", content=b"foo2"), + FileNode(b"bot/build/migrations/2.py", content=b"foo2"), + FileNode(b"bot/build/static/templates/f.html", content=b"foo2"), + FileNode(b"bot/build/static/templates/f1.html", content=b"foo2"), + FileNode(b"bot/build/templates/err.html", content=b"foo2"), + FileNode(b"bot/build/templates/err2.html", content=b"foo2"), ], }, ] - @pytest.mark.parametrize("path, expected_paths", [ - ('bot', [ - 'bot/build', - 'bot/templates', - 'bot/__init__.py']), - ('bot/build', [ - 'bot/build/migrations', - 'bot/build/static', - 'bot/build/templates']), - ('bot/build/static', [ - 'bot/build/static/templates']), - ('bot/build/static/templates', [ - 'bot/build/static/templates/f.html', - 'bot/build/static/templates/f1.html']), - ('bot/build/templates', [ - 'bot/build/templates/err.html', - 'bot/build/templates/err2.html']), - ('bot/templates/', [ - 'bot/templates/404.html', - 'bot/templates/500.html']), - ]) + @pytest.mark.parametrize( + "path, expected_paths", + [ + ("bot", ["bot/build", "bot/templates", "bot/__init__.py"]), + ("bot/build", ["bot/build/migrations", "bot/build/static", "bot/build/templates"]), + ("bot/build/static", ["bot/build/static/templates"]), + ("bot/build/static/templates", ["bot/build/static/templates/f.html", "bot/build/static/templates/f1.html"]), + ("bot/build/templates", ["bot/build/templates/err.html", "bot/build/templates/err2.html"]), + ("bot/templates/", ["bot/templates/404.html", "bot/templates/500.html"]), + ], + ) def test_similar_paths(self, path, expected_paths): commit = self.repo.get_commit() paths = [n.path for n in commit.get_nodes(path)] @@ -1186,122 +1123,120 @@ class TestGitRegression(BackendTestMixin): class TestDiscoverGitVersion(object): - def test_returns_git_version(self, baseapp): version = discover_git_version() assert version def test_returns_empty_string_without_vcsserver(self): mock_connection = mock.Mock() - mock_connection.discover_git_version = mock.Mock( - side_effect=Exception) - with mock.patch('rhodecode.lib.vcs.connection.Git', mock_connection): + mock_connection.discover_git_version = mock.Mock(side_effect=Exception) + with mock.patch("rhodecode.lib.vcs.connection.Git", mock_connection): version = discover_git_version() - assert version == '' + assert version == "" class TestGetSubmoduleUrl(object): def test_submodules_file_found(self): - commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1) + commit = GitCommit(repository=mock.Mock(), raw_id="abcdef12", idx=1) node = mock.Mock() - with mock.patch.object( - commit, 'get_node', return_value=node) as get_node_mock: + with mock.patch.object(commit, "get_node", return_value=node) as get_node_mock: node.str_content = ( - '[submodule "subrepo1"]\n' - '\tpath = subrepo1\n' - '\turl = https://code.rhodecode.com/dulwich\n' + '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" ) - result = commit._get_submodule_url('subrepo1') - get_node_mock.assert_called_once_with('.gitmodules') - assert result == 'https://code.rhodecode.com/dulwich' + result = commit._get_submodule_url("subrepo1") + get_node_mock.assert_called_once_with(".gitmodules") + assert result == "https://code.rhodecode.com/dulwich" def test_complex_submodule_path(self): - commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1) + commit = GitCommit(repository=mock.Mock(), raw_id="abcdef12", idx=1) node = mock.Mock() - with mock.patch.object( - commit, 'get_node', return_value=node) as get_node_mock: + with mock.patch.object(commit, "get_node", return_value=node) as get_node_mock: node.str_content = ( '[submodule "complex/subrepo/path"]\n' - '\tpath = complex/subrepo/path\n' - '\turl = https://code.rhodecode.com/dulwich\n' + "\tpath = complex/subrepo/path\n" + "\turl = https://code.rhodecode.com/dulwich\n" ) - result = commit._get_submodule_url('complex/subrepo/path') - get_node_mock.assert_called_once_with('.gitmodules') - assert result == 'https://code.rhodecode.com/dulwich' + result = commit._get_submodule_url("complex/subrepo/path") + get_node_mock.assert_called_once_with(".gitmodules") + assert result == "https://code.rhodecode.com/dulwich" def test_submodules_file_not_found(self): - commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1) - with mock.patch.object( - commit, 'get_node', side_effect=NodeDoesNotExistError): - result = commit._get_submodule_url('complex/subrepo/path') + commit = GitCommit(repository=mock.Mock(), raw_id="abcdef12", idx=1) + with mock.patch.object(commit, "get_node", side_effect=NodeDoesNotExistError): + result = commit._get_submodule_url("complex/subrepo/path") assert result is None def test_path_not_found(self): - commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1) + commit = GitCommit(repository=mock.Mock(), raw_id="abcdef12", idx=1) node = mock.Mock() - with mock.patch.object( - commit, 'get_node', return_value=node) as get_node_mock: + with mock.patch.object(commit, "get_node", return_value=node) as get_node_mock: node.str_content = ( - '[submodule "subrepo1"]\n' - '\tpath = subrepo1\n' - '\turl = https://code.rhodecode.com/dulwich\n' + '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" ) - result = commit._get_submodule_url('subrepo2') - get_node_mock.assert_called_once_with('.gitmodules') + result = commit._get_submodule_url("subrepo2") + get_node_mock.assert_called_once_with(".gitmodules") assert result is None def test_returns_cached_values(self): - commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1) + commit = GitCommit(repository=mock.Mock(), raw_id="abcdef12", idx=1) node = mock.Mock() - with mock.patch.object( - commit, 'get_node', return_value=node) as get_node_mock: + with mock.patch.object(commit, "get_node", return_value=node) as get_node_mock: node.str_content = ( - '[submodule "subrepo1"]\n' - '\tpath = subrepo1\n' - '\turl = https://code.rhodecode.com/dulwich\n' + '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" ) for _ in range(3): - commit._get_submodule_url('subrepo1') - get_node_mock.assert_called_once_with('.gitmodules') + commit._get_submodule_url("subrepo1") + get_node_mock.assert_called_once_with(".gitmodules") def test_get_node_returns_a_link(self): repository = mock.Mock() - repository.alias = 'git' - commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1) - submodule_url = 'https://code.rhodecode.com/dulwich' - get_id_patch = mock.patch.object( - commit, '_get_tree_id_for_path', return_value=(1, 'link')) - get_submodule_patch = mock.patch.object( - commit, '_get_submodule_url', return_value=submodule_url) + repository.alias = "git" + commit = GitCommit(repository=repository, raw_id="abcdef12", idx=1) + submodule_url = "https://code.rhodecode.com/dulwich" + get_id_patch = mock.patch.object(commit, "_get_tree_id_for_path", return_value=(1, "link")) + get_submodule_patch = mock.patch.object(commit, "_get_submodule_url", return_value=submodule_url) with get_id_patch, get_submodule_patch as submodule_mock: - node = commit.get_node('/abcde') + node = commit.get_node("/abcde") - submodule_mock.assert_called_once_with('/abcde') + submodule_mock.assert_called_once_with("/abcde") assert type(node) == SubModuleNode assert node.url == submodule_url def test_get_nodes_returns_links(self): repository = mock.MagicMock() - repository.alias = 'git' - repository._remote.tree_items.return_value = [ - ('subrepo', 'stat', 1, 'link') - ] - commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1) - submodule_url = 'https://code.rhodecode.com/dulwich' - get_id_patch = mock.patch.object( - commit, '_get_tree_id_for_path', return_value=(1, 'tree')) - get_submodule_patch = mock.patch.object( - commit, '_get_submodule_url', return_value=submodule_url) + repository.alias = "git" + repository._remote.tree_items.return_value = [("subrepo", "stat", 1, "link")] + commit = GitCommit(repository=repository, raw_id="abcdef12", idx=1) + submodule_url = "https://code.rhodecode.com/dulwich" + get_id_patch = mock.patch.object(commit, "_get_tree_id_for_path", return_value=(1, "tree")) + get_submodule_patch = mock.patch.object(commit, "_get_submodule_url", return_value=submodule_url) with get_id_patch, get_submodule_patch as submodule_mock: - nodes = commit.get_nodes('/abcde') + nodes = commit.get_nodes("/abcde") - submodule_mock.assert_called_once_with('/abcde/subrepo') + submodule_mock.assert_called_once_with("/abcde/subrepo") assert len(nodes) == 1 assert type(nodes[0]) == SubModuleNode assert nodes[0].url == submodule_url + + +class TestGetShadowInstance(object): + + @pytest.fixture() + def repo(self, vcsbackend_git): + _git_repo = vcsbackend_git.repo + + mock.patch.object(_git_repo, "config", mock.Mock()) + connection_mock = mock.Mock(unsafe=True, name="connection.Hg") + + mock.patch("rhodecode.lib.vcs.connection.Git", connection_mock) + return _git_repo + + def test_getting_shadow_instance_copies_config(self, repo): + shadow = repo.get_shadow_instance(repo.path) + assert shadow.config.serialize() == repo.config.serialize() diff --git a/rhodecode/tests/vcs/test_hg.py b/rhodecode/tests/vcs/test_hg.py index 20c90207..cf7ce267 100644 --- a/rhodecode/tests/vcs/test_hg.py +++ b/rhodecode/tests/vcs/test_hg.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -25,11 +24,9 @@ import pytest from rhodecode.lib.str_utils import safe_bytes from rhodecode.lib.utils import make_db_config from rhodecode.lib.vcs import backends -from rhodecode.lib.vcs.backends.base import ( - Reference, MergeResponse, MergeFailureReason) +from rhodecode.lib.vcs.backends.base import Reference, MergeResponse, MergeFailureReason from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialCommit -from rhodecode.lib.vcs.exceptions import ( - RepositoryError, VCSError, NodeDoesNotExistError, CommitDoesNotExistError) +from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError, CommitDoesNotExistError from rhodecode.lib.vcs.nodes import FileNode, NodeKind, NodeState from rhodecode.tests import TEST_HG_REPO, TEST_HG_REPO_CLONE, repo_id_generator @@ -44,12 +41,13 @@ def repo_path_generator(): i = 0 while True: i += 1 - yield '%s-%d' % (TEST_HG_REPO_CLONE, i) + yield "%s-%d" % (TEST_HG_REPO_CLONE, i) + REPO_PATH_GENERATOR = repo_path_generator() -@pytest.fixture(scope='class', autouse=True) +@pytest.fixture(scope="class", autouse=True) def repo(request, baseapp): repo = MercurialRepository(TEST_HG_REPO) if request.cls: @@ -58,7 +56,6 @@ def repo(request, baseapp): class TestMercurialRepository(object): - # pylint: disable=protected-access def get_clone_repo(self): @@ -66,8 +63,7 @@ class TestMercurialRepository(object): Return a clone of the base repo. """ clone_path = next(REPO_PATH_GENERATOR) - repo_clone = MercurialRepository( - clone_path, create=True, src_url=self.repo.path) + repo_clone = MercurialRepository(clone_path, create=True, src_url=self.repo.path) return repo_clone @@ -78,40 +74,39 @@ class TestMercurialRepository(object): return MercurialRepository(next(REPO_PATH_GENERATOR), create=True) def test_wrong_repo_path(self): - wrong_repo_path = '/tmp/errorrepo_hg' + wrong_repo_path = "/tmp/errorrepo_hg" with pytest.raises(RepositoryError): MercurialRepository(wrong_repo_path) def test_unicode_path_repo(self): with pytest.raises(VCSError): - MercurialRepository('iShouldFail') + MercurialRepository("iShouldFail") def test_unicode_commit_id(self): with pytest.raises(CommitDoesNotExistError): - self.repo.get_commit('unicode-commit-id') + self.repo.get_commit("unicode-commit-id") with pytest.raises(CommitDoesNotExistError): - self.repo.get_commit('unícøde-spéçial-chäråcter-commit-id') + self.repo.get_commit("unícøde-spéçial-chäråcter-commit-id") def test_unicode_bookmark(self): - self.repo.bookmark('unicode-bookmark') - self.repo.bookmark('unícøde-spéçial-chäråcter-bookmark') + self.repo.bookmark("unicode-bookmark") + self.repo.bookmark("unícøde-spéçial-chäråcter-bookmark") def test_unicode_branch(self): with pytest.raises(KeyError): - assert self.repo.branches['unicode-branch'] + assert self.repo.branches["unicode-branch"] with pytest.raises(KeyError): - assert self.repo.branches['unícøde-spéçial-chäråcter-branch'] + assert self.repo.branches["unícøde-spéçial-chäråcter-branch"] def test_repo_clone(self): if os.path.exists(TEST_HG_REPO_CLONE): self.fail( - 'Cannot test mercurial clone repo as location %s already ' - 'exists. You should manually remove it first.' - % TEST_HG_REPO_CLONE) + "Cannot test mercurial clone repo as location %s already " + "exists. You should manually remove it first." % TEST_HG_REPO_CLONE + ) repo = MercurialRepository(TEST_HG_REPO) - repo_clone = MercurialRepository(TEST_HG_REPO_CLONE, - src_url=TEST_HG_REPO) + repo_clone = MercurialRepository(TEST_HG_REPO_CLONE, create=True, src_url=TEST_HG_REPO) assert len(repo.commit_ids) == len(repo_clone.commit_ids) # Checking hashes of commits should be enough for commit in repo.get_commits(): @@ -121,72 +116,80 @@ class TestMercurialRepository(object): def test_repo_clone_with_update(self): repo = MercurialRepository(TEST_HG_REPO) repo_clone = MercurialRepository( - TEST_HG_REPO_CLONE + '_w_update', - src_url=TEST_HG_REPO, do_workspace_checkout=True) + TEST_HG_REPO_CLONE + "_w_update", create=True, src_url=TEST_HG_REPO, do_workspace_checkout=True + ) assert len(repo.commit_ids) == len(repo_clone.commit_ids) # check if current workdir was updated - assert os.path.isfile( - os.path.join(TEST_HG_REPO_CLONE + '_w_update', 'MANIFEST.in')) + assert os.path.isfile(os.path.join(TEST_HG_REPO_CLONE + "_w_update", "MANIFEST.in")) def test_repo_clone_without_update(self): repo = MercurialRepository(TEST_HG_REPO) repo_clone = MercurialRepository( - TEST_HG_REPO_CLONE + '_wo_update', - src_url=TEST_HG_REPO, do_workspace_checkout=False) + TEST_HG_REPO_CLONE + "_wo_update", create=True, src_url=TEST_HG_REPO, do_workspace_checkout=False + ) assert len(repo.commit_ids) == len(repo_clone.commit_ids) - assert not os.path.isfile( - os.path.join(TEST_HG_REPO_CLONE + '_wo_update', 'MANIFEST.in')) + assert not os.path.isfile(os.path.join(TEST_HG_REPO_CLONE + "_wo_update", "MANIFEST.in")) def test_commit_ids(self): # there are 21 commits at bitbucket now # so we can assume they would be available from now on - subset = {'b986218ba1c9b0d6a259fac9b050b1724ed8e545', '3d8f361e72ab303da48d799ff1ac40d5ac37c67e', - '6cba7170863a2411822803fa77a0a264f1310b35', '56349e29c2af3ac913b28bde9a2c6154436e615b', - '2dda4e345facb0ccff1a191052dd1606dba6781d', '6fff84722075f1607a30f436523403845f84cd9e', - '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb', - 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c', 'be90031137367893f1c406e0a8683010fd115b79', - 'db8e58be770518cbb2b1cdfa69146e47cd481481', '84478366594b424af694a6c784cb991a16b87c21', - '17f8e105dddb9f339600389c6dc7175d395a535c', '20a662e756499bde3095ffc9bc0643d1def2d0eb', - '2e319b85e70a707bba0beff866d9f9de032aa4f9', '786facd2c61deb9cf91e9534735124fb8fc11842', - '94593d2128d38210a2fcd1aabff6dda0d6d9edf8', 'aa6a0de05b7612707db567078e130a6cd114a9a7', - 'eada5a770da98ab0dd7325e29d00e0714f228d09' - } + subset = { + "b986218ba1c9b0d6a259fac9b050b1724ed8e545", + "3d8f361e72ab303da48d799ff1ac40d5ac37c67e", + "6cba7170863a2411822803fa77a0a264f1310b35", + "56349e29c2af3ac913b28bde9a2c6154436e615b", + "2dda4e345facb0ccff1a191052dd1606dba6781d", + "6fff84722075f1607a30f436523403845f84cd9e", + "7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7", + "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb", + "dc5d2c0661b61928834a785d3e64a3f80d3aad9c", + "be90031137367893f1c406e0a8683010fd115b79", + "db8e58be770518cbb2b1cdfa69146e47cd481481", + "84478366594b424af694a6c784cb991a16b87c21", + "17f8e105dddb9f339600389c6dc7175d395a535c", + "20a662e756499bde3095ffc9bc0643d1def2d0eb", + "2e319b85e70a707bba0beff866d9f9de032aa4f9", + "786facd2c61deb9cf91e9534735124fb8fc11842", + "94593d2128d38210a2fcd1aabff6dda0d6d9edf8", + "aa6a0de05b7612707db567078e130a6cd114a9a7", + "eada5a770da98ab0dd7325e29d00e0714f228d09", + } assert subset.issubset(set(self.repo.commit_ids)) # check if we have the proper order of commits org = [ - 'b986218ba1c9b0d6a259fac9b050b1724ed8e545', - '3d8f361e72ab303da48d799ff1ac40d5ac37c67e', - '6cba7170863a2411822803fa77a0a264f1310b35', - '56349e29c2af3ac913b28bde9a2c6154436e615b', - '2dda4e345facb0ccff1a191052dd1606dba6781d', - '6fff84722075f1607a30f436523403845f84cd9e', - '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7', - '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb', - 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c', - 'be90031137367893f1c406e0a8683010fd115b79', - 'db8e58be770518cbb2b1cdfa69146e47cd481481', - '84478366594b424af694a6c784cb991a16b87c21', - '17f8e105dddb9f339600389c6dc7175d395a535c', - '20a662e756499bde3095ffc9bc0643d1def2d0eb', - '2e319b85e70a707bba0beff866d9f9de032aa4f9', - '786facd2c61deb9cf91e9534735124fb8fc11842', - '94593d2128d38210a2fcd1aabff6dda0d6d9edf8', - 'aa6a0de05b7612707db567078e130a6cd114a9a7', - 'eada5a770da98ab0dd7325e29d00e0714f228d09', - '2c1885c735575ca478bf9e17b0029dca68824458', - 'd9bcd465040bf869799b09ad732c04e0eea99fe9', - '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7', - '4fb8326d78e5120da2c7468dcf7098997be385da', - '62b4a097164940bd66030c4db51687f3ec035eed', - '536c1a19428381cfea92ac44985304f6a8049569', - '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4', - '9bb326a04ae5d98d437dece54be04f830cf1edd9', - 'f8940bcb890a98c4702319fbe36db75ea309b475', - 'ff5ab059786ebc7411e559a2cc309dfae3625a3b', - '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08', - 'ee87846a61c12153b51543bf860e1026c6d3dcba', + "b986218ba1c9b0d6a259fac9b050b1724ed8e545", + "3d8f361e72ab303da48d799ff1ac40d5ac37c67e", + "6cba7170863a2411822803fa77a0a264f1310b35", + "56349e29c2af3ac913b28bde9a2c6154436e615b", + "2dda4e345facb0ccff1a191052dd1606dba6781d", + "6fff84722075f1607a30f436523403845f84cd9e", + "7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7", + "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb", + "dc5d2c0661b61928834a785d3e64a3f80d3aad9c", + "be90031137367893f1c406e0a8683010fd115b79", + "db8e58be770518cbb2b1cdfa69146e47cd481481", + "84478366594b424af694a6c784cb991a16b87c21", + "17f8e105dddb9f339600389c6dc7175d395a535c", + "20a662e756499bde3095ffc9bc0643d1def2d0eb", + "2e319b85e70a707bba0beff866d9f9de032aa4f9", + "786facd2c61deb9cf91e9534735124fb8fc11842", + "94593d2128d38210a2fcd1aabff6dda0d6d9edf8", + "aa6a0de05b7612707db567078e130a6cd114a9a7", + "eada5a770da98ab0dd7325e29d00e0714f228d09", + "2c1885c735575ca478bf9e17b0029dca68824458", + "d9bcd465040bf869799b09ad732c04e0eea99fe9", + "469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7", + "4fb8326d78e5120da2c7468dcf7098997be385da", + "62b4a097164940bd66030c4db51687f3ec035eed", + "536c1a19428381cfea92ac44985304f6a8049569", + "965e8ab3c44b070cdaa5bf727ddef0ada980ecc4", + "9bb326a04ae5d98d437dece54be04f830cf1edd9", + "f8940bcb890a98c4702319fbe36db75ea309b475", + "ff5ab059786ebc7411e559a2cc309dfae3625a3b", + "6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08", + "ee87846a61c12153b51543bf860e1026c6d3dcba", ] assert org == self.repo.commit_ids[:31] @@ -197,8 +200,7 @@ class TestMercurialRepository(object): def test_slicing(self): # 4 1 5 10 95 - for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5), - (10, 20, 10), (5, 100, 95)]: + for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5), (10, 20, 10), (5, 100, 95)]: indexes = list(self.repo[sfrom:sto]) assert len(indexes) == size assert indexes[0] == self.repo.get_commit(commit_idx=sfrom) @@ -208,64 +210,64 @@ class TestMercurialRepository(object): # TODO: Need more tests here # active branches - assert 'default' in self.repo.branches - assert 'stable' in self.repo.branches + assert "default" in self.repo.branches + assert "stable" in self.repo.branches # closed - assert 'git' in self.repo._get_branches(closed=True) - assert 'web' in self.repo._get_branches(closed=True) + assert "git" in self.repo._get_branches(closed=True) + assert "web" in self.repo._get_branches(closed=True) for name, id in self.repo.branches.items(): assert isinstance(self.repo.get_commit(id), MercurialCommit) def test_tip_in_tags(self): # tip is always a tag - assert 'tip' in self.repo.tags + assert "tip" in self.repo.tags def test_tip_commit_in_tags(self): tip = self.repo.get_commit() - assert self.repo.tags['tip'] == tip.raw_id + assert self.repo.tags["tip"] == tip.raw_id def test_initial_commit(self): init_commit = self.repo.get_commit(commit_idx=0) init_author = init_commit.author - assert init_commit.message == 'initial import' - assert init_author == 'Marcin Kuzminski ' + assert init_commit.message == "initial import" + assert init_author == "Marcin Kuzminski " assert init_author == init_commit.committer - assert sorted(init_commit._file_paths) == sorted([ - 'vcs/__init__.py', - 'vcs/backends/BaseRepository.py', - 'vcs/backends/__init__.py', - ]) - assert sorted(init_commit._dir_paths) == sorted( - ['', 'vcs', 'vcs/backends']) + assert sorted(init_commit._file_paths) == sorted( + [ + "vcs/__init__.py", + "vcs/backends/BaseRepository.py", + "vcs/backends/__init__.py", + ] + ) + assert sorted(init_commit._dir_paths) == sorted(["", "vcs", "vcs/backends"]) - assert init_commit._dir_paths + init_commit._file_paths == \ - init_commit._paths + assert init_commit._dir_paths + init_commit._file_paths == init_commit._paths with pytest.raises(NodeDoesNotExistError): - init_commit.get_node(path='foobar') + init_commit.get_node(path="foobar") - node = init_commit.get_node('vcs/') - assert hasattr(node, 'kind') + node = init_commit.get_node("vcs/") + assert hasattr(node, "kind") assert node.kind == NodeKind.DIR - node = init_commit.get_node('vcs') - assert hasattr(node, 'kind') + node = init_commit.get_node("vcs") + assert hasattr(node, "kind") assert node.kind == NodeKind.DIR - node = init_commit.get_node('vcs/__init__.py') - assert hasattr(node, 'kind') + node = init_commit.get_node("vcs/__init__.py") + assert hasattr(node, "kind") assert node.kind == NodeKind.FILE def test_not_existing_commit(self): # rawid with pytest.raises(RepositoryError): - self.repo.get_commit('abcd' * 10) + self.repo.get_commit("abcd" * 10) # shortid with pytest.raises(RepositoryError): - self.repo.get_commit('erro' * 4) + self.repo.get_commit("erro" * 4) # numeric with pytest.raises(RepositoryError): self.repo.get_commit(commit_idx=self.repo.count() + 1) @@ -289,7 +291,7 @@ Introduction TODO: To be written... """ - node = commit10.get_node('README.rst') + node = commit10.get_node("README.rst") assert node.kind == NodeKind.FILE assert node.str_content == README @@ -308,77 +310,73 @@ TODO: To be written... repo_clone = self.get_clone_repo() branches = repo_clone.branches - repo_clone._update('default') - assert branches['default'] == repo_clone._identify() - repo_clone._update('stable') - assert branches['stable'] == repo_clone._identify() + repo_clone._update("default") + assert branches["default"] == repo_clone._identify() + repo_clone._update("stable") + assert branches["stable"] == repo_clone._identify() def test_local_pull_branch(self): target_repo = self.get_empty_repo() source_repo = self.get_clone_repo() - default = Reference( - 'branch', 'default', source_repo.branches['default']) + default = Reference("branch", "default", source_repo.branches["default"]) target_repo._local_pull(source_repo.path, default) target_repo = MercurialRepository(target_repo.path) - assert (target_repo.branches['default'] == - source_repo.branches['default']) + assert target_repo.branches["default"] == source_repo.branches["default"] - stable = Reference('branch', 'stable', source_repo.branches['stable']) + stable = Reference("branch", "stable", source_repo.branches["stable"]) target_repo._local_pull(source_repo.path, stable) target_repo = MercurialRepository(target_repo.path) - assert target_repo.branches['stable'] == source_repo.branches['stable'] + assert target_repo.branches["stable"] == source_repo.branches["stable"] def test_local_pull_bookmark(self): target_repo = self.get_empty_repo() source_repo = self.get_clone_repo() - commits = list(source_repo.get_commits(branch_name='default')) + commits = list(source_repo.get_commits(branch_name="default")) foo1_id = commits[-5].raw_id - foo1 = Reference('book', 'foo1', foo1_id) + foo1 = Reference("book", "foo1", foo1_id) source_repo._update(foo1_id) - source_repo.bookmark('foo1') + source_repo.bookmark("foo1") foo2_id = commits[-3].raw_id - foo2 = Reference('book', 'foo2', foo2_id) + foo2 = Reference("book", "foo2", foo2_id) source_repo._update(foo2_id) - source_repo.bookmark('foo2') + source_repo.bookmark("foo2") target_repo._local_pull(source_repo.path, foo1) target_repo = MercurialRepository(target_repo.path) - assert target_repo.branches['default'] == commits[-5].raw_id + assert target_repo.branches["default"] == commits[-5].raw_id target_repo._local_pull(source_repo.path, foo2) target_repo = MercurialRepository(target_repo.path) - assert target_repo.branches['default'] == commits[-3].raw_id + assert target_repo.branches["default"] == commits[-3].raw_id def test_local_pull_commit(self): target_repo = self.get_empty_repo() source_repo = self.get_clone_repo() - commits = list(source_repo.get_commits(branch_name='default')) + commits = list(source_repo.get_commits(branch_name="default")) commit_id = commits[-5].raw_id - commit = Reference('rev', commit_id, commit_id) + commit = Reference("rev", commit_id, commit_id) target_repo._local_pull(source_repo.path, commit) target_repo = MercurialRepository(target_repo.path) - assert target_repo.branches['default'] == commit_id + assert target_repo.branches["default"] == commit_id commit_id = commits[-3].raw_id - commit = Reference('rev', commit_id, commit_id) + commit = Reference("rev", commit_id, commit_id) target_repo._local_pull(source_repo.path, commit) target_repo = MercurialRepository(target_repo.path) - assert target_repo.branches['default'] == commit_id + assert target_repo.branches["default"] == commit_id def test_local_pull_from_same_repo(self): - reference = Reference('branch', 'default', None) + reference = Reference("branch", "default", None) with pytest.raises(ValueError): self.repo._local_pull(self.repo.path, reference) - def test_validate_pull_reference_raises_on_missing_reference( - self, vcsbackend_hg): + def test_validate_pull_reference_raises_on_missing_reference(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) - reference = Reference( - 'book', 'invalid_reference', 'a' * 40) + reference = Reference("book", "invalid_reference", "a" * 40) with pytest.raises(CommitDoesNotExistError): target_repo._validate_pull_reference(reference) @@ -387,51 +385,48 @@ TODO: To be written... assert set(self.repo._heads()) == set(self.repo.branches.values()) def test_ancestor(self): - commits = [ - c.raw_id for c in self.repo.get_commits(branch_name='default')] + commits = [c.raw_id for c in self.repo.get_commits(branch_name="default")] assert self.repo._ancestor(commits[-3], commits[-5]) == commits[-5] assert self.repo._ancestor(commits[-5], commits[-3]) == commits[-5] def test_local_push(self): target_repo = self.get_empty_repo() - revisions = list(self.repo.get_commits(branch_name='default')) + revisions = list(self.repo.get_commits(branch_name="default")) revision = revisions[-5].raw_id self.repo._local_push(revision, target_repo.path) target_repo = MercurialRepository(target_repo.path) - assert target_repo.branches['default'] == revision + assert target_repo.branches["default"] == revision def test_hooks_can_be_enabled_for_local_push(self): - revision = 'deadbeef' - repo_path = 'test_group/test_repo' - with mock.patch.object(self.repo, '_remote') as remote_mock: + revision = "deadbeef" + repo_path = "test_group/test_repo" + with mock.patch.object(self.repo, "_remote") as remote_mock: self.repo._local_push(revision, repo_path, enable_hooks=True) - remote_mock.push.assert_called_once_with( - [revision], repo_path, hooks=True, push_branches=False) + remote_mock.push.assert_called_once_with([revision], repo_path, hooks=True, push_branches=False) def test_local_merge(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) source_repo = vcsbackend_hg.clone_repo(target_repo) - vcsbackend_hg.add_file(target_repo, b'README_MERGE1', b'Version 1') + vcsbackend_hg.add_file(target_repo, b"README_MERGE1", b"Version 1") target_repo = MercurialRepository(target_repo.path) - target_rev = target_repo.branches['default'] - target_ref = Reference( - type='branch', name='default', commit_id=target_rev) - vcsbackend_hg.add_file(source_repo, b'README_MERGE2', b'Version 2') + target_rev = target_repo.branches["default"] + target_ref = Reference(type="branch", name="default", commit_id=target_rev) + vcsbackend_hg.add_file(source_repo, b"README_MERGE2", b"Version 2") source_repo = MercurialRepository(source_repo.path) - source_rev = source_repo.branches['default'] - source_ref = Reference( - type='branch', name='default', commit_id=source_rev) + source_rev = source_repo.branches["default"] + source_ref = Reference(type="branch", name="default", commit_id=source_rev) target_repo._local_pull(source_repo.path, source_ref) - merge_message = 'Merge message\n\nDescription:...' - user_name = 'Albert Einstein' - user_email = 'albert@einstein.com' + merge_message = "Merge message\n\nDescription:..." + user_name = "Albert Einstein" + user_email = "albert@einstein.com" merge_commit_id, needs_push = target_repo._local_merge( - target_ref, merge_message, user_name, user_email, source_ref) + target_ref, merge_message, user_name, user_email, source_ref + ) assert needs_push target_repo = MercurialRepository(target_repo.path) @@ -439,30 +434,28 @@ TODO: To be written... assert target_repo.commit_ids[-2] == source_rev last_commit = target_repo.get_commit(merge_commit_id) assert last_commit.message.strip() == merge_message - assert last_commit.author == '%s <%s>' % (user_name, user_email) + assert last_commit.author == "%s <%s>" % (user_name, user_email) - assert not os.path.exists( - os.path.join(target_repo.path, '.hg', 'merge', 'state')) + assert not os.path.exists(os.path.join(target_repo.path, ".hg", "merge", "state")) def test_local_merge_source_is_fast_forward(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) source_repo = vcsbackend_hg.clone_repo(target_repo) - target_rev = target_repo.branches['default'] - target_ref = Reference( - type='branch', name='default', commit_id=target_rev) - vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2') + target_rev = target_repo.branches["default"] + target_ref = Reference(type="branch", name="default", commit_id=target_rev) + vcsbackend_hg.add_file(source_repo, "README_MERGE2", "Version 2") source_repo = MercurialRepository(source_repo.path) - source_rev = source_repo.branches['default'] - source_ref = Reference( - type='branch', name='default', commit_id=source_rev) + source_rev = source_repo.branches["default"] + source_ref = Reference(type="branch", name="default", commit_id=source_rev) target_repo._local_pull(source_repo.path, source_ref) - merge_message = 'Merge message\n\nDescription:...' - user_name = 'Albert Einstein' - user_email = 'albert@einstein.com' + merge_message = "Merge message\n\nDescription:..." + user_name = "Albert Einstein" + user_email = "albert@einstein.com" merge_commit_id, needs_push = target_repo._local_merge( - target_ref, merge_message, user_name, user_email, source_ref) + target_ref, merge_message, user_name, user_email, source_ref + ) assert merge_commit_id == source_rev assert needs_push @@ -470,70 +463,62 @@ TODO: To be written... assert target_repo.commit_ids[-2] == target_rev assert target_repo.commit_ids[-1] == source_rev - assert not os.path.exists( - os.path.join(target_repo.path, '.hg', 'merge', 'state')) + assert not os.path.exists(os.path.join(target_repo.path, ".hg", "merge", "state")) def test_local_merge_source_is_integrated(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) - target_rev = target_repo.branches['default'] - target_ref = Reference( - type='branch', name='default', commit_id=target_rev) + target_rev = target_repo.branches["default"] + target_ref = Reference(type="branch", name="default", commit_id=target_rev) - merge_message = 'Merge message\n\nDescription:...' - user_name = 'Albert Einstein' - user_email = 'albert@einstein.com' + merge_message = "Merge message\n\nDescription:..." + user_name = "Albert Einstein" + user_email = "albert@einstein.com" merge_commit_id, needs_push = target_repo._local_merge( - target_ref, merge_message, user_name, user_email, target_ref) + target_ref, merge_message, user_name, user_email, target_ref + ) assert merge_commit_id == target_rev assert not needs_push target_repo = MercurialRepository(target_repo.path) assert target_repo.commit_ids[-1] == target_rev - assert not os.path.exists( - os.path.join(target_repo.path, '.hg', 'merge', 'state')) + assert not os.path.exists(os.path.join(target_repo.path, ".hg", "merge", "state")) def test_local_merge_raises_exception_on_conflict(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) source_repo = vcsbackend_hg.clone_repo(target_repo) - vcsbackend_hg.add_file(target_repo, 'README_MERGE', 'Version 1') + vcsbackend_hg.add_file(target_repo, "README_MERGE", "Version 1") target_repo = MercurialRepository(target_repo.path) - target_rev = target_repo.branches['default'] - target_ref = Reference( - type='branch', name='default', commit_id=target_rev) - vcsbackend_hg.add_file(source_repo, 'README_MERGE', 'Version 2') + target_rev = target_repo.branches["default"] + target_ref = Reference(type="branch", name="default", commit_id=target_rev) + vcsbackend_hg.add_file(source_repo, "README_MERGE", "Version 2") source_repo = MercurialRepository(source_repo.path) - source_rev = source_repo.branches['default'] - source_ref = Reference( - type='branch', name='default', commit_id=source_rev) + source_rev = source_repo.branches["default"] + source_ref = Reference(type="branch", name="default", commit_id=source_rev) target_repo._local_pull(source_repo.path, source_ref) with pytest.raises(RepositoryError): - target_repo._local_merge( - target_ref, 'merge_message', 'user name', 'user@name.com', - source_ref) + target_repo._local_merge(target_ref, "merge_message", "user name", "user@name.com", source_ref) # Check we are not left in an intermediate merge state - assert not os.path.exists( - os.path.join(target_repo.path, '.hg', 'merge', 'state')) + assert not os.path.exists(os.path.join(target_repo.path, ".hg", "merge", "state")) def test_local_merge_of_two_branches_of_the_same_repo(self, backend_hg): commits = [ - {'message': 'a'}, - {'message': 'b', 'branch': 'b'}, + {"message": "a"}, + {"message": "b", "branch": "b"}, ] repo = backend_hg.create_repo(commits) commit_ids = backend_hg.commit_ids - target_ref = Reference( - type='branch', name='default', commit_id=commit_ids['a']) - source_ref = Reference( - type='branch', name='b', commit_id=commit_ids['b']) - merge_message = 'Merge message\n\nDescription:...' - user_name = 'Albert Einstein' - user_email = 'albert@einstein.com' + target_ref = Reference(type="branch", name="default", commit_id=commit_ids["a"]) + source_ref = Reference(type="branch", name="b", commit_id=commit_ids["b"]) + merge_message = "Merge message\n\nDescription:..." + user_name = "Albert Einstein" + user_email = "albert@einstein.com" vcs_repo = repo.scm_instance() merge_commit_id, needs_push = vcs_repo._local_merge( - target_ref, merge_message, user_name, user_email, source_ref) + target_ref, merge_message, user_name, user_email, source_ref + ) assert merge_commit_id != source_ref.commit_id assert needs_push is True commit = vcs_repo.get_commit(merge_commit_id) @@ -541,63 +526,62 @@ TODO: To be written... assert commit.message == merge_message def test_maybe_prepare_merge_workspace(self): - workspace = self.repo._maybe_prepare_merge_workspace( - 1, 'pr2', 'unused', 'unused2') + workspace = self.repo._maybe_prepare_merge_workspace(1, "pr2", "unused", "unused2") assert os.path.isdir(workspace) workspace_repo = MercurialRepository(workspace) assert workspace_repo.branches == self.repo.branches # Calling it a second time should also succeed - workspace = self.repo._maybe_prepare_merge_workspace( - 1, 'pr2', 'unused', 'unused2') + workspace = self.repo._maybe_prepare_merge_workspace(1, "pr2", "unused", "unused2") assert os.path.isdir(workspace) def test_cleanup_merge_workspace(self): - workspace = self.repo._maybe_prepare_merge_workspace( - 1, 'pr3', 'unused', 'unused2') + workspace = self.repo._maybe_prepare_merge_workspace(1, "pr3", "unused", "unused2") assert os.path.isdir(workspace) - self.repo.cleanup_merge_workspace(1, 'pr3') + self.repo.cleanup_merge_workspace(1, "pr3") assert not os.path.exists(workspace) def test_cleanup_merge_workspace_invalid_workspace_id(self): # No assert: because in case of an inexistent workspace this function # should still succeed. - self.repo.cleanup_merge_workspace(1, 'pr4') + self.repo.cleanup_merge_workspace(1, "pr4") def test_merge_target_is_bookmark(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) source_repo = vcsbackend_hg.clone_repo(target_repo) - vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1') - vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2') + vcsbackend_hg.add_file(target_repo, "README_MERGE1", "Version 1") + vcsbackend_hg.add_file(source_repo, "README_MERGE2", "Version 2") imc = source_repo.in_memory_commit - imc.add(FileNode(b'file_x', content=source_repo.name)) - imc.commit( - message='Automatic commit from repo merge test', - author='Automatic ') + imc.add(FileNode(b"file_x", content=source_repo.name)) + imc.commit(message="Automatic commit from repo merge test", author="Automatic ") target_commit = target_repo.get_commit() source_commit = source_repo.get_commit() default_branch = target_repo.DEFAULT_BRANCH_NAME - bookmark_name = 'bookmark' + bookmark_name = "bookmark" target_repo._update(default_branch) target_repo.bookmark(bookmark_name) - target_ref = Reference('book', bookmark_name, target_commit.raw_id) - source_ref = Reference('branch', default_branch, source_commit.raw_id) - workspace_id = 'test-merge' + target_ref = Reference("book", bookmark_name, target_commit.raw_id) + source_ref = Reference("branch", default_branch, source_commit.raw_id) + workspace_id = "test-merge" repo_id = repo_id_generator(target_repo.path) merge_response = target_repo.merge( - repo_id, workspace_id, target_ref, source_repo, source_ref, - 'test user', 'test@rhodecode.com', 'merge message 1', - dry_run=False) - expected_merge_response = MergeResponse( - True, True, merge_response.merge_ref, - MergeFailureReason.NONE) + repo_id, + workspace_id, + target_ref, + source_repo, + source_ref, + "test user", + "test@rhodecode.com", + "merge message 1", + dry_run=False, + ) + expected_merge_response = MergeResponse(True, True, merge_response.merge_ref, MergeFailureReason.NONE) assert merge_response == expected_merge_response - target_repo = backends.get_backend(vcsbackend_hg.alias)( - target_repo.path) + target_repo = backends.get_backend(vcsbackend_hg.alias)(target_repo.path) target_commits = list(target_repo.get_commits()) commit_ids = [c.raw_id for c in target_commits[:-1]] assert source_ref.commit_id in commit_ids @@ -605,43 +589,43 @@ TODO: To be written... merge_commit = target_commits[-1] assert merge_commit.raw_id == merge_response.merge_ref.commit_id - assert merge_commit.message.strip() == 'merge message 1' - assert merge_commit.author == 'test user ' + assert merge_commit.message.strip() == "merge message 1" + assert merge_commit.author == "test user " # Check the bookmark was updated in the target repo - assert ( - target_repo.bookmarks[bookmark_name] == - merge_response.merge_ref.commit_id) + assert target_repo.bookmarks[bookmark_name] == merge_response.merge_ref.commit_id def test_merge_source_is_bookmark(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) source_repo = vcsbackend_hg.clone_repo(target_repo) imc = source_repo.in_memory_commit - imc.add(FileNode(b'file_x', content=source_repo.name)) - imc.commit( - message='Automatic commit from repo merge test', - author='Automatic ') + imc.add(FileNode(b"file_x", content=source_repo.name)) + imc.commit(message="Automatic commit from repo merge test", author="Automatic ") target_commit = target_repo.get_commit() source_commit = source_repo.get_commit() default_branch = target_repo.DEFAULT_BRANCH_NAME - bookmark_name = 'bookmark' - target_ref = Reference('branch', default_branch, target_commit.raw_id) + bookmark_name = "bookmark" + target_ref = Reference("branch", default_branch, target_commit.raw_id) source_repo._update(default_branch) source_repo.bookmark(bookmark_name) - source_ref = Reference('book', bookmark_name, source_commit.raw_id) - workspace_id = 'test-merge' + source_ref = Reference("book", bookmark_name, source_commit.raw_id) + workspace_id = "test-merge" repo_id = repo_id_generator(target_repo.path) merge_response = target_repo.merge( - repo_id, workspace_id, target_ref, source_repo, source_ref, - 'test user', 'test@rhodecode.com', 'merge message 1', - dry_run=False) - expected_merge_response = MergeResponse( - True, True, merge_response.merge_ref, - MergeFailureReason.NONE) + repo_id, + workspace_id, + target_ref, + source_repo, + source_ref, + "test user", + "test@rhodecode.com", + "merge message 1", + dry_run=False, + ) + expected_merge_response = MergeResponse(True, True, merge_response.merge_ref, MergeFailureReason.NONE) assert merge_response == expected_merge_response - target_repo = backends.get_backend(vcsbackend_hg.alias)( - target_repo.path) + target_repo = backends.get_backend(vcsbackend_hg.alias)(target_repo.path) target_commits = list(target_repo.get_commits()) commit_ids = [c.raw_id for c in target_commits] assert source_ref.commit_id == commit_ids[-1] @@ -650,78 +634,89 @@ TODO: To be written... def test_merge_target_has_multiple_heads(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=2) source_repo = vcsbackend_hg.clone_repo(target_repo) - vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1') - vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2') + vcsbackend_hg.add_file(target_repo, "README_MERGE1", "Version 1") + vcsbackend_hg.add_file(source_repo, "README_MERGE2", "Version 2") # add an extra head to the target repo imc = target_repo.in_memory_commit - imc.add(FileNode(b'file_x', content='foo')) + imc.add(FileNode(b"file_x", content="foo")) commits = list(target_repo.get_commits()) imc.commit( - message='Automatic commit from repo merge test', - author='Automatic ', parents=commits[0:1]) + message="Automatic commit from repo merge test", + author="Automatic ", + parents=commits[0:1], + ) target_commit = target_repo.get_commit() source_commit = source_repo.get_commit() default_branch = target_repo.DEFAULT_BRANCH_NAME target_repo._update(default_branch) - target_ref = Reference('branch', default_branch, target_commit.raw_id) - source_ref = Reference('branch', default_branch, source_commit.raw_id) - workspace_id = 'test-merge' + target_ref = Reference("branch", default_branch, target_commit.raw_id) + source_ref = Reference("branch", default_branch, source_commit.raw_id) + workspace_id = "test-merge" - assert len(target_repo._heads(branch='default')) == 2 - heads = target_repo._heads(branch='default') + assert len(target_repo._heads(branch="default")) == 2 + heads = target_repo._heads(branch="default") expected_merge_response = MergeResponse( - False, False, None, - MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS, - metadata={'heads': heads}) + False, False, None, MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS, metadata={"heads": heads} + ) repo_id = repo_id_generator(target_repo.path) merge_response = target_repo.merge( - repo_id, workspace_id, target_ref, source_repo, source_ref, - 'test user', 'test@rhodecode.com', 'merge message 1', - dry_run=False) + repo_id, + workspace_id, + target_ref, + source_repo, + source_ref, + "test user", + "test@rhodecode.com", + "merge message 1", + dry_run=False, + ) assert merge_response == expected_merge_response def test_merge_rebase_source_is_updated_bookmark(self, vcsbackend_hg): target_repo = vcsbackend_hg.create_repo(number_of_commits=1) source_repo = vcsbackend_hg.clone_repo(target_repo) - vcsbackend_hg.add_file(target_repo, b'README_MERGE1', b'Version 1') - vcsbackend_hg.add_file(source_repo, b'README_MERGE2', b'Version 2') + vcsbackend_hg.add_file(target_repo, b"README_MERGE1", b"Version 1") + vcsbackend_hg.add_file(source_repo, b"README_MERGE2", b"Version 2") imc = source_repo.in_memory_commit - imc.add(FileNode(b'file_x', content=safe_bytes(source_repo.name))) - imc.commit( - message='Automatic commit from repo merge test', - author='Automatic ') + imc.add(FileNode(b"file_x", content=safe_bytes(source_repo.name))) + imc.commit(message="Automatic commit from repo merge test", author="Automatic ") target_commit = target_repo.get_commit() source_commit = source_repo.get_commit() - vcsbackend_hg.add_file(source_repo, b'LICENSE', b'LICENSE Info') + vcsbackend_hg.add_file(source_repo, b"LICENSE", b"LICENSE Info") default_branch = target_repo.DEFAULT_BRANCH_NAME - bookmark_name = 'bookmark' + bookmark_name = "bookmark" source_repo._update(default_branch) source_repo.bookmark(bookmark_name) - target_ref = Reference('branch', default_branch, target_commit.raw_id) - source_ref = Reference('book', bookmark_name, source_commit.raw_id) + target_ref = Reference("branch", default_branch, target_commit.raw_id) + source_ref = Reference("book", bookmark_name, source_commit.raw_id) repo_id = repo_id_generator(target_repo.path) - workspace_id = 'test-merge' + workspace_id = "test-merge" merge_response = target_repo.merge( - repo_id, workspace_id, target_ref, source_repo, source_ref, - 'test user', 'test@rhodecode.com', 'merge message 1', - dry_run=False, use_rebase=True) + repo_id, + workspace_id, + target_ref, + source_repo, + source_ref, + "test user", + "test@rhodecode.com", + "merge message 1", + dry_run=False, + use_rebase=True, + ) - expected_merge_response = MergeResponse( - True, True, merge_response.merge_ref, - MergeFailureReason.NONE) + expected_merge_response = MergeResponse(True, True, merge_response.merge_ref, MergeFailureReason.NONE) assert merge_response == expected_merge_response - target_repo = backends.get_backend(vcsbackend_hg.alias)( - target_repo.path) + target_repo = backends.get_backend(vcsbackend_hg.alias)(target_repo.path) last_commit = target_repo.get_commit() assert last_commit.message == source_commit.message assert last_commit.author == source_commit.author @@ -736,27 +731,28 @@ TODO: To be written... class TestGetShadowInstance(object): @pytest.fixture() - def repo(self, vcsbackend_hg, monkeypatch): - repo = vcsbackend_hg.repo - monkeypatch.setattr(repo, 'config', mock.Mock()) - monkeypatch.setattr('rhodecode.lib.vcs.connection.Hg', mock.Mock()) - return repo + def repo(self, vcsbackend_hg): + _hg_repo = vcsbackend_hg.repo + connection_mock = mock.Mock(unsafe=True, name="connection.Hg") + mock.patch("rhodecode.lib.vcs.connection.Hg", connection_mock) + return _hg_repo - def test_passes_config(self, repo): + def test_getting_shadow_instance_copies_config(self, repo): shadow = repo.get_shadow_instance(repo.path) - assert shadow.config == repo.config.copy() + assert shadow.config.serialize() == repo.config.serialize() - def test_disables_hooks(self, repo): + def test_disables_hooks_section(self, repo): + repo.config.set('hooks', 'foo', 'val') shadow = repo.get_shadow_instance(repo.path) - shadow.config.clear_section.assert_called_once_with('hooks') + assert not shadow.config.items('hooks') def test_allows_to_keep_hooks(self, repo): + repo.config.set('hooks', 'foo', 'val') shadow = repo.get_shadow_instance(repo.path, enable_hooks=True) - assert not shadow.config.clear_section.called + assert shadow.config.items('hooks') class TestMercurialCommit(object): - def _test_equality(self, commit): idx = commit.idx assert commit == self.repo.get_commit(commit_idx=idx) @@ -768,15 +764,15 @@ class TestMercurialCommit(object): self._test_equality(commit) def test_default_commit(self): - tip = self.repo.get_commit('tip') + tip = self.repo.get_commit("tip") assert tip == self.repo.get_commit() assert tip == self.repo.get_commit(commit_id=None) assert tip == self.repo.get_commit(commit_idx=None) assert tip == list(self.repo[-1:])[0] def test_root_node(self): - tip = self.repo.get_commit('tip') - assert tip.root is tip.get_node('') + tip = self.repo.get_commit("tip") + assert tip.root is tip.get_node("") def test_lazy_fetch(self): """ @@ -792,44 +788,43 @@ class TestMercurialCommit(object): # accessing root.nodes updates commit.nodes assert len(commit.nodes) == 9 - docs = root.get_node('docs') + docs = root.get_node("docs") # we haven't yet accessed anything new as docs dir was already cached assert len(commit.nodes) == 9 assert len(docs.nodes) == 8 # accessing docs.nodes updates commit.nodes assert len(commit.nodes) == 17 - assert docs is commit.get_node('docs') + assert docs is commit.get_node("docs") assert docs is root.nodes[0] assert docs is root.dirs[0] - assert docs is commit.get_node('docs') + assert docs is commit.get_node("docs") def test_nodes_with_commit(self): commit = self.repo.get_commit(commit_idx=45) root = commit.root - docs = root.get_node('docs') - assert docs is commit.get_node('docs') - api = docs.get_node('api') - assert api is commit.get_node('docs/api') - index = api.get_node('index.rst') - assert index is commit.get_node('docs/api/index.rst') - assert index is commit.get_node( - 'docs').get_node('api').get_node('index.rst') + docs = root.get_node("docs") + assert docs is commit.get_node("docs") + api = docs.get_node("api") + assert api is commit.get_node("docs/api") + index = api.get_node("index.rst") + assert index is commit.get_node("docs/api/index.rst") + assert index is commit.get_node("docs").get_node("api").get_node("index.rst") def test_branch_and_tags(self): commit0 = self.repo.get_commit(commit_idx=0) - assert commit0.branch == 'default' + assert commit0.branch == "default" assert commit0.tags == [] commit10 = self.repo.get_commit(commit_idx=10) - assert commit10.branch == 'default' + assert commit10.branch == "default" assert commit10.tags == [] commit44 = self.repo.get_commit(commit_idx=44) - assert commit44.branch == 'web' + assert commit44.branch == "web" - tip = self.repo.get_commit('tip') - assert 'tip' in tip.tags + tip = self.repo.get_commit("tip") + assert "tip" in tip.tags def test_bookmarks(self): commit0 = self.repo.get_commit(commit_idx=0) @@ -842,46 +837,84 @@ class TestMercurialCommit(object): def test_file_size(self): to_check = ( - (10, 'setup.py', 1068), - (20, 'setup.py', 1106), - (60, 'setup.py', 1074), - - (10, 'vcs/backends/base.py', 2921), - (20, 'vcs/backends/base.py', 3936), - (60, 'vcs/backends/base.py', 6189), + (10, "setup.py", 1068), + (20, "setup.py", 1106), + (60, "setup.py", 1074), + (10, "vcs/backends/base.py", 2921), + (20, "vcs/backends/base.py", 3936), + (60, "vcs/backends/base.py", 6189), ) for idx, path, size in to_check: self._test_file_size(idx, path, size) def test_file_history_from_commits(self): - node = self.repo[10].get_node('setup.py') + node = self.repo[10].get_node("setup.py") commit_ids = [commit.raw_id for commit in node.history] - assert ['3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == commit_ids + assert ["3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == commit_ids - node = self.repo[20].get_node('setup.py') + node = self.repo[20].get_node("setup.py") node_ids = [commit.raw_id for commit in node.history] - assert ['eada5a770da98ab0dd7325e29d00e0714f228d09', - '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == node_ids + assert ["eada5a770da98ab0dd7325e29d00e0714f228d09", "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == node_ids # special case we check history from commit that has this particular # file changed this means we check if it's included as well - node = self.repo.get_commit('eada5a770da98ab0dd7325e29d00e0714f228d09')\ - .get_node('setup.py') + node = self.repo.get_commit("eada5a770da98ab0dd7325e29d00e0714f228d09").get_node("setup.py") node_ids = [commit.raw_id for commit in node.history] - assert ['eada5a770da98ab0dd7325e29d00e0714f228d09', - '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == node_ids + assert ["eada5a770da98ab0dd7325e29d00e0714f228d09", "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == node_ids def test_file_history(self): # we can only check if those commits are present in the history # as we cannot update this test every time file is changed files = { - 'setup.py': [7, 18, 45, 46, 47, 69, 77], - 'vcs/nodes.py': [ - 7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60, 61, 73, 76], - 'vcs/backends/hg.py': [ - 4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23, 26, 27, 28, 30, - 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47, 48, 49, 53, 54, - 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79, 82], + "setup.py": [7, 18, 45, 46, 47, 69, 77], + "vcs/nodes.py": [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60, 61, 73, 76], + "vcs/backends/hg.py": [ + 4, + 5, + 6, + 11, + 12, + 13, + 14, + 15, + 16, + 21, + 22, + 23, + 26, + 27, + 28, + 30, + 31, + 33, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 44, + 45, + 47, + 48, + 49, + 53, + 54, + 55, + 58, + 60, + 61, + 67, + 68, + 69, + 70, + 73, + 77, + 78, + 79, + 82, + ], } for path, indexes in files.items(): tip = self.repo.get_commit(commit_idx=indexes[-1]) @@ -889,72 +922,105 @@ class TestMercurialCommit(object): node_indexes = [commit.idx for commit in node.history] assert set(indexes).issubset(set(node_indexes)), ( "We assumed that %s is subset of commits for which file %s " - "has been changed, and history of that node returned: %s" - % (indexes, path, node_indexes)) + "has been changed, and history of that node returned: %s" % (indexes, path, node_indexes) + ) def test_file_annotate(self): files = { - 'vcs/backends/__init__.py': { + "vcs/backends/__init__.py": { 89: { - 'lines_no': 31, - 'commits': [ - 32, 32, 61, 32, 32, 37, 32, 32, 32, 44, - 37, 37, 37, 37, 45, 37, 44, 37, 37, 37, - 32, 32, 32, 32, 37, 32, 37, 37, 32, - 32, 32 - ] - }, - 20: { - 'lines_no': 1, - 'commits': [4] + "lines_no": 31, + "commits": [ + 32, + 32, + 61, + 32, + 32, + 37, + 32, + 32, + 32, + 44, + 37, + 37, + 37, + 37, + 45, + 37, + 44, + 37, + 37, + 37, + 32, + 32, + 32, + 32, + 37, + 32, + 37, + 37, + 32, + 32, + 32, + ], }, + 20: {"lines_no": 1, "commits": [4]}, 55: { - 'lines_no': 31, - 'commits': [ - 32, 32, 45, 32, 32, 37, 32, 32, 32, 44, - 37, 37, 37, 37, 45, 37, 44, 37, 37, 37, - 32, 32, 32, 32, 37, 32, 37, 37, 32, - 32, 32 - ] - } + "lines_no": 31, + "commits": [ + 32, + 32, + 45, + 32, + 32, + 37, + 32, + 32, + 32, + 44, + 37, + 37, + 37, + 37, + 45, + 37, + 44, + 37, + 37, + 37, + 32, + 32, + 32, + 32, + 37, + 32, + 37, + 37, + 32, + 32, + 32, + ], + }, }, - 'vcs/exceptions.py': { + "vcs/exceptions.py": { 89: { - 'lines_no': 18, - 'commits': [ - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 17, 16, 16, 18, 18, 18 - ] + "lines_no": 18, + "commits": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 18, 18, 18], }, 20: { - 'lines_no': 18, - 'commits': [ - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 17, 16, 16, 18, 18, 18 - ] + "lines_no": 18, + "commits": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 18, 18, 18], }, 55: { - 'lines_no': 18, - 'commits': [ - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 17, 16, 16, 18, 18, 18 - ] - } + "lines_no": 18, + "commits": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 18, 18, 18], + }, + }, + "MANIFEST.in": { + 89: {"lines_no": 5, "commits": [7, 7, 7, 71, 71]}, + 20: {"lines_no": 3, "commits": [7, 7, 7]}, + 55: {"lines_no": 3, "commits": [7, 7, 7]}, }, - 'MANIFEST.in': { - 89: { - 'lines_no': 5, - 'commits': [7, 7, 7, 71, 71] - }, - 20: { - 'lines_no': 3, - 'commits': [7, 7, 7] - }, - 55: { - 'lines_no': 3, - 'commits': [7, 7, 7] - } - } } for fname, commit_dict in files.items(): @@ -963,13 +1029,13 @@ class TestMercurialCommit(object): l1_1 = [x[1] for x in commit.get_file_annotate(fname)] l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)] assert l1_1 == l1_2 - l1 = l1_2 = [ - x[2]().idx for x in commit.get_file_annotate(fname)] - l2 = files[fname][idx]['commits'] + l1 = l1_2 = [x[2]().idx for x in commit.get_file_annotate(fname)] + l2 = files[fname][idx]["commits"] assert l1 == l2, ( "The lists of commit for %s@commit_id%s" "from annotation list should match each other," - "got \n%s \nvs \n%s " % (fname, idx, l1, l2)) + "got \n%s \nvs \n%s " % (fname, idx, l1, l2) + ) def test_commit_state(self): """ @@ -981,28 +1047,52 @@ class TestMercurialCommit(object): # changed: 13 # added: 20 # removed: 1 - changed = set([ - '.hgignore', 'README.rst', 'docs/conf.py', 'docs/index.rst', - 'setup.py', 'tests/test_hg.py', 'tests/test_nodes.py', - 'vcs/__init__.py', 'vcs/backends/__init__.py', - 'vcs/backends/base.py', 'vcs/backends/hg.py', 'vcs/nodes.py', - 'vcs/utils/__init__.py']) + changed = set( + [ + ".hgignore", + "README.rst", + "docs/conf.py", + "docs/index.rst", + "setup.py", + "tests/test_hg.py", + "tests/test_nodes.py", + "vcs/__init__.py", + "vcs/backends/__init__.py", + "vcs/backends/base.py", + "vcs/backends/hg.py", + "vcs/nodes.py", + "vcs/utils/__init__.py", + ] + ) - added = set([ - 'docs/api/backends/hg.rst', 'docs/api/backends/index.rst', - 'docs/api/index.rst', 'docs/api/nodes.rst', - 'docs/api/web/index.rst', 'docs/api/web/simplevcs.rst', - 'docs/installation.rst', 'docs/quickstart.rst', 'setup.cfg', - 'vcs/utils/baseui_config.py', 'vcs/utils/web.py', - 'vcs/web/__init__.py', 'vcs/web/exceptions.py', - 'vcs/web/simplevcs/__init__.py', 'vcs/web/simplevcs/exceptions.py', - 'vcs/web/simplevcs/middleware.py', 'vcs/web/simplevcs/models.py', - 'vcs/web/simplevcs/settings.py', 'vcs/web/simplevcs/utils.py', - 'vcs/web/simplevcs/views.py']) + added = set( + [ + "docs/api/backends/hg.rst", + "docs/api/backends/index.rst", + "docs/api/index.rst", + "docs/api/nodes.rst", + "docs/api/web/index.rst", + "docs/api/web/simplevcs.rst", + "docs/installation.rst", + "docs/quickstart.rst", + "setup.cfg", + "vcs/utils/baseui_config.py", + "vcs/utils/web.py", + "vcs/web/__init__.py", + "vcs/web/exceptions.py", + "vcs/web/simplevcs/__init__.py", + "vcs/web/simplevcs/exceptions.py", + "vcs/web/simplevcs/middleware.py", + "vcs/web/simplevcs/models.py", + "vcs/web/simplevcs/settings.py", + "vcs/web/simplevcs/utils.py", + "vcs/web/simplevcs/views.py", + ] + ) - removed = set(['docs/api.rst']) + removed = set(["docs/api.rst"]) - commit64 = self.repo.get_commit('46ad32a4f974') + commit64 = self.repo.get_commit("46ad32a4f974") assert set((node.path for node in commit64.added)) == added assert set((node.path for node in commit64.changed)) == changed assert set((node.path for node in commit64.removed)) == removed @@ -1012,10 +1102,9 @@ class TestMercurialCommit(object): # changed: 13 # added: 20 # removed: 1 - commit88 = self.repo.get_commit('b090f22d27d6') + commit88 = self.repo.get_commit("b090f22d27d6") assert set((node.path for node in commit88.added)) == set() - assert set((node.path for node in commit88.changed)) == \ - set(['.hgignore']) + assert set((node.path for node in commit88.changed)) == set([".hgignore"]) assert set((node.path for node in commit88.removed)) == set() # @@ -1025,24 +1114,25 @@ class TestMercurialCommit(object): # changed: 4 ['vcs/web/simplevcs/models.py', ...] # removed: 1 ['vcs/utils/web.py'] commit85 = self.repo.get_commit(commit_idx=85) - assert set((node.path for node in commit85.added)) == set([ - 'vcs/utils/diffs.py', - 'vcs/web/simplevcs/views/diffs.py']) - assert set((node.path for node in commit85.changed)) == set([ - 'vcs/web/simplevcs/models.py', - 'vcs/web/simplevcs/utils.py', - 'vcs/web/simplevcs/views/__init__.py', - 'vcs/web/simplevcs/views/repository.py', - ]) - assert set((node.path for node in commit85.removed)) == \ - set(['vcs/utils/web.py']) + assert set((node.path for node in commit85.added)) == set( + ["vcs/utils/diffs.py", "vcs/web/simplevcs/views/diffs.py"] + ) + assert set((node.path for node in commit85.changed)) == set( + [ + "vcs/web/simplevcs/models.py", + "vcs/web/simplevcs/utils.py", + "vcs/web/simplevcs/views/__init__.py", + "vcs/web/simplevcs/views/repository.py", + ] + ) + assert set((node.path for node in commit85.removed)) == set(["vcs/utils/web.py"]) def test_files_state(self): """ Tests state of FileNodes. """ commit = self.repo.get_commit(commit_idx=85) - node = commit.get_node('vcs/utils/diffs.py') + node = commit.get_node("vcs/utils/diffs.py") assert node.state, NodeState.ADDED assert node.added assert not node.changed @@ -1050,7 +1140,7 @@ class TestMercurialCommit(object): assert not node.removed commit = self.repo.get_commit(commit_idx=88) - node = commit.get_node('.hgignore') + node = commit.get_node(".hgignore") assert node.state, NodeState.CHANGED assert not node.added assert node.changed @@ -1058,7 +1148,7 @@ class TestMercurialCommit(object): assert not node.removed commit = self.repo.get_commit(commit_idx=85) - node = commit.get_node('setup.py') + node = commit.get_node("setup.py") assert node.state, NodeState.NOT_CHANGED assert not node.added assert not node.changed @@ -1068,7 +1158,7 @@ class TestMercurialCommit(object): # If node has REMOVED state then trying to fetch it would raise # CommitError exception commit = self.repo.get_commit(commit_idx=2) - path = 'vcs/backends/BaseRepository.py' + path = "vcs/backends/BaseRepository.py" with pytest.raises(NodeDoesNotExistError): commit.get_node(path) # but it would be one of ``removed`` (commit's attribute) @@ -1084,57 +1174,53 @@ class TestMercurialCommit(object): def test_repo_files_content_type(self): test_commit = self.repo.get_commit(commit_idx=100) - for node in test_commit.get_node('/'): + for node in test_commit.get_node("/"): if node.is_file(): assert type(node.content) == bytes assert type(node.str_content) == str def test_wrong_path(self): # There is 'setup.py' in the root dir but not there: - path = 'foo/bar/setup.py' + path = "foo/bar/setup.py" with pytest.raises(VCSError): self.repo.get_commit().get_node(path) def test_author_email(self): - assert 'marcin@python-blog.com' == \ - self.repo.get_commit('b986218ba1c9').author_email - assert 'lukasz.balcerzak@python-center.pl' == \ - self.repo.get_commit('3803844fdbd3').author_email - assert '' == self.repo.get_commit('84478366594b').author_email + assert "marcin@python-blog.com" == self.repo.get_commit("b986218ba1c9").author_email + assert "lukasz.balcerzak@python-center.pl" == self.repo.get_commit("3803844fdbd3").author_email + assert "" == self.repo.get_commit("84478366594b").author_email def test_author_username(self): - assert 'Marcin Kuzminski' == \ - self.repo.get_commit('b986218ba1c9').author_name - assert 'Lukasz Balcerzak' == \ - self.repo.get_commit('3803844fdbd3').author_name - assert 'marcink' == \ - self.repo.get_commit('84478366594b').author_name + assert "Marcin Kuzminski" == self.repo.get_commit("b986218ba1c9").author_name + assert "Lukasz Balcerzak" == self.repo.get_commit("3803844fdbd3").author_name + assert "marcink" == self.repo.get_commit("84478366594b").author_name class TestLargeFileRepo(object): - def test_large_file(self, backend_hg): - repo = backend_hg.create_test_repo('largefiles', make_db_config()) + conf = make_db_config() + hg_largefiles_store = conf.get("largefiles", "usercache") + repo = backend_hg.create_test_repo("largefiles", conf) tip = repo.scm_instance().get_commit() - node = tip.get_node('.hglf/thisfileislarge') + node = tip.get_node(".hglf/thisfileislarge") lf_node = node.get_largefile_node() assert lf_node.is_largefile() is True assert lf_node.size == 1024000 - assert lf_node.name == '.hglf/thisfileislarge' + assert lf_node.name == ".hglf/thisfileislarge" class TestGetBranchName(object): def test_returns_ref_name_when_type_is_branch(self): - ref = self._create_ref('branch', 'fake-name') + ref = self._create_ref("branch", "fake-name") result = self.repo._get_branch_name(ref) assert result == ref.name @pytest.mark.parametrize("type_", ("book", "tag")) def test_queries_remote_when_type_is_not_branch(self, type_): - ref = self._create_ref(type_, 'wrong-fake-name') + ref = self._create_ref(type_, "wrong-fake-name") with mock.patch.object(self.repo, "_remote") as remote_mock: remote_mock.ctx_branch.return_value = "fake-name" result = self.repo._get_branch_name(ref) @@ -1144,7 +1230,7 @@ class TestGetBranchName(object): def _create_ref(self, type_, name): ref = mock.Mock() ref.type = type_ - ref.name = 'wrong-fake-name' + ref.name = "wrong-fake-name" ref.commit_id = "deadbeef" return ref @@ -1153,8 +1239,7 @@ class TestIsTheSameBranch(object): def test_returns_true_when_branches_are_equal(self): source_ref = mock.Mock(name="source-ref") target_ref = mock.Mock(name="target-ref") - branch_name_patcher = mock.patch.object( - self.repo, "_get_branch_name", return_value="default") + branch_name_patcher = mock.patch.object(self.repo, "_get_branch_name", return_value="default") with branch_name_patcher as branch_name_mock: result = self.repo._is_the_same_branch(source_ref, target_ref) @@ -1171,8 +1256,7 @@ class TestIsTheSameBranch(object): def side_effect(ref): return ref.name - branch_name_patcher = mock.patch.object( - self.repo, "_get_branch_name", side_effect=side_effect) + branch_name_patcher = mock.patch.object(self.repo, "_get_branch_name", side_effect=side_effect) with branch_name_patcher as branch_name_mock: result = self.repo._is_the_same_branch(source_ref, target_ref) diff --git a/rhodecode/tests/vcs/test_hg_vcsserver_cache_invalidation.py b/rhodecode/tests/vcs/test_hg_vcsserver_cache_invalidation.py index d5868e5c..c6b5f522 100644 --- a/rhodecode/tests/vcs/test_hg_vcsserver_cache_invalidation.py +++ b/rhodecode/tests/vcs/test_hg_vcsserver_cache_invalidation.py @@ -1,5 +1,3 @@ - - # Copyright (C) 2016-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -35,45 +33,47 @@ class TestMercurialRemoteRepoInvalidation(object): """ # Default reference used as a dummy during tests. - default_ref = Reference('branch', 'default', None) + default_ref = Reference("branch", "default", None) # Methods of vcsserver.hg.HgRemote that are "writing" operations. writing_methods = [ - 'bookmark', - 'commit', - 'merge', - 'pull', - 'pull_cmd', - 'rebase', - 'strip', - 'tag', + "bookmark", + "commit", + "merge", + "pull", + "pull_cmd", + "rebase", + "strip", + "tag", ] - @pytest.mark.parametrize('method_name, method_args', [ - ('_local_merge', [default_ref, None, None, None, default_ref]), - ('_local_pull', ['', default_ref]), - ('bookmark', [None]), - ('pull', ['', default_ref]), - ('remove_tag', ['mytag', None]), - ('strip', [None]), - ('tag', ['newtag', None]), - ]) - def test_method_invokes_invalidate_on_remote_repo( - self, method_name, method_args, backend_hg): + @pytest.mark.parametrize( + "method_name, method_args", + [ + ("_local_merge", [default_ref, None, None, None, default_ref]), + ("_local_pull", ["", default_ref]), + ("bookmark", [None]), + ("pull", ["", default_ref]), + ("remove_tag", ["mytag", None]), + ("strip", [None]), + ("tag", ["newtag", None]), + ], + ) + def test_method_invokes_invalidate_on_remote_repo(self, method_name, method_args, backend_hg): """ Check that the listed methods are invalidating the VCSServer cache after invoking a writing method of their remote repository object. """ - tags = {'mytag': 'mytag-id'} + tags = {"mytag": "mytag-id"} def add_tag(name, raw_id, *args, **kwds): tags[name] = raw_id repo = backend_hg.repo.scm_instance() - with patch.object(repo, '_remote') as remote: + with patch.object(repo, "_remote") as remote: repo.tags = tags - remote.lookup.return_value = ('commit-id', 'commit-idx') + remote.lookup.return_value = ("commit-id", "commit-idx") remote.tags.return_value = tags remote._get_tags.return_value = tags remote.is_empty.return_value = False @@ -98,6 +98,7 @@ class TestMercurialRemoteRepoInvalidation(object): references. """ from rhodecode.model.pull_request import PullRequestModel + repo_id = pull_request.target_repo.repo_id target_vcs = pull_request.target_repo.scm_instance() target_ref = pull_request.target_ref_parts @@ -107,7 +108,8 @@ class TestMercurialRemoteRepoInvalidation(object): pr = PullRequestModel() workspace_id = pr._workspace_id(pull_request) shadow_repository_path = target_vcs._maybe_prepare_merge_workspace( - repo_id, workspace_id, target_ref, source_ref) + repo_id, workspace_id, target_ref, source_ref + ) shadow_repo = target_vcs.get_shadow_instance(shadow_repository_path, cache=True) # This will populate the cache of the mercurial repository object @@ -116,7 +118,7 @@ class TestMercurialRemoteRepoInvalidation(object): return shadow_repo, source_ref, target_ref - @pytest.mark.backends('hg') + @pytest.mark.backends("hg") def test_commit_does_not_exist_error_happens(self, pr_util, app): """ This test is somewhat special. It does not really test the system @@ -132,18 +134,18 @@ class TestMercurialRemoteRepoInvalidation(object): source_vcs = pull_request.source_repo.scm_instance() shadow_repo, source_ref, target_ref = self._prepare_shadow_repo(pull_request) - initial_cache_uid = shadow_repo._remote._wire['context'] - initial_commit_ids = shadow_repo._remote.get_all_commit_ids('visible') + initial_cache_uid = shadow_repo._remote._wire["context"] + initial_commit_ids = shadow_repo._remote.get_all_commit_ids("visible") # Pull from target and source references but without invalidation of # RemoteRepo objects and without VCSServer caching of mercurial repository objects. - with patch.object(shadow_repo._remote, 'invalidate_vcs_cache'): + with patch.object(shadow_repo._remote, "invalidate_vcs_cache"): # NOTE: Do not use patch.dict() to disable the cache because it # restores the WHOLE dict and not only the patched keys. - shadow_repo._remote._wire['cache'] = False + shadow_repo._remote._wire["cache"] = False shadow_repo._local_pull(target_vcs.path, target_ref) shadow_repo._local_pull(source_vcs.path, source_ref) - shadow_repo._remote._wire['cache'] = True + shadow_repo._remote._wire["cache"] = True # Try to lookup the target_ref in shadow repo. This should work because # test_repo_maker_uses_session_for_instance_methods @@ -153,14 +155,14 @@ class TestMercurialRemoteRepoInvalidation(object): # we ensure that call context has not changed, this is what # `invalidate_vcs_cache` does - assert initial_cache_uid == shadow_repo._remote._wire['context'] + assert initial_cache_uid == shadow_repo._remote._wire["context"] # If we try to lookup all commits. # repo commit cache doesn't get invalidated. (Due to patched # invalidation and caching above). - assert initial_commit_ids == shadow_repo._remote.get_all_commit_ids('visible') + assert initial_commit_ids == shadow_repo._remote.get_all_commit_ids("visible") - @pytest.mark.backends('hg') + @pytest.mark.backends("hg") def test_commit_does_not_exist_error_does_not_happen(self, pr_util, app): """ This test simulates a pull request merge in which the pull operations @@ -179,10 +181,10 @@ class TestMercurialRemoteRepoInvalidation(object): # of RemoteRepo objects. # NOTE: Do not use patch.dict() to disable the cache because it # restores the WHOLE dict and not only the patched keys. - shadow_repo._remote._wire['cache'] = False + shadow_repo._remote._wire["cache"] = False shadow_repo._local_pull(target_vcs.path, target_ref) shadow_repo._local_pull(source_vcs.path, source_ref) - shadow_repo._remote._wire['cache'] = True + shadow_repo._remote._wire["cache"] = True # Try to lookup the target and source references in shadow repo. This # should work because the RemoteRepo object gets invalidated during the diff --git a/rhodecode/tests/vcs/test_inmemory.py b/rhodecode/tests/vcs/test_inmemory.py index 632d0638..e28bed33 100644 --- a/rhodecode/tests/vcs/test_inmemory.py +++ b/rhodecode/tests/vcs/test_inmemory.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -20,15 +19,21 @@ """ Tests so called "in memory commits" commit API of vcs. """ + import datetime import pytest from rhodecode.lib.str_utils import safe_bytes, safe_str from rhodecode.lib.vcs.exceptions import ( - EmptyRepositoryError, NodeAlreadyAddedError, NodeAlreadyExistsError, - NodeAlreadyRemovedError, NodeAlreadyChangedError, NodeDoesNotExistError, - NodeNotChangedError) + EmptyRepositoryError, + NodeAlreadyAddedError, + NodeAlreadyExistsError, + NodeAlreadyRemovedError, + NodeAlreadyChangedError, + NodeDoesNotExistError, + NodeNotChangedError, +) from rhodecode.lib.vcs.nodes import DirNode, FileNode from rhodecode.tests.vcs.conftest import BackendTestMixin @@ -36,19 +41,21 @@ from rhodecode.tests.vcs.conftest import BackendTestMixin @pytest.fixture() def nodes(): nodes = [ - FileNode(b'foobar', content=b'Foo & bar'), - FileNode(b'foobar2', content=b'Foo & bar, doubled!'), - FileNode(b'foo bar with spaces', content=b''), - FileNode(b'foo/bar/baz', content=b'Inside'), - FileNode(b'foo/bar/file.bin', content=( - b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x03\x00\xfe' - b'\xff\t\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x01\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00' - b'\x00\x18\x00\x00\x00\x01\x00\x00\x00\xfe\xff\xff\xff\x00\x00' - b'\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff' - b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' - ) + FileNode(b"foobar", content=b"Foo & bar"), + FileNode(b"foobar2", content=b"Foo & bar, doubled!"), + FileNode(b"foo bar with spaces", content=b""), + FileNode(b"foo/bar/baz", content=b"Inside"), + FileNode( + b"foo/bar/file.bin", + content=( + b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x03\x00\xfe" + b"\xff\t\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x01\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00" + b"\x00\x18\x00\x00\x00\x01\x00\x00\x00\xfe\xff\xff\xff\x00\x00" + b"\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff" + b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + ), ), ] return nodes @@ -80,14 +87,14 @@ class TestInMemoryCommit(BackendTestMixin): def test_add_on_branch_hg(self, nodes): for node in nodes: self.imc.add(node) - self.commit(branch='stable') + self.commit(branch="stable") self.assert_successful_commit(nodes) @pytest.mark.backends("git") def test_add_on_branch_git(self, nodes): for node in nodes: self.imc.add(node) - self.commit(branch='stable') + self.commit(branch="stable") self.assert_successful_commit(nodes) def test_add_in_bulk(self, nodes): @@ -98,10 +105,8 @@ class TestInMemoryCommit(BackendTestMixin): def test_add_non_ascii_files(self): nodes = [ - FileNode(safe_bytes('żółwik/zwierzątko_utf8_str'), - content=safe_bytes('ćććć')), - FileNode(safe_bytes('żółwik/zwierzątko_unicode'), - content=safe_bytes('ćććć')), + FileNode(safe_bytes("żółwik/zwierzątko_utf8_str"), content=safe_bytes("ćććć")), + FileNode(safe_bytes("żółwik/zwierzątko_unicode"), content=safe_bytes("ćććć")), ] for node in nodes: @@ -112,60 +117,56 @@ class TestInMemoryCommit(BackendTestMixin): def commit(self, branch=None): self.old_commit_count = len(self.repo.commit_ids) - self.commit_message = 'Test commit with unicode: żółwik' - self.commit_author = f'{self.__class__.__name__} ' - self.commit = self.imc.commit( - message=self.commit_message, author=self.commit_author, - branch=branch) + self.commit_message = "Test commit with unicode: żółwik" + self.commit_author = f"{self.__class__.__name__} " + self.commit = self.imc.commit(message=self.commit_message, author=self.commit_author, branch=branch) def test_add_actually_adds_all_nodes_at_second_commit_too(self): to_add = [ - FileNode(b'foo/bar/image.png', content=b'\0'), - FileNode(b'foo/README.txt', content=b'readme!'), + FileNode(b"foo/bar/image.png", content=b"\0"), + FileNode(b"foo/README.txt", content=b"readme!"), ] self.imc.add(*to_add) - commit = self.imc.commit('Initial', 'joe doe ') - assert isinstance(commit.get_node('foo'), DirNode) - assert isinstance(commit.get_node('foo/bar'), DirNode) + commit = self.imc.commit("Initial", "joe doe ") + assert isinstance(commit.get_node("foo"), DirNode) + assert isinstance(commit.get_node("foo/bar"), DirNode) self.assert_nodes_in_commit(commit, to_add) # commit some more files again to_add = [ - FileNode(b'foo/bar/foobaz/bar', content=b'foo'), - FileNode(b'foo/bar/another/bar', content=b'foo'), - FileNode(b'foo/baz.txt', content=b'foo'), - FileNode(b'foobar/foobaz/file', content=b'foo'), - FileNode(b'foobar/barbaz', content=b'foo'), + FileNode(b"foo/bar/foobaz/bar", content=b"foo"), + FileNode(b"foo/bar/another/bar", content=b"foo"), + FileNode(b"foo/baz.txt", content=b"foo"), + FileNode(b"foobar/foobaz/file", content=b"foo"), + FileNode(b"foobar/barbaz", content=b"foo"), ] self.imc.add(*to_add) - commit = self.imc.commit('Another', 'joe doe ') + commit = self.imc.commit("Another", "joe doe ") self.assert_nodes_in_commit(commit, to_add) def test_add_raise_already_added(self): - node = FileNode(b'foobar', content=b'baz') + node = FileNode(b"foobar", content=b"baz") self.imc.add(node) with pytest.raises(NodeAlreadyAddedError): self.imc.add(node) def test_check_integrity_raise_already_exist(self): - node = FileNode(b'foobar', content=b'baz') + node = FileNode(b"foobar", content=b"baz") self.imc.add(node) - self.imc.commit(message='Added foobar', - author='Some Name ') + self.imc.commit(message="Added foobar", author="Some Name ") self.imc.add(node) with pytest.raises(NodeAlreadyExistsError): - self.imc.commit(message='new message', - author='Some Name ') + self.imc.commit(message="new message", author="Some Name ") def test_change(self): - self.imc.add(FileNode(b'foo/bar/baz', content=b'foo')) - self.imc.add(FileNode(b'foo/fbar', content=b'foobar')) - tip = self.imc.commit('Initial', 'joe doe ') + self.imc.add(FileNode(b"foo/bar/baz", content=b"foo")) + self.imc.add(FileNode(b"foo/fbar", content=b"foobar")) + tip = self.imc.commit("Initial", "joe doe ") # Change node's content - node = FileNode(b'foo/bar/baz', content=b'My **changed** content') + node = FileNode(b"foo/bar/baz", content=b"My **changed** content") self.imc.change(node) - self.imc.commit('Changed %s' % node.path, 'joe doe ') + self.imc.commit("Changed %s" % node.path, "joe doe ") newtip = self.repo.get_commit() assert tip != newtip @@ -174,28 +175,22 @@ class TestInMemoryCommit(BackendTestMixin): def test_change_non_ascii(self): to_add = [ - FileNode(safe_bytes('żółwik/zwierzątko'), - content=safe_bytes('ćććć')), - FileNode(safe_bytes('żółwik/zwierzątko_uni'), - content=safe_bytes('ćććć')), + FileNode(safe_bytes("żółwik/zwierzątko"), content=safe_bytes("ćććć")), + FileNode(safe_bytes("żółwik/zwierzątko_uni"), content=safe_bytes("ćććć")), ] for node in to_add: self.imc.add(node) - tip = self.imc.commit('Initial', 'joe doe ') + tip = self.imc.commit("Initial", "joe doe ") # Change node's content - node = FileNode(safe_bytes('żółwik/zwierzątko'), - content=b'My **changed** content') + node = FileNode(safe_bytes("żółwik/zwierzątko"), content=b"My **changed** content") self.imc.change(node) - self.imc.commit('Changed %s' % safe_str(node.path), - author='joe doe ') + self.imc.commit("Changed %s" % safe_str(node.path), author="joe doe ") - node_uni = FileNode(safe_bytes('żółwik/zwierzątko_uni'), - content=b'My **changed** content') + node_uni = FileNode(safe_bytes("żółwik/zwierzątko_uni"), content=b"My **changed** content") self.imc.change(node_uni) - self.imc.commit('Changed %s' % safe_str(node_uni.path), - author='joe doe ') + self.imc.commit("Changed %s" % safe_str(node_uni.path), author="joe doe ") newtip = self.repo.get_commit() assert tip != newtip @@ -204,24 +199,24 @@ class TestInMemoryCommit(BackendTestMixin): self.assert_nodes_in_commit(newtip, (node, node_uni)) def test_change_raise_empty_repository(self): - node = FileNode(b'foobar') + node = FileNode(b"foobar") with pytest.raises(EmptyRepositoryError): self.imc.change(node) def test_check_integrity_change_raise_node_does_not_exist(self): - node = FileNode(b'foobar', content=b'baz') + node = FileNode(b"foobar", content=b"baz") self.imc.add(node) - self.imc.commit(message='Added foobar', author='Some Name ') - node = FileNode(b'not-foobar', content=b'') + self.imc.commit(message="Added foobar", author="Some Name ") + node = FileNode(b"not-foobar", content=b"") self.imc.change(node) with pytest.raises(NodeDoesNotExistError): - self.imc.commit(message='Changed not existing node', author='Some Name ') + self.imc.commit(message="Changed not existing node", author="Some Name ") def test_change_raise_node_already_changed(self): - node = FileNode(b'foobar', content=b'baz') + node = FileNode(b"foobar", content=b"baz") self.imc.add(node) - self.imc.commit(message='Added foobar', author='Some Nam ') - node = FileNode(b'foobar', content=b'more baz') + self.imc.commit(message="Added foobar", author="Some Nam ") + node = FileNode(b"foobar", content=b"more baz") self.imc.change(node) with pytest.raises(NodeAlreadyChangedError): self.imc.change(node) @@ -233,14 +228,14 @@ class TestInMemoryCommit(BackendTestMixin): self.imc.change(node) with pytest.raises(NodeNotChangedError): self.imc.commit( - message='Trying to mark node as changed without touching it', - author='Some Name ') + message="Trying to mark node as changed without touching it", author="Some Name " + ) def test_change_raise_node_already_removed(self): - node = FileNode(b'foobar', content=b'baz') + node = FileNode(b"foobar", content=b"baz") self.imc.add(node) - self.imc.commit(message='Added foobar', author='Some Name ') - self.imc.remove(FileNode(b'foobar')) + self.imc.commit(message="Added foobar", author="Some Name ") + self.imc.remove(FileNode(b"foobar")) with pytest.raises(NodeAlreadyRemovedError): self.imc.change(node) @@ -251,7 +246,7 @@ class TestInMemoryCommit(BackendTestMixin): node = nodes[0] assert node.content == tip.get_node(node.path).content self.imc.remove(node) - self.imc.commit(message=f'Removed {node.path}', author='Some Name ') + self.imc.commit(message=f"Removed {node.path}", author="Some Name ") newtip = self.repo.get_commit() assert tip != newtip @@ -260,31 +255,27 @@ class TestInMemoryCommit(BackendTestMixin): newtip.get_node(node.path) def test_remove_last_file_from_directory(self): - node = FileNode(b'omg/qwe/foo/bar', content=b'foobar') + node = FileNode(b"omg/qwe/foo/bar", content=b"foobar") self.imc.add(node) - self.imc.commit('added', author='joe doe ') + self.imc.commit("added", author="joe doe ") self.imc.remove(node) - tip = self.imc.commit('removed', 'joe doe ') + tip = self.imc.commit("removed", "joe doe ") with pytest.raises(NodeDoesNotExistError): - tip.get_node('omg/qwe/foo/bar') + tip.get_node("omg/qwe/foo/bar") def test_remove_raise_node_does_not_exist(self, nodes): self.imc.remove(nodes[0]) with pytest.raises(NodeDoesNotExistError): - self.imc.commit( - message='Trying to remove node at empty repository', - author='Some Name ') + self.imc.commit(message="Trying to remove node at empty repository", author="Some Name ") def test_check_integrity_remove_raise_node_does_not_exist(self, nodes): self.test_add(nodes) # Performs first commit - node = FileNode(b'no-such-file') + node = FileNode(b"no-such-file") self.imc.remove(node) with pytest.raises(NodeDoesNotExistError): - self.imc.commit( - message='Trying to remove not existing node', - author='Some Name ') + self.imc.commit(message="Trying to remove not existing node", author="Some Name ") def test_remove_raise_node_already_removed(self, nodes): self.test_add(nodes) # Performs first commit @@ -297,13 +288,13 @@ class TestInMemoryCommit(BackendTestMixin): def test_remove_raise_node_already_changed(self, nodes): self.test_add(nodes) # Performs first commit - node = FileNode(nodes[0].bytes_path, content=b'Bending time') + node = FileNode(nodes[0].bytes_path, content=b"Bending time") self.imc.change(node) with pytest.raises(NodeAlreadyChangedError): self.imc.remove(node) def test_reset(self): - self.imc.add(FileNode(b'foo', content=b'bar')) + self.imc.add(FileNode(b"foo", content=b"bar")) # self.imc.change(FileNode(b'baz', content='new')) # self.imc.remove(FileNode(b'qwe')) self.imc.reset() @@ -313,11 +304,11 @@ class TestInMemoryCommit(BackendTestMixin): N = 3 # number of commits to perform last = None for x in range(N): - fname = safe_bytes('file%s' % str(x).rjust(5, '0')) - content = safe_bytes('foobar\n' * x) + fname = safe_bytes("file%s" % str(x).rjust(5, "0")) + content = safe_bytes("foobar\n" * x) node = FileNode(fname, content=content) self.imc.add(node) - commit = self.imc.commit("Commit no. %s" % (x + 1), author='Vcs User ') + commit = self.imc.commit("Commit no. %s" % (x + 1), author="Vcs User ") assert last != commit last = commit @@ -329,12 +320,10 @@ class TestInMemoryCommit(BackendTestMixin): assert len(repo.commit_ids) == N def test_date_attr(self, local_dt_to_utc): - node = FileNode(b'foobar.txt', content=b'Foobared!') + node = FileNode(b"foobar.txt", content=b"Foobared!") self.imc.add(node) date = datetime.datetime(1985, 1, 30, 1, 45) - commit = self.imc.commit( - "Committed at time when I was born ;-)", - author='Test User ', date=date) + commit = self.imc.commit("Committed at time when I was born ;-)", author="Test User ", date=date) assert commit.date == local_dt_to_utc(date) diff --git a/rhodecode/tests/vcs/test_nodes.py b/rhodecode/tests/vcs/test_nodes.py index 5a3d5207..e745816e 100644 --- a/rhodecode/tests/vcs/test_nodes.py +++ b/rhodecode/tests/vcs/test_nodes.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -38,34 +37,34 @@ def binary_filenode(): b"\x10\x08\x06\x00\x00\x00\x1f??a\x00\x00\x00\x04gAMA\x00\x00\xaf?7" b"\x05\x8a?\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq?e<\x00" b"\x00\x025IDAT8?\xa5\x93?K\x94Q\x14\x87\x9f\xf7?Q\x1bs4?\x03\x9a" - b"\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?\"\x14j?\xa2M\x7fB\x14F\x9aQ?&" - b"\x842?\x0b\x89\"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?=" - b"\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04\'`EpNp\xa2X\'U?pVq\"Sw." + b'\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?"\x14j?\xa2M\x7fB\x14F\x9aQ?&' + b'\x842?\x0b\x89"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?=' + b"\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04'`EpNp\xa2X'U?pVq\"Sw." b"\x1e?\x08\x01D?jw????\xbc??7{|\x9b?\x89$\x01??W@\x15\x9c\x05q`Lt/" b"\x97?\x94\xa1d?\x18~?\x18?\x18W[%\xb0?\x83??\x14\x88\x8dB?\xa6H" - b"\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$\"q[" + b'\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$"q[' b"\x19?d\x00(o\n\xa0??\x7f\xb9\xa4?\x1bF\x1f\x8e\xac\xa8?j??eUU}?.?" - b"\x9f\x8cE??x\x94??\r\xbdtoJU5\"0N\x10U?\x00??V\t\x02\x9f\x81?U?" - b"\x00\x9eM\xae2?r\x9b7\x83\x82\x8aP3????.?&\"?\xb7ZP \x0cJ?\x80\x15T\x95\x9a\x00??S\x8c\r?\xa1" b"\x03\x07?\x96\x9b\xa7\xab=E??\xa4\xb3?\x19q??B\x91=\x8d??k?J" - b"\x0bV\"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X" + b'\x0bV"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X' b"\xb7=\xb9\x10?Z\x1by???cI??\x1ag?\x92\xbc?T?t[\x92\x81?<_\x17~" b"\x92\x88?H%?\x10Q\x02\x9f\n\x81qQ\x0bm?\x1bX?\xb1AK\xa6\x9e\xb9?u" - b"\xb2?1\xbe|/\x92M@\xa2!F?\xa9>\"\r\x92\x8e?>\x9a9Qv\x127?a" + b'\xb2?1\xbe|/\x92M@\xa2!F?\xa9>"\r\x92\x8e?>\x9a9Qv\x127?a' b"\xac?Y?8?:??]X???9\x80\xb7?u?\x0b#BZ\x8d=\x1d?p\x00\x00\x00\x00" - b"IEND\xaeB`\x82") + b"IEND\xaeB`\x82" + ) return FileNode(filename, content=data) + return node_maker class TestNodeBasics: - - @pytest.mark.parametrize("path", ['/foo', '/foo/bar']) - @pytest.mark.parametrize( - "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"]) + @pytest.mark.parametrize("path", ["/foo", "/foo/bar"]) + @pytest.mark.parametrize("kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"]) def test_init_wrong_paths(self, path, kind): """ Cannot initialize Node objects with path with slash at the beginning. @@ -74,44 +73,46 @@ class TestNodeBasics: with pytest.raises(NodeError): Node(path, kind) - @pytest.mark.parametrize("path", ['path', 'some/path']) - @pytest.mark.parametrize( - "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"]) + @pytest.mark.parametrize("path", ["path", "some/path"]) + @pytest.mark.parametrize("kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"]) def test_name(self, path, kind): path = safe_bytes(path) node = Node(path, kind) - assert node.name == 'path' + assert node.name == "path" def test_name_root(self): - node = Node(b'', NodeKind.DIR) - assert node.name == '' + node = Node(b"", NodeKind.DIR) + assert node.name == "" def test_root_node_cannot_be_file(self): with pytest.raises(NodeError): - Node(b'', NodeKind.FILE) + Node(b"", NodeKind.FILE) def test_kind_setter(self): - node = Node(b'', NodeKind.DIR) + node = Node(b"", NodeKind.DIR) with pytest.raises(NodeError): node.kind = NodeKind.FILE def test_compare_equal(self): - node1 = FileNode(b'test', content=b'') - node2 = FileNode(b'test', content=b'') + node1 = FileNode(b"test", content=b"") + node2 = FileNode(b"test", content=b"") assert node1 == node2 assert not node1 != node2 def test_compare_unequal(self): - node1 = FileNode(b'test', content=b'a') - node2 = FileNode(b'test', content=b'b') + node1 = FileNode(b"test", content=b"a") + node2 = FileNode(b"test", content=b"b") assert node1 != node2 assert not node1 == node2 - @pytest.mark.parametrize("node_path, expected_parent_path", [ - ('', b''), - ('some/path/', b'some/'), - ('some/longer/path/', b'some/longer/'), - ]) + @pytest.mark.parametrize( + "node_path, expected_parent_path", + [ + ("", b""), + ("some/path/", b"some/"), + ("some/longer/path/", b"some/longer/"), + ], + ) def test_parent_path_new(self, node_path, expected_parent_path): """ Tests if node's parent path are properly computed. @@ -119,11 +120,10 @@ class TestNodeBasics: node_path = safe_bytes(node_path) node = Node(node_path, NodeKind.DIR) parent_path = node.get_parent_path() - assert (parent_path.endswith(b'/') or - node.is_root() and parent_path == b'') + assert parent_path.endswith(b"/") or node.is_root() and parent_path == b"" assert parent_path == expected_parent_path - ''' + """ def _test_trailing_slash(self, path): if not path.endswith('/'): pytest.fail("Trailing slash tests needs paths to end with slash") @@ -134,22 +134,22 @@ class TestNodeBasics: def test_trailing_slash(self): for path in ('/', 'foo/', 'foo/bar/', 'foo/bar/biz/'): self._test_trailing_slash(path) - ''' + """ def test_is_file(self): - node = Node(b'any', NodeKind.FILE) + node = Node(b"any", NodeKind.FILE) assert node.is_file() - node = FileNode(b'any') + node = FileNode(b"any") assert node.is_file() with pytest.raises(AttributeError): node.nodes # noqa def test_is_dir(self): - node = Node(b'any_dir', NodeKind.DIR) + node = Node(b"any_dir", NodeKind.DIR) assert node.is_dir() - node = DirNode(b'any_dir') + node = DirNode(b"any_dir") assert node.is_dir() with pytest.raises(NodeError): @@ -157,14 +157,14 @@ class TestNodeBasics: def test_dir_node_iter(self): nodes = [ - DirNode(b'docs'), - DirNode(b'tests'), - FileNode(b'bar'), - FileNode(b'foo'), - FileNode(b'readme.txt'), - FileNode(b'setup.py'), + DirNode(b"docs"), + DirNode(b"tests"), + FileNode(b"bar"), + FileNode(b"foo"), + FileNode(b"readme.txt"), + FileNode(b"setup.py"), ] - dirnode = DirNode(b'', nodes=nodes) + dirnode = DirNode(b"", nodes=nodes) for node in dirnode: assert node == dirnode.get_node(node.path) @@ -172,15 +172,15 @@ class TestNodeBasics: """ Without link to commit nodes should raise NodeError. """ - node = FileNode(b'anything') + node = FileNode(b"anything") with pytest.raises(NodeError): node.state # noqa - node = DirNode(b'anything') + node = DirNode(b"anything") with pytest.raises(NodeError): node.state # noqa def test_file_node_stat(self): - node = FileNode(b'foobar', b'empty... almost') + node = FileNode(b"foobar", b"empty... almost") mode = node.mode # default should be 0100644 assert mode & stat.S_IRUSR assert mode & stat.S_IWUSR @@ -193,36 +193,36 @@ class TestNodeBasics: assert not mode & stat.S_IXOTH def test_file_node_is_executable(self): - node = FileNode(b'foobar', b'empty... almost', mode=0o100755) + node = FileNode(b"foobar", b"empty... almost", mode=0o100755) assert node.is_executable - node = FileNode(b'foobar', b'empty... almost', mode=0o100500) + node = FileNode(b"foobar", b"empty... almost", mode=0o100500) assert node.is_executable - node = FileNode(b'foobar', b'empty... almost', mode=0o100644) + node = FileNode(b"foobar", b"empty... almost", mode=0o100644) assert not node.is_executable def test_file_node_is_not_symlink(self): - node = FileNode(b'foobar', b'empty...') + node = FileNode(b"foobar", b"empty...") assert not node.is_link() def test_mimetype(self): - py_node = FileNode(b'test.py') - tar_node = FileNode(b'test.tar.gz') + py_node = FileNode(b"test.py") + tar_node = FileNode(b"test.tar.gz") - ext = 'CustomExtension' + ext = "CustomExtension" - my_node2 = FileNode(b'myfile2') + my_node2 = FileNode(b"myfile2") my_node2._mimetype = [ext] - my_node3 = FileNode(b'myfile3') + my_node3 = FileNode(b"myfile3") my_node3._mimetype = [ext, ext] - assert py_node.mimetype == 'text/x-python' - assert py_node.get_mimetype() == ('text/x-python', None) + assert py_node.mimetype == "text/x-python" + assert py_node.get_mimetype() == ("text/x-python", None) - assert tar_node.mimetype == 'application/x-tar' - assert tar_node.get_mimetype() == ('application/x-tar', 'gzip') + assert tar_node.mimetype == "application/x-tar" + assert tar_node.get_mimetype() == ("application/x-tar", "gzip") with pytest.raises(NodeError): my_node2.get_mimetype() @@ -232,47 +232,45 @@ class TestNodeBasics: def test_lines_counts(self): lines = [ - b'line1\n', - b'line2\n', - b'line3\n', - b'\n', - b'\n', - b'line4\n', + b"line1\n", + b"line2\n", + b"line3\n", + b"\n", + b"\n", + b"line4\n", ] - py_node = FileNode(b'test.py', b''.join(lines)) + py_node = FileNode(b"test.py", b"".join(lines)) assert (len(lines), len(lines)) == py_node.lines() assert (len(lines), len(lines) - 2) == py_node.lines(count_empty=True) def test_lines_no_newline(self): - py_node = FileNode(b'test.py', b'oneline') + py_node = FileNode(b"test.py", b"oneline") assert (1, 1) == py_node.lines() assert (1, 1) == py_node.lines(count_empty=True) class TestNodeContent(object): - def test_if_binary(self, binary_filenode): - filenode = binary_filenode(b'calendar.jpg') + filenode = binary_filenode(b"calendar.jpg") assert filenode.is_binary def test_binary_line_counts(self, binary_filenode): - tar_node = binary_filenode(b'archive.tar.gz') + tar_node = binary_filenode(b"archive.tar.gz") assert (0, 0) == tar_node.lines(count_empty=True) def test_binary_mimetype(self, binary_filenode): - tar_node = binary_filenode(b'archive.tar.gz') - assert tar_node.mimetype == 'application/x-tar' + tar_node = binary_filenode(b"archive.tar.gz") + assert tar_node.mimetype == "application/x-tar" @pytest.mark.usefixtures("vcs_repository_support") class TestNodesCommits(BackendTestMixin): - def test_node_last_commit(self, generate_repo_with_commits): repo = generate_repo_with_commits(20) last_commit = repo.get_commit() for x in range(3): - node = last_commit.get_node(f'file_{x}.txt') + node = last_commit.get_node(f"file_{x}.txt") assert node.last_commit == repo[x] diff --git a/rhodecode/tests/vcs/test_repository.py b/rhodecode/tests/vcs/test_repository.py index f1dc3a81..2ba82e49 100644 --- a/rhodecode/tests/vcs/test_repository.py +++ b/rhodecode/tests/vcs/test_repository.py @@ -114,9 +114,7 @@ class TestRepositoryBase(BackendTestMixin): assert len(self.repo.get_hook_location()) != 0 def test_last_change(self, local_dt_to_utc): - assert self.repo.last_change >= local_dt_to_utc( - datetime.datetime(2010, 1, 1, 21, 0) - ) + assert self.repo.last_change >= local_dt_to_utc(datetime.datetime(2010, 1, 1, 21, 0)) def test_last_change_in_empty_repository(self, vcsbackend, local_dt_to_utc): delta = datetime.timedelta(seconds=1) @@ -195,9 +193,7 @@ class TestRepositoryCompare: @pytest.mark.parametrize("merge", [True, False]) def test_compare_commits_of_same_repository(self, vcsbackend, merge): target_repo = vcsbackend.create_repo(number_of_commits=5) - target_repo.compare( - target_repo[1].raw_id, target_repo[3].raw_id, target_repo, merge=merge - ) + target_repo.compare(target_repo[1].raw_id, target_repo[3].raw_id, target_repo, merge=merge) @pytest.mark.xfail_backends("svn") @pytest.mark.parametrize("merge", [True, False]) @@ -209,9 +205,7 @@ class TestRepositoryCompare: vcsbackend.add_file(source_repo, b"newfile", b"somecontent") source_commit = source_repo.get_commit() - target_repo.compare( - target_repo[1].raw_id, source_repo[3].raw_id, source_repo, merge=merge - ) + target_repo.compare(target_repo[1].raw_id, source_repo[3].raw_id, source_repo, merge=merge) @pytest.mark.xfail_backends("svn") @pytest.mark.parametrize("merge", [True, False]) @@ -351,9 +345,7 @@ class TestRepositoryMerge(object): "merge message 1", dry_run=False, ) - expected_merge_response = MergeResponse( - True, True, merge_response.merge_ref, MergeFailureReason.NONE - ) + expected_merge_response = MergeResponse(True, True, merge_response.merge_ref, MergeFailureReason.NONE) assert merge_response == expected_merge_response target_repo = backends.get_backend(vcsbackend.alias)(self.target_repo.path) @@ -385,9 +377,7 @@ class TestRepositoryMerge(object): "merge message 2", dry_run=False, ) - expected_merge_response = MergeResponse( - True, True, merge_response.merge_ref, MergeFailureReason.NONE - ) + expected_merge_response = MergeResponse(True, True, merge_response.merge_ref, MergeFailureReason.NONE) assert merge_response == expected_merge_response target_repo = backends.get_backend(vcsbackend.alias)(self.target_repo.path) @@ -422,9 +412,9 @@ class TestRepositoryMerge(object): # Multiple merges may differ in their commit id. Therefore, we set the # commit id to `None` before comparing the merge responses. - merge_response.merge_ref.commit_id = 'abcdeabcde' + merge_response.merge_ref.commit_id = "abcdeabcde" - merge_response_update.merge_ref.commit_id = 'abcdeabcde' + merge_response_update.merge_ref.commit_id = "abcdeabcde" assert merge_response == merge_response_update assert merge_response.possible is True @@ -436,9 +426,7 @@ class TestRepositoryMerge(object): def test_merge_conflict(self, vcsbackend, dry_run): self.prepare_for_conflict(vcsbackend) - expected_merge_response = MergeResponse( - False, False, None, MergeFailureReason.MERGE_FAILED - ) + expected_merge_response = MergeResponse(False, False, None, MergeFailureReason.MERGE_FAILED) merge_response = self.target_repo.merge( self.repo_id, @@ -491,9 +479,7 @@ class TestRepositoryMerge(object): def test_merge_missing_source_reference(self, vcsbackend): self.prepare_for_success(vcsbackend) - source_ref = Reference( - self.source_ref.type, "not_existing", self.source_ref.commit_id - ) + source_ref = Reference(self.source_ref.type, "not_existing", self.source_ref.commit_id) expected_merge_response = MergeResponse( False, False, @@ -523,9 +509,7 @@ class TestRepositoryMerge(object): metadata={"exception": "ErrorForTest"}, ) - with mock.patch.object( - self.target_repo, "_merge_repo", side_effect=RepositoryError() - ): + with mock.patch.object(self.target_repo, "_merge_repo", side_effect=RepositoryError()): merge_response = self.target_repo.merge( self.repo_id, self.workspace_id, @@ -559,9 +543,7 @@ class TestRepositoryMerge(object): workspace_id = "test-errors-in-merge" repo_id = repo_id_generator(workspace_id) with pytest.raises(ValueError): - repo.merge( - repo_id, workspace_id, ref, self, ref, "user name", "user@email.com" - ) + repo.merge(repo_id, workspace_id, ref, self, ref, "user name", "user@email.com") @pytest.mark.usefixtures("vcs_repository_support") diff --git a/rhodecode/tests/vcs/test_svn.py b/rhodecode/tests/vcs/test_svn.py index f4510b35..620c244f 100644 --- a/rhodecode/tests/vcs/test_svn.py +++ b/rhodecode/tests/vcs/test_svn.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -47,13 +46,13 @@ def head(repo): def test_init_fails_if_path_does_not_exist(): - path = os.path.join(TEST_DIR, 'i-do-not-exist') + path = os.path.join(TEST_DIR, "i-do-not-exist") with pytest.raises(VCSError): SubversionRepository(path) def test_init_fails_if_path_is_not_a_valid_repository(tmpdir): - path = str(tmpdir.mkdir('unicode ä')) + path = str(tmpdir.mkdir("unicode ä")) with pytest.raises(VCSError): SubversionRepository(path) @@ -61,17 +60,14 @@ def test_init_fails_if_path_is_not_a_valid_repository(tmpdir): def test_repo_clone(vcsbackend, reposerver): source = vcsbackend.create_repo(number_of_commits=3) reposerver.serve(source) - repo = SubversionRepository( - vcsbackend.new_repo_path(), - create=True, - src_url=reposerver.url) + repo = SubversionRepository(vcsbackend.new_repo_path(), create=True, src_url=reposerver.url) assert source.commit_ids == repo.commit_ids assert source[0].message == repo[0].message def test_latest_commit(head): - assert head.raw_id == '393' + assert head.raw_id == "393" def test_commit_description(head): @@ -79,18 +75,22 @@ def test_commit_description(head): def test_commit_author(head): - assert head.author == 'marcin' + assert head.author == "marcin" -@pytest.mark.parametrize("filename, content, mime_type", [ - (b'test.txt', b'Text content\n', None), - (b'test.bin', b'\0 binary \0', 'application/octet-stream'), -], ids=['text', 'binary']) +@pytest.mark.parametrize( + "filename, content, mime_type", + [ + (b"test.txt", b"Text content\n", None), + (b"test.bin", b"\0 binary \0", "application/octet-stream"), + ], + ids=["text", "binary"], +) def test_sets_mime_type_correctly(vcsbackend, filename, content, mime_type): repo = vcsbackend.create_repo() vcsbackend.ensure_file(filename, content) file_properties = repo._remote.node_properties(filename, 1) - assert file_properties.get('svn:mime-type') == mime_type + assert file_properties.get("svn:mime-type") == mime_type def test_slice_access(repo): @@ -100,7 +100,7 @@ def test_slice_access(repo): end = start + page_size - 1 commits = list(repo[start:end]) - assert [commit.raw_id for commit in commits] == ['1', '2', '3', '4'] + assert [commit.raw_id for commit in commits] == ["1", "2", "3", "4"] def test_walk_changelog_page(repo): @@ -110,14 +110,14 @@ def test_walk_changelog_page(repo): end = start + page_size - 1 commits = list(repo[start:end]) - changelog = [ - 'r%s, %s, %s' % (c.raw_id, c.author, c.message) for c in commits] + changelog = ["r%s, %s, %s" % (c.raw_id, c.author, c.message) for c in commits] expexted_messages = [ - 'r1, marcin, initial import', - 'r2, marcin, hg ignore', - 'r3, marcin, Pip standards refactor', - 'r4, marcin, Base repository few new functions added'] + "r1, marcin, initial import", + "r2, marcin, hg ignore", + "r3, marcin, Pip standards refactor", + "r4, marcin, Base repository few new functions added", + ] assert changelog == expexted_messages @@ -128,68 +128,68 @@ def test_read_full_file_tree(head): def test_topnode_files_attribute(head): - topnode = head.get_node('') + topnode = head.get_node("") topnode.files - - -@pytest.mark.parametrize("filename, content, branch, mime_type", [ - ('branches/plain/test.txt', b'Text content\n', 'plain', None), - ('branches/uniçö∂e/test.bin', b'\0 binary \0', 'uniçö∂e', 'application/octet-stream'), -], ids=['text', 'binary']) +@pytest.mark.parametrize( + "filename, content, branch, mime_type", + [ + ("branches/plain/test.txt", b"Text content\n", "plain", None), + ("branches/uniçö∂e/test.bin", b"\0 binary \0", "uniçö∂e", "application/octet-stream"), + ], + ids=["text", "binary"], +) def test_unicode_refs(vcsbackend, filename, content, branch, mime_type): filename = safe_bytes(filename) repo = vcsbackend.create_repo() vcsbackend.ensure_file(filename, content) - with mock.patch(("rhodecode.lib.vcs.backends.svn.repository" - ".SubversionRepository._patterns_from_section"), - return_value=['branches/*']): - assert f'branches/{branch}' in repo.branches + with mock.patch( + ("rhodecode.lib.vcs.backends.svn.repository" ".SubversionRepository._patterns_from_section"), + return_value=["branches/*"], + ): + assert f"branches/{branch}" in repo.branches def test_compatible_version(monkeypatch, vcsbackend): - monkeypatch.setattr(settings, 'SVN_COMPATIBLE_VERSION', 'pre-1.8-compatible') + monkeypatch.setattr(settings, "SVN_COMPATIBLE_VERSION", "pre-1.8-compatible") path = vcsbackend.new_repo_path() SubversionRepository(path, create=True) - with open(f'{path}/db/format') as f: + with open(f"{path}/db/format") as f: first_line = f.readline().strip() - assert first_line == '4' + assert first_line == "4" def test_invalid_compatible_version(monkeypatch, vcsbackend): - monkeypatch.setattr(settings, 'SVN_COMPATIBLE_VERSION', 'i-am-an-invalid-setting') + monkeypatch.setattr(settings, "SVN_COMPATIBLE_VERSION", "i-am-an-invalid-setting") path = vcsbackend.new_repo_path() with pytest.raises(Exception): SubversionRepository(path, create=True) class TestSVNCommit(object): - @pytest.fixture(autouse=True) def prepare(self, repo): self.repo = repo def test_file_history_from_commits(self): - node = self.repo[10].get_node('setup.py') + node = self.repo[10].get_node("setup.py") commit_ids = [commit.raw_id for commit in node.history] - assert ['8'] == commit_ids + assert ["8"] == commit_ids - node = self.repo[20].get_node('setup.py') + node = self.repo[20].get_node("setup.py") node_ids = [commit.raw_id for commit in node.history] - assert ['18', - '8'] == node_ids + assert ["18", "8"] == node_ids # special case we check history from commit that has this particular # file changed this means we check if it's included as well - node = self.repo.get_commit('18').get_node('setup.py') + node = self.repo.get_commit("18").get_node("setup.py") node_ids = [commit.raw_id for commit in node.history] - assert ['18', - '8'] == node_ids + assert ["18", "8"] == node_ids def test_repo_files_content_type(self): test_commit = self.repo.get_commit(commit_idx=100) - for node in test_commit.get_node('/'): + for node in test_commit.get_node("/"): if node.is_file(): assert type(node.content) == bytes assert type(node.str_content) == str diff --git a/rhodecode/tests/vcs/test_tags.py b/rhodecode/tests/vcs/test_tags.py index 72972991..b5774ccb 100644 --- a/rhodecode/tests/vcs/test_tags.py +++ b/rhodecode/tests/vcs/test_tags.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -20,8 +19,7 @@ import pytest from rhodecode.tests.vcs.conftest import BackendTestMixin -from rhodecode.lib.vcs.exceptions import ( - TagAlreadyExistError, TagDoesNotExistError) +from rhodecode.lib.vcs.exceptions import TagAlreadyExistError, TagDoesNotExistError pytestmark = pytest.mark.backends("git", "hg") @@ -29,11 +27,10 @@ pytestmark = pytest.mark.backends("git", "hg") @pytest.mark.usefixtures("vcs_repository_support") class TestTags(BackendTestMixin): - def test_new_tag(self): tip = self.repo.get_commit() tagsize = len(self.repo.tags) - tag = self.repo.tag('last-commit', 'joe', tip.raw_id) + tag = self.repo.tag("last-commit", "joe", tip.raw_id) assert len(self.repo.tags) == tagsize + 1 for top, __, __ in tip.walk(): @@ -41,29 +38,29 @@ class TestTags(BackendTestMixin): def test_tag_already_exist(self): tip = self.repo.get_commit() - self.repo.tag('last-commit', 'joe', tip.raw_id) + self.repo.tag("last-commit", "joe", tip.raw_id) with pytest.raises(TagAlreadyExistError): - self.repo.tag('last-commit', 'joe', tip.raw_id) + self.repo.tag("last-commit", "joe", tip.raw_id) commit = self.repo.get_commit(commit_idx=0) with pytest.raises(TagAlreadyExistError): - self.repo.tag('last-commit', 'jane', commit.raw_id) + self.repo.tag("last-commit", "jane", commit.raw_id) def test_remove_tag(self): tip = self.repo.get_commit() - self.repo.tag('last-commit', 'joe', tip.raw_id) + self.repo.tag("last-commit", "joe", tip.raw_id) tagsize = len(self.repo.tags) - self.repo.remove_tag('last-commit', user='evil joe') + self.repo.remove_tag("last-commit", user="evil joe") assert len(self.repo.tags) == tagsize - 1 def test_remove_tag_which_does_not_exist(self): with pytest.raises(TagDoesNotExistError): - self.repo.remove_tag('last-commit', user='evil joe') + self.repo.remove_tag("last-commit", user="evil joe") def test_name_with_slash(self): - self.repo.tag('19/10/11', 'joe') - assert '19/10/11' in self.repo.tags - self.repo.tag('rel.11', 'joe') - assert 'rel.11' in self.repo.tags + self.repo.tag("19/10/11", "joe") + assert "19/10/11" in self.repo.tags + self.repo.tag("rel.11", "joe") + assert "rel.11" in self.repo.tags diff --git a/rhodecode/tests/vcs/test_utils.py b/rhodecode/tests/vcs/test_utils.py index ccf87c29..0b79766b 100644 --- a/rhodecode/tests/vcs/test_utils.py +++ b/rhodecode/tests/vcs/test_utils.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -34,23 +33,20 @@ from rhodecode.lib.vcs.utils.paths import get_dirs_for_path @pytest.mark.usefixtures("baseapp") class TestPaths(object): - def _test_get_dirs_for_path(self, path, expected): """ Tests if get_dirs_for_path returns same as expected. """ expected = sorted(expected) result = sorted(get_dirs_for_path(path)) - assert result == expected, ( - "%s != %s which was expected result for path %s" - % (result, expected, path)) + assert result == expected, "%s != %s which was expected result for path %s" % (result, expected, path) def test_get_dirs_for_path(self): - path = 'foo/bar/baz/file' + path = "foo/bar/baz/file" paths_and_results = ( - ('foo/bar/baz/file', ['foo', 'foo/bar', 'foo/bar/baz']), - ('foo/bar/', ['foo', 'foo/bar']), - ('foo/bar', ['foo']), + ("foo/bar/baz/file", ["foo", "foo/bar", "foo/bar/baz"]), + ("foo/bar/", ["foo", "foo/bar"]), + ("foo/bar", ["foo"]), ) for path, expected in paths_and_results: self._test_get_dirs_for_path(path, expected) @@ -59,18 +55,17 @@ class TestPaths(object): new = tmpdir.strpath assert get_scms_for_path(new) == [] - os.mkdir(os.path.join(new, '.tux')) + os.mkdir(os.path.join(new, ".tux")) assert get_scms_for_path(new) == [] - os.mkdir(os.path.join(new, '.git')) - assert set(get_scms_for_path(new)) == set(['git']) + os.mkdir(os.path.join(new, ".git")) + assert set(get_scms_for_path(new)) == set(["git"]) - os.mkdir(os.path.join(new, '.hg')) - assert set(get_scms_for_path(new)) == set(['git', 'hg']) + os.mkdir(os.path.join(new, ".hg")) + assert set(get_scms_for_path(new)) == set(["git", "hg"]) class TestGetScm(object): - def test_existing_repository(self, vcs_repository_support): alias, repo = vcs_repository_support assert (alias, repo.path) == get_scm(repo.path) @@ -81,114 +76,101 @@ class TestGetScm(object): def test_get_scm_error_path(self): with pytest.raises(VCSError): - get_scm('err') + get_scm("err") def test_get_two_scms_for_path(self, tmpdir): multialias_repo_path = str(tmpdir) git_default_branch = GitRepository.DEFAULT_BRANCH_NAME - subprocess.check_call(['hg', 'init', multialias_repo_path]) - subprocess.check_call(['git', '-c', f'init.defaultBranch={git_default_branch}', 'init', multialias_repo_path]) + subprocess.check_call(["hg", "init", multialias_repo_path]) + subprocess.check_call(["git", "-c", f"init.defaultBranch={git_default_branch}", "init", multialias_repo_path]) with pytest.raises(VCSError): get_scm(multialias_repo_path) def test_ignores_svn_working_copy(self, tmpdir): - tmpdir.mkdir('.svn') + tmpdir.mkdir(".svn") with pytest.raises(VCSError): get_scm(tmpdir.strpath) class TestParseDatetime(object): - def test_datetime_text(self): - assert parse_datetime('2010-04-07 21:29:41') == \ - datetime.datetime(2010, 4, 7, 21, 29, 41) + assert parse_datetime("2010-04-07 21:29:41") == datetime.datetime(2010, 4, 7, 21, 29, 41) def test_no_seconds(self): - assert parse_datetime('2010-04-07 21:29') == \ - datetime.datetime(2010, 4, 7, 21, 29) + assert parse_datetime("2010-04-07 21:29") == datetime.datetime(2010, 4, 7, 21, 29) def test_date_only(self): - assert parse_datetime('2010-04-07') == \ - datetime.datetime(2010, 4, 7) + assert parse_datetime("2010-04-07") == datetime.datetime(2010, 4, 7) def test_another_format(self): - assert parse_datetime('04/07/10 21:29:41') == \ - datetime.datetime(2010, 4, 7, 21, 29, 41) + assert parse_datetime("04/07/10 21:29:41") == datetime.datetime(2010, 4, 7, 21, 29, 41) def test_now(self): - assert parse_datetime('now') - datetime.datetime.now() < \ - datetime.timedelta(seconds=1) + assert parse_datetime("now") - datetime.datetime.now() < datetime.timedelta(seconds=1) def test_today(self): today = datetime.date.today() - assert parse_datetime('today') == \ - datetime.datetime(*today.timetuple()[:3]) + assert parse_datetime("today") == datetime.datetime(*today.timetuple()[:3]) def test_yesterday(self): yesterday = datetime.date.today() - datetime.timedelta(days=1) - assert parse_datetime('yesterday') == \ - datetime.datetime(*yesterday.timetuple()[:3]) + assert parse_datetime("yesterday") == datetime.datetime(*yesterday.timetuple()[:3]) def test_tomorrow(self): tomorrow = datetime.date.today() + datetime.timedelta(days=1) args = tomorrow.timetuple()[:3] + (23, 59, 59) - assert parse_datetime('tomorrow') == datetime.datetime(*args) + assert parse_datetime("tomorrow") == datetime.datetime(*args) def test_days(self): timestamp = datetime.datetime.today() - datetime.timedelta(days=3) args = timestamp.timetuple()[:3] + (0, 0, 0, 0) expected = datetime.datetime(*args) - assert parse_datetime('3d') == expected - assert parse_datetime('3 d') == expected - assert parse_datetime('3 day') == expected - assert parse_datetime('3 days') == expected + assert parse_datetime("3d") == expected + assert parse_datetime("3 d") == expected + assert parse_datetime("3 day") == expected + assert parse_datetime("3 days") == expected def test_weeks(self): timestamp = datetime.datetime.today() - datetime.timedelta(days=3 * 7) args = timestamp.timetuple()[:3] + (0, 0, 0, 0) expected = datetime.datetime(*args) - assert parse_datetime('3w') == expected - assert parse_datetime('3 w') == expected - assert parse_datetime('3 week') == expected - assert parse_datetime('3 weeks') == expected + assert parse_datetime("3w") == expected + assert parse_datetime("3 w") == expected + assert parse_datetime("3 week") == expected + assert parse_datetime("3 weeks") == expected def test_mixed(self): - timestamp = ( - datetime.datetime.today() - datetime.timedelta(days=2 * 7 + 3)) + timestamp = datetime.datetime.today() - datetime.timedelta(days=2 * 7 + 3) args = timestamp.timetuple()[:3] + (0, 0, 0, 0) expected = datetime.datetime(*args) - assert parse_datetime('2w3d') == expected - assert parse_datetime('2w 3d') == expected - assert parse_datetime('2w 3 days') == expected - assert parse_datetime('2 weeks 3 days') == expected + assert parse_datetime("2w3d") == expected + assert parse_datetime("2w 3d") == expected + assert parse_datetime("2w 3 days") == expected + assert parse_datetime("2 weeks 3 days") == expected -@pytest.mark.parametrize("test_str, name, email", [ - ('Marcin Kuzminski ', - 'Marcin Kuzminski', 'marcin@python-works.com'), - ('Marcin Kuzminski Spaces < marcin@python-works.com >', - 'Marcin Kuzminski Spaces', 'marcin@python-works.com'), - ('Marcin Kuzminski ', - 'Marcin Kuzminski', 'marcin.kuzminski@python-works.com'), - ('mrf RFC_SPEC ', - 'mrf RFC_SPEC', 'marcin+kuzminski@python-works.com'), - ('username ', - 'username', 'user@email.com'), - ('username ', - '', 'justemail@mail.com'), - ('justname', - 'justname', ''), - ('Mr Double Name withemail@email.com ', - 'Mr Double Name', 'withemail@email.com'), -]) +@pytest.mark.parametrize( + "test_str, name, email", + [ + ("Marcin Kuzminski ", "Marcin Kuzminski", "marcin@python-works.com"), + ("Marcin Kuzminski Spaces < marcin@python-works.com >", "Marcin Kuzminski Spaces", "marcin@python-works.com"), + ( + "Marcin Kuzminski ", + "Marcin Kuzminski", + "marcin.kuzminski@python-works.com", + ), + ("mrf RFC_SPEC ", "mrf RFC_SPEC", "marcin+kuzminski@python-works.com"), + ("username ", "username", "user@email.com"), + ("username ", "", "justemail@mail.com"), + ("justname", "justname", ""), + ("Mr Double Name withemail@email.com ", "Mr Double Name", "withemail@email.com"), + ], +) class TestAuthorExtractors(object): - def test_author_email(self, test_str, name, email): assert email == author_email(test_str) diff --git a/rhodecode/tests/vcs/test_vcs.py b/rhodecode/tests/vcs/test_vcs.py index 465c7a1a..3f9a89f8 100644 --- a/rhodecode/tests/vcs/test_vcs.py +++ b/rhodecode/tests/vcs/test_vcs.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -20,6 +19,7 @@ """ Tests for main module's methods. """ + import os import tempfile import shutil @@ -48,7 +48,7 @@ def test_alias_detect(backend): def test_wrong_alias(): - alias = 'wrong_alias' + alias = "wrong_alias" with pytest.raises(VCSError): get_backend(alias) @@ -77,12 +77,13 @@ def test_get_vcs_instance_by_path_multiple_repos(request): Test that ``get_vcs_instance_by_path`` returns None if a path is passed to a directory with multiple repositories. """ - empty_dir = tempfile.mkdtemp(prefix='pytest-empty-dir-') - os.mkdir(os.path.join(empty_dir, '.git')) - os.mkdir(os.path.join(empty_dir, '.hg')) + empty_dir = tempfile.mkdtemp(prefix="pytest-empty-dir-") + os.mkdir(os.path.join(empty_dir, ".git")) + os.mkdir(os.path.join(empty_dir, ".hg")) def fin(): shutil.rmtree(empty_dir) + request.addfinalizer(fin) repo = get_vcs_instance(empty_dir) @@ -90,39 +91,32 @@ def test_get_vcs_instance_by_path_multiple_repos(request): assert repo is None -@mock.patch('rhodecode.lib.vcs.backends.get_scm') -@mock.patch('rhodecode.lib.vcs.backends.get_backend') -def test_get_vcs_instance_by_path_args_passed( - get_backend_mock, get_scm_mock, tmpdir, vcs_repo): +@mock.patch("rhodecode.lib.vcs.backends.get_scm") +@mock.patch("rhodecode.lib.vcs.backends.get_backend") +def test_get_vcs_instance_by_path_args_passed(get_backend_mock, get_scm_mock, tmpdir, vcs_repo): """ Test that the arguments passed to ``get_vcs_instance_by_path`` are forwarded to the vcs backend class. """ backend = mock.MagicMock() get_backend_mock.return_value = backend - args = ['these-are-test-args', 0, True, None] + args = ["these-are-test-args", 0, True, None] repo = vcs_repo.path get_vcs_instance(repo, *args) backend.assert_called_with(*args, repo_path=repo) -@mock.patch('rhodecode.lib.vcs.backends.get_scm') -@mock.patch('rhodecode.lib.vcs.backends.get_backend') -def test_get_vcs_instance_by_path_kwargs_passed( - get_backend_mock, get_scm_mock, vcs_repo): +@mock.patch("rhodecode.lib.vcs.backends.get_scm") +@mock.patch("rhodecode.lib.vcs.backends.get_backend") +def test_get_vcs_instance_by_path_kwargs_passed(get_backend_mock, get_scm_mock, vcs_repo): """ Test that the keyword arguments passed to ``get_vcs_instance_by_path`` are forwarded to the vcs backend class. """ backend = mock.MagicMock() get_backend_mock.return_value = backend - kwargs = { - 'foo': 'these-are-test-args', - 'bar': 0, - 'baz': True, - 'foobar': None - } + kwargs = {"foo": "these-are-test-args", "bar": 0, "baz": True, "foobar": None} repo = vcs_repo.path get_vcs_instance(repo, **kwargs) diff --git a/rhodecode/tests/vcs/utils.py b/rhodecode/tests/vcs/utils.py index df0832f5..f3565848 100644 --- a/rhodecode/tests/vcs/utils.py +++ b/rhodecode/tests/vcs/utils.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -37,7 +36,7 @@ def run_command(cmd, args): """ Runs command on the system with given ``args``. """ - command = ' '.join((cmd, args)) + command = " ".join((cmd, args)) p = Popen(command, shell=True) status = os.waitpid(p.pid, 0)[1] return status @@ -51,12 +50,11 @@ def eprint(msg): Appends line break. """ sys.stderr.write(msg) - sys.stderr.write('\n') + sys.stderr.write("\n") # TODO: Revisit once we have CI running, if this is not helping us, remove it class SCMFetcher(object): - def __init__(self, alias, test_repo_path): """ :param clone_cmd: command which would clone remote repository; pass @@ -75,9 +73,8 @@ class SCMFetcher(object): Tries to fetch repository from remote path. """ remote = self.remote_repo - eprint( - "Fetching repository %s into %s" % (remote, self.test_repo_path)) - run_command(self.clone_cmd, '%s %s' % (remote, self.test_repo_path)) + eprint("Fetching repository %s into %s" % (remote, self.test_repo_path)) + run_command(self.clone_cmd, "%s %s" % (remote, self.test_repo_path)) def get_normalized_path(path): @@ -88,29 +85,29 @@ def get_normalized_path(path): """ if os.path.exists(path): dir, basename = os.path.split(path) - splitted_name = basename.split('.') + splitted_name = basename.split(".") if len(splitted_name) > 1: ext = splitted_name[-1] else: ext = None - name = '.'.join(splitted_name[:-1]) - matcher = re.compile(r'^.*-(\d{5})$') + name = ".".join(splitted_name[:-1]) + matcher = re.compile(r"^.*-(\d{5})$") start = 0 m = matcher.match(name) if not m: # Haven't append number yet so return first - newname = f'{name}-00000' + newname = f"{name}-00000" newpath = os.path.join(dir, newname) if ext: - newpath = '.'.join((newpath, ext)) + newpath = ".".join((newpath, ext)) return get_normalized_path(newpath) else: start = int(m.group(1)[-5:]) + 1 for x in range(start, 10000): - newname = name[:-5] + str(x).rjust(5, '0') + newname = name[:-5] + str(x).rjust(5, "0") newpath = os.path.join(dir, newname) if ext: - newpath = '.'.join((newpath, ext)) + newpath = ".".join((newpath, ext)) if not os.path.exists(newpath): return newpath raise VCSTestError("Couldn't compute new path for %s" % path) diff --git a/rhodecode/tests/vcs_operations/__init__.py b/rhodecode/tests/vcs_operations/__init__.py index b3881ecb..b77f7f30 100644 --- a/rhodecode/tests/vcs_operations/__init__.py +++ b/rhodecode/tests/vcs_operations/__init__.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -36,7 +35,6 @@ from rhodecode.lib.str_utils import safe_str from rhodecode.tests import GIT_REPO, HG_REPO, SVN_REPO DEBUG = True -RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log') REPO_GROUP = 'a_repo_group' HG_REPO_WITH_GROUP = f'{REPO_GROUP}/{HG_REPO}' GIT_REPO_WITH_GROUP = f'{REPO_GROUP}/{GIT_REPO}' diff --git a/rhodecode/tests/vcs_operations/conftest.py b/rhodecode/tests/vcs_operations/conftest.py index d70fb2cd..0cf8a4fb 100644 --- a/rhodecode/tests/vcs_operations/conftest.py +++ b/rhodecode/tests/vcs_operations/conftest.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -19,22 +18,17 @@ """ py.test config for test suite for making push/pull operations. - -.. important:: - - You must have git >= 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. """ import os -import tempfile -import textwrap + +import pyramid.paster import pytest import logging import requests from rhodecode import events -from rhodecode.lib.str_utils import safe_bytes +from rhodecode.lib.type_utils import AttributeDict from rhodecode.model.db import Integration, UserRepoToPerm, Permission, \ UserToRepoBranchPermission, User from rhodecode.model.integration import IntegrationModel @@ -42,11 +36,13 @@ from rhodecode.model.db import Repository from rhodecode.model.meta import Session from rhodecode.integrations.types.webhook import WebhookIntegrationType + from rhodecode.tests import GIT_REPO, HG_REPO, SVN_REPO from rhodecode.tests.conftest import HTTPBIN_DOMAIN, HTTPBIN_POST -from rhodecode.tests.fixture import Fixture -from rhodecode.tests.server_utils import RcWebServer - +from rhodecode.tests.fixtures.rc_fixture import Fixture +from rhodecode.tests.fixtures.fixture_utils import backend_base +from rhodecode.tests.utils import set_anonymous_access, AuthPluginManager +from rhodecode.tests import console_printer REPO_GROUP = 'a_repo_group' HG_REPO_WITH_GROUP = f'{REPO_GROUP}/{HG_REPO}' @@ -62,39 +58,42 @@ def check_httpbin_connection(): response = requests.get(HTTPBIN_DOMAIN, timeout=5) return response.status_code == 200 except Exception as e: - print(e) + console_printer(e) return False +#overrides backend_N with init_pyramid_app instead of baseapp +@pytest.fixture() +def vcs_backend_git(request, init_pyramid_app, test_repo): + return backend_base(request, 'git', test_repo) -@pytest.fixture(scope="module") -def rcextensions(request, db_connection, tmpdir_factory): - """ - Installs a testing rcextensions pack to ensure they work as expected. - """ - init_content = textwrap.dedent(""" - # Forward import the example rcextensions to make it - # active for our tests. - from rhodecode.tests.other.example_rcextensions import * - """) - # Note: rcextensions are looked up based on the path of the ini file - root_path = tmpdir_factory.getbasetemp() - rcextensions_path = root_path.join('rcextensions') - init_path = rcextensions_path.join('__init__.py') +@pytest.fixture() +def vcs_backend_hg(request, init_pyramid_app, test_repo): + return backend_base(request, 'hg', test_repo) - if rcextensions_path.check(): - pytest.fail( - "Path for rcextensions already exists, please clean up before " - "test run this path: %s" % (rcextensions_path, )) - else: - request.addfinalizer(rcextensions_path.remove) - init_path.write_binary(safe_bytes(init_content), ensure=True) + +@pytest.fixture() +def vcs_backend_svn(request, init_pyramid_app, test_repo): + return backend_base(request, 'svn', test_repo) @pytest.fixture(scope="module") -def repos(request, db_connection): +def tmp_storage_location(request, tmpdir_factory): + """ + Defines a module level storage_location, used mostly to define per-test persistent repo storage + shared across vcsserver, rhodecode and celery + """ + + dest = tmpdir_factory.mktemp('tmp_storage_location_', numbered=True) + log.info("Creating test TMP directory at %s", dest) + return dest + + +@pytest.fixture(scope="module") +def repo_group_repos(request): """Create a copy of each test repo in a repo group.""" + fixture = Fixture() repo_group = fixture.create_repo_group(REPO_GROUP) repo_group_id = repo_group.group_id @@ -116,67 +115,117 @@ def repos(request, db_connection): fixture.destroy_repo_group(repo_group_id) -@pytest.fixture(scope="module") -def rc_web_server_config_modification(): - return [] +@pytest.fixture(scope='module') +def rcstack_vcsserver_factory(vcsserver_factory): + return vcsserver_factory -@pytest.fixture(scope="module") -def rc_web_server_config_factory(testini_factory, rc_web_server_config_modification): +@pytest.fixture(scope='module') +def rcstack_celery_factory(celery_factory): + return celery_factory + + +@pytest.fixture(scope='module') +def rcstack_rhodecode_factory(rhodecode_factory): + return rhodecode_factory + + +@pytest.fixture(scope='module') +def init_pyramid_app(request, available_port_factory, ini_config_factory, rcstack_vcsserver_factory, tmp_storage_location): + from rhodecode.lib.config_utils import get_app_config + from rhodecode.config.middleware import make_pyramid_app + + store_dir = tmp_storage_location + port = available_port_factory() + rcstack_vcsserver_factory( + request, + store_dir=store_dir, + port=port, + info_prefix='init-app-' + ) + + app_ini_config = ini_config_factory(store_dir) + + pyramid.paster.setup_logging(app_ini_config) + + settings = get_app_config(app_ini_config) + settings['startup.import_repos'] = True + settings['vcs.server'] = f'localhost:{port}' + settings['repo_store.path'] = str(store_dir) + pyramid_app = make_pyramid_app({'__file__': app_ini_config}, **settings) + + return pyramid_app + + +@pytest.fixture(scope='module') +def rcstack(request, tmp_storage_location, rcextensions, available_port_factory, rcstack_vcsserver_factory, rcstack_celery_factory, rcstack_rhodecode_factory): """ - Configuration file used for the fixture `rc_web_server`. + Runs minimal rcstack, i.e vcsserver, celery, rhodecode unpacks rcextensions and repos to a shared location """ - - def factory(rcweb_port, vcsserver_port): - custom_params = [ - {'handler_console': {'level': 'DEBUG'}}, - {'server:main': {'port': rcweb_port}}, - {'app:main': {'vcs.server': 'localhost:%s' % vcsserver_port}} - ] - custom_params.extend(rc_web_server_config_modification) - return testini_factory(custom_params) - return factory - - -@pytest.fixture(scope="module") -def rc_web_server( - request, vcsserver_factory, available_port_factory, - rc_web_server_config_factory, repos, rcextensions): - """ - Run the web server as a subprocess. with its own instance of vcsserver - """ - rcweb_port: int = available_port_factory() - log.info('Using rcweb ops test port %s', rcweb_port) + rcstack_data = AttributeDict() + store_dir = tmp_storage_location vcsserver_port: int = available_port_factory() - log.info('Using vcsserver ops test port %s', vcsserver_port) + vcsserver_log = os.path.join(tmp_storage_location, 'vcsserver.log') - vcs_log = os.path.join(tempfile.gettempdir(), 'rc_op_vcs.log') - vcsserver_factory( - request, vcsserver_port=vcsserver_port, - log_file=vcs_log, + log.info('Using vcsserver test port %s and log %s', vcsserver_port, vcsserver_log) # start vcsserver + _factory = rcstack_vcsserver_factory( + request, + store_dir=store_dir, + port=vcsserver_port, + log_file=vcsserver_log, overrides=( - {'server:main': {'workers': 2}}, - {'server:main': {'graceful_timeout': 10}}, + {'handler_console': {'level': 'DEBUG'}}, + )) + rcstack_data.vcsserver_port = vcsserver_port + rcstack_data.vcsserver_log = _factory.log_file + + + celery_log = os.path.join(tmp_storage_location, 'celery.log') + + + log.info('Using celery log %s', celery_log) + # start celery + _factory = rcstack_celery_factory( + request, + store_dir=store_dir, + port=None, + log_file=celery_log, + overrides=( + {'handler_console': {'level': 'DEBUG'}}, + {'app:main': {'vcs.server': f'localhost:{vcsserver_port}'}}, + {'app:main': {'repo_store.path': store_dir}} )) - rc_log = os.path.join(tempfile.gettempdir(), 'rc_op_web.log') - rc_web_server_config = rc_web_server_config_factory( - rcweb_port=rcweb_port, - vcsserver_port=vcsserver_port) - server = RcWebServer(rc_web_server_config, log_file=rc_log) - server.start() + rcstack_data.celery_log = _factory.log_file - @request.addfinalizer - def cleanup(): - server.shutdown() + rhodecode_port: int = available_port_factory() + rhodecode_log = os.path.join(tmp_storage_location, 'rhodecode.log') - server.wait_until_ready() - return server + + log.info('Using rhodecode test port %s and log %s', rhodecode_port, rhodecode_port) + + # start rhodecode + rc = rcstack_rhodecode_factory( + request, + store_dir=store_dir, + port=rhodecode_port, + log_file=rhodecode_log, + overrides=( + {'handler_console': {'level': 'DEBUG'}}, + {'app:main': {'vcs.server': f'localhost:{vcsserver_port}'}}, + {'app:main': {'repo_store.path': store_dir}} + )) + + rcstack_data.rhodecode_port = rhodecode_port + rcstack_data.rhodecode_log = rc.log_file + + rc.rcstack_data = rcstack_data + return rc @pytest.fixture() -def disable_locking(baseapp): +def disable_locking(init_pyramid_app): r = Repository.get_by_repo_name(GIT_REPO) Repository.unlock(r) r.enable_locking = False @@ -190,6 +239,28 @@ def disable_locking(baseapp): Session().commit() +@pytest.fixture() +def disable_anonymous_user(request, init_pyramid_app, db_connection): + set_anonymous_access(False) + + @request.addfinalizer + def cleanup(): + set_anonymous_access(True) + + +@pytest.fixture(scope='module') +def enable_auth_plugins(request, init_pyramid_app): + """ + Return a factory object that when called, allows to control which + authentication plugins are enabled. + """ + + enabler = AuthPluginManager() + request.addfinalizer(enabler.cleanup) + + return enabler + + @pytest.fixture() def fs_repo_only(request, rhodecode_fixtures): def fs_repo_fabric(repo_name, repo_type): diff --git a/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_403.py b/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_403.py index 24d022bc..76fc59ca 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_403.py +++ b/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_403.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -32,25 +31,27 @@ from rhodecode.tests import (GIT_REPO, HG_REPO) from rhodecode.tests.vcs_operations import Command -@pytest.fixture(scope="module") -def rc_web_server_config_modification(): - return [ - {'app:main': {'auth_ret_code': '403'}}, - #{'app:main': {'auth_ret_code_detection': 'true'}}, - ] +custom_code = [ + {'app:main': {'auth_ret_code': '403'}}, +] +@pytest.mark.parametrize('rcstack', custom_code, indirect=True) +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsAuthCode403(object): -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperationsOnCustomIniConfig(object): - - def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_hg_ret_code(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: HTTP Error 403: Forbidden' in stderr - def test_clone_wrong_credentials_git_ret_code(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_git_ret_code(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) assert 'The requested URL returned error: 403' in stderr diff --git a/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_404.py b/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_404.py index acd13296..3271cb7f 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_404.py +++ b/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_404.py @@ -31,26 +31,27 @@ import pytest from rhodecode.tests import (GIT_REPO, HG_REPO) from rhodecode.tests.vcs_operations import Command +custom_code = [ + {'app:main': {'auth_ret_code': '404'}}, +] -@pytest.fixture(scope="module") -def rc_web_server_config_modification(): - return [ - {'app:main': {'auth_ret_code': '404'}}, - #{'app:main': {'auth_ret_code_detection': 'false'}}, - ] +@pytest.mark.parametrize('rcstack', custom_code, indirect=True) +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsOnCustomAuthCode404(object): - -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperationsOnCustomIniConfig(object): - - def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_hg_ret_code(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: HTTP Error 404: Not Found' in stderr - def test_clone_wrong_credentials_git_ret_code(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_git_ret_code(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) assert 'not found' in stderr diff --git a/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_bad_code.py b/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_bad_code.py index 9202aace..18610a4e 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_bad_code.py +++ b/rhodecode/tests/vcs_operations/test_vcs_calls_custom_auth_code_bad_code.py @@ -32,25 +32,27 @@ from rhodecode.tests import (GIT_REPO, HG_REPO) from rhodecode.tests.vcs_operations import Command -@pytest.fixture(scope="module") -def rc_web_server_config_modification(): - return [ - {'app:main': {'auth_ret_code': '600'}}, - #{'app:main': {'auth_ret_code_detection': 'false'}}, - ] +custom_code = [ + {'app:main': {'auth_ret_code': '600'}}, +] - -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") +@pytest.mark.parametrize('rcstack', custom_code, indirect=True) +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) class TestVCSOperationsOnCustomIniConfig(object): - def test_clone_wrong_credentials_hg_ret_code(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_hg_ret_code(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: authorization failed' in stderr - def test_clone_wrong_credentials_git_ret_code(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_git_ret_code(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) assert 'fatal: Authentication failed' in stderr diff --git a/rhodecode/tests/vcs_operations/test_vcs_calls_small_post_buffer.py b/rhodecode/tests/vcs_operations/test_vcs_calls_small_post_buffer.py index b419622d..1b0b8aeb 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_calls_small_post_buffer.py +++ b/rhodecode/tests/vcs_operations/test_vcs_calls_small_post_buffer.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -20,13 +19,10 @@ """ Test suite for making push/pull operations, on specially modified INI files -.. important:: - - You must have git >= 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. """ import os +import pytest from rhodecode.lib.vcs.backends.git.repository import GitRepository from rhodecode.lib.vcs.nodes import FileNode @@ -35,31 +31,39 @@ from rhodecode.tests.vcs_operations import Command from .test_vcs_operations_git import _check_proper_clone, _check_proper_git_push -def test_git_clone_with_small_push_buffer(backend_git, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - cmd = Command('/tmp') - stdout, stderr = cmd.execute( - 'git -c http.postBuffer=1024 clone', clone_url, tmpdir.strpath) - _check_proper_clone(stdout, stderr, 'git') - cmd.assert_returncode_success() +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsOnCustomIniConfig(object): + + def test_git_clone_with_small_push_buffer(self, vcs_backend_git, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + cmd = Command(tmpdir.strpath) + stdout, stderr = cmd.execute( + 'git -c http.postBuffer=1024 clone', clone_url, tmpdir.strpath) + _check_proper_clone(stdout, stderr, 'git') + cmd.assert_returncode_success() -def test_git_push_with_small_push_buffer(backend_git, rc_web_server, tmpdir): - empty_repo = backend_git.create_repo() + def test_git_push_with_small_push_buffer(self, vcs_backend_git, rcstack, tmpdir): + empty_repo = vcs_backend_git.create_repo() - clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name) + clone_url = rcstack.repo_clone_url(empty_repo.repo_name) - cmd = Command(tmpdir.strpath) - cmd.execute('git clone', clone_url) + cmd = Command(tmpdir.strpath) + cmd.execute('git clone', clone_url) - repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name)) - repo.in_memory_commit.add(FileNode(b'readme.md', content=b'## Hello')) - repo.in_memory_commit.commit( - message='Commit on branch Master', - author='Automatic test ', - branch='master') + repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name)) + repo.in_memory_commit.add(FileNode(b'readme.md', content=b'## Hello')) + repo.in_memory_commit.commit( + message='Commit on branch Master', + author='Automatic test ', + branch='master') - repo_cmd = Command(repo.path) - stdout, stderr = repo_cmd.execute( - f'git -c http.postBuffer=1024 push --verbose {clone_url} master') - _check_proper_git_push(stdout, stderr, branch='master') + repo_cmd = Command(repo.path) + stdout, stderr = repo_cmd.execute( + f'git -c http.postBuffer=1024 push --verbose {clone_url} master') + _check_proper_git_push(stdout, stderr, branch='master') diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_bad_client.py b/rhodecode/tests/vcs_operations/test_vcs_operations_bad_client.py new file mode 100644 index 00000000..514e3f19 --- /dev/null +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_bad_client.py @@ -0,0 +1,89 @@ +# Copyright (C) 2010-2023 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/ + +""" +Test suite for making push/pull operations, on specially modified INI files +""" + +import pytest + +from rhodecode.model.meta import Session +from rhodecode.model.settings import SettingsModel + +from rhodecode.tests import GIT_REPO, HG_REPO +from rhodecode.tests.vcs_operations import Command, _add_files_and_push + + +@pytest.fixture() +def bad_client_setter_factory(request): + def _factory(client_type, client_str_val): + # set allowed clients + setting = SettingsModel().create_or_update_setting(name=f"{client_type}_allowed_clients", val=client_str_val) + Session().add(setting) + Session().commit() + + @request.addfinalizer + def cleanup(): + setting2 = SettingsModel().create_or_update_setting(name=f"{client_type}_allowed_clients", val="*") + Session().add(setting2) + Session().commit() + + return _factory + + +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsOnUsingBadClient(object): + def test_push_with_bad_client_repo_by_other_user_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("hg clone", clone_url, tmpdir.strpath) + + # set allowed clients + setting = SettingsModel().create_or_update_setting(name=f"hg_allowed_clients", val="0.0.0") + Session().add(setting) + Session().commit() + + # push fails repo is locked by other user ! + push_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = _add_files_and_push("hg", tmpdir.strpath, clone_url=push_url) + msg = "Your hg client (ver=mercurial/proto-1.0 (Mercurial 6.7.4)) is forbidden by security rules" + assert msg in stderr + + def test_push_with_bad_client_repo_by_other_user_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("git clone", clone_url, tmpdir.strpath) + + # set allowed clients + setting = SettingsModel().create_or_update_setting(name=f"git_allowed_clients", val="0.0.0") + Session().add(setting) + Session().commit() + + # push fails repo is locked by other user! + push_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = _add_files_and_push("git", tmpdir.strpath, clone_url=push_url) + + err = "Your git client (ver=git/2.45.2) is forbidden by security rules" + assert err in stderr + + @pytest.mark.xfail(reason="Lack of proper SVN support of cloning") + def test_push_with_bad_client_repo_by_other_user_svn(self, rcstack, tmpdir): + raise NotImplementedError("lacks svn support") diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_branch_protection.py b/rhodecode/tests/vcs_operations/test_vcs_operations_branch_protection.py index 3b1be329..400fac8d 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_branch_protection.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_branch_protection.py @@ -27,7 +27,12 @@ from rhodecode.tests.vcs_operations import ( Command, _check_proper_hg_push, _check_proper_git_push, _add_files_and_push) -@pytest.mark.usefixtures("disable_anonymous_user") +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) class TestVCSOperations(object): @pytest.mark.parametrize('username, password', [ @@ -41,13 +46,13 @@ class TestVCSOperations(object): 'branch.push_force', ]) def test_push_to_protected_branch_fails_with_message_hg( - self, rc_web_server, tmpdir, branch_perm, user_util, + self, rcstack, tmpdir, branch_perm, user_util, branch_permission_setter, username, password): repo = user_util.create_repo(repo_type='hg') repo_name = repo.repo_name branch_permission_setter(repo_name, username, permission=branch_perm) - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( repo.repo_name, user=username, passwd=password) Command(os.path.dirname(tmpdir.strpath)).execute( 'hg clone', clone_url, tmpdir.strpath) @@ -58,8 +63,8 @@ class TestVCSOperations(object): _check_proper_hg_push(stdout, stderr) else: msg = f"Branch `default` changes rejected by rule `*`=>{branch_perm}" - assert msg in stdout - assert "transaction abort" in stdout + assert msg in stderr + #assert "transaction abort" in stdout @pytest.mark.parametrize('username, password', [ (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS), @@ -72,13 +77,13 @@ class TestVCSOperations(object): 'branch.push_force', ]) def test_push_to_protected_branch_fails_with_message_git( - self, rc_web_server, tmpdir, branch_perm, user_util, + self, rcstack, tmpdir, branch_perm, user_util, branch_permission_setter, username, password): repo = user_util.create_repo(repo_type='git') repo_name = repo.repo_name branch_permission_setter(repo_name, username, permission=branch_perm) - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( repo.repo_name, user=username, passwd=password) Command(os.path.dirname(tmpdir.strpath)).execute( 'git clone', clone_url, tmpdir.strpath) diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_by_auth_tokens.py b/rhodecode/tests/vcs_operations/test_vcs_operations_by_auth_tokens.py index 05c7ab7c..9890825a 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_by_auth_tokens.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_by_auth_tokens.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -20,10 +19,6 @@ """ Test suite for making push/pull operations, on specially modified INI files -.. important:: - - You must have git >= 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. """ import pytest @@ -36,10 +31,15 @@ from rhodecode.tests import (GIT_REPO, HG_REPO) from rhodecode.tests.vcs_operations import (Command, _check_proper_clone) -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperations(object): +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsByAuthTokens: def test_clone_by_auth_token( - self, rc_web_server, tmpdir, user_util, enable_auth_plugins): + self, rcstack, tmpdir, user_util, enable_auth_plugins): enable_auth_plugins.enable([ 'egg:rhodecode-enterprise-ce#token', @@ -49,16 +49,16 @@ class TestVCSOperations(object): user = user_util.create_user() token = user.auth_tokens[1] - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( HG_REPO, user=user.username, passwd=token) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'hg') def test_clone_by_auth_token_expired( - self, rc_web_server, tmpdir, user_util, enable_auth_plugins): + self, rcstack, tmpdir, user_util, enable_auth_plugins): enable_auth_plugins.enable([ 'egg:rhodecode-enterprise-ce#token', 'egg:rhodecode-enterprise-ce#rhodecode' @@ -69,18 +69,18 @@ class TestVCSOperations(object): user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS) token = auth_token.api_key - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( HG_REPO, user=user.username, passwd=token) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: authorization failed' in stderr msg = 'reason: bad or inactive token.' - rc_web_server.assert_message_in_server_logs(msg) + rcstack.assert_message_in_server_logs(msg) def test_clone_by_auth_token_bad_role( - self, rc_web_server, tmpdir, user_util, enable_auth_plugins): + self, rcstack, tmpdir, user_util, enable_auth_plugins): enable_auth_plugins.enable([ 'egg:rhodecode-enterprise-ce#token', 'egg:rhodecode-enterprise-ce#rhodecode' @@ -91,15 +91,15 @@ class TestVCSOperations(object): user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API) token = auth_token.api_key - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( HG_REPO, user=user.username, passwd=token) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: authorization failed' in stderr def test_clone_by_auth_token_user_disabled( - self, rc_web_server, tmpdir, user_util, enable_auth_plugins): + self, rcstack, tmpdir, user_util, enable_auth_plugins): enable_auth_plugins.enable([ 'egg:rhodecode-enterprise-ce#token', 'egg:rhodecode-enterprise-ce#rhodecode' @@ -111,18 +111,18 @@ class TestVCSOperations(object): Session().commit() token = user.auth_tokens[1] - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( HG_REPO, user=user.username, passwd=token) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: authorization failed' in stderr msg = 'reason: account not active.' - rc_web_server.assert_message_in_server_logs(msg) + rcstack.assert_message_in_server_logs(msg) def test_clone_by_auth_token_with_scope( - self, rc_web_server, tmpdir, user_util, enable_auth_plugins): + self, rcstack, tmpdir, user_util, enable_auth_plugins): enable_auth_plugins.enable([ 'egg:rhodecode-enterprise-ce#token', 'egg:rhodecode-enterprise-ce#rhodecode' @@ -138,15 +138,15 @@ class TestVCSOperations(object): Session().add(auth_token) Session().commit() - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( HG_REPO, user=user.username, passwd=token) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'hg') def test_clone_by_auth_token_with_wrong_scope( - self, rc_web_server, tmpdir, user_util, enable_auth_plugins): + self, rcstack, tmpdir, user_util, enable_auth_plugins): enable_auth_plugins.enable([ 'egg:rhodecode-enterprise-ce#token', 'egg:rhodecode-enterprise-ce#rhodecode' @@ -162,13 +162,13 @@ class TestVCSOperations(object): Session().add(auth_token) Session().commit() - clone_url = rc_web_server.repo_clone_url( + clone_url = rcstack.repo_clone_url( HG_REPO, user=user.username, passwd=token) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: authorization failed' in stderr msg = 'reason: bad or inactive token.' - rc_web_server.assert_message_in_server_logs(msg) + rcstack.assert_message_in_server_logs(msg) diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_clone.py b/rhodecode/tests/vcs_operations/test_vcs_operations_clone.py new file mode 100644 index 00000000..04d6d6ba --- /dev/null +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_clone.py @@ -0,0 +1,63 @@ + +# Copyright (C) 2010-2023 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/ + +""" +Test suite for making push/pull operations, on specially modified INI files +""" + +import pytest + +from rhodecode.tests import GIT_REPO, SVN_REPO, HG_REPO + +from rhodecode.tests.vcs_operations import (Command, _check_proper_clone) +from rhodecode.tests.vcs_operations.test_vcs_operations_svn import get_cli_flags + + +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsClone: + + def test_clone_git_repo_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + cmd = Command(tmpdir.strpath) + stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath) + _check_proper_clone(stdout, stderr, 'git') + cmd.assert_returncode_success() + + def test_clone_hg_repo_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + cmd = Command(tmpdir.strpath) + stdout, stderr = cmd.execute('hg clone', clone_url, tmpdir.strpath) + _check_proper_clone(stdout, stderr, 'hg') + cmd.assert_returncode_success() + + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_clone_svn_repo_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(SVN_REPO) + username, password = rcstack.repo_clone_credentials() + flags, auth = get_cli_flags(username, password) + cmd = Command(tmpdir.strpath) + stdout, stderr = cmd.execute( + f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) + _check_proper_clone(stdout, stderr, 'svn') + cmd.assert_returncode_success() diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_force_push.py b/rhodecode/tests/vcs_operations/test_vcs_operations_force_push.py index b290c406..53469d95 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_force_push.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_force_push.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -27,12 +26,17 @@ from rhodecode.tests.vcs_operations import ( _add_files, _add_files_and_push) -@pytest.mark.usefixtures("disable_anonymous_user") +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) class TestVCSOperations(object): - def test_push_force_hg(self, rc_web_server, tmpdir, user_util): + def test_push_force_hg(self, rcstack, tmpdir, user_util): repo = user_util.create_repo(repo_type='hg') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'hg clone', clone_url, tmpdir.strpath) @@ -50,9 +54,9 @@ class TestVCSOperations(object): _check_proper_hg_push(stdout, stderr) - def test_push_force_git(self, rc_web_server, tmpdir, user_util): + def test_push_force_git(self, rcstack, tmpdir, user_util): repo = user_util.create_repo(repo_type='git') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'git clone', clone_url, tmpdir.strpath) @@ -69,13 +73,12 @@ class TestVCSOperations(object): assert '(forced update)' in stderr def test_push_force_hg_blocked_by_branch_permissions( - self, rc_web_server, tmpdir, user_util, branch_permission_setter): + self, rcstack, tmpdir, user_util, branch_permission_setter): repo = user_util.create_repo(repo_type='hg') repo_name = repo.repo_name username = TEST_USER_ADMIN_LOGIN - branch_permission_setter(repo_name, username, permission='branch.push') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'hg clone', clone_url, tmpdir.strpath) @@ -88,21 +91,21 @@ class TestVCSOperations(object): 'hg checkout -r 1 && hg commit -m "starting new head"') _add_files('hg', tmpdir.strpath, clone_url=clone_url) + branch_permission_setter(repo_name, username, permission='branch.push') stdout, stderr = Command(tmpdir.strpath).execute( f'hg push --verbose -f {clone_url}') - assert "Branch `default` changes rejected by rule `*`=>branch.push" in stdout - assert "FORCE PUSH FORBIDDEN" in stdout - assert "transaction abort" in stdout + assert "Branch `default` changes rejected by rule `*`=>branch.push" in stderr + assert "FORCE PUSH FORBIDDEN" in stderr def test_push_force_git_blocked_by_branch_permissions( - self, rc_web_server, tmpdir, user_util, branch_permission_setter): + self, rcstack, tmpdir, user_util, branch_permission_setter): repo = user_util.create_repo(repo_type='git') repo_name = repo.repo_name username = TEST_USER_ADMIN_LOGIN branch_permission_setter(repo_name, username, permission='branch.push') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'git clone', clone_url, tmpdir.strpath) diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_git.py b/rhodecode/tests/vcs_operations/test_vcs_operations_git.py index d8d85cda..561da0e1 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_git.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_git.py @@ -25,8 +25,6 @@ Test suite for making push/pull operations, on specially modified INI files You must have git >= 1.8.5 for tests to work fine. With 68b939b git started to redirect things to stderr instead of stdout. """ - - import time import pytest @@ -42,41 +40,46 @@ from rhodecode.tests.vcs_operations import ( _add_files_and_push, GIT_REPO_WITH_GROUP) -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperations(object): +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsGit: - def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - cmd = Command('/tmp') + def test_clone_git_repo_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + cmd = Command(tmpdir.strpath) stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'git') cmd.assert_returncode_success() - def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - cmd = Command('/tmp') + def test_clone_git_repo_by_admin_with_git_suffix(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + cmd = Command(tmpdir.strpath) stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath) _check_proper_clone(stdout, stderr, 'git') cmd.assert_returncode_success() - def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir): + def test_clone_git_repo_by_id_by_admin(self, rcstack, tmpdir): repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id - clone_url = rc_web_server.repo_clone_url('_%s' % repo_id) - cmd = Command('/tmp') + clone_url = rcstack.repo_clone_url('_%s' % repo_id) + cmd = Command(tmpdir.strpath) stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'git') cmd.assert_returncode_success() - def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP) - cmd = Command('/tmp') + def test_clone_git_repo_with_group_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO_WITH_GROUP) + cmd = Command(tmpdir.strpath) stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'git') cmd.assert_returncode_success() - def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - cmd = Command('/tmp') + def test_clone_git_repo_shallow_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + cmd = Command(tmpdir.strpath) stdout, stderr = cmd.execute( 'git clone --depth=1', clone_url, tmpdir.strpath) @@ -84,65 +87,64 @@ class TestVCSOperations(object): assert 'Cloning into' in stderr cmd.assert_returncode_success() - - def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) assert 'fatal: Authentication failed' in stderr - def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + def test_clone_git_dir_as_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr - def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url('trololo') - stdout, stderr = Command('/tmp').execute( + def test_clone_non_existing_path_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url('trololo') + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr - def test_clone_non_existing_path_git(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url('trololo') - stdout, stderr = Command('/tmp').execute('git clone', clone_url) + def test_clone_non_existing_path_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url('trololo') + stdout, stderr = Command(tmpdir.strpath).execute('git clone', clone_url) assert 'not found' in stderr - def test_clone_git_with_slashes(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url('//' + GIT_REPO) - stdout, stderr = Command('/tmp').execute('git clone', clone_url) + def test_clone_git_with_slashes(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url('//' + GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute('git clone', clone_url) assert 'not found' in stderr def test_clone_existing_path_git_not_in_database( - self, rc_web_server, tmpdir, fs_repo_only): + self, rcstack, tmpdir, fs_repo_only): db_name = fs_repo_only('not-in-db-git', repo_type='git') - clone_url = rc_web_server.repo_clone_url(db_name) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(db_name) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) assert 'not found' in stderr def test_clone_existing_path_git_not_in_database_different_scm( - self, rc_web_server, tmpdir, fs_repo_only): + self, rcstack, tmpdir, fs_repo_only): db_name = fs_repo_only('not-in-db-hg', repo_type='hg') - clone_url = rc_web_server.repo_clone_url(db_name) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(db_name) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) assert 'not found' in stderr - def test_clone_non_existing_store_path_git(self, rc_web_server, tmpdir, user_util): + def test_clone_non_existing_store_path_git(self, rcstack, tmpdir, user_util): repo = user_util.create_repo(repo_type='git') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) # Damage repo by removing it's folder RepoModel()._delete_filesystem_repo(repo) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) assert 'not found' in stderr - def test_push_new_file_git(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + def test_push_new_file_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) # commit some stuff into this repo @@ -151,37 +153,37 @@ class TestVCSOperations(object): _check_proper_git_push(stdout, stderr) - def test_push_wrong_credentials_git(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + def test_push_wrong_credentials_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) - push_url = rc_web_server.repo_clone_url( + push_url = rcstack.repo_clone_url( GIT_REPO, user='bad', passwd='name') stdout, stderr = _add_files_and_push( 'git', tmpdir.strpath, clone_url=push_url) assert 'fatal: Authentication failed' in stderr - def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + def test_push_back_to_wrong_url_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) stdout, stderr = _add_files_and_push( 'git', tmpdir.strpath, - clone_url=rc_web_server.repo_clone_url('not-existing')) + clone_url=rcstack.repo_clone_url('not-existing')) assert 'not found' in stderr - def test_ip_restriction_git(self, rc_web_server, tmpdir): + def test_ip_restriction_git(self, rcstack, tmpdir): user_model = UserModel() try: user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32') Session().commit() time.sleep(2) - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) msg = "The requested URL returned error: 403" assert msg in stderr @@ -193,7 +195,7 @@ class TestVCSOperations(object): time.sleep(2) - cmd = Command('/tmp') + cmd = Command(tmpdir.strpath) stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath) cmd.assert_returncode_success() _check_proper_clone(stdout, stderr, 'git') diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_hg.py b/rhodecode/tests/vcs_operations/test_vcs_operations_hg.py index 74d8e035..ae9a47e7 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_hg.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_hg.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2010-2023 RhodeCode GmbH +# 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 @@ -19,11 +18,6 @@ """ Test suite for making push/pull operations, on specially modified INI files - -.. important:: - - You must have git >= 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. """ @@ -42,96 +36,101 @@ from rhodecode.tests.vcs_operations import ( Command, _check_proper_clone, _add_files_and_push, HG_REPO_WITH_GROUP) -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperations(object): +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsHg(object): - def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + def test_clone_hg_repo_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'hg') - def test_clone_hg_repo_by_admin_pull_protocol(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + def test_clone_hg_repo_by_admin_pull_protocol(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone --pull', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'hg') - def test_clone_hg_repo_by_admin_pull_stream_protocol(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + def test_clone_hg_repo_by_admin_pull_stream_protocol(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone --pull --stream', clone_url, tmpdir.strpath) assert 'files to transfer,' in stdout assert 'transferred 1.' in stdout assert '114 files updated,' in stdout - def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir): + def test_clone_hg_repo_by_id_by_admin(self, rcstack, tmpdir): repo_id = Repository.get_by_repo_name(HG_REPO).repo_id - clone_url = rc_web_server.repo_clone_url('_%s' % repo_id) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url('_%s' % repo_id) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'hg') - def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP) - stdout, stderr = Command('/tmp').execute( + def test_clone_hg_repo_with_group_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO_WITH_GROUP) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'hg') - def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!') - stdout, stderr = Command('/tmp').execute( + def test_clone_wrong_credentials_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO, passwd='bad!') + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: authorization failed' in stderr - def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + def test_clone_git_dir_as_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr - def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url('trololo') - stdout, stderr = Command('/tmp').execute( + def test_clone_non_existing_path_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url('trololo') + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr - def test_clone_hg_with_slashes(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url('//' + HG_REPO) - stdout, stderr = Command('/tmp').execute('hg clone', clone_url, tmpdir.strpath) + def test_clone_hg_with_slashes(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url('//' + HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute('hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr def test_clone_existing_path_hg_not_in_database( - self, rc_web_server, tmpdir, fs_repo_only): + self, rcstack, tmpdir, fs_repo_only): db_name = fs_repo_only('not-in-db-hg', repo_type='hg') - clone_url = rc_web_server.repo_clone_url(db_name) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(db_name) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr def test_clone_existing_path_hg_not_in_database_different_scm( - self, rc_web_server, tmpdir, fs_repo_only): + self, rcstack, tmpdir, fs_repo_only): db_name = fs_repo_only('not-in-db-git', repo_type='git') - clone_url = rc_web_server.repo_clone_url(db_name) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(db_name) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr - def test_clone_non_existing_store_path_hg(self, rc_web_server, tmpdir, user_util): + def test_clone_non_existing_store_path_hg(self, rcstack, tmpdir, user_util): repo = user_util.create_repo() - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) # Damage repo by removing it's folder RepoModel()._delete_filesystem_repo(repo) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'HTTP Error 404: Not Found' in stderr - def test_push_new_file_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + def test_push_new_file_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) stdout, stderr = _add_files_and_push( @@ -140,7 +139,7 @@ class TestVCSOperations(object): assert 'pushing to' in stdout assert 'size summary' in stdout - def test_push_invalidates_cache(self, rc_web_server, tmpdir): + def test_push_invalidates_cache(self, rcstack, tmpdir): hg_repo = Repository.get_by_repo_name(HG_REPO) # init cache objects @@ -159,8 +158,8 @@ class TestVCSOperations(object): old_ids = [x.cache_state_uid for x in cache_keys] # clone to init cache - clone_url = rc_web_server.repo_clone_url(hg_repo.repo_name) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(hg_repo.repo_name) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) cache_keys = hg_repo.cache_keys @@ -180,37 +179,37 @@ class TestVCSOperations(object): new_ids = [x.cache_state_uid for x in cache_keys] assert new_ids != old_ids - def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + def test_push_wrong_credentials_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) - push_url = rc_web_server.repo_clone_url( + push_url = rcstack.repo_clone_url( HG_REPO, user='bad', passwd='name') stdout, stderr = _add_files_and_push( 'hg', tmpdir.strpath, clone_url=push_url) assert 'abort: authorization failed' in stderr - def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + def test_push_back_to_wrong_url_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) stdout, stderr = _add_files_and_push( 'hg', tmpdir.strpath, - clone_url=rc_web_server.repo_clone_url('not-existing')) + clone_url=rcstack.repo_clone_url('not-existing')) assert 'HTTP Error 404: Not Found' in stderr - def test_ip_restriction_hg(self, rc_web_server, tmpdir): + def test_ip_restriction_hg(self, rcstack, tmpdir): user_model = UserModel() try: user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32') Session().commit() time.sleep(2) - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) assert 'abort: HTTP Error 403: Forbidden' in stderr finally: @@ -221,6 +220,6 @@ class TestVCSOperations(object): time.sleep(2) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'hg') diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_integrations_trigger.py b/rhodecode/tests/vcs_operations/test_vcs_operations_integrations_trigger.py index f9d918c3..c316fd2f 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_integrations_trigger.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_integrations_trigger.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -19,11 +18,6 @@ """ Test suite for making push/pull operations, on specially modified INI files - -.. important:: - - You must have git >= 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. """ import pytest @@ -37,32 +31,41 @@ from rhodecode.tests.vcs_operations.conftest import check_httpbin_connection connection_available = pytest.mark.skipif( not check_httpbin_connection(), reason="No outside internet connection available") +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", + "enable_webhook_push_integration" +) +class TestVCSOperationsOnIntegrationsTrigger(object): -@pytest.mark.usefixtures("baseapp", "enable_webhook_push_integration") -class TestVCSOperationsOnCustomIniConfig(object): + def test_push_with_webhook_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) - def test_push_with_webhook_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) + Command(tmpdir.strpath).execute('hg clone', clone_url, tmpdir.strpath) - Command('/tmp').execute('hg clone', clone_url, tmpdir.strpath) - - push_url = rc_web_server.repo_clone_url(HG_REPO) + push_url = rcstack.repo_clone_url(HG_REPO) _add_files_and_push('hg', tmpdir.strpath, clone_url=push_url) - rc_log = rc_web_server.get_rc_log() - assert 'ERROR' not in rc_log - assert "executing task TASK:<@task: rhodecode.integrations.types.webhook.post_to_webhook" in rc_log - assert "handling event repo-push with integration = 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. """ - import pytest from rhodecode.model.db import User, Repository from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel -from rhodecode.tests import ( - GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, - TEST_USER_REGULAR_PASS) -from rhodecode.tests.vcs_operations import ( - Command, _check_proper_clone, _check_proper_git_push, _add_files_and_push) +from rhodecode.tests import GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS +from rhodecode.tests.vcs_operations import Command, _check_proper_clone, _check_proper_git_push, _add_files_and_push -@pytest.fixture(scope="module") -def rc_web_server_config_modification(): - return [ - {'app:main': {'lock_ret_code': '423'}}, - ] +custom_code = [ + {'app:main': {'auth_ret_code': '423'}}, +] - -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperationsOnCustomIniConfig(object): - - def test_clone_and_create_lock_hg(self, rc_web_server, tmpdir): +@pytest.mark.parametrize('rcstack', custom_code, indirect=True) +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsOnLockingRepos(object): + def test_clone_and_create_lock_hg(self, rcstack, tmpdir): # enable locking r = Repository.get_by_repo_name(HG_REPO) r.enable_locking = True Session().add(r) Session().commit() # clone - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( - 'hg clone', clone_url, tmpdir.strpath) + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("hg clone", clone_url, tmpdir.strpath) # check if lock was made r = Repository.get_by_repo_name(HG_REPO) - assert r.locked[0] == User.get_by_username( - TEST_USER_ADMIN_LOGIN).user_id + assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id - def test_clone_and_create_lock_git(self, rc_web_server, tmpdir): + def test_clone_and_create_lock_git(self, rcstack, tmpdir): # enable locking r = Repository.get_by_repo_name(GIT_REPO) r.enable_locking = True Session().add(r) Session().commit() # clone - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( - 'git clone', clone_url, tmpdir.strpath) + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("git clone", clone_url, tmpdir.strpath) # check if lock was made r = Repository.get_by_repo_name(GIT_REPO) - assert r.locked[0] == User.get_by_username( - TEST_USER_ADMIN_LOGIN).user_id + assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id - def test_clone_after_repo_was_locked_hg(self, rc_web_server, tmpdir): + def test_clone_after_repo_was_locked_hg(self, rcstack, tmpdir): # lock repo r = Repository.get_by_repo_name(HG_REPO) Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # pull fails since repo is locked - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( - 'hg clone', clone_url, tmpdir.strpath) - msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`""" - % (HG_REPO, TEST_USER_ADMIN_LOGIN)) + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("hg clone", clone_url, tmpdir.strpath) + msg = f"abort: HTTP Error 423: Repository `{HG_REPO}` locked by user `{TEST_USER_ADMIN_LOGIN}`" assert msg in stderr - def test_clone_after_repo_was_locked_git(self, rc_web_server, tmpdir): + def test_clone_after_repo_was_locked_git(self, rcstack, tmpdir): # lock repo r = Repository.get_by_repo_name(GIT_REPO) Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # pull fails since repo is locked - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( - 'git clone', clone_url, tmpdir.strpath) + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("git clone", clone_url, tmpdir.strpath) - lock_msg = ( - 'remote: ERROR: Repository `vcs_test_git` locked by user ' + - '`test_admin`. Reason:`lock_auto`') + lock_msg = "remote: ERROR: Repository `vcs_test_git` locked by user `test_admin`. Reason:`lock_auto`" assert lock_msg in stderr - assert 'remote: Pre pull hook failed: aborting' in stderr - assert 'fatal: remote did not send all necessary objects' in stderr + assert "fatal: remote did not send all necessary objects" in stderr + assert "remote: Pre pull hook failed: aborting" in stderr - def test_push_on_locked_repo_by_other_user_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( - 'hg clone', clone_url, tmpdir.strpath) + def test_push_on_locked_repo_by_other_user_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("hg clone", clone_url, tmpdir.strpath) # lock repo r = Repository.get_by_repo_name(HG_REPO) # let this user actually push ! - RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, - perm='repository.write') + RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, perm="repository.write") Session().commit() Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # push fails repo is locked by other user ! - push_url = rc_web_server.repo_clone_url( - HG_REPO, - user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) - stdout, stderr = _add_files_and_push( - 'hg', tmpdir.strpath, clone_url=push_url) - msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`""" - % (HG_REPO, TEST_USER_ADMIN_LOGIN)) + push_url = rcstack.repo_clone_url(HG_REPO, user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) + stdout, stderr = _add_files_and_push("hg", tmpdir.strpath, clone_url=push_url) + msg = f"abort: HTTP Error 423: Repository `{HG_REPO}` locked by user `{TEST_USER_ADMIN_LOGIN}`" assert msg in stderr - def test_push_on_locked_repo_by_other_user_git( - self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( - 'git clone', clone_url, tmpdir.strpath) + def test_push_on_locked_repo_by_other_user_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("git clone", clone_url, tmpdir.strpath) # lock repo r = Repository.get_by_repo_name(GIT_REPO) # let this user actually push ! - RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, - perm='repository.write') + RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, perm="repository.write") Session().commit() Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # push fails repo is locked by other user! - push_url = rc_web_server.repo_clone_url( - GIT_REPO, - user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) - stdout, stderr = _add_files_and_push( - 'git', tmpdir.strpath, clone_url=push_url) + push_url = rcstack.repo_clone_url(GIT_REPO, user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) + stdout, stderr = _add_files_and_push("git", tmpdir.strpath, clone_url=push_url) - err = 'Repository `%s` locked by user `%s`' % ( - GIT_REPO, TEST_USER_ADMIN_LOGIN) + err = f"Repository `{GIT_REPO}` locked by user `{TEST_USER_ADMIN_LOGIN}`" # err = 'RPC failed; result=22, HTTP code = 423' assert err in stderr - def test_push_unlocks_repository_hg(self, rc_web_server, tmpdir): + def test_push_unlocks_repository_hg(self, rcstack, tmpdir): # enable locking r = Repository.get_by_repo_name(HG_REPO) r.enable_locking = True Session().add(r) Session().commit() - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( - 'hg clone', clone_url, tmpdir.strpath) - _check_proper_clone(stdout, stderr, 'hg') + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("hg clone", clone_url, tmpdir.strpath) + _check_proper_clone(stdout, stderr, "hg") # check for lock repo after clone r = Repository.get_by_repo_name(HG_REPO) @@ -177,16 +146,14 @@ class TestVCSOperationsOnCustomIniConfig(object): assert r.locked[0] == uid # push is ok and repo is now unlocked - stdout, stderr = _add_files_and_push( - 'hg', tmpdir.strpath, clone_url=clone_url) - assert ('remote: Released lock on repo `%s`' % HG_REPO) in stdout + stdout, stderr = _add_files_and_push("hg", tmpdir.strpath, clone_url=clone_url) + assert f"remote: Released lock on repo `{HG_REPO}`" in stdout # we need to cleanup the Session Here ! Session.remove() r = Repository.get_by_repo_name(HG_REPO) assert r.locked == [None, None, None] - def test_push_unlocks_repository_git(self, rc_web_server, tmpdir): - + def test_push_unlocks_repository_git(self, rcstack, tmpdir): # Note: Did a first debugging session. Seems that # Repository.get_locking_state is called twice. The second call # has the action "pull" and does not reset the lock. @@ -197,19 +164,16 @@ class TestVCSOperationsOnCustomIniConfig(object): Session().add(r) Session().commit() - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( - 'git clone', clone_url, tmpdir.strpath) - _check_proper_clone(stdout, stderr, 'git') + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("git clone", clone_url, tmpdir.strpath) + _check_proper_clone(stdout, stderr, "git") # check for lock repo after clone r = Repository.get_by_repo_name(GIT_REPO) - assert r.locked[0] == User.get_by_username( - TEST_USER_ADMIN_LOGIN).user_id + assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id # push is ok and repo is now unlocked - stdout, stderr = _add_files_and_push( - 'git', tmpdir.strpath, clone_url=clone_url) + stdout, stderr = _add_files_and_push("git", tmpdir.strpath, clone_url=clone_url) _check_proper_git_push(stdout, stderr) # assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_locking_custom_code.py b/rhodecode/tests/vcs_operations/test_vcs_operations_locking_custom_code.py index 820c71ea..9bddd51f 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_locking_custom_code.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_locking_custom_code.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -26,102 +25,83 @@ Test suite for making push/pull operations, on specially modified INI files to redirect things to stderr instead of stdout. """ - import pytest from rhodecode.model.db import User, Repository from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel -from rhodecode.tests import ( - GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, - TEST_USER_REGULAR_PASS) +from rhodecode.tests import GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS from rhodecode.tests.vcs_operations import Command, _add_files_and_push +custom_code = [ + {'app:main': {'lock_ret_code': '400'}}, +] -@pytest.fixture(scope="module") -def rc_web_server_config_modification(): - return [ - {'app:main': {'lock_ret_code': '400'}}, - ] - - -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperationsOnCustomIniConfig(object): - - def test_clone_after_repo_was_locked_hg(self, rc_web_server, tmpdir): +@pytest.mark.parametrize('rcstack', custom_code, indirect=True) +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsOnCustomLockingCode(object): + def test_clone_after_repo_was_locked_hg(self, rcstack, tmpdir): # lock repo r = Repository.get_by_repo_name(HG_REPO) Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # pull fails since repo is locked - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( - 'hg clone', clone_url, tmpdir.strpath) - msg = ("""abort: HTTP Error 400: Repository `%s` locked by user `%s`""" - % (HG_REPO, TEST_USER_ADMIN_LOGIN)) + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("hg clone", clone_url, tmpdir.strpath) + msg = f"abort: HTTP Error 400: Repository `{HG_REPO}` locked by user `{TEST_USER_ADMIN_LOGIN}`" assert msg in stderr - def test_clone_after_repo_was_locked_git(self, rc_web_server, tmpdir): + def test_clone_after_repo_was_locked_git(self, rcstack, tmpdir): # lock repo r = Repository.get_by_repo_name(GIT_REPO) Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # pull fails since repo is locked - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( - 'git clone', clone_url, tmpdir.strpath) + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("git clone", clone_url, tmpdir.strpath) - lock_msg = ( - 'remote: ERROR: Repository `vcs_test_git` locked by user ' + - '`test_admin`. Reason:`lock_auto`') + lock_msg = "remote: ERROR: Repository `vcs_test_git` locked by user `test_admin`. Reason:`lock_auto`" assert lock_msg in stderr - assert 'remote: Pre pull hook failed: aborting' in stderr - assert 'fatal: remote did not send all necessary objects' in stderr + assert "remote: Pre pull hook failed: aborting" in stderr + assert "fatal: remote did not send all necessary objects" in stderr - def test_push_on_locked_repo_by_other_user_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( - 'hg clone', clone_url, tmpdir.strpath) + def test_push_on_locked_repo_by_other_user_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("hg clone", clone_url, tmpdir.strpath) # lock repo r = Repository.get_by_repo_name(HG_REPO) # let this user actually push ! - RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, - perm='repository.write') + RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, perm="repository.write") Session().commit() Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # push fails repo is locked by other user ! - push_url = rc_web_server.repo_clone_url( - HG_REPO, - user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) - stdout, stderr = _add_files_and_push( - 'hg', tmpdir.strpath, clone_url=push_url) - msg = ("""abort: HTTP Error 400: Repository `%s` locked by user `%s`""" - % (HG_REPO, TEST_USER_ADMIN_LOGIN)) + push_url = rcstack.repo_clone_url(HG_REPO, user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) + stdout, stderr = _add_files_and_push("hg", tmpdir.strpath, clone_url=push_url) + msg = f"abort: HTTP Error 400: Repository `{HG_REPO}` locked by user `{TEST_USER_ADMIN_LOGIN}`" + assert msg in stderr - def test_push_on_locked_repo_by_other_user_git( - self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( - 'git clone', clone_url, tmpdir.strpath) + def test_push_on_locked_repo_by_other_user_git(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute("git clone", clone_url, tmpdir.strpath) # lock repo r = Repository.get_by_repo_name(GIT_REPO) # let this user actually push ! - RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, - perm='repository.write') + RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, perm="repository.write") Session().commit() Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) # push fails repo is locked by other user! - push_url = rc_web_server.repo_clone_url( - GIT_REPO, - user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) - stdout, stderr = _add_files_and_push( - 'git', tmpdir.strpath, clone_url=push_url) + push_url = rcstack.repo_clone_url(GIT_REPO, user=TEST_USER_REGULAR_LOGIN, passwd=TEST_USER_REGULAR_PASS) + stdout, stderr = _add_files_and_push("git", tmpdir.strpath, clone_url=push_url) - err = 'Repository `%s` locked by user `%s`' % ( - GIT_REPO, TEST_USER_ADMIN_LOGIN) + err = f"Repository `{GIT_REPO}` locked by user `{TEST_USER_ADMIN_LOGIN}`" assert err in stderr diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_new_branch_push.py b/rhodecode/tests/vcs_operations/test_vcs_operations_new_branch_push.py index 1d09112c..fa469b0a 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_new_branch_push.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_new_branch_push.py @@ -26,12 +26,16 @@ from rhodecode.tests.vcs_operations import ( Command, _check_proper_hg_push, _check_proper_git_push, _add_files_and_push) -@pytest.mark.usefixtures("disable_anonymous_user") -class TestVCSOperations(object): +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", +) +class TestVCSOperationsNewBranchPush(object): - def test_push_new_branch_hg(self, rc_web_server, tmpdir, user_util): + def test_push_new_branch_hg(self, rcstack, tmpdir, user_util): repo = user_util.create_repo(repo_type='hg') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'hg clone', clone_url, tmpdir.strpath) @@ -48,9 +52,9 @@ class TestVCSOperations(object): _check_proper_hg_push(stdout, stderr) - def test_push_new_branch_git(self, rc_web_server, tmpdir, user_util): + def test_push_new_branch_git(self, rcstack, tmpdir, user_util): repo = user_util.create_repo(repo_type='git') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'git clone', clone_url, tmpdir.strpath) @@ -67,13 +71,13 @@ class TestVCSOperations(object): _check_proper_git_push(stdout, stderr, branch='dev') def test_push_new_branch_hg_with_branch_permissions_no_force_push( - self, rc_web_server, tmpdir, user_util, branch_permission_setter): + self, rcstack, tmpdir, user_util, branch_permission_setter): repo = user_util.create_repo(repo_type='hg') repo_name = repo.repo_name username = TEST_USER_ADMIN_LOGIN branch_permission_setter(repo_name, username, permission='branch.push') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'hg clone', clone_url, tmpdir.strpath) @@ -91,13 +95,13 @@ class TestVCSOperations(object): _check_proper_hg_push(stdout, stderr) def test_push_new_branch_git_with_branch_permissions_no_force_push( - self, rc_web_server, tmpdir, user_util, branch_permission_setter): + self, rcstack, tmpdir, user_util, branch_permission_setter): repo = user_util.create_repo(repo_type='git') repo_name = repo.repo_name username = TEST_USER_ADMIN_LOGIN branch_permission_setter(repo_name, username, permission='branch.push') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) Command(os.path.dirname(tmpdir.strpath)).execute( 'git clone', clone_url, tmpdir.strpath) diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_rcextensions_push.py b/rhodecode/tests/vcs_operations/test_vcs_operations_rcextensions_push.py new file mode 100644 index 00000000..899261b8 --- /dev/null +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_rcextensions_push.py @@ -0,0 +1,81 @@ +# Copyright (C) 2010-2023 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 os +import pytest + +from rhodecode.tests.fixtures.rcextensions_fixtures import store_rcextensions +from rhodecode.tests.vcs_operations import ( + Command, _check_proper_hg_push, _check_proper_git_push, + _add_files_and_push) + + +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsWithRCExtensions(object): + + def test_push_when_rcextensions_fail_hg(self, rcstack, tmpdir, user_util, rcextensions, rcextensions_modification): + repo = user_util.create_repo(repo_type='hg') + clone_url = rcstack.repo_clone_url(repo.repo_name) + Command(os.path.dirname(tmpdir.strpath)).execute( + 'hg clone', clone_url, tmpdir.strpath) + + stdout, stderr = _add_files_and_push( + 'hg', tmpdir.strpath, clone_url=clone_url) + _check_proper_hg_push(stdout, stderr) + + mods = [ + ('_pre_push_hook', + """ + return HookResponse(1, 'HOOK_FAIL_TEST_HG') + """) + ] + rcstack_location = os.path.dirname(rcstack.config_file) + with rcextensions_modification(rcstack_location, mods): + stdout, stderr = _add_files_and_push( + 'hg', tmpdir.strpath, clone_url=clone_url) + assert 'HOOK_FAIL_TEST_HG' in stdout + + def test_push_when_rcextensions_fail_git(self, rcstack, tmpdir, user_util, rcextensions, rcextensions_modification): + repo = user_util.create_repo(repo_type='git') + clone_url = rcstack.repo_clone_url(repo.repo_name) + Command(os.path.dirname(tmpdir.strpath)).execute( + 'git clone', clone_url, tmpdir.strpath) + + stdout, stderr = _add_files_and_push( + 'git', tmpdir.strpath, clone_url=clone_url) + _check_proper_git_push(stdout, stderr) + + mods = [ + ('_pre_push_hook', + """ + return HookResponse(1, 'HOOK_FAIL_TEST_GIT') + """) + ] + + rcstack_location = os.path.dirname(rcstack.config_file) + with rcextensions_modification(rcstack_location, mods): + stdout, stderr = _add_files_and_push( + 'git', tmpdir.strpath, clone_url=clone_url) + assert 'HOOK_FAIL_TEST_GIT' in stderr + diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_special.py b/rhodecode/tests/vcs_operations/test_vcs_operations_special.py index b28e9e1f..d5bff9a4 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_special.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_special.py @@ -32,13 +32,16 @@ from rhodecode.tests.vcs_operations import ( _add_files_and_push) -@pytest.mark.usefixtures("disable_locking") +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_locking", +) class TestVCSOperationsSpecial(object): - def test_git_sets_default_branch_if_not_master( - self, backend_git, tmpdir, rc_web_server): - empty_repo = backend_git.create_repo() - clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name) + def test_git_sets_default_branch_if_not_master(self, vcs_backend_git, tmpdir, rcstack): + empty_repo = vcs_backend_git.create_repo() + clone_url = rcstack.repo_clone_url(empty_repo.repo_name) cmd = Command(tmpdir.strpath) cmd.execute('git clone', clone_url) @@ -63,25 +66,24 @@ class TestVCSOperationsSpecial(object): # Doing an explicit commit in order to get latest user logs on MySQL Session().commit() - def test_git_fetches_from_remote_repository_with_annotated_tags( - self, backend_git, rc_web_server): + def test_git_fetches_from_remote_repository_with_annotated_tags(self, vcs_backend_git, rcstack): # Note: This is a test specific to the git backend. It checks the # integration of fetching from a remote repository which contains # annotated tags. # Dulwich shows this specific behavior only when # operating against a remote repository. - source_repo = backend_git['annotated-tag'] - target_vcs_repo = backend_git.create_repo().scm_instance() - target_vcs_repo.fetch(rc_web_server.repo_clone_url(source_repo.repo_name)) + source_repo = vcs_backend_git['annotated-tag'] + target_vcs_repo = vcs_backend_git.create_repo().scm_instance() + target_vcs_repo.fetch(rcstack.repo_clone_url(source_repo.repo_name)) - def test_git_push_shows_pull_request_refs(self, backend_git, rc_web_server, tmpdir): + def test_git_push_shows_pull_request_refs(self, vcs_backend_git, rcstack, tmpdir): """ test if remote info about refs is visible """ - empty_repo = backend_git.create_repo() + empty_repo = vcs_backend_git.create_repo() - clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name) + clone_url = rcstack.repo_clone_url(empty_repo.repo_name) cmd = Command(tmpdir.strpath) cmd.execute('git clone', clone_url) @@ -97,7 +99,7 @@ class TestVCSOperationsSpecial(object): stdout, stderr = repo_cmd.execute('git push --verbose origin master') _check_proper_git_push(stdout, stderr, branch='master') - ref = f'{rc_web_server.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=master' + ref = f'{rcstack.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=master' assert f'remote: RhodeCode: open pull request link: {ref}' in stderr assert 'remote: RhodeCode: push completed' in stderr @@ -129,14 +131,14 @@ class TestVCSOperationsSpecial(object): stdout, stderr = repo_cmd.execute('git push --verbose origin feature') _check_proper_git_push(stdout, stderr, branch='feature') - ref = f'{rc_web_server.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=feature' + ref = f'{rcstack.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=feature' assert f'remote: RhodeCode: open pull request link: {ref}' in stderr assert 'remote: RhodeCode: push completed' in stderr - def test_hg_push_shows_pull_request_refs(self, backend_hg, rc_web_server, tmpdir): - empty_repo = backend_hg.create_repo() + def test_hg_push_shows_pull_request_refs(self, vcs_backend_hg, rcstack, tmpdir): + empty_repo = vcs_backend_hg.create_repo() - clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name) + clone_url = rcstack.repo_clone_url(empty_repo.repo_name) cmd = Command(tmpdir.strpath) cmd.execute('hg clone', clone_url) @@ -154,7 +156,7 @@ class TestVCSOperationsSpecial(object): stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url) _check_proper_hg_push(stdout, stderr, branch='default') - ref = f'{rc_web_server.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=default' + ref = f'{rcstack.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=default' assert f'remote: RhodeCode: open pull request link: {ref}' in stdout assert 'remote: RhodeCode: push completed' in stdout @@ -189,14 +191,14 @@ class TestVCSOperationsSpecial(object): stdout, stderr = repo_cmd.execute('hg push --new-branch --verbose', clone_url) _check_proper_hg_push(stdout, stderr, branch='feature') - ref = f'{rc_web_server.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=feature' + ref = f'{rcstack.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=feature' assert f'remote: RhodeCode: open pull request link: {ref}' in stdout assert 'remote: RhodeCode: push completed' in stdout - def test_hg_push_shows_pull_request_refs_book(self, backend_hg, rc_web_server, tmpdir): - empty_repo = backend_hg.create_repo() + def test_hg_push_shows_pull_request_refs_book(self, vcs_backend_hg, rcstack, tmpdir): + empty_repo = vcs_backend_hg.create_repo() - clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name) + clone_url = rcstack.repo_clone_url(empty_repo.repo_name) cmd = Command(tmpdir.strpath) cmd.execute('hg clone', clone_url) @@ -214,7 +216,7 @@ class TestVCSOperationsSpecial(object): stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url) _check_proper_hg_push(stdout, stderr, branch='default') - ref = f'{rc_web_server.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=default' + ref = f'{rcstack.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=default' assert f'remote: RhodeCode: open pull request link: {ref}' in stdout assert 'remote: RhodeCode: push completed' in stdout @@ -232,23 +234,23 @@ class TestVCSOperationsSpecial(object): stdout, stderr = repo_cmd.execute('hg push -B feature2 --verbose', clone_url) _check_proper_hg_push(stdout, stderr, branch='default') - ref = f'{rc_web_server.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=default' + ref = f'{rcstack.host_url()}/{empty_repo.repo_name}/pull-request/new?branch=default' assert f'remote: RhodeCode: open pull request link: {ref}' in stdout - ref = f'{rc_web_server.host_url()}/{empty_repo.repo_name}/pull-request/new?bookmark=feature2' + ref = f'{rcstack.host_url()}/{empty_repo.repo_name}/pull-request/new?bookmark=feature2' assert f'remote: RhodeCode: open pull request link: {ref}' in stdout assert 'remote: RhodeCode: push completed' in stdout assert 'exporting bookmark feature2' in stdout - def test_push_is_forbidden_on_archived_repo_hg(self, backend_hg, rc_web_server, tmpdir): - empty_repo = backend_hg.create_repo() + def test_push_is_forbidden_on_archived_repo_hg(self, vcs_backend_hg, rcstack, tmpdir): + empty_repo = vcs_backend_hg.create_repo() repo_name = empty_repo.repo_name repo = Repository.get_by_repo_name(repo_name) repo.archived = True Session().commit() - clone_url = rc_web_server.repo_clone_url(repo_name) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(repo_name) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) stdout, stderr = _add_files_and_push( @@ -256,16 +258,16 @@ class TestVCSOperationsSpecial(object): assert 'abort: HTTP Error 403: Forbidden' in stderr - def test_push_is_forbidden_on_archived_repo_git(self, backend_git, rc_web_server, tmpdir): - empty_repo = backend_git.create_repo() + def test_push_is_forbidden_on_archived_repo_git(self, vcs_backend_git, rcstack, tmpdir): + empty_repo = vcs_backend_git.create_repo() repo_name = empty_repo.repo_name repo = Repository.get_by_repo_name(repo_name) repo.archived = True Session().commit() - clone_url = rc_web_server.repo_clone_url(repo_name) - stdout, stderr = Command('/tmp').execute( + clone_url = rcstack.repo_clone_url(repo_name) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) stdout, stderr = _add_files_and_push( diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_svn.py b/rhodecode/tests/vcs_operations/test_vcs_operations_svn.py index 0b60ebfc..4dc7a98a 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_svn.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_svn.py @@ -49,97 +49,110 @@ def get_cli_flags(username, password): return flags, auth -@pytest.mark.usefixtures("disable_locking", "disable_anonymous_user") -class TestVCSOperations(object): +@pytest.mark.usefixtures( + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", +) +class TestVCSOperationsSVN(object): - def test_clone_svn_repo_by_admin(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(SVN_REPO) - username, password = rc_web_server.repo_clone_credentials() + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_clone_svn_repo_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(SVN_REPO) + username, password = rcstack.repo_clone_credentials() - cmd = Command('/tmp') + cmd = Command(tmpdir.strpath) flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'svn') cmd.assert_returncode_success() - def test_clone_svn_repo_by_id_by_admin(self, rc_web_server, tmpdir): + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_clone_svn_repo_by_id_by_admin(self, rcstack, tmpdir): repo_id = Repository.get_by_repo_name(SVN_REPO).repo_id - username, password = rc_web_server.repo_clone_credentials() + username, password = rcstack.repo_clone_credentials() - clone_url = rc_web_server.repo_clone_url('_%s' % repo_id) - cmd = Command('/tmp') + clone_url = rcstack.repo_clone_url('_%s' % repo_id) + cmd = Command(tmpdir.strpath) flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'svn') cmd.assert_returncode_success() - def test_clone_svn_repo_with_group_by_admin(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(SVN_REPO_WITH_GROUP) - username, password = rc_web_server.repo_clone_credentials() + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_clone_svn_repo_with_group_by_admin(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(SVN_REPO_WITH_GROUP) + username, password = rcstack.repo_clone_credentials() flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) _check_proper_clone(stdout, stderr, 'svn') - cmd.assert_returncode_success() + rcstack.assert_returncode_success() - def test_clone_wrong_credentials_svn(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(SVN_REPO) - username, password = rc_web_server.repo_clone_credentials() + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_clone_wrong_credentials_svn(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(SVN_REPO) + username, password = rcstack.repo_clone_credentials() password = 'bad-password' flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) assert 'fatal: Authentication failed' in stderr - def test_clone_svn_with_slashes(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url('//' + SVN_REPO) + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_clone_svn_with_slashes(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url('//' + SVN_REPO) username, password = '', '' flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url) assert 'not found' in stderr + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') def test_clone_existing_path_svn_not_in_database( - self, rc_web_server, tmpdir, fs_repo_only): + self, rcstack, tmpdir, fs_repo_only): db_name = fs_repo_only('not-in-db-git', repo_type='git') - clone_url = rc_web_server.repo_clone_url(db_name) + clone_url = rcstack.repo_clone_url(db_name) username, password = '', '' flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) assert 'not found' in stderr + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') def test_clone_existing_path_svn_not_in_database_different_scm( - self, rc_web_server, tmpdir, fs_repo_only): + self, rcstack, tmpdir, fs_repo_only): db_name = fs_repo_only('not-in-db-hg', repo_type='hg') - clone_url = rc_web_server.repo_clone_url(db_name) + clone_url = rcstack.repo_clone_url(db_name) username, password = '', '' flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) assert 'not found' in stderr - def test_clone_non_existing_store_path_svn(self, rc_web_server, tmpdir, user_util): + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_clone_non_existing_store_path_svn(self, rcstack, tmpdir, user_util): repo = user_util.create_repo(repo_type='git') - clone_url = rc_web_server.repo_clone_url(repo.repo_name) + clone_url = rcstack.repo_clone_url(repo.repo_name) # Damage repo by removing it's folder RepoModel()._delete_filesystem_repo(repo) @@ -147,16 +160,17 @@ class TestVCSOperations(object): username, password = '', '' flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) assert 'not found' in stderr - def test_push_new_file_svn(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(SVN_REPO) + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_push_new_file_svn(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(SVN_REPO) username, password = '', '' flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) # commit some stuff into this repo @@ -165,37 +179,40 @@ class TestVCSOperations(object): _check_proper_svn_push(stdout, stderr) - def test_push_wrong_credentials_svn(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(SVN_REPO) + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_push_wrong_credentials_svn(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(SVN_REPO) - username, password = rc_web_server.repo_clone_credentials() + username, password = rcstack.repo_clone_credentials() flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) - push_url = rc_web_server.repo_clone_url( + push_url = rcstack.repo_clone_url( SVN_REPO, user='bad', passwd='name') stdout, stderr = _add_files_and_push( 'svn', tmpdir.strpath, clone_url=push_url, username=username, password=password) assert 'fatal: Authentication failed' in stderr - def test_push_back_to_wrong_url_svn(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(SVN_REPO) + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_push_back_to_wrong_url_svn(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(SVN_REPO) username, password = '', '' flags, auth = get_cli_flags(username, password) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) stdout, stderr = _add_files_and_push( 'svn', tmpdir.strpath, - clone_url=rc_web_server.repo_clone_url('not-existing'), username=username, password=password) + clone_url=rcstack.repo_clone_url('not-existing'), username=username, password=password) assert 'not found' in stderr - def test_ip_restriction_svn(self, rc_web_server, tmpdir): + @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') + def test_ip_restriction_svn(self, rcstack, tmpdir): user_model = UserModel() username, password = '', '' flags, auth = get_cli_flags(username, password) @@ -204,9 +221,9 @@ class TestVCSOperations(object): user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32') Session().commit() time.sleep(2) - clone_url = rc_web_server.repo_clone_url(SVN_REPO) + clone_url = rcstack.repo_clone_url(SVN_REPO) - stdout, stderr = Command('/tmp').execute( + stdout, stderr = Command(tmpdir.strpath).execute( f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) msg = "The requested URL returned error: 403" assert msg in stderr @@ -218,7 +235,7 @@ class TestVCSOperations(object): time.sleep(2) - cmd = Command('/tmp') + cmd = Command(tmpdir.strpath) stdout, stderr = cmd.execute(f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) cmd.assert_returncode_success() _check_proper_clone(stdout, stderr, 'svn') diff --git a/rhodecode/tests/vcs_operations/test_vcs_operations_tag_push.py b/rhodecode/tests/vcs_operations/test_vcs_operations_tag_push.py index eed4aefd..ecee4f9c 100644 --- a/rhodecode/tests/vcs_operations/test_vcs_operations_tag_push.py +++ b/rhodecode/tests/vcs_operations/test_vcs_operations_tag_push.py @@ -1,4 +1,3 @@ - # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify @@ -19,11 +18,6 @@ """ Test suite for making push/pull operations, on specially modified INI files - -.. important:: - - You must have git >= 1.8.5 for tests to work fine. With 68b939b git started - to redirect things to stderr instead of stdout. """ import pytest @@ -37,54 +31,59 @@ connection_available = pytest.mark.skipif( not check_httpbin_connection(), reason="No outside internet connection available") + @pytest.mark.usefixtures( - "disable_locking", "disable_anonymous_user", - "enable_webhook_push_integration") -class TestVCSOperationsOnCustomIniConfig(object): + "init_pyramid_app", + "repo_group_repos", + "disable_anonymous_user", + "disable_locking", + "enable_webhook_push_integration" +) +class TestVCSOperationsTagPush(object): @connection_available - def test_push_tag_with_commit_hg(self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(HG_REPO) - stdout, stderr = Command('/tmp').execute( + def test_push_tag_with_commit_hg(self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(HG_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'hg clone', clone_url, tmpdir.strpath) - push_url = rc_web_server.repo_clone_url(HG_REPO) + push_url = rcstack.repo_clone_url(HG_REPO) _add_files_and_push( 'hg', tmpdir.strpath, clone_url=push_url, tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}]) - rc_log = rc_web_server.get_rc_log() - assert 'ERROR' not in rc_log - assert "{'name': 'v1.0.0'," in rc_log + celery_log = open(rcstack.rcstack_data.celery_log).read() + assert 'ERROR' not in celery_log + assert "{'name': 'v1.0.0'," in celery_log @connection_available def test_push_tag_with_commit_git( - self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) - push_url = rc_web_server.repo_clone_url(GIT_REPO) + push_url = rcstack.repo_clone_url(GIT_REPO) _add_files_and_push( 'git', tmpdir.strpath, clone_url=push_url, tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}]) - rc_log = rc_web_server.get_rc_log() - assert 'ERROR' not in rc_log - assert "{'name': 'v1.0.0'," in rc_log + celery_log = open(rcstack.rcstack_data.celery_log).read() + assert 'ERROR' not in celery_log + assert "{'name': 'v1.0.0'," in celery_log @connection_available def test_push_tag_with_no_commit_git( - self, rc_web_server, tmpdir): - clone_url = rc_web_server.repo_clone_url(GIT_REPO) - stdout, stderr = Command('/tmp').execute( + self, rcstack, tmpdir): + clone_url = rcstack.repo_clone_url(GIT_REPO) + stdout, stderr = Command(tmpdir.strpath).execute( 'git clone', clone_url, tmpdir.strpath) - push_url = rc_web_server.repo_clone_url(GIT_REPO) + push_url = rcstack.repo_clone_url(GIT_REPO) _add_files_and_push( 'git', tmpdir.strpath, clone_url=push_url, tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}]) - rc_log = rc_web_server.get_rc_log() - assert 'ERROR' not in rc_log - assert "{'name': 'v1.0.0'," in rc_log + celery_log = open(rcstack.rcstack_data.celery_log).read() + assert 'ERROR' not in celery_log + assert "{'name': 'v1.0.0'," in celery_log diff --git a/rhodecode/tests/vcsserver_http.ini b/rhodecode/tests/vcsserver_http.ini index 62c09696..f72a785d 100644 --- a/rhodecode/tests/vcsserver_http.ini +++ b/rhodecode/tests/vcsserver_http.ini @@ -60,7 +60,7 @@ vcs.svn.redis_conn = redis://redis:6379/0 ; Default cache dir for caches. Putting this into a ramdisk can boost performance. ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space -cache_dir = %(here)s/data +cache_dir = %(here)s/.rc-vcs-test-data ; *************************************** ; `repo_object` cache, default file based @@ -141,7 +141,7 @@ level = NOTSET handlers = console [logger_vcsserver] -level = INFO +level = DEBUG handlers = qualname = vcsserver propagate = 1