blob: 50ab161aa838b5f078454fc077aee86707654898 [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 setup contains helpers to set up a DUT for a power test.
package setup
import (
"context"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/testing"
)
// CleanupCallback cleans up a single setup item.
type CleanupCallback func(context.Context) error
// Nested is used by setup items that have multiple stages that need separate
// cleanup callbacks.
func Nested(ctx context.Context, name string, nestedSetup func(s *Setup) error) (CleanupCallback, error) {
s, callback := New(name)
succeeded := false
defer func() {
if !succeeded {
callback(ctx)
}
}()
testing.ContextLogf(ctx, "Setting up %q", name)
if err := nestedSetup(s); err != nil {
return nil, err
}
if err := s.Check(ctx); err != nil {
return nil, err
}
succeeded = true
return callback, nil
}
// Setup accumulates the results of setup items so that their results can be
// checked, errors logged, and cleaned up.
type Setup struct {
name string
callbacks []CleanupCallback
errs []error
}
// New creates a Setup object to collect the results of setup items, and a
// cleanup function that should be immediately deferred to make sure cleanup
// callbacks are called.
func New(name string) (*Setup, CleanupCallback) {
s := &Setup{
name: name,
callbacks: nil,
errs: nil,
}
cleanedUp := false
return s, func(ctx context.Context) error {
if cleanedUp {
return errors.Errorf("cleanup %q has already been called", name)
}
cleanedUp = true
if count, err := s.cleanUp(ctx); err != nil {
return errors.Wrapf(err, "cleanup %q had %d items fail, first failure", name, count)
}
return nil
}
}
// cleanUp is a helper that runs all cleanup callbacks and logs any failures.
// Returns true if all cleanup
func (s *Setup) cleanUp(ctx context.Context) (errorCount int, firstError error) {
for _, c := range s.callbacks {
// Defer cleanup calls so that if any of them panic, the rest still run.
defer func(callback CleanupCallback) {
if err := callback(ctx); err != nil {
errorCount++
if firstError == nil {
firstError = err
}
testing.ContextLogf(ctx, "Cleanup %q failed: %s", s.name, err)
}
}(c)
}
return 0, nil
}
// Add adds a result to be checked or cleaned up later.
func (s *Setup) Add(callback CleanupCallback, err error) {
if callback != nil {
s.callbacks = append(s.callbacks, callback)
}
if err != nil {
s.errs = append(s.errs, err)
}
}
// Check checks if any Result shows a failure happened. All failures are logged,
// and a summary of failures is returned.
func (s *Setup) Check(ctx context.Context) error {
for _, err := range s.errs {
testing.ContextLogf(ctx, "Setup %q failed: %s", s.name, err)
}
if len(s.errs) > 0 {
return errors.Wrapf(s.errs[0], "setup %q had %d items fail, first failure", s.name, len(s.errs))
}
return nil
}
// BatteryDischargeMode what setup is needed for a test
type BatteryDischargeMode int
const (
// NoBatteryDischarge option requests setup not to try
// forcing discharge of battery
NoBatteryDischarge BatteryDischargeMode = iota
// ForceBatteryDischarge option requests setup to force
// discharging battery during a test
ForceBatteryDischarge
)
// WifiInterfacesMode describes how to setup WiFi interfaces for a test.
type WifiInterfacesMode int
const (
// DoNotChangeWifiInterfaces indicates that WiFi interfaces should be left in the same state.
DoNotChangeWifiInterfaces = iota
// DisableWifiInterfaces indicates that WiFi interfaces should be disabled.
DisableWifiInterfaces
)
// NightLightMode what setup is needed for a test.
type NightLightMode int
const (
// DoNotDisableNightLight indicates that Night Light should be left in the same state.
DoNotDisableNightLight = iota
// DisableNightLight indicates that Night Light should be disabled.
DisableNightLight
)
// PowerTestOptions describes how to set up a power test.
type PowerTestOptions struct {
Battery BatteryDischargeMode
Wifi WifiInterfacesMode
NightLight NightLightMode
}
// PowerTest configures a DUT to run a power test by disabling features that add
// noise, and consistently configuring components that change power draw.
func PowerTest(ctx context.Context, c *chrome.TestConn, options PowerTestOptions) (CleanupCallback, error) {
return Nested(ctx, "power test", func(s *Setup) error {
s.Add(DisableService(ctx, "powerd"))
s.Add(DisableService(ctx, "update-engine"))
s.Add(DisableServiceIfExists(ctx, "vnc"))
s.Add(DisableServiceIfExists(ctx, "dptf"))
s.Add(SetBacklightLux(ctx, 150))
s.Add(SetKeyboardBrightness(ctx, 24))
s.Add(MuteAudio(ctx))
if options.Wifi == DisableWifiInterfaces {
s.Add(DisableWiFiInterfaces(ctx))
}
if options.Battery == ForceBatteryDischarge {
s.Add(SetBatteryDischarge(ctx, 2.0))
}
s.Add(DisableBluetooth(ctx))
if options.NightLight == DisableNightLight {
s.Add(TurnOffNightLight(ctx, c))
}
return nil
})
}