1
# Copyright 2014 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Tests for the `uec2roottar` script and its supporting module.."""
6
from __future__ import (
19
from subprocess import CalledProcessError
21
from maastesting.factory import factory
22
from maastesting.matchers import (
27
from maastesting.testcase import MAASTestCase
29
from provisioningserver.import_images import uec2roottar
30
from testtools.matchers import HasLength
31
from testtools.testcase import ExpectedException
34
def make_image_name(suffix='.img'):
35
"""Create an image file name (but not the actual file)."""
36
return factory.make_name('root') + suffix
39
def make_image(testcase, contents=None, suffix='.img'):
40
"""Create an image file."""
41
name = make_image_name(suffix)
42
return testcase.make_file(name=name, contents=contents)
45
def make_tarball_name(prefix='tarball'):
46
"""Create an arbitrary name for a tarball."""
47
return factory.make_name(prefix) + '.tar.gz'
50
def make_roottar_location(testcase):
51
"""Create a name for an output root tarball, in an empty directory."""
52
name = make_tarball_name('root')
53
return os.path.join(testcase.make_dir(), name)
56
def patch_is_filesystem_file(testcase, answer):
57
"""Patch `is_filesystem_file` to return the given answer."""
58
testcase.patch(uec2roottar, 'is_filesystem_file').return_value = answer
61
class TestMakeArgParser(MAASTestCase):
62
"""Tests for `make_argparser`."""
64
def test__defines_expected_options(self):
65
image = make_image(self)
66
output = make_roottar_location(self)
67
user = factory.make_name('user')
69
parser = uec2roottar.make_argparser(factory.getRandomString())
70
args = parser.parse_args([image, output, '--user', user])
84
def test__user_defaults_to_None(self):
85
parser = uec2roottar.make_argparser(factory.getRandomString())
86
args = parser.parse_args(
87
[make_image(self), make_roottar_location(self)])
88
self.assertIsNone(args.user)
91
class TestIsFilesystemFile(MAASTestCase):
92
"""Tests for `is_filesystem_file`."""
94
def test__returns_True_if_file_looks_like_filesystem(self):
95
image = make_image(self, suffix='.img')
96
self.patch(uec2roottar, 'check_output').return_value = (
97
("%s: filesystem data" % image).encode('utf-8'))
98
self.assertTrue(uec2roottar.is_filesystem_file(image))
100
def test__returns_False_for_tarball(self):
101
image = make_image(self, suffix='.tar.gz')
102
self.patch(uec2roottar, 'check_output').return_value = (
103
("%s: gzip compressed data, was ..." % image).encode('utf-8'))
104
self.assertFalse(uec2roottar.is_filesystem_file(image))
106
def test__calls_file_with_C_language_setting(self):
107
env_during_invocation = {}
109
def fake_check_output(*args, **kwargs):
110
env_during_invocation.update(os.environ)
113
self.patch(uec2roottar, 'check_output', fake_check_output)
115
uec2roottar.is_filesystem_file(make_image(self))
117
self.assertEqual('C', env_during_invocation.get('LANG'))
120
class TestExtractImageFromTarball(MAASTestCase):
121
"""Tests for `extract_image_from_tarball`."""
123
def test__extracts_image(self):
124
tarball = make_tarball_name()
125
self.patch(uec2roottar, 'check_call')
126
# Cheat: patch away extraction of the tarball, but pass a temporary
127
# directory with an image already in it. The function will think it
128
# just extracted the image from the tarball.
129
image = make_image(self)
130
working_dir = os.path.dirname(image)
132
result = uec2roottar.extract_image_from_tarball(tarball, working_dir)
135
uec2roottar.check_call,
139
'--wildcards', '*.img',
143
self.assertEqual(image, result)
145
def test__ignores_other_files(self):
146
tarball = make_tarball_name()
147
self.patch(uec2roottar, 'check_call')
148
# Make the function think that it found two files in the tarball: an
149
# image and some other file.
150
image = make_image(self)
151
working_dir = os.path.dirname(image)
152
# This other file doesn't upset things, because it doesn't look like
154
factory.make_file(working_dir)
158
uec2roottar.extract_image_from_tarball(tarball, working_dir))
160
def test__fails_if_no_image_found(self):
161
tarball = make_tarball_name()
162
self.patch(uec2roottar, 'check_call')
163
empty_dir = self.make_dir()
164
error = self.assertRaises(
165
uec2roottar.ImageFileError,
166
uec2roottar.extract_image_from_tarball, tarball, empty_dir)
168
"Tarball %s does not contain any *.img." % tarball,
171
def test__fails_if_multiple_images_found(self):
172
tarball = make_tarball_name()
173
self.patch(uec2roottar, 'check_call')
174
working_dir = self.make_dir()
176
factory.make_file(working_dir, name=make_image_name())
178
error = self.assertRaises(
179
uec2roottar.ImageFileError,
180
uec2roottar.extract_image_from_tarball, tarball, working_dir)
182
"Tarball %s contains multiple image files: %s."
183
% (tarball, ', '.join(files)),
187
class TestGetImageFile(MAASTestCase):
188
"""Tests for `get_image_file`."""
190
def test__returns_actual_image_file_unchanged(self):
191
patch_is_filesystem_file(self, True)
192
image = make_image(self)
195
uec2roottar.get_image_file(image, factory.make_name('dir')))
197
def test__extracts_tarball_into_temp_dir(self):
198
patch_is_filesystem_file(self, False)
199
tarball = make_tarball_name()
200
temp_dir = self.make_dir()
201
image = make_image_name()
202
patch = self.patch(uec2roottar, 'extract_image_from_tarball')
203
patch.return_value = image
204
result = uec2roottar.get_image_file(tarball, temp_dir)
205
self.assertEqual(image, result)
206
self.assertThat(patch, MockCalledOnceWith(tarball, temp_dir))
208
def test__rejects_other_files(self):
209
patch_is_filesystem_file(self, False)
210
filename = factory.make_name('weird-file')
211
error = self.assertRaises(
212
uec2roottar.ImageFileError,
213
uec2roottar.get_image_file, filename, factory.make_name('dir'))
215
"Expected '%s' to be either a filesystem file, or a "
216
"gzipped tarball containing one." % filename,
220
class TestUnmount(MAASTestCase):
221
"""Tests for `unmount`."""
223
def test__calls_umount(self):
224
self.patch(uec2roottar, 'check_call')
225
mountpoint = factory.make_name('mount')
226
uec2roottar.unmount(mountpoint)
228
uec2roottar.check_call,
229
MockCalledOnceWith(['umount', mountpoint]))
231
def test__propagates_failure(self):
232
failure = CalledProcessError(9, factory.make_name('delibfail'))
233
self.patch(uec2roottar, 'check_call').side_effect = failure
234
self.patch(uec2roottar, 'logger')
235
mountpoint = factory.make_name('mount')
236
self.assertRaises(CalledProcessError, uec2roottar.unmount, mountpoint)
238
uec2roottar.logger.error,
240
"Could not unmount %s: %s", mountpoint, failure))
243
class TestLoopMount(MAASTestCase):
244
"""Tests for `loop_mount`."""
246
def test__mounts_and_unmounts_image(self):
247
image = make_image_name()
248
self.patch(uec2roottar, 'check_call')
249
mountpoint = factory.make_name('mount')
251
calls_before = len(uec2roottar.check_call.mock_calls)
252
with uec2roottar.loop_mount(image, mountpoint):
253
calls_during = len(uec2roottar.check_call.mock_calls)
254
calls_after = len(uec2roottar.check_call.mock_calls)
258
(calls_before, calls_during, calls_after))
260
uec2roottar.check_call,
261
MockAnyCall(['mount', '-o', 'ro', image, mountpoint]))
263
uec2roottar.check_call,
264
MockAnyCall(['umount', mountpoint]))
266
def test__cleans_up_after_failure(self):
267
class DeliberateException(Exception):
270
self.patch(uec2roottar, 'check_call')
271
image = make_image_name()
272
mountpoint = factory.make_name('mount')
273
with ExpectedException(DeliberateException):
274
with uec2roottar.loop_mount(image, mountpoint):
275
raise DeliberateException()
278
uec2roottar.check_call, MockAnyCall(['umount', mountpoint]))
281
class TestExtractImage(MAASTestCase):
282
"""Tests for `extract_image`."""
284
def extract_command_line(self, call):
285
"""Extract the command line from a `mock.call` for `check_call`."""
290
def test__extracts_image(self):
291
image = make_image_name()
292
output = make_tarball_name()
293
self.patch(uec2roottar, 'check_call')
294
uec2roottar.extract_image(image, output)
295
self.assertThat(uec2roottar.check_call.mock_calls, HasLength(3))
296
[mount_call, tar_call, umount_call] = uec2roottar.check_call.mock_calls
297
self.assertEqual('mount', self.extract_command_line(mount_call)[0])
298
tar_command = self.extract_command_line(tar_call)
299
self.assertEqual(['tar', '-C'], tar_command[:2])
300
self.assertEqual('umount', self.extract_command_line(umount_call)[0])
303
class TestSetOwnership(MAASTestCase):
304
"""Tests for `set_ownership`."""
306
def test__does_nothing_if_no_user_specified(self):
307
self.patch(uec2roottar, 'check_call')
308
uec2roottar.set_ownership(make_tarball_name(), user=None)
309
self.assertThat(uec2roottar.check_call, MockNotCalled())
311
def test__calls_chown_if_user_specified(self):
312
self.patch(uec2roottar, 'check_call')
313
user = factory.make_name('user')
314
tarball = make_tarball_name()
315
uec2roottar.set_ownership(tarball, user=user)
317
uec2roottar.check_call,
318
MockCalledOnceWith(['/bin/chown', user, tarball]))
321
class TestUEC2RootTar(MAASTestCase):
322
"""Integration tests for `uec2roottar`."""
324
def make_args(self, **kwargs):
325
"""Fake an `argparser` arguments object."""
327
for key, value in kwargs.items():
328
setattr(args, key, value)
331
def test__integrates(self):
332
image_name = factory.make_name('root-image') + '.img'
333
image = self.make_file(name=image_name)
334
output_name = factory.make_name('root-tar') + '.tar.gz'
335
output = os.path.join(self.make_dir(), output_name)
336
args = self.make_args(image=image, output=output)
337
self.patch(uec2roottar, 'check_call')
338
patch_is_filesystem_file(self, True)
340
uec2roottar.main(args)
343
uec2roottar.is_filesystem_file, MockCalledOnceWith(image))