mirror of https://github.com/apache/cassandra
183 lines
8.4 KiB
Python
183 lines
8.4 KiB
Python
#!/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()
|