Chris Cheng 7712c0b7ee allow disabled to remain the last setting
In the latest version of Jenkins, the default value of 'disabled' is
true. When 'disabled' is not set (or None), reconfig_job will activate
the job, but in some cases we hope that 'disabled' remain the last
setting.
When 'disabled' is not set (or None) in yaml, and the job exists,
fix_disabled will try to get 'disabled' from the jenkins server. So the
'disabled' value should match the current value on the jenkins server.

Change-Id: Icd840f15c4d48cd8a8ebc0f891beac68c2bb6bdd
2024-10-30 17:19:06 +00:00

547 lines
19 KiB
Python

# Joint copyright:
# - Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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.
# The goal of these tests is to check that given a particular set of flags to
# Jenkins Job Builder's command line tools it will result in a particular set
# of actions by the JJB library, usually through interaction with the
# python-jenkins library.
from unittest import mock
import pytest
def test_update_jobs(mocker, fixtures_dir, default_config_file, execute_jenkins_jobs):
"""
Test update_job is called
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs")
mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
reconfig_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_job")
path = fixtures_dir / "cmd-002.yaml"
args = ["--conf", default_config_file, "update", str(path)]
execute_jenkins_jobs(args)
reconfig_job.assert_has_calls(
[
mock.call(job_name, mock.ANY)
for job_name in ["bar001", "bar002", "baz001", "bam001"]
],
any_order=True,
)
def test_update_jobs_enabled_only(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""
Test update_job is called with --enabled-only
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs")
mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
reconfig_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_job")
path = fixtures_dir / "cmd-002.yaml"
args = ["--conf", default_config_file, "update", "--enabled-only", str(path)]
execute_jenkins_jobs(args)
reconfig_job.assert_has_calls(
[mock.call(job_name, mock.ANY) for job_name in ["bar001", "bar002", "baz001"]],
any_order=True,
)
# make sure that there are only those 3 calls checked before
assert len(reconfig_job.call_args_list) == 3
def test_update_jobs_decode_job_output(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""
Test that job xml output has been decoded before attempting to update
"""
mocker.patch("jenkins_jobs.builder.JenkinsManager.is_job", return_value=True)
mocker.patch("jenkins_jobs.builder.JenkinsManager.get_jobs")
mocker.patch("jenkins_jobs.builder.JenkinsManager.get_job_md5")
mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
update_job_mock = mocker.patch("jenkins_jobs.builder.JenkinsManager.update_job")
# don't care about the value returned here
update_job_mock.return_value = ([], 0)
path = fixtures_dir / "cmd-002.yaml"
args = ["--conf", default_config_file, "update", str(path)]
execute_jenkins_jobs(args)
assert isinstance(update_job_mock.call_args[0][1], str)
def test_update_jobs_and_delete_old(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""Test update behaviour with --delete-old option.
* mock out a call to jenkins.Jenkins.get_jobs() to return a known list
of job names.
* mock out a call to jenkins.Jenkins.reconfig_job() and
jenkins.Jenkins.delete_job() to detect calls being made to determine
that JJB does correctly delete the jobs it should delete when passed
a specific set of inputs.
* mock out a call to jenkins.Jenkins.job_exists() to always return
True.
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
jenkins_get_all_jobs = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs"
)
jenkins_reconfig_job = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.reconfig_job"
)
jenkins_delete_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.delete_job")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_views")
yaml_jobs = ["bar001", "bar002", "baz001", "bam001"]
extra_jobs = ["old_job001", "old_job002", "unmanaged"]
path = fixtures_dir / "cmd-002.yaml"
args = ["--conf", default_config_file, "update", "--delete-old", str(path)]
jenkins_get_all_jobs.return_value = [
{"fullname": name} for name in yaml_jobs + extra_jobs
]
mocker.patch(
"jenkins_jobs.builder.JenkinsManager.is_managed_job",
side_effect=(lambda name: name != "unmanaged"),
)
execute_jenkins_jobs(args)
jenkins_reconfig_job.assert_has_calls(
[mock.call(job_name, mock.ANY) for job_name in yaml_jobs], any_order=True
)
calls = [mock.call(name) for name in extra_jobs if name != "unmanaged"]
jenkins_delete_job.assert_has_calls(calls)
# to ensure only the calls we expected were made, have to check
# there were no others, as no API call for assert_has_only_calls
assert jenkins_delete_job.call_count == len(calls)
def test_update_jobs_and_delete_old_views_only(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""Test update behaviour with --delete-old option
with --views-only option specified.
No jobs should be deleted.
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
jenkins_get_all_jobs = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs"
)
jenkins_reconfig_job = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.reconfig_job"
)
jenkins_delete_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.delete_job")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.view_exists")
jenkins_get_all_views = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_views"
)
jenkins_reconfig_view = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.reconfig_view"
)
jenkins_delete_view = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.delete_view"
)
yaml_jobs = ["job-1", "job-2", "job-3"]
extra_managed_jobs = ["old-job-1", "old-job-2"]
unmanaged_jobs = ["unmanaged-job"]
yaml_views = ["view-1", "view-2", "view-3"]
extra_managed_views = ["old-view-1", "old-view-2"]
unmanaged_views = ["unmanaged-view"]
jenkins_get_all_jobs.return_value = [
{"fullname": name} for name in yaml_jobs + extra_managed_jobs + unmanaged_jobs
]
jenkins_get_all_views.return_value = [
{"name": name} for name in yaml_views + extra_managed_views + unmanaged_views
]
mocker.patch(
"jenkins_jobs.builder.JenkinsManager.is_managed_job",
side_effect=(lambda name: name not in unmanaged_jobs),
)
mocker.patch(
"jenkins_jobs.builder.JenkinsManager.is_managed_view",
side_effect=(lambda name: name not in unmanaged_views),
)
path = fixtures_dir / "update-both.yaml"
args = [
"--conf",
default_config_file,
"update",
"--delete-old",
"--views-only",
str(path),
]
execute_jenkins_jobs(args)
assert jenkins_reconfig_job.call_count == 0
assert jenkins_delete_job.call_count == 0
assert jenkins_reconfig_view.call_count == 3
assert jenkins_delete_view.call_count == 2
def test_update_views(mocker, fixtures_dir, default_config_file, execute_jenkins_jobs):
"""
Test update_job is called for project with views
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.view_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_views")
reconfig_view = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_view")
path = fixtures_dir / "update-views.yaml"
args = ["--conf", default_config_file, "update", str(path)]
execute_jenkins_jobs(args)
reconfig_view.assert_has_calls(
[
mock.call(view_name, mock.ANY)
for view_name in ["view-1", "view-2", "view-3"]
],
any_order=True,
)
assert reconfig_view.call_count == 3
def test_update_jobs_and_views(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""
Test update_job is called for project with both jobs and views
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs")
mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
reconfig_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_job")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.view_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_views")
reconfig_view = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_view")
path = fixtures_dir / "update-both.yaml"
args = ["--conf", default_config_file, "update", str(path)]
execute_jenkins_jobs(args)
reconfig_job.assert_has_calls(
[mock.call(job_name, mock.ANY) for job_name in ["job-1", "job-2", "job-3"]],
any_order=True,
)
assert reconfig_job.call_count == 3
reconfig_view.assert_has_calls(
[
mock.call(view_name, mock.ANY)
for view_name in ["view-1", "view-2", "view-3"]
],
any_order=True,
)
assert reconfig_view.call_count == 3
def test_update_jobs_only(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""
Test update_job is called for project with both jobs and views
but only jobs update is requested
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs")
mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
reconfig_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_job")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.view_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_views")
reconfig_view = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_view")
path = fixtures_dir / "update-both.yaml"
args = ["--conf", default_config_file, "update", "--jobs-only", str(path)]
execute_jenkins_jobs(args)
assert reconfig_job.call_count == 3
assert reconfig_view.call_count == 0
def test_update_views_only(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""
Test update_job is called for project with both jobs and views
but only views update is requested
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs")
reconfig_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_job")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.view_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_views")
reconfig_view = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_view")
path = fixtures_dir / "update-both.yaml"
args = ["--conf", default_config_file, "update", "--views-only", str(path)]
execute_jenkins_jobs(args)
assert reconfig_job.call_count == 0
assert reconfig_view.call_count == 3
def test_update_views_and_delete_old(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""Test update behaviour with --delete-old option for views."""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.view_exists")
jenkins_get_all_views = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_views"
)
jenkins_reconfig_view = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.reconfig_view"
)
jenkins_delete_view = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.delete_view"
)
yaml_views = ["view-1", "view-2", "view-3"]
extra_managed_views = ["old-view-1", "old-view-2"]
unmanaged_views = ["unmanaged"]
path = fixtures_dir / "update-views.yaml"
args = ["--conf", default_config_file, "update", "--delete-old", str(path)]
jenkins_get_all_views.return_value = [
{"name": name} for name in yaml_views + extra_managed_views + unmanaged_views
]
mocker.patch(
"jenkins_jobs.builder.JenkinsManager.is_managed_view",
side_effect=(lambda name: name not in unmanaged_views),
)
execute_jenkins_jobs(args)
jenkins_reconfig_view.assert_has_calls(
[mock.call(view_name, mock.ANY) for view_name in yaml_views], any_order=True
)
calls = [mock.call(name) for name in extra_managed_views]
jenkins_delete_view.assert_has_calls(calls)
assert jenkins_delete_view.call_count == len(calls)
def test_update_views_and_delete_old_jobs_only(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""Test update behaviour with --delete-old option
with --jobs-only option specified.
No views should be deleted or updated.
"""
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
jenkins_get_all_jobs = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs"
)
jenkins_reconfig_job = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.reconfig_job"
)
jenkins_delete_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.delete_job")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.view_exists")
jenkins_get_all_views = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_views"
)
jenkins_reconfig_view = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.reconfig_view"
)
jenkins_delete_view = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.delete_view"
)
yaml_jobs = ["job-1", "job-2", "job-3"]
extra_managed_jobs = ["old-job-1", "old-job-2"]
unmanaged_jobs = ["unmanaged-job"]
yaml_views = ["view-1", "view-2", "view-3"]
extra_managed_views = ["old-view-1", "old-view-2"]
unmanaged_views = ["unmanaged-view"]
path = fixtures_dir / "update-both.yaml"
args = [
"--conf",
default_config_file,
"update",
"--delete-old",
"--jobs-only",
str(path),
]
jenkins_get_all_jobs.return_value = [
{"fullname": name} for name in yaml_jobs + extra_managed_jobs + unmanaged_jobs
]
jenkins_get_all_views.return_value = [
{"name": name} for name in yaml_views + extra_managed_views + unmanaged_views
]
mocker.patch(
"jenkins_jobs.builder.JenkinsManager.is_managed_job",
side_effect=(lambda name: name not in unmanaged_jobs),
)
mocker.patch(
"jenkins_jobs.builder.JenkinsManager.is_managed_view",
side_effect=(lambda name: name not in unmanaged_views),
)
execute_jenkins_jobs(args)
assert jenkins_reconfig_job.call_count == 3
assert jenkins_delete_job.call_count == 2
assert jenkins_reconfig_view.call_count == 0
assert jenkins_delete_view.call_count == 0
def test_update_jobs_fix_disabled(
mocker, fixtures_dir, default_config_file, execute_jenkins_jobs
):
"""
Test update_job fix_disabled
"""
# Case 1: Explicitly specifying disabled as True or False will skip the fix_disabled method.
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.job_exists")
mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.get_all_jobs")
jenkins_get_job_info = mocker.patch(
"jenkins_jobs.builder.jenkins.Jenkins.get_job_info",
return_value={"disabled": False},
)
reconfig_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.reconfig_job")
path = fixtures_dir / "cmd-002.yaml"
args = ["--conf", default_config_file, "update", str(path)]
execute_jenkins_jobs(args)
jenkins_get_job_info.assert_has_calls(
[mock.call(job_name) for job_name in ["bar001"]],
any_order=True,
)
assert jenkins_get_job_info.call_count == 1
# Case 2: allow disabled to remain the last setting
reconfig_job.reset_mock()
def check_disabled_value(xml_str, expected_value):
import xml.etree.ElementTree as XML
xml = XML.fromstring(
xml_str,
)
disabled = xml.find("disabled")
if expected_value is None:
assert disabled is None
else:
assert disabled is not None
assert disabled.text == str(expected_value).lower()
path = fixtures_dir / "cmd-001.yaml"
args = ["--conf", default_config_file, "update", str(path)]
jenkins_get_job_info.return_value = {}
execute_jenkins_jobs(args)
check_disabled_value(reconfig_job.call_args[0][1], False)
jenkins_get_job_info.return_value = {"disabled": False}
execute_jenkins_jobs(args)
check_disabled_value(reconfig_job.call_args[0][1], False)
jenkins_get_job_info.return_value = {"disabled": True}
execute_jenkins_jobs(args)
check_disabled_value(reconfig_job.call_args[0][1], True)
# Case 3: if job does not exist, then the value of disabled depends on the job definition
mocker.patch("jenkins_jobs.builder.JenkinsManager.is_job", return_value=False)
create_job = mocker.patch("jenkins_jobs.builder.jenkins.Jenkins.create_job")
path = fixtures_dir / "cmd-002.yaml"
args = ["--conf", default_config_file, "update", str(path)]
create_job.reset_mock()
execute_jenkins_jobs(args)
expected_values = {
"bar001": None,
"bar002": False,
"baz001": False,
"bam001": True,
}
assert create_job.call_count == 4
for call in create_job.call_args_list:
check_disabled_value(call[0][1], expected_values[call[0][0]])
@pytest.mark.skip(reason="TODO: Develop actual update timeout test approach.")
def test_update_timeout_not_set():
"""Validate update timeout behavior when timeout not explicitly configured."""
pass
@pytest.mark.skip(reason="TODO: Develop actual update timeout test approach.")
def test_update_timeout_set():
"""Validate update timeout behavior when timeout is explicitly configured."""
pass