blob: d32b87627eb0c25f7b6b0c204e54db09f4f252c4 [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 hardware
import (
"bufio"
"context"
"regexp"
"strconv"
"strings"
"time"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/hardware/iio"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: SensorActivity,
Desc: "Tests that activity sensors can be read and give proximity event",
Contacts: []string{
"gwendal@chromium.com", // Chrome OS sensors point of contact
"chingkang@chromium.org", // Test author
"chromeos-sensors-eng@google.com",
},
Attr: []string{"group:mainline", "informational"},
HardwareDeps: hwdep.D(hwdep.ChromeEC()),
Params: []testing.Param{{
ExtraHardwareDeps: hwdep.D(hwdep.Model("coachz")),
}},
})
}
func SensorActivity(ctx context.Context, s *testing.State) {
sensors, err := iio.GetSensors(ctx)
if err != nil {
s.Fatal("Error reading sensors on DUT: ", err)
}
// Find activity sensor.
var sensor *iio.Sensor
for _, sn := range sensors {
if sn.Name == iio.Activity {
sensor = sn
}
}
if sensor == nil {
s.Log("No activity sensor is found")
return
}
activities, err := listActivities(ctx, s)
if err != nil {
s.Fatal("Error listing activities: ", err)
}
for _, x := range activities {
switch x {
case iio.OnBodyDetectionID:
s.Log("Testing on-body detection:")
testOnBodyDetection(ctx, s, sensor)
default:
s.Logf("Testing activity %d is not implemented", int(x))
}
}
}
// testOnBodyDetection tests whether the on-body detection works properly on the DUT.
func testOnBodyDetection(ctx context.Context, s *testing.State, sensor *iio.Sensor) {
// Query and save the original status of spoofing on-body detection: {spoofEnable, spoofState}
// spoofEnable: whether the spoofing is enable
// spoofState: while the spoofing is enabled, what the activity state is.
output, err := spoofActivity(ctx, sensor, iio.OnBodyDetectionID)
if err != nil {
s.Fatal("ectool failed to query spoofEnable of on-body detection: ", err)
}
originalSpoofEnable := strings.Contains(string(output), "enabled")
originalSpoofState := 0
if originalSpoofEnable {
originalSpoofState, err = sensor.ReadIntegerAttr("in_proximity_raw")
if err != nil {
s.Fatal("Failed to read spoofState: ", err)
}
} else {
// Enable spoofing on-body detection first to prevent unexpected proximity event.
_, err = spoofActivity(ctx, sensor, iio.OnBodyDetectionID, 1)
if err != nil {
s.Fatal("ectool failed to enable spoofing on-body detection: ", err)
}
}
defer func() {
s.Log("Recover the status of spoofing on-body detection")
if originalSpoofEnable {
// Spoof the on-body detection to origin state.
_, err = spoofActivity(ctx, sensor, iio.OnBodyDetectionID, 1, originalSpoofState)
if err != nil {
s.Fatal("ectool failed to recover spoofing on-body detection state: ", err)
}
} else {
// Disable the on-body detection spoofing.
_, err = spoofActivity(ctx, sensor, iio.OnBodyDetectionID, 0)
if err != nil {
s.Fatal("ectool failed to disable spoofing on-body detection: ", err)
}
}
}()
// Start to listen on powerd log.
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(5*time.Second))
defer cancel()
cmd := testexec.CommandContext(timeoutCtx, "tail", "-n", "0", "-f", "/var/log/power_manager/powerd.LATEST")
stdout, err := cmd.StdoutPipe()
if err != nil {
s.Fatal("Failed to create stdout pipe of cmd: ", err)
}
reader := bufio.NewScanner(stdout)
if err := cmd.Start(); err != nil {
s.Fatal("Failed to start cmd: ", err)
}
// Test if on-body detection can detect on-body and off-body.
if err := testOnBodyDetectionStateChange(ctx, sensor, reader, 1); err != nil {
s.Fatal("Failed to switch to on-body: ", err)
}
if err := testOnBodyDetectionStateChange(ctx, sensor, reader, 0); err != nil {
s.Fatal("Failed to switch to off-body: ", err)
}
}
// spoofActivity is a helper function for command line: ectool motionsense spoof sensorID activity activityID [enable/disable] [state]
// If the |args| is empty, it queries the spoofing enable status.
// See https://chromium.googlesource.com/chromiumos/platform/ec/+/refs/heads/main/util/ectool.c for more details.
func spoofActivity(ctx context.Context, sensor *iio.Sensor, act iio.ActivityID, args ...int) (output []byte, err error) {
id := strconv.Itoa(int(sensor.ID))
strArgs := []string{"motionsense", "spoof", id, "activity", strconv.Itoa(int(act))}
for _, arg := range args {
s := strconv.Itoa(arg)
strArgs = append(strArgs, s)
}
return testexec.CommandContext(ctx, "ectool", strArgs...).Output()
}
// listActivities is a helper function for command line: ectool motionsense list_activities
// It will return the ID of all available activities on the DUT.
// See https://chromium.googlesource.com/chromiumos/platform/ec/+/refs/heads/main/util/ectool.c for more details.
func listActivities(ctx context.Context, s *testing.State) (activities []iio.ActivityID, err error) {
rawOutput, err := testexec.CommandContext(ctx, "ectool", "motionsense", "list_activities").Output()
if err != nil {
return nil, errors.Wrap(err, "ectool failed to list the activities")
}
for _, line := range strings.Split(string(rawOutput), "\n") {
re := regexp.MustCompile("^[0-9]+")
idData := re.Find([]byte(line))
if idData != nil {
id, err := strconv.Atoi(string(idData))
if err != nil {
return nil, errors.Wrap(err, "failed to convert activity id")
}
activities = append(activities, iio.ActivityID(id))
}
}
return
}
// testOnBodyDetectionStateChange tests whether the DUT can handle the event when on-body detection change status to the given state.
// Note that this function changes the spoofing status of on-body detection.
func testOnBodyDetectionStateChange(ctx context.Context, sensor *iio.Sensor, scanner *bufio.Scanner, state int) error {
// Spoof the on-body detection to given state.
_, err := spoofActivity(ctx, sensor, iio.OnBodyDetectionID, 1, state)
if err != nil {
return errors.Wrap(err, "ectool failed to spoof on-body detection")
}
// Read the proximity state from the sensor's attribute, in order to
// check if the kernel receives proximity event from EC.
proximity, err := sensor.ReadIntegerAttr("in_proximity_raw")
if err != nil {
return errors.Wrap(err, "failed to read in_proximity_raw")
}
if proximity != state {
return errors.Errorf("in_proximity_raw state is %d, expected %d", proximity, state)
}
// Read the proximity state from powerd log, in order to check if the
// powerd receives and handles the proximity event properly.
for scanner.Scan() {
content := scanner.Text()
// Check if the content contains the proximity log.
// -1 if not found, 0 for off-body and 1 for on-body.
proximity := -1
if strings.HasSuffix(content, "User proximity: Near") {
proximity = 1
} else if strings.HasSuffix(content, "User proximity: Far") {
proximity = 0
}
if proximity != -1 {
if proximity != state {
return errors.Errorf("powerd proximity state is %d, expected %d", proximity, state)
}
// Succeeded to find the correct proximity log
return nil
}
}
if err := scanner.Err(); err != nil {
return errors.Wrap(err, "error reading powerd log")
}
// Failed to find any proximity log
return errors.New("not found any proximity event in the log")
}