##// END OF EJS Templates
tests: pass in config ini into global configuration, same as the real app does.
marcink -
r2354:164d8f96 default
parent child Browse files
Show More
@@ -1,386 +1,386 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 os
21 import os
22 import json
22 import json
23 import platform
23 import platform
24 import socket
24 import socket
25 import subprocess32
25 import subprocess32
26 import time
26 import time
27 from urllib2 import urlopen, URLError
27 from urllib2 import urlopen, URLError
28
28
29 import configobj
29 import configobj
30 import pytest
30 import pytest
31
31
32 import pyramid.paster
32 import pyramid.paster
33 from rhodecode.tests.fixture import TestINI
33 from rhodecode.tests.fixture import TestINI
34 import rhodecode
34 import rhodecode
35
35
36
36
37 def _parse_json(value):
37 def _parse_json(value):
38 return json.loads(value) if value else None
38 return json.loads(value) if value else None
39
39
40
40
41 def pytest_addoption(parser):
41 def pytest_addoption(parser):
42 parser.addoption(
42 parser.addoption(
43 '--test-loglevel', dest='test_loglevel',
43 '--test-loglevel', dest='test_loglevel',
44 help="Set default Logging level for tests, warn (default), info, debug")
44 help="Set default Logging level for tests, warn (default), info, debug")
45 group = parser.getgroup('pylons')
45 group = parser.getgroup('pylons')
46 group.addoption(
46 group.addoption(
47 '--with-pylons', dest='pylons_config',
47 '--with-pylons', dest='pylons_config',
48 help="Set up a Pylons environment with the specified config file.")
48 help="Set up a Pylons environment with the specified config file.")
49 group.addoption(
49 group.addoption(
50 '--ini-config-override', action='store', type=_parse_json,
50 '--ini-config-override', action='store', type=_parse_json,
51 default=None, dest='pylons_config_override', help=(
51 default=None, dest='pylons_config_override', help=(
52 "Overrides the .ini file settings. Should be specified in JSON"
52 "Overrides the .ini file settings. Should be specified in JSON"
53 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
53 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
54 )
54 )
55 )
55 )
56 parser.addini(
56 parser.addini(
57 'pylons_config',
57 'pylons_config',
58 "Set up a Pylons environment with the specified config file.")
58 "Set up a Pylons environment with the specified config file.")
59
59
60 vcsgroup = parser.getgroup('vcs')
60 vcsgroup = parser.getgroup('vcs')
61 vcsgroup.addoption(
61 vcsgroup.addoption(
62 '--without-vcsserver', dest='with_vcsserver', action='store_false',
62 '--without-vcsserver', dest='with_vcsserver', action='store_false',
63 help="Do not start the VCSServer in a background process.")
63 help="Do not start the VCSServer in a background process.")
64 vcsgroup.addoption(
64 vcsgroup.addoption(
65 '--with-vcsserver-http', dest='vcsserver_config_http',
65 '--with-vcsserver-http', dest='vcsserver_config_http',
66 help="Start the HTTP VCSServer with the specified config file.")
66 help="Start the HTTP VCSServer with the specified config file.")
67 vcsgroup.addoption(
67 vcsgroup.addoption(
68 '--vcsserver-protocol', dest='vcsserver_protocol',
68 '--vcsserver-protocol', dest='vcsserver_protocol',
69 help="Start the VCSServer with HTTP protocol support.")
69 help="Start the VCSServer with HTTP protocol support.")
70 vcsgroup.addoption(
70 vcsgroup.addoption(
71 '--vcsserver-config-override', action='store', type=_parse_json,
71 '--vcsserver-config-override', action='store', type=_parse_json,
72 default=None, dest='vcsserver_config_override', help=(
72 default=None, dest='vcsserver_config_override', help=(
73 "Overrides the .ini file settings for the VCSServer. "
73 "Overrides the .ini file settings for the VCSServer. "
74 "Should be specified in JSON "
74 "Should be specified in JSON "
75 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
75 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
76 )
76 )
77 )
77 )
78 vcsgroup.addoption(
78 vcsgroup.addoption(
79 '--vcsserver-port', action='store', type=int,
79 '--vcsserver-port', action='store', type=int,
80 default=None, help=(
80 default=None, help=(
81 "Allows to set the port of the vcsserver. Useful when testing "
81 "Allows to set the port of the vcsserver. Useful when testing "
82 "against an already running server and random ports cause "
82 "against an already running server and random ports cause "
83 "trouble."))
83 "trouble."))
84 parser.addini(
84 parser.addini(
85 'vcsserver_config_http',
85 'vcsserver_config_http',
86 "Start the HTTP VCSServer with the specified config file.")
86 "Start the HTTP VCSServer with the specified config file.")
87 parser.addini(
87 parser.addini(
88 'vcsserver_protocol',
88 'vcsserver_protocol',
89 "Start the VCSServer with HTTP protocol support.")
89 "Start the VCSServer with HTTP protocol support.")
90
90
91
91
92 @pytest.fixture(scope='session')
92 @pytest.fixture(scope='session')
93 def vcsserver(request, vcsserver_port, vcsserver_factory):
93 def vcsserver(request, vcsserver_port, vcsserver_factory):
94 """
94 """
95 Session scope VCSServer.
95 Session scope VCSServer.
96
96
97 Tests wich need the VCSServer have to rely on this fixture in order
97 Tests wich need the VCSServer have to rely on this fixture in order
98 to ensure it will be running.
98 to ensure it will be running.
99
99
100 For specific needs, the fixture vcsserver_factory can be used. It allows to
100 For specific needs, the fixture vcsserver_factory can be used. It allows to
101 adjust the configuration file for the test run.
101 adjust the configuration file for the test run.
102
102
103 Command line args:
103 Command line args:
104
104
105 --without-vcsserver: Allows to switch this fixture off. You have to
105 --without-vcsserver: Allows to switch this fixture off. You have to
106 manually start the server.
106 manually start the server.
107
107
108 --vcsserver-port: Will expect the VCSServer to listen on this port.
108 --vcsserver-port: Will expect the VCSServer to listen on this port.
109 """
109 """
110
110
111 if not request.config.getoption('with_vcsserver'):
111 if not request.config.getoption('with_vcsserver'):
112 return None
112 return None
113
113
114 use_http = _use_vcs_http_server(request.config)
114 use_http = _use_vcs_http_server(request.config)
115 return vcsserver_factory(
115 return vcsserver_factory(
116 request, use_http=use_http, vcsserver_port=vcsserver_port)
116 request, use_http=use_http, vcsserver_port=vcsserver_port)
117
117
118
118
119 @pytest.fixture(scope='session')
119 @pytest.fixture(scope='session')
120 def vcsserver_factory(tmpdir_factory):
120 def vcsserver_factory(tmpdir_factory):
121 """
121 """
122 Use this if you need a running vcsserver with a special configuration.
122 Use this if you need a running vcsserver with a special configuration.
123 """
123 """
124
124
125 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
125 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
126
126
127 if vcsserver_port is None:
127 if vcsserver_port is None:
128 vcsserver_port = get_available_port()
128 vcsserver_port = get_available_port()
129
129
130 overrides = list(overrides)
130 overrides = list(overrides)
131 if use_http:
131 if use_http:
132 overrides.append({'server:main': {'port': vcsserver_port}})
132 overrides.append({'server:main': {'port': vcsserver_port}})
133 else:
133 else:
134 overrides.append({'DEFAULT': {'port': vcsserver_port}})
134 overrides.append({'DEFAULT': {'port': vcsserver_port}})
135
135
136 if is_cygwin():
136 if is_cygwin():
137 platform_override = {'DEFAULT': {
137 platform_override = {'DEFAULT': {
138 'beaker.cache.repo_object.type': 'nocache'}}
138 'beaker.cache.repo_object.type': 'nocache'}}
139 overrides.append(platform_override)
139 overrides.append(platform_override)
140
140
141 option_name = 'vcsserver_config_http' if use_http else ''
141 option_name = 'vcsserver_config_http' if use_http else ''
142 override_option_name = 'vcsserver_config_override'
142 override_option_name = 'vcsserver_config_override'
143 config_file = get_config(
143 config_file = get_config(
144 request.config, option_name=option_name,
144 request.config, option_name=option_name,
145 override_option_name=override_option_name, overrides=overrides,
145 override_option_name=override_option_name, overrides=overrides,
146 basetemp=tmpdir_factory.getbasetemp().strpath,
146 basetemp=tmpdir_factory.getbasetemp().strpath,
147 prefix='test_vcs_')
147 prefix='test_vcs_')
148
148
149 print("Using the VCSServer configuration:{}".format(config_file))
149 print("Using the VCSServer configuration:{}".format(config_file))
150 ServerClass = HttpVCSServer if use_http else None
150 ServerClass = HttpVCSServer if use_http else None
151 server = ServerClass(config_file)
151 server = ServerClass(config_file)
152 server.start()
152 server.start()
153
153
154 @request.addfinalizer
154 @request.addfinalizer
155 def cleanup():
155 def cleanup():
156 server.shutdown()
156 server.shutdown()
157
157
158 server.wait_until_ready()
158 server.wait_until_ready()
159 return server
159 return server
160
160
161 return factory
161 return factory
162
162
163
163
164 def is_cygwin():
164 def is_cygwin():
165 return 'cygwin' in platform.system().lower()
165 return 'cygwin' in platform.system().lower()
166
166
167
167
168 def _use_vcs_http_server(config):
168 def _use_vcs_http_server(config):
169 protocol_option = 'vcsserver_protocol'
169 protocol_option = 'vcsserver_protocol'
170 protocol = (
170 protocol = (
171 config.getoption(protocol_option) or
171 config.getoption(protocol_option) or
172 config.getini(protocol_option) or
172 config.getini(protocol_option) or
173 'http')
173 'http')
174 return protocol == 'http'
174 return protocol == 'http'
175
175
176
176
177 def _use_log_level(config):
177 def _use_log_level(config):
178 level = config.getoption('test_loglevel') or 'warn'
178 level = config.getoption('test_loglevel') or 'warn'
179 return level.upper()
179 return level.upper()
180
180
181
181
182 class VCSServer(object):
182 class VCSServer(object):
183 """
183 """
184 Represents a running VCSServer instance.
184 Represents a running VCSServer instance.
185 """
185 """
186
186
187 _args = []
187 _args = []
188
188
189 def start(self):
189 def start(self):
190 print("Starting the VCSServer: {}".format(self._args))
190 print("Starting the VCSServer: {}".format(self._args))
191 self.process = subprocess32.Popen(self._args)
191 self.process = subprocess32.Popen(self._args)
192
192
193 def wait_until_ready(self, timeout=30):
193 def wait_until_ready(self, timeout=30):
194 raise NotImplementedError()
194 raise NotImplementedError()
195
195
196 def shutdown(self):
196 def shutdown(self):
197 self.process.kill()
197 self.process.kill()
198
198
199
199
200 class HttpVCSServer(VCSServer):
200 class HttpVCSServer(VCSServer):
201 """
201 """
202 Represents a running VCSServer instance.
202 Represents a running VCSServer instance.
203 """
203 """
204 def __init__(self, config_file):
204 def __init__(self, config_file):
205 config_data = configobj.ConfigObj(config_file)
205 config_data = configobj.ConfigObj(config_file)
206 self._config = config_data['server:main']
206 self._config = config_data['server:main']
207
207
208 args = ['pserve', config_file]
208 args = ['pserve', config_file]
209 self._args = args
209 self._args = args
210
210
211 @property
211 @property
212 def http_url(self):
212 def http_url(self):
213 template = 'http://{host}:{port}/'
213 template = 'http://{host}:{port}/'
214 return template.format(**self._config)
214 return template.format(**self._config)
215
215
216 def start(self):
216 def start(self):
217 self.process = subprocess32.Popen(self._args)
217 self.process = subprocess32.Popen(self._args)
218
218
219 def wait_until_ready(self, timeout=30):
219 def wait_until_ready(self, timeout=30):
220 host = self._config['host']
220 host = self._config['host']
221 port = self._config['port']
221 port = self._config['port']
222 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
222 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
223 start = time.time()
223 start = time.time()
224
224
225 while time.time() - start < timeout:
225 while time.time() - start < timeout:
226 try:
226 try:
227 urlopen(status_url)
227 urlopen(status_url)
228 break
228 break
229 except URLError:
229 except URLError:
230 time.sleep(0.2)
230 time.sleep(0.2)
231 else:
231 else:
232 pytest.exit(
232 pytest.exit(
233 "Starting the VCSServer failed or took more than {} "
233 "Starting the VCSServer failed or took more than {} "
234 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
234 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
235
235
236 def shutdown(self):
236 def shutdown(self):
237 self.process.kill()
237 self.process.kill()
238
238
239
239
240 @pytest.fixture(scope='session')
240 @pytest.fixture(scope='session')
241 def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
241 def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
242 option_name = 'pylons_config'
242 option_name = 'pylons_config'
243 log_level = _use_log_level(request.config)
243 log_level = _use_log_level(request.config)
244
244
245 overrides = [
245 overrides = [
246 {'server:main': {'port': rcserver_port}},
246 {'server:main': {'port': rcserver_port}},
247 {'app:main': {
247 {'app:main': {
248 'vcs.server': 'localhost:%s' % vcsserver_port,
248 'vcs.server': 'localhost:%s' % vcsserver_port,
249 # johbo: We will always start the VCSServer on our own based on the
249 # johbo: We will always start the VCSServer on our own based on the
250 # fixtures of the test cases. For the test run it must always be
250 # fixtures of the test cases. For the test run it must always be
251 # off in the INI file.
251 # off in the INI file.
252 'vcs.start_server': 'false',
252 'vcs.start_server': 'false',
253 }},
253 }},
254
254
255 {'handler_console': {
255 {'handler_console': {
256 'class ': 'StreamHandler',
256 'class ': 'StreamHandler',
257 'args ': '(sys.stderr,)',
257 'args ': '(sys.stderr,)',
258 'level': log_level,
258 'level': log_level,
259 }},
259 }},
260
260
261 ]
261 ]
262 if _use_vcs_http_server(request.config):
262 if _use_vcs_http_server(request.config):
263 overrides.append({
263 overrides.append({
264 'app:main': {
264 'app:main': {
265 'vcs.server.protocol': 'http',
265 'vcs.server.protocol': 'http',
266 'vcs.scm_app_implementation': 'http',
266 'vcs.scm_app_implementation': 'http',
267 'vcs.hooks.protocol': 'http',
267 'vcs.hooks.protocol': 'http',
268 }
268 }
269 })
269 })
270
270
271 filename = get_config(
271 filename = get_config(
272 request.config, option_name=option_name,
272 request.config, option_name=option_name,
273 override_option_name='{}_override'.format(option_name),
273 override_option_name='{}_override'.format(option_name),
274 overrides=overrides,
274 overrides=overrides,
275 basetemp=tmpdir_factory.getbasetemp().strpath,
275 basetemp=tmpdir_factory.getbasetemp().strpath,
276 prefix='test_rce_')
276 prefix='test_rce_')
277 return filename
277 return filename
278
278
279
279
280 @pytest.fixture(scope='session')
280 @pytest.fixture(scope='session')
281 def rcserver_port(request):
281 def rcserver_port(request):
282 port = get_available_port()
282 port = get_available_port()
283 print('Using rcserver port {}'.format(port))
283 print('Using rcserver port {}'.format(port))
284 return port
284 return port
285
285
286
286
287 @pytest.fixture(scope='session')
287 @pytest.fixture(scope='session')
288 def vcsserver_port(request):
288 def vcsserver_port(request):
289 port = request.config.getoption('--vcsserver-port')
289 port = request.config.getoption('--vcsserver-port')
290 if port is None:
290 if port is None:
291 port = get_available_port()
291 port = get_available_port()
292 print('Using vcsserver port {}'.format(port))
292 print('Using vcsserver port {}'.format(port))
293 return port
293 return port
294
294
295
295
296 def get_available_port():
296 def get_available_port():
297 family = socket.AF_INET
297 family = socket.AF_INET
298 socktype = socket.SOCK_STREAM
298 socktype = socket.SOCK_STREAM
299 host = '127.0.0.1'
299 host = '127.0.0.1'
300
300
301 mysocket = socket.socket(family, socktype)
301 mysocket = socket.socket(family, socktype)
302 mysocket.bind((host, 0))
302 mysocket.bind((host, 0))
303 port = mysocket.getsockname()[1]
303 port = mysocket.getsockname()[1]
304 mysocket.close()
304 mysocket.close()
305 del mysocket
305 del mysocket
306 return port
306 return port
307
307
308
308
309 @pytest.fixture(scope='session')
309 @pytest.fixture(scope='session')
310 def available_port_factory():
310 def available_port_factory():
311 """
311 """
312 Returns a callable which returns free port numbers.
312 Returns a callable which returns free port numbers.
313 """
313 """
314 return get_available_port
314 return get_available_port
315
315
316
316
317 @pytest.fixture
317 @pytest.fixture
318 def available_port(available_port_factory):
318 def available_port(available_port_factory):
319 """
319 """
320 Gives you one free port for the current test.
320 Gives you one free port for the current test.
321
321
322 Uses "available_port_factory" to retrieve the port.
322 Uses "available_port_factory" to retrieve the port.
323 """
323 """
324 return available_port_factory()
324 return available_port_factory()
325
325
326
326
327 @pytest.fixture(scope='session')
327 @pytest.fixture(scope='session')
328 def baseapp(ini_config, vcsserver, http_environ_session):
328 def baseapp(ini_config, vcsserver, http_environ_session):
329 from rhodecode.lib.pyramid_utils import get_app_config
329 from rhodecode.lib.pyramid_utils import get_app_config
330 from rhodecode.config.middleware import make_pyramid_app
330 from rhodecode.config.middleware import make_pyramid_app
331
331
332 print("Using the RhodeCode configuration:{}".format(ini_config))
332 print("Using the RhodeCode configuration:{}".format(ini_config))
333 pyramid.paster.setup_logging(ini_config)
333 pyramid.paster.setup_logging(ini_config)
334
334
335 settings = get_app_config(ini_config)
335 settings = get_app_config(ini_config)
336 app = make_pyramid_app({}, **settings)
336 app = make_pyramid_app({'__file__': ini_config}, **settings)
337
337
338 return app
338 return app
339
339
340
340
341 @pytest.fixture(scope='session')
341 @pytest.fixture(scope='session')
342 def testini_factory(tmpdir_factory, ini_config):
342 def testini_factory(tmpdir_factory, ini_config):
343 """
343 """
344 Factory to create an INI file based on TestINI.
344 Factory to create an INI file based on TestINI.
345
345
346 It will make sure to place the INI file in the correct directory.
346 It will make sure to place the INI file in the correct directory.
347 """
347 """
348 basetemp = tmpdir_factory.getbasetemp().strpath
348 basetemp = tmpdir_factory.getbasetemp().strpath
349 return TestIniFactory(basetemp, ini_config)
349 return TestIniFactory(basetemp, ini_config)
350
350
351
351
352 class TestIniFactory(object):
352 class TestIniFactory(object):
353
353
354 def __init__(self, basetemp, template_ini):
354 def __init__(self, basetemp, template_ini):
355 self._basetemp = basetemp
355 self._basetemp = basetemp
356 self._template_ini = template_ini
356 self._template_ini = template_ini
357
357
358 def __call__(self, ini_params, new_file_prefix='test'):
358 def __call__(self, ini_params, new_file_prefix='test'):
359 ini_file = TestINI(
359 ini_file = TestINI(
360 self._template_ini, ini_params=ini_params,
360 self._template_ini, ini_params=ini_params,
361 new_file_prefix=new_file_prefix, dir=self._basetemp)
361 new_file_prefix=new_file_prefix, dir=self._basetemp)
362 result = ini_file.create()
362 result = ini_file.create()
363 return result
363 return result
364
364
365
365
366 def get_config(
366 def get_config(
367 config, option_name, override_option_name, overrides=None,
367 config, option_name, override_option_name, overrides=None,
368 basetemp=None, prefix='test'):
368 basetemp=None, prefix='test'):
369 """
369 """
370 Find a configuration file and apply overrides for the given `prefix`.
370 Find a configuration file and apply overrides for the given `prefix`.
371 """
371 """
372 config_file = (
372 config_file = (
373 config.getoption(option_name) or config.getini(option_name))
373 config.getoption(option_name) or config.getini(option_name))
374 if not config_file:
374 if not config_file:
375 pytest.exit(
375 pytest.exit(
376 "Configuration error, could not extract {}.".format(option_name))
376 "Configuration error, could not extract {}.".format(option_name))
377
377
378 overrides = overrides or []
378 overrides = overrides or []
379 config_override = config.getoption(override_option_name)
379 config_override = config.getoption(override_option_name)
380 if config_override:
380 if config_override:
381 overrides.append(config_override)
381 overrides.append(config_override)
382 temp_ini_file = TestINI(
382 temp_ini_file = TestINI(
383 config_file, ini_params=overrides, new_file_prefix=prefix,
383 config_file, ini_params=overrides, new_file_prefix=prefix,
384 dir=basetemp)
384 dir=basetemp)
385
385
386 return temp_ini_file.create()
386 return temp_ini_file.create()
General Comments 0
You need to be logged in to leave comments. Login now