diff --git a/runbot/controllers/frontend.py b/runbot/controllers/frontend.py
index 9f3b921f..3249c8b1 100644
--- a/runbot/controllers/frontend.py
+++ b/runbot/controllers/frontend.py
@@ -33,7 +33,7 @@ class Runbot(http.Controller):
'port': real_build.port,
'server_match': real_build.server_match,
'duplicate_of': build.duplicate_id if build.state == 'duplicate' else False,
- 'coverage': build.branch_id.coverage,
+ 'coverage': build.coverage or build.branch_id.coverage,
'revdep_build_ids': sorted(build.revdep_build_ids, key=lambda x: x.repo_id.name),
}
diff --git a/runbot/models/branch.py b/runbot/models/branch.py
index 0166f1f1..581f2964 100644
--- a/runbot/models/branch.py
+++ b/runbot/models/branch.py
@@ -21,6 +21,7 @@ class runbot_branch(models.Model):
pull_head_name = fields.Char(compute='_get_pull_head_name', type='char', string='PR HEAD name', readonly=1, store=True)
sticky = fields.Boolean('Sticky')
coverage = fields.Boolean('Coverage')
+ coverage_result = fields.Float(compute='_get_last_coverage', type='Float', string='Last coverage', store=False)
state = fields.Char('Status')
modules = fields.Char("Modules to Install", help="Comma-separated list of modules to install and test.")
job_timeout = fields.Integer('Job Timeout (minutes)', help='For default timeout: Mark it zero')
@@ -79,3 +80,13 @@ class runbot_branch(models.Model):
r = {}
r[self.id] = "http://%s/web/login?db=%s-all&login=admin&redirect=/web?debug=1" % (fqdn, dest)
return r
+
+ def _get_last_coverage(self):
+ """ Return the coverage result of the last build in branch """
+ for branch in self:
+ last_build = self.env['runbot.build'].search([
+ ('branch_id.id', '=', branch.id),
+ ('state', 'in', ['done', 'running']),
+ ('coverage_result', '>=', 0.0),
+ ], order='sequence desc', limit=1)
+ branch.coverage_result = last_build.coverage_result or 0.0
diff --git a/runbot/models/build.py b/runbot/models/build.py
index e0c42ac9..e573e27d 100644
--- a/runbot/models/build.py
+++ b/runbot/models/build.py
@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
+import coverage
import glob
import logging
import operator
import os
import re
import resource
+import shlex
import shutil
import signal
import subprocess
@@ -62,6 +64,9 @@ class runbot_build(models.Model):
revdep_build_ids = fields.Many2many('runbot.build', 'runbot_rev_dep_builds',
column1='rev_dep_id', column2='dependent_id',
string='Builds that depends on this build')
+ extra_params = fields.Char('Extra cmd args')
+ coverage = fields.Boolean('Enable code coverage')
+ coverage_result = fields.Float('Coverage result', digits=(5, 2))
def copy(self, values=None):
raise UserError("Cannot duplicate build!")
@@ -749,6 +754,8 @@ class runbot_build(models.Model):
if grep(build._server("tools/config.py"), "test-enable"):
cmd.append("--test-enable")
cmd += ['-d', '%s-base' % build.dest, '-i', 'base', '--stop-after-init', '--log-level=test', '--max-cron-threads=0']
+ if build.extra_params:
+ cmd.extend(shlex.split(build.extra_params))
return self._spawn(cmd, lock_path, log_path, cpu_limit=300)
def _job_20_test_all(self, build, lock_path, log_path):
@@ -758,8 +765,10 @@ class runbot_build(models.Model):
if grep(build._server("tools/config.py"), "test-enable"):
cmd.append("--test-enable")
cmd += ['-d', '%s-all' % build.dest, '-i', mods, '--stop-after-init', '--log-level=test', '--max-cron-threads=0']
+ if build.extra_params:
+ cmd.extend(build.extra_params.split(' '))
env = None
- if build.branch_id.coverage:
+ if build.coverage:
pyversion = get_py_version(build)
env = self._coverage_env(build)
available_modules = [
@@ -777,8 +786,8 @@ class runbot_build(models.Model):
def _coverage_env(self, build):
return dict(os.environ, COVERAGE_FILE=build._path('.coverage'))
- def _job_21_coverage(self, build, lock_path, log_path):
- if not build.branch_id.coverage:
+ def _job_21_coverage_html(self, build, lock_path, log_path):
+ if not build.coverage:
return -2
pyversion = get_py_version(build)
cov_path = build._path('coverage')
@@ -786,6 +795,14 @@ class runbot_build(models.Model):
cmd = [pyversion, "-m", "coverage", "html", "-d", cov_path, "--ignore-errors"]
return self._spawn(cmd, lock_path, log_path, env=self._coverage_env(build))
+ def _job_22_coverage_result(self, build, lock_path, log_path):
+ if not build.coverage:
+ return -2
+ cov = coverage.coverage(data_file=build._path('.coverage'))
+ cov.load()
+ build.coverage_result = cov.report()
+ return -2 # nothing to wait for
+
def _job_30_run(self, build, lock_path, log_path):
# adjust job_end to record an accurate job_20 job_time
build._log('run', 'Start running build %s' % build.dest)
diff --git a/runbot/models/repo.py b/runbot/models/repo.py
index 13d0be7b..83be6af7 100644
--- a/runbot/models/repo.py
+++ b/runbot/models/repo.py
@@ -195,6 +195,7 @@ class runbot_repo(models.Model):
'committer_email': committer_email,
'subject': subject,
'date': dateutil.parser.parse(date[:19]),
+ 'coverage': branch.coverage,
}
if not branch.sticky:
# pending builds are skipped as we have a new ref
diff --git a/runbot/templates/frontend.xml b/runbot/templates/frontend.xml
index 1ef9ed7e..eedda425 100644
--- a/runbot/templates/frontend.xml
+++ b/runbot/templates/frontend.xml
@@ -125,6 +125,10 @@
Branch or pull
+
+
+ cov: %
+
default
diff --git a/runbot/views/build_views.xml b/runbot/views/build_views.xml
index ac460736..2d56aee9 100644
--- a/runbot/views/build_views.xml
+++ b/runbot/views/build_views.xml
@@ -50,6 +50,7 @@
+