##// END OF EJS Templates
tests: Add an autouse fixture to patch the pyro proxy factory.
Martin Bornhold -
r343:73e7f43f default
parent child Browse files
Show More
@@ -1,1601 +1,1622 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 collections
21 import collections
22 import datetime
22 import datetime
23 import hashlib
23 import hashlib
24 import os
24 import os
25 import re
25 import re
26 import pprint
26 import pprint
27 import shutil
27 import shutil
28 import socket
28 import socket
29 import subprocess
29 import subprocess
30 import time
30 import time
31 import uuid
31 import uuid
32
32
33 import mock
33 import mock
34 import pyramid.testing
34 import pyramid.testing
35 import pytest
35 import pytest
36 import requests
36 import requests
37 from webtest.app import TestApp
37 from webtest.app import TestApp
38
38
39 import rhodecode
39 import rhodecode
40 from rhodecode.model.changeset_status import ChangesetStatusModel
40 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.comment import ChangesetCommentsModel
41 from rhodecode.model.comment import ChangesetCommentsModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
43 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
44 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
44 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.pull_request import PullRequestModel
46 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
51 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.lib.utils import repo2db_mapper
52 from rhodecode.lib.utils import repo2db_mapper
53 from rhodecode.lib.vcs import create_vcsserver_proxy
53 from rhodecode.lib.vcs import create_vcsserver_proxy
54 from rhodecode.lib.vcs.backends import get_backend
54 from rhodecode.lib.vcs.backends import get_backend
55 from rhodecode.lib.vcs.nodes import FileNode
55 from rhodecode.lib.vcs.nodes import FileNode
56 from rhodecode.tests import (
56 from rhodecode.tests import (
57 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
57 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
58 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
58 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
59 TEST_USER_REGULAR_PASS)
59 TEST_USER_REGULAR_PASS)
60 from rhodecode.tests.fixture import Fixture
60 from rhodecode.tests.fixture import Fixture
61
61
62
62
63 def _split_comma(value):
63 def _split_comma(value):
64 return value.split(',')
64 return value.split(',')
65
65
66
66
67 def pytest_addoption(parser):
67 def pytest_addoption(parser):
68 parser.addoption(
68 parser.addoption(
69 '--keep-tmp-path', action='store_true',
69 '--keep-tmp-path', action='store_true',
70 help="Keep the test temporary directories")
70 help="Keep the test temporary directories")
71 parser.addoption(
71 parser.addoption(
72 '--backends', action='store', type=_split_comma,
72 '--backends', action='store', type=_split_comma,
73 default=['git', 'hg', 'svn'],
73 default=['git', 'hg', 'svn'],
74 help="Select which backends to test for backend specific tests.")
74 help="Select which backends to test for backend specific tests.")
75 parser.addoption(
75 parser.addoption(
76 '--dbs', action='store', type=_split_comma,
76 '--dbs', action='store', type=_split_comma,
77 default=['sqlite'],
77 default=['sqlite'],
78 help="Select which database to test for database specific tests. "
78 help="Select which database to test for database specific tests. "
79 "Possible options are sqlite,postgres,mysql")
79 "Possible options are sqlite,postgres,mysql")
80 parser.addoption(
80 parser.addoption(
81 '--appenlight', '--ae', action='store_true',
81 '--appenlight', '--ae', action='store_true',
82 help="Track statistics in appenlight.")
82 help="Track statistics in appenlight.")
83 parser.addoption(
83 parser.addoption(
84 '--appenlight-api-key', '--ae-key',
84 '--appenlight-api-key', '--ae-key',
85 help="API key for Appenlight.")
85 help="API key for Appenlight.")
86 parser.addoption(
86 parser.addoption(
87 '--appenlight-url', '--ae-url',
87 '--appenlight-url', '--ae-url',
88 default="https://ae.rhodecode.com",
88 default="https://ae.rhodecode.com",
89 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
89 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
90 parser.addoption(
90 parser.addoption(
91 '--sqlite-connection-string', action='store',
91 '--sqlite-connection-string', action='store',
92 default='', help="Connection string for the dbs tests with SQLite")
92 default='', help="Connection string for the dbs tests with SQLite")
93 parser.addoption(
93 parser.addoption(
94 '--postgres-connection-string', action='store',
94 '--postgres-connection-string', action='store',
95 default='', help="Connection string for the dbs tests with Postgres")
95 default='', help="Connection string for the dbs tests with Postgres")
96 parser.addoption(
96 parser.addoption(
97 '--mysql-connection-string', action='store',
97 '--mysql-connection-string', action='store',
98 default='', help="Connection string for the dbs tests with MySQL")
98 default='', help="Connection string for the dbs tests with MySQL")
99 parser.addoption(
99 parser.addoption(
100 '--repeat', type=int, default=100,
100 '--repeat', type=int, default=100,
101 help="Number of repetitions in performance tests.")
101 help="Number of repetitions in performance tests.")
102
102
103
103
104 def pytest_configure(config):
104 def pytest_configure(config):
105 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
105 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
106 from rhodecode.config import patches
106 from rhodecode.config import patches
107 patches.kombu_1_5_1_python_2_7_11()
107 patches.kombu_1_5_1_python_2_7_11()
108
108
109
109
110 def pytest_collection_modifyitems(session, config, items):
110 def pytest_collection_modifyitems(session, config, items):
111 # nottest marked, compare nose, used for transition from nose to pytest
111 # nottest marked, compare nose, used for transition from nose to pytest
112 remaining = [
112 remaining = [
113 i for i in items if getattr(i.obj, '__test__', True)]
113 i for i in items if getattr(i.obj, '__test__', True)]
114 items[:] = remaining
114 items[:] = remaining
115
115
116
116
117 def pytest_generate_tests(metafunc):
117 def pytest_generate_tests(metafunc):
118 # Support test generation based on --backend parameter
118 # Support test generation based on --backend parameter
119 if 'backend_alias' in metafunc.fixturenames:
119 if 'backend_alias' in metafunc.fixturenames:
120 backends = get_backends_from_metafunc(metafunc)
120 backends = get_backends_from_metafunc(metafunc)
121 scope = None
121 scope = None
122 if not backends:
122 if not backends:
123 pytest.skip("Not enabled for any of selected backends")
123 pytest.skip("Not enabled for any of selected backends")
124 metafunc.parametrize('backend_alias', backends, scope=scope)
124 metafunc.parametrize('backend_alias', backends, scope=scope)
125 elif hasattr(metafunc.function, 'backends'):
125 elif hasattr(metafunc.function, 'backends'):
126 backends = get_backends_from_metafunc(metafunc)
126 backends = get_backends_from_metafunc(metafunc)
127 if not backends:
127 if not backends:
128 pytest.skip("Not enabled for any of selected backends")
128 pytest.skip("Not enabled for any of selected backends")
129
129
130
130
131 def get_backends_from_metafunc(metafunc):
131 def get_backends_from_metafunc(metafunc):
132 requested_backends = set(metafunc.config.getoption('--backends'))
132 requested_backends = set(metafunc.config.getoption('--backends'))
133 if hasattr(metafunc.function, 'backends'):
133 if hasattr(metafunc.function, 'backends'):
134 # Supported backends by this test function, created from
134 # Supported backends by this test function, created from
135 # pytest.mark.backends
135 # pytest.mark.backends
136 backends = metafunc.function.backends.args
136 backends = metafunc.function.backends.args
137 elif hasattr(metafunc.cls, 'backend_alias'):
137 elif hasattr(metafunc.cls, 'backend_alias'):
138 # Support class attribute "backend_alias", this is mainly
138 # Support class attribute "backend_alias", this is mainly
139 # for legacy reasons for tests not yet using pytest.mark.backends
139 # for legacy reasons for tests not yet using pytest.mark.backends
140 backends = [metafunc.cls.backend_alias]
140 backends = [metafunc.cls.backend_alias]
141 else:
141 else:
142 backends = metafunc.config.getoption('--backends')
142 backends = metafunc.config.getoption('--backends')
143 return requested_backends.intersection(backends)
143 return requested_backends.intersection(backends)
144
144
145
145
146 @pytest.fixture(scope='session', autouse=True)
146 @pytest.fixture(scope='session', autouse=True)
147 def activate_example_rcextensions(request):
147 def activate_example_rcextensions(request):
148 """
148 """
149 Patch in an example rcextensions module which verifies passed in kwargs.
149 Patch in an example rcextensions module which verifies passed in kwargs.
150 """
150 """
151 from rhodecode.tests.other import example_rcextensions
151 from rhodecode.tests.other import example_rcextensions
152
152
153 old_extensions = rhodecode.EXTENSIONS
153 old_extensions = rhodecode.EXTENSIONS
154 rhodecode.EXTENSIONS = example_rcextensions
154 rhodecode.EXTENSIONS = example_rcextensions
155
155
156 @request.addfinalizer
156 @request.addfinalizer
157 def cleanup():
157 def cleanup():
158 rhodecode.EXTENSIONS = old_extensions
158 rhodecode.EXTENSIONS = old_extensions
159
159
160
160
161 @pytest.fixture
161 @pytest.fixture
162 def capture_rcextensions():
162 def capture_rcextensions():
163 """
163 """
164 Returns the recorded calls to entry points in rcextensions.
164 Returns the recorded calls to entry points in rcextensions.
165 """
165 """
166 calls = rhodecode.EXTENSIONS.calls
166 calls = rhodecode.EXTENSIONS.calls
167 calls.clear()
167 calls.clear()
168 # Note: At this moment, it is still the empty dict, but that will
168 # Note: At this moment, it is still the empty dict, but that will
169 # be filled during the test run and since it is a reference this
169 # be filled during the test run and since it is a reference this
170 # is enough to make it work.
170 # is enough to make it work.
171 return calls
171 return calls
172
172
173
173
174 @pytest.fixture(scope='session')
174 @pytest.fixture(scope='session')
175 def http_environ_session():
175 def http_environ_session():
176 """
176 """
177 Allow to use "http_environ" in session scope.
177 Allow to use "http_environ" in session scope.
178 """
178 """
179 return http_environ(
179 return http_environ(
180 http_host_stub=http_host_stub())
180 http_host_stub=http_host_stub())
181
181
182
182
183 @pytest.fixture
183 @pytest.fixture
184 def http_host_stub():
184 def http_host_stub():
185 """
185 """
186 Value of HTTP_HOST in the test run.
186 Value of HTTP_HOST in the test run.
187 """
187 """
188 return 'test.example.com:80'
188 return 'test.example.com:80'
189
189
190
190
191 @pytest.fixture
191 @pytest.fixture
192 def http_environ(http_host_stub):
192 def http_environ(http_host_stub):
193 """
193 """
194 HTTP extra environ keys.
194 HTTP extra environ keys.
195
195
196 User by the test application and as well for setting up the pylons
196 User by the test application and as well for setting up the pylons
197 environment. In the case of the fixture "app" it should be possible
197 environment. In the case of the fixture "app" it should be possible
198 to override this for a specific test case.
198 to override this for a specific test case.
199 """
199 """
200 return {
200 return {
201 'SERVER_NAME': http_host_stub.split(':')[0],
201 'SERVER_NAME': http_host_stub.split(':')[0],
202 'SERVER_PORT': http_host_stub.split(':')[1],
202 'SERVER_PORT': http_host_stub.split(':')[1],
203 'HTTP_HOST': http_host_stub,
203 'HTTP_HOST': http_host_stub,
204 }
204 }
205
205
206
206
207 @pytest.fixture(scope='function')
207 @pytest.fixture(scope='function')
208 def app(request, pylonsapp, http_environ):
208 def app(request, pylonsapp, http_environ):
209 app = TestApp(
209 app = TestApp(
210 pylonsapp,
210 pylonsapp,
211 extra_environ=http_environ)
211 extra_environ=http_environ)
212 if request.cls:
212 if request.cls:
213 request.cls.app = app
213 request.cls.app = app
214 return app
214 return app
215
215
216
216
217 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
217 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
218
218
219
219
220 def _autologin_user(app, *args):
220 def _autologin_user(app, *args):
221 session = login_user_session(app, *args)
221 session = login_user_session(app, *args)
222 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
222 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
223 return LoginData(csrf_token, session['rhodecode_user'])
223 return LoginData(csrf_token, session['rhodecode_user'])
224
224
225
225
226 @pytest.fixture
226 @pytest.fixture
227 def autologin_user(app):
227 def autologin_user(app):
228 """
228 """
229 Utility fixture which makes sure that the admin user is logged in
229 Utility fixture which makes sure that the admin user is logged in
230 """
230 """
231 return _autologin_user(app)
231 return _autologin_user(app)
232
232
233
233
234 @pytest.fixture
234 @pytest.fixture
235 def autologin_regular_user(app):
235 def autologin_regular_user(app):
236 """
236 """
237 Utility fixture which makes sure that the regular user is logged in
237 Utility fixture which makes sure that the regular user is logged in
238 """
238 """
239 return _autologin_user(
239 return _autologin_user(
240 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
240 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
241
241
242
242
243 @pytest.fixture(scope='function')
243 @pytest.fixture(scope='function')
244 def csrf_token(request, autologin_user):
244 def csrf_token(request, autologin_user):
245 return autologin_user.csrf_token
245 return autologin_user.csrf_token
246
246
247
247
248 @pytest.fixture(scope='function')
248 @pytest.fixture(scope='function')
249 def xhr_header(request):
249 def xhr_header(request):
250 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
250 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
251
251
252
252
253 @pytest.fixture
253 @pytest.fixture
254 def real_crypto_backend(monkeypatch):
254 def real_crypto_backend(monkeypatch):
255 """
255 """
256 Switch the production crypto backend on for this test.
256 Switch the production crypto backend on for this test.
257
257
258 During the test run the crypto backend is replaced with a faster
258 During the test run the crypto backend is replaced with a faster
259 implementation based on the MD5 algorithm.
259 implementation based on the MD5 algorithm.
260 """
260 """
261 monkeypatch.setattr(rhodecode, 'is_test', False)
261 monkeypatch.setattr(rhodecode, 'is_test', False)
262
262
263
263
264 @pytest.fixture(scope='class')
264 @pytest.fixture(scope='class')
265 def index_location(request, pylonsapp):
265 def index_location(request, pylonsapp):
266 index_location = pylonsapp.config['app_conf']['search.location']
266 index_location = pylonsapp.config['app_conf']['search.location']
267 if request.cls:
267 if request.cls:
268 request.cls.index_location = index_location
268 request.cls.index_location = index_location
269 return index_location
269 return index_location
270
270
271
271
272 @pytest.fixture(scope='session', autouse=True)
272 @pytest.fixture(scope='session', autouse=True)
273 def tests_tmp_path(request):
273 def tests_tmp_path(request):
274 """
274 """
275 Create temporary directory to be used during the test session.
275 Create temporary directory to be used during the test session.
276 """
276 """
277 if not os.path.exists(TESTS_TMP_PATH):
277 if not os.path.exists(TESTS_TMP_PATH):
278 os.makedirs(TESTS_TMP_PATH)
278 os.makedirs(TESTS_TMP_PATH)
279
279
280 if not request.config.getoption('--keep-tmp-path'):
280 if not request.config.getoption('--keep-tmp-path'):
281 @request.addfinalizer
281 @request.addfinalizer
282 def remove_tmp_path():
282 def remove_tmp_path():
283 shutil.rmtree(TESTS_TMP_PATH)
283 shutil.rmtree(TESTS_TMP_PATH)
284
284
285 return TESTS_TMP_PATH
285 return TESTS_TMP_PATH
286
286
287
287
288 @pytest.fixture(scope='session', autouse=True)
289 def patch_pyro_request_scope_proxy_factory(request):
290 """
291 Patch the pyro proxy factory to always use the same dummy request object
292 when under test. This will return the same pyro proxy on every call.
293 """
294 dummy_request = pyramid.testing.DummyRequest()
295
296 def mocked_call(self, request=None):
297 return self.getProxy(request=dummy_request)
298
299 patcher = mock.patch(
300 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
301 new=mocked_call)
302 patcher.start()
303
304 @request.addfinalizer
305 def undo_patching():
306 patcher.stop()
307
308
288 @pytest.fixture
309 @pytest.fixture
289 def test_repo_group(request):
310 def test_repo_group(request):
290 """
311 """
291 Create a temporary repository group, and destroy it after
312 Create a temporary repository group, and destroy it after
292 usage automatically
313 usage automatically
293 """
314 """
294 fixture = Fixture()
315 fixture = Fixture()
295 repogroupid = 'test_repo_group_%s' % int(time.time())
316 repogroupid = 'test_repo_group_%s' % int(time.time())
296 repo_group = fixture.create_repo_group(repogroupid)
317 repo_group = fixture.create_repo_group(repogroupid)
297
318
298 def _cleanup():
319 def _cleanup():
299 fixture.destroy_repo_group(repogroupid)
320 fixture.destroy_repo_group(repogroupid)
300
321
301 request.addfinalizer(_cleanup)
322 request.addfinalizer(_cleanup)
302 return repo_group
323 return repo_group
303
324
304
325
305 @pytest.fixture
326 @pytest.fixture
306 def test_user_group(request):
327 def test_user_group(request):
307 """
328 """
308 Create a temporary user group, and destroy it after
329 Create a temporary user group, and destroy it after
309 usage automatically
330 usage automatically
310 """
331 """
311 fixture = Fixture()
332 fixture = Fixture()
312 usergroupid = 'test_user_group_%s' % int(time.time())
333 usergroupid = 'test_user_group_%s' % int(time.time())
313 user_group = fixture.create_user_group(usergroupid)
334 user_group = fixture.create_user_group(usergroupid)
314
335
315 def _cleanup():
336 def _cleanup():
316 fixture.destroy_user_group(user_group)
337 fixture.destroy_user_group(user_group)
317
338
318 request.addfinalizer(_cleanup)
339 request.addfinalizer(_cleanup)
319 return user_group
340 return user_group
320
341
321
342
322 @pytest.fixture(scope='session')
343 @pytest.fixture(scope='session')
323 def test_repo(request):
344 def test_repo(request):
324 container = TestRepoContainer()
345 container = TestRepoContainer()
325 request.addfinalizer(container._cleanup)
346 request.addfinalizer(container._cleanup)
326 return container
347 return container
327
348
328
349
329 class TestRepoContainer(object):
350 class TestRepoContainer(object):
330 """
351 """
331 Container for test repositories which are used read only.
352 Container for test repositories which are used read only.
332
353
333 Repositories will be created on demand and re-used during the lifetime
354 Repositories will be created on demand and re-used during the lifetime
334 of this object.
355 of this object.
335
356
336 Usage to get the svn test repository "minimal"::
357 Usage to get the svn test repository "minimal"::
337
358
338 test_repo = TestContainer()
359 test_repo = TestContainer()
339 repo = test_repo('minimal', 'svn')
360 repo = test_repo('minimal', 'svn')
340
361
341 """
362 """
342
363
343 dump_extractors = {
364 dump_extractors = {
344 'git': utils.extract_git_repo_from_dump,
365 'git': utils.extract_git_repo_from_dump,
345 'hg': utils.extract_hg_repo_from_dump,
366 'hg': utils.extract_hg_repo_from_dump,
346 'svn': utils.extract_svn_repo_from_dump,
367 'svn': utils.extract_svn_repo_from_dump,
347 }
368 }
348
369
349 def __init__(self):
370 def __init__(self):
350 self._cleanup_repos = []
371 self._cleanup_repos = []
351 self._fixture = Fixture()
372 self._fixture = Fixture()
352 self._repos = {}
373 self._repos = {}
353
374
354 def __call__(self, dump_name, backend_alias):
375 def __call__(self, dump_name, backend_alias):
355 key = (dump_name, backend_alias)
376 key = (dump_name, backend_alias)
356 if key not in self._repos:
377 if key not in self._repos:
357 repo = self._create_repo(dump_name, backend_alias)
378 repo = self._create_repo(dump_name, backend_alias)
358 self._repos[key] = repo.repo_id
379 self._repos[key] = repo.repo_id
359 return Repository.get(self._repos[key])
380 return Repository.get(self._repos[key])
360
381
361 def _create_repo(self, dump_name, backend_alias):
382 def _create_repo(self, dump_name, backend_alias):
362 repo_name = '%s-%s' % (backend_alias, dump_name)
383 repo_name = '%s-%s' % (backend_alias, dump_name)
363 backend_class = get_backend(backend_alias)
384 backend_class = get_backend(backend_alias)
364 dump_extractor = self.dump_extractors[backend_alias]
385 dump_extractor = self.dump_extractors[backend_alias]
365 repo_path = dump_extractor(dump_name, repo_name)
386 repo_path = dump_extractor(dump_name, repo_name)
366 vcs_repo = backend_class(repo_path)
387 vcs_repo = backend_class(repo_path)
367 repo2db_mapper({repo_name: vcs_repo})
388 repo2db_mapper({repo_name: vcs_repo})
368 repo = RepoModel().get_by_repo_name(repo_name)
389 repo = RepoModel().get_by_repo_name(repo_name)
369 self._cleanup_repos.append(repo_name)
390 self._cleanup_repos.append(repo_name)
370 return repo
391 return repo
371
392
372 def _cleanup(self):
393 def _cleanup(self):
373 for repo_name in reversed(self._cleanup_repos):
394 for repo_name in reversed(self._cleanup_repos):
374 self._fixture.destroy_repo(repo_name)
395 self._fixture.destroy_repo(repo_name)
375
396
376
397
377 @pytest.fixture
398 @pytest.fixture
378 def backend(request, backend_alias, pylonsapp, test_repo):
399 def backend(request, backend_alias, pylonsapp, test_repo):
379 """
400 """
380 Parametrized fixture which represents a single backend implementation.
401 Parametrized fixture which represents a single backend implementation.
381
402
382 It respects the option `--backends` to focus the test run on specific
403 It respects the option `--backends` to focus the test run on specific
383 backend implementations.
404 backend implementations.
384
405
385 It also supports `pytest.mark.xfail_backends` to mark tests as failing
406 It also supports `pytest.mark.xfail_backends` to mark tests as failing
386 for specific backends. This is intended as a utility for incremental
407 for specific backends. This is intended as a utility for incremental
387 development of a new backend implementation.
408 development of a new backend implementation.
388 """
409 """
389 if backend_alias not in request.config.getoption('--backends'):
410 if backend_alias not in request.config.getoption('--backends'):
390 pytest.skip("Backend %s not selected." % (backend_alias, ))
411 pytest.skip("Backend %s not selected." % (backend_alias, ))
391
412
392 utils.check_xfail_backends(request.node, backend_alias)
413 utils.check_xfail_backends(request.node, backend_alias)
393 utils.check_skip_backends(request.node, backend_alias)
414 utils.check_skip_backends(request.node, backend_alias)
394
415
395 repo_name = 'vcs_test_%s' % (backend_alias, )
416 repo_name = 'vcs_test_%s' % (backend_alias, )
396 backend = Backend(
417 backend = Backend(
397 alias=backend_alias,
418 alias=backend_alias,
398 repo_name=repo_name,
419 repo_name=repo_name,
399 test_name=request.node.name,
420 test_name=request.node.name,
400 test_repo_container=test_repo)
421 test_repo_container=test_repo)
401 request.addfinalizer(backend.cleanup)
422 request.addfinalizer(backend.cleanup)
402 return backend
423 return backend
403
424
404
425
405 @pytest.fixture
426 @pytest.fixture
406 def backend_git(request, pylonsapp, test_repo):
427 def backend_git(request, pylonsapp, test_repo):
407 return backend(request, 'git', pylonsapp, test_repo)
428 return backend(request, 'git', pylonsapp, test_repo)
408
429
409
430
410 @pytest.fixture
431 @pytest.fixture
411 def backend_hg(request, pylonsapp, test_repo):
432 def backend_hg(request, pylonsapp, test_repo):
412 return backend(request, 'hg', pylonsapp, test_repo)
433 return backend(request, 'hg', pylonsapp, test_repo)
413
434
414
435
415 @pytest.fixture
436 @pytest.fixture
416 def backend_svn(request, pylonsapp, test_repo):
437 def backend_svn(request, pylonsapp, test_repo):
417 return backend(request, 'svn', pylonsapp, test_repo)
438 return backend(request, 'svn', pylonsapp, test_repo)
418
439
419
440
420 @pytest.fixture
441 @pytest.fixture
421 def backend_random(backend_git):
442 def backend_random(backend_git):
422 """
443 """
423 Use this to express that your tests need "a backend.
444 Use this to express that your tests need "a backend.
424
445
425 A few of our tests need a backend, so that we can run the code. This
446 A few of our tests need a backend, so that we can run the code. This
426 fixture is intended to be used for such cases. It will pick one of the
447 fixture is intended to be used for such cases. It will pick one of the
427 backends and run the tests.
448 backends and run the tests.
428
449
429 The fixture `backend` would run the test multiple times for each
450 The fixture `backend` would run the test multiple times for each
430 available backend which is a pure waste of time if the test is
451 available backend which is a pure waste of time if the test is
431 independent of the backend type.
452 independent of the backend type.
432 """
453 """
433 # TODO: johbo: Change this to pick a random backend
454 # TODO: johbo: Change this to pick a random backend
434 return backend_git
455 return backend_git
435
456
436
457
437 @pytest.fixture
458 @pytest.fixture
438 def backend_stub(backend_git):
459 def backend_stub(backend_git):
439 """
460 """
440 Use this to express that your tests need a backend stub
461 Use this to express that your tests need a backend stub
441
462
442 TODO: mikhail: Implement a real stub logic instead of returning
463 TODO: mikhail: Implement a real stub logic instead of returning
443 a git backend
464 a git backend
444 """
465 """
445 return backend_git
466 return backend_git
446
467
447
468
448 @pytest.fixture
469 @pytest.fixture
449 def repo_stub(backend_stub):
470 def repo_stub(backend_stub):
450 """
471 """
451 Use this to express that your tests need a repository stub
472 Use this to express that your tests need a repository stub
452 """
473 """
453 return backend_stub.create_repo()
474 return backend_stub.create_repo()
454
475
455
476
456 class Backend(object):
477 class Backend(object):
457 """
478 """
458 Represents the test configuration for one supported backend
479 Represents the test configuration for one supported backend
459
480
460 Provides easy access to different test repositories based on
481 Provides easy access to different test repositories based on
461 `__getitem__`. Such repositories will only be created once per test
482 `__getitem__`. Such repositories will only be created once per test
462 session.
483 session.
463 """
484 """
464
485
465 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
486 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
466 _master_repo = None
487 _master_repo = None
467 _commit_ids = {}
488 _commit_ids = {}
468
489
469 def __init__(self, alias, repo_name, test_name, test_repo_container):
490 def __init__(self, alias, repo_name, test_name, test_repo_container):
470 self.alias = alias
491 self.alias = alias
471 self.repo_name = repo_name
492 self.repo_name = repo_name
472 self._cleanup_repos = []
493 self._cleanup_repos = []
473 self._test_name = test_name
494 self._test_name = test_name
474 self._test_repo_container = test_repo_container
495 self._test_repo_container = test_repo_container
475 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
496 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
476 # Fixture will survive in the end.
497 # Fixture will survive in the end.
477 self._fixture = Fixture()
498 self._fixture = Fixture()
478
499
479 def __getitem__(self, key):
500 def __getitem__(self, key):
480 return self._test_repo_container(key, self.alias)
501 return self._test_repo_container(key, self.alias)
481
502
482 @property
503 @property
483 def repo(self):
504 def repo(self):
484 """
505 """
485 Returns the "current" repository. This is the vcs_test repo or the
506 Returns the "current" repository. This is the vcs_test repo or the
486 last repo which has been created with `create_repo`.
507 last repo which has been created with `create_repo`.
487 """
508 """
488 from rhodecode.model.db import Repository
509 from rhodecode.model.db import Repository
489 return Repository.get_by_repo_name(self.repo_name)
510 return Repository.get_by_repo_name(self.repo_name)
490
511
491 @property
512 @property
492 def default_branch_name(self):
513 def default_branch_name(self):
493 VcsRepository = get_backend(self.alias)
514 VcsRepository = get_backend(self.alias)
494 return VcsRepository.DEFAULT_BRANCH_NAME
515 return VcsRepository.DEFAULT_BRANCH_NAME
495
516
496 @property
517 @property
497 def default_head_id(self):
518 def default_head_id(self):
498 """
519 """
499 Returns the default head id of the underlying backend.
520 Returns the default head id of the underlying backend.
500
521
501 This will be the default branch name in case the backend does have a
522 This will be the default branch name in case the backend does have a
502 default branch. In the other cases it will point to a valid head
523 default branch. In the other cases it will point to a valid head
503 which can serve as the base to create a new commit on top of it.
524 which can serve as the base to create a new commit on top of it.
504 """
525 """
505 vcsrepo = self.repo.scm_instance()
526 vcsrepo = self.repo.scm_instance()
506 head_id = (
527 head_id = (
507 vcsrepo.DEFAULT_BRANCH_NAME or
528 vcsrepo.DEFAULT_BRANCH_NAME or
508 vcsrepo.commit_ids[-1])
529 vcsrepo.commit_ids[-1])
509 return head_id
530 return head_id
510
531
511 @property
532 @property
512 def commit_ids(self):
533 def commit_ids(self):
513 """
534 """
514 Returns the list of commits for the last created repository
535 Returns the list of commits for the last created repository
515 """
536 """
516 return self._commit_ids
537 return self._commit_ids
517
538
518 def create_master_repo(self, commits):
539 def create_master_repo(self, commits):
519 """
540 """
520 Create a repository and remember it as a template.
541 Create a repository and remember it as a template.
521
542
522 This allows to easily create derived repositories to construct
543 This allows to easily create derived repositories to construct
523 more complex scenarios for diff, compare and pull requests.
544 more complex scenarios for diff, compare and pull requests.
524
545
525 Returns a commit map which maps from commit message to raw_id.
546 Returns a commit map which maps from commit message to raw_id.
526 """
547 """
527 self._master_repo = self.create_repo(commits=commits)
548 self._master_repo = self.create_repo(commits=commits)
528 return self._commit_ids
549 return self._commit_ids
529
550
530 def create_repo(
551 def create_repo(
531 self, commits=None, number_of_commits=0, heads=None,
552 self, commits=None, number_of_commits=0, heads=None,
532 name_suffix=u'', **kwargs):
553 name_suffix=u'', **kwargs):
533 """
554 """
534 Create a repository and record it for later cleanup.
555 Create a repository and record it for later cleanup.
535
556
536 :param commits: Optional. A sequence of dict instances.
557 :param commits: Optional. A sequence of dict instances.
537 Will add a commit per entry to the new repository.
558 Will add a commit per entry to the new repository.
538 :param number_of_commits: Optional. If set to a number, this number of
559 :param number_of_commits: Optional. If set to a number, this number of
539 commits will be added to the new repository.
560 commits will be added to the new repository.
540 :param heads: Optional. Can be set to a sequence of of commit
561 :param heads: Optional. Can be set to a sequence of of commit
541 names which shall be pulled in from the master repository.
562 names which shall be pulled in from the master repository.
542
563
543 """
564 """
544 self.repo_name = self._next_repo_name() + name_suffix
565 self.repo_name = self._next_repo_name() + name_suffix
545 repo = self._fixture.create_repo(
566 repo = self._fixture.create_repo(
546 self.repo_name, repo_type=self.alias, **kwargs)
567 self.repo_name, repo_type=self.alias, **kwargs)
547 self._cleanup_repos.append(repo.repo_name)
568 self._cleanup_repos.append(repo.repo_name)
548
569
549 commits = commits or [
570 commits = commits or [
550 {'message': 'Commit %s of %s' % (x, self.repo_name)}
571 {'message': 'Commit %s of %s' % (x, self.repo_name)}
551 for x in xrange(number_of_commits)]
572 for x in xrange(number_of_commits)]
552 self._add_commits_to_repo(repo.scm_instance(), commits)
573 self._add_commits_to_repo(repo.scm_instance(), commits)
553 if heads:
574 if heads:
554 self.pull_heads(repo, heads)
575 self.pull_heads(repo, heads)
555
576
556 return repo
577 return repo
557
578
558 def pull_heads(self, repo, heads):
579 def pull_heads(self, repo, heads):
559 """
580 """
560 Make sure that repo contains all commits mentioned in `heads`
581 Make sure that repo contains all commits mentioned in `heads`
561 """
582 """
562 vcsmaster = self._master_repo.scm_instance()
583 vcsmaster = self._master_repo.scm_instance()
563 vcsrepo = repo.scm_instance()
584 vcsrepo = repo.scm_instance()
564 vcsrepo.config.clear_section('hooks')
585 vcsrepo.config.clear_section('hooks')
565 commit_ids = [self._commit_ids[h] for h in heads]
586 commit_ids = [self._commit_ids[h] for h in heads]
566 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
587 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
567
588
568 def create_fork(self):
589 def create_fork(self):
569 repo_to_fork = self.repo_name
590 repo_to_fork = self.repo_name
570 self.repo_name = self._next_repo_name()
591 self.repo_name = self._next_repo_name()
571 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
592 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
572 self._cleanup_repos.append(self.repo_name)
593 self._cleanup_repos.append(self.repo_name)
573 return repo
594 return repo
574
595
575 def new_repo_name(self, suffix=u''):
596 def new_repo_name(self, suffix=u''):
576 self.repo_name = self._next_repo_name() + suffix
597 self.repo_name = self._next_repo_name() + suffix
577 self._cleanup_repos.append(self.repo_name)
598 self._cleanup_repos.append(self.repo_name)
578 return self.repo_name
599 return self.repo_name
579
600
580 def _next_repo_name(self):
601 def _next_repo_name(self):
581 return u"%s_%s" % (
602 return u"%s_%s" % (
582 self.invalid_repo_name.sub(u'_', self._test_name),
603 self.invalid_repo_name.sub(u'_', self._test_name),
583 len(self._cleanup_repos))
604 len(self._cleanup_repos))
584
605
585 def ensure_file(self, filename, content='Test content\n'):
606 def ensure_file(self, filename, content='Test content\n'):
586 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
607 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
587 commits = [
608 commits = [
588 {'added': [
609 {'added': [
589 FileNode(filename, content=content),
610 FileNode(filename, content=content),
590 ]},
611 ]},
591 ]
612 ]
592 self._add_commits_to_repo(self.repo.scm_instance(), commits)
613 self._add_commits_to_repo(self.repo.scm_instance(), commits)
593
614
594 def enable_downloads(self):
615 def enable_downloads(self):
595 repo = self.repo
616 repo = self.repo
596 repo.enable_downloads = True
617 repo.enable_downloads = True
597 Session().add(repo)
618 Session().add(repo)
598 Session().commit()
619 Session().commit()
599
620
600 def cleanup(self):
621 def cleanup(self):
601 for repo_name in reversed(self._cleanup_repos):
622 for repo_name in reversed(self._cleanup_repos):
602 self._fixture.destroy_repo(repo_name)
623 self._fixture.destroy_repo(repo_name)
603
624
604 def _add_commits_to_repo(self, repo, commits):
625 def _add_commits_to_repo(self, repo, commits):
605 if not commits:
626 if not commits:
606 return
627 return
607
628
608 imc = repo.in_memory_commit
629 imc = repo.in_memory_commit
609 commit = None
630 commit = None
610 self._commit_ids = {}
631 self._commit_ids = {}
611
632
612 for idx, commit in enumerate(commits):
633 for idx, commit in enumerate(commits):
613 message = unicode(commit.get('message', 'Commit %s' % idx))
634 message = unicode(commit.get('message', 'Commit %s' % idx))
614
635
615 for node in commit.get('added', []):
636 for node in commit.get('added', []):
616 imc.add(FileNode(node.path, content=node.content))
637 imc.add(FileNode(node.path, content=node.content))
617 for node in commit.get('changed', []):
638 for node in commit.get('changed', []):
618 imc.change(FileNode(node.path, content=node.content))
639 imc.change(FileNode(node.path, content=node.content))
619 for node in commit.get('removed', []):
640 for node in commit.get('removed', []):
620 imc.remove(FileNode(node.path))
641 imc.remove(FileNode(node.path))
621
642
622 parents = [
643 parents = [
623 repo.get_commit(commit_id=self._commit_ids[p])
644 repo.get_commit(commit_id=self._commit_ids[p])
624 for p in commit.get('parents', [])]
645 for p in commit.get('parents', [])]
625
646
626 operations = ('added', 'changed', 'removed')
647 operations = ('added', 'changed', 'removed')
627 if not any((commit.get(o) for o in operations)):
648 if not any((commit.get(o) for o in operations)):
628 imc.add(FileNode('file_%s' % idx, content=message))
649 imc.add(FileNode('file_%s' % idx, content=message))
629
650
630 commit = imc.commit(
651 commit = imc.commit(
631 message=message,
652 message=message,
632 author=unicode(commit.get('author', 'Automatic')),
653 author=unicode(commit.get('author', 'Automatic')),
633 date=commit.get('date'),
654 date=commit.get('date'),
634 branch=commit.get('branch'),
655 branch=commit.get('branch'),
635 parents=parents)
656 parents=parents)
636
657
637 self._commit_ids[commit.message] = commit.raw_id
658 self._commit_ids[commit.message] = commit.raw_id
638
659
639 # Creating refs for Git to allow fetching them from remote repository
660 # Creating refs for Git to allow fetching them from remote repository
640 if self.alias == 'git':
661 if self.alias == 'git':
641 refs = {}
662 refs = {}
642 for message in self._commit_ids:
663 for message in self._commit_ids:
643 # TODO: mikhail: do more special chars replacements
664 # TODO: mikhail: do more special chars replacements
644 ref_name = 'refs/test-refs/{}'.format(
665 ref_name = 'refs/test-refs/{}'.format(
645 message.replace(' ', ''))
666 message.replace(' ', ''))
646 refs[ref_name] = self._commit_ids[message]
667 refs[ref_name] = self._commit_ids[message]
647 self._create_refs(repo, refs)
668 self._create_refs(repo, refs)
648
669
649 return commit
670 return commit
650
671
651 def _create_refs(self, repo, refs):
672 def _create_refs(self, repo, refs):
652 for ref_name in refs:
673 for ref_name in refs:
653 repo.set_refs(ref_name, refs[ref_name])
674 repo.set_refs(ref_name, refs[ref_name])
654
675
655
676
656 @pytest.fixture
677 @pytest.fixture
657 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
678 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
658 """
679 """
659 Parametrized fixture which represents a single vcs backend implementation.
680 Parametrized fixture which represents a single vcs backend implementation.
660
681
661 See the fixture `backend` for more details. This one implements the same
682 See the fixture `backend` for more details. This one implements the same
662 concept, but on vcs level. So it does not provide model instances etc.
683 concept, but on vcs level. So it does not provide model instances etc.
663
684
664 Parameters are generated dynamically, see :func:`pytest_generate_tests`
685 Parameters are generated dynamically, see :func:`pytest_generate_tests`
665 for how this works.
686 for how this works.
666 """
687 """
667 if backend_alias not in request.config.getoption('--backends'):
688 if backend_alias not in request.config.getoption('--backends'):
668 pytest.skip("Backend %s not selected." % (backend_alias, ))
689 pytest.skip("Backend %s not selected." % (backend_alias, ))
669
690
670 utils.check_xfail_backends(request.node, backend_alias)
691 utils.check_xfail_backends(request.node, backend_alias)
671 utils.check_skip_backends(request.node, backend_alias)
692 utils.check_skip_backends(request.node, backend_alias)
672
693
673 repo_name = 'vcs_test_%s' % (backend_alias, )
694 repo_name = 'vcs_test_%s' % (backend_alias, )
674 repo_path = os.path.join(tests_tmp_path, repo_name)
695 repo_path = os.path.join(tests_tmp_path, repo_name)
675 backend = VcsBackend(
696 backend = VcsBackend(
676 alias=backend_alias,
697 alias=backend_alias,
677 repo_path=repo_path,
698 repo_path=repo_path,
678 test_name=request.node.name,
699 test_name=request.node.name,
679 test_repo_container=test_repo)
700 test_repo_container=test_repo)
680 request.addfinalizer(backend.cleanup)
701 request.addfinalizer(backend.cleanup)
681 return backend
702 return backend
682
703
683
704
684 @pytest.fixture
705 @pytest.fixture
685 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
706 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
686 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
707 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
687
708
688
709
689 @pytest.fixture
710 @pytest.fixture
690 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
711 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
691 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
712 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
692
713
693
714
694 @pytest.fixture
715 @pytest.fixture
695 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
716 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
696 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
717 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
697
718
698
719
699 @pytest.fixture
720 @pytest.fixture
700 def vcsbackend_random(vcsbackend_git):
721 def vcsbackend_random(vcsbackend_git):
701 """
722 """
702 Use this to express that your tests need "a vcsbackend".
723 Use this to express that your tests need "a vcsbackend".
703
724
704 The fixture `vcsbackend` would run the test multiple times for each
725 The fixture `vcsbackend` would run the test multiple times for each
705 available vcs backend which is a pure waste of time if the test is
726 available vcs backend which is a pure waste of time if the test is
706 independent of the vcs backend type.
727 independent of the vcs backend type.
707 """
728 """
708 # TODO: johbo: Change this to pick a random backend
729 # TODO: johbo: Change this to pick a random backend
709 return vcsbackend_git
730 return vcsbackend_git
710
731
711
732
712 class VcsBackend(object):
733 class VcsBackend(object):
713 """
734 """
714 Represents the test configuration for one supported vcs backend.
735 Represents the test configuration for one supported vcs backend.
715 """
736 """
716
737
717 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
738 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
718
739
719 def __init__(self, alias, repo_path, test_name, test_repo_container):
740 def __init__(self, alias, repo_path, test_name, test_repo_container):
720 self.alias = alias
741 self.alias = alias
721 self._repo_path = repo_path
742 self._repo_path = repo_path
722 self._cleanup_repos = []
743 self._cleanup_repos = []
723 self._test_name = test_name
744 self._test_name = test_name
724 self._test_repo_container = test_repo_container
745 self._test_repo_container = test_repo_container
725
746
726 def __getitem__(self, key):
747 def __getitem__(self, key):
727 return self._test_repo_container(key, self.alias).scm_instance()
748 return self._test_repo_container(key, self.alias).scm_instance()
728
749
729 @property
750 @property
730 def repo(self):
751 def repo(self):
731 """
752 """
732 Returns the "current" repository. This is the vcs_test repo of the last
753 Returns the "current" repository. This is the vcs_test repo of the last
733 repo which has been created.
754 repo which has been created.
734 """
755 """
735 Repository = get_backend(self.alias)
756 Repository = get_backend(self.alias)
736 return Repository(self._repo_path)
757 return Repository(self._repo_path)
737
758
738 @property
759 @property
739 def backend(self):
760 def backend(self):
740 """
761 """
741 Returns the backend implementation class.
762 Returns the backend implementation class.
742 """
763 """
743 return get_backend(self.alias)
764 return get_backend(self.alias)
744
765
745 def create_repo(self, number_of_commits=0, _clone_repo=None):
766 def create_repo(self, number_of_commits=0, _clone_repo=None):
746 repo_name = self._next_repo_name()
767 repo_name = self._next_repo_name()
747 self._repo_path = get_new_dir(repo_name)
768 self._repo_path = get_new_dir(repo_name)
748 Repository = get_backend(self.alias)
769 Repository = get_backend(self.alias)
749 src_url = None
770 src_url = None
750 if _clone_repo:
771 if _clone_repo:
751 src_url = _clone_repo.path
772 src_url = _clone_repo.path
752 repo = Repository(self._repo_path, create=True, src_url=src_url)
773 repo = Repository(self._repo_path, create=True, src_url=src_url)
753 self._cleanup_repos.append(repo)
774 self._cleanup_repos.append(repo)
754 for idx in xrange(number_of_commits):
775 for idx in xrange(number_of_commits):
755 self.ensure_file(filename='file_%s' % idx, content=repo.name)
776 self.ensure_file(filename='file_%s' % idx, content=repo.name)
756 return repo
777 return repo
757
778
758 def clone_repo(self, repo):
779 def clone_repo(self, repo):
759 return self.create_repo(_clone_repo=repo)
780 return self.create_repo(_clone_repo=repo)
760
781
761 def cleanup(self):
782 def cleanup(self):
762 for repo in self._cleanup_repos:
783 for repo in self._cleanup_repos:
763 shutil.rmtree(repo.path)
784 shutil.rmtree(repo.path)
764
785
765 def new_repo_path(self):
786 def new_repo_path(self):
766 repo_name = self._next_repo_name()
787 repo_name = self._next_repo_name()
767 self._repo_path = get_new_dir(repo_name)
788 self._repo_path = get_new_dir(repo_name)
768 return self._repo_path
789 return self._repo_path
769
790
770 def _next_repo_name(self):
791 def _next_repo_name(self):
771 return "%s_%s" % (
792 return "%s_%s" % (
772 self.invalid_repo_name.sub('_', self._test_name),
793 self.invalid_repo_name.sub('_', self._test_name),
773 len(self._cleanup_repos))
794 len(self._cleanup_repos))
774
795
775 def add_file(self, repo, filename, content='Test content\n'):
796 def add_file(self, repo, filename, content='Test content\n'):
776 imc = repo.in_memory_commit
797 imc = repo.in_memory_commit
777 imc.add(FileNode(filename, content=content))
798 imc.add(FileNode(filename, content=content))
778 imc.commit(
799 imc.commit(
779 message=u'Automatic commit from vcsbackend fixture',
800 message=u'Automatic commit from vcsbackend fixture',
780 author=u'Automatic')
801 author=u'Automatic')
781
802
782 def ensure_file(self, filename, content='Test content\n'):
803 def ensure_file(self, filename, content='Test content\n'):
783 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
804 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
784 self.add_file(self.repo, filename, content)
805 self.add_file(self.repo, filename, content)
785
806
786
807
787 @pytest.fixture
808 @pytest.fixture
788 def reposerver(request):
809 def reposerver(request):
789 """
810 """
790 Allows to serve a backend repository
811 Allows to serve a backend repository
791 """
812 """
792
813
793 repo_server = RepoServer()
814 repo_server = RepoServer()
794 request.addfinalizer(repo_server.cleanup)
815 request.addfinalizer(repo_server.cleanup)
795 return repo_server
816 return repo_server
796
817
797
818
798 class RepoServer(object):
819 class RepoServer(object):
799 """
820 """
800 Utility to serve a local repository for the duration of a test case.
821 Utility to serve a local repository for the duration of a test case.
801
822
802 Supports only Subversion so far.
823 Supports only Subversion so far.
803 """
824 """
804
825
805 url = None
826 url = None
806
827
807 def __init__(self):
828 def __init__(self):
808 self._cleanup_servers = []
829 self._cleanup_servers = []
809
830
810 def serve(self, vcsrepo):
831 def serve(self, vcsrepo):
811 if vcsrepo.alias != 'svn':
832 if vcsrepo.alias != 'svn':
812 raise TypeError("Backend %s not supported" % vcsrepo.alias)
833 raise TypeError("Backend %s not supported" % vcsrepo.alias)
813
834
814 proc = subprocess.Popen(
835 proc = subprocess.Popen(
815 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
836 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
816 '--root', vcsrepo.path])
837 '--root', vcsrepo.path])
817 self._cleanup_servers.append(proc)
838 self._cleanup_servers.append(proc)
818 self.url = 'svn://localhost'
839 self.url = 'svn://localhost'
819
840
820 def cleanup(self):
841 def cleanup(self):
821 for proc in self._cleanup_servers:
842 for proc in self._cleanup_servers:
822 proc.terminate()
843 proc.terminate()
823
844
824
845
825 @pytest.fixture
846 @pytest.fixture
826 def pr_util(backend, request):
847 def pr_util(backend, request):
827 """
848 """
828 Utility for tests of models and for functional tests around pull requests.
849 Utility for tests of models and for functional tests around pull requests.
829
850
830 It gives an instance of :class:`PRTestUtility` which provides various
851 It gives an instance of :class:`PRTestUtility` which provides various
831 utility methods around one pull request.
852 utility methods around one pull request.
832
853
833 This fixture uses `backend` and inherits its parameterization.
854 This fixture uses `backend` and inherits its parameterization.
834 """
855 """
835
856
836 util = PRTestUtility(backend)
857 util = PRTestUtility(backend)
837
858
838 @request.addfinalizer
859 @request.addfinalizer
839 def cleanup():
860 def cleanup():
840 util.cleanup()
861 util.cleanup()
841
862
842 return util
863 return util
843
864
844
865
845 class PRTestUtility(object):
866 class PRTestUtility(object):
846
867
847 pull_request = None
868 pull_request = None
848 pull_request_id = None
869 pull_request_id = None
849 mergeable_patcher = None
870 mergeable_patcher = None
850 mergeable_mock = None
871 mergeable_mock = None
851 notification_patcher = None
872 notification_patcher = None
852
873
853 def __init__(self, backend):
874 def __init__(self, backend):
854 self.backend = backend
875 self.backend = backend
855
876
856 def create_pull_request(
877 def create_pull_request(
857 self, commits=None, target_head=None, source_head=None,
878 self, commits=None, target_head=None, source_head=None,
858 revisions=None, approved=False, author=None, mergeable=False,
879 revisions=None, approved=False, author=None, mergeable=False,
859 enable_notifications=True, name_suffix=u'', reviewers=None,
880 enable_notifications=True, name_suffix=u'', reviewers=None,
860 title=u"Test", description=u"Description"):
881 title=u"Test", description=u"Description"):
861 self.set_mergeable(mergeable)
882 self.set_mergeable(mergeable)
862 if not enable_notifications:
883 if not enable_notifications:
863 # mock notification side effect
884 # mock notification side effect
864 self.notification_patcher = mock.patch(
885 self.notification_patcher = mock.patch(
865 'rhodecode.model.notification.NotificationModel.create')
886 'rhodecode.model.notification.NotificationModel.create')
866 self.notification_patcher.start()
887 self.notification_patcher.start()
867
888
868 if not self.pull_request:
889 if not self.pull_request:
869 if not commits:
890 if not commits:
870 commits = [
891 commits = [
871 {'message': 'c1'},
892 {'message': 'c1'},
872 {'message': 'c2'},
893 {'message': 'c2'},
873 {'message': 'c3'},
894 {'message': 'c3'},
874 ]
895 ]
875 target_head = 'c1'
896 target_head = 'c1'
876 source_head = 'c2'
897 source_head = 'c2'
877 revisions = ['c2']
898 revisions = ['c2']
878
899
879 self.commit_ids = self.backend.create_master_repo(commits)
900 self.commit_ids = self.backend.create_master_repo(commits)
880 self.target_repository = self.backend.create_repo(
901 self.target_repository = self.backend.create_repo(
881 heads=[target_head], name_suffix=name_suffix)
902 heads=[target_head], name_suffix=name_suffix)
882 self.source_repository = self.backend.create_repo(
903 self.source_repository = self.backend.create_repo(
883 heads=[source_head], name_suffix=name_suffix)
904 heads=[source_head], name_suffix=name_suffix)
884 self.author = author or UserModel().get_by_username(
905 self.author = author or UserModel().get_by_username(
885 TEST_USER_ADMIN_LOGIN)
906 TEST_USER_ADMIN_LOGIN)
886
907
887 model = PullRequestModel()
908 model = PullRequestModel()
888 self.create_parameters = {
909 self.create_parameters = {
889 'created_by': self.author,
910 'created_by': self.author,
890 'source_repo': self.source_repository.repo_name,
911 'source_repo': self.source_repository.repo_name,
891 'source_ref': self._default_branch_reference(source_head),
912 'source_ref': self._default_branch_reference(source_head),
892 'target_repo': self.target_repository.repo_name,
913 'target_repo': self.target_repository.repo_name,
893 'target_ref': self._default_branch_reference(target_head),
914 'target_ref': self._default_branch_reference(target_head),
894 'revisions': [self.commit_ids[r] for r in revisions],
915 'revisions': [self.commit_ids[r] for r in revisions],
895 'reviewers': reviewers or self._get_reviewers(),
916 'reviewers': reviewers or self._get_reviewers(),
896 'title': title,
917 'title': title,
897 'description': description,
918 'description': description,
898 }
919 }
899 self.pull_request = model.create(**self.create_parameters)
920 self.pull_request = model.create(**self.create_parameters)
900 assert model.get_versions(self.pull_request) == []
921 assert model.get_versions(self.pull_request) == []
901
922
902 self.pull_request_id = self.pull_request.pull_request_id
923 self.pull_request_id = self.pull_request.pull_request_id
903
924
904 if approved:
925 if approved:
905 self.approve()
926 self.approve()
906
927
907 Session().add(self.pull_request)
928 Session().add(self.pull_request)
908 Session().commit()
929 Session().commit()
909
930
910 return self.pull_request
931 return self.pull_request
911
932
912 def approve(self):
933 def approve(self):
913 self.create_status_votes(
934 self.create_status_votes(
914 ChangesetStatus.STATUS_APPROVED,
935 ChangesetStatus.STATUS_APPROVED,
915 *self.pull_request.reviewers)
936 *self.pull_request.reviewers)
916
937
917 def close(self):
938 def close(self):
918 PullRequestModel().close_pull_request(self.pull_request, self.author)
939 PullRequestModel().close_pull_request(self.pull_request, self.author)
919
940
920 def _default_branch_reference(self, commit_message):
941 def _default_branch_reference(self, commit_message):
921 reference = '%s:%s:%s' % (
942 reference = '%s:%s:%s' % (
922 'branch',
943 'branch',
923 self.backend.default_branch_name,
944 self.backend.default_branch_name,
924 self.commit_ids[commit_message])
945 self.commit_ids[commit_message])
925 return reference
946 return reference
926
947
927 def _get_reviewers(self):
948 def _get_reviewers(self):
928 model = UserModel()
949 model = UserModel()
929 return [
950 return [
930 model.get_by_username(TEST_USER_REGULAR_LOGIN),
951 model.get_by_username(TEST_USER_REGULAR_LOGIN),
931 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
952 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
932 ]
953 ]
933
954
934 def update_source_repository(self, head=None):
955 def update_source_repository(self, head=None):
935 heads = [head or 'c3']
956 heads = [head or 'c3']
936 self.backend.pull_heads(self.source_repository, heads=heads)
957 self.backend.pull_heads(self.source_repository, heads=heads)
937
958
938 def add_one_commit(self, head=None):
959 def add_one_commit(self, head=None):
939 self.update_source_repository(head=head)
960 self.update_source_repository(head=head)
940 old_commit_ids = set(self.pull_request.revisions)
961 old_commit_ids = set(self.pull_request.revisions)
941 PullRequestModel().update_commits(self.pull_request)
962 PullRequestModel().update_commits(self.pull_request)
942 commit_ids = set(self.pull_request.revisions)
963 commit_ids = set(self.pull_request.revisions)
943 new_commit_ids = commit_ids - old_commit_ids
964 new_commit_ids = commit_ids - old_commit_ids
944 assert len(new_commit_ids) == 1
965 assert len(new_commit_ids) == 1
945 return new_commit_ids.pop()
966 return new_commit_ids.pop()
946
967
947 def remove_one_commit(self):
968 def remove_one_commit(self):
948 assert len(self.pull_request.revisions) == 2
969 assert len(self.pull_request.revisions) == 2
949 source_vcs = self.source_repository.scm_instance()
970 source_vcs = self.source_repository.scm_instance()
950 removed_commit_id = source_vcs.commit_ids[-1]
971 removed_commit_id = source_vcs.commit_ids[-1]
951
972
952 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
973 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
953 # remove the if once that's sorted out.
974 # remove the if once that's sorted out.
954 if self.backend.alias == "git":
975 if self.backend.alias == "git":
955 kwargs = {'branch_name': self.backend.default_branch_name}
976 kwargs = {'branch_name': self.backend.default_branch_name}
956 else:
977 else:
957 kwargs = {}
978 kwargs = {}
958 source_vcs.strip(removed_commit_id, **kwargs)
979 source_vcs.strip(removed_commit_id, **kwargs)
959
980
960 PullRequestModel().update_commits(self.pull_request)
981 PullRequestModel().update_commits(self.pull_request)
961 assert len(self.pull_request.revisions) == 1
982 assert len(self.pull_request.revisions) == 1
962 return removed_commit_id
983 return removed_commit_id
963
984
964 def create_comment(self, linked_to=None):
985 def create_comment(self, linked_to=None):
965 comment = ChangesetCommentsModel().create(
986 comment = ChangesetCommentsModel().create(
966 text=u"Test comment",
987 text=u"Test comment",
967 repo=self.target_repository.repo_name,
988 repo=self.target_repository.repo_name,
968 user=self.author,
989 user=self.author,
969 pull_request=self.pull_request)
990 pull_request=self.pull_request)
970 assert comment.pull_request_version_id is None
991 assert comment.pull_request_version_id is None
971
992
972 if linked_to:
993 if linked_to:
973 PullRequestModel()._link_comments_to_version(linked_to)
994 PullRequestModel()._link_comments_to_version(linked_to)
974
995
975 return comment
996 return comment
976
997
977 def create_inline_comment(
998 def create_inline_comment(
978 self, linked_to=None, line_no=u'n1', file_path='file_1'):
999 self, linked_to=None, line_no=u'n1', file_path='file_1'):
979 comment = ChangesetCommentsModel().create(
1000 comment = ChangesetCommentsModel().create(
980 text=u"Test comment",
1001 text=u"Test comment",
981 repo=self.target_repository.repo_name,
1002 repo=self.target_repository.repo_name,
982 user=self.author,
1003 user=self.author,
983 line_no=line_no,
1004 line_no=line_no,
984 f_path=file_path,
1005 f_path=file_path,
985 pull_request=self.pull_request)
1006 pull_request=self.pull_request)
986 assert comment.pull_request_version_id is None
1007 assert comment.pull_request_version_id is None
987
1008
988 if linked_to:
1009 if linked_to:
989 PullRequestModel()._link_comments_to_version(linked_to)
1010 PullRequestModel()._link_comments_to_version(linked_to)
990
1011
991 return comment
1012 return comment
992
1013
993 def create_version_of_pull_request(self):
1014 def create_version_of_pull_request(self):
994 pull_request = self.create_pull_request()
1015 pull_request = self.create_pull_request()
995 version = PullRequestModel()._create_version_from_snapshot(
1016 version = PullRequestModel()._create_version_from_snapshot(
996 pull_request)
1017 pull_request)
997 return version
1018 return version
998
1019
999 def create_status_votes(self, status, *reviewers):
1020 def create_status_votes(self, status, *reviewers):
1000 for reviewer in reviewers:
1021 for reviewer in reviewers:
1001 ChangesetStatusModel().set_status(
1022 ChangesetStatusModel().set_status(
1002 repo=self.pull_request.target_repo,
1023 repo=self.pull_request.target_repo,
1003 status=status,
1024 status=status,
1004 user=reviewer.user_id,
1025 user=reviewer.user_id,
1005 pull_request=self.pull_request)
1026 pull_request=self.pull_request)
1006
1027
1007 def set_mergeable(self, value):
1028 def set_mergeable(self, value):
1008 if not self.mergeable_patcher:
1029 if not self.mergeable_patcher:
1009 self.mergeable_patcher = mock.patch.object(
1030 self.mergeable_patcher = mock.patch.object(
1010 VcsSettingsModel, 'get_general_settings')
1031 VcsSettingsModel, 'get_general_settings')
1011 self.mergeable_mock = self.mergeable_patcher.start()
1032 self.mergeable_mock = self.mergeable_patcher.start()
1012 self.mergeable_mock.return_value = {
1033 self.mergeable_mock.return_value = {
1013 'rhodecode_pr_merge_enabled': value}
1034 'rhodecode_pr_merge_enabled': value}
1014
1035
1015 def cleanup(self):
1036 def cleanup(self):
1016 # In case the source repository is already cleaned up, the pull
1037 # In case the source repository is already cleaned up, the pull
1017 # request will already be deleted.
1038 # request will already be deleted.
1018 pull_request = PullRequest().get(self.pull_request_id)
1039 pull_request = PullRequest().get(self.pull_request_id)
1019 if pull_request:
1040 if pull_request:
1020 PullRequestModel().delete(pull_request)
1041 PullRequestModel().delete(pull_request)
1021 Session().commit()
1042 Session().commit()
1022
1043
1023 if self.notification_patcher:
1044 if self.notification_patcher:
1024 self.notification_patcher.stop()
1045 self.notification_patcher.stop()
1025
1046
1026 if self.mergeable_patcher:
1047 if self.mergeable_patcher:
1027 self.mergeable_patcher.stop()
1048 self.mergeable_patcher.stop()
1028
1049
1029
1050
1030 @pytest.fixture
1051 @pytest.fixture
1031 def user_admin(pylonsapp):
1052 def user_admin(pylonsapp):
1032 """
1053 """
1033 Provides the default admin test user as an instance of `db.User`.
1054 Provides the default admin test user as an instance of `db.User`.
1034 """
1055 """
1035 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1056 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1036 return user
1057 return user
1037
1058
1038
1059
1039 @pytest.fixture
1060 @pytest.fixture
1040 def user_regular(pylonsapp):
1061 def user_regular(pylonsapp):
1041 """
1062 """
1042 Provides the default regular test user as an instance of `db.User`.
1063 Provides the default regular test user as an instance of `db.User`.
1043 """
1064 """
1044 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1065 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1045 return user
1066 return user
1046
1067
1047
1068
1048 @pytest.fixture
1069 @pytest.fixture
1049 def user_util(request, pylonsapp):
1070 def user_util(request, pylonsapp):
1050 """
1071 """
1051 Provides a wired instance of `UserUtility` with integrated cleanup.
1072 Provides a wired instance of `UserUtility` with integrated cleanup.
1052 """
1073 """
1053 utility = UserUtility(test_name=request.node.name)
1074 utility = UserUtility(test_name=request.node.name)
1054 request.addfinalizer(utility.cleanup)
1075 request.addfinalizer(utility.cleanup)
1055 return utility
1076 return utility
1056
1077
1057
1078
1058 # TODO: johbo: Split this up into utilities per domain or something similar
1079 # TODO: johbo: Split this up into utilities per domain or something similar
1059 class UserUtility(object):
1080 class UserUtility(object):
1060
1081
1061 def __init__(self, test_name="test"):
1082 def __init__(self, test_name="test"):
1062 self._test_name = test_name
1083 self._test_name = test_name
1063 self.fixture = Fixture()
1084 self.fixture = Fixture()
1064 self.repo_group_ids = []
1085 self.repo_group_ids = []
1065 self.user_ids = []
1086 self.user_ids = []
1066 self.user_group_ids = []
1087 self.user_group_ids = []
1067 self.user_repo_permission_ids = []
1088 self.user_repo_permission_ids = []
1068 self.user_group_repo_permission_ids = []
1089 self.user_group_repo_permission_ids = []
1069 self.user_repo_group_permission_ids = []
1090 self.user_repo_group_permission_ids = []
1070 self.user_group_repo_group_permission_ids = []
1091 self.user_group_repo_group_permission_ids = []
1071 self.user_user_group_permission_ids = []
1092 self.user_user_group_permission_ids = []
1072 self.user_group_user_group_permission_ids = []
1093 self.user_group_user_group_permission_ids = []
1073 self.user_permissions = []
1094 self.user_permissions = []
1074
1095
1075 def create_repo_group(
1096 def create_repo_group(
1076 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1097 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1077 group_name = "{prefix}_repogroup_{count}".format(
1098 group_name = "{prefix}_repogroup_{count}".format(
1078 prefix=self._test_name,
1099 prefix=self._test_name,
1079 count=len(self.repo_group_ids))
1100 count=len(self.repo_group_ids))
1080 repo_group = self.fixture.create_repo_group(
1101 repo_group = self.fixture.create_repo_group(
1081 group_name, cur_user=owner)
1102 group_name, cur_user=owner)
1082 if auto_cleanup:
1103 if auto_cleanup:
1083 self.repo_group_ids.append(repo_group.group_id)
1104 self.repo_group_ids.append(repo_group.group_id)
1084 return repo_group
1105 return repo_group
1085
1106
1086 def create_user(self, auto_cleanup=True, **kwargs):
1107 def create_user(self, auto_cleanup=True, **kwargs):
1087 user_name = "{prefix}_user_{count}".format(
1108 user_name = "{prefix}_user_{count}".format(
1088 prefix=self._test_name,
1109 prefix=self._test_name,
1089 count=len(self.user_ids))
1110 count=len(self.user_ids))
1090 user = self.fixture.create_user(user_name, **kwargs)
1111 user = self.fixture.create_user(user_name, **kwargs)
1091 if auto_cleanup:
1112 if auto_cleanup:
1092 self.user_ids.append(user.user_id)
1113 self.user_ids.append(user.user_id)
1093 return user
1114 return user
1094
1115
1095 def create_user_with_group(self):
1116 def create_user_with_group(self):
1096 user = self.create_user()
1117 user = self.create_user()
1097 user_group = self.create_user_group(members=[user])
1118 user_group = self.create_user_group(members=[user])
1098 return user, user_group
1119 return user, user_group
1099
1120
1100 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1121 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1101 group_name = "{prefix}_usergroup_{count}".format(
1122 group_name = "{prefix}_usergroup_{count}".format(
1102 prefix=self._test_name,
1123 prefix=self._test_name,
1103 count=len(self.user_group_ids))
1124 count=len(self.user_group_ids))
1104 user_group = self.fixture.create_user_group(group_name, **kwargs)
1125 user_group = self.fixture.create_user_group(group_name, **kwargs)
1105 if auto_cleanup:
1126 if auto_cleanup:
1106 self.user_group_ids.append(user_group.users_group_id)
1127 self.user_group_ids.append(user_group.users_group_id)
1107 if members:
1128 if members:
1108 for user in members:
1129 for user in members:
1109 UserGroupModel().add_user_to_group(user_group, user)
1130 UserGroupModel().add_user_to_group(user_group, user)
1110 return user_group
1131 return user_group
1111
1132
1112 def grant_user_permission(self, user_name, permission_name):
1133 def grant_user_permission(self, user_name, permission_name):
1113 self._inherit_default_user_permissions(user_name, False)
1134 self._inherit_default_user_permissions(user_name, False)
1114 self.user_permissions.append((user_name, permission_name))
1135 self.user_permissions.append((user_name, permission_name))
1115
1136
1116 def grant_user_permission_to_repo_group(
1137 def grant_user_permission_to_repo_group(
1117 self, repo_group, user, permission_name):
1138 self, repo_group, user, permission_name):
1118 permission = RepoGroupModel().grant_user_permission(
1139 permission = RepoGroupModel().grant_user_permission(
1119 repo_group, user, permission_name)
1140 repo_group, user, permission_name)
1120 self.user_repo_group_permission_ids.append(
1141 self.user_repo_group_permission_ids.append(
1121 (repo_group.group_id, user.user_id))
1142 (repo_group.group_id, user.user_id))
1122 return permission
1143 return permission
1123
1144
1124 def grant_user_group_permission_to_repo_group(
1145 def grant_user_group_permission_to_repo_group(
1125 self, repo_group, user_group, permission_name):
1146 self, repo_group, user_group, permission_name):
1126 permission = RepoGroupModel().grant_user_group_permission(
1147 permission = RepoGroupModel().grant_user_group_permission(
1127 repo_group, user_group, permission_name)
1148 repo_group, user_group, permission_name)
1128 self.user_group_repo_group_permission_ids.append(
1149 self.user_group_repo_group_permission_ids.append(
1129 (repo_group.group_id, user_group.users_group_id))
1150 (repo_group.group_id, user_group.users_group_id))
1130 return permission
1151 return permission
1131
1152
1132 def grant_user_permission_to_repo(
1153 def grant_user_permission_to_repo(
1133 self, repo, user, permission_name):
1154 self, repo, user, permission_name):
1134 permission = RepoModel().grant_user_permission(
1155 permission = RepoModel().grant_user_permission(
1135 repo, user, permission_name)
1156 repo, user, permission_name)
1136 self.user_repo_permission_ids.append(
1157 self.user_repo_permission_ids.append(
1137 (repo.repo_id, user.user_id))
1158 (repo.repo_id, user.user_id))
1138 return permission
1159 return permission
1139
1160
1140 def grant_user_group_permission_to_repo(
1161 def grant_user_group_permission_to_repo(
1141 self, repo, user_group, permission_name):
1162 self, repo, user_group, permission_name):
1142 permission = RepoModel().grant_user_group_permission(
1163 permission = RepoModel().grant_user_group_permission(
1143 repo, user_group, permission_name)
1164 repo, user_group, permission_name)
1144 self.user_group_repo_permission_ids.append(
1165 self.user_group_repo_permission_ids.append(
1145 (repo.repo_id, user_group.users_group_id))
1166 (repo.repo_id, user_group.users_group_id))
1146 return permission
1167 return permission
1147
1168
1148 def grant_user_permission_to_user_group(
1169 def grant_user_permission_to_user_group(
1149 self, target_user_group, user, permission_name):
1170 self, target_user_group, user, permission_name):
1150 permission = UserGroupModel().grant_user_permission(
1171 permission = UserGroupModel().grant_user_permission(
1151 target_user_group, user, permission_name)
1172 target_user_group, user, permission_name)
1152 self.user_user_group_permission_ids.append(
1173 self.user_user_group_permission_ids.append(
1153 (target_user_group.users_group_id, user.user_id))
1174 (target_user_group.users_group_id, user.user_id))
1154 return permission
1175 return permission
1155
1176
1156 def grant_user_group_permission_to_user_group(
1177 def grant_user_group_permission_to_user_group(
1157 self, target_user_group, user_group, permission_name):
1178 self, target_user_group, user_group, permission_name):
1158 permission = UserGroupModel().grant_user_group_permission(
1179 permission = UserGroupModel().grant_user_group_permission(
1159 target_user_group, user_group, permission_name)
1180 target_user_group, user_group, permission_name)
1160 self.user_group_user_group_permission_ids.append(
1181 self.user_group_user_group_permission_ids.append(
1161 (target_user_group.users_group_id, user_group.users_group_id))
1182 (target_user_group.users_group_id, user_group.users_group_id))
1162 return permission
1183 return permission
1163
1184
1164 def revoke_user_permission(self, user_name, permission_name):
1185 def revoke_user_permission(self, user_name, permission_name):
1165 self._inherit_default_user_permissions(user_name, True)
1186 self._inherit_default_user_permissions(user_name, True)
1166 UserModel().revoke_perm(user_name, permission_name)
1187 UserModel().revoke_perm(user_name, permission_name)
1167
1188
1168 def _inherit_default_user_permissions(self, user_name, value):
1189 def _inherit_default_user_permissions(self, user_name, value):
1169 user = UserModel().get_by_username(user_name)
1190 user = UserModel().get_by_username(user_name)
1170 user.inherit_default_permissions = value
1191 user.inherit_default_permissions = value
1171 Session.add(user)
1192 Session.add(user)
1172 Session.commit()
1193 Session.commit()
1173
1194
1174 def cleanup(self):
1195 def cleanup(self):
1175 self._cleanup_permissions()
1196 self._cleanup_permissions()
1176 self._cleanup_repo_groups()
1197 self._cleanup_repo_groups()
1177 self._cleanup_user_groups()
1198 self._cleanup_user_groups()
1178 self._cleanup_users()
1199 self._cleanup_users()
1179
1200
1180 def _cleanup_permissions(self):
1201 def _cleanup_permissions(self):
1181 if self.user_permissions:
1202 if self.user_permissions:
1182 for user_name, permission_name in self.user_permissions:
1203 for user_name, permission_name in self.user_permissions:
1183 self.revoke_user_permission(user_name, permission_name)
1204 self.revoke_user_permission(user_name, permission_name)
1184
1205
1185 for permission in self.user_repo_permission_ids:
1206 for permission in self.user_repo_permission_ids:
1186 RepoModel().revoke_user_permission(*permission)
1207 RepoModel().revoke_user_permission(*permission)
1187
1208
1188 for permission in self.user_group_repo_permission_ids:
1209 for permission in self.user_group_repo_permission_ids:
1189 RepoModel().revoke_user_group_permission(*permission)
1210 RepoModel().revoke_user_group_permission(*permission)
1190
1211
1191 for permission in self.user_repo_group_permission_ids:
1212 for permission in self.user_repo_group_permission_ids:
1192 RepoGroupModel().revoke_user_permission(*permission)
1213 RepoGroupModel().revoke_user_permission(*permission)
1193
1214
1194 for permission in self.user_group_repo_group_permission_ids:
1215 for permission in self.user_group_repo_group_permission_ids:
1195 RepoGroupModel().revoke_user_group_permission(*permission)
1216 RepoGroupModel().revoke_user_group_permission(*permission)
1196
1217
1197 for permission in self.user_user_group_permission_ids:
1218 for permission in self.user_user_group_permission_ids:
1198 UserGroupModel().revoke_user_permission(*permission)
1219 UserGroupModel().revoke_user_permission(*permission)
1199
1220
1200 for permission in self.user_group_user_group_permission_ids:
1221 for permission in self.user_group_user_group_permission_ids:
1201 UserGroupModel().revoke_user_group_permission(*permission)
1222 UserGroupModel().revoke_user_group_permission(*permission)
1202
1223
1203 def _cleanup_repo_groups(self):
1224 def _cleanup_repo_groups(self):
1204 def _repo_group_compare(first_group_id, second_group_id):
1225 def _repo_group_compare(first_group_id, second_group_id):
1205 """
1226 """
1206 Gives higher priority to the groups with the most complex paths
1227 Gives higher priority to the groups with the most complex paths
1207 """
1228 """
1208 first_group = RepoGroup.get(first_group_id)
1229 first_group = RepoGroup.get(first_group_id)
1209 second_group = RepoGroup.get(second_group_id)
1230 second_group = RepoGroup.get(second_group_id)
1210 first_group_parts = (
1231 first_group_parts = (
1211 len(first_group.group_name.split('/')) if first_group else 0)
1232 len(first_group.group_name.split('/')) if first_group else 0)
1212 second_group_parts = (
1233 second_group_parts = (
1213 len(second_group.group_name.split('/')) if second_group else 0)
1234 len(second_group.group_name.split('/')) if second_group else 0)
1214 return cmp(second_group_parts, first_group_parts)
1235 return cmp(second_group_parts, first_group_parts)
1215
1236
1216 sorted_repo_group_ids = sorted(
1237 sorted_repo_group_ids = sorted(
1217 self.repo_group_ids, cmp=_repo_group_compare)
1238 self.repo_group_ids, cmp=_repo_group_compare)
1218 for repo_group_id in sorted_repo_group_ids:
1239 for repo_group_id in sorted_repo_group_ids:
1219 self.fixture.destroy_repo_group(repo_group_id)
1240 self.fixture.destroy_repo_group(repo_group_id)
1220
1241
1221 def _cleanup_user_groups(self):
1242 def _cleanup_user_groups(self):
1222 def _user_group_compare(first_group_id, second_group_id):
1243 def _user_group_compare(first_group_id, second_group_id):
1223 """
1244 """
1224 Gives higher priority to the groups with the most complex paths
1245 Gives higher priority to the groups with the most complex paths
1225 """
1246 """
1226 first_group = UserGroup.get(first_group_id)
1247 first_group = UserGroup.get(first_group_id)
1227 second_group = UserGroup.get(second_group_id)
1248 second_group = UserGroup.get(second_group_id)
1228 first_group_parts = (
1249 first_group_parts = (
1229 len(first_group.users_group_name.split('/'))
1250 len(first_group.users_group_name.split('/'))
1230 if first_group else 0)
1251 if first_group else 0)
1231 second_group_parts = (
1252 second_group_parts = (
1232 len(second_group.users_group_name.split('/'))
1253 len(second_group.users_group_name.split('/'))
1233 if second_group else 0)
1254 if second_group else 0)
1234 return cmp(second_group_parts, first_group_parts)
1255 return cmp(second_group_parts, first_group_parts)
1235
1256
1236 sorted_user_group_ids = sorted(
1257 sorted_user_group_ids = sorted(
1237 self.user_group_ids, cmp=_user_group_compare)
1258 self.user_group_ids, cmp=_user_group_compare)
1238 for user_group_id in sorted_user_group_ids:
1259 for user_group_id in sorted_user_group_ids:
1239 self.fixture.destroy_user_group(user_group_id)
1260 self.fixture.destroy_user_group(user_group_id)
1240
1261
1241 def _cleanup_users(self):
1262 def _cleanup_users(self):
1242 for user_id in self.user_ids:
1263 for user_id in self.user_ids:
1243 self.fixture.destroy_user(user_id)
1264 self.fixture.destroy_user(user_id)
1244
1265
1245
1266
1246 # TODO: Think about moving this into a pytest-pyro package and make it a
1267 # TODO: Think about moving this into a pytest-pyro package and make it a
1247 # pytest plugin
1268 # pytest plugin
1248 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1269 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1249 def pytest_runtest_makereport(item, call):
1270 def pytest_runtest_makereport(item, call):
1250 """
1271 """
1251 Adding the remote traceback if the exception has this information.
1272 Adding the remote traceback if the exception has this information.
1252
1273
1253 Pyro4 attaches this information as the attribute `_pyroTraceback`
1274 Pyro4 attaches this information as the attribute `_pyroTraceback`
1254 to the exception instance.
1275 to the exception instance.
1255 """
1276 """
1256 outcome = yield
1277 outcome = yield
1257 report = outcome.get_result()
1278 report = outcome.get_result()
1258 if call.excinfo:
1279 if call.excinfo:
1259 _add_pyro_remote_traceback(report, call.excinfo.value)
1280 _add_pyro_remote_traceback(report, call.excinfo.value)
1260
1281
1261
1282
1262 def _add_pyro_remote_traceback(report, exc):
1283 def _add_pyro_remote_traceback(report, exc):
1263 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1284 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1264
1285
1265 if pyro_traceback:
1286 if pyro_traceback:
1266 traceback = ''.join(pyro_traceback)
1287 traceback = ''.join(pyro_traceback)
1267 section = 'Pyro4 remote traceback ' + report.when
1288 section = 'Pyro4 remote traceback ' + report.when
1268 report.sections.append((section, traceback))
1289 report.sections.append((section, traceback))
1269
1290
1270
1291
1271 @pytest.fixture(scope='session')
1292 @pytest.fixture(scope='session')
1272 def testrun():
1293 def testrun():
1273 return {
1294 return {
1274 'uuid': uuid.uuid4(),
1295 'uuid': uuid.uuid4(),
1275 'start': datetime.datetime.utcnow().isoformat(),
1296 'start': datetime.datetime.utcnow().isoformat(),
1276 'timestamp': int(time.time()),
1297 'timestamp': int(time.time()),
1277 }
1298 }
1278
1299
1279
1300
1280 @pytest.fixture(autouse=True)
1301 @pytest.fixture(autouse=True)
1281 def collect_appenlight_stats(request, testrun):
1302 def collect_appenlight_stats(request, testrun):
1282 """
1303 """
1283 This fixture reports memory consumtion of single tests.
1304 This fixture reports memory consumtion of single tests.
1284
1305
1285 It gathers data based on `psutil` and sends them to Appenlight. The option
1306 It gathers data based on `psutil` and sends them to Appenlight. The option
1286 ``--ae`` has te be used to enable this fixture and the API key for your
1307 ``--ae`` has te be used to enable this fixture and the API key for your
1287 application has to be provided in ``--ae-key``.
1308 application has to be provided in ``--ae-key``.
1288 """
1309 """
1289 try:
1310 try:
1290 # cygwin cannot have yet psutil support.
1311 # cygwin cannot have yet psutil support.
1291 import psutil
1312 import psutil
1292 except ImportError:
1313 except ImportError:
1293 return
1314 return
1294
1315
1295 if not request.config.getoption('--appenlight'):
1316 if not request.config.getoption('--appenlight'):
1296 return
1317 return
1297 else:
1318 else:
1298 # Only request the pylonsapp fixture if appenlight tracking is
1319 # Only request the pylonsapp fixture if appenlight tracking is
1299 # enabled. This will speed up a test run of unit tests by 2 to 3
1320 # enabled. This will speed up a test run of unit tests by 2 to 3
1300 # seconds if appenlight is not enabled.
1321 # seconds if appenlight is not enabled.
1301 pylonsapp = request.getfuncargvalue("pylonsapp")
1322 pylonsapp = request.getfuncargvalue("pylonsapp")
1302 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1323 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1303 client = AppenlightClient(
1324 client = AppenlightClient(
1304 url=url,
1325 url=url,
1305 api_key=request.config.getoption('--appenlight-api-key'),
1326 api_key=request.config.getoption('--appenlight-api-key'),
1306 namespace=request.node.nodeid,
1327 namespace=request.node.nodeid,
1307 request=str(testrun['uuid']),
1328 request=str(testrun['uuid']),
1308 testrun=testrun)
1329 testrun=testrun)
1309
1330
1310 client.collect({
1331 client.collect({
1311 'message': "Starting",
1332 'message': "Starting",
1312 })
1333 })
1313
1334
1314 server_and_port = pylonsapp.config['vcs.server']
1335 server_and_port = pylonsapp.config['vcs.server']
1315 server = create_vcsserver_proxy(server_and_port)
1336 server = create_vcsserver_proxy(server_and_port)
1316 with server:
1337 with server:
1317 vcs_pid = server.get_pid()
1338 vcs_pid = server.get_pid()
1318 server.run_gc()
1339 server.run_gc()
1319 vcs_process = psutil.Process(vcs_pid)
1340 vcs_process = psutil.Process(vcs_pid)
1320 mem = vcs_process.memory_info()
1341 mem = vcs_process.memory_info()
1321 client.tag_before('vcsserver.rss', mem.rss)
1342 client.tag_before('vcsserver.rss', mem.rss)
1322 client.tag_before('vcsserver.vms', mem.vms)
1343 client.tag_before('vcsserver.vms', mem.vms)
1323
1344
1324 test_process = psutil.Process()
1345 test_process = psutil.Process()
1325 mem = test_process.memory_info()
1346 mem = test_process.memory_info()
1326 client.tag_before('test.rss', mem.rss)
1347 client.tag_before('test.rss', mem.rss)
1327 client.tag_before('test.vms', mem.vms)
1348 client.tag_before('test.vms', mem.vms)
1328
1349
1329 client.tag_before('time', time.time())
1350 client.tag_before('time', time.time())
1330
1351
1331 @request.addfinalizer
1352 @request.addfinalizer
1332 def send_stats():
1353 def send_stats():
1333 client.tag_after('time', time.time())
1354 client.tag_after('time', time.time())
1334 with server:
1355 with server:
1335 gc_stats = server.run_gc()
1356 gc_stats = server.run_gc()
1336 for tag, value in gc_stats.items():
1357 for tag, value in gc_stats.items():
1337 client.tag_after(tag, value)
1358 client.tag_after(tag, value)
1338 mem = vcs_process.memory_info()
1359 mem = vcs_process.memory_info()
1339 client.tag_after('vcsserver.rss', mem.rss)
1360 client.tag_after('vcsserver.rss', mem.rss)
1340 client.tag_after('vcsserver.vms', mem.vms)
1361 client.tag_after('vcsserver.vms', mem.vms)
1341
1362
1342 mem = test_process.memory_info()
1363 mem = test_process.memory_info()
1343 client.tag_after('test.rss', mem.rss)
1364 client.tag_after('test.rss', mem.rss)
1344 client.tag_after('test.vms', mem.vms)
1365 client.tag_after('test.vms', mem.vms)
1345
1366
1346 client.collect({
1367 client.collect({
1347 'message': "Finished",
1368 'message': "Finished",
1348 })
1369 })
1349 client.send_stats()
1370 client.send_stats()
1350
1371
1351 return client
1372 return client
1352
1373
1353
1374
1354 class AppenlightClient():
1375 class AppenlightClient():
1355
1376
1356 url_template = '{url}?protocol_version=0.5'
1377 url_template = '{url}?protocol_version=0.5'
1357
1378
1358 def __init__(
1379 def __init__(
1359 self, url, api_key, add_server=True, add_timestamp=True,
1380 self, url, api_key, add_server=True, add_timestamp=True,
1360 namespace=None, request=None, testrun=None):
1381 namespace=None, request=None, testrun=None):
1361 self.url = self.url_template.format(url=url)
1382 self.url = self.url_template.format(url=url)
1362 self.api_key = api_key
1383 self.api_key = api_key
1363 self.add_server = add_server
1384 self.add_server = add_server
1364 self.add_timestamp = add_timestamp
1385 self.add_timestamp = add_timestamp
1365 self.namespace = namespace
1386 self.namespace = namespace
1366 self.request = request
1387 self.request = request
1367 self.server = socket.getfqdn(socket.gethostname())
1388 self.server = socket.getfqdn(socket.gethostname())
1368 self.tags_before = {}
1389 self.tags_before = {}
1369 self.tags_after = {}
1390 self.tags_after = {}
1370 self.stats = []
1391 self.stats = []
1371 self.testrun = testrun or {}
1392 self.testrun = testrun or {}
1372
1393
1373 def tag_before(self, tag, value):
1394 def tag_before(self, tag, value):
1374 self.tags_before[tag] = value
1395 self.tags_before[tag] = value
1375
1396
1376 def tag_after(self, tag, value):
1397 def tag_after(self, tag, value):
1377 self.tags_after[tag] = value
1398 self.tags_after[tag] = value
1378
1399
1379 def collect(self, data):
1400 def collect(self, data):
1380 if self.add_server:
1401 if self.add_server:
1381 data.setdefault('server', self.server)
1402 data.setdefault('server', self.server)
1382 if self.add_timestamp:
1403 if self.add_timestamp:
1383 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1404 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1384 if self.namespace:
1405 if self.namespace:
1385 data.setdefault('namespace', self.namespace)
1406 data.setdefault('namespace', self.namespace)
1386 if self.request:
1407 if self.request:
1387 data.setdefault('request', self.request)
1408 data.setdefault('request', self.request)
1388 self.stats.append(data)
1409 self.stats.append(data)
1389
1410
1390 def send_stats(self):
1411 def send_stats(self):
1391 tags = [
1412 tags = [
1392 ('testrun', self.request),
1413 ('testrun', self.request),
1393 ('testrun.start', self.testrun['start']),
1414 ('testrun.start', self.testrun['start']),
1394 ('testrun.timestamp', self.testrun['timestamp']),
1415 ('testrun.timestamp', self.testrun['timestamp']),
1395 ('test', self.namespace),
1416 ('test', self.namespace),
1396 ]
1417 ]
1397 for key, value in self.tags_before.items():
1418 for key, value in self.tags_before.items():
1398 tags.append((key + '.before', value))
1419 tags.append((key + '.before', value))
1399 try:
1420 try:
1400 delta = self.tags_after[key] - value
1421 delta = self.tags_after[key] - value
1401 tags.append((key + '.delta', delta))
1422 tags.append((key + '.delta', delta))
1402 except Exception:
1423 except Exception:
1403 pass
1424 pass
1404 for key, value in self.tags_after.items():
1425 for key, value in self.tags_after.items():
1405 tags.append((key + '.after', value))
1426 tags.append((key + '.after', value))
1406 self.collect({
1427 self.collect({
1407 'message': "Collected tags",
1428 'message': "Collected tags",
1408 'tags': tags,
1429 'tags': tags,
1409 })
1430 })
1410
1431
1411 response = requests.post(
1432 response = requests.post(
1412 self.url,
1433 self.url,
1413 headers={
1434 headers={
1414 'X-appenlight-api-key': self.api_key},
1435 'X-appenlight-api-key': self.api_key},
1415 json=self.stats,
1436 json=self.stats,
1416 )
1437 )
1417
1438
1418 if not response.status_code == 200:
1439 if not response.status_code == 200:
1419 pprint.pprint(self.stats)
1440 pprint.pprint(self.stats)
1420 print response.headers
1441 print response.headers
1421 print response.text
1442 print response.text
1422 raise Exception('Sending to appenlight failed')
1443 raise Exception('Sending to appenlight failed')
1423
1444
1424
1445
1425 @pytest.fixture
1446 @pytest.fixture
1426 def gist_util(request, pylonsapp):
1447 def gist_util(request, pylonsapp):
1427 """
1448 """
1428 Provides a wired instance of `GistUtility` with integrated cleanup.
1449 Provides a wired instance of `GistUtility` with integrated cleanup.
1429 """
1450 """
1430 utility = GistUtility()
1451 utility = GistUtility()
1431 request.addfinalizer(utility.cleanup)
1452 request.addfinalizer(utility.cleanup)
1432 return utility
1453 return utility
1433
1454
1434
1455
1435 class GistUtility(object):
1456 class GistUtility(object):
1436 def __init__(self):
1457 def __init__(self):
1437 self.fixture = Fixture()
1458 self.fixture = Fixture()
1438 self.gist_ids = []
1459 self.gist_ids = []
1439
1460
1440 def create_gist(self, **kwargs):
1461 def create_gist(self, **kwargs):
1441 gist = self.fixture.create_gist(**kwargs)
1462 gist = self.fixture.create_gist(**kwargs)
1442 self.gist_ids.append(gist.gist_id)
1463 self.gist_ids.append(gist.gist_id)
1443 return gist
1464 return gist
1444
1465
1445 def cleanup(self):
1466 def cleanup(self):
1446 for id_ in self.gist_ids:
1467 for id_ in self.gist_ids:
1447 self.fixture.destroy_gists(str(id_))
1468 self.fixture.destroy_gists(str(id_))
1448
1469
1449
1470
1450 @pytest.fixture
1471 @pytest.fixture
1451 def enabled_backends(request):
1472 def enabled_backends(request):
1452 backends = request.config.option.backends
1473 backends = request.config.option.backends
1453 return backends[:]
1474 return backends[:]
1454
1475
1455
1476
1456 @pytest.fixture
1477 @pytest.fixture
1457 def settings_util(request):
1478 def settings_util(request):
1458 """
1479 """
1459 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1480 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1460 """
1481 """
1461 utility = SettingsUtility()
1482 utility = SettingsUtility()
1462 request.addfinalizer(utility.cleanup)
1483 request.addfinalizer(utility.cleanup)
1463 return utility
1484 return utility
1464
1485
1465
1486
1466 class SettingsUtility(object):
1487 class SettingsUtility(object):
1467 def __init__(self):
1488 def __init__(self):
1468 self.rhodecode_ui_ids = []
1489 self.rhodecode_ui_ids = []
1469 self.rhodecode_setting_ids = []
1490 self.rhodecode_setting_ids = []
1470 self.repo_rhodecode_ui_ids = []
1491 self.repo_rhodecode_ui_ids = []
1471 self.repo_rhodecode_setting_ids = []
1492 self.repo_rhodecode_setting_ids = []
1472
1493
1473 def create_repo_rhodecode_ui(
1494 def create_repo_rhodecode_ui(
1474 self, repo, section, value, key=None, active=True, cleanup=True):
1495 self, repo, section, value, key=None, active=True, cleanup=True):
1475 key = key or hashlib.sha1(
1496 key = key or hashlib.sha1(
1476 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1497 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1477
1498
1478 setting = RepoRhodeCodeUi()
1499 setting = RepoRhodeCodeUi()
1479 setting.repository_id = repo.repo_id
1500 setting.repository_id = repo.repo_id
1480 setting.ui_section = section
1501 setting.ui_section = section
1481 setting.ui_value = value
1502 setting.ui_value = value
1482 setting.ui_key = key
1503 setting.ui_key = key
1483 setting.ui_active = active
1504 setting.ui_active = active
1484 Session().add(setting)
1505 Session().add(setting)
1485 Session().commit()
1506 Session().commit()
1486
1507
1487 if cleanup:
1508 if cleanup:
1488 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1509 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1489 return setting
1510 return setting
1490
1511
1491 def create_rhodecode_ui(
1512 def create_rhodecode_ui(
1492 self, section, value, key=None, active=True, cleanup=True):
1513 self, section, value, key=None, active=True, cleanup=True):
1493 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1514 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1494
1515
1495 setting = RhodeCodeUi()
1516 setting = RhodeCodeUi()
1496 setting.ui_section = section
1517 setting.ui_section = section
1497 setting.ui_value = value
1518 setting.ui_value = value
1498 setting.ui_key = key
1519 setting.ui_key = key
1499 setting.ui_active = active
1520 setting.ui_active = active
1500 Session().add(setting)
1521 Session().add(setting)
1501 Session().commit()
1522 Session().commit()
1502
1523
1503 if cleanup:
1524 if cleanup:
1504 self.rhodecode_ui_ids.append(setting.ui_id)
1525 self.rhodecode_ui_ids.append(setting.ui_id)
1505 return setting
1526 return setting
1506
1527
1507 def create_repo_rhodecode_setting(
1528 def create_repo_rhodecode_setting(
1508 self, repo, name, value, type_, cleanup=True):
1529 self, repo, name, value, type_, cleanup=True):
1509 setting = RepoRhodeCodeSetting(
1530 setting = RepoRhodeCodeSetting(
1510 repo.repo_id, key=name, val=value, type=type_)
1531 repo.repo_id, key=name, val=value, type=type_)
1511 Session().add(setting)
1532 Session().add(setting)
1512 Session().commit()
1533 Session().commit()
1513
1534
1514 if cleanup:
1535 if cleanup:
1515 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1536 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1516 return setting
1537 return setting
1517
1538
1518 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1539 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1519 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1540 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1520 Session().add(setting)
1541 Session().add(setting)
1521 Session().commit()
1542 Session().commit()
1522
1543
1523 if cleanup:
1544 if cleanup:
1524 self.rhodecode_setting_ids.append(setting.app_settings_id)
1545 self.rhodecode_setting_ids.append(setting.app_settings_id)
1525
1546
1526 return setting
1547 return setting
1527
1548
1528 def cleanup(self):
1549 def cleanup(self):
1529 for id_ in self.rhodecode_ui_ids:
1550 for id_ in self.rhodecode_ui_ids:
1530 setting = RhodeCodeUi.get(id_)
1551 setting = RhodeCodeUi.get(id_)
1531 Session().delete(setting)
1552 Session().delete(setting)
1532
1553
1533 for id_ in self.rhodecode_setting_ids:
1554 for id_ in self.rhodecode_setting_ids:
1534 setting = RhodeCodeSetting.get(id_)
1555 setting = RhodeCodeSetting.get(id_)
1535 Session().delete(setting)
1556 Session().delete(setting)
1536
1557
1537 for id_ in self.repo_rhodecode_ui_ids:
1558 for id_ in self.repo_rhodecode_ui_ids:
1538 setting = RepoRhodeCodeUi.get(id_)
1559 setting = RepoRhodeCodeUi.get(id_)
1539 Session().delete(setting)
1560 Session().delete(setting)
1540
1561
1541 for id_ in self.repo_rhodecode_setting_ids:
1562 for id_ in self.repo_rhodecode_setting_ids:
1542 setting = RepoRhodeCodeSetting.get(id_)
1563 setting = RepoRhodeCodeSetting.get(id_)
1543 Session().delete(setting)
1564 Session().delete(setting)
1544
1565
1545 Session().commit()
1566 Session().commit()
1546
1567
1547
1568
1548 @pytest.fixture
1569 @pytest.fixture
1549 def no_notifications(request):
1570 def no_notifications(request):
1550 notification_patcher = mock.patch(
1571 notification_patcher = mock.patch(
1551 'rhodecode.model.notification.NotificationModel.create')
1572 'rhodecode.model.notification.NotificationModel.create')
1552 notification_patcher.start()
1573 notification_patcher.start()
1553 request.addfinalizer(notification_patcher.stop)
1574 request.addfinalizer(notification_patcher.stop)
1554
1575
1555
1576
1556 @pytest.fixture
1577 @pytest.fixture
1557 def silence_action_logger(request):
1578 def silence_action_logger(request):
1558 notification_patcher = mock.patch(
1579 notification_patcher = mock.patch(
1559 'rhodecode.lib.utils.action_logger')
1580 'rhodecode.lib.utils.action_logger')
1560 notification_patcher.start()
1581 notification_patcher.start()
1561 request.addfinalizer(notification_patcher.stop)
1582 request.addfinalizer(notification_patcher.stop)
1562
1583
1563
1584
1564 @pytest.fixture(scope='session')
1585 @pytest.fixture(scope='session')
1565 def repeat(request):
1586 def repeat(request):
1566 """
1587 """
1567 The number of repetitions is based on this fixture.
1588 The number of repetitions is based on this fixture.
1568
1589
1569 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1590 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1570 tests are not too slow in our default test suite.
1591 tests are not too slow in our default test suite.
1571 """
1592 """
1572 return request.config.getoption('--repeat')
1593 return request.config.getoption('--repeat')
1573
1594
1574
1595
1575 @pytest.fixture
1596 @pytest.fixture
1576 def rhodecode_fixtures():
1597 def rhodecode_fixtures():
1577 return Fixture()
1598 return Fixture()
1578
1599
1579
1600
1580 @pytest.fixture
1601 @pytest.fixture
1581 def request_stub():
1602 def request_stub():
1582 """
1603 """
1583 Stub request object.
1604 Stub request object.
1584 """
1605 """
1585 request = pyramid.testing.DummyRequest()
1606 request = pyramid.testing.DummyRequest()
1586 request.scheme = 'https'
1607 request.scheme = 'https'
1587 return request
1608 return request
1588
1609
1589
1610
1590 @pytest.fixture
1611 @pytest.fixture
1591 def config_stub(request, request_stub):
1612 def config_stub(request, request_stub):
1592 """
1613 """
1593 Set up pyramid.testing and return the Configurator.
1614 Set up pyramid.testing and return the Configurator.
1594 """
1615 """
1595 config = pyramid.testing.setUp(request=request_stub)
1616 config = pyramid.testing.setUp(request=request_stub)
1596
1617
1597 @request.addfinalizer
1618 @request.addfinalizer
1598 def cleanup():
1619 def cleanup():
1599 pyramid.testing.tearDown()
1620 pyramid.testing.tearDown()
1600
1621
1601 return config
1622 return config
General Comments 0
You need to be logged in to leave comments. Login now