##// END OF EJS Templates
tests: fixes for gunicorn tests workers
marcink -
r2455:82863912 default
parent child Browse files
Show More
@@ -1,271 +1,271 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 py.test config for test suite for making push/pull operations.
22 py.test config for test suite for making push/pull operations.
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 import ConfigParser
30 import ConfigParser
31 import os
31 import os
32 import subprocess32
32 import subprocess32
33 import tempfile
33 import tempfile
34 import textwrap
34 import textwrap
35 import pytest
35 import pytest
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.model.db import Repository
38 from rhodecode.model.db import Repository
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.settings import SettingsModel
41 from rhodecode.tests import (
41 from rhodecode.tests import (
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
43 from rhodecode.tests.fixture import Fixture
43 from rhodecode.tests.fixture import Fixture
44 from rhodecode.tests.utils import is_url_reachable, wait_for_url
44 from rhodecode.tests.utils import is_url_reachable, wait_for_url
45
45
46 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
46 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
47 REPO_GROUP = 'a_repo_group'
47 REPO_GROUP = 'a_repo_group'
48 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
48 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
49 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
49 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
50
50
51
51
52 def assert_no_running_instance(url):
52 def assert_no_running_instance(url):
53 if is_url_reachable(url):
53 if is_url_reachable(url):
54 print("Hint: Usually this means another instance of Enterprise "
54 print("Hint: Usually this means another instance of Enterprise "
55 "is running in the background.")
55 "is running in the background.")
56 pytest.fail(
56 pytest.fail(
57 "Port is not free at %s, cannot start web interface" % url)
57 "Port is not free at %s, cannot start web interface" % url)
58
58
59
59
60 def get_port(pyramid_config):
60 def get_port(pyramid_config):
61 config = ConfigParser.ConfigParser()
61 config = ConfigParser.ConfigParser()
62 config.read(pyramid_config)
62 config.read(pyramid_config)
63 return config.get('server:main', 'port')
63 return config.get('server:main', 'port')
64
64
65
65
66 def get_host_url(pyramid_config):
66 def get_host_url(pyramid_config):
67 """Construct the host url using the port in the test configuration."""
67 """Construct the host url using the port in the test configuration."""
68 return '127.0.0.1:%s' % get_port(pyramid_config)
68 return '127.0.0.1:%s' % get_port(pyramid_config)
69
69
70
70
71 class RcWebServer(object):
71 class RcWebServer(object):
72 """
72 """
73 Represents a running RCE web server used as a test fixture.
73 Represents a running RCE web server used as a test fixture.
74 """
74 """
75 def __init__(self, pyramid_config, log_file):
75 def __init__(self, pyramid_config, log_file):
76 self.pyramid_config = pyramid_config
76 self.pyramid_config = pyramid_config
77 self.log_file = log_file
77 self.log_file = log_file
78
78
79 def repo_clone_url(self, repo_name, **kwargs):
79 def repo_clone_url(self, repo_name, **kwargs):
80 params = {
80 params = {
81 'user': TEST_USER_ADMIN_LOGIN,
81 'user': TEST_USER_ADMIN_LOGIN,
82 'passwd': TEST_USER_ADMIN_PASS,
82 'passwd': TEST_USER_ADMIN_PASS,
83 'host': get_host_url(self.pyramid_config),
83 'host': get_host_url(self.pyramid_config),
84 'cloned_repo': repo_name,
84 'cloned_repo': repo_name,
85 }
85 }
86 params.update(**kwargs)
86 params.update(**kwargs)
87 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
87 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
88 return _url
88 return _url
89
89
90 def host_url(self):
90 def host_url(self):
91 return 'http://' + get_host_url(self.pyramid_config)
91 return 'http://' + get_host_url(self.pyramid_config)
92
92
93 def get_rc_log(self):
93 def get_rc_log(self):
94 with open(self.log_file) as f:
94 with open(self.log_file) as f:
95 return f.read()
95 return f.read()
96
96
97
97
98 @pytest.fixture(scope="module")
98 @pytest.fixture(scope="module")
99 def rcextensions(request, baseapp, tmpdir_factory):
99 def rcextensions(request, baseapp, tmpdir_factory):
100 """
100 """
101 Installs a testing rcextensions pack to ensure they work as expected.
101 Installs a testing rcextensions pack to ensure they work as expected.
102 """
102 """
103 init_content = textwrap.dedent("""
103 init_content = textwrap.dedent("""
104 # Forward import the example rcextensions to make it
104 # Forward import the example rcextensions to make it
105 # active for our tests.
105 # active for our tests.
106 from rhodecode.tests.other.example_rcextensions import *
106 from rhodecode.tests.other.example_rcextensions import *
107 """)
107 """)
108
108
109 # Note: rcextensions are looked up based on the path of the ini file
109 # Note: rcextensions are looked up based on the path of the ini file
110 root_path = tmpdir_factory.getbasetemp()
110 root_path = tmpdir_factory.getbasetemp()
111 rcextensions_path = root_path.join('rcextensions')
111 rcextensions_path = root_path.join('rcextensions')
112 init_path = rcextensions_path.join('__init__.py')
112 init_path = rcextensions_path.join('__init__.py')
113
113
114 if rcextensions_path.check():
114 if rcextensions_path.check():
115 pytest.fail(
115 pytest.fail(
116 "Path for rcextensions already exists, please clean up before "
116 "Path for rcextensions already exists, please clean up before "
117 "test run this path: %s" % (rcextensions_path, ))
117 "test run this path: %s" % (rcextensions_path, ))
118 return
118 return
119
119
120 request.addfinalizer(rcextensions_path.remove)
120 request.addfinalizer(rcextensions_path.remove)
121 init_path.write_binary(init_content, ensure=True)
121 init_path.write_binary(init_content, ensure=True)
122
122
123
123
124 @pytest.fixture(scope="module")
124 @pytest.fixture(scope="module")
125 def repos(request, baseapp):
125 def repos(request, baseapp):
126 """Create a copy of each test repo in a repo group."""
126 """Create a copy of each test repo in a repo group."""
127 fixture = Fixture()
127 fixture = Fixture()
128 repo_group = fixture.create_repo_group(REPO_GROUP)
128 repo_group = fixture.create_repo_group(REPO_GROUP)
129 repo_group_id = repo_group.group_id
129 repo_group_id = repo_group.group_id
130 fixture.create_fork(HG_REPO, HG_REPO,
130 fixture.create_fork(HG_REPO, HG_REPO,
131 repo_name_full=HG_REPO_WITH_GROUP,
131 repo_name_full=HG_REPO_WITH_GROUP,
132 repo_group=repo_group_id)
132 repo_group=repo_group_id)
133 fixture.create_fork(GIT_REPO, GIT_REPO,
133 fixture.create_fork(GIT_REPO, GIT_REPO,
134 repo_name_full=GIT_REPO_WITH_GROUP,
134 repo_name_full=GIT_REPO_WITH_GROUP,
135 repo_group=repo_group_id)
135 repo_group=repo_group_id)
136
136
137 @request.addfinalizer
137 @request.addfinalizer
138 def cleanup():
138 def cleanup():
139 fixture.destroy_repo(HG_REPO_WITH_GROUP)
139 fixture.destroy_repo(HG_REPO_WITH_GROUP)
140 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
140 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
141 fixture.destroy_repo_group(repo_group_id)
141 fixture.destroy_repo_group(repo_group_id)
142
142
143
143
144 @pytest.fixture(scope="module")
144 @pytest.fixture(scope="module")
145 def rc_web_server_config(testini_factory):
145 def rc_web_server_config(testini_factory):
146 """
146 """
147 Configuration file used for the fixture `rc_web_server`.
147 Configuration file used for the fixture `rc_web_server`.
148 """
148 """
149 CUSTOM_PARAMS = [
149 CUSTOM_PARAMS = [
150 {'handler_console': {'level': 'DEBUG'}},
150 {'handler_console': {'level': 'DEBUG'}},
151 ]
151 ]
152 return testini_factory(CUSTOM_PARAMS)
152 return testini_factory(CUSTOM_PARAMS)
153
153
154
154
155 @pytest.fixture(scope="module")
155 @pytest.fixture(scope="module")
156 def rc_web_server(
156 def rc_web_server(
157 request, baseapp, rc_web_server_config, repos, rcextensions):
157 request, baseapp, rc_web_server_config, repos, rcextensions):
158 """
158 """
159 Run the web server as a subprocess.
159 Run the web server as a subprocess.
160
160
161 Since we have already a running vcsserver, this is not spawned again.
161 Since we have already a running vcsserver, this is not spawned again.
162 """
162 """
163 env = os.environ.copy()
163 env = os.environ.copy()
164 env['RC_NO_TMP_PATH'] = '1'
164 env['RC_NO_TMP_PATH'] = '1'
165
165
166 rc_log = list(RC_LOG.partition('.log'))
166 rc_log = list(RC_LOG.partition('.log'))
167 rc_log.insert(1, get_port(rc_web_server_config))
167 rc_log.insert(1, get_port(rc_web_server_config))
168 rc_log = ''.join(rc_log)
168 rc_log = ''.join(rc_log)
169
169
170 server_out = open(rc_log, 'w')
170 server_out = open(rc_log, 'w')
171
171
172 host_url = 'http://' + get_host_url(rc_web_server_config)
172 host_url = 'http://' + get_host_url(rc_web_server_config)
173 assert_no_running_instance(host_url)
173 assert_no_running_instance(host_url)
174 command = ['gunicorn', '--worker-class', 'gevent', '--paste', rc_web_server_config]
174 command = ['gunicorn', '--worker-class', 'gevent', '--paste', rc_web_server_config]
175
175
176 print('Starting rhodecode server: {}'.format(host_url))
176 print('rhodecode-web starting at: {}'.format(host_url))
177 print('Command: {}'.format(command))
177 print('rhodecode-web command: {}'.format(command))
178 print('Logfile: {}'.format(rc_log))
178 print('rhodecode-web logfile: {}'.format(rc_log))
179
179
180 proc = subprocess32.Popen(
180 proc = subprocess32.Popen(
181 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
181 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
182
182
183 wait_for_url(host_url, timeout=30)
183 wait_for_url(host_url, timeout=30)
184
184
185 @request.addfinalizer
185 @request.addfinalizer
186 def stop_web_server():
186 def stop_web_server():
187 # TODO: Find out how to integrate with the reporting of py.test to
187 # TODO: Find out how to integrate with the reporting of py.test to
188 # make this information available.
188 # make this information available.
189 print("\nServer log file written to %s" % (rc_log, ))
189 print("\nServer log file written to %s" % (rc_log, ))
190 proc.kill()
190 proc.kill()
191 server_out.flush()
191 server_out.flush()
192 server_out.close()
192 server_out.close()
193
193
194 return RcWebServer(rc_web_server_config, log_file=rc_log)
194 return RcWebServer(rc_web_server_config, log_file=rc_log)
195
195
196
196
197 @pytest.fixture
197 @pytest.fixture
198 def disable_locking(baseapp):
198 def disable_locking(baseapp):
199 r = Repository.get_by_repo_name(GIT_REPO)
199 r = Repository.get_by_repo_name(GIT_REPO)
200 Repository.unlock(r)
200 Repository.unlock(r)
201 r.enable_locking = False
201 r.enable_locking = False
202 Session().add(r)
202 Session().add(r)
203 Session().commit()
203 Session().commit()
204
204
205 r = Repository.get_by_repo_name(HG_REPO)
205 r = Repository.get_by_repo_name(HG_REPO)
206 Repository.unlock(r)
206 Repository.unlock(r)
207 r.enable_locking = False
207 r.enable_locking = False
208 Session().add(r)
208 Session().add(r)
209 Session().commit()
209 Session().commit()
210
210
211
211
212 @pytest.fixture
212 @pytest.fixture
213 def enable_auth_plugins(request, baseapp, csrf_token):
213 def enable_auth_plugins(request, baseapp, csrf_token):
214 """
214 """
215 Return a factory object that when called, allows to control which
215 Return a factory object that when called, allows to control which
216 authentication plugins are enabled.
216 authentication plugins are enabled.
217 """
217 """
218 def _enable_plugins(plugins_list, override=None):
218 def _enable_plugins(plugins_list, override=None):
219 override = override or {}
219 override = override or {}
220 params = {
220 params = {
221 'auth_plugins': ','.join(plugins_list),
221 'auth_plugins': ','.join(plugins_list),
222 }
222 }
223
223
224 # helper translate some names to others
224 # helper translate some names to others
225 name_map = {
225 name_map = {
226 'token': 'authtoken'
226 'token': 'authtoken'
227 }
227 }
228
228
229 for module in plugins_list:
229 for module in plugins_list:
230 plugin_name = module.partition('#')[-1]
230 plugin_name = module.partition('#')[-1]
231 if plugin_name in name_map:
231 if plugin_name in name_map:
232 plugin_name = name_map[plugin_name]
232 plugin_name = name_map[plugin_name]
233 enabled_plugin = 'auth_%s_enabled' % plugin_name
233 enabled_plugin = 'auth_%s_enabled' % plugin_name
234 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
234 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
235
235
236 # default params that are needed for each plugin,
236 # default params that are needed for each plugin,
237 # `enabled` and `cache_ttl`
237 # `enabled` and `cache_ttl`
238 params.update({
238 params.update({
239 enabled_plugin: True,
239 enabled_plugin: True,
240 cache_ttl: 0
240 cache_ttl: 0
241 })
241 })
242 if override.get:
242 if override.get:
243 params.update(override.get(module, {}))
243 params.update(override.get(module, {}))
244
244
245 validated_params = params
245 validated_params = params
246 for k, v in validated_params.items():
246 for k, v in validated_params.items():
247 setting = SettingsModel().create_or_update_setting(k, v)
247 setting = SettingsModel().create_or_update_setting(k, v)
248 Session().add(setting)
248 Session().add(setting)
249 Session().commit()
249 Session().commit()
250
250
251 def cleanup():
251 def cleanup():
252 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
252 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
253
253
254 request.addfinalizer(cleanup)
254 request.addfinalizer(cleanup)
255
255
256 return _enable_plugins
256 return _enable_plugins
257
257
258
258
259 @pytest.fixture
259 @pytest.fixture
260 def fs_repo_only(request, rhodecode_fixtures):
260 def fs_repo_only(request, rhodecode_fixtures):
261 def fs_repo_fabric(repo_name, repo_type):
261 def fs_repo_fabric(repo_name, repo_type):
262 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
262 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
263 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
263 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
264
264
265 def cleanup():
265 def cleanup():
266 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
266 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
267 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
267 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
268
268
269 request.addfinalizer(cleanup)
269 request.addfinalizer(cleanup)
270
270
271 return fs_repo_fabric
271 return fs_repo_fabric
@@ -1,77 +1,79 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Test suite for making push/pull operations, on specially modified INI files
22 Test suite for making push/pull operations, on specially modified INI files
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 import os
30 import os
31 import pytest
31 import pytest
32
32
33 from rhodecode.lib.vcs.backends.git.repository import GitRepository
33 from rhodecode.lib.vcs.backends.git.repository import GitRepository
34 from rhodecode.lib.vcs.nodes import FileNode
34 from rhodecode.lib.vcs.nodes import FileNode
35 from rhodecode.tests import GIT_REPO
35 from rhodecode.tests import GIT_REPO
36 from rhodecode.tests.other.vcs_operations import Command
36 from rhodecode.tests.other.vcs_operations import Command
37 from .test_vcs_operations import _check_proper_clone, _check_proper_git_push
37 from .test_vcs_operations import _check_proper_clone, _check_proper_git_push
38
38
39
39
40 # override rc_web_server_config fixture with custom INI
40 # override rc_web_server_config fixture with custom INI
41 @pytest.fixture(scope="module")
41 @pytest.fixture(scope="module")
42 def rc_web_server_config(testini_factory):
42 def rc_web_server_config(testini_factory):
43 """
43 """
44 Configuration file used for the fixture `rc_web_server`.
44 Configuration file used for the fixture `rc_web_server`.
45 """
45 """
46 CUSTOM_PARAMS = [
46 CUSTOM_PARAMS = [
47 {'handler_console': {'level': 'DEBUG'}},
47 {'handler_console': {'level': 'DEBUG'}},
48 ]
48 ]
49 return testini_factory(CUSTOM_PARAMS)
49 return testini_factory(CUSTOM_PARAMS)
50
50
51
51
52 def test_git_clone_with_small_push_buffer(backend_git, rc_web_server, tmpdir):
52 def test_git_clone_with_small_push_buffer(backend_git, rc_web_server, tmpdir):
53 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
53 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
54 cmd = Command('/tmp')
54 cmd = Command('/tmp')
55 stdout, stderr = cmd.execute('git -c http.postBuffer=1024 clone', clone_url, tmpdir.strpath)
55 stdout, stderr = cmd.execute(
56 'git -c http.postBuffer=1024 clone', clone_url, tmpdir.strpath)
56 _check_proper_clone(stdout, stderr, 'git')
57 _check_proper_clone(stdout, stderr, 'git')
57 cmd.assert_returncode_success()
58 cmd.assert_returncode_success()
58
59
59
60
60 def test_git_push_with_small_push_buffer(backend_git, rc_web_server, tmpdir):
61 def test_git_push_with_small_push_buffer(backend_git, rc_web_server, tmpdir):
61 empty_repo = backend_git.create_repo()
62 empty_repo = backend_git.create_repo()
62
63
63 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
64 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
64
65
65 cmd = Command(tmpdir.strpath)
66 cmd = Command(tmpdir.strpath)
66 cmd.execute('git clone', clone_url)
67 cmd.execute('git clone', clone_url)
67
68
68 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
69 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
69 repo.in_memory_commit.add(FileNode('readme.md', content='## Hello'))
70 repo.in_memory_commit.add(FileNode('readme.md', content='## Hello'))
70 repo.in_memory_commit.commit(
71 repo.in_memory_commit.commit(
71 message='Commit on branch Master',
72 message='Commit on branch Master',
72 author='Automatic test',
73 author='Automatic test',
73 branch='master')
74 branch='master')
74
75
75 repo_cmd = Command(repo.path)
76 repo_cmd = Command(repo.path)
76 stdout, stderr = repo_cmd.execute('git -c http.postBuffer=1024 push --verbose origin master')
77 stdout, stderr = repo_cmd.execute(
78 'git -c http.postBuffer=1024 push --verbose origin master')
77 _check_proper_git_push(stdout, stderr, branch='master')
79 _check_proper_git_push(stdout, stderr, branch='master')
@@ -1,678 +1,682 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Test suite for making push/pull operations, on specially modified INI files
22 Test suite for making push/pull operations, on specially modified INI files
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30
30
31 import os
31 import os
32 import time
32 import time
33
33
34 import pytest
34 import pytest
35
35
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
37 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
37 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.db import Repository, UserIpMap, CacheKey
40 from rhodecode.model.db import Repository, UserIpMap, CacheKey
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.user import UserModel
43 from rhodecode.model.user import UserModel
44 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
44 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
45
45
46 from rhodecode.tests.other.vcs_operations import (
46 from rhodecode.tests.other.vcs_operations import (
47 Command, _check_proper_clone, _check_proper_git_push,
47 Command, _check_proper_clone, _check_proper_git_push,
48 _check_proper_hg_push, _add_files_and_push,
48 _check_proper_hg_push, _add_files_and_push,
49 HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
49 HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
50
50
51
51
52 @pytest.fixture(scope="session")
53 def vcs_server_config_override():
54 return ({'server:main': {'workers': 2}},)
55
56
52 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
57 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
53 class TestVCSOperations(object):
58 class TestVCSOperations(object):
54
59
55 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
60 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
56 clone_url = rc_web_server.repo_clone_url(HG_REPO)
61 clone_url = rc_web_server.repo_clone_url(HG_REPO)
57 stdout, stderr = Command('/tmp').execute(
62 stdout, stderr = Command('/tmp').execute(
58 'hg clone', clone_url, tmpdir.strpath)
63 'hg clone', clone_url, tmpdir.strpath)
59 _check_proper_clone(stdout, stderr, 'hg')
64 _check_proper_clone(stdout, stderr, 'hg')
60
65
61 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
66 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
62 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
67 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
63 cmd = Command('/tmp')
68 cmd = Command('/tmp')
64 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
69 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
65 _check_proper_clone(stdout, stderr, 'git')
70 _check_proper_clone(stdout, stderr, 'git')
66 cmd.assert_returncode_success()
71 cmd.assert_returncode_success()
67
72
68 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
73 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
69 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
74 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
70 cmd = Command('/tmp')
75 cmd = Command('/tmp')
71 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
76 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
72 _check_proper_clone(stdout, stderr, 'git')
77 _check_proper_clone(stdout, stderr, 'git')
73 cmd.assert_returncode_success()
78 cmd.assert_returncode_success()
74
79
75 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
80 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
76 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
81 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
77 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
82 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
78 stdout, stderr = Command('/tmp').execute(
83 stdout, stderr = Command('/tmp').execute(
79 'hg clone', clone_url, tmpdir.strpath)
84 'hg clone', clone_url, tmpdir.strpath)
80 _check_proper_clone(stdout, stderr, 'hg')
85 _check_proper_clone(stdout, stderr, 'hg')
81
86
82 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
87 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
83 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
88 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
84 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
89 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
85 cmd = Command('/tmp')
90 cmd = Command('/tmp')
86 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
91 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
87 _check_proper_clone(stdout, stderr, 'git')
92 _check_proper_clone(stdout, stderr, 'git')
88 cmd.assert_returncode_success()
93 cmd.assert_returncode_success()
89
94
90 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
95 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
91 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
96 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
92 stdout, stderr = Command('/tmp').execute(
97 stdout, stderr = Command('/tmp').execute(
93 'hg clone', clone_url, tmpdir.strpath)
98 'hg clone', clone_url, tmpdir.strpath)
94 _check_proper_clone(stdout, stderr, 'hg')
99 _check_proper_clone(stdout, stderr, 'hg')
95
100
96 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
101 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
97 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
102 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
98 cmd = Command('/tmp')
103 cmd = Command('/tmp')
99 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
104 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
100 _check_proper_clone(stdout, stderr, 'git')
105 _check_proper_clone(stdout, stderr, 'git')
101 cmd.assert_returncode_success()
106 cmd.assert_returncode_success()
102
107
103 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
108 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
104 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
109 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
105 cmd = Command('/tmp')
110 cmd = Command('/tmp')
106 stdout, stderr = cmd.execute(
111 stdout, stderr = cmd.execute(
107 'git clone --depth=1', clone_url, tmpdir.strpath)
112 'git clone --depth=1', clone_url, tmpdir.strpath)
108
113
109 assert '' == stdout
114 assert '' == stdout
110 assert 'Cloning into' in stderr
115 assert 'Cloning into' in stderr
111 cmd.assert_returncode_success()
116 cmd.assert_returncode_success()
112
117
113 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
118 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
114 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
119 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
115 stdout, stderr = Command('/tmp').execute(
120 stdout, stderr = Command('/tmp').execute(
116 'hg clone', clone_url, tmpdir.strpath)
121 'hg clone', clone_url, tmpdir.strpath)
117 assert 'abort: authorization failed' in stderr
122 assert 'abort: authorization failed' in stderr
118
123
119 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
124 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
120 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
125 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
121 stdout, stderr = Command('/tmp').execute(
126 stdout, stderr = Command('/tmp').execute(
122 'git clone', clone_url, tmpdir.strpath)
127 'git clone', clone_url, tmpdir.strpath)
123 assert 'fatal: Authentication failed' in stderr
128 assert 'fatal: Authentication failed' in stderr
124
129
125 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
130 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
126 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
131 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
127 stdout, stderr = Command('/tmp').execute(
132 stdout, stderr = Command('/tmp').execute(
128 'hg clone', clone_url, tmpdir.strpath)
133 'hg clone', clone_url, tmpdir.strpath)
129 assert 'HTTP Error 404: Not Found' in stderr
134 assert 'HTTP Error 404: Not Found' in stderr
130
135
131 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
136 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
132 clone_url = rc_web_server.repo_clone_url(HG_REPO)
137 clone_url = rc_web_server.repo_clone_url(HG_REPO)
133 stdout, stderr = Command('/tmp').execute(
138 stdout, stderr = Command('/tmp').execute(
134 'git clone', clone_url, tmpdir.strpath)
139 'git clone', clone_url, tmpdir.strpath)
135 assert 'not found' in stderr
140 assert 'not found' in stderr
136
141
137 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
142 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
138 clone_url = rc_web_server.repo_clone_url('trololo')
143 clone_url = rc_web_server.repo_clone_url('trololo')
139 stdout, stderr = Command('/tmp').execute(
144 stdout, stderr = Command('/tmp').execute(
140 'hg clone', clone_url, tmpdir.strpath)
145 'hg clone', clone_url, tmpdir.strpath)
141 assert 'HTTP Error 404: Not Found' in stderr
146 assert 'HTTP Error 404: Not Found' in stderr
142
147
143 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
148 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
144 clone_url = rc_web_server.repo_clone_url('trololo')
149 clone_url = rc_web_server.repo_clone_url('trololo')
145 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
150 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
146 assert 'not found' in stderr
151 assert 'not found' in stderr
147
152
148 def test_clone_existing_path_hg_not_in_database(
153 def test_clone_existing_path_hg_not_in_database(
149 self, rc_web_server, tmpdir, fs_repo_only):
154 self, rc_web_server, tmpdir, fs_repo_only):
150
155
151 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
156 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
152 clone_url = rc_web_server.repo_clone_url(db_name)
157 clone_url = rc_web_server.repo_clone_url(db_name)
153 stdout, stderr = Command('/tmp').execute(
158 stdout, stderr = Command('/tmp').execute(
154 'hg clone', clone_url, tmpdir.strpath)
159 'hg clone', clone_url, tmpdir.strpath)
155 assert 'HTTP Error 404: Not Found' in stderr
160 assert 'HTTP Error 404: Not Found' in stderr
156
161
157 def test_clone_existing_path_git_not_in_database(
162 def test_clone_existing_path_git_not_in_database(
158 self, rc_web_server, tmpdir, fs_repo_only):
163 self, rc_web_server, tmpdir, fs_repo_only):
159 db_name = fs_repo_only('not-in-db-git', repo_type='git')
164 db_name = fs_repo_only('not-in-db-git', repo_type='git')
160 clone_url = rc_web_server.repo_clone_url(db_name)
165 clone_url = rc_web_server.repo_clone_url(db_name)
161 stdout, stderr = Command('/tmp').execute(
166 stdout, stderr = Command('/tmp').execute(
162 'git clone', clone_url, tmpdir.strpath)
167 'git clone', clone_url, tmpdir.strpath)
163 assert 'not found' in stderr
168 assert 'not found' in stderr
164
169
165 def test_clone_existing_path_hg_not_in_database_different_scm(
170 def test_clone_existing_path_hg_not_in_database_different_scm(
166 self, rc_web_server, tmpdir, fs_repo_only):
171 self, rc_web_server, tmpdir, fs_repo_only):
167 db_name = fs_repo_only('not-in-db-git', repo_type='git')
172 db_name = fs_repo_only('not-in-db-git', repo_type='git')
168 clone_url = rc_web_server.repo_clone_url(db_name)
173 clone_url = rc_web_server.repo_clone_url(db_name)
169 stdout, stderr = Command('/tmp').execute(
174 stdout, stderr = Command('/tmp').execute(
170 'hg clone', clone_url, tmpdir.strpath)
175 'hg clone', clone_url, tmpdir.strpath)
171 assert 'HTTP Error 404: Not Found' in stderr
176 assert 'HTTP Error 404: Not Found' in stderr
172
177
173 def test_clone_existing_path_git_not_in_database_different_scm(
178 def test_clone_existing_path_git_not_in_database_different_scm(
174 self, rc_web_server, tmpdir, fs_repo_only):
179 self, rc_web_server, tmpdir, fs_repo_only):
175 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
180 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
176 clone_url = rc_web_server.repo_clone_url(db_name)
181 clone_url = rc_web_server.repo_clone_url(db_name)
177 stdout, stderr = Command('/tmp').execute(
182 stdout, stderr = Command('/tmp').execute(
178 'git clone', clone_url, tmpdir.strpath)
183 'git clone', clone_url, tmpdir.strpath)
179 assert 'not found' in stderr
184 assert 'not found' in stderr
180
185
181 def test_clone_non_existing_store_path_hg(self, rc_web_server, tmpdir, user_util):
186 def test_clone_non_existing_store_path_hg(self, rc_web_server, tmpdir, user_util):
182 repo = user_util.create_repo()
187 repo = user_util.create_repo()
183 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
188 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
184
189
185 # Damage repo by removing it's folder
190 # Damage repo by removing it's folder
186 RepoModel()._delete_filesystem_repo(repo)
191 RepoModel()._delete_filesystem_repo(repo)
187
192
188 stdout, stderr = Command('/tmp').execute(
193 stdout, stderr = Command('/tmp').execute(
189 'hg clone', clone_url, tmpdir.strpath)
194 'hg clone', clone_url, tmpdir.strpath)
190 assert 'HTTP Error 404: Not Found' in stderr
195 assert 'HTTP Error 404: Not Found' in stderr
191
196
192 def test_clone_non_existing_store_path_git(self, rc_web_server, tmpdir, user_util):
197 def test_clone_non_existing_store_path_git(self, rc_web_server, tmpdir, user_util):
193 repo = user_util.create_repo(repo_type='git')
198 repo = user_util.create_repo(repo_type='git')
194 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
199 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
195
200
196 # Damage repo by removing it's folder
201 # Damage repo by removing it's folder
197 RepoModel()._delete_filesystem_repo(repo)
202 RepoModel()._delete_filesystem_repo(repo)
198
203
199 stdout, stderr = Command('/tmp').execute(
204 stdout, stderr = Command('/tmp').execute(
200 'git clone', clone_url, tmpdir.strpath)
205 'git clone', clone_url, tmpdir.strpath)
201 assert 'not found' in stderr
206 assert 'not found' in stderr
202
207
203 def test_push_new_file_hg(self, rc_web_server, tmpdir):
208 def test_push_new_file_hg(self, rc_web_server, tmpdir):
204 clone_url = rc_web_server.repo_clone_url(HG_REPO)
209 clone_url = rc_web_server.repo_clone_url(HG_REPO)
205 stdout, stderr = Command('/tmp').execute(
210 stdout, stderr = Command('/tmp').execute(
206 'hg clone', clone_url, tmpdir.strpath)
211 'hg clone', clone_url, tmpdir.strpath)
207
212
208 stdout, stderr = _add_files_and_push(
213 stdout, stderr = _add_files_and_push(
209 'hg', tmpdir.strpath, clone_url=clone_url)
214 'hg', tmpdir.strpath, clone_url=clone_url)
210
215
211 assert 'pushing to' in stdout
216 assert 'pushing to' in stdout
212 assert 'size summary' in stdout
217 assert 'size summary' in stdout
213
218
214 def test_push_new_file_git(self, rc_web_server, tmpdir):
219 def test_push_new_file_git(self, rc_web_server, tmpdir):
215 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
220 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
216 stdout, stderr = Command('/tmp').execute(
221 stdout, stderr = Command('/tmp').execute(
217 'git clone', clone_url, tmpdir.strpath)
222 'git clone', clone_url, tmpdir.strpath)
218
223
219 # commit some stuff into this repo
224 # commit some stuff into this repo
220 stdout, stderr = _add_files_and_push(
225 stdout, stderr = _add_files_and_push(
221 'git', tmpdir.strpath, clone_url=clone_url)
226 'git', tmpdir.strpath, clone_url=clone_url)
222
227
223 _check_proper_git_push(stdout, stderr)
228 _check_proper_git_push(stdout, stderr)
224
229
225 def test_push_invalidates_cache_hg(self, rc_web_server, tmpdir):
230 def test_push_invalidates_cache_hg(self, rc_web_server, tmpdir):
226 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).scalar()
231 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).scalar()
227 if not key:
232 if not key:
228 key = CacheKey(HG_REPO, HG_REPO)
233 key = CacheKey(HG_REPO, HG_REPO)
229
234
230 key.cache_active = True
235 key.cache_active = True
231 Session().add(key)
236 Session().add(key)
232 Session().commit()
237 Session().commit()
233
238
234 clone_url = rc_web_server.repo_clone_url(HG_REPO)
239 clone_url = rc_web_server.repo_clone_url(HG_REPO)
235 stdout, stderr = Command('/tmp').execute(
240 stdout, stderr = Command('/tmp').execute(
236 'hg clone', clone_url, tmpdir.strpath)
241 'hg clone', clone_url, tmpdir.strpath)
237
242
238 stdout, stderr = _add_files_and_push(
243 stdout, stderr = _add_files_and_push(
239 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
244 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
240
245
241 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).one()
246 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).one()
242 assert key.cache_active is False
247 assert key.cache_active is False
243
248
244 def test_push_invalidates_cache_git(self, rc_web_server, tmpdir):
249 def test_push_invalidates_cache_git(self, rc_web_server, tmpdir):
245 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).scalar()
250 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).scalar()
246 if not key:
251 if not key:
247 key = CacheKey(GIT_REPO, GIT_REPO)
252 key = CacheKey(GIT_REPO, GIT_REPO)
248
253
249 key.cache_active = True
254 key.cache_active = True
250 Session().add(key)
255 Session().add(key)
251 Session().commit()
256 Session().commit()
252
257
253 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
258 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
254 stdout, stderr = Command('/tmp').execute(
259 stdout, stderr = Command('/tmp').execute(
255 'git clone', clone_url, tmpdir.strpath)
260 'git clone', clone_url, tmpdir.strpath)
256
261
257 # commit some stuff into this repo
262 # commit some stuff into this repo
258 stdout, stderr = _add_files_and_push(
263 stdout, stderr = _add_files_and_push(
259 'git', tmpdir.strpath, clone_url=clone_url, files_no=1)
264 'git', tmpdir.strpath, clone_url=clone_url, files_no=1)
260 _check_proper_git_push(stdout, stderr)
265 _check_proper_git_push(stdout, stderr)
261
266
262 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).one()
267 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).one()
263
268
264 assert key.cache_active is False
269 assert key.cache_active is False
265
270
266 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
271 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
267 clone_url = rc_web_server.repo_clone_url(HG_REPO)
272 clone_url = rc_web_server.repo_clone_url(HG_REPO)
268 stdout, stderr = Command('/tmp').execute(
273 stdout, stderr = Command('/tmp').execute(
269 'hg clone', clone_url, tmpdir.strpath)
274 'hg clone', clone_url, tmpdir.strpath)
270
275
271 push_url = rc_web_server.repo_clone_url(
276 push_url = rc_web_server.repo_clone_url(
272 HG_REPO, user='bad', passwd='name')
277 HG_REPO, user='bad', passwd='name')
273 stdout, stderr = _add_files_and_push(
278 stdout, stderr = _add_files_and_push(
274 'hg', tmpdir.strpath, clone_url=push_url)
279 'hg', tmpdir.strpath, clone_url=push_url)
275
280
276 assert 'abort: authorization failed' in stderr
281 assert 'abort: authorization failed' in stderr
277
282
278 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
283 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
279 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
284 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
280 stdout, stderr = Command('/tmp').execute(
285 stdout, stderr = Command('/tmp').execute(
281 'git clone', clone_url, tmpdir.strpath)
286 'git clone', clone_url, tmpdir.strpath)
282
287
283 push_url = rc_web_server.repo_clone_url(
288 push_url = rc_web_server.repo_clone_url(
284 GIT_REPO, user='bad', passwd='name')
289 GIT_REPO, user='bad', passwd='name')
285 stdout, stderr = _add_files_and_push(
290 stdout, stderr = _add_files_and_push(
286 'git', tmpdir.strpath, clone_url=push_url)
291 'git', tmpdir.strpath, clone_url=push_url)
287
292
288 assert 'fatal: Authentication failed' in stderr
293 assert 'fatal: Authentication failed' in stderr
289
294
290 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
295 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
291 clone_url = rc_web_server.repo_clone_url(HG_REPO)
296 clone_url = rc_web_server.repo_clone_url(HG_REPO)
292 stdout, stderr = Command('/tmp').execute(
297 stdout, stderr = Command('/tmp').execute(
293 'hg clone', clone_url, tmpdir.strpath)
298 'hg clone', clone_url, tmpdir.strpath)
294
299
295 stdout, stderr = _add_files_and_push(
300 stdout, stderr = _add_files_and_push(
296 'hg', tmpdir.strpath,
301 'hg', tmpdir.strpath,
297 clone_url=rc_web_server.repo_clone_url('not-existing'))
302 clone_url=rc_web_server.repo_clone_url('not-existing'))
298
303
299 assert 'HTTP Error 404: Not Found' in stderr
304 assert 'HTTP Error 404: Not Found' in stderr
300
305
301 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
306 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
302 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
307 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
303 stdout, stderr = Command('/tmp').execute(
308 stdout, stderr = Command('/tmp').execute(
304 'git clone', clone_url, tmpdir.strpath)
309 'git clone', clone_url, tmpdir.strpath)
305
310
306 stdout, stderr = _add_files_and_push(
311 stdout, stderr = _add_files_and_push(
307 'git', tmpdir.strpath,
312 'git', tmpdir.strpath,
308 clone_url=rc_web_server.repo_clone_url('not-existing'))
313 clone_url=rc_web_server.repo_clone_url('not-existing'))
309
314
310 assert 'not found' in stderr
315 assert 'not found' in stderr
311
316
312 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
317 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
313 user_model = UserModel()
318 user_model = UserModel()
314 try:
319 try:
315 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
320 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
316 Session().commit()
321 Session().commit()
317 time.sleep(2)
322 time.sleep(2)
318 clone_url = rc_web_server.repo_clone_url(HG_REPO)
323 clone_url = rc_web_server.repo_clone_url(HG_REPO)
319 stdout, stderr = Command('/tmp').execute(
324 stdout, stderr = Command('/tmp').execute(
320 'hg clone', clone_url, tmpdir.strpath)
325 'hg clone', clone_url, tmpdir.strpath)
321 assert 'abort: HTTP Error 403: Forbidden' in stderr
326 assert 'abort: HTTP Error 403: Forbidden' in stderr
322 finally:
327 finally:
323 # release IP restrictions
328 # release IP restrictions
324 for ip in UserIpMap.getAll():
329 for ip in UserIpMap.getAll():
325 UserIpMap.delete(ip.ip_id)
330 UserIpMap.delete(ip.ip_id)
326 Session().commit()
331 Session().commit()
327
332
328 time.sleep(2)
333 time.sleep(2)
329
334
330 stdout, stderr = Command('/tmp').execute(
335 stdout, stderr = Command('/tmp').execute(
331 'hg clone', clone_url, tmpdir.strpath)
336 'hg clone', clone_url, tmpdir.strpath)
332 _check_proper_clone(stdout, stderr, 'hg')
337 _check_proper_clone(stdout, stderr, 'hg')
333
338
334 def test_ip_restriction_git(self, rc_web_server, tmpdir):
339 def test_ip_restriction_git(self, rc_web_server, tmpdir):
335 user_model = UserModel()
340 user_model = UserModel()
336 try:
341 try:
337 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
342 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
338 Session().commit()
343 Session().commit()
339 time.sleep(2)
344 time.sleep(2)
340 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
345 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
341 stdout, stderr = Command('/tmp').execute(
346 stdout, stderr = Command('/tmp').execute(
342 'git clone', clone_url, tmpdir.strpath)
347 'git clone', clone_url, tmpdir.strpath)
343 msg = "The requested URL returned error: 403"
348 msg = "The requested URL returned error: 403"
344 assert msg in stderr
349 assert msg in stderr
345 finally:
350 finally:
346 # release IP restrictions
351 # release IP restrictions
347 for ip in UserIpMap.getAll():
352 for ip in UserIpMap.getAll():
348 UserIpMap.delete(ip.ip_id)
353 UserIpMap.delete(ip.ip_id)
349 Session().commit()
354 Session().commit()
350
355
351 time.sleep(2)
356 time.sleep(2)
352
357
353 cmd = Command('/tmp')
358 cmd = Command('/tmp')
354 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
359 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
355 cmd.assert_returncode_success()
360 cmd.assert_returncode_success()
356 _check_proper_clone(stdout, stderr, 'git')
361 _check_proper_clone(stdout, stderr, 'git')
357
362
358 def test_clone_by_auth_token(
363 def test_clone_by_auth_token(
359 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
364 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
360 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
365 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
361 'egg:rhodecode-enterprise-ce#rhodecode'])
366 'egg:rhodecode-enterprise-ce#rhodecode'])
362
367
363 user = user_util.create_user()
368 user = user_util.create_user()
364 token = user.auth_tokens[1]
369 token = user.auth_tokens[1]
365
370
366 clone_url = rc_web_server.repo_clone_url(
371 clone_url = rc_web_server.repo_clone_url(
367 HG_REPO, user=user.username, passwd=token)
372 HG_REPO, user=user.username, passwd=token)
368
373
369 stdout, stderr = Command('/tmp').execute(
374 stdout, stderr = Command('/tmp').execute(
370 'hg clone', clone_url, tmpdir.strpath)
375 'hg clone', clone_url, tmpdir.strpath)
371 _check_proper_clone(stdout, stderr, 'hg')
376 _check_proper_clone(stdout, stderr, 'hg')
372
377
373 def test_clone_by_auth_token_expired(
378 def test_clone_by_auth_token_expired(
374 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
379 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
375 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
380 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
376 'egg:rhodecode-enterprise-ce#rhodecode'])
381 'egg:rhodecode-enterprise-ce#rhodecode'])
377
382
378 user = user_util.create_user()
383 user = user_util.create_user()
379 auth_token = AuthTokenModel().create(
384 auth_token = AuthTokenModel().create(
380 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
385 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
381 token = auth_token.api_key
386 token = auth_token.api_key
382
387
383 clone_url = rc_web_server.repo_clone_url(
388 clone_url = rc_web_server.repo_clone_url(
384 HG_REPO, user=user.username, passwd=token)
389 HG_REPO, user=user.username, passwd=token)
385
390
386 stdout, stderr = Command('/tmp').execute(
391 stdout, stderr = Command('/tmp').execute(
387 'hg clone', clone_url, tmpdir.strpath)
392 'hg clone', clone_url, tmpdir.strpath)
388 assert 'abort: authorization failed' in stderr
393 assert 'abort: authorization failed' in stderr
389
394
390 def test_clone_by_auth_token_bad_role(
395 def test_clone_by_auth_token_bad_role(
391 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
396 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
392 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
397 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
393 'egg:rhodecode-enterprise-ce#rhodecode'])
398 'egg:rhodecode-enterprise-ce#rhodecode'])
394
399
395 user = user_util.create_user()
400 user = user_util.create_user()
396 auth_token = AuthTokenModel().create(
401 auth_token = AuthTokenModel().create(
397 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
402 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
398 token = auth_token.api_key
403 token = auth_token.api_key
399
404
400 clone_url = rc_web_server.repo_clone_url(
405 clone_url = rc_web_server.repo_clone_url(
401 HG_REPO, user=user.username, passwd=token)
406 HG_REPO, user=user.username, passwd=token)
402
407
403 stdout, stderr = Command('/tmp').execute(
408 stdout, stderr = Command('/tmp').execute(
404 'hg clone', clone_url, tmpdir.strpath)
409 'hg clone', clone_url, tmpdir.strpath)
405 assert 'abort: authorization failed' in stderr
410 assert 'abort: authorization failed' in stderr
406
411
407 def test_clone_by_auth_token_user_disabled(
412 def test_clone_by_auth_token_user_disabled(
408 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
413 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
409 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
414 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
410 'egg:rhodecode-enterprise-ce#rhodecode'])
415 'egg:rhodecode-enterprise-ce#rhodecode'])
411 user = user_util.create_user()
416 user = user_util.create_user()
412 user.active = False
417 user.active = False
413 Session().add(user)
418 Session().add(user)
414 Session().commit()
419 Session().commit()
415 token = user.auth_tokens[1]
420 token = user.auth_tokens[1]
416
421
417 clone_url = rc_web_server.repo_clone_url(
422 clone_url = rc_web_server.repo_clone_url(
418 HG_REPO, user=user.username, passwd=token)
423 HG_REPO, user=user.username, passwd=token)
419
424
420 stdout, stderr = Command('/tmp').execute(
425 stdout, stderr = Command('/tmp').execute(
421 'hg clone', clone_url, tmpdir.strpath)
426 'hg clone', clone_url, tmpdir.strpath)
422 assert 'abort: authorization failed' in stderr
427 assert 'abort: authorization failed' in stderr
423
428
424 def test_clone_by_auth_token_with_scope(
429 def test_clone_by_auth_token_with_scope(
425 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
430 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
426 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
431 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
427 'egg:rhodecode-enterprise-ce#rhodecode'])
432 'egg:rhodecode-enterprise-ce#rhodecode'])
428 user = user_util.create_user()
433 user = user_util.create_user()
429 auth_token = AuthTokenModel().create(
434 auth_token = AuthTokenModel().create(
430 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
435 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
431 token = auth_token.api_key
436 token = auth_token.api_key
432
437
433 # manually set scope
438 # manually set scope
434 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
439 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
435 Session().add(auth_token)
440 Session().add(auth_token)
436 Session().commit()
441 Session().commit()
437
442
438 clone_url = rc_web_server.repo_clone_url(
443 clone_url = rc_web_server.repo_clone_url(
439 HG_REPO, user=user.username, passwd=token)
444 HG_REPO, user=user.username, passwd=token)
440
445
441 stdout, stderr = Command('/tmp').execute(
446 stdout, stderr = Command('/tmp').execute(
442 'hg clone', clone_url, tmpdir.strpath)
447 'hg clone', clone_url, tmpdir.strpath)
443 _check_proper_clone(stdout, stderr, 'hg')
448 _check_proper_clone(stdout, stderr, 'hg')
444
449
445 def test_clone_by_auth_token_with_wrong_scope(
450 def test_clone_by_auth_token_with_wrong_scope(
446 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
451 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
447 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
452 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
448 'egg:rhodecode-enterprise-ce#rhodecode'])
453 'egg:rhodecode-enterprise-ce#rhodecode'])
449 user = user_util.create_user()
454 user = user_util.create_user()
450 auth_token = AuthTokenModel().create(
455 auth_token = AuthTokenModel().create(
451 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
456 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
452 token = auth_token.api_key
457 token = auth_token.api_key
453
458
454 # manually set scope
459 # manually set scope
455 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
460 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
456 Session().add(auth_token)
461 Session().add(auth_token)
457 Session().commit()
462 Session().commit()
458
463
459 clone_url = rc_web_server.repo_clone_url(
464 clone_url = rc_web_server.repo_clone_url(
460 HG_REPO, user=user.username, passwd=token)
465 HG_REPO, user=user.username, passwd=token)
461
466
462 stdout, stderr = Command('/tmp').execute(
467 stdout, stderr = Command('/tmp').execute(
463 'hg clone', clone_url, tmpdir.strpath)
468 'hg clone', clone_url, tmpdir.strpath)
464 assert 'abort: authorization failed' in stderr
469 assert 'abort: authorization failed' in stderr
465
470
466
471
472 @pytest.mark.usefixtures("disable_locking")
473 class TestVCSOperationsSpecial(object):
474
467 def test_git_sets_default_branch_if_not_master(
475 def test_git_sets_default_branch_if_not_master(
468 backend_git, tmpdir, disable_locking, rc_web_server):
476 self, backend_git, tmpdir, rc_web_server):
469 empty_repo = backend_git.create_repo()
477 empty_repo = backend_git.create_repo()
470 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
478 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
471
479
472 cmd = Command(tmpdir.strpath)
480 cmd = Command(tmpdir.strpath)
473 cmd.execute('git clone', clone_url)
481 cmd.execute('git clone', clone_url)
474
482
475 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
483 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
476 repo.in_memory_commit.add(FileNode('file', content=''))
484 repo.in_memory_commit.add(FileNode('file', content=''))
477 repo.in_memory_commit.commit(
485 repo.in_memory_commit.commit(
478 message='Commit on branch test',
486 message='Commit on branch test',
479 author='Automatic test',
487 author='Automatic test',
480 branch='test')
488 branch='test')
481
489
482 repo_cmd = Command(repo.path)
490 repo_cmd = Command(repo.path)
483 stdout, stderr = repo_cmd.execute('git push --verbose origin test')
491 stdout, stderr = repo_cmd.execute('git push --verbose origin test')
484 _check_proper_git_push(
492 _check_proper_git_push(
485 stdout, stderr, branch='test', should_set_default_branch=True)
493 stdout, stderr, branch='test', should_set_default_branch=True)
486
494
487 stdout, stderr = cmd.execute(
495 stdout, stderr = cmd.execute(
488 'git clone', clone_url, empty_repo.repo_name + '-clone')
496 'git clone', clone_url, empty_repo.repo_name + '-clone')
489 _check_proper_clone(stdout, stderr, 'git')
497 _check_proper_clone(stdout, stderr, 'git')
490
498
491 # Doing an explicit commit in order to get latest user logs on MySQL
499 # Doing an explicit commit in order to get latest user logs on MySQL
492 Session().commit()
500 Session().commit()
493
501
494
495 def test_git_fetches_from_remote_repository_with_annotated_tags(
502 def test_git_fetches_from_remote_repository_with_annotated_tags(
496 backend_git, disable_locking, rc_web_server):
503 self, backend_git, rc_web_server):
497 # Note: This is a test specific to the git backend. It checks the
504 # Note: This is a test specific to the git backend. It checks the
498 # integration of fetching from a remote repository which contains
505 # integration of fetching from a remote repository which contains
499 # annotated tags.
506 # annotated tags.
500
507
501 # Dulwich shows this specific behavior only when
508 # Dulwich shows this specific behavior only when
502 # operating against a remote repository.
509 # operating against a remote repository.
503 source_repo = backend_git['annotated-tag']
510 source_repo = backend_git['annotated-tag']
504 target_vcs_repo = backend_git.create_repo().scm_instance()
511 target_vcs_repo = backend_git.create_repo().scm_instance()
505 target_vcs_repo.fetch(rc_web_server.repo_clone_url(source_repo.repo_name))
512 target_vcs_repo.fetch(rc_web_server.repo_clone_url(source_repo.repo_name))
506
513
507
514 def test_git_push_shows_pull_request_refs(self, backend_git, rc_web_server, tmpdir):
508 def test_git_push_shows_pull_request_refs(backend_git, rc_web_server, tmpdir):
509 """
515 """
510 test if remote info about refs is visible
516 test if remote info about refs is visible
511 """
517 """
512 empty_repo = backend_git.create_repo()
518 empty_repo = backend_git.create_repo()
513
519
514 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
520 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
515
521
516 cmd = Command(tmpdir.strpath)
522 cmd = Command(tmpdir.strpath)
517 cmd.execute('git clone', clone_url)
523 cmd.execute('git clone', clone_url)
518
524
519 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
525 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
520 repo.in_memory_commit.add(FileNode('readme.md', content='## Hello'))
526 repo.in_memory_commit.add(FileNode('readme.md', content='## Hello'))
521 repo.in_memory_commit.commit(
527 repo.in_memory_commit.commit(
522 message='Commit on branch Master',
528 message='Commit on branch Master',
523 author='Automatic test',
529 author='Automatic test',
524 branch='master')
530 branch='master')
525
531
526 repo_cmd = Command(repo.path)
532 repo_cmd = Command(repo.path)
527 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
533 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
528 _check_proper_git_push(stdout, stderr, branch='master')
534 _check_proper_git_push(stdout, stderr, branch='master')
529
535
530 ref = '{}/{}/pull-request/new?branch=master'.format(
536 ref = '{}/{}/pull-request/new?branch=master'.format(
531 rc_web_server.host_url(), empty_repo.repo_name)
537 rc_web_server.host_url(), empty_repo.repo_name)
532 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
538 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
533 assert 'remote: RhodeCode: push completed' in stderr
539 assert 'remote: RhodeCode: push completed' in stderr
534
540
535 # push on the same branch
541 # push on the same branch
536 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
542 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
537 repo.in_memory_commit.add(FileNode('setup.py', content='print\n'))
543 repo.in_memory_commit.add(FileNode('setup.py', content='print\n'))
538 repo.in_memory_commit.commit(
544 repo.in_memory_commit.commit(
539 message='Commit2 on branch Master',
545 message='Commit2 on branch Master',
540 author='Automatic test2',
546 author='Automatic test2',
541 branch='master')
547 branch='master')
542
548
543 repo_cmd = Command(repo.path)
549 repo_cmd = Command(repo.path)
544 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
550 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
545 _check_proper_git_push(stdout, stderr, branch='master')
551 _check_proper_git_push(stdout, stderr, branch='master')
546
552
547 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
553 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
548 assert 'remote: RhodeCode: push completed' in stderr
554 assert 'remote: RhodeCode: push completed' in stderr
549
555
550 # new Branch
556 # new Branch
551 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
557 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
552 repo.in_memory_commit.add(FileNode('feature1.py', content='## Hello world'))
558 repo.in_memory_commit.add(FileNode('feature1.py', content='## Hello world'))
553 repo.in_memory_commit.commit(
559 repo.in_memory_commit.commit(
554 message='Commit on branch feature',
560 message='Commit on branch feature',
555 author='Automatic test',
561 author='Automatic test',
556 branch='feature')
562 branch='feature')
557
563
558 repo_cmd = Command(repo.path)
564 repo_cmd = Command(repo.path)
559 stdout, stderr = repo_cmd.execute('git push --verbose origin feature')
565 stdout, stderr = repo_cmd.execute('git push --verbose origin feature')
560 _check_proper_git_push(stdout, stderr, branch='feature')
566 _check_proper_git_push(stdout, stderr, branch='feature')
561
567
562 ref = '{}/{}/pull-request/new?branch=feature'.format(
568 ref = '{}/{}/pull-request/new?branch=feature'.format(
563 rc_web_server.host_url(), empty_repo.repo_name)
569 rc_web_server.host_url(), empty_repo.repo_name)
564 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
570 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
565 assert 'remote: RhodeCode: push completed' in stderr
571 assert 'remote: RhodeCode: push completed' in stderr
566
572
567
573 def test_hg_push_shows_pull_request_refs(self, backend_hg, rc_web_server, tmpdir):
568 def test_hg_push_shows_pull_request_refs(backend_hg, rc_web_server, tmpdir):
569 empty_repo = backend_hg.create_repo()
574 empty_repo = backend_hg.create_repo()
570
575
571 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
576 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
572
577
573 cmd = Command(tmpdir.strpath)
578 cmd = Command(tmpdir.strpath)
574 cmd.execute('hg clone', clone_url)
579 cmd.execute('hg clone', clone_url)
575
580
576 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
581 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
577 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
582 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
578 repo.in_memory_commit.commit(
583 repo.in_memory_commit.commit(
579 message=u'Commit on branch default',
584 message=u'Commit on branch default',
580 author=u'Automatic test',
585 author=u'Automatic test',
581 branch='default')
586 branch='default')
582
587
583 repo_cmd = Command(repo.path)
588 repo_cmd = Command(repo.path)
584 repo_cmd.execute('hg checkout default')
589 repo_cmd.execute('hg checkout default')
585
590
586 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
591 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
587 _check_proper_hg_push(stdout, stderr, branch='default')
592 _check_proper_hg_push(stdout, stderr, branch='default')
588
593
589 ref = '{}/{}/pull-request/new?branch=default'.format(
594 ref = '{}/{}/pull-request/new?branch=default'.format(
590 rc_web_server.host_url(), empty_repo.repo_name)
595 rc_web_server.host_url(), empty_repo.repo_name)
591 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
596 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
592 assert 'remote: RhodeCode: push completed' in stdout
597 assert 'remote: RhodeCode: push completed' in stdout
593
598
594 # push on the same branch
599 # push on the same branch
595 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
600 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
596 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
601 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
597 repo.in_memory_commit.commit(
602 repo.in_memory_commit.commit(
598 message=u'Commit2 on branch default',
603 message=u'Commit2 on branch default',
599 author=u'Automatic test2',
604 author=u'Automatic test2',
600 branch=u'default')
605 branch=u'default')
601
606
602 repo_cmd = Command(repo.path)
607 repo_cmd = Command(repo.path)
603 repo_cmd.execute('hg checkout default')
608 repo_cmd.execute('hg checkout default')
604
609
605 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
610 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
606 _check_proper_hg_push(stdout, stderr, branch='default')
611 _check_proper_hg_push(stdout, stderr, branch='default')
607
612
608 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
613 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
609 assert 'remote: RhodeCode: push completed' in stdout
614 assert 'remote: RhodeCode: push completed' in stdout
610
615
611 # new Branch
616 # new Branch
612 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
617 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
613 repo.in_memory_commit.add(FileNode(u'feature1.py', content=u'## Hello world'))
618 repo.in_memory_commit.add(FileNode(u'feature1.py', content=u'## Hello world'))
614 repo.in_memory_commit.commit(
619 repo.in_memory_commit.commit(
615 message=u'Commit on branch feature',
620 message=u'Commit on branch feature',
616 author=u'Automatic test',
621 author=u'Automatic test',
617 branch=u'feature')
622 branch=u'feature')
618
623
619 repo_cmd = Command(repo.path)
624 repo_cmd = Command(repo.path)
620 repo_cmd.execute('hg checkout feature')
625 repo_cmd.execute('hg checkout feature')
621
626
622 stdout, stderr = repo_cmd.execute('hg push --new-branch --verbose', clone_url)
627 stdout, stderr = repo_cmd.execute('hg push --new-branch --verbose', clone_url)
623 _check_proper_hg_push(stdout, stderr, branch='feature')
628 _check_proper_hg_push(stdout, stderr, branch='feature')
624
629
625 ref = '{}/{}/pull-request/new?branch=feature'.format(
630 ref = '{}/{}/pull-request/new?branch=feature'.format(
626 rc_web_server.host_url(), empty_repo.repo_name)
631 rc_web_server.host_url(), empty_repo.repo_name)
627 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
632 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
628 assert 'remote: RhodeCode: push completed' in stdout
633 assert 'remote: RhodeCode: push completed' in stdout
629
634
630
635 def test_hg_push_shows_pull_request_refs_book(self, backend_hg, rc_web_server, tmpdir):
631 def test_hg_push_shows_pull_request_refs_book(backend_hg, rc_web_server, tmpdir):
632 empty_repo = backend_hg.create_repo()
636 empty_repo = backend_hg.create_repo()
633
637
634 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
638 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
635
639
636 cmd = Command(tmpdir.strpath)
640 cmd = Command(tmpdir.strpath)
637 cmd.execute('hg clone', clone_url)
641 cmd.execute('hg clone', clone_url)
638
642
639 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
643 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
640 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
644 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
641 repo.in_memory_commit.commit(
645 repo.in_memory_commit.commit(
642 message=u'Commit on branch default',
646 message=u'Commit on branch default',
643 author=u'Automatic test',
647 author=u'Automatic test',
644 branch='default')
648 branch='default')
645
649
646 repo_cmd = Command(repo.path)
650 repo_cmd = Command(repo.path)
647 repo_cmd.execute('hg checkout default')
651 repo_cmd.execute('hg checkout default')
648
652
649 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
653 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
650 _check_proper_hg_push(stdout, stderr, branch='default')
654 _check_proper_hg_push(stdout, stderr, branch='default')
651
655
652 ref = '{}/{}/pull-request/new?branch=default'.format(
656 ref = '{}/{}/pull-request/new?branch=default'.format(
653 rc_web_server.host_url(), empty_repo.repo_name)
657 rc_web_server.host_url(), empty_repo.repo_name)
654 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
658 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
655 assert 'remote: RhodeCode: push completed' in stdout
659 assert 'remote: RhodeCode: push completed' in stdout
656
660
657 # add bookmark
661 # add bookmark
658 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
662 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
659 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
663 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
660 repo.in_memory_commit.commit(
664 repo.in_memory_commit.commit(
661 message=u'Commit2 on branch default',
665 message=u'Commit2 on branch default',
662 author=u'Automatic test2',
666 author=u'Automatic test2',
663 branch=u'default')
667 branch=u'default')
664
668
665 repo_cmd = Command(repo.path)
669 repo_cmd = Command(repo.path)
666 repo_cmd.execute('hg checkout default')
670 repo_cmd.execute('hg checkout default')
667 repo_cmd.execute('hg bookmark feature2')
671 repo_cmd.execute('hg bookmark feature2')
668 stdout, stderr = repo_cmd.execute('hg push -B feature2 --verbose', clone_url)
672 stdout, stderr = repo_cmd.execute('hg push -B feature2 --verbose', clone_url)
669 _check_proper_hg_push(stdout, stderr, branch='default')
673 _check_proper_hg_push(stdout, stderr, branch='default')
670
674
671 ref = '{}/{}/pull-request/new?branch=default'.format(
675 ref = '{}/{}/pull-request/new?branch=default'.format(
672 rc_web_server.host_url(), empty_repo.repo_name)
676 rc_web_server.host_url(), empty_repo.repo_name)
673 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
677 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
674 ref = '{}/{}/pull-request/new?bookmark=feature2'.format(
678 ref = '{}/{}/pull-request/new?bookmark=feature2'.format(
675 rc_web_server.host_url(), empty_repo.repo_name)
679 rc_web_server.host_url(), empty_repo.repo_name)
676 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
680 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
677 assert 'remote: RhodeCode: push completed' in stdout
681 assert 'remote: RhodeCode: push completed' in stdout
678 assert 'exporting bookmark feature2' in stdout
682 assert 'exporting bookmark feature2' in stdout
@@ -1,141 +1,146 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Test suite for making push/pull operations, on specially modified INI files
22 Test suite for making push/pull operations, on specially modified INI files
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 import pytest
30 import pytest
31 import requests
31 import requests
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.model.db import Integration
34 from rhodecode.model.db import Integration
35 from rhodecode.model.integration import IntegrationModel
35 from rhodecode.model.integration import IntegrationModel
36 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
37
37
38 from rhodecode.tests import GIT_REPO, HG_REPO
38 from rhodecode.tests import GIT_REPO, HG_REPO
39 from rhodecode.tests.other.vcs_operations import Command, _add_files_and_push
39 from rhodecode.tests.other.vcs_operations import Command, _add_files_and_push
40 from rhodecode.integrations.types.webhook import WebhookIntegrationType
40 from rhodecode.integrations.types.webhook import WebhookIntegrationType
41
41
42
42
43 def check_connection():
43 def check_connection():
44 try:
44 try:
45 response = requests.get('http://httpbin.org')
45 response = requests.get('http://httpbin.org')
46 return response.status_code == 200
46 return response.status_code == 200
47 except Exception as e:
47 except Exception as e:
48 print(e)
48 print(e)
49
49
50 return False
50 return False
51
51
52
52
53 connection_available = pytest.mark.skipif(
53 connection_available = pytest.mark.skipif(
54 not check_connection(), reason="No outside internet connection available")
54 not check_connection(), reason="No outside internet connection available")
55
55
56
56
57 @pytest.fixture
57 @pytest.fixture
58 def enable_webhook_push_integration(request):
58 def enable_webhook_push_integration(request):
59 integration = Integration()
59 integration = Integration()
60 integration.integration_type = WebhookIntegrationType.key
60 integration.integration_type = WebhookIntegrationType.key
61 Session().add(integration)
61 Session().add(integration)
62
62
63 settings = dict(
63 settings = dict(
64 url='http://httpbin.org',
64 url='http://httpbin.org',
65 secret_token='secret',
65 secret_token='secret',
66 username=None,
66 username=None,
67 password=None,
67 password=None,
68 custom_header_key=None,
68 custom_header_key=None,
69 custom_header_val=None,
69 custom_header_val=None,
70 method_type='get',
70 method_type='get',
71 events=[events.RepoPushEvent.name],
71 events=[events.RepoPushEvent.name],
72 log_data=True
72 log_data=True
73 )
73 )
74
74
75 IntegrationModel().update_integration(
75 IntegrationModel().update_integration(
76 integration,
76 integration,
77 name='IntegrationWebhookTest',
77 name='IntegrationWebhookTest',
78 enabled=True,
78 enabled=True,
79 settings=settings,
79 settings=settings,
80 repo=None,
80 repo=None,
81 repo_group=None,
81 repo_group=None,
82 child_repos_only=False,
82 child_repos_only=False,
83 )
83 )
84 Session().commit()
84 Session().commit()
85 integration_id = integration.integration_id
85 integration_id = integration.integration_id
86
86
87 @request.addfinalizer
87 @request.addfinalizer
88 def cleanup():
88 def cleanup():
89 integration = Integration.get(integration_id)
89 integration = Integration.get(integration_id)
90 Session().delete(integration)
90 Session().delete(integration)
91 Session().commit()
91 Session().commit()
92
92
93
93
94 @pytest.fixture(scope="session")
95 def vcs_server_config_override():
96 return ({'server:main': {'workers': 2}},)
97
98
94 @pytest.mark.usefixtures(
99 @pytest.mark.usefixtures(
95 "disable_locking", "disable_anonymous_user",
100 "disable_locking", "disable_anonymous_user",
96 "enable_webhook_push_integration")
101 "enable_webhook_push_integration")
97 class TestVCSOperationsOnCustomIniConfig(object):
102 class TestVCSOperationsOnCustomIniConfig(object):
98
103
99 def test_push_tag_with_commit_hg(self, rc_web_server, tmpdir):
104 def test_push_tag_with_commit_hg(self, rc_web_server, tmpdir):
100 clone_url = rc_web_server.repo_clone_url(HG_REPO)
105 clone_url = rc_web_server.repo_clone_url(HG_REPO)
101 stdout, stderr = Command('/tmp').execute(
106 stdout, stderr = Command('/tmp').execute(
102 'hg clone', clone_url, tmpdir.strpath)
107 'hg clone', clone_url, tmpdir.strpath)
103
108
104 push_url = rc_web_server.repo_clone_url(HG_REPO)
109 push_url = rc_web_server.repo_clone_url(HG_REPO)
105 _add_files_and_push(
110 _add_files_and_push(
106 'hg', tmpdir.strpath, clone_url=push_url,
111 'hg', tmpdir.strpath, clone_url=push_url,
107 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
112 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
108
113
109 rc_log = rc_web_server.get_rc_log()
114 rc_log = rc_web_server.get_rc_log()
110 assert 'ERROR' not in rc_log
115 assert 'ERROR' not in rc_log
111 assert "'name': u'v1.0.0'" in rc_log
116 assert "'name': u'v1.0.0'" in rc_log
112
117
113 def test_push_tag_with_commit_git(
118 def test_push_tag_with_commit_git(
114 self, rc_web_server, tmpdir):
119 self, rc_web_server, tmpdir):
115 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
120 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
116 stdout, stderr = Command('/tmp').execute(
121 stdout, stderr = Command('/tmp').execute(
117 'git clone', clone_url, tmpdir.strpath)
122 'git clone', clone_url, tmpdir.strpath)
118
123
119 push_url = rc_web_server.repo_clone_url(GIT_REPO)
124 push_url = rc_web_server.repo_clone_url(GIT_REPO)
120 _add_files_and_push(
125 _add_files_and_push(
121 'git', tmpdir.strpath, clone_url=push_url,
126 'git', tmpdir.strpath, clone_url=push_url,
122 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
127 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
123
128
124 rc_log = rc_web_server.get_rc_log()
129 rc_log = rc_web_server.get_rc_log()
125 assert 'ERROR' not in rc_log
130 assert 'ERROR' not in rc_log
126 assert "'name': u'v1.0.0'" in rc_log
131 assert "'name': u'v1.0.0'" in rc_log
127
132
128 def test_push_tag_with_no_commit_git(
133 def test_push_tag_with_no_commit_git(
129 self, rc_web_server, tmpdir):
134 self, rc_web_server, tmpdir):
130 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
135 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
131 stdout, stderr = Command('/tmp').execute(
136 stdout, stderr = Command('/tmp').execute(
132 'git clone', clone_url, tmpdir.strpath)
137 'git clone', clone_url, tmpdir.strpath)
133
138
134 push_url = rc_web_server.repo_clone_url(GIT_REPO)
139 push_url = rc_web_server.repo_clone_url(GIT_REPO)
135 _add_files_and_push(
140 _add_files_and_push(
136 'git', tmpdir.strpath, clone_url=push_url,
141 'git', tmpdir.strpath, clone_url=push_url,
137 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
142 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
138
143
139 rc_log = rc_web_server.get_rc_log()
144 rc_log = rc_web_server.get_rc_log()
140 assert 'ERROR' not in rc_log
145 assert 'ERROR' not in rc_log
141 assert "'name': u'v1.0.0'" in rc_log
146 assert "'name': u'v1.0.0'" in rc_log
@@ -1,400 +1,410 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import json
22 import json
23 import platform
23 import platform
24 import socket
24 import socket
25 import tempfile
25 import tempfile
26
26
27 import subprocess32
27 import subprocess32
28 import time
28 import time
29 from urllib2 import urlopen, URLError
29 from urllib2 import urlopen, URLError
30
30
31 import configobj
31 import configobj
32 import pytest
32 import pytest
33
33
34 import pyramid.paster
34 import pyramid.paster
35
35
36 from rhodecode.lib.pyramid_utils import get_app_config
36 from rhodecode.lib.pyramid_utils import get_app_config
37 from rhodecode.tests.fixture import TestINI
37 from rhodecode.tests.fixture import TestINI
38 import rhodecode
38 import rhodecode
39 from rhodecode.tests.other.vcs_operations.conftest import get_host_url, get_port
39 from rhodecode.tests.other.vcs_operations.conftest import get_host_url, get_port
40
40
41 VCSSERVER_LOG = os.path.join(tempfile.gettempdir(), 'rc-vcsserver.log')
41 VCSSERVER_LOG = os.path.join(tempfile.gettempdir(), 'rc-vcsserver.log')
42
42
43
43
44 def _parse_json(value):
44 def _parse_json(value):
45 return json.loads(value) if value else None
45 return json.loads(value) if value else None
46
46
47
47
48 def pytest_addoption(parser):
48 def pytest_addoption(parser):
49 parser.addoption(
49 parser.addoption(
50 '--test-loglevel', dest='test_loglevel',
50 '--test-loglevel', dest='test_loglevel',
51 help="Set default Logging level for tests, warn (default), info, debug")
51 help="Set default Logging level for tests, warn (default), info, debug")
52 group = parser.getgroup('pylons')
52 group = parser.getgroup('pylons')
53 group.addoption(
53 group.addoption(
54 '--with-pylons', dest='pyramid_config',
54 '--with-pylons', dest='pyramid_config',
55 help="Set up a Pylons environment with the specified config file.")
55 help="Set up a Pylons environment with the specified config file.")
56 group.addoption(
56 group.addoption(
57 '--ini-config-override', action='store', type=_parse_json,
57 '--ini-config-override', action='store', type=_parse_json,
58 default=None, dest='pyramid_config_override', help=(
58 default=None, dest='pyramid_config_override', help=(
59 "Overrides the .ini file settings. Should be specified in JSON"
59 "Overrides the .ini file settings. Should be specified in JSON"
60 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
60 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
61 )
61 )
62 )
62 )
63 parser.addini(
63 parser.addini(
64 'pyramid_config',
64 'pyramid_config',
65 "Set up a Pyramid environment with the specified config file.")
65 "Set up a Pyramid environment with the specified config file.")
66
66
67 vcsgroup = parser.getgroup('vcs')
67 vcsgroup = parser.getgroup('vcs')
68 vcsgroup.addoption(
68 vcsgroup.addoption(
69 '--without-vcsserver', dest='with_vcsserver', action='store_false',
69 '--without-vcsserver', dest='with_vcsserver', action='store_false',
70 help="Do not start the VCSServer in a background process.")
70 help="Do not start the VCSServer in a background process.")
71 vcsgroup.addoption(
71 vcsgroup.addoption(
72 '--with-vcsserver-http', dest='vcsserver_config_http',
72 '--with-vcsserver-http', dest='vcsserver_config_http',
73 help="Start the HTTP VCSServer with the specified config file.")
73 help="Start the HTTP VCSServer with the specified config file.")
74 vcsgroup.addoption(
74 vcsgroup.addoption(
75 '--vcsserver-protocol', dest='vcsserver_protocol',
75 '--vcsserver-protocol', dest='vcsserver_protocol',
76 help="Start the VCSServer with HTTP protocol support.")
76 help="Start the VCSServer with HTTP protocol support.")
77 vcsgroup.addoption(
77 vcsgroup.addoption(
78 '--vcsserver-config-override', action='store', type=_parse_json,
78 '--vcsserver-config-override', action='store', type=_parse_json,
79 default=None, dest='vcsserver_config_override', help=(
79 default=None, dest='vcsserver_config_override', help=(
80 "Overrides the .ini file settings for the VCSServer. "
80 "Overrides the .ini file settings for the VCSServer. "
81 "Should be specified in JSON "
81 "Should be specified in JSON "
82 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
82 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
83 )
83 )
84 )
84 )
85 vcsgroup.addoption(
85 vcsgroup.addoption(
86 '--vcsserver-port', action='store', type=int,
86 '--vcsserver-port', action='store', type=int,
87 default=None, help=(
87 default=None, help=(
88 "Allows to set the port of the vcsserver. Useful when testing "
88 "Allows to set the port of the vcsserver. Useful when testing "
89 "against an already running server and random ports cause "
89 "against an already running server and random ports cause "
90 "trouble."))
90 "trouble."))
91 parser.addini(
91 parser.addini(
92 'vcsserver_config_http',
92 'vcsserver_config_http',
93 "Start the HTTP VCSServer with the specified config file.")
93 "Start the HTTP VCSServer with the specified config file.")
94 parser.addini(
94 parser.addini(
95 'vcsserver_protocol',
95 'vcsserver_protocol',
96 "Start the VCSServer with HTTP protocol support.")
96 "Start the VCSServer with HTTP protocol support.")
97
97
98
98
99 @pytest.fixture(scope="session")
100 def vcs_server_config_override(request):
101 """
102 Allows injecting the overrides by specifying this inside test class
103 """
104
105 return ()
106
107
99 @pytest.fixture(scope='session')
108 @pytest.fixture(scope='session')
100 def vcsserver(request, vcsserver_port, vcsserver_factory):
109 def vcsserver(request, vcsserver_port, vcsserver_factory, vcs_server_config_override):
101 """
110 """
102 Session scope VCSServer.
111 Session scope VCSServer.
103
112
104 Tests wich need the VCSServer have to rely on this fixture in order
113 Tests wich need the VCSServer have to rely on this fixture in order
105 to ensure it will be running.
114 to ensure it will be running.
106
115
107 For specific needs, the fixture vcsserver_factory can be used. It allows to
116 For specific needs, the fixture vcsserver_factory can be used. It allows to
108 adjust the configuration file for the test run.
117 adjust the configuration file for the test run.
109
118
110 Command line args:
119 Command line args:
111
120
112 --without-vcsserver: Allows to switch this fixture off. You have to
121 --without-vcsserver: Allows to switch this fixture off. You have to
113 manually start the server.
122 manually start the server.
114
123
115 --vcsserver-port: Will expect the VCSServer to listen on this port.
124 --vcsserver-port: Will expect the VCSServer to listen on this port.
116 """
125 """
117
126
118 if not request.config.getoption('with_vcsserver'):
127 if not request.config.getoption('with_vcsserver'):
119 return None
128 return None
120
129
121 use_http = _use_vcs_http_server(request.config)
130 use_http = _use_vcs_http_server(request.config)
122 return vcsserver_factory(
131 return vcsserver_factory(
123 request, use_http=use_http, vcsserver_port=vcsserver_port)
132 request, use_http=use_http, vcsserver_port=vcsserver_port,
133 overrides=vcs_server_config_override)
124
134
125
135
126 @pytest.fixture(scope='session')
136 @pytest.fixture(scope='session')
127 def vcsserver_factory(tmpdir_factory):
137 def vcsserver_factory(tmpdir_factory):
128 """
138 """
129 Use this if you need a running vcsserver with a special configuration.
139 Use this if you need a running vcsserver with a special configuration.
130 """
140 """
131
141
132 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
142 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
133
143
134 if vcsserver_port is None:
144 if vcsserver_port is None:
135 vcsserver_port = get_available_port()
145 vcsserver_port = get_available_port()
136
146
137 overrides = list(overrides)
147 overrides = list(overrides)
138 if use_http:
148 if use_http:
139 overrides.append({'server:main': {'port': vcsserver_port}})
149 overrides.append({'server:main': {'port': vcsserver_port}})
140 else:
150 else:
141 overrides.append({'DEFAULT': {'port': vcsserver_port}})
151 overrides.append({'DEFAULT': {'port': vcsserver_port}})
142
152
143 if is_cygwin():
153 if is_cygwin():
144 platform_override = {'DEFAULT': {
154 platform_override = {'DEFAULT': {
145 'beaker.cache.repo_object.type': 'nocache'}}
155 'beaker.cache.repo_object.type': 'nocache'}}
146 overrides.append(platform_override)
156 overrides.append(platform_override)
147
157
148 option_name = 'vcsserver_config_http' if use_http else ''
158 option_name = 'vcsserver_config_http' if use_http else ''
149 override_option_name = 'vcsserver_config_override'
159 override_option_name = 'vcsserver_config_override'
150 config_file = get_config(
160 config_file = get_config(
151 request.config, option_name=option_name,
161 request.config, option_name=option_name,
152 override_option_name=override_option_name, overrides=overrides,
162 override_option_name=override_option_name, overrides=overrides,
153 basetemp=tmpdir_factory.getbasetemp().strpath,
163 basetemp=tmpdir_factory.getbasetemp().strpath,
154 prefix='test_vcs_')
164 prefix='test_vcs_')
155
165
156 print("Using the VCSServer configuration:{}".format(config_file))
166 print("Using the VCSServer configuration:{}".format(config_file))
157 ServerClass = HttpVCSServer if use_http else None
167 ServerClass = HttpVCSServer if use_http else None
158 server = ServerClass(config_file)
168 server = ServerClass(config_file)
159 server.start()
169 server.start()
160
170
161 @request.addfinalizer
171 @request.addfinalizer
162 def cleanup():
172 def cleanup():
163 server.shutdown()
173 server.shutdown()
164
174
165 server.wait_until_ready()
175 server.wait_until_ready()
166 return server
176 return server
167
177
168 return factory
178 return factory
169
179
170
180
171 def is_cygwin():
181 def is_cygwin():
172 return 'cygwin' in platform.system().lower()
182 return 'cygwin' in platform.system().lower()
173
183
174
184
175 def _use_vcs_http_server(config):
185 def _use_vcs_http_server(config):
176 protocol_option = 'vcsserver_protocol'
186 protocol_option = 'vcsserver_protocol'
177 protocol = (
187 protocol = (
178 config.getoption(protocol_option) or
188 config.getoption(protocol_option) or
179 config.getini(protocol_option) or
189 config.getini(protocol_option) or
180 'http')
190 'http')
181 return protocol == 'http'
191 return protocol == 'http'
182
192
183
193
184 def _use_log_level(config):
194 def _use_log_level(config):
185 level = config.getoption('test_loglevel') or 'warn'
195 level = config.getoption('test_loglevel') or 'warn'
186 return level.upper()
196 return level.upper()
187
197
188
198
189 class VCSServer(object):
199 class VCSServer(object):
190 """
200 """
191 Represents a running VCSServer instance.
201 Represents a running VCSServer instance.
192 """
202 """
193
203
194 _args = []
204 _args = []
195
205
196 def start(self):
206 def start(self):
197 print("Starting the VCSServer: {}".format(self._args))
207 print("Starting the VCSServer: {}".format(self._args))
198 self.process = subprocess32.Popen(self._args)
208 self.process = subprocess32.Popen(self._args)
199
209
200 def wait_until_ready(self, timeout=30):
210 def wait_until_ready(self, timeout=30):
201 raise NotImplementedError()
211 raise NotImplementedError()
202
212
203 def shutdown(self):
213 def shutdown(self):
204 self.process.kill()
214 self.process.kill()
205
215
206
216
207 class HttpVCSServer(VCSServer):
217 class HttpVCSServer(VCSServer):
208 """
218 """
209 Represents a running VCSServer instance.
219 Represents a running VCSServer instance.
210 """
220 """
211 def __init__(self, config_file):
221 def __init__(self, config_file):
212 self.config_file = config_file
222 self.config_file = config_file
213 config_data = configobj.ConfigObj(config_file)
223 config_data = configobj.ConfigObj(config_file)
214 self._config = config_data['server:main']
224 self._config = config_data['server:main']
215
225
216 args = ['gunicorn', '--workers', '1', '--paste', config_file]
226 args = ['gunicorn', '--paste', config_file]
217 self._args = args
227 self._args = args
218
228
219 @property
229 @property
220 def http_url(self):
230 def http_url(self):
221 template = 'http://{host}:{port}/'
231 template = 'http://{host}:{port}/'
222 return template.format(**self._config)
232 return template.format(**self._config)
223
233
224 def start(self):
234 def start(self):
225 env = os.environ.copy()
235 env = os.environ.copy()
226 host_url = 'http://' + get_host_url(self.config_file)
236 host_url = 'http://' + get_host_url(self.config_file)
227
237
228 rc_log = list(VCSSERVER_LOG.partition('.log'))
238 rc_log = list(VCSSERVER_LOG.partition('.log'))
229 rc_log.insert(1, get_port(self.config_file))
239 rc_log.insert(1, get_port(self.config_file))
230 rc_log = ''.join(rc_log)
240 rc_log = ''.join(rc_log)
231
241
232 server_out = open(rc_log, 'w')
242 server_out = open(rc_log, 'w')
233
243
234 command = ' '.join(self._args)
244 command = ' '.join(self._args)
235 print('Starting rhodecode-vcsserver: {}'.format(host_url))
245 print('rhodecode-vcsserver starting at: {}'.format(host_url))
236 print('Command: {}'.format(command))
246 print('rhodecode-vcsserver command: {}'.format(command))
237 print('Logfile: {}'.format(rc_log))
247 print('rhodecode-vcsserver logfile: {}'.format(rc_log))
238 self.process = subprocess32.Popen(
248 self.process = subprocess32.Popen(
239 self._args, bufsize=0, env=env, stdout=server_out, stderr=server_out)
249 self._args, bufsize=0, env=env, stdout=server_out, stderr=server_out)
240
250
241 def wait_until_ready(self, timeout=30):
251 def wait_until_ready(self, timeout=30):
242 host = self._config['host']
252 host = self._config['host']
243 port = self._config['port']
253 port = self._config['port']
244 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
254 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
245 start = time.time()
255 start = time.time()
246
256
247 while time.time() - start < timeout:
257 while time.time() - start < timeout:
248 try:
258 try:
249 urlopen(status_url)
259 urlopen(status_url)
250 break
260 break
251 except URLError:
261 except URLError:
252 time.sleep(0.2)
262 time.sleep(0.2)
253 else:
263 else:
254 pytest.exit(
264 pytest.exit(
255 "Starting the VCSServer failed or took more than {} "
265 "Starting the VCSServer failed or took more than {} "
256 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
266 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
257
267
258 def shutdown(self):
268 def shutdown(self):
259 self.process.kill()
269 self.process.kill()
260
270
261
271
262 @pytest.fixture(scope='session')
272 @pytest.fixture(scope='session')
263 def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
273 def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
264 option_name = 'pyramid_config'
274 option_name = 'pyramid_config'
265 log_level = _use_log_level(request.config)
275 log_level = _use_log_level(request.config)
266
276
267 overrides = [
277 overrides = [
268 {'server:main': {'port': rcserver_port}},
278 {'server:main': {'port': rcserver_port}},
269 {'app:main': {
279 {'app:main': {
270 'vcs.server': 'localhost:%s' % vcsserver_port,
280 'vcs.server': 'localhost:%s' % vcsserver_port,
271 # johbo: We will always start the VCSServer on our own based on the
281 # johbo: We will always start the VCSServer on our own based on the
272 # fixtures of the test cases. For the test run it must always be
282 # fixtures of the test cases. For the test run it must always be
273 # off in the INI file.
283 # off in the INI file.
274 'vcs.start_server': 'false',
284 'vcs.start_server': 'false',
275 }},
285 }},
276
286
277 {'handler_console': {
287 {'handler_console': {
278 'class ': 'StreamHandler',
288 'class ': 'StreamHandler',
279 'args ': '(sys.stderr,)',
289 'args ': '(sys.stderr,)',
280 'level': log_level,
290 'level': log_level,
281 }},
291 }},
282
292
283 ]
293 ]
284 if _use_vcs_http_server(request.config):
294 if _use_vcs_http_server(request.config):
285 overrides.append({
295 overrides.append({
286 'app:main': {
296 'app:main': {
287 'vcs.server.protocol': 'http',
297 'vcs.server.protocol': 'http',
288 'vcs.scm_app_implementation': 'http',
298 'vcs.scm_app_implementation': 'http',
289 'vcs.hooks.protocol': 'http',
299 'vcs.hooks.protocol': 'http',
290 }
300 }
291 })
301 })
292
302
293 filename = get_config(
303 filename = get_config(
294 request.config, option_name=option_name,
304 request.config, option_name=option_name,
295 override_option_name='{}_override'.format(option_name),
305 override_option_name='{}_override'.format(option_name),
296 overrides=overrides,
306 overrides=overrides,
297 basetemp=tmpdir_factory.getbasetemp().strpath,
307 basetemp=tmpdir_factory.getbasetemp().strpath,
298 prefix='test_rce_')
308 prefix='test_rce_')
299 return filename
309 return filename
300
310
301
311
302 @pytest.fixture(scope='session')
312 @pytest.fixture(scope='session')
303 def ini_settings(ini_config):
313 def ini_settings(ini_config):
304 ini_path = ini_config
314 ini_path = ini_config
305 return get_app_config(ini_path)
315 return get_app_config(ini_path)
306
316
307
317
308 @pytest.fixture(scope='session')
318 @pytest.fixture(scope='session')
309 def rcserver_port(request):
319 def rcserver_port(request):
310 port = get_available_port()
320 port = get_available_port()
311 print('Using rcserver port {}'.format(port))
321 print('Using rcserver port {}'.format(port))
312 return port
322 return port
313
323
314
324
315 @pytest.fixture(scope='session')
325 @pytest.fixture(scope='session')
316 def vcsserver_port(request):
326 def vcsserver_port(request):
317 port = request.config.getoption('--vcsserver-port')
327 port = request.config.getoption('--vcsserver-port')
318 if port is None:
328 if port is None:
319 port = get_available_port()
329 port = get_available_port()
320 print('Using vcsserver port {}'.format(port))
330 print('Using vcsserver port {}'.format(port))
321 return port
331 return port
322
332
323
333
324 def get_available_port():
334 def get_available_port():
325 family = socket.AF_INET
335 family = socket.AF_INET
326 socktype = socket.SOCK_STREAM
336 socktype = socket.SOCK_STREAM
327 host = '127.0.0.1'
337 host = '127.0.0.1'
328
338
329 mysocket = socket.socket(family, socktype)
339 mysocket = socket.socket(family, socktype)
330 mysocket.bind((host, 0))
340 mysocket.bind((host, 0))
331 port = mysocket.getsockname()[1]
341 port = mysocket.getsockname()[1]
332 mysocket.close()
342 mysocket.close()
333 del mysocket
343 del mysocket
334 return port
344 return port
335
345
336
346
337 @pytest.fixture(scope='session')
347 @pytest.fixture(scope='session')
338 def available_port_factory():
348 def available_port_factory():
339 """
349 """
340 Returns a callable which returns free port numbers.
350 Returns a callable which returns free port numbers.
341 """
351 """
342 return get_available_port
352 return get_available_port
343
353
344
354
345 @pytest.fixture
355 @pytest.fixture
346 def available_port(available_port_factory):
356 def available_port(available_port_factory):
347 """
357 """
348 Gives you one free port for the current test.
358 Gives you one free port for the current test.
349
359
350 Uses "available_port_factory" to retrieve the port.
360 Uses "available_port_factory" to retrieve the port.
351 """
361 """
352 return available_port_factory()
362 return available_port_factory()
353
363
354
364
355 @pytest.fixture(scope='session')
365 @pytest.fixture(scope='session')
356 def testini_factory(tmpdir_factory, ini_config):
366 def testini_factory(tmpdir_factory, ini_config):
357 """
367 """
358 Factory to create an INI file based on TestINI.
368 Factory to create an INI file based on TestINI.
359
369
360 It will make sure to place the INI file in the correct directory.
370 It will make sure to place the INI file in the correct directory.
361 """
371 """
362 basetemp = tmpdir_factory.getbasetemp().strpath
372 basetemp = tmpdir_factory.getbasetemp().strpath
363 return TestIniFactory(basetemp, ini_config)
373 return TestIniFactory(basetemp, ini_config)
364
374
365
375
366 class TestIniFactory(object):
376 class TestIniFactory(object):
367
377
368 def __init__(self, basetemp, template_ini):
378 def __init__(self, basetemp, template_ini):
369 self._basetemp = basetemp
379 self._basetemp = basetemp
370 self._template_ini = template_ini
380 self._template_ini = template_ini
371
381
372 def __call__(self, ini_params, new_file_prefix='test'):
382 def __call__(self, ini_params, new_file_prefix='test'):
373 ini_file = TestINI(
383 ini_file = TestINI(
374 self._template_ini, ini_params=ini_params,
384 self._template_ini, ini_params=ini_params,
375 new_file_prefix=new_file_prefix, dir=self._basetemp)
385 new_file_prefix=new_file_prefix, dir=self._basetemp)
376 result = ini_file.create()
386 result = ini_file.create()
377 return result
387 return result
378
388
379
389
380 def get_config(
390 def get_config(
381 config, option_name, override_option_name, overrides=None,
391 config, option_name, override_option_name, overrides=None,
382 basetemp=None, prefix='test'):
392 basetemp=None, prefix='test'):
383 """
393 """
384 Find a configuration file and apply overrides for the given `prefix`.
394 Find a configuration file and apply overrides for the given `prefix`.
385 """
395 """
386 config_file = (
396 config_file = (
387 config.getoption(option_name) or config.getini(option_name))
397 config.getoption(option_name) or config.getini(option_name))
388 if not config_file:
398 if not config_file:
389 pytest.exit(
399 pytest.exit(
390 "Configuration error, could not extract {}.".format(option_name))
400 "Configuration error, could not extract {}.".format(option_name))
391
401
392 overrides = overrides or []
402 overrides = overrides or []
393 config_override = config.getoption(override_option_name)
403 config_override = config.getoption(override_option_name)
394 if config_override:
404 if config_override:
395 overrides.append(config_override)
405 overrides.append(config_override)
396 temp_ini_file = TestINI(
406 temp_ini_file = TestINI(
397 config_file, ini_params=overrides, new_file_prefix=prefix,
407 config_file, ini_params=overrides, new_file_prefix=prefix,
398 dir=basetemp)
408 dir=basetemp)
399
409
400 return temp_ini_file.create()
410 return temp_ini_file.create()
General Comments 0
You need to be logged in to leave comments. Login now