blob: 7ce29c7e38e2bead897fbe8f157ec216f9ead793 [file] [log] [blame]
// Copyright 2020 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 crostini
import (
"bytes"
"context"
"io/ioutil"
"os"
"path/filepath"
"time"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/ui/mouse"
"chromiumos/tast/local/chrome/uiauto/filesapp"
"chromiumos/tast/local/coords"
"chromiumos/tast/local/crostini"
"chromiumos/tast/local/crostini/ui/sharedfolders"
"chromiumos/tast/testing"
)
const (
// dragApplet and dropApplet are data dependencies.
dragApplet = "drag_applet.py"
dragAppletTitle = "gtk3_drag_demo"
dropApplet = "drop_applet.py"
dropAppletTitle = "gtk3_drop_demo"
)
func init() {
testing.AddTest(&testing.Test{
Func: DragDrop,
Desc: "Drag and drop a file and folder in both directions between FilesApp and crostini",
Contacts: []string{"joelhockey@google.com", "cros-containers-dev@google.com"},
Attr: []string{"group:mainline", "informational"},
Vars: []string{"keepState"},
VarDeps: []string{"ui.gaiaPoolDefault"},
Data: []string{dragApplet, dropApplet},
SoftwareDeps: []string{"chrome", "vm_host"},
Params: []testing.Param{
// Parameters generated by params_test.go. DO NOT EDIT.
{
Name: "stretch_stable",
ExtraData: []string{crostini.GetContainerMetadataArtifact("stretch", false), crostini.GetContainerRootfsArtifact("stretch", false)},
ExtraSoftwareDeps: []string{"dlc"},
ExtraHardwareDeps: crostini.CrostiniStable,
Pre: crostini.StartedByDlcStretch(),
Timeout: 7 * time.Minute,
}, {
Name: "stretch_unstable",
ExtraAttr: []string{"informational"},
ExtraData: []string{crostini.GetContainerMetadataArtifact("stretch", false), crostini.GetContainerRootfsArtifact("stretch", false)},
ExtraSoftwareDeps: []string{"dlc"},
ExtraHardwareDeps: crostini.CrostiniUnstable,
Pre: crostini.StartedByDlcStretch(),
Timeout: 7 * time.Minute,
}, {
Name: "buster_stable",
ExtraData: []string{crostini.GetContainerMetadataArtifact("buster", false), crostini.GetContainerRootfsArtifact("buster", false)},
ExtraSoftwareDeps: []string{"dlc"},
ExtraHardwareDeps: crostini.CrostiniStable,
Pre: crostini.StartedByDlcBuster(),
Timeout: 7 * time.Minute,
}, {
Name: "buster_unstable",
ExtraAttr: []string{"informational"},
ExtraData: []string{crostini.GetContainerMetadataArtifact("buster", false), crostini.GetContainerRootfsArtifact("buster", false)},
ExtraSoftwareDeps: []string{"dlc"},
ExtraHardwareDeps: crostini.CrostiniUnstable,
Pre: crostini.StartedByDlcBuster(),
Timeout: 7 * time.Minute,
}, {
Name: "bullseye_stable",
ExtraAttr: []string{"informational"},
ExtraData: []string{crostini.GetContainerMetadataArtifact("bullseye", false), crostini.GetContainerRootfsArtifact("bullseye", false)},
ExtraSoftwareDeps: []string{"dlc"},
ExtraHardwareDeps: crostini.CrostiniStable,
Pre: crostini.StartedByDlcBullseye(),
Timeout: 7 * time.Minute,
}, {
Name: "bullseye_unstable",
ExtraAttr: []string{"informational"},
ExtraData: []string{crostini.GetContainerMetadataArtifact("bullseye", false), crostini.GetContainerRootfsArtifact("bullseye", false)},
ExtraSoftwareDeps: []string{"dlc"},
ExtraHardwareDeps: crostini.CrostiniUnstable,
Pre: crostini.StartedByDlcBullseye(),
Timeout: 7 * time.Minute,
},
},
})
}
func DragDrop(ctx context.Context, s *testing.State) {
pre := s.PreValue().(crostini.PreData)
tconn := pre.TestAPIConn
cont := pre.Container
defer crostini.RunCrostiniPostTest(ctx, s.PreValue().(crostini.PreData))
s.Log("Copying testing applets to container")
if err := cont.PushFile(ctx, s.DataPath(dragApplet), dragApplet); err != nil {
s.Fatal("Failed to push drag applet to container: ", err)
}
if err := cont.PushFile(ctx, s.DataPath(dropApplet), dropApplet); err != nil {
s.Fatal("Failed to push drop applet to container: ", err)
}
// Setup the test file and folder.
const (
dirDragFromFilesapp = "filesappdir"
dirDragFromCrostini = "crostinidir"
fileDragFromFilesapp = "filesapp.txt"
fileDragFromCrostini = "crostini.txt"
)
path := filepath.Join(filesapp.MyFilesPath, dirDragFromFilesapp)
if err := os.Mkdir(path, 0644); err != nil {
s.Fatalf("Create dir %s failed: %s", path, err)
}
defer os.Remove(path)
path = filepath.Join(filesapp.MyFilesPath, fileDragFromFilesapp)
if err := ioutil.WriteFile(path, []byte(fileDragFromFilesapp), 0644); err != nil {
s.Fatalf("Create file %s failed: %s", path, err)
}
defer os.Remove(path)
if err := cont.Command(ctx, "mkdir", dirDragFromCrostini).Run(testexec.DumpLogOnError); err != nil {
s.Fatal("Create container dir failed: ", err)
}
defer cont.RemoveAll(ctx, dirDragFromCrostini)
if err := cont.WriteFile(ctx, fileDragFromCrostini, fileDragFromCrostini); err != nil {
s.Fatal("Create container file failed: ", err)
}
defer cont.RemoveAll(ctx, fileDragFromCrostini)
// Open FilesApp left-snapped.
files, err := filesapp.Launch(ctx, tconn)
if err != nil {
s.Fatal("Launching the Files App failed: ", err)
}
defer files.Close(ctx)
_, err = setWindowState(ctx, tconn, "Files - My files", ash.WindowStateLeftSnapped)
if err != nil {
s.Fatal("Failed to set Files App left-snapped: ", err)
}
defer setWindowState(ctx, tconn, "Files - My files", ash.WindowStateNormal)
// Drag and drop file and dir from FilesApp to app.
if err = dragFromFilesApp(ctx, pre, files, dirDragFromFilesapp, "['file:///mnt/chromeos/MyFiles/filesappdir']"); err != nil {
s.Fatalf("Failed to drag %s from FilesApp to crostini: %v", dirDragFromFilesapp, err)
}
if err = dragFromFilesApp(ctx, pre, files, fileDragFromFilesapp, "['file:///mnt/chromeos/MyFiles/filesapp.txt']"); err != nil {
s.Fatalf("Failed to drag %s from FilesApp to crostini: %v", fileDragFromFilesapp, err)
}
// Drag and drop file from drag app to FilesApp.
if err = dragFromCrostini(ctx, pre, files, dirDragFromCrostini, false); err != nil {
s.Fatalf("Failed to drag %s from crostini to FilesApp: %v", dirDragFromCrostini, err)
}
if err = dragFromCrostini(ctx, pre, files, fileDragFromCrostini, true); err != nil {
s.Fatalf("Failed to drag %s from crostini to FilesApp: %v", fileDragFromCrostini, err)
}
}
// setWindowState sets the window with specified title to the state specified.
func setWindowState(ctx context.Context, tconn *chrome.TestConn, title string, state ash.WindowStateType) (*ash.Window, error) {
var window *ash.Window
if err := ash.WaitForCondition(ctx, tconn, func(w *ash.Window) bool {
window = w
return w.Title == title
}, &testing.PollOptions{Timeout: 30 * time.Second}); err != nil {
return nil, errors.Wrapf(err, "failed to find window %q", title)
}
if err := ash.SetWindowStateAndWait(ctx, tconn, window.ID, state); err != nil {
return nil, errors.Wrapf(err, "failed to set app %q right-snapped", title)
}
return ash.GetWindow(ctx, tconn, window.ID)
}
// dragFromFilesApp drags the specified file or directory from FilesApp to crostini.
func dragFromFilesApp(ctx context.Context, pre crostini.PreData, files *filesapp.FilesApp, path, expected string) error {
tconn := pre.TestAPIConn
cont := pre.Container
kb := pre.Keyboard
// Open drop_applet.py right-snapped.
testing.ContextLogf(ctx, "Starting %s for %s", dropAppletTitle, path)
cmdArgs := []string{"python3", dropApplet}
cmd := cont.Command(ctx, cmdArgs...)
var buf bytes.Buffer
cmd.Stdout = &buf
if err := cmd.Start(); err != nil {
return errors.Wrapf(err, "command %v", cmdArgs)
}
dropAppletWindow, err := setWindowState(ctx, tconn, dropAppletTitle, ash.WindowStateRightSnapped)
if err != nil {
return errors.Wrap(err, "set drop app right-snapped")
}
// Drag and drop file from FilesApp to drop app.
dropPoint := dropAppletWindow.BoundsInRoot.CenterPoint()
if err = files.DragAndDropFile(path, dropPoint, kb)(ctx); err != nil {
return errors.Wrap(err, "drag and drop")
}
defer files.SelectFile(path)(ctx)
// Wait for drop app to close and write dropped filename.
if err = cmd.Wait(testexec.DumpLogOnError); err != nil {
return err
}
output := string(buf.Bytes())
if output != expected {
return errors.Errorf("unexpected drop output: got %q, want %q", output, expected)
}
// Validate file is shared.
if err = cont.CheckFilesExistInDir(ctx, sharedfolders.MountPathMyFiles, path); err != nil {
return errors.Wrap(err, "file not shared with VM")
}
return nil
}
// dragFromCrostini drags the specified file or directory from crostini to FilesApp.
func dragFromCrostini(ctx context.Context, pre crostini.PreData, files *filesapp.FilesApp, path string, isFile bool) error {
tconn := pre.TestAPIConn
cont := pre.Container
testing.ContextLogf(ctx, "Starting %s for %s", dragAppletTitle, path)
cmdArgs := []string{"python3", dragApplet, path}
cmd := cont.Command(ctx, cmdArgs...)
if err := cmd.Start(); err != nil {
return errors.Wrapf(err, "command %v", cmdArgs)
}
defer cmd.Wait(testexec.DumpLogOnError)
dragAppletWindow, err := setWindowState(ctx, tconn, dragAppletTitle, ash.WindowStateRightSnapped)
if err != nil {
return errors.Wrap(err, "set drag app right-snapped: ")
}
dragPoint := dragAppletWindow.BoundsInRoot.CenterPoint()
dropPoint := coords.Point{X: dragAppletWindow.BoundsInRoot.Left - 100, Y: 400}
if err = mouse.Drag(ctx, tconn, dragPoint, dropPoint, time.Second); err != nil {
return errors.Wrap(err, "drag and drop")
}
// Validate file is copied to FilesApp MyFiles.
if err = files.WaitForFile(path)(ctx); err != nil {
return errors.Wrap(err, "find the test file in Files app")
}
crosPath := filepath.Join(filesapp.MyFilesPath, path)
if _, err := os.Stat(crosPath); err != nil {
return errors.Wrap(err, "stat")
}
if isFile {
b, err := ioutil.ReadFile(crosPath)
if err != nil {
return errors.Wrap(err, "read the file in Chrome OS")
}
if string(b) != path {
return errors.Errorf("verify the content of the file: got %s, want %s", string(b), path)
}
}
os.Remove(crosPath)
return nil
}