Add platform_CrosDisksFormat to verify cros-disks formatting support.

BUG=chromium-os:23313
TEST=Ran platform_CrosDisksFormat in a VM.

Change-Id: Ie41055ccc3032ba32722d82762082c68f7d6821c
Reviewed-on: https://gerrit.chromium.org/gerrit/12020
Reviewed-by: Chris Sosa <[email protected]>
Tested-by: Ben Chan <[email protected]>
diff --git a/client/cros/cros_disks.py b/client/cros/cros_disks.py
index 24a21c3..f9c8bb6 100644
--- a/client/cros/cros_disks.py
+++ b/client/cros/cros_disks.py
@@ -116,7 +116,10 @@
                                             interface)
 
     def wait_for_signal(self, signal_name):
-        """Waits for the receiption of a signal.
+        """Waits for the reception of a signal.
+
+        Args:
+            signal_name: The name of the signal to wait for.
 
         Returns:
             The content of the signal.
@@ -139,6 +142,37 @@
         self.__signal_content[signal_name] = None
         return content
 
+    def expect_signal(self, signal_name, expected_content):
+        """Waits the the reception of a signal and verifies its content.
+
+        Args:
+            signal_name: The name of the signal to wait for.
+            expected_content: The expected content of the signal, which can be
+                              partially specified. Only specified fields are
+                              compared between the actual and expected content.
+
+        Returns:
+            The actual content of the signal.
+
+        Raises:
+            error.TestFail: A test failure when there is a mismatch between the
+                            actual and expected content of the signal.
+        """
+        actual_content = self.wait_for_signal(signal_name)
+        logging.debug("%s signal: expected=%s actual=%s",
+                      signal_name, expected_content, actual_content)
+        for argument, expected_value in expected_content.iteritems():
+            if argument not in actual_content:
+                raise error.TestFail(
+                    ('%s signal missing "%s": expected=%s, actual=%s') %
+                    (signal_name, argument, expected_content, actual_content))
+
+            if actual_content[argument] != expected_value:
+                raise error.TestFail(
+                    ('%s signal not matched on "%s": expected=%s, actual=%s') %
+                    (signal_name, argument, expected_content, actual_content))
+        return actual_content
+
 
 class CrosDisksClient(DBusClient):
     """A DBus proxy client for testing the CrosDisks DBus server.
@@ -149,6 +183,10 @@
     CROS_DISKS_OBJECT_PATH = '/org/chromium/CrosDisks'
     DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
     EXPERIMENTAL_FEATURES_ENABLED_PROPERTY = 'ExperimentalFeaturesEnabled'
+    FORMAT_COMPLETED_SIGNAL = 'FormatCompleted'
+    FORMAT_COMPLETED_SIGNAL_ARGUMENTS = (
+        'status', 'path'
+    )
     MOUNT_COMPLETED_SIGNAL = 'MountCompleted'
     MOUNT_COMPLETED_SIGNAL_ARGUMENTS = (
         'status', 'source_path', 'source_type', 'mount_path'
@@ -169,6 +207,9 @@
         self.properties = dbus.Interface(self.proxy_object,
                                          self.DBUS_PROPERTIES_INTERFACE)
         self.handle_signal(self.CROS_DISKS_INTERFACE,
+                           self.FORMAT_COMPLETED_SIGNAL,
+                           self.FORMAT_COMPLETED_SIGNAL_ARGUMENTS)
+        self.handle_signal(self.CROS_DISKS_INTERFACE,
                            self.MOUNT_COMPLETED_SIGNAL,
                            self.MOUNT_COMPLETED_SIGNAL_ARGUMENTS)
 
@@ -231,6 +272,49 @@
         """
         return self.interface.GetDeviceProperties(path)
 
+    def format(self, path, filesystem_type=None, options=None):
+        """Invokes the CrosDisks Format method.
+
+        Args:
+            path: The device path to format.
+            filesystem_type: The filesystem type used for formatting the device.
+            options: A list of options used for formatting the device.
+        """
+        if filesystem_type is None:
+            filesystem_type = ''
+        if options is None:
+            options = []
+        self.clear_signal_content(self.FORMAT_COMPLETED_SIGNAL)
+        self.interface.Format(path, filesystem_type, options)
+
+    def wait_for_format_completion(self):
+        """Waits for the CrosDisks FormatCompleted signal.
+
+        Returns:
+            The content of the FormatCompleted signal.
+        """
+        return self.wait_for_signal(self.FORMAT_COMPLETED_SIGNAL)
+
+    def expect_format_completion(self, expected_content):
+        """Waits and verifies for the CrosDisks FormatCompleted signal.
+
+        Args:
+            expected_content: The expected content of the FormatCompleted
+                              signal, which can be partially specified.
+                              Only specified fields are compared between the
+                              actual and expected content.
+
+        Returns:
+            The actual content of the FormatCompleted signal.
+
+        Raises:
+            error.TestFail: A test failure when there is a mismatch between the
+                            actual and expected content of the FormatCompleted
+                            signal.
+        """
+        return self.expect_signal(self.FORMAT_COMPLETED_SIGNAL,
+                                  expected_content)
+
     def mount(self, path, filesystem_type=None, options=None):
         """Invokes the CrosDisks Mount method.
 
@@ -277,24 +361,13 @@
         Returns:
             The actual content of the MountCompleted signal.
 
-
         Raises:
             error.TestFail: A test failure when there is a mismatch between the
                             actual and expected content of the MountCompleted
                             signal.
         """
-        actual_content = self.wait_for_mount_completion()
-        logging.debug("MountCompleted signal: expected=%s actual=%s",
-                      expected_content, actual_content)
-        for argument in self.MOUNT_COMPLETED_SIGNAL_ARGUMENTS:
-            if argument not in expected_content:
-                continue
-            if actual_content[argument] != expected_content[argument]:
-                raise error.TestFail(
-                    ('MountCompleted signal not matched on "%s": '
-                     'expected=%s, actual=%s') %
-                    (argument, expected_content, actual_content))
-        return actual_content
+        return self.expect_signal(self.MOUNT_COMPLETED_SIGNAL,
+                                  expected_content)
 
 
 class CrosDisksTester(GenericTesterMainLoop):
diff --git a/client/site_tests/platform_CrosDisksFormat/control b/client/site_tests/platform_CrosDisksFormat/control
new file mode 100644
index 0000000..33cc65c
--- /dev/null
+++ b/client/site_tests/platform_CrosDisksFormat/control
@@ -0,0 +1,21 @@
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = "ChromeOS Team"
+NAME = "platform_CrosDisksFormat"
+PURPOSE = "Verify that cros-disks can format supported filesystems correctly"
+
+CRITERIA = """
+"""
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "platform"
+TEST_TYPE = "client"
+
+DOC = """
+Calls cros-disks to format supported filesystems
+"""
+
+job.run_test('platform_CrosDisksFormat', timeout_s=10,
+             config_file='vfat_tests', tag='vfat')
diff --git a/client/site_tests/platform_CrosDisksFormat/platform_CrosDisksFormat.py b/client/site_tests/platform_CrosDisksFormat/platform_CrosDisksFormat.py
new file mode 100644
index 0000000..057827d
--- /dev/null
+++ b/client/site_tests/platform_CrosDisksFormat/platform_CrosDisksFormat.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import logging
+import json
+
+from autotest_lib.client.bin import test
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.cros.cros_disks import CrosDisksTester
+from autotest_lib.client.cros.cros_disks import VirtualFilesystemImage
+from autotest_lib.client.cros.cros_disks import DefaultFilesystemTestContent
+
+
+class CrosDisksFormatTester(CrosDisksTester):
+    """A tester to verify format support in CrosDisks.
+    """
+    def __init__(self, test, test_configs):
+        super(CrosDisksFormatTester, self).__init__(test)
+        self._test_configs = test_configs
+
+    def _run_test_config(self, config):
+        logging.info('Testing "%s"', config['description'])
+        is_experimental = config.get('experimental_features_enabled', False)
+        filesystem_type = config['filesystem_type']
+        format_options = config.get('format_options')
+        # Create a zero-filled virtual filesystem image to help stimulate
+        # a removable drive.
+        with VirtualFilesystemImage(
+                block_size=1024,
+                block_count=65536,
+                filesystem_type=filesystem_type) as image:
+            # Attach the zero-filled virtual filesystem image to a loop device
+            # without actually formatting it.
+            device_file = image.attach_to_loop_device()
+
+            self.cros_disks.experimental_features_enabled = is_experimental
+
+            # Format the virtual filesystem image via CrosDisks.
+            self.cros_disks.format(device_file, filesystem_type, format_options)
+            expected_format_completion = {
+                'path': device_file
+            }
+            if 'expected_format_status' in config:
+                expected_format_completion['status'] = \
+                        config['expected_format_status']
+            result = self.cros_disks.expect_format_completion(
+                expected_format_completion)
+
+            if result['status'] == 0:
+                # Test creating and verifying content the formatted device.
+                logging.info("Test filesystem access on formatted device")
+                test_content = DefaultFilesystemTestContent()
+                mount_path = image.mount()
+                if not test_content.create(mount_path):
+                    raise error.TestFail("Failed to create test content")
+                if not test_content.verify(mount_path):
+                    raise error.TestFail("Failed to verify test content")
+
+    def test_using_virtual_filesystem_image(self):
+        experimental = self.cros_disks.experimental_features_enabled
+        try:
+            for config in self._test_configs:
+                self._run_test_config(config)
+        finally:
+            # Always restore the original value of ExperimentalFeaturesEnabled
+            # property, so cros-disks maintains in the same state of support
+            # experimental features before and after tests.
+            self.cros_disks.experimental_features_enabled = experimental
+
+    def get_tests(self):
+        return [self.test_using_virtual_filesystem_image]
+
+
+class platform_CrosDisksFormat(test.test):
+    version = 1
+
+    def run_once(self, *args, **kwargs):
+        test_configs = []
+        config_file = '%s/%s' % (self.bindir, kwargs['config_file'])
+        with open(config_file, 'rb') as f:
+            test_configs.extend(json.load(f))
+
+        tester = CrosDisksFormatTester(self, test_configs)
+        tester.run(*args, **kwargs)
diff --git a/client/site_tests/platform_CrosDisksFormat/vfat_tests b/client/site_tests/platform_CrosDisksFormat/vfat_tests
new file mode 100644
index 0000000..49214fe
--- /dev/null
+++ b/client/site_tests/platform_CrosDisksFormat/vfat_tests
@@ -0,0 +1,7 @@
+[
+  {
+    "description": "VFAT filesystem",
+    "filesystem_type": "vfat",
+    "expected_format_status": 0
+  }
+]