summaryrefslogtreecommitdiff
path: root/tests/integration_tests/conftest.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration_tests/conftest.py')
-rw-r--r--tests/integration_tests/conftest.py182
1 files changed, 182 insertions, 0 deletions
diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py
new file mode 100644
index 00000000..73b44bfc
--- /dev/null
+++ b/tests/integration_tests/conftest.py
@@ -0,0 +1,182 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+import logging
+import os
+import pytest
+import sys
+from contextlib import contextmanager
+
+from tests.integration_tests import integration_settings
+from tests.integration_tests.clouds import (
+ Ec2Cloud,
+ GceCloud,
+ AzureCloud,
+ OciCloud,
+ LxdContainerCloud,
+ LxdVmCloud,
+)
+
+
+log = logging.getLogger('integration_testing')
+log.addHandler(logging.StreamHandler(sys.stdout))
+log.setLevel(logging.INFO)
+
+platforms = {
+ 'ec2': Ec2Cloud,
+ 'gce': GceCloud,
+ 'azure': AzureCloud,
+ 'oci': OciCloud,
+ 'lxd_container': LxdContainerCloud,
+ 'lxd_vm': LxdVmCloud,
+}
+
+
+def pytest_runtest_setup(item):
+ """Skip tests on unsupported clouds.
+
+ A test can take any number of marks to specify the platforms it can
+ run on. If a platform(s) is specified and we're not running on that
+ platform, then skip the test. If platform specific marks are not
+ specified, then we assume the test can be run anywhere.
+ """
+ all_platforms = platforms.keys()
+ test_marks = [mark.name for mark in item.iter_markers()]
+ supported_platforms = set(all_platforms).intersection(test_marks)
+ current_platform = integration_settings.PLATFORM
+ unsupported_message = 'Cannot run on platform {}'.format(current_platform)
+ if 'no_container' in test_marks:
+ if 'lxd_container' in test_marks:
+ raise Exception(
+ 'lxd_container and no_container marks simultaneously set '
+ 'on test'
+ )
+ if current_platform == 'lxd_container':
+ pytest.skip(unsupported_message)
+ if supported_platforms and current_platform not in supported_platforms:
+ pytest.skip(unsupported_message)
+
+
+# disable_subp_usage is defined at a higher level, but we don't
+# want it applied here
+@pytest.fixture()
+def disable_subp_usage(request):
+ pass
+
+
+@pytest.yield_fixture(scope='session')
+def session_cloud():
+ if integration_settings.PLATFORM not in platforms.keys():
+ raise ValueError(
+ "{} is an invalid PLATFORM specified in settings. "
+ "Must be one of {}".format(
+ integration_settings.PLATFORM, list(platforms.keys())
+ )
+ )
+
+ cloud = platforms[integration_settings.PLATFORM]()
+ cloud.emit_settings_to_log()
+ yield cloud
+ cloud.destroy()
+
+
+@pytest.fixture(scope='session', autouse=True)
+def setup_image(session_cloud):
+ """Setup the target environment with the correct version of cloud-init.
+
+ So we can launch instances / run tests with the correct image
+ """
+ client = None
+ log.info('Setting up environment for %s', session_cloud.datasource)
+ if integration_settings.CLOUD_INIT_SOURCE == 'NONE':
+ pass # that was easy
+ elif integration_settings.CLOUD_INIT_SOURCE == 'IN_PLACE':
+ if session_cloud.datasource not in ['lxd_container', 'lxd_vm']:
+ raise ValueError(
+ 'IN_PLACE as CLOUD_INIT_SOURCE only works for LXD')
+ # The mount needs to happen after the instance is created, so
+ # no further action needed here
+ elif integration_settings.CLOUD_INIT_SOURCE == 'PROPOSED':
+ client = session_cloud.launch()
+ client.install_proposed_image()
+ elif integration_settings.CLOUD_INIT_SOURCE.startswith('ppa:'):
+ client = session_cloud.launch()
+ client.install_ppa(integration_settings.CLOUD_INIT_SOURCE)
+ elif os.path.isfile(str(integration_settings.CLOUD_INIT_SOURCE)):
+ client = session_cloud.launch()
+ client.install_deb()
+ else:
+ raise ValueError(
+ 'Invalid value for CLOUD_INIT_SOURCE setting: {}'.format(
+ integration_settings.CLOUD_INIT_SOURCE))
+ if client:
+ # Even if we're keeping instances, we don't want to keep this
+ # one around as it was just for image creation
+ client.destroy()
+ log.info('Done with environment setup')
+
+
+@contextmanager
+def _client(request, fixture_utils, session_cloud):
+ """Fixture implementation for the client fixtures.
+
+ Launch the dynamic IntegrationClient instance using any provided
+ userdata, yield to the test, then cleanup
+ """
+ user_data = fixture_utils.closest_marker_first_arg_or(
+ request, 'user_data', None)
+ name = fixture_utils.closest_marker_first_arg_or(
+ request, 'instance_name', None
+ )
+ launch_kwargs = {}
+ if name is not None:
+ launch_kwargs = {"name": name}
+ with session_cloud.launch(
+ user_data=user_data, launch_kwargs=launch_kwargs
+ ) as instance:
+ yield instance
+
+
+@pytest.yield_fixture
+def client(request, fixture_utils, session_cloud):
+ """Provide a client that runs for every test."""
+ with _client(request, fixture_utils, session_cloud) as client:
+ yield client
+
+
+@pytest.yield_fixture(scope='module')
+def module_client(request, fixture_utils, session_cloud):
+ """Provide a client that runs once per module."""
+ with _client(request, fixture_utils, session_cloud) as client:
+ yield client
+
+
+@pytest.yield_fixture(scope='class')
+def class_client(request, fixture_utils, session_cloud):
+ """Provide a client that runs once per class."""
+ with _client(request, fixture_utils, session_cloud) as client:
+ yield client
+
+
+def pytest_assertrepr_compare(op, left, right):
+ """Custom integration test assertion explanations.
+
+ See
+ https://docs.pytest.org/en/stable/assert.html#defining-your-own-explanation-for-failed-assertions
+ for pytest's documentation.
+ """
+ if op == "not in" and isinstance(left, str) and isinstance(right, str):
+ # This stanza emits an improved assertion message if we're testing for
+ # the presence of a string within a cloud-init log: it will report only
+ # the specific lines containing the string (instead of the full log,
+ # the default behaviour).
+ potential_log_lines = right.splitlines()
+ first_line = potential_log_lines[0]
+ if "DEBUG" in first_line and "Cloud-init" in first_line:
+ # We are looking at a cloud-init log, so just pick out the relevant
+ # lines
+ found_lines = [
+ line for line in potential_log_lines if left in line
+ ]
+ return [
+ '"{}" not in cloud-init.log string; unexpectedly found on'
+ " these lines:".format(left)
+ ] + found_lines