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