blob: fe5465e6f060fe5fa697ba0ada7818808666d0fa [file] [log] [blame]
// Copyright 2019 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.
// Package crosdisks provides a series of tests to verify CrosDisks'
// D-Bus API behavior.
package crosdisks
import (
"context"
"github.com/godbus/dbus"
"chromiumos/tast/errors"
"chromiumos/tast/local/crosdisks"
"chromiumos/tast/testing"
)
// verifyProp checks if the passed prop satisfies the device property
// conditions. Reports an error on failure.
func verifyProp(prop map[string]dbus.Variant) []error {
type field struct {
name string
signature string
}
var errs []error
for _, f := range []field{
{"DeviceFile", "s"},
{"DeviceIsDrive", "b"},
{"DeviceIsDrive", "b"},
{"DeviceIsMediaAvailable", "b"},
{"DeviceIsOnBootDevice", "b"},
{"DeviceIsVirtual", "b"},
{"DeviceIsMounted", "b"},
{"DeviceIsReadOnly", "b"},
{"DeviceMediaType", "u"},
{"DeviceMountPaths", "as"},
{"DevicePresentationHide", "b"},
{"DeviceSize", "t"},
{"DriveModel", "s"},
{"IdLabel", "s"},
{"StorageDevicePath", "s"},
{"FileSystemType", "s"},
} {
if v, ok := prop[f.name]; !ok {
errs = append(errs, errors.Errorf("%s not found in the property", f.name))
} else if v.Signature().String() != f.signature {
errs = append(errs, errors.Errorf("unexpected signature for %s: got %s; want %s", f.name, v.Signature().String(), f.signature))
}
}
if len(errs) > 0 {
return errs
}
// Hereafter type assersion should not fail thanks to godbus
// convention.
// DeviceFile must not be empty.
if df := prop["DeviceFile"].Value().(string); df == "" {
return []error{errors.New("deviceFile is empty")}
}
// Check if the values of DeviceIsMounted and DeviceMountPaths are
// consistent, and any DeviceMountPaths entry is not empty.
paths := prop["DeviceMountPaths"].Value().([]string)
if prop["DeviceIsMounted"].Value().(bool) {
if len(paths) == 0 {
return []error{errors.New("prop.DeviceMountPaths should not be empty if prop.DeviceIsMounted is true")}
}
} else {
if len(paths) != 0 {
return []error{errors.New("prop.DeviceMountPaths should be empty if prop.DeviceIsMounted is false")}
}
}
for _, path := range paths {
if path == "" {
return []error{errors.New("prop.DeviceMountPaths should not contain any empty string")}
}
}
return nil
}
func testEnumerateDevices(ctx context.Context, s *testing.State, cd *crosdisks.CrosDisks) {
s.Log("Running testEnumerateDevices")
ds, err := cd.EnumerateDevices(ctx)
if err != nil {
s.Error("Failed to enumerate devices: ", err)
return
}
for _, d := range ds {
if d == "" {
s.Error("Device returned by EnumerateDevices should be non-empty string")
continue
}
prop, err := cd.GetDeviceProperties(ctx, d)
if err != nil {
s.Errorf("Failed to fetch device property for %s: %v", d, err)
continue
}
for _, err := range verifyProp(prop) {
s.Errorf("Failed to verify property for %s: %v", d, err)
}
}
}
func testNonExistentDeviceProp(ctx context.Context, s *testing.State, cd *crosdisks.CrosDisks) {
s.Log("Running testNonExistentDeviceProp")
const path = "/dev/nonexistent"
if _, err := cd.GetDeviceProperties(ctx, path); err == nil {
s.Errorf("GetDeviceProperties for %s unexpectedly succeeds", path)
}
}
func testMountNonExistentDevice(ctx context.Context, s *testing.State, cd *crosdisks.CrosDisks) {
s.Log("Running testMountNonExistentDevice")
const path = "/dev/nonexistent"
w, err := cd.WatchMountCompleted(ctx)
if err != nil {
s.Error("Failed to start watching MountCompleted: ", err)
return
}
defer w.Close(ctx)
if err := cd.Mount(ctx, path, "" /* filesystem type */, nil /* options */); err != nil {
s.Error("Failed to call Mount: ", err)
return
}
s.Log("Waiting for MountCompleted D-Bus signal")
m, err := w.Wait(ctx)
if err != nil {
s.Error("Failed to see MountCompleted D-Bus signal: ", err)
return
}
if m.SourcePath != path {
s.Errorf("Unexpected source_path: got %q; want %q", m.SourcePath, path)
}
if m.MountPath != "" {
s.Errorf("Unexpected mount_path: got %q; want %q", m.MountPath, "")
}
}
func testBootDeviceRejected(ctx context.Context, s *testing.State, cd *crosdisks.CrosDisks) {
s.Log("Running testBootDeviceRejected")
ds, err := cd.EnumerateDevices(ctx)
if err != nil {
s.Error("Failed to enumerate devices: ", err)
return
}
// Find boot device.
dev := ""
for _, d := range ds {
// Note: Verification is done in testEnumerateDevices, so
// just skip invalid property.
prop, err := cd.GetDeviceProperties(ctx, d)
if err != nil {
continue
}
if v, ok := prop["DeviceIsOnBootDevice"].Value().(bool); ok && v {
dev = d
break
}
}
if dev != "" {
s.Errorf("Boot device %s was unexpectedly found", dev)
}
}
func testUnmountNonExistentDevice(ctx context.Context, s *testing.State, cd *crosdisks.CrosDisks) {
s.Log("Running testUnmountNonExistentDevice")
const path = "/dev/nonexistent"
status, err := cd.Unmount(ctx, path, nil /* options */)
if err != nil {
s.Errorf("Failed to call Unmount for %s: %v", path, err)
return
}
if status != crosdisks.MountErrorPathNotMounted {
s.Errorf("Unexpected Unmount status code: got %d; want %d", status, crosdisks.MountErrorPathNotMounted)
}
}
// RunBasicTests runs a series of tests.
func RunBasicTests(ctx context.Context, s *testing.State) {
cd, err := crosdisks.New(ctx)
if err != nil {
s.Fatal("Failed to connect CrosDisks D-Bus service: ", err)
}
testEnumerateDevices(ctx, s, cd)
testNonExistentDeviceProp(ctx, s, cd)
testMountNonExistentDevice(ctx, s, cd)
testBootDeviceRejected(ctx, s, cd)
testUnmountNonExistentDevice(ctx, s, cd)
}