##// END OF EJS Templates
tests: allow running tests via make file on different databases
marcink -
r2100:f2bf7ee3 default
parent child Browse files
Show More
@@ -1,35 +1,53 b''
1 1
2 .PHONY: clean docs docs-clean docs-cleanup test test-clean test-only web-build
2 .PHONY: clean docs docs-clean docs-cleanup test test-clean test-only test-only-postgres test-only-mysql web-build
3 3
4 4 WEBPACK=./node_modules/webpack/bin/webpack.js
5 5 GRUNT=grunt
6 6 NODE_PATH=./node_modules
7 7
8 8
9 9 clean:
10 10 make test-clean
11 11 find . -type f \( -iname '*.c' -o -iname '*.pyc' -o -iname '*.so' \) -exec rm '{}' ';'
12 12
13 13 test:
14 14 make test-clean
15 15 make test-only
16 16
17 17 test-clean:
18 18 rm -rf coverage.xml htmlcov junit.xml pylint.log result
19 19 find . -type d -name "__pycache__" -prune -exec rm -rf '{}' ';'
20 20
21 21 test-only:
22 PYTHONHASHSEED=random py.test -vv -r xw --cov=rhodecode --cov-report=term-missing --cov-report=html rhodecode
22 PYTHONHASHSEED=random \
23 py.test -x -vv -r xw -p no:sugar --cov=rhodecode \
24 --cov-report=term-missing --cov-report=html \
25 rhodecode
26
27 test-only-mysql:
28 PYTHONHASHSEED=random \
29 py.test -x -vv -r xw -p no:sugar --cov=rhodecode \
30 --cov-report=term-missing --cov-report=html \
31 --ini-config-override='{"app:main": {"sqlalchemy.db1.url": "mysql://root:qweqwe@localhost/rhodecode_test"}}' \
32 rhodecode
33
34 test-only-postgres:
35 PYTHONHASHSEED=random \
36 py.test -x -vv -r xw -p no:sugar --cov=rhodecode \
37 --cov-report=term-missing --cov-report=html \
38 --ini-config-override='{"app:main": {"sqlalchemy.db1.url": "postgresql://postgres:qweqwe@localhost/rhodecode_test"}}' \
39 rhodecode
40
23 41
24 42 docs:
25 43 (cd docs; nix-build default.nix -o result; make clean html)
26 44
27 45 docs-clean:
28 46 (cd docs; make clean)
29 47
30 48 docs-cleanup:
31 49 (cd docs; make cleanup)
32 50
33 51 web-build:
34 52 NODE_PATH=$(NODE_PATH) $(GRUNT)
35 53
@@ -1,422 +1,422 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 22 import logging.config
23 23 import os
24 24 import platform
25 25 import socket
26 26 import subprocess32
27 27 import time
28 28 from urllib2 import urlopen, URLError
29 29
30 30 import configobj
31 31 import pylons
32 32 import pytest
33 33 import webob
34 34 from beaker.session import SessionObject
35 35 from paste.deploy import loadapp
36 36 from pylons.i18n.translation import _get_translator
37 37 from pylons.util import ContextObj
38 38 from routes.util import URLGenerator
39 39
40 40 from rhodecode.lib import vcs
41 41 from rhodecode.tests.fixture import TestINI
42 42 import rhodecode
43 43
44 44
45 45 def _parse_json(value):
46 46 return json.loads(value) if value else None
47 47
48 48
49 49 def pytest_addoption(parser):
50 50 parser.addoption(
51 51 '--test-loglevel', dest='test_loglevel',
52 52 help="Set default Logging level for tests, warn (default), info, debug")
53 53 group = parser.getgroup('pylons')
54 54 group.addoption(
55 55 '--with-pylons', dest='pylons_config',
56 56 help="Set up a Pylons environment with the specified config file.")
57 57 group.addoption(
58 '--pylons-config-override', action='store', type=_parse_json,
58 '--ini-config-override', action='store', type=_parse_json,
59 59 default=None, dest='pylons_config_override', help=(
60 60 "Overrides the .ini file settings. Should be specified in JSON"
61 61 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
62 62 )
63 63 )
64 64 parser.addini(
65 65 'pylons_config',
66 66 "Set up a Pylons environment with the specified config file.")
67 67
68 68 vcsgroup = parser.getgroup('vcs')
69 69 vcsgroup.addoption(
70 70 '--without-vcsserver', dest='with_vcsserver', action='store_false',
71 71 help="Do not start the VCSServer in a background process.")
72 72 vcsgroup.addoption(
73 73 '--with-vcsserver-http', dest='vcsserver_config_http',
74 74 help="Start the HTTP VCSServer with the specified config file.")
75 75 vcsgroup.addoption(
76 76 '--vcsserver-protocol', dest='vcsserver_protocol',
77 77 help="Start the VCSServer with HTTP protocol support.")
78 78 vcsgroup.addoption(
79 79 '--vcsserver-config-override', action='store', type=_parse_json,
80 80 default=None, dest='vcsserver_config_override', help=(
81 81 "Overrides the .ini file settings for the VCSServer. "
82 82 "Should be specified in JSON "
83 83 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
84 84 )
85 85 )
86 86 vcsgroup.addoption(
87 87 '--vcsserver-port', action='store', type=int,
88 88 default=None, help=(
89 89 "Allows to set the port of the vcsserver. Useful when testing "
90 90 "against an already running server and random ports cause "
91 91 "trouble."))
92 92 parser.addini(
93 93 'vcsserver_config_http',
94 94 "Start the HTTP VCSServer with the specified config file.")
95 95 parser.addini(
96 96 'vcsserver_protocol',
97 97 "Start the VCSServer with HTTP protocol support.")
98 98
99 99
100 100 @pytest.fixture(scope='session')
101 101 def vcsserver(request, vcsserver_port, vcsserver_factory):
102 102 """
103 103 Session scope VCSServer.
104 104
105 105 Tests wich need the VCSServer have to rely on this fixture in order
106 106 to ensure it will be running.
107 107
108 108 For specific needs, the fixture vcsserver_factory can be used. It allows to
109 109 adjust the configuration file for the test run.
110 110
111 111 Command line args:
112 112
113 113 --without-vcsserver: Allows to switch this fixture off. You have to
114 114 manually start the server.
115 115
116 116 --vcsserver-port: Will expect the VCSServer to listen on this port.
117 117 """
118 118
119 119 if not request.config.getoption('with_vcsserver'):
120 120 return None
121 121
122 122 use_http = _use_vcs_http_server(request.config)
123 123 return vcsserver_factory(
124 124 request, use_http=use_http, vcsserver_port=vcsserver_port)
125 125
126 126
127 127 @pytest.fixture(scope='session')
128 128 def vcsserver_factory(tmpdir_factory):
129 129 """
130 130 Use this if you need a running vcsserver with a special configuration.
131 131 """
132 132
133 133 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
134 134
135 135 if vcsserver_port is None:
136 136 vcsserver_port = get_available_port()
137 137
138 138 overrides = list(overrides)
139 139 if use_http:
140 140 overrides.append({'server:main': {'port': vcsserver_port}})
141 141 else:
142 142 overrides.append({'DEFAULT': {'port': vcsserver_port}})
143 143
144 144 if is_cygwin():
145 145 platform_override = {'DEFAULT': {
146 146 'beaker.cache.repo_object.type': 'nocache'}}
147 147 overrides.append(platform_override)
148 148
149 149 option_name = 'vcsserver_config_http' if use_http else ''
150 150 override_option_name = 'vcsserver_config_override'
151 151 config_file = get_config(
152 152 request.config, option_name=option_name,
153 153 override_option_name=override_option_name, overrides=overrides,
154 154 basetemp=tmpdir_factory.getbasetemp().strpath,
155 155 prefix='test_vcs_')
156 156
157 157 print("Using the VCSServer configuration:{}".format(config_file))
158 158 ServerClass = HttpVCSServer if use_http else None
159 159 server = ServerClass(config_file)
160 160 server.start()
161 161
162 162 @request.addfinalizer
163 163 def cleanup():
164 164 server.shutdown()
165 165
166 166 server.wait_until_ready()
167 167 return server
168 168
169 169 return factory
170 170
171 171
172 172 def is_cygwin():
173 173 return 'cygwin' in platform.system().lower()
174 174
175 175
176 176 def _use_vcs_http_server(config):
177 177 protocol_option = 'vcsserver_protocol'
178 178 protocol = (
179 179 config.getoption(protocol_option) or
180 180 config.getini(protocol_option) or
181 181 'http')
182 182 return protocol == 'http'
183 183
184 184
185 185 def _use_log_level(config):
186 186 level = config.getoption('test_loglevel') or 'warn'
187 187 return level.upper()
188 188
189 189
190 190 class VCSServer(object):
191 191 """
192 192 Represents a running VCSServer instance.
193 193 """
194 194
195 195 _args = []
196 196
197 197 def start(self):
198 198 print("Starting the VCSServer: {}".format(self._args))
199 199 self.process = subprocess32.Popen(self._args)
200 200
201 201 def wait_until_ready(self, timeout=30):
202 202 raise NotImplementedError()
203 203
204 204 def shutdown(self):
205 205 self.process.kill()
206 206
207 207
208 208 class HttpVCSServer(VCSServer):
209 209 """
210 210 Represents a running VCSServer instance.
211 211 """
212 212 def __init__(self, config_file):
213 213 config_data = configobj.ConfigObj(config_file)
214 214 self._config = config_data['server:main']
215 215
216 216 args = ['pserve', config_file]
217 217 self._args = args
218 218
219 219 @property
220 220 def http_url(self):
221 221 template = 'http://{host}:{port}/'
222 222 return template.format(**self._config)
223 223
224 224 def start(self):
225 225 self.process = subprocess32.Popen(self._args)
226 226
227 227 def wait_until_ready(self, timeout=30):
228 228 host = self._config['host']
229 229 port = self._config['port']
230 230 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
231 231 start = time.time()
232 232
233 233 while time.time() - start < timeout:
234 234 try:
235 235 urlopen(status_url)
236 236 break
237 237 except URLError:
238 238 time.sleep(0.2)
239 239 else:
240 240 pytest.exit(
241 241 "Starting the VCSServer failed or took more than {} "
242 242 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
243 243
244 244 def shutdown(self):
245 245 self.process.kill()
246 246
247 247
248 248 @pytest.fixture(scope='session')
249 249 def pylons_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
250 250 option_name = 'pylons_config'
251 251 log_level = _use_log_level(request.config)
252 252
253 253 overrides = [
254 254 {'server:main': {'port': rcserver_port}},
255 255 {'app:main': {
256 256 'vcs.server': 'localhost:%s' % vcsserver_port,
257 257 # johbo: We will always start the VCSServer on our own based on the
258 258 # fixtures of the test cases. For the test run it must always be
259 259 # off in the INI file.
260 260 'vcs.start_server': 'false',
261 261 }},
262 262
263 263 {'handler_console': {
264 264 'class ': 'StreamHandler',
265 265 'args ': '(sys.stderr,)',
266 266 'level': log_level,
267 267 }},
268 268
269 269 ]
270 270 if _use_vcs_http_server(request.config):
271 271 overrides.append({
272 272 'app:main': {
273 273 'vcs.server.protocol': 'http',
274 274 'vcs.scm_app_implementation': 'http',
275 275 'vcs.hooks.protocol': 'http',
276 276 }
277 277 })
278 278
279 279 filename = get_config(
280 280 request.config, option_name=option_name,
281 281 override_option_name='{}_override'.format(option_name),
282 282 overrides=overrides,
283 283 basetemp=tmpdir_factory.getbasetemp().strpath,
284 284 prefix='test_rce_')
285 285 return filename
286 286
287 287
288 288 @pytest.fixture(scope='session')
289 289 def rcserver_port(request):
290 290 port = get_available_port()
291 291 print('Using rcserver port {}'.format(port))
292 292 return port
293 293
294 294
295 295 @pytest.fixture(scope='session')
296 296 def vcsserver_port(request):
297 297 port = request.config.getoption('--vcsserver-port')
298 298 if port is None:
299 299 port = get_available_port()
300 300 print('Using vcsserver port {}'.format(port))
301 301 return port
302 302
303 303
304 304 def get_available_port():
305 305 family = socket.AF_INET
306 306 socktype = socket.SOCK_STREAM
307 307 host = '127.0.0.1'
308 308
309 309 mysocket = socket.socket(family, socktype)
310 310 mysocket.bind((host, 0))
311 311 port = mysocket.getsockname()[1]
312 312 mysocket.close()
313 313 del mysocket
314 314 return port
315 315
316 316
317 317 @pytest.fixture(scope='session')
318 318 def available_port_factory():
319 319 """
320 320 Returns a callable which returns free port numbers.
321 321 """
322 322 return get_available_port
323 323
324 324
325 325 @pytest.fixture
326 326 def available_port(available_port_factory):
327 327 """
328 328 Gives you one free port for the current test.
329 329
330 330 Uses "available_port_factory" to retrieve the port.
331 331 """
332 332 return available_port_factory()
333 333
334 334
335 335 @pytest.fixture(scope='session')
336 336 def pylonsapp(pylons_config, vcsserver, http_environ_session):
337 337 print("Using the RhodeCode configuration:{}".format(pylons_config))
338 338 logging.config.fileConfig(
339 339 pylons_config, disable_existing_loggers=False)
340 340 app = _setup_pylons_environment(pylons_config, http_environ_session)
341 341 return app
342 342
343 343
344 344 @pytest.fixture(scope='session')
345 345 def testini_factory(tmpdir_factory, pylons_config):
346 346 """
347 347 Factory to create an INI file based on TestINI.
348 348
349 349 It will make sure to place the INI file in the correct directory.
350 350 """
351 351 basetemp = tmpdir_factory.getbasetemp().strpath
352 352 return TestIniFactory(basetemp, pylons_config)
353 353
354 354
355 355 class TestIniFactory(object):
356 356
357 357 def __init__(self, basetemp, template_ini):
358 358 self._basetemp = basetemp
359 359 self._template_ini = template_ini
360 360
361 361 def __call__(self, ini_params, new_file_prefix='test'):
362 362 ini_file = TestINI(
363 363 self._template_ini, ini_params=ini_params,
364 364 new_file_prefix=new_file_prefix, dir=self._basetemp)
365 365 result = ini_file.create()
366 366 return result
367 367
368 368
369 369 def get_config(
370 370 config, option_name, override_option_name, overrides=None,
371 371 basetemp=None, prefix='test'):
372 372 """
373 373 Find a configuration file and apply overrides for the given `prefix`.
374 374 """
375 375 config_file = (
376 376 config.getoption(option_name) or config.getini(option_name))
377 377 if not config_file:
378 378 pytest.exit(
379 379 "Configuration error, could not extract {}.".format(option_name))
380 380
381 381 overrides = overrides or []
382 382 config_override = config.getoption(override_option_name)
383 383 if config_override:
384 384 overrides.append(config_override)
385 385 temp_ini_file = TestINI(
386 386 config_file, ini_params=overrides, new_file_prefix=prefix,
387 387 dir=basetemp)
388 388
389 389 return temp_ini_file.create()
390 390
391 391
392 392 def _setup_pylons_environment(pylons_config, http_environ):
393 393 current_path = os.getcwd()
394 394 pylonsapp = loadapp(
395 395 'config:' + pylons_config, relative_to=current_path)
396 396
397 397 # Using rhodecode.CONFIG which is assigned during "load_environment".
398 398 # The indirect approach is used, because "pylonsapp" may actually be
399 399 # the Pyramid application.
400 400 pylonsapp_config = rhodecode.CONFIG
401 401 _init_stack(pylonsapp_config, environ=http_environ)
402 402
403 403 # For compatibility add the attribute "config" which would be
404 404 # present on the Pylons application.
405 405 pylonsapp.config = pylonsapp_config
406 406 return pylonsapp
407 407
408 408
409 409 def _init_stack(config=None, environ=None):
410 410 if not config:
411 411 config = pylons.test.pylonsapp.config
412 412 if not environ:
413 413 environ = {}
414 414 pylons.url._push_object(URLGenerator(config['routes.map'], environ or {}))
415 415 pylons.app_globals._push_object(config['pylons.app_globals'])
416 416 pylons.config._push_object(config)
417 417 pylons.tmpl_context._push_object(ContextObj())
418 418 # Initialize a translator for tests that utilize i18n
419 419 translator = _get_translator(pylons.config.get('lang'))
420 420 pylons.translator._push_object(translator)
421 421 pylons.session._push_object(SessionObject(environ or {}))
422 422 pylons.request._push_object(webob.Request.blank('', environ=environ))
General Comments 0
You need to be logged in to leave comments. Login now