diff --git a/runbot/__manifest__.py b/runbot/__manifest__.py index 8650b1d6..b2f64666 100644 --- a/runbot/__manifest__.py +++ b/runbot/__manifest__.py @@ -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', diff --git a/runbot/models/branch.py b/runbot/models/branch.py index bd5b99ac..51b16631 100644 --- a/runbot/models/branch.py +++ b/runbot/models/branch.py @@ -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): diff --git a/runbot/models/build.py b/runbot/models/build.py index b05e9256..748f78e9 100644 --- a/runbot/models/build.py +++ b/runbot/models/build.py @@ -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) diff --git a/runbot/models/repo.py b/runbot/models/repo.py index c932fea5..c702757c 100644 --- a/runbot/models/repo.py +++ b/runbot/models/repo.py @@ -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, ...) diff --git a/runbot/tests/__init__.py b/runbot/tests/__init__.py index 809caab7..79c8d97b 100644 --- a/runbot/tests/__init__.py +++ b/runbot/tests/__init__.py @@ -2,4 +2,5 @@ from . import test_repo from . import test_branch from . import test_build from . import test_jobs -from . import test_frontend \ No newline at end of file +from . import test_frontend +from . import test_job_types diff --git a/runbot/tests/test_branch.py b/runbot/tests/test_branch.py index c626a1b8..fb9baee2 100644 --- a/runbot/tests/test_branch.py +++ b/runbot/tests/test_branch.py @@ -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): diff --git a/runbot/tests/test_build.py b/runbot/tests/test_build.py index b13f9bea..4897f417 100644 --- a/runbot/tests/test_build.py +++ b/runbot/tests/test_build.py @@ -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 """ diff --git a/runbot/tests/test_job_types.py b/runbot/tests/test_job_types.py new file mode 100644 index 00000000..f25dced9 --- /dev/null +++ b/runbot/tests/test_job_types.py @@ -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") \ No newline at end of file diff --git a/runbot/views/branch_views.xml b/runbot/views/branch_views.xml index 1c20f338..870fbe86 100644 --- a/runbot/views/branch_views.xml +++ b/runbot/views/branch_views.xml @@ -16,6 +16,7 @@ + @@ -39,6 +40,7 @@ + diff --git a/runbot_cla/runbot.py b/runbot_cla/runbot.py index aa937f0f..f79202cb 100644 --- a/runbot_cla/runbot.py +++ b/runbot_cla/runbot.py @@ -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: