Projet

Général

Profil

0001-workflows-only-log-condition-errors-as-functional-er.patch

Thomas Noël, 29 juin 2018 15:02

Télécharger (12,7 ko)

Voir les différences:

Subject: [PATCH] workflows: only log condition errors as functional errors
 (#24472)

 tests/test_backoffice_pages.py |  9 +++---
 tests/test_form_pages.py       |  3 +-
 tests/test_workflows.py        | 54 +++++++++++++++++++++++++++++++++-
 wcs/admin/logged_errors.py     |  8 +++++
 wcs/conditions.py              | 11 +++++--
 wcs/logged_errors.py           | 21 ++++++++++---
 wcs/wf/jump.py                 |  3 +-
 wcs/workflows.py               |  3 +-
 8 files changed, 98 insertions(+), 14 deletions(-)
tests/test_backoffice_pages.py
3907 3907

  
3908 3908
    app = login(get_app(pub))
3909 3909
    resp = app.get('/backoffice/forms/%s/' % formdef.id)
3910
    assert 'ZeroDivisionError' in resp.body
3910
    assert 'Failed to evaluate condition (error ZeroDivisionError)' in resp.body
3911 3911
    resp = resp.click('1 error')
3912 3912

  
3913 3913
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
3914 3914
    resp2 = resp.click('1 error')
3915
    assert 'ZeroDivisionError' in resp2.body
3915
    assert 'Failed to evaluate condition (error ZeroDivisionError)' in resp2.body
3916 3916
    resp = resp2.click('ZeroDivisionError')
3917
    assert 'integer division or modulo by zero' in resp.body
3917
    assert 'ZeroDivisionError: integer division or modulo by zero' in resp.body
3918
    assert 'Expression (type python): <pre>1/0</pre>' in resp.body
3918 3919
    assert not 'Acked' in resp.body
3919 3920
    resp = resp.click('Ack').follow()
3920 3921
    assert 'Acked' in resp.body
......
3933 3934

  
3934 3935
    app = login(get_app(pub))
3935 3936
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
3936
    assert 'ZeroDivisionError' in resp.body
3937
    assert 'Failed to evaluate condition (error ZeroDivisionError)' in resp2.body
3937 3938
    resp2 = resp.click('1 error')
3938 3939
    resp = resp2.click('ZeroDivisionError')
3939 3940
    assert 'href="http://example.net/backoffice/management/test/' in resp.body
tests/test_form_pages.py
4492 4492
    assert LoggedError.count() == 1
4493 4493

  
4494 4494
    error = LoggedError.get_on_index(
4495
            '34-12-None-None-zerodivisionerror-integer-division-or-modulo-by-zero', 'tech_id')
4495
        '34-12-just_submitted-_jump-failed-to-evaluate-condition-error-zerodivisionerror',
4496
        'tech_id')
4496 4497
    assert error.occurences_count == 2
4497 4498

  
4498 4499
    assert len(LoggedError.get_ids_with_indexed_value('formdef_id', '34')) == 1
tests/test_workflows.py
19 19
from wcs import sessions
20 20
from wcs.fields import (StringField, DateField, MapField, FileField, ItemField,
21 21
        ItemsField, CommentField)
22
from wcs.logged_errors import LoggedError
22 23
from wcs.roles import Role
23 24
from wcs.workflows import (Workflow, WorkflowStatusItem,
24 25
        SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem,
......
258 259
            'value': 'utils.time_delta(utils.time.localtime(), "2015-01-04").days > 0'}
259 260
    assert item.must_jump(formdata) is True
260 261

  
261

  
262 262
def test_jump_count_condition(pub):
263 263
    FormDef.wipe()
264 264
    formdef = FormDef()
......
278 278
    item.condition = {'type': 'python', 'value': 'form_objects.count < 2'}
279 279
    assert item.must_jump(formdata) is False
280 280

  
281
def test_jump_bad_python_condition(pub):
282
    FormDef.wipe()
283
    formdef = FormDef()
284
    formdef.name = 'foobar'
285
    formdef.store()
286
    pub.substitutions.feed(formdef)
287
    formdef.data_class().wipe()
288
    formdata = formdef.data_class()()
289
    item = JumpWorkflowStatusItem()
290

  
291
    LoggedError.wipe()
292
    item.condition = {'type': 'python', 'value': 'form_var_foobar == 0'}
293
    assert item.must_jump(formdata) is False
294
    assert LoggedError.count() == 1
295
    logged_error = LoggedError.select()[0]
296
    assert logged_error.summary == 'Failed to evaluate condition (error NameError)'
297
    assert logged_error.error_message == "NameError: name 'form_var_foobar' is not defined"
298
    assert logged_error.expression == 'form_var_foobar == 0'
299
    assert logged_error.expression_type == 'python'
300

  
301
    LoggedError.wipe()
302
    item.condition = {'type': 'python', 'value': '~ invalid ~'}
303
    assert item.must_jump(formdata) is False
304
    assert LoggedError.count() == 1
305
    logged_error = LoggedError.select()[0]
306
    assert logged_error.summary == 'Failed to evaluate condition (error SyntaxError)'
307
    assert logged_error.error_message == "SyntaxError: unexpected EOF while parsing (<string>, line 1)"
308
    assert logged_error.expression == '~ invalid ~'
309
    assert logged_error.expression_type == 'python'
310

  
281 311
def test_jump_django_conditions(pub):
282 312
    FormDef.wipe()
283 313
    formdef = FormDef()
......
289 319
    pub.substitutions.feed(formdata)
290 320
    item = JumpWorkflowStatusItem()
291 321

  
322
    LoggedError.wipe()
292 323
    item.condition = {'type': 'django', 'value': '1 < 2'}
293 324
    assert item.must_jump(formdata) is True
294 325

  
......
301 332
    item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "X"'}
302 333
    assert item.must_jump(formdata) is False
303 334

  
335
    assert LoggedError.count() == 0
336

  
304 337
    item.condition = {'type': 'django', 'value': '~ invalid ~'}
305 338
    assert item.must_jump(formdata) is False
339
    assert LoggedError.count() == 1
340
    logged_error = LoggedError.select()[0]
341
    assert logged_error.summary == 'Failed to evaluate condition (error TemplateSyntaxError)'
342
    assert logged_error.error_message == "TemplateSyntaxError: Could not parse the remainder: '~' from '~'"
343
    assert logged_error.expression == '~ invalid ~'
344
    assert logged_error.expression_type == 'django'
306 345

  
307 346
def test_check_auth(pub):
308 347
    user = pub.user_class(name='foo')
......
3504 3543
    choice.condition = {'type': 'python', 'value': 'form_name == "baz"'}
3505 3544
    workflow.store()
3506 3545
    assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 2
3546

  
3547
    # bad condition
3548
    LoggedError.wipe()
3549
    choice.condition = {'type': 'python', 'value': 'foobar == barfoo'}
3550
    workflow.store()
3551
    assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 0
3552
    assert LoggedError.count() == 1
3553
    logged_error = LoggedError.select()[0]
3554
    assert logged_error.occurences_count > 1  # should be 2... == 12 with pickle, 4 with sql
3555
    assert logged_error.summary == 'Failed to evaluate condition (error NameError)'
3556
    assert logged_error.error_message == "NameError: name 'foobar' is not defined"
3557
    assert logged_error.expression == 'foobar == barfoo'
3558
    assert logged_error.expression_type == 'python'
wcs/admin/logged_errors.py
70 70
                            _(status_item.description))
71 71
                r += htmltext('</ul>')
72 72

  
73
        if self.error.expression or self.error.expression_type:
74
            r += htmltext('  <li>%s <pre>%s</pre></li>') % (
75
                    _('Expression (type %s):') % self.error.expression_type,
76
                    self.error.expression)
77

  
78
        if self.error.error_message:
79
            r += htmltext('  <li>%s %s</li>') % (_('Error message:'), self.error.error_message)
80

  
73 81
        if formdef:
74 82
            formdata = self.error.get_formdata()
75 83
            if formdata:
wcs/conditions.py
30 30
    record_errors = True
31 31
    log_errors = False
32 32

  
33
    def __init__(self, condition, context=None):
33
    def __init__(self, condition, context={}):
34 34
        if not condition:
35 35
            condition = {}
36 36
        self.type = condition.get('type')
......
53 53
            if self.log_errors:
54 54
                get_logger().warn('failed to evaluate %r (%r)', self, e)
55 55
            if self.record_errors:
56
                get_publisher().notify_of_exception(sys.exc_info())
56
                from wcs.logged_errors import LoggedError
57
                summary = _('Failed to evaluate condition (error %s)' % e.__class__.__name__)
58
                LoggedError.record(summary,
59
                                   formdata=self.context.get('formdata'),
60
                                   status_item=self.context.get('status_item'),
61
                                   expression=self.value,
62
                                   expression_type=self.type,
63
                                   error_message='%s: %s' % (e.__class__.__name__, e))
57 64
            raise RuntimeError()
58 65

  
59 66
    def evaluate_python(self, local_variables):
wcs/logged_errors.py
33 33
    workflow_id = None
34 34
    status_id = None
35 35
    status_item_id = None
36
    expression = None
37
    expression_type = None
38
    error_message = None
36 39
    traceback = None
37 40
    occurences_count = 0
38 41
    first_occurence_timestamp = None
......
42 45
    # declarations for serialization
43 46
    XML_NODES = [
44 47
            ('summary', 'str'), ('traceback', 'str'),
48
            ('expression', 'str'), ('expression_type', 'str'), ('error_message', 'str'),
45 49
            ('formdata_id', 'str'), ('formdef_id', 'str'), ('workflow_id', 'str'),
46 50
            ('status_id', 'str'), ('status_item_id', 'str'),
47 51
            ('occurences_count', 'int'),
......
51 55

  
52 56
    @classmethod
53 57
    def record(cls, error_summary, plain_error_msg=None, formdata=None,
54
               formdef=None, workflow=None, status=None, status_item=None):
58
               formdef=None, workflow=None, status=None, status_item=None,
59
               expression=None, expression_type=None, error_message=None):
55 60
        error = cls()
56 61
        error.summary = error_summary
57 62
        error.traceback = plain_error_msg
63
        error.expression = expression
64
        error.expression_type = expression_type
65
        error.error_message = error_message
58 66

  
59 67
        if formdata:
60 68
            error.formdata_id = str(formdata.id)
......
74 82

  
75 83
        if status_item:
76 84
            error.status_item_id = status_item.id
77
            if status_item.parent:
85
            if getattr(status_item, 'parent', None):
78 86
                error.status_id = status_item.parent.id
79 87
        if status:
80 88
            error.status_id = status.id
......
110 118

  
111 119
    @property
112 120
    def tech_id(self):
113
        return ('%s-%s-%s-%s-%s' % (self.formdef_id, self.workflow_id, self.status_id,
114
                                    self.status_item_id, simplify(self.summary)))[:200]
121
        tech_id = '%s-%s-' % (self.formdef_id, self.workflow_id)
122
        if self.status_id:
123
            tech_id += '%s-' % self.status_id
124
        if self.status_item_id:
125
            tech_id += '%s-' % self.status_item_id
126
        tech_id += '%s' % simplify(self.summary)
127
        return tech_id[:200]
115 128

  
116 129
    def get_formdef(self):
117 130
        return FormDef.get(self.formdef_id, ignore_errors=True)
wcs/wf/jump.py
218 218
        must_jump = True
219 219

  
220 220
        if self.condition:
221
            context = {'formdata': formdata, 'status_item': self}
221 222
            try:
222
                must_jump = Condition(self.condition).evaluate()
223
                must_jump = Condition(self.condition, context).evaluate()
223 224
            except RuntimeError:
224 225
                must_jump = False
225 226

  
wcs/workflows.py
1583 1583
        return False
1584 1584

  
1585 1585
    def check_condition(self, formdata):
1586
        context = {'formdata': formdata, 'status_item': self}
1586 1587
        try:
1587
            return Condition(self.condition).evaluate()
1588
            return Condition(self.condition, context).evaluate()
1588 1589
        except RuntimeError:
1589 1590
            return False
1590 1591

  
1591
-