From 76242030066e4938e3145675fa259b9ee9d37e12 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Thu, 9 Jan 2014 16:49:02 -0500 Subject: [PATCH] parse the failed jobs in stream one of the big issues today with er is the amount the there is coupling between the bot and the classifier about knowing when jobs are ready. The impact of this is that we are often incorrectly determining when jobs are ready, because we have this small set of files we test for, that aren't right for various jobs. This is the beginning of decoupling that. By parsing the job names that have failed in the jenkins failure message we can move all the readiness checking into the Stream. This commit adds the parsing and the unit tests, though it doesn't actually change behavior to use it yet (next patch). Change-Id: I54ffa3495a36c2d61b1824794a672c8f5552df54 --- elastic_recheck/elasticRecheck.py | 48 +++++++++++------ .../tests/unit/jenkins/events.json | 20 +++++++ elastic_recheck/tests/unit/test_stream.py | 52 +++++++++++++++++++ 3 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 elastic_recheck/tests/unit/jenkins/events.json create mode 100644 elastic_recheck/tests/unit/test_stream.py diff --git a/elastic_recheck/elasticRecheck.py b/elastic_recheck/elasticRecheck.py index b5cd85a2..83d98a5e 100755 --- a/elastic_recheck/elasticRecheck.py +++ b/elastic_recheck/elasticRecheck.py @@ -22,6 +22,7 @@ import urllib2 import ConfigParser import logging import os +import re import sys import time @@ -57,7 +58,8 @@ class Stream(object): if thread: self.gerrit.startWatching() - def _is_jenkins_failure(self, event): + @staticmethod + def parse_jenkins_failure(event): """Is this comment a jenkins failure comment.""" if event.get('type', '') != 'comment-added': return False @@ -66,8 +68,16 @@ class Stream(object): if (username != 'jenkins'): return False - return ("Build failed. For information on how to proceed" in - event['comment']) + if not ("Build failed. For information on how to proceed" in + event['comment']): + return False + + failed_tests = {} + for line in event['comment'].split("\n"): + m = re.search("- ([\w-]+)\s*(http://\S+)\s*:\s*FAILURE", line) + if m: + failed_tests[m.group(1)] = m.group(2) + return failed_tests def _failed_unit_tests(self, line): """Did we fail unit tests? If so not a valid failure.""" @@ -84,22 +94,26 @@ class Stream(object): self.log.debug("entering get_failed_tempest") while True: event = self.gerrit.getEvent() - if self._is_jenkins_failure(event): - self.log.debug("potential failed_tempest") - found = False - for line in event['comment'].split('\n'): - if self._failed_unit_tests(line): - found = False - break - if self._valid_failure(line): - url = [x for x in line.split() if "http" in x][0] - if RequiredFiles.files_at_url(url): - self.log.debug("All file present") - found = True - if found: - return event + failed_jobs = Stream.parse_jenkin_failure(event) + if not failed_jobs: + # nothing to see here, lets try the next event continue + self.log.debug("potential failed_tempest") + found = False + for line in event['comment'].split('\n'): + if self._failed_unit_tests(line): + found = False + break + if self._valid_failure(line): + url = [x for x in line.split() if "http" in x][0] + if RequiredFiles.files_at_url(url): + self.log.debug("All file present") + found = True + if found: + return event + continue + def leave_comment(self, project, commit, bugs=None): if bugs: bug_urls = ['https://bugs.launchpad.net/bugs/%s' % x for x in bugs] diff --git a/elastic_recheck/tests/unit/jenkins/events.json b/elastic_recheck/tests/unit/jenkins/events.json new file mode 100644 index 00000000..bff139f7 --- /dev/null +++ b/elastic_recheck/tests/unit/jenkins/events.json @@ -0,0 +1,20 @@ +{ + "events": [ + { + "type": "comment-added", + "author": { + "username": "jenkins" + }, + "comment":"Patch Set 1:\n\nBuild failed. For information on how to proceed, see https://wiki.openstack.org/wiki/GerritJenkinsGit#Test_Failures\n\n- gate-requirements-pep8 http://logs.openstack.org/31/64831/1/check/gate-requirements-pep8/f5abe44 : SUCCESS in 46s\n- gate-requirements-python27 http://logs.openstack.org/31/64831/1/check/gate-requirements-python27/d09e102 : SUCCESS in 1m 51s\n- gate-requirements-pypy http://logs.openstack.org/31/64831/1/check/gate-requirements-pypy/b5c4672 : SUCCESS in 2m 14s\n- check-requirements-integration-dsvm http://logs.openstack.org/31/64831/1/check/check-requirements-integration-dsvm/135d0b4 : FAILURE in 9m 54s\n- check-tempest-dsvm-full http://logs.openstack.org/31/64831/1/check/check-tempest-dsvm-full/287c655 : FAILURE in 38m 36s\n- check-tempest-dsvm-postgres-full http://logs.openstack.org/31/64831/1/check/check-tempest-dsvm-postgres-full/91f3b16 : FAILURE in 1h 01m 15s\n- check-tempest-dsvm-neutron http://logs.openstack.org/31/64831/1/check/check-tempest-dsvm-neutron/117634b : FAILURE in 31m 20s\n- gate-tempest-dsvm-large-ops http://logs.openstack.org/31/64831/1/check/gate-tempest-dsvm-large-ops/31f47cd : SUCCESS in 14m 28s\n- gate-tempest-dsvm-neutron-large-ops http://logs.openstack.org/31/64831/1/check/gate-tempest-dsvm-neutron-large-ops/ea934a5 : SUCCESS in 15m 42s\n- check-grenade-dsvm http://logs.openstack.org/31/64831/1/check/check-grenade-dsvm/c025bc2 : SUCCESS in 31m 12s\n- check-swift-dsvm-functional http://logs.openstack.org/31/64831/1/check/check-swift-dsvm-functional/91ccf18 : SUCCESS in 12m 14s\n" + }, + { + "type": "blah" + }, + { + "type": "comment-added", + "author": { + "username": "sdague" + } + } + ] +} diff --git a/elastic_recheck/tests/unit/test_stream.py b/elastic_recheck/tests/unit/test_stream.py new file mode 100644 index 00000000..7a27eecf --- /dev/null +++ b/elastic_recheck/tests/unit/test_stream.py @@ -0,0 +1,52 @@ +# Copyright 2014 Samsung Electronics. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from elastic_recheck import elasticRecheck +from elastic_recheck import tests + + +class TestStream(tests.TestCase): + + def setUp(self): + super(TestStream, self).setUp() + with open("elastic_recheck/tests/unit/jenkins/events.json") as f: + j = json.load(f) + self.events = j['events'] + + def test_gerrit_parsing_none(self): + self.assertFalse( + elasticRecheck.Stream.parse_jenkins_failure(self.events[1])) + self.assertFalse( + elasticRecheck.Stream.parse_jenkins_failure(self.events[2])) + + def test_gerrit_parsing(self): + jobs = elasticRecheck.Stream.parse_jenkins_failure(self.events[0]) + self.assertIn('check-requirements-integration-dsvm', jobs) + self.assertIn('check-tempest-dsvm-full', jobs) + self.assertIn('check-tempest-dsvm-postgres-full', jobs) + self.assertIn('check-tempest-dsvm-neutron', jobs) + + self.assertEqual(jobs['check-requirements-integration-dsvm'], + "http://logs.openstack.org/31/64831/1/check/" + "check-requirements-integration-dsvm/135d0b4") + + self.assertNotIn('gate-requirements-pep8', jobs) + self.assertNotIn('gate-requirements-python27', jobs) + self.assertNotIn('gate-requirements-pypy', jobs) + self.assertNotIn('gate-tempest-dsvm-large-ops', jobs) + self.assertNotIn('gate-tempest-dsvm-neutron-large-ops', jobs) + self.assertNotIn('check-grenade-dsvm', jobs) + self.assertNotIn('check-swift-dsvm-functional', jobs)