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