~jamesodhunt/ubuntu/wily/ubuntu-core-upgrader/call-upgrader-directly

« back to all changes in this revision

Viewing changes to functional/test_upgrader.py

  • Committer: James Hunt
  • Date: 2015-03-04 13:53:01 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.hunt@ubuntu.com-20150304135301-plhlzn0o7x1be975
* Moved common test code into ubuntucoreupgrader/tests/utils.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
import os
26
26
import logging
27
27
import tarfile
28
 
import tempfile
29
28
import unittest
30
29
import shutil
31
30
 
36
35
    parse_args,
37
36
)
38
37
 
39
 
 
40
 
script_name = os.path.basename(__file__)
41
 
 
42
 
# file mode to use for creating test directories.
43
 
TEST_DIR_MODE = 0o750
 
38
base_dir = os.path.abspath(os.path.dirname(__file__))
 
39
module_dir = os.path.normpath(os.path.realpath(base_dir + os.sep + '..'))
 
40
sys.path.append(base_dir)
 
41
 
 
42
from ubuntucoreupgrader.tests.utils import *
44
43
 
45
44
CMD_FILE = 'ubuntu_command'
46
45
 
47
46
 
48
 
def mock_get_root_partitions_by_label():
49
 
    matches = []
50
 
 
51
 
    matches.append(('system-a', '/dev/sda3', '/'))
52
 
    matches.append(('system-b', '/dev/sda4', '/writable/cache/system'))
53
 
 
54
 
    return matches
55
 
 
56
 
 
57
 
def mock_make_mount_private(target):
58
 
    pass
59
 
 
60
 
 
61
47
def call_upgrader(command_file, root_dir, update):
62
48
    '''
63
49
    Invoke the upgrader.
94
80
    shutil.rmtree(cache_dir)
95
81
 
96
82
 
97
 
def make_tmp_dir(tag=None):
98
 
    '''
99
 
    Create a temporary directory.
100
 
    '''
101
 
 
102
 
    prefix = '{}-{}-'.format(script_name, tag) \
103
 
        if tag else script_name
104
 
 
105
 
    return tempfile.mkdtemp(prefix=prefix)
106
 
 
107
 
 
108
 
def append_file(path, contents):
109
 
    '''
110
 
    Append to a regular file (create it doesn't exist).
111
 
    '''
112
 
 
113
 
    dirname = os.path.dirname(path)
114
 
    os.makedirs(dirname, mode=TEST_DIR_MODE, exist_ok=True)
115
 
 
116
 
    with open(path, 'a') as fh:
117
 
        fh.writelines(contents)
118
 
 
119
 
        if not contents.endswith('\n'):
120
 
            fh.write('\n')
121
 
 
122
 
 
123
 
def create_file(path, contents):
124
 
    '''
125
 
    Create a regular file.
126
 
    '''
127
 
    append_file(path, contents)
128
 
 
129
 
 
130
83
def create_device_file(path, type='c', major=-1, minor=-1):
131
84
    '''
132
85
    Create a device file.
233
186
    return lines
234
187
 
235
188
 
236
 
class UpdateTree():
237
 
    '''
238
 
    Representation of a directory tree that will be converted into an
239
 
    update archive.
240
 
    '''
241
 
    TEST_REMOVED_FILE = 'removed'
242
 
    TEST_SYSTEM_DIR = 'system/'
243
 
 
244
 
    def __init__(self):
245
 
 
246
 
        # Directory tree used to construct the tar file from.
247
 
        # Also used to hold the TEST_REMOVED_FILE file.
248
 
        self.dir = make_tmp_dir(tag='UpdateTree-tar-source')
249
 
 
250
 
        self.removed_file = os.path.join(self.dir, self.TEST_REMOVED_FILE)
251
 
 
252
 
        # Directory to place create/modify files into.
253
 
        self.system_dir = os.path.join(self.dir, self.TEST_SYSTEM_DIR)
254
 
 
255
 
        # Directory used to write the generated tarfile to.
256
 
        # This directory should also be used to write the command file
257
 
        # to.
258
 
        self.tmp_dir = make_tmp_dir(tag='UpdateTree-cache')
259
 
 
260
 
    def destroy(self):
261
 
        if os.path.exists(self.dir):
262
 
            shutil.rmtree(self.dir)
263
 
 
264
 
        if os.path.exists(self.tmp_dir):
265
 
            shutil.rmtree(self.tmp_dir)
266
 
 
267
 
    def add_to_removed_file(self, removed_files):
268
 
        '''
269
 
        Add the specified list of files to the removed file.
270
 
 
271
 
        The 'removed' file is simply a file with a well-known name that
272
 
        contains a list of files (one per line) to be removed from a
273
 
        system before the rest of the update archive is unpacked.
274
 
 
275
 
        :param removed_files: list of file names to add to the removed file.
276
 
 
277
 
        '''
278
 
        # all files listed in the removed list must be system files
279
 
        final = list(map(lambda a:
280
 
                     '{}{}'.format(self.TEST_SYSTEM_DIR, a), removed_files))
281
 
 
282
 
        contents = "".join(final)
283
 
        append_file(self.removed_file, contents)
284
 
 
285
 
    def tar_filter(self, member):
286
 
        '''
287
 
        Function to filter the tarinfo members before creating the
288
 
        archive.
289
 
        '''
290
 
        # members are created with relative paths (no leading slash)
291
 
        path = os.sep + member.name
292
 
 
293
 
        if member.name == '/.':
294
 
            return None
295
 
 
296
 
        i = path.find(self.dir)
297
 
        assert(i == 0)
298
 
 
299
 
        # remove the temporary directory elements
300
 
        # (+1 for the os.sep we added above)
301
 
        member.name = path[len(self.dir)+1:]
302
 
 
303
 
        return member
304
 
 
305
 
    def create_archive(self, name):
306
 
        '''
307
 
        Create an archive with the specified name from the UpdateTree
308
 
        object. Also creates a fake signature file alongside the archive
309
 
        file since this is currently required by the upgrader (although
310
 
        it is not validated).
311
 
 
312
 
        :param name: name of tarfile.
313
 
        :param name: full path to xz archive to create.
314
 
        :return full path to tar file with name @name.
315
 
        '''
316
 
 
317
 
        self.tar_path = os.path.join(self.tmp_dir, name)
318
 
        tar = tarfile.open(self.tar_path, 'w:xz')
319
 
 
320
 
        # We can't just add recursively since that would attempt to add
321
 
        # the parent directory. However, the real update tars don't
322
 
        # include that, and attempting to ignore the parent directory
323
 
        # results in an empty archive. So, walk the tree and add
324
 
        # file-by-file.
325
 
        for path, names, files in os.walk(self.dir):
326
 
            for file in files:
327
 
                full = os.path.join(path, file)
328
 
                tar.add(full, recursive=False, filter=self.tar_filter)
329
 
            if not files and not names:
330
 
                # add (empty) directories
331
 
                tar.add(path, recursive=False, filter=self.tar_filter)
332
 
 
333
 
        tar.close()
334
 
 
335
 
        signature = '{}.asc'.format(self.tar_path)
336
 
 
337
 
        with open(signature, 'w') as fh:
338
 
            fh.write('fake signature file')
339
 
 
340
 
        return self.tar_path
341
 
 
342
 
 
343
 
class UbuntuCoreUpgraderTestCase(unittest.TestCase):
344
 
    '''
345
 
    Base class for Upgrader tests.
346
 
 
347
 
    Most of the tests follow a standard pattern:
348
 
 
349
 
    1) Create an UpdateTree object:
350
 
 
351
 
         update = UpdateTree()
352
 
 
353
 
       This creates 2 temporary directories:
354
 
 
355
 
       - self.system dir: Used as to generate an update archive from.
356
 
 
357
 
       - self.tmp_dir: Used to write the generated archive file to. The
358
 
         intention is that this directory should also be used to hold
359
 
         the command file.
360
 
 
361
 
    2) Removal tests call update.add_to_removed_file(file) to add a
362
 
       particular file to the removals file in the update archive.
363
 
 
364
 
    3) Create/Modify tests create files below update.system_dir.
365
 
 
366
 
    4) Create an update archive (which includes the removals file
367
 
       and all files below update.system_dir):
368
 
 
369
 
         archive = update.create_archive(self.TARFILE)
370
 
 
371
 
    5) Create a command file (which tells the upgrader what to do
372
 
       and which archive files to apply):
373
 
 
374
 
         make_command_file(...)
375
 
 
376
 
    6) Create a victim directory. This is a temporary directory where
377
 
       the upgrade will happen.
378
 
 
379
 
    7) Start the upgrade:
380
 
 
381
 
         call_upgrader(...)
382
 
 
383
 
    8) Perform checks on the victim directory to ensure that upgrade
384
 
       did what was expected.
385
 
 
386
 
    '''
387
 
 
388
 
    TARFILE = 'update.tar.xz'
389
 
 
390
 
    # result of last test run. Hack to deal with fact that even if a
391
 
    # test fails, unittest still calls .tearDown() (whomever thought
392
 
    # that was a good idea...?)
393
 
    currentResult = None
394
 
 
395
 
    def setUp(self):
396
 
        '''
397
 
        Test setup.
398
 
        '''
399
 
        # Create an object to hold the tree that will be converted into
400
 
        # an upgrade archive.
401
 
        self.update = UpdateTree()
402
 
 
403
 
        # The directory which will have the update archive applied to
404
 
        # it.
405
 
        self.victim_dir = make_tmp_dir(tag='victim')
406
 
 
407
 
        self.patch_get_root_partitions_by_label = \
408
 
            patch('ubuntucoreupgrader.upgrader.get_root_partitions_by_label',
409
 
                  mock_get_root_partitions_by_label)
410
 
 
411
 
        self.patch_make_mount_private = \
412
 
            patch('ubuntucoreupgrader.upgrader.make_mount_private',
413
 
                  mock_make_mount_private)
414
 
 
415
 
        self.mock_get_root_partitions_by_label = \
416
 
            self.patch_get_root_partitions_by_label.start()
417
 
 
418
 
        self.mock_make_mount_private = \
419
 
            self.patch_make_mount_private.start()
420
 
 
421
 
    def tearDown(self):
422
 
        '''
423
 
        Test cleanup.
424
 
        '''
425
 
 
426
 
        if not self.currentResult.wasSuccessful():
427
 
            # Do not clean up - the only sane option if a test fails.
428
 
            return
429
 
 
430
 
        self.update.destroy()
431
 
        self.update = None
432
 
 
433
 
        for d in (self.victim_dir,):
434
 
            shutil.rmtree(d)
435
 
        self.victim_dir = None
436
 
 
437
 
        self.patch_get_root_partitions_by_label.stop()
438
 
        self.patch_make_mount_private.stop()
439
 
 
440
 
    def run(self, result=None):
441
 
        self.currentResult = result
442
 
        unittest.TestCase.run(self, result)
443
 
 
444
 
 
445
189
class UpgraderFileRemovalTestCase(UbuntuCoreUpgraderTestCase):
446
190
    '''
447
191
    Test how the upgrader handles the removals file.