##// END OF EJS Templates
fix(tests): fixed db migration tests after introduction of RC_TEST flag
super-admin -
r5351:adc169b4 default
parent child Browse files
Show More
@@ -1,91 +1,91 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import os
20 20 import datetime
21 21 import collections
22 22 import logging
23 23
24 24
25 25 now = datetime.datetime.now()
26 26 now = now.strftime("%Y-%m-%d %H:%M:%S") + '.' + f"{int(now.microsecond/1000):03d}"
27 27
28 28 log = logging.getLogger(__name__)
29 29 log.debug(f'{now} Starting RhodeCode imports...')
30 30
31 31
32 32 VERSION = tuple(open(os.path.join(
33 33 os.path.dirname(__file__), 'VERSION')).read().split('.'))
34 34
35 35 BACKENDS = collections.OrderedDict()
36 36
37 37 BACKENDS['hg'] = 'Mercurial repository'
38 38 BACKENDS['git'] = 'Git repository'
39 39 BACKENDS['svn'] = 'Subversion repository'
40 40
41 41
42 42 CELERY_ENABLED = False
43 43 CELERY_EAGER = False
44 44
45 45 # link to config for pyramid
46 46 CONFIG = {}
47 47
48 48
49 49 class ConfigGet:
50 50 NotGiven = object()
51 51
52 52 def _get_val_or_missing(self, key, missing):
53 53 if key not in CONFIG:
54 54 if missing == self.NotGiven:
55 55 return missing
56 56 # we don't get key, we don't get missing value, return nothing similar as config.get(key)
57 57 return None
58 58 else:
59 59 val = CONFIG[key]
60 60 return val
61 61
62 62 def get_str(self, key, missing=NotGiven):
63 63 from rhodecode.lib.str_utils import safe_str
64 64 val = self._get_val_or_missing(key, missing)
65 65 return safe_str(val)
66 66
67 67 def get_int(self, key, missing=NotGiven):
68 68 from rhodecode.lib.str_utils import safe_int
69 69 val = self._get_val_or_missing(key, missing)
70 70 return safe_int(val)
71 71
72 72 def get_bool(self, key, missing=NotGiven):
73 73 from rhodecode.lib.type_utils import str2bool
74 74 val = self._get_val_or_missing(key, missing)
75 75 return str2bool(val)
76 76
77 77 # Populated with the settings dictionary from application init in
78 78 # rhodecode.conf.environment.load_pyramid_environment
79 79 PYRAMID_SETTINGS = {}
80 80
81 81 # Linked module for extensions
82 82 EXTENSIONS = {}
83 83
84 84 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
85 85 __dbversion__ = 114 # defines current db version for migrations
86 86 __license__ = 'AGPLv3, and Commercial License'
87 87 __author__ = 'RhodeCode GmbH'
88 88 __url__ = 'https://code.rhodecode.com'
89 89
90 is_test = os.getenv('RC_TEST')
90 is_test = os.getenv('RC_TEST', '0') == '1'
91 91 disable_error_handler = False
@@ -1,287 +1,290 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 from subprocess import Popen, PIPE
21 21 import os
22 22 import sys
23 23 import tempfile
24 24
25 25 import pytest
26 26 from sqlalchemy.engine import url
27 27
28 28 from rhodecode.lib.str_utils import safe_str, safe_bytes
29 29 from rhodecode.tests.fixture import TestINI
30 30
31 31
32 32 def _get_dbs_from_metafunc(metafunc):
33 33 dbs_mark = metafunc.definition.get_closest_marker('dbs')
34 34
35 35 if dbs_mark:
36 36 # Supported backends by this test function, created from pytest.mark.dbs
37 37 backends = dbs_mark.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, ini_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=ini_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 'startup.import_repos': 'false'}}]
90 90 _db_url = [{'app:main': {'sqlalchemy.db1.url': ''}}]
91 91 _base_db_name = 'rhodecode_test_db_backend'
92 std_env = {'RC_TEST': '0'}
92 93
93 94 def __init__(
94 95 self, config_file, db_name=None, basetemp=None,
95 96 connection_string=None):
96 97
97 98 from rhodecode.lib.vcs.backends.hg import largefiles_store
98 99 from rhodecode.lib.vcs.backends.git import lfs_store
99 100
100 101 self.fixture_store = os.path.join(self._store, self._type)
101 102 self.db_name = db_name or self._base_db_name
102 103 self._base_ini_file = config_file
103 104 self.stderr = ''
104 105 self.stdout = ''
105 106 self._basetemp = basetemp or tempfile.gettempdir()
106 107 self._repos_location = os.path.join(self._basetemp, 'rc_test_repos')
107 108 self._repos_hg_largefiles_store = largefiles_store(self._basetemp)
108 109 self._repos_git_lfs_store = lfs_store(self._basetemp)
109 110 self.connection_string = connection_string
110 111
111 112 @property
112 113 def connection_string(self):
113 114 return self._connection_string
114 115
115 116 @connection_string.setter
116 117 def connection_string(self, new_connection_string):
117 118 if not new_connection_string:
118 119 new_connection_string = self.get_default_connection_string()
119 120 else:
120 121 new_connection_string = new_connection_string.format(
121 122 db_name=self.db_name)
122 123 url_parts = url.make_url(new_connection_string)
123 124 self._connection_string = new_connection_string
124 125 self.user = url_parts.username
125 126 self.password = url_parts.password
126 127 self.host = url_parts.host
127 128
128 129 def get_default_connection_string(self):
129 130 raise NotImplementedError('default connection_string is required.')
130 131
131 132 def execute(self, cmd, env=None, *args):
132 133 """
133 134 Runs command on the system with given ``args``.
134 135 """
135 136
136 137 command = cmd + ' ' + ' '.join(args)
137 sys.stdout.write(command)
138 sys.stdout.write(f'CMD: {command}')
138 139
139 140 # Tell Python to use UTF-8 encoding out stdout
140 141 _env = os.environ.copy()
141 142 _env['PYTHONIOENCODING'] = 'UTF-8'
143 _env.update(self.std_env)
142 144 if env:
143 145 _env.update(env)
146
144 147 self.p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, env=_env)
145 148 self.stdout, self.stderr = self.p.communicate()
146 149 stdout_str = safe_str(self.stdout)
147 150 sys.stdout.write(f'COMMAND:{command}\n')
148 151 sys.stdout.write(stdout_str)
149 152 return self.stdout, self.stderr
150 153
151 154 def assert_returncode_success(self):
152 155 from rich import print as pprint
153 156 if not self.p.returncode == 0:
154 157 pprint(safe_str(self.stderr))
155 158 raise AssertionError(f'non 0 retcode:{self.p.returncode}')
156 159
157 160 def assert_correct_output(self, stdout, version):
158 161 assert b'UPGRADE FOR STEP %b COMPLETED' % safe_bytes(version) in stdout
159 162
160 163 def setup_rhodecode_db(self, ini_params=None, env=None):
161 164 if not ini_params:
162 165 ini_params = self._base_ini_config
163 166
164 167 ini_params.extend(self._db_url)
165 168 with TestINI(self._base_ini_file, ini_params,
166 169 self._type, destroy=True) as _ini_file:
167 170
168 171 if not os.path.isdir(self._repos_location):
169 172 os.makedirs(self._repos_location)
170 173 if not os.path.isdir(self._repos_hg_largefiles_store):
171 174 os.makedirs(self._repos_hg_largefiles_store)
172 175 if not os.path.isdir(self._repos_git_lfs_store):
173 176 os.makedirs(self._repos_git_lfs_store)
174 177
175 178 return self.execute(
176 179 "rc-setup-app {0} --user=marcink "
177 180 "--email=marcin@rhodeocode.com --password={1} "
178 181 "--repos={2} --force-yes".format(
179 182 _ini_file, 'qweqwe', self._repos_location), env=env)
180 183
181 184 def upgrade_database(self, ini_params=None):
182 185 if not ini_params:
183 186 ini_params = self._base_ini_config
184 187 ini_params.extend(self._db_url)
185 188
186 189 test_ini = TestINI(
187 190 self._base_ini_file, ini_params, self._type, destroy=True)
188 191 with test_ini as ini_file:
189 192 if not os.path.isdir(self._repos_location):
190 193 os.makedirs(self._repos_location)
191 194
192 195 return self.execute(
193 196 "rc-upgrade-db {0} --force-yes".format(ini_file))
194 197
195 198 def setup_db(self):
196 199 raise NotImplementedError
197 200
198 201 def teardown_db(self):
199 202 raise NotImplementedError
200 203
201 204 def import_dump(self, dumpname):
202 205 raise NotImplementedError
203 206
204 207
205 208 class EmptyDBBackend(DBBackend):
206 209 _type = ''
207 210
208 211 def setup_db(self):
209 212 pass
210 213
211 214 def teardown_db(self):
212 215 pass
213 216
214 217 def import_dump(self, dumpname):
215 218 pass
216 219
217 220 def assert_returncode_success(self):
218 221 assert True
219 222
220 223
221 224 class SQLiteDBBackend(DBBackend):
222 225 _type = 'sqlite'
223 226
224 227 def get_default_connection_string(self):
225 228 return 'sqlite:///{}/{}.sqlite'.format(self._basetemp, self.db_name)
226 229
227 230 def setup_db(self):
228 231 # dump schema for tests
229 232 # cp -v $TEST_DB_NAME
230 233 self._db_url = [{'app:main': {
231 234 'sqlalchemy.db1.url': self.connection_string}}]
232 235
233 236 def import_dump(self, dumpname):
234 237 dump = os.path.join(self.fixture_store, dumpname)
235 238 target = os.path.join(self._basetemp, '{0.db_name}.sqlite'.format(self))
236 239 return self.execute(f'cp -v {dump} {target}')
237 240
238 241 def teardown_db(self):
239 242 target_db = os.path.join(self._basetemp, self.db_name)
240 243 return self.execute(f"rm -rf {target_db}.sqlite")
241 244
242 245
243 246 class MySQLDBBackend(DBBackend):
244 247 _type = 'mysql'
245 248
246 249 def get_default_connection_string(self):
247 250 return 'mysql://root:qweqwe@127.0.0.1/{}'.format(self.db_name)
248 251
249 252 def setup_db(self):
250 253 # dump schema for tests
251 254 # mysqldump -uroot -pqweqwe $TEST_DB_NAME
252 255 self._db_url = [{'app:main': {
253 256 'sqlalchemy.db1.url': self.connection_string}}]
254 257 return self.execute("mysql -v -u{} -p{} -e 'create database '{}';'".format(
255 258 self.user, self.password, self.db_name))
256 259
257 260 def import_dump(self, dumpname):
258 261 dump = os.path.join(self.fixture_store, dumpname)
259 262 return self.execute("mysql -u{} -p{} {} < {}".format(
260 263 self.user, self.password, self.db_name, dump))
261 264
262 265 def teardown_db(self):
263 266 return self.execute("mysql -v -u{} -p{} -e 'drop database '{}';'".format(
264 267 self.user, self.password, self.db_name))
265 268
266 269
267 270 class PostgresDBBackend(DBBackend):
268 271 _type = 'postgres'
269 272
270 273 def get_default_connection_string(self):
271 274 return 'postgresql://postgres:qweqwe@localhost/{}'.format(self.db_name)
272 275
273 276 def setup_db(self):
274 277 # dump schema for tests
275 278 # pg_dump -U postgres -h localhost $TEST_DB_NAME
276 279 self._db_url = [{'app:main': {'sqlalchemy.db1.url': self.connection_string}}]
277 280 cmd = f"PGPASSWORD={self.password} psql -U {self.user} -h localhost -c 'create database '{self.db_name}';'"
278 281 return self.execute(cmd)
279 282
280 283 def teardown_db(self):
281 284 cmd = f"PGPASSWORD={self.password} psql -U {self.user} -h localhost -c 'drop database if exists '{self.db_name}';'"
282 285 return self.execute(cmd)
283 286
284 287 def import_dump(self, dumpname):
285 288 dump = os.path.join(self.fixture_store, dumpname)
286 289 cmd = f"PGPASSWORD={self.password} psql -U {self.user} -h localhost -d {self.db_name} -1 -f {dump}"
287 290 return self.execute(cmd)
General Comments 0
You need to be logged in to leave comments. Login now