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