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