test_reports: first draft of a full action/wizard/report tester
Use with a yaml test like: - I will test the action reports - !python {model: res.partner}: | from tools import test_reports reload(test_reports) # ;) test_reports.try_report_action(cr, uid, 'action_account_balance_menu', our_module='account') bzr revid: p_christ@hol.gr-20101219200608-ffl2k3qiexjxvp48
This commit is contained in:
parent
e81905c7bc
commit
5918f08733
|
@ -28,16 +28,21 @@
|
|||
import netsvc
|
||||
import tools
|
||||
import logging
|
||||
import pooler
|
||||
from tools.safe_eval import safe_eval
|
||||
from subprocess import Popen, PIPE
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
def try_report(cr, uid, rname, ids, data=None, context=None):
|
||||
def try_report(cr, uid, rname, ids, data=None, context=None, our_module=None):
|
||||
""" Try to render a report <rname> with contents of ids
|
||||
|
||||
This function should also check for common pitfalls of reports.
|
||||
"""
|
||||
log = logging.getLogger('tools.test_reports')
|
||||
if our_module:
|
||||
log = logging.getLogger('tests.%s' % our_module)
|
||||
else:
|
||||
log = logging.getLogger('tools.test_reports')
|
||||
if data is None:
|
||||
data = {}
|
||||
if context is None:
|
||||
|
@ -46,7 +51,7 @@ def try_report(cr, uid, rname, ids, data=None, context=None):
|
|||
rname_s = rname[7:]
|
||||
else:
|
||||
rname_s = rname
|
||||
log.debug("Trying %s.create(%r)", rname, ids)
|
||||
log.log(netsvc.logging.TEST, " - Trying %s.create(%r)", rname, ids)
|
||||
res = netsvc.LocalService(rname).create(cr, uid, ids, data, context)
|
||||
if not isinstance(res, tuple):
|
||||
raise RuntimeError("Result of %s.create() should be a (data,format) tuple, now it is a %s" % \
|
||||
|
@ -86,6 +91,238 @@ def try_report(cr, uid, rname, ids, data=None, context=None):
|
|||
pass
|
||||
else:
|
||||
log.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format)
|
||||
return False
|
||||
|
||||
log.log(netsvc.logging.TEST, "Report %s produced correctly.", rname)
|
||||
return True
|
||||
|
||||
def try_report_action(cr, uid, action_id, res_model=None, res_ids=None,
|
||||
wiz_data=None, wiz_buttons=None,
|
||||
context=None, our_module=None):
|
||||
"""Take an ir.action.act_window and follow it until a report is produced
|
||||
|
||||
@param action_id the integer id of an action, or a reference to xml id
|
||||
of the act_window (can search [our_module.]+xml_id
|
||||
@param res_model, res_ids call the action as if it had been launched
|
||||
from that model+ids (tree/form view action)
|
||||
@param wiz_data a dictionary of values to use in the wizard, if needed.
|
||||
They will override (or complete) the default values of the
|
||||
wizard form.
|
||||
@param wiz_buttons a list of button names, or button icon strings, which
|
||||
should be preferred to press during the wizard.
|
||||
Eg. 'OK' or 'gtk-print'
|
||||
@param our_module the name of the calling module (string), like 'account'
|
||||
"""
|
||||
|
||||
if not our_module and isinstance(action_id, basestring):
|
||||
if '.' in action_id:
|
||||
our_module = action_id.split('.', 1)[0]
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
else:
|
||||
context = context.copy() # keep it local
|
||||
# TODO context fill-up
|
||||
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
if our_module:
|
||||
log = logging.getLogger('tests.%s' % our_module)
|
||||
else:
|
||||
log = logging.getLogger('tools.test_reports')
|
||||
|
||||
def log_test(msg, *args):
|
||||
log.log(netsvc.logging.TEST, " - " + msg, *args)
|
||||
|
||||
datas = {}
|
||||
if res_model:
|
||||
datas['model'] = res_model
|
||||
if res_ids:
|
||||
datas['ids'] = res_ids
|
||||
|
||||
if not wiz_buttons:
|
||||
wiz_buttons = []
|
||||
|
||||
if isinstance(action_id, basestring):
|
||||
if '.' in action_id:
|
||||
act_module, act_xmlid = action_id.split('.', 1)
|
||||
else:
|
||||
if not our_module:
|
||||
raise ValueError('You cannot only specify action_id "%s" without a module name' % action_id)
|
||||
act_module = our_module
|
||||
act_xmlid = action_id
|
||||
act_model, act_id = pool.get('ir.model.data').get_object_reference(cr, uid, act_module, act_xmlid)
|
||||
else:
|
||||
assert isinstance(action_id, (long, int))
|
||||
act_model = 'ir.action.act_window' # assume that
|
||||
act_id = action_id
|
||||
act_xmlid = '<%s>' % act_id
|
||||
|
||||
def _exec_action(action, datas, context):
|
||||
# taken from client/modules/action/main.py:84 _exec_action()
|
||||
if isinstance(action, bool) or 'type' not in action:
|
||||
return
|
||||
# Updating the context : Adding the context of action in order to use it on Views called from buttons
|
||||
if datas.get('id',False):
|
||||
context.update( {'active_id': datas.get('id',False), 'active_ids': datas.get('ids',[]), 'active_model': datas.get('model',False)})
|
||||
context.update(safe_eval(action.get('context','{}'), context.copy()))
|
||||
if action['type'] in ['ir.actions.act_window', 'ir.actions.submenu']:
|
||||
for key in ('res_id', 'res_model', 'view_type', 'view_mode',
|
||||
'limit', 'auto_refresh', 'search_view', 'auto_search', 'search_view_id'):
|
||||
datas[key] = action.get(key, datas.get(key, None))
|
||||
|
||||
view_id = False
|
||||
if action.get('views', []):
|
||||
if isinstance(action['views'],list):
|
||||
view_id = action['views'][0][0]
|
||||
datas['view_mode']= action['views'][0][1]
|
||||
else:
|
||||
if action.get('view_id', False):
|
||||
view_id = action['view_id'][0]
|
||||
elif action.get('view_id', False):
|
||||
view_id = action['view_id'][0]
|
||||
|
||||
assert datas['res_model'], "Cannot use the view without a model"
|
||||
# Here, we have a view that we need to emulate
|
||||
log_test("will emulate a %s view: %s#%s",
|
||||
action['view_type'], datas['res_model'], view_id or '?')
|
||||
|
||||
view_res = pool.get(datas['res_model']).fields_view_get(cr, uid, view_id, action['view_type'], context)
|
||||
assert view_res and view_res.get('arch'), "Did not return any arch for the view"
|
||||
view_data = {}
|
||||
if view_res.get('fields',{}).keys():
|
||||
view_data = pool.get(datas['res_model']).default_get(cr, uid, view_res['fields'].keys(), context)
|
||||
if datas.get('form'):
|
||||
view_data.update(datas.get('form'))
|
||||
if wiz_data:
|
||||
view_data.update(wiz_data)
|
||||
log.debug("View data is: %r", view_data)
|
||||
|
||||
action_name = action.get('name')
|
||||
try:
|
||||
from xml.dom import minidom
|
||||
cancel_found = False
|
||||
buttons = []
|
||||
dom_doc = minidom.parseString(view_res['arch'])
|
||||
if not action_name:
|
||||
action_name = dom_doc.documentElement.getAttribute('name')
|
||||
|
||||
for button in dom_doc.getElementsByTagName('button'):
|
||||
button_weight = 0
|
||||
if button.getAttribute('special') == 'cancel':
|
||||
cancel_found = True
|
||||
continue
|
||||
if button.getAttribute('icon') == 'gtk-cancel':
|
||||
cancel_found = True
|
||||
continue
|
||||
if button.getAttribute('default_focus') == '1':
|
||||
button_weight += 20
|
||||
if button.getAttribute('string') in wiz_buttons:
|
||||
button_weight += 30
|
||||
elif button.getAttribute('icon') in wiz_buttons:
|
||||
button_weight += 10
|
||||
string = button.getAttribute('string') or '?%s' % len(buttons)
|
||||
|
||||
buttons.append( { 'name': button.getAttribute('name'),
|
||||
'string': string,
|
||||
'type': button.getAttribute('type'),
|
||||
'weight': button_weight,
|
||||
})
|
||||
except Exception, e:
|
||||
log.warning("Cannot resolve the view arch and locate the buttons!", exc_info=True)
|
||||
raise AssertionError(e.args[0])
|
||||
|
||||
if not datas['res_id']:
|
||||
# it is probably an orm_memory object, we need to create
|
||||
# an instance
|
||||
datas['res_id'] = pool.get(datas['res_model']).create(cr, uid, view_data, context)
|
||||
|
||||
if not buttons:
|
||||
raise AssertionError("view form doesn't have any buttons to press!")
|
||||
|
||||
buttons.sort(key=lambda b: b['weight'])
|
||||
log.debug('Buttons are: %s', ', '.join([ '%s: %d' % (b['string'], b['weight']) for b in buttons]))
|
||||
|
||||
res = None
|
||||
while buttons and not res:
|
||||
b = buttons.pop()
|
||||
log_test("in the %s form, I will press the %s button.", action_name, b['string'])
|
||||
if not b['type']:
|
||||
log_test("the %s button has no type, cannot use it", b['string'])
|
||||
continue
|
||||
if b['type'] == 'object':
|
||||
#there we are! press the button!
|
||||
fn = getattr(pool.get(datas['res_model']), b['name'])
|
||||
if not fn:
|
||||
log.error("The %s model doesn't have a %s attribute!", datas['res_model'], b['name'])
|
||||
continue
|
||||
res = fn(cr, uid, [datas['res_id'],], context)
|
||||
break
|
||||
else:
|
||||
log.warning("in the %s form, the %s button has unknown type %s",
|
||||
action_name, b['string'], b['type'])
|
||||
return res
|
||||
#elif action['type']=='ir.actions.server':
|
||||
#res = rpc.session.rpc_exec_auth('/object', 'execute', 'ir.actions.server', 'run', [action['id']], context)
|
||||
#if res:
|
||||
#if not isinstance(res, list):
|
||||
#res = [res]
|
||||
#for r in res:
|
||||
#self._exec_action(r, datas, context)
|
||||
|
||||
#elif action['type']=='ir.actions.wizard':
|
||||
#win=None
|
||||
#if 'window' in datas:
|
||||
#win=datas['window']
|
||||
#del datas['window']
|
||||
#wizard.execute(action['wiz_name'], datas, parent=win, context=context)
|
||||
|
||||
#elif action['type']=='ir.actions.report.custom':
|
||||
#if 'window' in datas:
|
||||
#win=datas['window']
|
||||
#del datas['window']
|
||||
#datas['report_id'] = action['report_id']
|
||||
#self.exec_report('custom', datas, context)
|
||||
|
||||
#elif action['type']=='ir.actions.report.int':
|
||||
#if 'window' in datas:
|
||||
#win=datas['window']
|
||||
#del datas['window']
|
||||
#self.exec_report(action['report_name'], datas)
|
||||
|
||||
elif action['type']=='ir.actions.report.xml':
|
||||
if 'window' in datas:
|
||||
del datas['window']
|
||||
if not datas:
|
||||
datas = action.get('datas',{})
|
||||
datas = datas.copy()
|
||||
ids = datas.get('ids')
|
||||
if 'ids' in datas:
|
||||
del datas['ids']
|
||||
res = try_report(cr, uid, 'report.'+action['report_name'], ids, datas, context, our_module=our_module)
|
||||
return res
|
||||
else:
|
||||
raise Exception("Cannot handle action of type %s" % act_model)
|
||||
|
||||
log_test("will be using %s action %s #%d", act_model, act_xmlid, act_id)
|
||||
action = pool.get(act_model).read(cr, uid, act_id, context=context)
|
||||
assert action, "Could not read action %s[%s]" %(act_model, act_id)
|
||||
loop = 0
|
||||
while action:
|
||||
loop += 1
|
||||
# This part tries to emulate the loop of the Gtk client
|
||||
if loop > 100:
|
||||
log.error("Passed %d loops, giving up", loop)
|
||||
raise Exception("Too many loops at action")
|
||||
log_test("it is an %s action at loop #%d", action.get('type', 'unknown'), loop)
|
||||
result = _exec_action(action, datas, context)
|
||||
if not isinstance(result, dict):
|
||||
break
|
||||
datas = result.get('datas', {})
|
||||
if datas:
|
||||
del result['datas']
|
||||
action = result
|
||||
|
||||
return True
|
||||
|
||||
|
||||
#eof
|
||||
|
|
Loading…
Reference in New Issue