cassandra/.build/run-ci.d/run-ci-test.py

183 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
#
# Used to test `.build/run-ci`
# Run with `python .build/run-ci.d/run-ci-test.py`
#
#
# lint with:
# `pylint --disable=C0301,W0511,C0114,C0103,W0702,C0415,C0116,C0115,R0914,W0603,R0915,R0913,R0911 run-ci-test.py`
import argparse
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch, MagicMock
# Import the functions from the script
from run_ci import (
DEPLOY_YAML,
debug,
install_jenkins,
get_jenkins,
trigger_jenkins_build,
spin_while,
delete_remote_junit_files,
cleanup_and_maybe_teardown,
helm_installation_lock,
)
class TestCIPipeline(unittest.TestCase):
def setUp(self):
print("\ntesting ", self._testMethodName)
@patch('run_ci.os.environ.get')
@patch('run_ci.print')
def test_debug(self, mock_print, mock_get):
mock_get.return_value = "1"
debug("Test message")
mock_print.assert_called_with("Test message")
# the pre-flight check is nested inside install_jenkins, so it is exercised through it: the mocked
# `helm get values` stdout stands in for the values the site already has deployed
LIVE_STORAGE_CLASS = "persistence:\n storageClass: gp2\n"
@patch('run_ci.subprocess.run')
def test_install_jenkins(self, mock_run):
# empty stdout, i.e. nothing deployed yet, so the pre-flight check has nothing to warn about
mock_run.return_value = MagicMock(returncode=0, stdout="")
install_jenkins("test-namespace", Path("/fake/cassandra/dir"), "default")
mock_run.assert_any_call(["helm", "repo", "add", "jenkins", "https://charts.jenkins.io"], check=True)
mock_run.assert_any_call(["helm", "repo", "update"], check=True)
@patch('run_ci.subprocess.run')
def test_install_jenkins_values_override(self, mock_run):
mock_run.return_value = MagicMock(returncode=0, stdout="")
with tempfile.NamedTemporaryFile("w", suffix=".yaml") as override:
override.write(self.LIVE_STORAGE_CLASS)
override.flush()
install_jenkins(None, None, "default", override.name)
upgrade_cmd = [c for c in [call.args[0] for call in mock_run.call_args_list] if "upgrade" in c][0]
# the site's overrides must come after, and never replace, the repo's deployment yaml
self.assertEqual(["-f", DEPLOY_YAML, "-f", override.name], upgrade_cmd[5:9])
@patch('run_ci.sys.stdin.isatty')
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_aborts_non_interactively(self, mock_run, mock_print, mock_isatty):
mock_isatty.return_value = False
mock_run.return_value = MagicMock(returncode=0, stdout=self.LIVE_STORAGE_CLASS)
with self.assertRaises(SystemExit):
install_jenkins(None, None, "default")
self.assertEqual([], [c for c in [call.args[0] for call in mock_run.call_args_list] if "upgrade" in c])
# and continues when the customisation is passed back in as an override. This also proves the merge is
# per key: were the override to replace the whole persistence map, its other keys would now be reported lost
with tempfile.NamedTemporaryFile("w", suffix=".yaml") as override:
override.write(self.LIVE_STORAGE_CLASS)
override.flush()
install_jenkins(None, None, "default", override.name)
self.assertEqual(1, len([c for c in [call.args[0] for call in mock_run.call_args_list] if "upgrade" in c]))
@patch('run_ci.input')
@patch('run_ci.sys.stdin.isatty')
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_prompts(self, mock_run, mock_print, mock_isatty, mock_input):
mock_isatty.return_value = True
mock_run.return_value = MagicMock(returncode=0, stdout=self.LIVE_STORAGE_CLASS)
mock_input.return_value = "n"
with self.assertRaises(SystemExit):
install_jenkins(None, None, "default")
mock_input.return_value = "y"
install_jenkins(None, None, "default")
@patch('run_ci.sys.stdin.isatty')
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_reports_only_detectable_losses(self, mock_run, mock_print, mock_isatty):
mock_isatty.return_value = False
# a plugin only this site installs is reported, as is a key the site alone holds; a key held in both but
# locally edited (persistence.size) cannot be seen, and must not be claimed
mock_run.return_value = MagicMock(returncode=0, stdout="persistence:\n size: 1Ti\n"
"controller:\n installPlugins:\n - site-only-plugin\n")
with self.assertRaises(SystemExit):
install_jenkins(None, None, "default")
printed = " ".join(str(call.args[0]) for call in mock_print.call_args_list if call.args)
self.assertIn("controller.installPlugins[]", printed)
self.assertIn("site-only-plugin", printed)
self.assertNotIn("persistence.size", printed)
@patch('run_ci.print')
@patch('run_ci.subprocess.run')
def test_install_jenkins_when_nothing_deployed(self, mock_run, mock_print):
# `helm get values` fails when there is no release, and nothing is then warned about
mock_run.side_effect = lambda cmd, **kwargs: MagicMock(returncode=1 if "get" in cmd else 0,
stdout="", stderr="release: not found")
install_jenkins(None, None, "default")
self.assertEqual([], [call.args[0] for call in mock_print.call_args_list if "WARNING" in str(call.args)])
@patch('run_ci.subprocess.run')
@patch('run_ci.jenkins.Jenkins')
def test_get_jenkins(self, mock_jenkins, mock_run):
mock_k8s_client = MagicMock()
mock_run.return_value = MagicMock(stdout="fake-password")
mock_jenkins_instance = MagicMock()
mock_jenkins.return_value = mock_jenkins_instance
# hack use False values instead of None
args = argparse.Namespace(kubeconfig="/fake/kubeconfig", kubecontext="test-context", user=False, url=False)
_, server = get_jenkins(mock_k8s_client, args, "default")
self.assertEqual(server, mock_jenkins_instance)
@patch('run_ci.jenkins.Jenkins.build_job')
@patch('run_ci.wait_for_build_number')
def test_trigger_jenkins_build(self, mock_wait_for_build_number, mock_build_job):
mock_server = MagicMock()
mock_build_job.return_value = mock_server.build_job.return_value = 123
mock_wait_for_build_number.return_value = 456
with patch('run_ci.spin_while', side_effect=lambda msg, condition: 0):
queue_item = trigger_jenkins_build(mock_server, "test-job", param1="value1")
self.assertEqual(queue_item, 123)
def test_spin_while(self):
result = spin_while("Testing", lambda: True)
self.assertEqual(result, 0)
@patch('run_ci.stream.stream')
def test_delete_remote_junit_files(self, mock_stream):
mock_k8s_client = MagicMock()
delete_remote_junit_files(mock_k8s_client, "test-pod", "test-namespace", "test-job", 456)
mock_stream.assert_called()
@patch('run_ci.subprocess.run')
def test_cleanup_and_maybe_teardown(self, mock_run):
cleanup_and_maybe_teardown(None, None, "test-namespace", True)
mock_run.assert_called_with(["helm", "--namespace", "test-namespace", "uninstall", "cassius"],
capture_output=False, text=True, check=True)
@patch('run_ci.fcntl.flock')
def test_helm_installation_lock(self, mock_flock):
with helm_installation_lock(Path("/tmp/.fake.lock")):
mock_flock.assert_called()
if __name__ == '__main__':
unittest.main()