##// END OF EJS Templates
tests: use WARN loglevel by default, and add --test-loglevel flag to change this....
marcink -
r1123:c96fec20 default
parent child Browse files
Show More
@@ -1,455 +1,471 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 Pyro4.errors import CommunicationError
39 39 from routes.util import URLGenerator
40 40
41 41 from rhodecode.lib import vcs
42 42 from rhodecode.tests.fixture import TestINI
43 43 import rhodecode
44 44
45 45
46 46 def _parse_json(value):
47 47 return json.loads(value) if value else None
48 48
49 49
50 50 def pytest_addoption(parser):
51 parser.addoption(
52 '--test-loglevel', dest='test_loglevel',
53 help="Set default Logging level for tests, warn (default), info, debug")
51 54 group = parser.getgroup('pylons')
52 55 group.addoption(
53 56 '--with-pylons', dest='pylons_config',
54 57 help="Set up a Pylons environment with the specified config file.")
55 58 group.addoption(
56 59 '--pylons-config-override', action='store', type=_parse_json,
57 60 default=None, dest='pylons_config_override', help=(
58 61 "Overrides the .ini file settings. Should be specified in JSON"
59 62 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
60 63 )
61 64 )
62 65 parser.addini(
63 66 'pylons_config',
64 67 "Set up a Pylons environment with the specified config file.")
65 68
66 69 vcsgroup = parser.getgroup('vcs')
67 70 vcsgroup.addoption(
68 71 '--without-vcsserver', dest='with_vcsserver', action='store_false',
69 72 help="Do not start the VCSServer in a background process.")
70 73 vcsgroup.addoption(
71 74 '--with-vcsserver', dest='vcsserver_config_pyro4',
72 75 help="Start the VCSServer with the specified config file.")
73 76 vcsgroup.addoption(
74 77 '--with-vcsserver-http', dest='vcsserver_config_http',
75 78 help="Start the HTTP VCSServer with the specified config file.")
76 79 vcsgroup.addoption(
77 80 '--vcsserver-protocol', dest='vcsserver_protocol',
78 81 help="Start the VCSServer with HTTP / Pyro4 protocol support.")
79 82 vcsgroup.addoption(
80 83 '--vcsserver-config-override', action='store', type=_parse_json,
81 84 default=None, dest='vcsserver_config_override', help=(
82 85 "Overrides the .ini file settings for the VCSServer. "
83 86 "Should be specified in JSON "
84 87 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
85 88 )
86 89 )
87 90 vcsgroup.addoption(
88 91 '--vcsserver-port', action='store', type=int,
89 92 default=None, help=(
90 93 "Allows to set the port of the vcsserver. Useful when testing "
91 94 "against an already running server and random ports cause "
92 95 "trouble."))
93 96 parser.addini(
94 97 'vcsserver_config_pyro4',
95 98 "Start the VCSServer with the specified config file.")
96 99 parser.addini(
97 100 'vcsserver_config_http',
98 101 "Start the HTTP VCSServer with the specified config file.")
99 102 parser.addini(
100 103 'vcsserver_protocol',
101 104 "Start the VCSServer with HTTP / Pyro4 protocol support.")
102 105
103 106
104 107 @pytest.fixture(scope='session')
105 108 def vcsserver(request, vcsserver_port, vcsserver_factory):
106 109 """
107 110 Session scope VCSServer.
108 111
109 112 Tests wich need the VCSServer have to rely on this fixture in order
110 113 to ensure it will be running.
111 114
112 115 For specific needs, the fixture vcsserver_factory can be used. It allows to
113 116 adjust the configuration file for the test run.
114 117
115 118 Command line args:
116 119
117 120 --without-vcsserver: Allows to switch this fixture off. You have to
118 121 manually start the server.
119 122
120 123 --vcsserver-port: Will expect the VCSServer to listen on this port.
121 124 """
122 125
123 126 if not request.config.getoption('with_vcsserver'):
124 127 return None
125 128
126 129 use_http = _use_vcs_http_server(request.config)
127 130 return vcsserver_factory(
128 131 request, use_http=use_http, vcsserver_port=vcsserver_port)
129 132
130 133
131 134 @pytest.fixture(scope='session')
132 135 def vcsserver_factory(tmpdir_factory):
133 136 """
134 137 Use this if you need a running vcsserver with a special configuration.
135 138 """
136 139
137 140 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
138 141
139 142 if vcsserver_port is None:
140 143 vcsserver_port = get_available_port()
141 144
142 145 overrides = list(overrides)
143 146 if use_http:
144 147 overrides.append({'server:main': {'port': vcsserver_port}})
145 148 else:
146 149 overrides.append({'DEFAULT': {'port': vcsserver_port}})
147 150
148 151 if is_cygwin():
149 152 platform_override = {'DEFAULT': {
150 153 'beaker.cache.repo_object.type': 'nocache'}}
151 154 overrides.append(platform_override)
152 155
153 156 option_name = (
154 157 'vcsserver_config_http' if use_http else 'vcsserver_config_pyro4')
155 158 override_option_name = 'vcsserver_config_override'
156 159 config_file = get_config(
157 160 request.config, option_name=option_name,
158 161 override_option_name=override_option_name, overrides=overrides,
159 162 basetemp=tmpdir_factory.getbasetemp().strpath,
160 163 prefix='test_vcs_')
161 164
162 165 print "Using the VCSServer configuration", config_file
163 166 ServerClass = HttpVCSServer if use_http else Pyro4VCSServer
164 167 server = ServerClass(config_file)
165 168 server.start()
166 169
167 170 @request.addfinalizer
168 171 def cleanup():
169 172 server.shutdown()
170 173
171 174 server.wait_until_ready()
172 175 return server
173 176
174 177 return factory
175 178
176 179
177 180 def is_cygwin():
178 181 return 'cygwin' in platform.system().lower()
179 182
180 183
181 184 def _use_vcs_http_server(config):
182 185 protocol_option = 'vcsserver_protocol'
183 186 protocol = (
184 187 config.getoption(protocol_option) or
185 188 config.getini(protocol_option) or
186 189 'http')
187 190 return protocol == 'http'
188 191
189 192
193 def _use_log_level(config):
194 level = config.getoption('test_loglevel') or 'warn'
195 return level.upper()
196
197
190 198 class VCSServer(object):
191 199 """
192 200 Represents a running VCSServer instance.
193 201 """
194 202
195 203 _args = []
196 204
197 205 def start(self):
198 206 print("Starting the VCSServer: {}".format(self._args))
199 207 self.process = subprocess32.Popen(self._args)
200 208
201 209 def wait_until_ready(self, timeout=30):
202 210 raise NotImplementedError()
203 211
204 212 def shutdown(self):
205 213 self.process.kill()
206 214
207 215
208 216 class Pyro4VCSServer(VCSServer):
209 217 def __init__(self, config_file):
210 218 """
211 219 :param config_file: The config file to start the server with
212 220 """
213 221
214 222 config_data = configobj.ConfigObj(config_file)
215 223 self._config = config_data['DEFAULT']
216 224
217 225 args = ['vcsserver', '--config', config_file]
218 226 self._args = args
219 227
220 228 def wait_until_ready(self, timeout=30):
221 229 remote_server = vcs.create_vcsserver_proxy(
222 230 self.server_and_port, 'pyro4')
223 231 start = time.time()
224 232 with remote_server:
225 233 while time.time() - start < timeout:
226 234 try:
227 235 remote_server.ping()
228 236 break
229 237 except CommunicationError:
230 238 time.sleep(0.2)
231 239 else:
232 240 pytest.exit(
233 241 "Starting the VCSServer failed or took more than {} "
234 242 "seconds.".format(timeout))
235 243
236 244 @property
237 245 def server_and_port(self):
238 246 return '{host}:{port}'.format(**self._config)
239 247
240 248
241 249 class HttpVCSServer(VCSServer):
242 250 """
243 251 Represents a running VCSServer instance.
244 252 """
245 253 def __init__(self, config_file):
246 254 config_data = configobj.ConfigObj(config_file)
247 255 self._config = config_data['server:main']
248 256
249 257 args = ['pserve', config_file]
250 258 self._args = args
251 259
252 260 @property
253 261 def http_url(self):
254 262 template = 'http://{host}:{port}/'
255 263 return template.format(**self._config)
256 264
257 265 def start(self):
258 266 self.process = subprocess32.Popen(self._args)
259 267
260 268 def wait_until_ready(self, timeout=30):
261 269 host = self._config['host']
262 270 port = self._config['port']
263 271 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
264 272 start = time.time()
265 273
266 274 while time.time() - start < timeout:
267 275 try:
268 276 urlopen(status_url)
269 277 break
270 278 except URLError:
271 279 time.sleep(0.2)
272 280 else:
273 281 pytest.exit(
274 282 "Starting the VCSServer failed or took more than {} "
275 283 "seconds.".format(timeout))
276 284
277 285 def shutdown(self):
278 286 self.process.kill()
279 287
280 288
281 289 @pytest.fixture(scope='session')
282 290 def pylons_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
283 291 option_name = 'pylons_config'
292 log_level = _use_log_level(request.config)
284 293
285 294 overrides = [
286 295 {'server:main': {'port': rcserver_port}},
287 296 {'app:main': {
288 297 'vcs.server': 'localhost:%s' % vcsserver_port,
289 298 # johbo: We will always start the VCSServer on our own based on the
290 299 # fixtures of the test cases. For the test run it must always be
291 300 # off in the INI file.
292 301 'vcs.start_server': 'false',
293 302 }},
303
304 {'handler_console': {
305 'class ': 'StreamHandler',
306 'args ': '(sys.stderr,)',
307 'level': log_level,
308 }},
309
294 310 ]
295 311 if _use_vcs_http_server(request.config):
296 312 overrides.append({
297 313 'app:main': {
298 314 'vcs.server.protocol': 'http',
299 315 'vcs.scm_app_implementation': 'http',
300 316 'vcs.hooks.protocol': 'http',
301 317 }
302 318 })
303 319 else:
304 320 overrides.append({
305 321 'app:main': {
306 322 'vcs.server.protocol': 'pyro4',
307 323 'vcs.scm_app_implementation': 'pyro4',
308 324 'vcs.hooks.protocol': 'pyro4',
309 325 }
310 326 })
311 327
312 328 filename = get_config(
313 329 request.config, option_name=option_name,
314 330 override_option_name='{}_override'.format(option_name),
315 331 overrides=overrides,
316 332 basetemp=tmpdir_factory.getbasetemp().strpath,
317 333 prefix='test_rce_')
318 334 return filename
319 335
320 336
321 337 @pytest.fixture(scope='session')
322 338 def rcserver_port(request):
323 339 port = get_available_port()
324 340 print 'Using rcserver port %s' % (port, )
325 341 return port
326 342
327 343
328 344 @pytest.fixture(scope='session')
329 345 def vcsserver_port(request):
330 346 port = request.config.getoption('--vcsserver-port')
331 347 if port is None:
332 348 port = get_available_port()
333 349 print 'Using vcsserver port %s' % (port, )
334 350 return port
335 351
336 352
337 353 def get_available_port():
338 354 family = socket.AF_INET
339 355 socktype = socket.SOCK_STREAM
340 356 host = '127.0.0.1'
341 357
342 358 mysocket = socket.socket(family, socktype)
343 359 mysocket.bind((host, 0))
344 360 port = mysocket.getsockname()[1]
345 361 mysocket.close()
346 362 del mysocket
347 363 return port
348 364
349 365
350 366 @pytest.fixture(scope='session')
351 367 def available_port_factory():
352 368 """
353 369 Returns a callable which returns free port numbers.
354 370 """
355 371 return get_available_port
356 372
357 373
358 374 @pytest.fixture
359 375 def available_port(available_port_factory):
360 376 """
361 377 Gives you one free port for the current test.
362 378
363 379 Uses "available_port_factory" to retrieve the port.
364 380 """
365 381 return available_port_factory()
366 382
367 383
368 384 @pytest.fixture(scope='session')
369 385 def pylonsapp(pylons_config, vcsserver, http_environ_session):
370 386 print "Using the RhodeCode configuration", pylons_config
371 387 logging.config.fileConfig(
372 388 pylons_config, disable_existing_loggers=False)
373 389 app = _setup_pylons_environment(pylons_config, http_environ_session)
374 390 return app
375 391
376 392
377 393 @pytest.fixture(scope='session')
378 394 def testini_factory(tmpdir_factory, pylons_config):
379 395 """
380 396 Factory to create an INI file based on TestINI.
381 397
382 398 It will make sure to place the INI file in the correct directory.
383 399 """
384 400 basetemp = tmpdir_factory.getbasetemp().strpath
385 401 return TestIniFactory(basetemp, pylons_config)
386 402
387 403
388 404 class TestIniFactory(object):
389 405
390 406 def __init__(self, basetemp, template_ini):
391 407 self._basetemp = basetemp
392 408 self._template_ini = template_ini
393 409
394 410 def __call__(self, ini_params, new_file_prefix='test'):
395 411 ini_file = TestINI(
396 412 self._template_ini, ini_params=ini_params,
397 413 new_file_prefix=new_file_prefix, dir=self._basetemp)
398 414 result = ini_file.create()
399 415 return result
400 416
401 417
402 418 def get_config(
403 419 config, option_name, override_option_name, overrides=None,
404 420 basetemp=None, prefix='test'):
405 421 """
406 422 Find a configuration file and apply overrides for the given `prefix`.
407 423 """
408 424 config_file = (
409 425 config.getoption(option_name) or config.getini(option_name))
410 426 if not config_file:
411 427 pytest.exit(
412 428 "Configuration error, could not extract {}.".format(option_name))
413 429
414 430 overrides = overrides or []
415 431 config_override = config.getoption(override_option_name)
416 432 if config_override:
417 433 overrides.append(config_override)
418 434 temp_ini_file = TestINI(
419 435 config_file, ini_params=overrides, new_file_prefix=prefix,
420 436 dir=basetemp)
421 437
422 438 return temp_ini_file.create()
423 439
424 440
425 441 def _setup_pylons_environment(pylons_config, http_environ):
426 442 current_path = os.getcwd()
427 443 pylonsapp = loadapp(
428 444 'config:' + pylons_config, relative_to=current_path)
429 445
430 446 # Using rhodecode.CONFIG which is assigned during "load_environment".
431 447 # The indirect approach is used, because "pylonsapp" may actually be
432 448 # the Pyramid application.
433 449 pylonsapp_config = rhodecode.CONFIG
434 450 _init_stack(pylonsapp_config, environ=http_environ)
435 451
436 452 # For compatibility add the attribute "config" which would be
437 453 # present on the Pylons application.
438 454 pylonsapp.config = pylonsapp_config
439 455 return pylonsapp
440 456
441 457
442 458 def _init_stack(config=None, environ=None):
443 459 if not config:
444 460 config = pylons.test.pylonsapp.config
445 461 if not environ:
446 462 environ = {}
447 463 pylons.url._push_object(URLGenerator(config['routes.map'], environ or {}))
448 464 pylons.app_globals._push_object(config['pylons.app_globals'])
449 465 pylons.config._push_object(config)
450 466 pylons.tmpl_context._push_object(ContextObj())
451 467 # Initialize a translator for tests that utilize i18n
452 468 translator = _get_translator(pylons.config.get('lang'))
453 469 pylons.translator._push_object(translator)
454 470 pylons.session._push_object(SessionObject(environ or {}))
455 471 pylons.request._push_object(webob.Request.blank('', environ=environ))
General Comments 0
You need to be logged in to leave comments. Login now