[IMP] runbot: add a job_type on branch and build

Since the runbot_merge module, some branches does not need to be built.
For example the tmp.* branches.
Some other branches does need to be tested but it could be useless to
keep them running. For example the staging branches.
Finally, some builds are generated by server actions during the night.
Those builds does not need to be kept running despite the branch configuration.

For example, the master branch can be configured to create builds with
testing and running but nightly multiple builds can be generated with
testing only.

For that purpose, this commit adds a job_type selection field on the
branch. That way, a branch can be configured by selecting the type of
jobs wanted.
A same kind of job_type was also added on the build that uses the
branch's value if nothing is specified at build creation.

A decorator is used on the job_ methods to specify their job types.
For example, a job method decorated by 'testing' will run if the
branch/build job_type is 'testing' or 'all'.
This commit is contained in:
Xavier Morel 2018-12-18 13:54:24 +01:00 committed by Christophe Monniez
parent 8ef9eafc60
commit 8c73e6a901
10 changed files with 133 additions and 7 deletions

View File

@ -6,7 +6,7 @@
'author': "Odoo SA",
'website': "http://runbot.odoo.com",
'category': 'Website',
'version': '2.5',
'version': '2.6',
'depends': ['website', 'base'],
'data': [
'security/runbot_security.xml',

View File

@ -27,6 +27,12 @@ class runbot_branch(models.Model):
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')
priority = fields.Boolean('Build priority', default=False)
job_type = fields.Selection([
('testing', 'Testing jobs only'),
('running', 'Running job only'),
('all', 'All jobs'),
('none', 'Do not execute jobs')
], required=True, default='all')
@api.depends('name')
def _get_branch_infos(self):

View File

@ -25,6 +25,19 @@ re_job = re.compile('_job_\d')
_logger = logging.getLogger(__name__)
def runbot_job(*accepted_job_types):
""" Decorator for runbot_build _job_x methods to filter build jobs """
accepted_job_types += ('all', )
def job_decorator(func):
def wrapper(self, build, log_path):
if build.job_type == 'none' or build.job_type not in accepted_job_types:
build._log(func.__name__, 'Skipping job')
return -2
return func(self, build, log_path)
return wrapper
return job_decorator
class runbot_build(models.Model):
_name = "runbot.build"
@ -74,6 +87,13 @@ class runbot_build(models.Model):
],
default='normal',
string='Build type')
job_type = fields.Selection([
('testing', 'Testing jobs only'),
('running', 'Running job only'),
('all', 'All jobs'),
('none', 'Do not execute jobs'),
],
)
def copy(self, values=None):
raise UserError("Cannot duplicate build!")
@ -81,6 +101,8 @@ class runbot_build(models.Model):
def create(self, vals):
build_id = super(runbot_build, self).create(vals)
extra_info = {'sequence': build_id.id}
job_type = vals['job_type'] if 'job_type' in vals else build_id.branch_id.job_type
extra_info.update({'job_type': job_type})
context = self.env.context
# detect duplicate
@ -733,6 +755,7 @@ class runbot_build(models.Model):
# Jobs definitions
# They all need "build log_path" parameters
@runbot_job('testing', 'running')
def _job_00_init(self, build, log_path):
build._log('init', 'Init build environment')
# notify pending build - avoid confusing users by saying nothing
@ -740,12 +763,14 @@ class runbot_build(models.Model):
build._checkout()
return -2
@runbot_job('testing', 'running')
def _job_02_docker_build(self, build, log_path):
"""Build the docker image"""
build._log('docker_build', 'Building docker image')
docker_build(log_path, build._path())
return -2
@runbot_job('testing')
def _job_10_test_base(self, build, log_path):
build._log('test_base', 'Start test base module')
# run base test
@ -758,14 +783,18 @@ class runbot_build(models.Model):
cmd.extend(shlex.split(build.extra_params))
return docker_run(cmd, log_path, build._path(), build._get_docker_name(), cpu_limit=600)
@runbot_job('testing', 'running')
def _job_20_test_all(self, build, log_path):
build._log('test_all', 'Start test all modules')
cpu_limit = 2400
self._local_pg_createdb("%s-all" % build.dest)
cmd, mods = build._cmd()
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']
build._log('test_all', 'Start test all modules')
if grep(build._server("tools/config.py"), "test-enable") and build.job_type in ('testing', 'all'):
cmd.extend(['--test-enable', '--log-level=test'])
else:
build._log('test_all', 'Installing modules without testing')
cmd += ['-d', '%s-all' % build.dest, '-i', mods, '--stop-after-init', '--max-cron-threads=0']
if build.extra_params:
cmd.extend(build.extra_params.split(' '))
if build.coverage:
@ -782,6 +811,7 @@ class runbot_build(models.Model):
build.write({'job_start': now()})
return docker_run(cmd, log_path, build._path(), build._get_docker_name(), cpu_limit=cpu_limit)
@runbot_job('testing')
def _job_21_coverage_html(self, build, log_path):
if not build.coverage:
return -2
@ -791,6 +821,7 @@ class runbot_build(models.Model):
cmd = [ get_py_version(build), "-m", "coverage", "html", "-d", "/data/build/coverage", "--ignore-errors"]
return docker_run(cmd, log_path, build._path(), build._get_docker_name())
@runbot_job('testing')
def _job_22_coverage_result(self, build, log_path):
if not build.coverage:
return -2
@ -805,6 +836,7 @@ class runbot_build(models.Model):
build._log('coverage_result', 'Coverage file not found')
return -2 # nothing to wait for
@runbot_job('running')
def _job_30_run(self, build, log_path):
# adjust job_end to record an accurate job_20 job_time
build._log('run', 'Start running build %s' % build.dest)

View File

@ -248,7 +248,7 @@ class runbot_repo(models.Model):
host = fqdn()
Build = self.env['runbot.build']
domain = [('repo_id', 'in', ids)]
domain = [('repo_id', 'in', ids), ('branch_id.job_type', '!=', 'none')]
domain_host = domain + [('host', '=', host)]
# schedule jobs (transitions testing -> running, kill jobs, ...)

View File

@ -2,4 +2,5 @@ from . import test_repo
from . import test_branch
from . import test_build
from . import test_jobs
from . import test_frontend
from . import test_frontend
from . import test_job_types

View File

@ -22,6 +22,7 @@ class Test_Branch(common.TransactionCase):
self.assertEqual(branch.branch_name, 'master')
self.assertEqual(branch.branch_url, 'https://example.com/foo/bar/tree/master')
self.assertEqual(branch.job_type, 'all')
@patch('odoo.addons.runbot.models.repo.runbot_repo._github')
def test_pull_request(self, mock_github):

View File

@ -112,6 +112,33 @@ class Test_Build(common.TransactionCase):
self.assertEqual(dev_build.state, 'duplicate')
self.assertEqual(dev_build.duplicate_id.id, pr_build.id)
def test_build_job_type_from_branch_default(self):
"""test build job_type is computed from branch default job_type"""
build = self.Build.create({
'branch_id': self.branch.id,
'name': 'd0d0caca0000ffffffffffffffffffffffffffff',
})
self.assertEqual(build.job_type, 'all', "job_type should be the same as the branch")
def test_build_job_type_from_branch_none(self):
"""test build job_type is computed from branch"""
self.branch.job_type = 'none'
build = self.Build.create({
'branch_id': self.branch.id,
'name': 'd0d0caca0000ffffffffffffffffffffffffffff',
})
self.assertEqual(build.job_type, 'none', "job_type should be the same as the branch")
def test_build_job_type_can_be_set(self):
"""test build job_type can be set to something different than the one on the branch"""
self.branch.job_type = 'running'
build = self.Build.create({
'branch_id': self.branch.id,
'name': 'd0d0caca0000ffffffffffffffffffffffffffff',
'job_type': 'testing'
})
self.assertEqual(build.job_type, 'testing', "job_type should be the one set on the build")
@patch('odoo.addons.runbot.models.branch.runbot_branch._is_on_remote')
def test_closest_branch_01(self, mock_is_on_remote):
""" test find a matching branch in a target repo based on branch name """

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
from time import localtime
from unittest.mock import patch
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tests import common
class Test_Jobs(common.TransactionCase):
def setUp(self):
super(Test_Jobs, self).setUp()
self.Repo = self.env['runbot.repo']
self.repo = self.Repo.create({'name': 'bla@example.com:foo/bar', 'token': 'xxx'})
self.Branch = self.env['runbot.branch']
self.branch_master = self.Branch.create({
'repo_id': self.repo.id,
'name': 'refs/heads/master',
})
self.Build = self.env['runbot.build']
self.build = self.Build.create({
'branch_id': self.branch_master.id,
'name': 'd0d0caca0000ffffffffffffffffffffffffffff',
'port' : '1234',
})
@patch('odoo.addons.runbot.models.build.docker_run')
@patch('odoo.addons.runbot.models.build.runbot_build._local_pg_createdb')
def test_job_10(self, mock_create_db, mock_docker_run):
""" Test that job10 is done or skipped depending on job_type """
# test that the default job_type value executes the tests
mock_docker_run.return_value = "Mocked run"
ret = self.Build._job_10_test_base(self.build, '/tmp/x.log')
self.assertEqual("Mocked run", ret, "A branch with default job_type should run job_10")
# test skip when job_type is none
self.build.job_type = 'none'
ret = self.Build._job_10_test_base(self.build, '/tmp/x.log')
self.assertEqual(-2, ret, "A branch with job_type 'none' should skip job_10")
# test skip when job_type is running
self.build.job_type = 'running'
ret = self.Build._job_10_test_base(self.build, '/tmp/x.log')
self.assertEqual(-2, ret, "A branch with job_type 'running' should skip job_10")
# test run when job_type is testing
self.build.job_type = 'testing'
ret = self.Build._job_10_test_base(self.build, '/tmp/x.log')
self.assertEqual("Mocked run", ret, "A branch with job_type 'testing' should run job_10")
# test run when job_type is all
self.build.job_type = 'all'
ret = self.Build._job_10_test_base(self.build, '/tmp/x.log')
self.assertEqual("Mocked run", ret, "A branch with job_type 'all' should run job_10")

View File

@ -16,6 +16,7 @@
<field name="pull_head_name"/>
<field name="sticky"/>
<field name="priority"/>
<field name="job_type"/>
<field name="coverage"/>
<field name="state"/>
<field name="modules"/>
@ -39,6 +40,7 @@
<field name="name"/>
<field name="sticky"/>
<field name="priority"/>
<field name="job_type"/>
<field name="coverage"/>
<field name="state"/>
</tree>

View File

@ -5,6 +5,7 @@ import io
import logging
import re
from odoo.addons.runbot.models.build import runbot_job
from odoo import models
_logger = logging.getLogger(__name__)
@ -13,6 +14,7 @@ _logger = logging.getLogger(__name__)
class runbot_build(models.Model):
_inherit = "runbot.build"
@runbot_job('testing')
def _job_05_check_cla(self, build, log_path):
cla_glob = glob.glob(build._path("doc/cla/*/*.md"))
if cla_glob: