[IMP] runbot: create a new build error when a fixed one reappear

When a build error appears with the same fingerprint as already known
one which was supposedly fixed, the build is simply added to the known
build error.

In order to keep an eye on such reappearing bugs and keep the fixing
history separated, this commit simply creates a new build_error.
Old build errors with the same hash (or child_ids 's hashes) appears in
a computed field error_history_ids.
This commit is contained in:
Christophe Monniez 2019-09-14 10:42:10 +02:00 committed by XavierDo
parent 54f0488b26
commit b7df8566e4
4 changed files with 121 additions and 2 deletions

View File

@ -33,6 +33,7 @@ class RunbotBuildError(models.Model):
parent_id = fields.Many2one('runbot.build.error', 'Linked to')
child_ids = fields.One2many('runbot.build.error', 'parent_id', string='Child Errors')
children_build_ids = fields.Many2many('runbot.build', compute='_compute_children_build_ids', string='Children builds')
error_history_ids = fields.One2many('runbot.build.error', compute='_compute_error_history_ids', string='Old errors')
@api.model
def create(self, vals):
@ -69,6 +70,16 @@ class RunbotBuildError(models.Model):
for build_error in self:
build_error.children_build_ids = build_error.mapped('child_ids.build_ids')
@api.depends('fingerprint')
def _compute_error_history_ids(self):
for error in self:
fingerprints = [error.fingerprint] + [rec.fingerprint for rec in error.child_ids]
error.error_history_ids = self.search([('fingerprint', 'in', fingerprints), ('active', '=', False), ('id', '!=', error.id)])
@api.onchange('active')
def _onchange_active(self):
self.child_ids.write({'active': self.active})
@api.model
def _digest(self, s):
"""
@ -91,14 +102,15 @@ class RunbotBuildError(models.Model):
hash_dict[fingerprint].append(log)
# add build ids to already detected errors
for build_error in self.env['runbot.build.error'].search([('fingerprint', 'in', list(hash_dict.keys()))]):
for build_error in self.env['runbot.build.error'].search([('fingerprint', 'in', list(hash_dict.keys())), ('active', '=', True)]):
for build in {rec.build_id for rec in hash_dict[build_error.fingerprint]}:
build.build_error_ids += build_error
del hash_dict[build_error.fingerprint]
fixed_errors_dict = {rec.fingerprint: rec for rec in self.env['runbot.build.error'].search([('fingerprint', 'in', list(hash_dict.keys())), ('active', '=', False)])}
# create an error for the remaining entries
for fingerprint, logs in hash_dict.items():
self.env['runbot.build.error'].create({
build_error = self.env['runbot.build.error'].create({
'content': logs[0].message,
'module_name': logs[0].name,
'function': logs[0].func,

View File

@ -1,7 +1,9 @@
from . import test_repo
from . import test_build_error
from . import test_branch
from . import test_build
from . import test_frontend
from . import test_schedule
from . import test_cron
from . import test_build_config_step
from . import test_build_error

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
from unittest.mock import patch
from odoo.tests import common
RTE_ERROR = """FAIL: TestUiTranslate.test_admin_tour_rte_translator
Traceback (most recent call last):
File "/data/build/odoo/addons/website/tests/test_ui.py", line 89, in test_admin_tour_rte_translator
self.start_tour("/", 'rte_translator', login='admin', timeout=120)
File "/data/build/odoo/odoo/tests/common.py", line 1062, in start_tour
res = self.browser_js(url_path=url_path, code=code, ready=ready, **kwargs)
File "/data/build/odoo/odoo/tests/common.py", line 1046, in browser_js
self.fail('%s\n%s' % (message, error))
AssertionError: The test code "odoo.startTour('rte_translator')" failed
Tour rte_translator failed at step click language dropdown (trigger: .js_language_selector .dropdown-toggle)
"""
class TestBuildError(common.TransactionCase):
def create_build(self, vals):
create_vals = {
'branch_id': self.branch.id,
'name': 'deadbeaf0000ffffffffffffffffffffffffffff',
'port': '1234',
'local_result': 'ok'
}
create_vals.update(vals)
return self.Build.create(create_vals)
@patch('odoo.addons.runbot.models.build.runbot_build._get_params')
def setUp(self, mock_get_params):
super(TestBuildError, self).setUp()
repo = self.env['runbot.repo'].create({'name': 'bla@example.com:foo/bar'})
self.branch = self.env['runbot.branch'].create({
'repo_id': repo.id,
'name': 'refs/heads/master'
})
self.Build = self.env['runbot.build']
self.BuildError = self.env['runbot.build.error']
@patch('odoo.addons.runbot.models.build.runbot_build._get_params')
def test_build_scan(self, mock_get_params):
IrLog = self.env['ir.logging']
ko_build = self.create_build({'local_result': 'ko'})
ok_build = self.create_build({'local_result': 'ok'})
log = {'message': RTE_ERROR,
'build_id': ko_build.id,
'level': 'ERROR',
'type': 'server',
'name': 'test-build-error-name',
'path': 'test-build-error-path',
'func': 'test-build-error-func',
'line': 1,
}
# Test the build parse and ensure that an 'ok' build is not parsed
IrLog.create(log)
log.update({'build_id': ok_build.id})
IrLog.create(log)
ko_build._parse_logs()
ok_build._parse_logs()
build_error = self.BuildError.search([('build_ids','in', [ko_build.id])])
self.assertIn(ko_build, build_error.build_ids, 'The parsed build should be added to the runbot.build.error')
self.assertFalse(self.BuildError.search([('build_ids','in', [ok_build.id])]), 'A successful build should not associated to a runbot.build.error')
# Test that build with same error is added to the errors
ko_build_same_error = self.create_build({'local_result': 'ko'})
log.update({'build_id': ko_build_same_error.id})
IrLog.create(log)
ko_build_same_error._parse_logs()
self.assertIn(ko_build_same_error, build_error.build_ids, 'The parsed build should be added to the existing runbot.build.error')
# Test that line numbers does not interfere with error recognition
ko_build_diff_number = self.create_build({'local_result': 'ko'})
rte_diff_numbers = RTE_ERROR.replace('89','100').replace('1062','1000').replace('1046', '4610')
log.update({'build_id': ko_build_diff_number.id, 'message': rte_diff_numbers})
IrLog.create(log)
ko_build_diff_number._parse_logs()
self.assertIn(ko_build_diff_number, build_error.build_ids, 'The parsed build with different line numbers in error should be added to the runbot.build.error')
# Test that when an error re-appears after the bug has been fixed,
# a new build error is created, with the old one linked
build_error.active = False
ko_build_new = self.create_build({'local_result': 'ko'})
log.update({'build_id': ko_build_new.id})
IrLog.create(log)
ko_build_new._parse_logs()
self.assertNotIn(ko_build_new, build_error.build_ids, 'The parsed build should not be added to a fixed runbot.build.error')
new_build_error = self.BuildError.search([('build_ids','in', [ko_build_new.id])])
self.assertIn(ko_build_new, new_build_error.build_ids, 'The parsed build with a re-apearing error should generate a new runbot.build.error')
self.assertIn(build_error, new_build_error.error_history_ids, 'The old error should appear in history')

View File

@ -44,6 +44,18 @@
<field name="build_url" widget="url" readonly="1" text="View build"/>
</tree>
</field>
<label for="error_history_ids" string="Error history"/>
<field name="error_history_ids" widget="one2many" options="{'not_delete': True, 'no_create': True}">
<tree>
<field name="create_date"/>
<field name="module_name"/>
<field name="summary"/>
<field name="random"/>
<field name="build_count"/>
<field name="responsible"/>
<field name="fixing_commit"/>
</tree>
</field>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>