##// END OF EJS Templates
migration-tests: improved error reporting
marcink -
r1293:5d5d616f default
parent child Browse files
Show More
@@ -1,273 +1,275 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 from subprocess32 import Popen, PIPE
22 22 import os
23 23 import shutil
24 24 import sys
25 25 import tempfile
26 26
27 27 import pytest
28 28 from sqlalchemy.engine import url
29 29
30 30 from rhodecode.tests.fixture import TestINI
31 31
32 32
33 33 def _get_dbs_from_metafunc(metafunc):
34 34 if hasattr(metafunc.function, 'dbs'):
35 35 # Supported backends by this test function, created from
36 36 # pytest.mark.dbs
37 37 backends = metafunc.function.dbs.args
38 38 else:
39 39 backends = metafunc.config.getoption('--dbs')
40 40 return backends
41 41
42 42
43 43 def pytest_generate_tests(metafunc):
44 44 # Support test generation based on --dbs parameter
45 45 if 'db_backend' in metafunc.fixturenames:
46 46 requested_backends = set(metafunc.config.getoption('--dbs'))
47 47 backends = _get_dbs_from_metafunc(metafunc)
48 48 backends = requested_backends.intersection(backends)
49 49 # TODO: johbo: Disabling a backend did not work out with
50 50 # parametrization, find better way to achieve this.
51 51 if not backends:
52 52 metafunc.function._skip = True
53 53 metafunc.parametrize('db_backend_name', backends)
54 54
55 55
56 56 def pytest_collection_modifyitems(session, config, items):
57 57 remaining = [
58 58 i for i in items if not getattr(i.obj, '_skip', False)]
59 59 items[:] = remaining
60 60
61 61
62 62 @pytest.fixture
63 63 def db_backend(
64 64 request, db_backend_name, pylons_config, tmpdir_factory):
65 65 basetemp = tmpdir_factory.getbasetemp().strpath
66 66 klass = _get_backend(db_backend_name)
67 67
68 68 option_name = '--{}-connection-string'.format(db_backend_name)
69 69 connection_string = request.config.getoption(option_name) or None
70 70
71 71 return klass(
72 72 config_file=pylons_config, basetemp=basetemp,
73 73 connection_string=connection_string)
74 74
75 75
76 76 def _get_backend(backend_type):
77 77 return {
78 78 'sqlite': SQLiteDBBackend,
79 79 'postgres': PostgresDBBackend,
80 80 'mysql': MySQLDBBackend,
81 81 '': EmptyDBBackend
82 82 }[backend_type]
83 83
84 84
85 85 class DBBackend(object):
86 86 _store = os.path.dirname(os.path.abspath(__file__))
87 87 _type = None
88 88 _base_ini_config = [{'app:main': {'vcs.start_server': 'false'}}]
89 89 _db_url = [{'app:main': {'sqlalchemy.db1.url': ''}}]
90 90 _base_db_name = 'rhodecode_test_db_backend'
91 91
92 92 def __init__(
93 93 self, config_file, db_name=None, basetemp=None,
94 94 connection_string=None):
95 95 self.fixture_store = os.path.join(self._store, self._type)
96 96 self.db_name = db_name or self._base_db_name
97 97 self._base_ini_file = config_file
98 98 self.stderr = ''
99 99 self.stdout = ''
100 100 self._basetemp = basetemp or tempfile.gettempdir()
101 101 self._repos_location = os.path.join(self._basetemp, 'rc_test_repos')
102 102 self.connection_string = connection_string
103 103
104 104 @property
105 105 def connection_string(self):
106 106 return self._connection_string
107 107
108 108 @connection_string.setter
109 109 def connection_string(self, new_connection_string):
110 110 if not new_connection_string:
111 111 new_connection_string = self.get_default_connection_string()
112 112 else:
113 113 new_connection_string = new_connection_string.format(
114 114 db_name=self.db_name)
115 115 url_parts = url.make_url(new_connection_string)
116 116 self._connection_string = new_connection_string
117 117 self.user = url_parts.username
118 118 self.password = url_parts.password
119 119 self.host = url_parts.host
120 120
121 121 def get_default_connection_string(self):
122 122 raise NotImplementedError('default connection_string is required.')
123 123
124 124 def execute(self, cmd, env=None, *args):
125 125 """
126 126 Runs command on the system with given ``args``.
127 127 """
128 128
129 129 command = cmd + ' ' + ' '.join(args)
130 130 sys.stdout.write(command)
131 131
132 132 # Tell Python to use UTF-8 encoding out stdout
133 133 _env = os.environ.copy()
134 134 _env['PYTHONIOENCODING'] = 'UTF-8'
135 135 if env:
136 136 _env.update(env)
137 137 self.p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, env=_env)
138 138 self.stdout, self.stderr = self.p.communicate()
139 139 sys.stdout.write('COMMAND:'+command+'\n')
140 140 sys.stdout.write(self.stdout)
141 141 return self.stdout, self.stderr
142 142
143 143 def assert_returncode_success(self):
144 assert self.p.returncode == 0, self.stderr
144 if not self.p.returncode == 0:
145 print(self.stderr)
146 raise AssertionError('non 0 retcode:{}'.format(self.p.returncode))
145 147
146 148 def setup_rhodecode_db(self, ini_params=None, env=None):
147 149 if not ini_params:
148 150 ini_params = self._base_ini_config
149 151
150 152 ini_params.extend(self._db_url)
151 153 with TestINI(self._base_ini_file, ini_params,
152 154 self._type, destroy=True) as _ini_file:
153 155 if not os.path.isdir(self._repos_location):
154 156 os.makedirs(self._repos_location)
155 157 self.execute(
156 158 "paster setup-rhodecode {0} --user=marcink "
157 159 "--email=marcin@rhodeocode.com --password={1} "
158 160 "--repos={2} --force-yes".format(
159 161 _ini_file, 'qweqwe', self._repos_location), env=env)
160 162
161 163 def upgrade_database(self, ini_params=None):
162 164 if not ini_params:
163 165 ini_params = self._base_ini_config
164 166 ini_params.extend(self._db_url)
165 167
166 168 test_ini = TestINI(
167 169 self._base_ini_file, ini_params, self._type, destroy=True)
168 170 with test_ini as ini_file:
169 171 if not os.path.isdir(self._repos_location):
170 172 os.makedirs(self._repos_location)
171 173 self.execute(
172 174 "paster upgrade-db {} --force-yes".format(ini_file))
173 175
174 176 def setup_db(self):
175 177 raise NotImplementedError
176 178
177 179 def teardown_db(self):
178 180 raise NotImplementedError
179 181
180 182 def import_dump(self, dumpname):
181 183 raise NotImplementedError
182 184
183 185
184 186 class EmptyDBBackend(DBBackend):
185 187 _type = ''
186 188
187 189 def setup_db(self):
188 190 pass
189 191
190 192 def teardown_db(self):
191 193 pass
192 194
193 195 def import_dump(self, dumpname):
194 196 pass
195 197
196 198 def assert_returncode_success(self):
197 199 assert True
198 200
199 201
200 202 class SQLiteDBBackend(DBBackend):
201 203 _type = 'sqlite'
202 204
203 205 def get_default_connection_string(self):
204 206 return 'sqlite:///{}/{}.sqlite'.format(self._basetemp, self.db_name)
205 207
206 208 def setup_db(self):
207 209 # dump schema for tests
208 210 # cp -v $TEST_DB_NAME
209 211 self._db_url = [{'app:main': {
210 212 'sqlalchemy.db1.url': self.connection_string}}]
211 213
212 214 def import_dump(self, dumpname):
213 215 dump = os.path.join(self.fixture_store, dumpname)
214 216 shutil.copy(
215 217 dump,
216 218 os.path.join(self._basetemp, '{0.db_name}.sqlite'.format(self)))
217 219
218 220 def teardown_db(self):
219 221 self.execute("rm -rf {}.sqlite".format(
220 222 os.path.join(self._basetemp, self.db_name)))
221 223
222 224
223 225 class MySQLDBBackend(DBBackend):
224 226 _type = 'mysql'
225 227
226 228 def get_default_connection_string(self):
227 229 return 'mysql://root:qweqwe@127.0.0.1/{}'.format(self.db_name)
228 230
229 231 def setup_db(self):
230 232 # dump schema for tests
231 233 # mysqldump -uroot -pqweqwe $TEST_DB_NAME
232 234 self._db_url = [{'app:main': {
233 235 'sqlalchemy.db1.url': self.connection_string}}]
234 236 self.execute("mysql -v -u{} -p{} -e 'create database '{}';'".format(
235 237 self.user, self.password, self.db_name))
236 238
237 239 def import_dump(self, dumpname):
238 240 dump = os.path.join(self.fixture_store, dumpname)
239 241 self.execute("mysql -u{} -p{} {} < {}".format(
240 242 self.user, self.password, self.db_name, dump))
241 243
242 244 def teardown_db(self):
243 245 self.execute("mysql -v -u{} -p{} -e 'drop database '{}';'".format(
244 246 self.user, self.password, self.db_name))
245 247
246 248
247 249 class PostgresDBBackend(DBBackend):
248 250 _type = 'postgres'
249 251
250 252 def get_default_connection_string(self):
251 253 return 'postgresql://postgres:qweqwe@localhost/{}'.format(self.db_name)
252 254
253 255 def setup_db(self):
254 256 # dump schema for tests
255 257 # pg_dump -U postgres -h localhost $TEST_DB_NAME
256 258 self._db_url = [{'app:main': {
257 259 'sqlalchemy.db1.url':
258 260 self.connection_string}}]
259 261 self.execute("PGPASSWORD={} psql -U {} -h localhost "
260 262 "-c 'create database '{}';'".format(
261 263 self.password, self.user, self.db_name))
262 264
263 265 def teardown_db(self):
264 266 self.execute("PGPASSWORD={} psql -U {} -h localhost "
265 267 "-c 'drop database if exists '{}';'".format(
266 268 self.password, self.user, self.db_name))
267 269
268 270 def import_dump(self, dumpname):
269 271 dump = os.path.join(self.fixture_store, dumpname)
270 272 self.execute(
271 273 "PGPASSWORD={} psql -U {} -h localhost -d {} -1 "
272 274 "-f {}".format(
273 275 self.password, self.user, self.db_name, dump))
General Comments 0
You need to be logged in to leave comments. Login now