Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions cloudinit/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,4 +704,71 @@ def test_read_conf_from_cmdline_config(self, expected_cfg, cmdline):
assert expected_cfg == util.read_conf_from_cmdline(cmdline=cmdline)


class TestMountCb:
"""Tests for ``util.mount_cb``.

These tests consider the "unit" under test to be ``util.mount_cb`` and
``util.unmounter``, which is only used by ``mount_cb``.

TODO: Test default mtype determination
TODO: Test the if/else branch that actually performs the mounting operation
"""

@pytest.yield_fixture
def already_mounted_device_and_mountdict(self):
"""Mock an already-mounted device, and yield (device, mount dict)"""
device = "/dev/fake0"
mountpoint = "/mnt/fake"
with mock.patch("cloudinit.util.subp.subp"):
with mock.patch("cloudinit.util.mounts") as m_mounts:
mounts = {device: {"mountpoint": mountpoint}}
m_mounts.return_value = mounts
yield device, mounts[device]

@pytest.fixture
def already_mounted_device(self, already_mounted_device_and_mountdict):
"""already_mounted_device_and_mountdict, but return only the device"""
return already_mounted_device_and_mountdict[0]

@pytest.mark.parametrize("invalid_mtype", [int(0), float(0.0), dict()])
def test_typeerror_raised_for_invalid_mtype(self, invalid_mtype):
with pytest.raises(TypeError):
util.mount_cb(mock.Mock(), mock.Mock(), mtype=invalid_mtype)

@mock.patch("cloudinit.util.subp.subp")
def test_already_mounted_does_not_mount_or_umount_anything(
self, m_subp, already_mounted_device
):
util.mount_cb(already_mounted_device, mock.Mock())

assert 0 == m_subp.call_count

@pytest.mark.parametrize("trailing_slash_in_mounts", ["/", ""])
def test_already_mounted_calls_callback(
self, trailing_slash_in_mounts, already_mounted_device_and_mountdict
):
device, mount_dict = already_mounted_device_and_mountdict
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that this is an arg in the test and it's a generator so is mock doing the functional calling? I was expecting this to end with parens but perhaps that's handled in the pytest tooling?

device, mount_dict = already_mounted_device_and_mountdict() 

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already_mounted_device_and_mountdict is a pytest fixture, specifically the one defined on line 718 in this file (and within this class). We yield a tuple there, which pytest passes in as the fixture argument (it will have first run everything up to the yield; it runs the remainder of the fixture once the test is complete, for tear-down).

If fixtures always required calling, then any tests which need to use the passed value in more than one place would end up with an additional fixture_name = fixture_name() at the top.

Note that you can have fixtures return callables: ubuntu-advantage-client's conftest.py has a couple of examples (caplog_text returns _func, FakeConfig returns a class/type object), and pytest ships the tmpdir_factory fixture.

(As a side note: I realised that this method definition isn't following the parameter order guidelines that we have laid out in HACKING.md, so I've also reversed their order.)

mountpoint = mount_dict["mountpoint"]
mount_dict["mountpoint"] += trailing_slash_in_mounts

callback = mock.Mock()
util.mount_cb(device, callback)

# The mountpoint passed to callback should always have a trailing
# slash, regardless of the input
assert [mock.call(mountpoint + "/")] == callback.call_args_list

def test_already_mounted_calls_callback_with_data(
self, already_mounted_device
):
callback = mock.Mock()
util.mount_cb(
already_mounted_device, callback, data=mock.sentinel.data
)

assert [
mock.call(mock.ANY, mock.sentinel.data)
] == callback.call_args_list


# vi: ts=4 expandtab