blob: 2046354d073f4577e85e9c1f3334d68ca6e71ef0 [file] [log] [blame]
# Copyright 2014 the Melange authors.
#
# 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 re
from buildbot.steps import shell
from buildbot.status import results
from buildbot.steps.source import git
from buildbot.process import properties
from buildbot import util, interfaces
from zope.interface import implements # pylint: disable=no-name-in-module
import private
FAILED_TESTS_REGEX = re.compile(r'^(FAIL:) (.*)$')
ERRORED_TESTS_REGEX = re.compile(r'^(ERROR:) (.*)$')
ERRORED_PYLINT_REGEX = re.compile(r'(make:) (\*\*\*) (\[pylint\] Error.*)')
TESTS_SUMMARY_REGEX = re.compile(r'^(Ran) ([0-9]+) tests in ([0-9]+.[0-9]+)s')
DOCS_LOCATION = private.buildbot_url
class PyUnitTestsObserver(object):
"""Class reading the stdio log generated by PyUnit tests and returning
the tests results via some getter methods implemented."""
def __init__(self):
self.testsResults = {'errored': [], 'failed': []}
self.failedTestsCount = 0
self.passedTestsCount = 0
self.skippedTestsCount = 0
self.errorTestsCount = 0
self.totalTests = 0
self.totalTime = 0
self.regroupfailed = [(FAILED_TESTS_REGEX, 1)]
self.regroup_time = [TESTS_SUMMARY_REGEX]
self.regroup_error = [(ERRORED_TESTS_REGEX, 1)]
def outLineReceived(self, line):
matched = False
for regex in self.regroupfailed:
result = regex[0].search(line)
if result:
self.failedTestsCount += 1
self.testsResults['failed'].append(result.groups()[regex[1]].strip())
matched = True
if not matched:
for regex in self.regroup_error:
result = regex[0].search(line)
if result:
self.testsResults['errored'].append(
result.groups()[regex[1]].strip())
matched = True
if not matched:
for regex in self.regroup_time:
result = regex.search(line)
if result:
self.totalTests = result.groups()[1]
self.totalTime = result.groups()[2]
matched = True
return
if not matched:
if '.....' in line:
for chars in line:
if chars == 'S':
self.skippedTestsCount += 1
if chars == '.':
self.passedTestsCount += 1
if chars == 'E':
self.errorTestsCount += 1
class TestCommand(shell.ShellCommand):
"""ShellCommand class for Melange tests."""
def __init__(self, command=None, type=None, log_observer=None, **kwargs):
test_name = command[0] if command else ""
shell.ShellCommand.__init__(
self, description=['Running ' + test_name],
descriptionDone=['Ran ' + test_name],
command=command, **kwargs)
self.flunkOnFailure = True
self.warnOnWarnings = True
self.log_observer = log_observer
self.log_observer.__init__()
self.testsResults = None
self.type = type
def createSummary(self, stdio):
self.updateStats(stdio)
def updateStats(self, log):
stdio = log.getText()
stdio_lines = stdio.split('\n')
total = fails = warnings = errors = 0
hastests = False
# This matches Nose and Django output
if not hastests:
fails += len(re.findall('FAIL:', stdio))
errors += len(
re.findall('=========================================='
'============================\nERROR:', stdio))
for number in re.findall(r"Ran (?P<count>[\d]+)", stdio):
total += int(number)
hastests = True
self.log_observer = self.log_observer or PyUnitTestsObserver()
for lines in stdio_lines:
self.log_observer.outLineReceived(lines)
self.testsResults = self.log_observer.testsResults
skippedTestsCount = self.log_observer.skippedTestsCount
passed = total - (skippedTestsCount + fails + errors + warnings)
# Update the step statistics.
if hastests:
self.step_status.setStatistic('total', total)
self.step_status.setStatistic('fails', fails)
self.step_status.setStatistic('errors', errors)
self.step_status.setStatistic('skipped', skippedTestsCount)
self.step_status.setStatistic('passed', passed)
if fails > 0 or passed > 0 or errors > 0:
self.addHTMLLog(self.type + ' summary', self.createTestsSummary())
def createTestsSummary(self):
#TODO(piyush.devel): Use Templating to construct this HTML response.
html_open = '<html>'
html_closed = '</html>'
html_string = html_open
test_report = self.testsResults
html_string += str(
'We ran ' + str(self.step_status.getStatistic('total'))
+ ' tests </br> </br>')
html_string += str(
str(self.step_status.getStatistic('fails'))
+ ' Tests failed. </br> </br>')
if len(test_report['failed']):
html_string += 'Here is a list of failed tests </br> </br>'
for result in test_report['failed']:
html_string += result
html_string += '</br>'
html_string += '</br> </br> </br>'
html_string += str(str(self.step_status.getStatistic('errors'))
+ ' Tests exited with some error.</br>')
if len(test_report['errored']):
html_string += 'Here is a list of errored tests </br> </br>'
for result in test_report['errored']:
html_string += result
html_string += '</br>'
html_string += '</br> </br> </br>'
html_string += str(str(self.step_status.getStatistic('skipped'))
+ ' Tests were skipped. </br> </br>')
return html_string+html_closed
def getText2(self, cmd, result):
description = shell.ShellCommand.getText2(self, cmd, result)
if self.step_status.hasStatistic('total'):
def append(stat, fmtstring):
val = self.step_status.getStatistic(stat, 0)
if val:
description.append(fmtstring % val)
append('total', '\n%d tests\n')
append('fails', '%d fails\n')
append('errors', '%d errors\n')
append('skipped', '%d skipped\n')
append('passed', '%d passed')
return description
def evaluateCommand(self, cmd):
stdio = self.getLog('stdio').getText()
fails = len(re.findall('FAIL:', stdio))
errors = len(re.findall('========================\nERROR:', stdio))
pylint_errors = len(re.findall(ERRORED_PYLINT_REGEX, stdio))
return results.FAILURE if any(
(fails, errors, pylint_errors)) else results.SUCCESS
class LinkDocs(shell.ShellCommand):
"""This class inherits from shell.ShellCommand and defines a buildstep
to link epydoc documentation to specified url."""
def __init__(self, **kwargs):
"""Calls the constructor of parent class in a buildbot-style,
non-super way."""
shell.ShellCommand.__init__(self, **kwargs)
def createSummary(self, log):
docs_location = DOCS_LOCATION
buildername = self.getProperty('buildername')
buildnumber = self.getProperty('buildnumber')
docs_url = '%sbuilds/%s/%d/docs/index.html' % (
docs_location, buildername, buildnumber)
self.addURL('Documentation', docs_url)
class LinkBuild(shell.ShellCommand):
"""This class inherits from shell.ShellCommand and defines a buildstep
to link the compressed build directory to specified url."""
def __init__(self, **kwargs):
"""Calls the constructor of parent class in a buildbot-style,
non-super way."""
shell.ShellCommand.__init__(self, **kwargs)
def createSummary(self, log):
build_location = DOCS_LOCATION
buildername = self.getProperty('buildername')
buildnumber = self.getProperty('buildnumber')
build_url = '%sbuilds/%s/%d/build/build.tar.gz' % (
build_location, buildername, buildnumber)
self.addURL('Compressed Build Directory', build_url)
def isImportant(change):
"""Determines if a change should trigger a build."""
not_important = ['LICENSE-2.0.txt', 'AUTHORS', 'README.md', '.*wiki']
for file in change.files:
triggerBuild = True
for pattern in not_important:
match = re.match(pattern, file)
if match:
triggerBuild = False
break
if triggerBuild:
return True
return False
class UsersAreEmails(util.ComparableMixin):
implements(interfaces.IEmailLookup)
@staticmethod
def getAddress(name):
return name
def populateBuildFactory(factory):
"""This function populates the BuildFactory Object with
build steps."""
factory.addStep(git.Git(
repourl='https://melange.googlesource.com/soc/',
mode='full', clobberOnFailure=True,
submodules=True,
name='Fetching Source code.'))
factory.addStep(shell.ShellCommand(
command=['make', 'initial-setup'],
name='Initial Setup.'))
factory.addStep(shell.ShellCommand(
command=['make', 'setup'],
name='Setup.'))
factory.addStep(TestCommand(
command=['make', 'pylint'],
name='PyLint',
type='PyLint'))
factory.addStep(shell.ShellCommand(
command=['make', 'jstest'],
name='Javascript Tests.'))
factory.addStep(TestCommand(
command=['make', 'functionaltest'],
name='Functional Tests',
type='Functional'))
factory.addStep(TestCommand(
command=['make', 'pyunit'],
name='PyUnit Tests',
type='PyUnit'))
# factory.addStep(shell.ShellCommand(
# command=['make', 'docs'],
# name='Documentation.'))
# Let's move the project generated documentation
factory.addStep(shell.ShellCommand(
command=['/bin/sh', '-c',
properties.WithProperties(
'echo %s/%s | tee ../documentationsubdir',
'buildername', 'buildnumber')],
description="Save build ID",
name='Saving build ID'))
# factory.addStep(LinkDocs(
# command=['/bin/sh', '../../../master/upload.sh'],
# description='Linking Documentation.',
# name='Generating Documentation.'))
factory.addStep(LinkBuild(
command=['/bin/sh', '../../../master/upload_build_dir.sh'],
description='Linking Build Directory.',
name='Linking Build Directory.'))
return factory