author | Sylvain Thénault <sylvain.thenault@logilab.fr> |
Wed, 09 Sep 2009 16:48:39 +0200 | |
branch | stable |
changeset 3143 | 0e549b299f0b |
parent 2732 | c28b5f16f3af |
child 2773 | b2530e3e0afb |
child 3153 | 93ce84956679 |
permissions | -rw-r--r-- |
0 | 1 |
"""Test tools for cubicweb |
2 |
||
3 |
:organization: Logilab |
|
1977
606923dff11b
big bunch of copyright / docstring update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1802
diff
changeset
|
4 |
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
0 | 5 |
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
1977
606923dff11b
big bunch of copyright / docstring update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1802
diff
changeset
|
6 |
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
0 | 7 |
""" |
8 |
__docformat__ = "restructuredtext en" |
|
9 |
||
10 |
import os |
|
11 |
import logging |
|
1016
26387b836099
use datetime instead of mx.DateTime
sylvain.thenault@logilab.fr
parents:
937
diff
changeset
|
12 |
from datetime import timedelta |
0 | 13 |
from os.path import (abspath, join, exists, basename, dirname, normpath, split, |
14 |
isfile, isabs) |
|
15 |
||
16 |
from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError |
|
1016
26387b836099
use datetime instead of mx.DateTime
sylvain.thenault@logilab.fr
parents:
937
diff
changeset
|
17 |
from cubicweb.utils import strptime |
0 | 18 |
from cubicweb.toolsutils import read_config |
19 |
from cubicweb.cwconfig import CubicWebConfiguration, merge_options |
|
20 |
from cubicweb.server.serverconfig import ServerConfiguration |
|
21 |
from cubicweb.etwist.twconfig import TwistedConfiguration |
|
22 |
||
23 |
# validators are used to validate (XML, DTD, whatever) view's content |
|
24 |
# validators availables are : |
|
25 |
# 'dtd' : validates XML + declared DTD |
|
26 |
# 'xml' : guarantees XML is well formed |
|
27 |
# None : do not try to validate anything |
|
28 |
VIEW_VALIDATORS = {} |
|
29 |
BASE_URL = 'http://testing.fr/cubicweb/' |
|
30 |
DEFAULT_SOURCES = {'system': {'adapter' : 'native', |
|
31 |
'db-encoding' : 'UTF-8', #'ISO-8859-1', |
|
32 |
'db-user' : u'admin', |
|
33 |
'db-password' : 'gingkow', |
|
34 |
'db-name' : 'tmpdb', |
|
35 |
'db-driver' : 'sqlite', |
|
36 |
'db-host' : None, |
|
37 |
}, |
|
38 |
'admin' : {'login': u'admin', |
|
39 |
'password': u'gingkow', |
|
40 |
}, |
|
41 |
} |
|
42 |
||
43 |
class TestServerConfiguration(ServerConfiguration): |
|
44 |
mode = 'test' |
|
45 |
set_language = False |
|
2476
1294a6bdf3bf
application -> instance where it makes sense
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1978
diff
changeset
|
46 |
read_instance_schema = False |
0 | 47 |
bootstrap_schema = False |
48 |
init_repository = True |
|
49 |
options = merge_options(ServerConfiguration.options + ( |
|
50 |
('anonymous-user', |
|
51 |
{'type' : 'string', |
|
52 |
'default': None, |
|
53 |
'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)', |
|
54 |
'group': 'main', 'inputlevel': 1, |
|
55 |
}), |
|
56 |
('anonymous-password', |
|
57 |
{'type' : 'string', |
|
58 |
'default': None, |
|
59 |
'help': 'password of the CubicWeb user account matching login', |
|
60 |
'group': 'main', 'inputlevel': 1, |
|
61 |
}), |
|
62 |
)) |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
63 |
|
0 | 64 |
if not os.environ.get('APYCOT_ROOT'): |
65 |
REGISTRY_DIR = normpath(join(CW_SOFTWARE_ROOT, '../cubes')) |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
66 |
|
0 | 67 |
def __init__(self, appid, log_threshold=logging.CRITICAL+10): |
68 |
ServerConfiguration.__init__(self, appid) |
|
69 |
self.global_set_option('log-file', None) |
|
70 |
self.init_log(log_threshold, force=True) |
|
71 |
# need this, usually triggered by cubicweb-ctl |
|
72 |
self.load_cwctl_plugins() |
|
73 |
||
74 |
anonymous_user = TwistedConfiguration.anonymous_user.im_func |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
75 |
|
0 | 76 |
@property |
77 |
def apphome(self): |
|
78 |
if exists(self.appid): |
|
79 |
return abspath(self.appid) |
|
2476
1294a6bdf3bf
application -> instance where it makes sense
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1978
diff
changeset
|
80 |
# cube test |
0 | 81 |
return abspath('..') |
82 |
appdatahome = apphome |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
83 |
|
0 | 84 |
def main_config_file(self): |
2476
1294a6bdf3bf
application -> instance where it makes sense
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1978
diff
changeset
|
85 |
"""return instance's control configuration file""" |
0 | 86 |
return join(self.apphome, '%s.conf' % self.name) |
87 |
||
88 |
def instance_md5_version(self): |
|
89 |
return '' |
|
90 |
||
91 |
def bootstrap_cubes(self): |
|
92 |
try: |
|
93 |
super(TestServerConfiguration, self).bootstrap_cubes() |
|
94 |
except IOError: |
|
95 |
# no cubes |
|
96 |
self.init_cubes( () ) |
|
97 |
||
98 |
sourcefile = None |
|
99 |
def sources_file(self): |
|
100 |
"""define in subclasses self.sourcefile if necessary""" |
|
101 |
if self.sourcefile: |
|
102 |
print 'Reading sources from', self.sourcefile |
|
103 |
sourcefile = self.sourcefile |
|
104 |
if not isabs(sourcefile): |
|
105 |
sourcefile = join(self.apphome, sourcefile) |
|
106 |
else: |
|
107 |
sourcefile = super(TestServerConfiguration, self).sources_file() |
|
108 |
return sourcefile |
|
109 |
||
110 |
def sources(self): |
|
111 |
"""By default, we run tests with the sqlite DB backend. One may use its |
|
112 |
own configuration by just creating a 'sources' file in the test |
|
113 |
directory from wich tests are launched or by specifying an alternative |
|
114 |
sources file using self.sourcefile. |
|
115 |
""" |
|
116 |
sources = super(TestServerConfiguration, self).sources() |
|
117 |
if not sources: |
|
118 |
sources = DEFAULT_SOURCES |
|
119 |
return sources |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
120 |
|
0 | 121 |
def load_defaults(self): |
122 |
super(TestServerConfiguration, self).load_defaults() |
|
123 |
# note: don't call global set option here, OptionManager may not yet be initialized |
|
124 |
# add anonymous user |
|
125 |
self.set_option('anonymous-user', 'anon') |
|
126 |
self.set_option('anonymous-password', 'anon') |
|
127 |
# uncomment the line below if you want rql queries to be logged |
|
128 |
#self.set_option('query-log-file', '/tmp/test_rql_log.' + `os.getpid()`) |
|
129 |
self.set_option('sender-name', 'cubicweb-test') |
|
130 |
self.set_option('sender-addr', 'cubicweb-test@logilab.fr') |
|
131 |
try: |
|
132 |
send_to = '%s@logilab.fr' % os.getlogin() |
|
133 |
except OSError: |
|
134 |
send_to = '%s@logilab.fr' % (os.environ.get('USER') |
|
135 |
or os.environ.get('USERNAME') |
|
136 |
or os.environ.get('LOGNAME')) |
|
137 |
self.set_option('sender-addr', send_to) |
|
138 |
self.set_option('default-dest-addrs', send_to) |
|
139 |
self.set_option('base-url', BASE_URL) |
|
140 |
||
141 |
||
142 |
class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration): |
|
143 |
repo_method = 'inmemory' |
|
144 |
options = merge_options(TestServerConfiguration.options + TwistedConfiguration.options) |
|
2657
de974465d381
[appobject] kill VObject class, move base selector classes to appobject
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
2476
diff
changeset
|
145 |
cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path |
de974465d381
[appobject] kill VObject class, move base selector classes to appobject
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
2476
diff
changeset
|
146 |
cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path |
0 | 147 |
|
148 |
def available_languages(self, *args): |
|
149 |
return ('en', 'fr', 'de') |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
150 |
|
0 | 151 |
def ext_resources_file(self): |
2476
1294a6bdf3bf
application -> instance where it makes sense
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1978
diff
changeset
|
152 |
"""return instance's external resources file""" |
0 | 153 |
return join(self.apphome, 'data', 'external_resources') |
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
154 |
|
0 | 155 |
def pyro_enabled(self): |
156 |
# but export PYRO_MULTITHREAD=0 or you get problems with sqlite and threads |
|
157 |
return True |
|
158 |
||
159 |
||
160 |
class ApptestConfiguration(BaseApptestConfiguration): |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
161 |
|
0 | 162 |
def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None): |
163 |
BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold) |
|
164 |
self.init_repository = sourcefile is None |
|
165 |
self.sourcefile = sourcefile |
|
166 |
import re |
|
167 |
self.global_set_option('embed-allowed', re.compile('.*')) |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
168 |
|
0 | 169 |
|
170 |
class RealDatabaseConfiguration(ApptestConfiguration): |
|
171 |
init_repository = False |
|
172 |
sourcesdef = {'system': {'adapter' : 'native', |
|
173 |
'db-encoding' : 'UTF-8', #'ISO-8859-1', |
|
174 |
'db-user' : u'admin', |
|
175 |
'db-password' : 'gingkow', |
|
176 |
'db-name' : 'seotest', |
|
177 |
'db-driver' : 'postgres', |
|
178 |
'db-host' : None, |
|
179 |
}, |
|
180 |
'admin' : {'login': u'admin', |
|
181 |
'password': u'gingkow', |
|
182 |
}, |
|
183 |
} |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
184 |
|
0 | 185 |
def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None): |
186 |
ApptestConfiguration.__init__(self, appid) |
|
187 |
self.init_repository = False |
|
188 |
||
189 |
||
190 |
def sources(self): |
|
191 |
""" |
|
192 |
By default, we run tests with the sqlite DB backend. |
|
193 |
One may use its own configuration by just creating a |
|
194 |
'sources' file in the test directory from wich tests are |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
195 |
launched. |
0 | 196 |
""" |
197 |
self._sources = self.sourcesdef |
|
198 |
return self._sources |
|
199 |
||
200 |
||
201 |
def buildconfig(dbuser, dbpassword, dbname, adminuser, adminpassword, dbhost=None): |
|
202 |
"""convenience function that builds a real-db configuration class""" |
|
203 |
sourcesdef = {'system': {'adapter' : 'native', |
|
204 |
'db-encoding' : 'UTF-8', #'ISO-8859-1', |
|
205 |
'db-user' : dbuser, |
|
206 |
'db-password' : dbpassword, |
|
207 |
'db-name' : dbname, |
|
208 |
'db-driver' : 'postgres', |
|
209 |
'db-host' : dbhost, |
|
210 |
}, |
|
211 |
'admin' : {'login': adminuser, |
|
212 |
'password': adminpassword, |
|
213 |
}, |
|
214 |
} |
|
215 |
return type('MyRealDBConfig', (RealDatabaseConfiguration,), |
|
216 |
{'sourcesdef': sourcesdef}) |
|
217 |
||
218 |
def loadconfig(filename): |
|
219 |
"""convenience function that builds a real-db configuration class |
|
220 |
from a file |
|
221 |
""" |
|
222 |
return type('MyRealDBConfig', (RealDatabaseConfiguration,), |
|
223 |
{'sourcesdef': read_config(filename)}) |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
224 |
|
0 | 225 |
|
226 |
class LivetestConfiguration(BaseApptestConfiguration): |
|
227 |
init_repository = False |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
228 |
|
0 | 229 |
def __init__(self, cube=None, sourcefile=None, pyro_name=None, |
230 |
log_threshold=logging.CRITICAL): |
|
231 |
TestServerConfiguration.__init__(self, cube, log_threshold=log_threshold) |
|
232 |
self.appid = pyro_name or cube |
|
233 |
# don't change this, else some symlink problems may arise in some |
|
234 |
# environment (e.g. mine (syt) ;o) |
|
235 |
# XXX I'm afraid this test will prevent to run test from a production |
|
236 |
# environment |
|
237 |
self._sources = None |
|
2476
1294a6bdf3bf
application -> instance where it makes sense
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1978
diff
changeset
|
238 |
# instance cube test |
0 | 239 |
if cube is not None: |
240 |
self.apphome = self.cube_dir(cube) |
|
241 |
elif 'web' in os.getcwd().split(os.sep): |
|
242 |
# web test |
|
243 |
self.apphome = join(normpath(join(dirname(__file__), '..')), 'web') |
|
244 |
else: |
|
2476
1294a6bdf3bf
application -> instance where it makes sense
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1978
diff
changeset
|
245 |
# cube test |
0 | 246 |
self.apphome = abspath('..') |
247 |
self.sourcefile = sourcefile |
|
248 |
self.global_set_option('realm', '') |
|
249 |
self.use_pyro = pyro_name is not None |
|
250 |
||
251 |
def pyro_enabled(self): |
|
252 |
if self.use_pyro: |
|
253 |
return True |
|
254 |
else: |
|
255 |
return False |
|
256 |
||
257 |
CubicWebConfiguration.cls_adjust_sys_path() |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
258 |
|
2732 | 259 |
def install_sqlite_patch(querier): |
0 | 260 |
"""This patch hotfixes the following sqlite bug : |
298
3e6d32667140
[doc] Fix docstring indentation.
Sandrine Ribeau <sandrine.ribeau@logilab.fr>
parents:
0
diff
changeset
|
261 |
- http://www.sqlite.org/cvstrac/tktview?tn=1327,33 |
3e6d32667140
[doc] Fix docstring indentation.
Sandrine Ribeau <sandrine.ribeau@logilab.fr>
parents:
0
diff
changeset
|
262 |
(some dates are returned as strings rather thant date objects) |
0 | 263 |
""" |
937 | 264 |
if hasattr(querier.__class__, '_devtools_sqlite_patched'): |
265 |
return # already monkey patched |
|
0 | 266 |
def wrap_execute(base_execute): |
267 |
def new_execute(*args, **kwargs): |
|
268 |
rset = base_execute(*args, **kwargs) |
|
269 |
if rset.description: |
|
270 |
found_date = False |
|
271 |
for row, rowdesc in zip(rset, rset.description): |
|
272 |
for cellindex, (value, vtype) in enumerate(zip(row, rowdesc)): |
|
273 |
if vtype in ('Date', 'Datetime') and type(value) is unicode: |
|
274 |
found_date = True |
|
1016
26387b836099
use datetime instead of mx.DateTime
sylvain.thenault@logilab.fr
parents:
937
diff
changeset
|
275 |
value = value.rsplit('.', 1)[0] |
0 | 276 |
try: |
277 |
row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S') |
|
278 |
except: |
|
279 |
row[cellindex] = strptime(value, '%Y-%m-%d') |
|
280 |
if vtype == 'Time' and type(value) is unicode: |
|
281 |
found_date = True |
|
282 |
try: |
|
283 |
row[cellindex] = strptime(value, '%H:%M:%S') |
|
284 |
except: |
|
285 |
# DateTime used as Time? |
|
286 |
row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S') |
|
287 |
if vtype == 'Interval' and type(value) is int: |
|
288 |
found_date = True |
|
1016
26387b836099
use datetime instead of mx.DateTime
sylvain.thenault@logilab.fr
parents:
937
diff
changeset
|
289 |
row[cellindex] = timedelta(0, value, 0) # XXX value is in number of seconds? |
0 | 290 |
if not found_date: |
291 |
break |
|
292 |
return rset |
|
293 |
return new_execute |
|
294 |
querier.__class__.execute = wrap_execute(querier.__class__.execute) |
|
937 | 295 |
querier.__class__._devtools_sqlite_patched = True |
0 | 296 |
|
297 |
def init_test_database(driver='sqlite', configdir='data', config=None, |
|
298 |
vreg=None): |
|
299 |
"""init a test database for a specific driver""" |
|
300 |
from cubicweb.dbapi import in_memory_cnx |
|
301 |
if vreg and not config: |
|
302 |
config = vreg.config |
|
303 |
config = config or TestServerConfiguration(configdir) |
|
304 |
source = config.sources() |
|
305 |
if driver == 'sqlite': |
|
306 |
init_test_database_sqlite(config, source) |
|
307 |
elif driver == 'postgres': |
|
308 |
init_test_database_postgres(config, source) |
|
309 |
else: |
|
310 |
raise ValueError('no initialization function for driver %r' % driver) |
|
311 |
config._cubes = None # avoid assertion error |
|
312 |
repo, cnx = in_memory_cnx(vreg or config, unicode(source['admin']['login']), |
|
313 |
source['admin']['password'] or 'xxx') |
|
314 |
if driver == 'sqlite': |
|
2732 | 315 |
install_sqlite_patch(repo.querier) |
0 | 316 |
return repo, cnx |
317 |
||
318 |
def init_test_database_postgres(config, source, vreg=None): |
|
319 |
"""initialize a fresh sqlite databse used for testing purpose""" |
|
320 |
if config.init_repository: |
|
321 |
from cubicweb.server import init_repository |
|
322 |
init_repository(config, interactive=False, drop=True, vreg=vreg) |
|
323 |
||
324 |
def cleanup_sqlite(dbfile, removecube=False): |
|
325 |
try: |
|
326 |
os.remove(dbfile) |
|
327 |
os.remove('%s-journal' % dbfile) |
|
328 |
except OSError: |
|
329 |
pass |
|
330 |
if removecube: |
|
331 |
try: |
|
1978
7df9c566aa3d
fix name of test database template (bad sed during template->cube migration)
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1977
diff
changeset
|
332 |
os.remove('%s-template' % dbfile) |
0 | 333 |
except OSError: |
334 |
pass |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
1016
diff
changeset
|
335 |
|
0 | 336 |
def init_test_database_sqlite(config, source, vreg=None): |
337 |
"""initialize a fresh sqlite databse used for testing purpose""" |
|
338 |
import shutil |
|
339 |
# remove database file if it exists (actually I know driver == 'sqlite' :) |
|
340 |
dbfile = source['system']['db-name'] |
|
341 |
cleanup_sqlite(dbfile) |
|
1978
7df9c566aa3d
fix name of test database template (bad sed during template->cube migration)
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1977
diff
changeset
|
342 |
template = '%s-template' % dbfile |
7df9c566aa3d
fix name of test database template (bad sed during template->cube migration)
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1977
diff
changeset
|
343 |
if exists(template): |
7df9c566aa3d
fix name of test database template (bad sed during template->cube migration)
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1977
diff
changeset
|
344 |
shutil.copy(template, dbfile) |
0 | 345 |
else: |
346 |
# initialize the database |
|
347 |
from cubicweb.server import init_repository |
|
348 |
init_repository(config, interactive=False, vreg=vreg) |
|
1978
7df9c566aa3d
fix name of test database template (bad sed during template->cube migration)
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
1977
diff
changeset
|
349 |
shutil.copy(dbfile, template) |