1fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan# Use of this source code is governed by a BSD-style license that can be
3fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan# found in the LICENSE file.
4fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
5fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanimport logging
6fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanimport os
7cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chanimport shutil
8fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanimport zipfile
9fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
10fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanfrom autotest_lib.client.bin import test
11fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanfrom autotest_lib.client.common_lib import autotemp, error
12fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanfrom autotest_lib.client.cros.cros_disks import CrosDisksTester
13fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanfrom autotest_lib.client.cros.cros_disks import VirtualFilesystemImage
14fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanfrom autotest_lib.client.cros.cros_disks import DefaultFilesystemTestContent
15fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanfrom collections import deque
16fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
17fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
18fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanclass CrosDisksArchiveTester(CrosDisksTester):
19fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    """A tester to verify archive support in CrosDisks.
20fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    """
21fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def __init__(self, test, archive_types):
22fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        super(CrosDisksArchiveTester, self).__init__(test)
23cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        self._data_dir = os.path.join(test.bindir, 'data')
24fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        self._archive_types = archive_types
25fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
26fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def _find_all_files(self, root_dir):
27fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        """Returns all files under a directory and its sub-directories.
28fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
29fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           This is a generator that performs a breadth-first-search of
30fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           all files under a specified directory and its sub-directories.
31fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
32fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        Args:
33fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            root_dir: The root directory where the search starts from.
34fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        Yields:
35fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            Path of any found file relative to the root directory.
36fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        """
37fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        dirs_to_explore = deque([''])
38fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        while len(dirs_to_explore) > 0:
39fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            current_dir = dirs_to_explore.popleft()
40fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            for path in os.listdir(os.path.join(root_dir, current_dir)):
41fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                expanded_path = os.path.join(root_dir, current_dir, path)
42fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                relative_path = os.path.join(current_dir, path)
43fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                if os.path.isdir(expanded_path):
44fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                    dirs_to_explore.append(relative_path)
45fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                else:
46fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                    yield relative_path
47fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
48fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def _make_zip_archive(self, archive_path, root_dir,
49fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                         compression=zipfile.ZIP_DEFLATED):
50fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        """Archives a specified directory into a ZIP file.
51fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
52fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           The created ZIP file contains all files and sub-directories
53fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           under the specified root directory, but not the root directory
54fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           itself.
55fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
56fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        Args:
57fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            archive_path: Path of the output archive.
58fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            root_dir: The root directory to archive.
59fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            compression: The ZIP compression method.
60fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        """
61fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        # ZipFile in Python 2.6 does not work with the 'with' statement.
62fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        archive = zipfile.ZipFile(archive_path, 'w', compression)
63fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        for path in self._find_all_files(root_dir):
64fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            archive.write(os.path.join(root_dir, path), path)
65fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        archive.close()
66fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
67cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan    def _make_rar_archive(self, archive_path, root_dir):
68cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        """Archives a specified directory into a RAR file.
69cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan
70cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan           The created RAR file contains all files and sub-directories
71cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan           under the specified root directory, but not the root directory
72cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan           itself.
73cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan
74cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        Args:
75cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan            archive_path: Path of the output archive.
76cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan            root_dir: The root directory to archive.
77cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        """
78cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        # DESPICABLE HACK: app-arch/rar provides only pre-compiled rar binaries
79cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        # for x86/amd64. As a workaround, we pretend the RAR creation here
80cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        # using a precanned RAR file.
81cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        shutil.copyfile(os.path.join(self._data_dir, 'test.rar'), archive_path)
82cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan
83fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def _make_archive(self, archive_type, archive_path, root_dir):
84fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        """Archives a specified directory into an archive of specified type.
85fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
86fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           The created archive file contains all files and sub-directories
87fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           under the specified root directory, but not the root directory
88fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan           itself.
89fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
90fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        Args:
91fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            archive_type: Type of the output archive.
92fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            archive_path: Path of the output archive.
93fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            root_dir: The root directory to archive.
94fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        """
95fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        if archive_type in ['zip']:
96fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            self._make_zip_archive(archive_path, root_dir)
97cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan        elif archive_type in ['rar']:
98cf0668dd4d93484764f3d1b62cecffcb6ebb63c8Ben Chan            self._make_rar_archive(archive_path, root_dir)
99fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        else:
100fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            raise error.TestFail("Unsupported archive type " + archive_type)
101fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
102fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def _test_archive(self, archive_type):
103fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        # Create the archive file content in a temporary directory.
104fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        archive_dir = autotemp.tempdir(unique_id='CrosDisks')
105fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        test_content = DefaultFilesystemTestContent()
106fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        if not test_content.create(archive_dir.name):
107fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            raise error.TestFail("Failed to create archive test content")
108fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
109fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        # Create a FAT-formatted virtual filesystem image containing an
110fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        # archive file to help stimulate mounting an archive file on a
111fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        # removable drive.
112fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        with VirtualFilesystemImage(
113fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                block_size=1024,
114fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                block_count=65536,
115fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                filesystem_type='vfat',
116fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                mkfs_options=[ '-F', '32', '-n', 'ARCHIVE' ]) as image:
117fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            image.format()
118fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            image.mount(options=['sync'])
119fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            # Create the archive file on the virtual filesystem image.
120fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            archive_name = 'test.' + archive_type
121fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            archive_path = os.path.join(image.mount_dir, archive_name)
122fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            self._make_archive(archive_type, archive_path, archive_dir.name)
123fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            image.unmount()
124fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
125fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            # Mount the virtual filesystem image via CrosDisks.
126fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            device_file = image.loop_device
127fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            self.cros_disks.mount(device_file, '',
128fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                                  [ "ro", "nodev", "noexec", "nosuid" ])
129fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            result = self.cros_disks.expect_mount_completion({
130fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                'status': 0,
131fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                'source_path': device_file
132fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            })
133fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
134fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            # Mount the archive file on the mounted filesystem via CrosDisks.
135fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            archive_path = os.path.join(result['mount_path'], archive_name)
136fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            expected_mount_path = os.path.join('/media/archive', archive_name)
137fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            self.cros_disks.mount(archive_path)
138fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            result = self.cros_disks.expect_mount_completion({
139fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                'status': 0,
140fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                'source_path': archive_path,
141fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                'mount_path': expected_mount_path
142fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            })
143fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
144fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            # Verify the content of the mounted archive file.
145fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            if not test_content.verify(expected_mount_path):
146fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan                raise error.TestFail("Failed to verify filesystem test content")
147fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
148fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            self.cros_disks.unmount(expected_mount_path, ['lazy'])
149fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            self.cros_disks.unmount(device_file, ['lazy'])
150fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
151fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def test_archives(self):
152fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        for archive_type in self._archive_types:
153fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan            self._test_archive(archive_type)
154fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
155fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def get_tests(self):
156fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        return [self.test_archives]
157fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
158fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
159fb1049af45d812e87ae0918c9febe46fef9c024cBen Chanclass platform_CrosDisksArchive(test.test):
160fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    version = 1
161fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan
162fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan    def run_once(self, *args, **kwargs):
163fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        tester = CrosDisksArchiveTester(self, kwargs['archive_types'])
164fb1049af45d812e87ae0918c9febe46fef9c024cBen Chan        tester.run(*args, **kwargs)
165