Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion runbot/controllers/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ def access_running(self, build_id, db_suffix=None, **kwargs):

@route(['/runbot/parse_log/<model("ir.logging"):ir_log>'], type='http', auth='user', sitemap=False)
def parse_log(self, ir_log, **kwargs):
request.env['runbot.build.error']._parse_logs(ir_log)
request.env['runbot.build.error']._parse_logs(ir_log, update_tags=True)
return werkzeug.utils.redirect('/runbot/build/%s' % ir_log.build_id.id)

@route(['/runbot/bundle/<int:bundle_id>/triggers/<string:action>'], type='http', auth='user', sitemap=False)
Expand Down
2 changes: 1 addition & 1 deletion runbot/data/build_parse.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<field name="type">ir.actions.server</field>
<field name="state">code</field>
<field name="code">
action = records._parse_logs()
action = records._parse_logs(update_tags=True)
</field>
</record>
</odoo>
4 changes: 2 additions & 2 deletions runbot/models/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1478,12 +1478,12 @@ def _get_py_version(self):
return '3'
return ''

def _parse_logs(self):
def _parse_logs(self, update_tags=False):
""" Parse build logs to classify errors """
# only parse logs from builds in error and not already scanned
builds_to_scan = self.filtered(lambda b: b.local_result in ('ko', 'killed', 'warn') and not b.build_error_link_ids)
ir_logs = builds_to_scan.log_ids.filtered(lambda l: l.level in ('ERROR', 'WARNING', 'CRITICAL'))
return self.env['runbot.build.error']._parse_logs(ir_logs)
return self.env['runbot.build.error']._parse_logs(ir_logs, update_tags=update_tags)

def _is_file(self, file, mode='r'):
file_path = self._path(file)
Expand Down
21 changes: 20 additions & 1 deletion runbot/models/build_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,17 @@ def _onchange_test_tags(self):
self.tags_min_version_id = min(self.version_ids, key=lambda rec: rec.number)
self.tags_max_version_id = max(self.version_ids, key=lambda rec: rec.number)

def _update_version_tags(self):
for error in self:
if not (error.test_tags and error.version_ids):
continue
new_min = min(error.version_ids, key=lambda rec: rec.number)
new_max = max(error.version_ids, key=lambda rec: rec.number)
if not error.tags_min_version_id or new_min.number < error.tags_min_version_id.number:
error.tags_min_version_id = new_min
if not error.tags_max_version_id or new_max.number > error.tags_max_version_id.number:
error.tags_max_version_id = new_max

@api.onchange('customer')
def _onchange_customer(self):
if not self.responsible:
Expand Down Expand Up @@ -789,7 +800,7 @@ def action_copy_canonical_tag(self):
record._onchange_test_tags()

@api.model
def _parse_logs(self, ir_logs):
def _parse_logs(self, ir_logs, update_tags=False):
if not ir_logs:
return None
regexes = self.env['runbot.error.regex'].search([])
Expand Down Expand Up @@ -845,6 +856,14 @@ def _parse_logs(self, ir_logs):
'log_date': rec.create_date,
})

if update_tags:
errors_to_update = self.env['runbot.build.error']
for build_error_content in build_error_contents:
if build_error_content.error_id and build_error_content.error_id.test_tags:
errors_to_update |= build_error_content.error_id
for error in errors_to_update:
error._update_version_tags()

if build_error_contents:
window_action = {
"type": "ir.actions.act_window",
Expand Down
114 changes: 114 additions & 0 deletions runbot/tests/test_build_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,120 @@ def test_duplicate_breaking_pr(self):
error_a.breaking_pr_id = False
self.assertEqual(error_a.duplicate_breaking_pr_count, 0)

def test_onchange_test_tags(self):
error = self.BuildError.create({})
error_content = self.BuildErrorContent.create({'content': 'very bad trip', 'error_id': error.id})
build_13 = self.create_test_build({'local_result': 'ko'})
self.BuildErrorLink.create({
'build_id': build_13.id,
'error_content_id': error_content.id,
})
self.assertEqual(error.tags_min_version_id.id, False)
self.assertEqual(error.tags_max_version_id.id, False)

error.test_tags = '/account'
error._onchange_test_tags()
self.assertEqual(error.tags_min_version_id, self.version_13)
self.assertEqual(error.tags_max_version_id, self.version_13)

version_14 = self.Version._get('14.0')
params_14 = self.create_params({'version_id': version_14.id})
build_14 = self.create_test_build({'local_result': 'ko', 'params_id': params_14.id})
self.BuildErrorLink.create({
'build_id': build_14.id,
'error_content_id': error_content.id,
})
error._onchange_test_tags()
self.assertEqual(error.tags_min_version_id, self.version_13)
self.assertEqual(error.tags_max_version_id, version_14)

def test_parse_logs_updates_version_bounds(self):
build_13 = self.create_test_build({'local_result': 'ko', 'local_state': 'done'})
log_data = {
'name': 'test-parse-logs',
'type': 'server',
'path': 'runbot',
'level': 'ERROR',
'func': 'test_trip',
'line': 242,
'message': 'Error Very Bad Trip',
'build_id': build_13.id,
}
log_13 = self.IrLog.create(log_data)

action = self.BuildError._parse_logs(log_13, update_tags=True)
self.assertEqual(action.get('type'), 'ir.actions.act_window')
error_content = self.BuildErrorContent.browse(action.get('res_id'))
error = error_content.error_id
error.test_tags = '/account'
error._update_version_tags()
self.assertEqual(error.tags_min_version_id, self.version_13)
self.assertEqual(error.tags_max_version_id, self.version_13)

# Scanning a newer build extends the max without updating the min
version_14 = self.Version._get('14.0')
params_14 = self.create_params({'version_id': version_14.id})
build_14 = self.create_test_build({'local_result': 'ko', 'local_state': 'done', 'params_id': params_14.id})
log_data.update({'build_id': build_14.id})
log_14 = self.IrLog.create(log_data)
self.BuildError._parse_logs(log_14, update_tags=True)
self.assertIn(build_14, error.build_ids)
self.assertEqual(error.tags_min_version_id, self.version_13)
self.assertEqual(error.tags_max_version_id, version_14)

def test_parse_logs_no_update_without_flag(self):
build_13 = self.create_test_build({'local_result': 'ko', 'local_state': 'done'})
log_13 = self.IrLog.create({
'name': 'test-parse-logs',
'type': 'server',
'path': 'runbot',
'level': 'ERROR',
'func': 'test_trip',
'line': 242,
'message': 'Error Very Bad Trip',
'build_id': build_13.id,
})
action = self.BuildError._parse_logs(log_13)
self.assertEqual(action.get('type'), 'ir.actions.act_window')
error_content = self.BuildErrorContent.browse(action.get('res_id'))
error = error_content.error_id
error.test_tags = '/discuss'
self.assertFalse(error.tags_min_version_id)
self.assertFalse(error.tags_max_version_id)

def test_update_version_tags_no_update_inside_bounds(self):
version_14 = self.Version._get('14.0')
version_16 = self.Version._get('16.0')
params_14 = self.create_params({'version_id': version_14.id})
params_16 = self.create_params({'version_id': version_16.id})
build_14 = self.create_test_build({'local_result': 'ko', 'params_id': params_14.id})
build_16 = self.create_test_build({'local_result': 'ko', 'params_id': params_16.id})

error = self.BuildError.create({})
error_content = self.BuildErrorContent.create({'content': 'inside bounds test', 'error_id': error.id})
link_14 = self.BuildErrorLink.create({'build_id': build_14.id, 'error_content_id': error_content.id})
link_16 = self.BuildErrorLink.create({'build_id': build_16.id, 'error_content_id': error_content.id})

error.test_tags = '/account'
error._update_version_tags()
self.assertEqual(error.tags_min_version_id, version_14)
self.assertEqual(error.tags_max_version_id, version_16)

link_14.unlink()
self.assertNotIn(version_14, error.version_ids)
# There is only version 16.0 on the error now but if we update the tags, it should leave the min at 14.0
error._update_version_tags()
self.assertEqual(error.tags_min_version_id, version_14)
self.assertEqual(error.tags_max_version_id, version_16)

link_14 = self.BuildErrorLink.create({'build_id': build_14.id, 'error_content_id': error_content.id})
link_16.unlink()
self.assertNotIn(version_16, error.version_ids)
# Now there is only version 14.0 on the error but if we update the tags, it should leave the max at 16.0
error._update_version_tags()
self.assertEqual(error.tags_min_version_id, version_14)
self.assertEqual(error.tags_max_version_id, version_16)

def test_relink_contents(self):
build_a = self.create_test_build({'local_result': 'ko', 'local_state': 'done'})
error_content_a = self.BuildErrorContent.create({'content': 'foo bar'})
Expand Down