diff --git a/runbot/models/build_error.py b/runbot/models/build_error.py index d37179cd..a7909333 100644 --- a/runbot/models/build_error.py +++ b/runbot/models/build_error.py @@ -204,6 +204,8 @@ class BuildError(models.Model): @api.model def _parse_logs(self, ir_logs): + if not ir_logs: + return regexes = self.env['runbot.error.regex'].search([]) search_regs = regexes.filtered(lambda r: r.re_type == 'filter') cleaning_regs = regexes.filtered(lambda r: r.re_type == 'cleaning') @@ -218,24 +220,19 @@ class BuildError(models.Model): build_errors = self.env['runbot.build.error'] # add build ids to already detected errors existing_errors = self.env['runbot.build.error'].search([('fingerprint', 'in', list(hash_dict.keys())), ('active', '=', True)]) + existing_fingerprints = existing_errors.mapped('fingerprint') build_errors |= existing_errors for build_error in existing_errors: logs = hash_dict[build_error.fingerprint] - self.env['runbot.build.error.link'].create([{ - 'build_id': rec.build_id.id, - 'build_error_id': build_error.id, - 'log_date': rec.create_date} - for rec in logs if rec.build_id not in build_error.build_ids]) - # update filepath if it changed. This is optionnal and mainly there in case we adapt the OdooRunner log if logs[0].path != build_error.file_path: build_error.file_path = logs[0].path build_error.function = logs[0].func - del hash_dict[build_error.fingerprint] - # create an error for the remaining entries for fingerprint, logs in hash_dict.items(): + if fingerprint in existing_fingerprints: + continue new_build_error = self.env['runbot.build.error'].create({ 'content': logs[0].message, 'module_name': logs[0].name.removeprefix('odoo.').removeprefix('addons.'), @@ -243,12 +240,17 @@ class BuildError(models.Model): 'function': logs[0].func, }) build_errors |= new_build_error - self.env['runbot.build.error.link'].create([{ - 'build_id': rec.build_id.id, - 'build_error_id': new_build_error.id, - 'log_date': rec.create_date} - for rec in logs]) + existing_fingerprints.append(fingerprint) + for build_error in build_errors: + logs = hash_dict[build_error.fingerprint] + for rec in logs: + if rec.build_id not in build_error.build_error_link_ids.build_id: + self.env['runbot.build.error.link'].create({ + 'build_id': rec.build_id.id, + 'build_error_id': build_error.id, + 'log_date': rec.create_date + }) if build_errors: window_action = { diff --git a/runbot/tests/test_build_error.py b/runbot/tests/test_build_error.py index e0df3860..009462fa 100644 --- a/runbot/tests/test_build_error.py +++ b/runbot/tests/test_build_error.py @@ -118,6 +118,7 @@ class TestBuildError(RunbotCase): def test_build_scan(self): IrLog = self.env['ir.logging'] ko_build = self.create_test_build({'local_result': 'ok', 'local_state': 'testing'}) + ko_build_b = self.create_test_build({'local_result': 'ok', 'local_state': 'testing'}) ok_build = self.create_test_build({'local_result': 'ok', 'local_state': 'running'}) cleaner = self.env['runbot.error.regex'].create({ @@ -144,6 +145,14 @@ class TestBuildError(RunbotCase): # Test the build parse and ensure that an 'ok' build is not parsed IrLog.create(log) + # As it happens that a same error could appear again in the same build, ensure that the parsing adds only one link + log.update({'create_date': fields.Datetime.from_string('2023-08-29 00:48:21')}) + IrLog.create(log) + + # now simulate another build with the same errors + log.update({'build_id': ko_build_b.id, 'create_date': fields.Datetime.from_string('2023-08-29 01:46:21')}) + log.update({'build_id': ko_build_b.id, 'create_date': fields.Datetime.from_string('2023-08-29 01:48:21')}) + log.update({'build_id': ok_build.id}) IrLog.create(log) @@ -151,6 +160,7 @@ class TestBuildError(RunbotCase): self.assertEqual(ok_build.local_result, 'ok', 'Running build should not have gone ko after error log') ko_build._parse_logs() + ko_build_b._parse_logs() ok_build._parse_logs() # build_error = self.BuildError.search([('build_ids', 'in', [ko_build.id])]) build_error = self.BuildErrorLink.search([('build_id.id', '=', ko_build.id)]).mapped('build_error_id')