blob: c653a08ddd0cfe90a182bcce9d07a7550e514706 [file]
// 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 wilco
import (
"context"
"regexp"
"strings"
"time"
"github.com/golang/protobuf/ptypes/empty"
"chromiumos/tast/common/servo"
"chromiumos/tast/errors"
"chromiumos/tast/rpc"
pb "chromiumos/tast/services/cros/wilco"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: DrallionTabletPower,
LacrosStatus: testing.LacrosVariantUnknown,
Desc: "Verifies power button behavior on Drallion 360 devices in tablet mode",
Contacts: []string{
"mwiitala@google.com", // Author
"tast-owners@google.com",
},
SoftwareDeps: []string{"wilco", "chrome"},
ServiceDeps: []string{"tast.cros.wilco.PowerMenuService"},
// On Drallion360, the power button is on the keyboard rather than the side
// of the device. To account for this, the power button behaves differently
// on drallion360 devices when in tablet mode and requires a separate test.
HardwareDeps: hwdep.D(hwdep.Model("drallion360")),
// TODO(mwiitala): Restore attributes after fixing http://b/149035007
// Attr: []string{ "group:mainline", "informational"},
Vars: []string{"servo"},
})
}
func DrallionTabletPower(ctx context.Context, s *testing.State) {
d := s.DUT()
readBootID := func(ctx context.Context) (string, error) {
out, err := d.Conn().CommandContext(ctx, "cat", "/proc/sys/kernel/random/boot_id").Output()
if err != nil {
return "", errors.Wrap(err, "error reading boot id")
}
return strings.TrimSpace(string(out)), nil
}
// This is expected to fail in VMs, since Servo is unusable there and the "servo" var won't
// be supplied. https://crbug.com/967901 tracks finding a way to skip tests when needed.
servoSpec, _ := s.Var("servo")
pxy, err := servo.NewProxy(ctx, servoSpec, d.KeyFile(), d.KeyDir())
if err != nil {
s.Fatal("Failed to connect to servo: ", err)
}
defer pxy.Close(ctx)
// Connect to the gRPC server on the DUT.
cl, err := rpc.Dial(ctx, s.DUT(), s.RPCHint())
if err != nil {
s.Fatal("Failed to connect to the RPC: ", err)
}
defer cl.Close(ctx)
powerMenuService := pb.NewPowerMenuServiceClient(cl.Conn)
// Get initial boot ID
initID, err := readBootID(ctx)
if err != nil {
s.Fatal("Failed to read boot ID: ", err)
}
s.Logf("Initial boot ID: %s", initID)
// Get the initial tablet_mode_angle settings to set back at end of test
re := regexp.MustCompile(`tablet_mode_angle=(\d+) hys=(\d+)`)
out, err := d.Conn().CommandContext(ctx, "ectool", "--name=cros_ish", "motionsense", "tablet_mode_angle").Output()
if err != nil {
s.Fatal("Failed to retreive tablet_mode_angle settings: ", err)
}
initLidAngle := re.FindStringSubmatch(string(out))
if len(initLidAngle) != 3 {
s.Fatal("Failed to get initial tablet_mode_angle settings")
}
s.Logf("Initial settings: lid_angle=%s hys=%s", initLidAngle[1], initLidAngle[2])
// Restore tablet_mode_angle settings before returning
defer d.Conn().CommandContext(ctx, "ectool", "--name=cros_ish", "motionsense", "tablet_mode_angle", initLidAngle[1], initLidAngle[2]).Run()
setTabletMode := func(tabletMode bool) error {
// Setting tabletModeAngle to 0 will force the DUT into tablet mode
tabletModeAngle := "0"
mode := "tablet"
if !tabletMode {
// Setting tabletModeAngle to 360 will force the DUT into clamshell mode
tabletModeAngle = "360"
mode = "clamshell"
}
// Use servo to set tablet_mode_angle
out, err = d.Conn().CommandContext(ctx, "ectool", "--name=cros_ish", "motionsense", "tablet_mode_angle", tabletModeAngle, "0").Output()
if err != nil {
return errors.Wrap(err, "failed to set tablet_mode_angle")
}
s.Logf("Put DUT into %s mode", mode)
return nil
}
// Press power key for pressDuration seconds and verify DUT reboots as expected
testCaseReboot := func(pressDuration string) error {
// Restarting Chrome clears the power down menu if already present
if _, err = powerMenuService.NewChrome(ctx, &empty.Empty{}); err != nil {
return errors.Wrap(err, "failed to create new chrome instance")
}
// Close chrome instance before rebooting
if _, err = powerMenuService.CloseChrome(ctx, &empty.Empty{}); err != nil {
return errors.Wrap(err, "failed to close chrome instance")
}
// Use servo to hold down power button
s.Logf("Pressing power key for %s seconds", pressDuration)
if err = pxy.Servo().SetString(ctx, "power_key", pressDuration); err != nil {
return errors.Wrap(err, "error pressing the power button")
}
waitUnreachableCtx, cancelUnreachable := context.WithTimeout(ctx, 30*time.Second)
defer cancelUnreachable()
// Wait for DUT to power off as expected
s.Log("Waiting for DUT to power OFF")
if err = d.WaitUnreachable(waitUnreachableCtx); err != nil {
return errors.New("DUT did not power down after power key press > 8 seconds")
}
// Use servo to power DUT on
s.Log("Sending power key press to turn DUT back on")
if err = pxy.Servo().SetString(ctx, "power_state", "on"); err != nil {
return errors.Wrap(err, "failed to send power key press")
}
s.Log("Waiting for DUT to power ON")
waitConnectCtx, cancelWaitConnect := context.WithTimeout(ctx, 30*time.Second)
defer cancelWaitConnect()
// Wait for DUT to reboot and reconnect
if err = d.WaitConnect(waitConnectCtx); err != nil {
return errors.Wrap(err, "failed to reconnect to DUT")
}
// Verify that DUT rebooted
curID, err := readBootID(ctx)
if err != nil {
return errors.Wrap(err, "failed to read boot ID")
}
if curID == initID {
return errors.Errorf("DUT failed to reboot after power key press of %s seconds", pressDuration)
}
// Update initID for following test cases
initID = curID
// Reconnect to the gRPC server on the DUT for following test cases
cl, err = rpc.Dial(ctx, s.DUT(), s.RPCHint())
if err != nil {
return errors.Wrap(err, "failed to connect to the RPC")
}
powerMenuService = pb.NewPowerMenuServiceClient(cl.Conn)
return nil
}
// Press power key for pressDuration seconds, check that power menu only appears if expected, confirm DUT did not reboot
testCaseNoReboot := func(pressDuration string, menuExpected bool) error {
// Chrome instance is necessary to check for the presence of the power menu
if _, err = powerMenuService.NewChrome(ctx, &empty.Empty{}); err != nil {
return errors.Wrap(err, "failed to create new chrome instance")
}
defer powerMenuService.CloseChrome(ctx, &empty.Empty{})
// Use servo to hold down power button
s.Logf("Pressing power key for %s seconds", pressDuration)
if err = pxy.Servo().SetString(ctx, "power_key", pressDuration); err != nil {
return errors.Wrap(err, "error pressing the power button")
}
// Verify that power down menu is only present when expected
res, err := powerMenuService.IsPowerMenuPresent(ctx, &empty.Empty{})
if err != nil {
return errors.Wrap(err, "RPC call failed")
}
if res.IsMenuPresent != menuExpected {
return errors.Errorf("Power menu did not behave as expected after pressing power key for %s seconds", pressDuration)
}
// Verify that DUT did not reboot
curID, err := readBootID(ctx)
if err != nil {
return errors.Wrap(err, "failed to read boot ID")
}
if curID != initID {
return errors.Errorf("DUT rebooted after power key press of %s seconds", pressDuration)
}
return nil
}
/* Iterate over test cases and verify expected behavior:
Clamshell mode: Power menu appears after power key press of any duration
Tablet mode: Power menu only appears after power key press > 1.5 seconds
DUT powers down after power key press > 8.0 seconds in both modes
*/
for _, tc := range []struct {
tabletMode bool
pressDuration string
menuExpected bool
rebootExpected bool
}{
{false, "0.5", true, false},
{true, "0.5", false, false},
{false, "2.0", true, false},
{true, "2.0", true, false},
{false, "8.5", true, true},
{true, "8.5", true, true},
} {
// Use servo to force DUT into tablet or clamshell mode
if err := setTabletMode(tc.tabletMode); err != nil {
s.Fatal("Failed to set tablet mode angel: ", err)
}
// Verify test case expectations
if !tc.rebootExpected {
err = testCaseNoReboot(tc.pressDuration, tc.menuExpected)
} else {
err = testCaseReboot(tc.pressDuration)
}
if err != nil {
s.Fatalf("Failed on test case with tabletMode=%t, pressDuration=%s, menuExpected=%t, rebootExpected=%t: %v",
tc.tabletMode, tc.pressDuration, tc.menuExpected, tc.rebootExpected, err)
}
}
}