-
Notifications
You must be signed in to change notification settings - Fork 884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: pytestify test_main.py #5758
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,75 +1,74 @@ | ||
# This file is part of cloud-init. See LICENSE file for license information. | ||
|
||
import copy | ||
import getpass | ||
import os | ||
from collections import namedtuple | ||
from io import StringIO | ||
from unittest import mock | ||
|
||
import pytest | ||
|
||
from cloudinit import safeyaml | ||
from cloudinit.cmd import main | ||
from cloudinit.util import ensure_dir, load_text_file, write_file | ||
from tests.unittests.helpers import FilesystemMockingTestCase, wrap_and_call | ||
|
||
MyArgs = namedtuple( | ||
"MyArgs", "debug files force local reporter subcommand skip_log_setup" | ||
) | ||
|
||
|
||
class TestMain(FilesystemMockingTestCase): | ||
with_logs = True | ||
allowed_subp = False | ||
|
||
def setUp(self): | ||
super(TestMain, self).setUp() | ||
self.new_root = self.tmp_dir() | ||
self.cloud_dir = self.tmp_path("var/lib/cloud/", dir=self.new_root) | ||
os.makedirs(self.cloud_dir) | ||
self.replicateTestRoot("simple_ubuntu", self.new_root) | ||
self.cfg = { | ||
class TestMain: | ||
@pytest.fixture(autouse=True) | ||
def common_mocks(self, mocker): | ||
mocker.patch("cloudinit.cmd.main.close_stdin") | ||
mocker.patch( | ||
"cloudinit.cmd.main.netinfo.debug_info", | ||
return_value="my net debug info", | ||
) | ||
mocker.patch( | ||
"cloudinit.cmd.main.util.fixup_output", | ||
return_value=("outfmt", "errfmt"), | ||
) | ||
mocker.patch("cloudinit.cmd.main.util.get_cmdline", return_value="") | ||
mocker.patch("cloudinit.cmd.main.util.uptime", return_value="12345") | ||
os.environ["_CLOUD_INIT_SAVE_STDOUT"] = "true" | ||
yield | ||
os.environ.pop("_CLOUD_INIT_SAVE_STDOUT") | ||
|
||
@pytest.fixture | ||
def cloud_cfg(self, mocker, tmpdir, fake_filesystem): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had wondered about using our paths fixture from conftest.py here. But, because of the need to ensure_dir throughout it didn't make sense. |
||
cloud_dir = os.path.join(tmpdir, "var/lib/cloud/") | ||
log_dir = os.path.join(tmpdir, "var/log/") | ||
ensure_dir(cloud_dir) | ||
ensure_dir(os.path.join(tmpdir, "etc/cloud")) | ||
ensure_dir(log_dir) | ||
cloud_cfg_file = os.path.join(tmpdir, "etc/cloud/cloud.cfg") | ||
|
||
cfg = { | ||
"datasource_list": ["None"], | ||
# "def_log_file": os.path.join(log_dir, "cloud-init.log"), | ||
"def_log_file": "", | ||
"runcmd": ["ls /etc"], # test ALL_DISTROS | ||
"system_info": { | ||
"paths": { | ||
"cloud_dir": self.cloud_dir, | ||
"run_dir": self.new_root, | ||
"cloud_dir": cloud_dir, | ||
"run_dir": str(tmpdir), | ||
} | ||
}, | ||
"write_files": [ | ||
{ | ||
"path": "/etc/blah.ini", | ||
"path": os.path.join(tmpdir, "etc/blah.ini"), | ||
"content": "blah", | ||
"permissions": 0o755, | ||
"owner": getpass.getuser(), | ||
}, | ||
], | ||
"cloud_init_modules": ["write_files", "runcmd"], | ||
} | ||
cloud_cfg = safeyaml.dumps(self.cfg) | ||
ensure_dir(os.path.join(self.new_root, "etc", "cloud")) | ||
self.cloud_cfg_file = os.path.join( | ||
self.new_root, "etc", "cloud", "cloud.cfg" | ||
) | ||
write_file(self.cloud_cfg_file, cloud_cfg) | ||
self.patchOS(self.new_root) | ||
self.patchUtils(self.new_root) | ||
self.stderr = StringIO() | ||
self.patchStdoutAndStderr(stderr=self.stderr) | ||
# Every cc_ module calls get_meta_doc on import. | ||
# This call will fail if filesystem redirection mocks are in place | ||
# and the module hasn't already been imported which can depend | ||
# on test ordering. | ||
self.m_doc = mock.patch( | ||
"cloudinit.config.schema.get_meta_doc", return_value={} | ||
) | ||
self.m_doc.start() | ||
|
||
def tearDown(self): | ||
self.m_doc.stop() | ||
super().tearDown() | ||
write_file(cloud_cfg_file, safeyaml.dumps(cfg)) | ||
yield copy.deepcopy(cfg), cloud_cfg_file | ||
|
||
def test_main_init_run_net_runs_modules(self): | ||
def test_main_init_run_net_runs_modules(self, cloud_cfg, capsys, tmpdir): | ||
"""Modules like write_files are run in 'net' mode.""" | ||
cmdargs = MyArgs( | ||
debug=False, | ||
|
@@ -80,44 +79,32 @@ def test_main_init_run_net_runs_modules(self): | |
subcommand="init", | ||
skip_log_setup=False, | ||
) | ||
(_item1, item2) = wrap_and_call( | ||
"cloudinit.cmd.main", | ||
{ | ||
"close_stdin": True, | ||
"netinfo.debug_info": "my net debug info", | ||
"util.fixup_output": ("outfmt", "errfmt"), | ||
}, | ||
main.main_init, | ||
"init", | ||
cmdargs, | ||
) | ||
self.assertEqual([], item2) | ||
_ds, msg = main.main_init("init", cmdargs) | ||
assert msg == [] | ||
# Instancify is called | ||
instance_id_path = "var/lib/cloud/data/instance-id" | ||
self.assertEqual( | ||
"iid-datasource-none\n", | ||
os.path.join( | ||
load_text_file(os.path.join(self.new_root, instance_id_path)) | ||
), | ||
assert "iid-datasource-none\n" == os.path.join( | ||
load_text_file(os.path.join(tmpdir, instance_id_path)) | ||
) | ||
# modules are run (including write_files) | ||
self.assertEqual( | ||
"blah", load_text_file(os.path.join(self.new_root, "etc/blah.ini")) | ||
) | ||
assert "blah" == load_text_file(os.path.join(tmpdir, "etc/blah.ini")) | ||
expected_logs = [ | ||
"network config is disabled by fallback", # apply_network_config | ||
"my net debug info", # netinfo.debug_info | ||
] | ||
stderr = capsys.readouterr().err | ||
for log in expected_logs: | ||
self.assertIn(log, self.stderr.getvalue()) | ||
assert log in stderr | ||
|
||
def test_main_init_run_net_calls_set_hostname_when_metadata_present(self): | ||
def test_main_init_run_net_calls_set_hostname_when_metadata_present( | ||
self, cloud_cfg, mocker | ||
): | ||
"""When local-hostname metadata is present, call cc_set_hostname.""" | ||
self.cfg["datasource"] = { | ||
cfg, cloud_cfg_file = cloud_cfg | ||
cfg["datasource"] = { | ||
"None": {"metadata": {"local-hostname": "md-hostname"}} | ||
} | ||
cloud_cfg = safeyaml.dumps(self.cfg) | ||
write_file(self.cloud_cfg_file, cloud_cfg) | ||
write_file(cloud_cfg_file, safeyaml.dumps(cfg)) | ||
cmdargs = MyArgs( | ||
debug=False, | ||
files=None, | ||
|
@@ -129,58 +116,15 @@ def test_main_init_run_net_calls_set_hostname_when_metadata_present(self): | |
) | ||
|
||
def set_hostname(name, cfg, cloud, args): | ||
self.assertEqual("set_hostname", name) | ||
updated_cfg = copy.deepcopy(self.cfg) | ||
updated_cfg.update( | ||
{ | ||
"def_log_file": "/var/log/cloud-init.log", | ||
"log_cfgs": [], | ||
"syslog_fix_perms": [ | ||
"syslog:adm", | ||
"root:adm", | ||
"root:wheel", | ||
"root:root", | ||
], | ||
"vendor_data": {"enabled": True, "prefix": []}, | ||
"vendor_data2": {"enabled": True, "prefix": []}, | ||
} | ||
) | ||
updated_cfg.pop("system_info") | ||
|
||
self.assertEqual(updated_cfg, cfg) | ||
self.assertIsNone(args) | ||
|
||
(_item1, item2) = wrap_and_call( | ||
"cloudinit.cmd.main", | ||
{ | ||
"close_stdin": True, | ||
"netinfo.debug_info": "my net debug info", | ||
"cc_set_hostname.handle": {"side_effect": set_hostname}, | ||
"util.fixup_output": ("outfmt", "errfmt"), | ||
}, | ||
main.main_init, | ||
"init", | ||
cmdargs, | ||
) | ||
self.assertEqual([], item2) | ||
# Instancify is called | ||
instance_id_path = "var/lib/cloud/data/instance-id" | ||
self.assertEqual( | ||
"iid-datasource-none\n", | ||
os.path.join( | ||
load_text_file(os.path.join(self.new_root, instance_id_path)) | ||
), | ||
) | ||
# modules are run (including write_files) | ||
self.assertEqual( | ||
"blah", load_text_file(os.path.join(self.new_root, "etc/blah.ini")) | ||
assert "set_hostname" == name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did simplify the logic here and remove a lot of extraneous checks in the rest of the test. The goal of this test was to ensure that |
||
|
||
m_hostname = mocker.patch( | ||
"cloudinit.cmd.main.cc_set_hostname.handle", | ||
side_effect=set_hostname, | ||
) | ||
expected_logs = [ | ||
"network config is disabled by fallback", # apply_network_config | ||
"my net debug info", # netinfo.debug_info | ||
] | ||
for log in expected_logs: | ||
self.assertIn(log, self.stderr.getvalue()) | ||
main.main_init("init", cmdargs) | ||
|
||
m_hostname.assert_called_once() | ||
|
||
@mock.patch("cloudinit.cmd.clean.get_parser") | ||
@mock.patch("cloudinit.cmd.clean.handle_clean_args") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL about avoiding the stdout/err redirect.