Save state file in-between runs to mark which packages have already been tested on the given clouds.

sil2100/cloud-only-run-once
Łukasz 'sil2100' Zemczak 2 years ago
parent dde56aa490
commit 340b41afe1

@ -143,6 +143,9 @@ CLOUD_ENABLE = no
# A directory to store Cloud test results and logs. Is created at the start of # A directory to store Cloud test results and logs. Is created at the start of
# each policy run and deleted after test results are parsed. # each policy run and deleted after test results are parsed.
CLOUD_WORK_DIR = cloud_tests CLOUD_WORK_DIR = cloud_tests
# Path to a persistent state file, not to re-run cloud tests for packages that
# have already been tested.
CLOUD_STATE_FILE = cloud_state
# Who to notify regarding test failures # Who to notify regarding test failures
CLOUD_FAILURE_EMAILS = cpc@canonical.com CLOUD_FAILURE_EMAILS = cpc@canonical.com
# Who to notify regarding test errors # Who to notify regarding test errors

@ -50,6 +50,7 @@ Regards, Ubuntu Release Team.
""" """
class CloudPolicy(BasePolicy): class CloudPolicy(BasePolicy):
PACKAGE_SET_FILE = "cloud_package_set" PACKAGE_SET_FILE = "cloud_package_set"
STATE_FILE = "cloud_state"
DEFAULT_EMAILS = ["cpc@canonical.com"] DEFAULT_EMAILS = ["cpc@canonical.com"]
TEST_LOG_FILE = "CTF.log" TEST_LOG_FILE = "CTF.log"
@ -68,6 +69,9 @@ class CloudPolicy(BasePolicy):
self.work_dir = getattr(self.options, "cloud_work_dir", "cloud_tests") self.work_dir = getattr(self.options, "cloud_work_dir", "cloud_tests")
self.failure_emails = getattr(self.options, "cloud_failure_emails", self.DEFAULT_EMAILS) self.failure_emails = getattr(self.options, "cloud_failure_emails", self.DEFAULT_EMAILS)
self.error_emails = getattr(self.options, "cloud_error_emails", self.DEFAULT_EMAILS) self.error_emails = getattr(self.options, "cloud_error_emails", self.DEFAULT_EMAILS)
self.state_filename = getattr(self.options, "cloud_state_file", self.STATE_FILE)
self.state = {}
adt_ppas = getattr(self.options, "adt_ppas", "") adt_ppas = getattr(self.options, "adt_ppas", "")
if not isinstance(adt_ppas, list): if not isinstance(adt_ppas, list):
@ -88,6 +92,7 @@ class CloudPolicy(BasePolicy):
super().initialise(britney) super().initialise(britney)
self.package_set = self._retrieve_cloud_package_set_for_series(self.options.series) self.package_set = self._retrieve_cloud_package_set_for_series(self.options.series)
self._load_state()
def apply_src_policy_impl(self, policy_info, item, source_data_tdist, source_data_srcdist, excuse): def apply_src_policy_impl(self, policy_info, item, source_data_tdist, source_data_srcdist, excuse):
if item.package not in self.package_set: if item.package not in self.package_set:
@ -103,7 +108,8 @@ class CloudPolicy(BasePolicy):
self.failures = {} self.failures = {}
self.errors = {} self.errors = {}
self._run_cloud_tests(item.package, self.options.series, self.sources, self.source_type) self._run_cloud_tests(item.package, source_data_srcdist.version, self.options.series,
self.sources, self.source_type)
if len(self.failures) > 0 or len(self.errors) > 0: if len(self.failures) > 0 or len(self.errors) > 0:
self._send_emails_if_needed(item.package, source_data_srcdist.version, self.options.series) self._send_emails_if_needed(item.package, source_data_srcdist.version, self.options.series)
@ -117,6 +123,54 @@ class CloudPolicy(BasePolicy):
self._cleanup_work_directory() self._cleanup_work_directory()
return PolicyVerdict.PASS return PolicyVerdict.PASS
def _mark_tests_run(self, package, version, series, source_type, cloud):
"""Mark the selected package version as already tested.
This takes which cloud we're testing into consideration.
:param package The name of the package to test
:param version Version of the package
:param series The Ubuntu codename for the series (e.g. jammy)
:param source_type Either 'archive' or 'ppa'
:param cloud The name of the cloud being tested (e.g. azure)
"""
if cloud not in self.state:
self.state[cloud] = {}
if source_type not in self.state[cloud]:
self.state[cloud][source_type] = {}
if series not in self.state[cloud][source_type]:
self.state[cloud][source_type][series] = {}
self.state[cloud][source_type][series][package] = version
self._save_state()
def _check_if_tests_run(self, package, version, series, source_type, cloud):
"""Check if tests were already run for the given package version.
This takes which cloud we're testing into consideration.
:param package The name of the package to test
:param version Version of the package
:param series The Ubuntu codename for the series (e.g. jammy)
:param source_type Either 'archive' or 'ppa'
:param cloud The name of the cloud being tested (e.g. azure)
"""
try:
return self.state[cloud][source_type][series][package] == version
except KeyError:
return False
def _load_state(self):
"""Load the save state of which packages have already been tested."""
if os.path.exists(self.state_filename):
with open(self.state_filename, encoding="utf-8") as data:
self.state = json.load(data)
self.logger.info("Loaded cloud policy state file %s" % self.state_filename)
def _save_state(self):
"""Save which packages have already been tested."""
with open(self.state_filename, "w", encoding="utf-8") as data:
json.dump(self.state, data)
self.logger.info("Saved cloud policy state file %s" % self.state_filename)
def _retrieve_cloud_package_set_for_series(self, series): def _retrieve_cloud_package_set_for_series(self, series):
"""Retrieves a set of packages for the given series in which cloud """Retrieves a set of packages for the given series in which cloud
tests should be run. tests should be run.
@ -134,16 +188,17 @@ class CloudPolicy(BasePolicy):
return package_set return package_set
def _run_cloud_tests(self, package, series, sources, source_type): def _run_cloud_tests(self, package, version, series, sources, source_type):
"""Runs any cloud tests for the given package. """Runs any cloud tests for the given package.
Nothing is returned but test failures and errors are stored in instance variables. Nothing is returned but test failures and errors are stored in instance variables.
:param package The name of the package to test :param package The name of the package to test
:param version Version of the package
:param series The Ubuntu codename for the series (e.g. jammy) :param series The Ubuntu codename for the series (e.g. jammy)
:param sources List of sources where the package should be installed from (e.g. [proposed] or PPAs) :param sources List of sources where the package should be installed from (e.g. [proposed] or PPAs)
:param source_type Either 'archive' or 'ppa' :param source_type Either 'archive' or 'ppa'
""" """
self._run_azure_tests(package, series, sources, source_type) self._run_azure_tests(package, version, series, sources, source_type)
def _send_emails_if_needed(self, package, version, series): def _send_emails_if_needed(self, package, version, series):
"""Sends email(s) if there are test failures and/or errors """Sends email(s) if there are test failures and/or errors
@ -168,14 +223,18 @@ class CloudPolicy(BasePolicy):
self.logger.info("Cloud Policy: Sending error email for {}, to {}".format(package, emails)) self.logger.info("Cloud Policy: Sending error email for {}, to {}".format(package, emails))
self._send_email(emails, message) self._send_email(emails, message)
def _run_azure_tests(self, package, series, sources, source_type): def _run_azure_tests(self, package, version, series, sources, source_type):
"""Runs Azure's required package tests. """Runs Azure's required package tests.
:param package The name of the package to test :param package The name of the package to test
:param version Version of the package
:param series The Ubuntu codename for the series (e.g. jammy) :param series The Ubuntu codename for the series (e.g. jammy)
:param sources List of sources where the package should be installed from (e.g. [proposed] or PPAs) :param sources List of sources where the package should be installed from (e.g. [proposed] or PPAs)
:param source_type Either 'archive' or 'ppa' :param source_type Either 'archive' or 'ppa'
""" """
if self._check_if_tests_run(package, version, series, source_type, "azure"):
return
urn = self._retrieve_urn(series) urn = self._retrieve_urn(series)
self.logger.info("Cloud Policy: Running Azure tests for: {} in {}".format(package, series)) self.logger.info("Cloud Policy: Running Azure tests for: {} in {}".format(package, series))
@ -204,6 +263,7 @@ class CloudPolicy(BasePolicy):
results_file_paths = self._find_results_files(r"TEST-NetworkTests-[0-9]*.xml") results_file_paths = self._find_results_files(r"TEST-NetworkTests-[0-9]*.xml")
self._parse_xunit_test_results("Azure", results_file_paths) self._parse_xunit_test_results("Azure", results_file_paths)
self._store_extra_test_result_info(self, package) self._store_extra_test_result_info(self, package)
self._mark_tests_run(package, version, series, source_type, "azure")
def _retrieve_urn(self, series): def _retrieve_urn(self, series):
"""Retrieves an URN from the configuration options based on series. """Retrieves an URN from the configuration options based on series.

@ -7,6 +7,7 @@
# (at your option) any later version. # (at your option) any later version.
import os import os
import json
import pathlib import pathlib
import sys import sys
from types import SimpleNamespace from types import SimpleNamespace
@ -35,7 +36,8 @@ class T(unittest.TestCase):
verbose = False, verbose = False,
cloud_source = "zazzy-proposed", cloud_source = "zazzy-proposed",
cloud_source_type = "archive", cloud_source_type = "archive",
cloud_azure_zazzy_urn = "fake-urn-value" cloud_azure_zazzy_urn = "fake-urn-value",
cloud_state_file = "/tmp/test_state.json"
) )
self.policy = CloudPolicy(self.fake_options, {}) self.policy = CloudPolicy(self.fake_options, {})
self.policy._setup_work_directory() self.policy._setup_work_directory()
@ -43,6 +45,46 @@ class T(unittest.TestCase):
def tearDown(self): def tearDown(self):
self.policy._cleanup_work_directory() self.policy._cleanup_work_directory()
@patch("britney2.policies.cloud.CloudPolicy._store_extra_test_result_info")
@patch("britney2.policies.cloud.CloudPolicy._parse_xunit_test_results")
@patch("subprocess.run")
def test_run_cloud_tests_state_handling(self, mock_run, mock_xunit, mock_extra):
"""Cloud tests should save state and not re-run tests for packages
already tested."""
expected_state = {
"azure": {
"archive": {
"zazzy": {
"chromium-browser": "55.0"
}
}
}
}
with open(self.policy.options.cloud_state_file, "w") as file:
json.dump(expected_state, file)
self.policy._load_state()
# Package already tested, no tests should run
self.policy._run_cloud_tests("chromium-browser", "55.0", "zazzy", ["proposed"], "archive")
self.assertDictEqual(expected_state, self.policy.state)
mock_run.assert_not_called()
# A new package appears, tests should run
expected_state["azure"]["archive"]["zazzy"]["hello"] = "2.10"
self.policy._run_cloud_tests("hello", "2.10", "zazzy", ["proposed"], "archive")
self.assertDictEqual(expected_state, self.policy.state)
mock_run.assert_called()
# A new version of existing package, tests should run
expected_state["azure"]["archive"]["zazzy"]["chromium-browser"] = "55.1"
self.policy._run_cloud_tests("chromium-browser", "55.1", "zazzy", ["proposed"], "archive")
self.assertDictEqual(expected_state, self.policy.state)
self.assertEqual(mock_run.call_count, 2)
# Make sure the state was saved properly
with open(self.policy.options.cloud_state_file, "r") as file:
self.assertDictEqual(expected_state, json.load(file))
@patch("britney2.policies.cloud.CloudPolicy._run_cloud_tests") @patch("britney2.policies.cloud.CloudPolicy._run_cloud_tests")
def test_run_cloud_tests_called_for_package_in_manifest(self, mock_run): def test_run_cloud_tests_called_for_package_in_manifest(self, mock_run):
"""Cloud tests should run for a package in the cloud package set. """Cloud tests should run for a package in the cloud package set.
@ -55,7 +97,7 @@ class T(unittest.TestCase):
) )
mock_run.assert_called_once_with( mock_run.assert_called_once_with(
"chromium-browser", "jammy", ["proposed"], "archive" "chromium-browser", "55.0", "jammy", ["proposed"], "archive"
) )
@patch("britney2.policies.cloud.CloudPolicy._run_cloud_tests") @patch("britney2.policies.cloud.CloudPolicy._run_cloud_tests")

Loading…
Cancel
Save