| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package intel |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "regexp" |
| "strconv" |
| "time" |
| |
| "go.chromium.org/tast-tests/cros/common/servo" |
| "go.chromium.org/tast-tests/cros/remote/powercontrol" |
| "go.chromium.org/tast-tests/cros/services/cros/ui" |
| "go.chromium.org/tast/core/ctxutil" |
| "go.chromium.org/tast/core/dut" |
| "go.chromium.org/tast/core/errors" |
| "go.chromium.org/tast/core/rpc" |
| "go.chromium.org/tast/core/testing" |
| "go.chromium.org/tast/core/testing/hwdep" |
| ) |
| |
| type onboardKeyboardTestType int |
| |
| const ( |
| suspendStressTest onboardKeyboardTestType = iota |
| shutdownStressTest |
| ) |
| |
| func init() { |
| testing.AddTest(&testing.Test{ |
| Func: OnboardKeyboardFunctionalityCheck, |
| LacrosStatus: testing.LacrosVariantUnneeded, |
| Desc: "Verifies on-board keyboard functionality check with suspend-resume and coldboot operation", |
| Contacts: []string{"intel.chrome.automation.team@intel.com", "ambalavanan.m.m@intel.com"}, |
| BugComponent: "b:157291", // ChromeOS > External > Intel |
| SoftwareDeps: []string{"chrome"}, |
| ServiceDeps: []string{"tast.cros.ui.AudioService", "tast.cros.browser.ChromeService"}, |
| VarDeps: []string{"servo"}, |
| Params: []testing.Param{{ |
| Name: "suspend_resume", |
| Val: suspendStressTest, |
| ExtraHardwareDeps: hwdep.D(hwdep.X86()), |
| ExtraAttr: []string{"group:intel-sleep"}, |
| Timeout: 10 * time.Minute, |
| }, { |
| Name: "coldboot", |
| Val: shutdownStressTest, |
| ExtraHardwareDeps: hwdep.D(hwdep.ChromeEC()), |
| ExtraAttr: []string{"group:intel-nda"}, |
| Timeout: 15 * time.Minute, |
| }, |
| }}) |
| } |
| |
| func OnboardKeyboardFunctionalityCheck(ctx context.Context, s *testing.State) { |
| ctxForCleanUp := ctx |
| ctx, cancel := ctxutil.Shorten(ctx, 2*time.Minute) |
| defer cancel() |
| |
| dut := s.DUT() |
| servoSpec := s.RequiredVar("servo") |
| pxy, err := servo.NewProxy(ctx, servoSpec, dut.KeyFile(), dut.KeyDir()) |
| if err != nil { |
| s.Fatal("Failed to connect to servo: ", err) |
| } |
| defer pxy.Close(ctxForCleanUp) |
| |
| // Performs a Chrome login. |
| loginChrome := func() (*rpc.Client, error) { |
| cl, err := rpc.Dial(ctx, dut, s.RPCHint()) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to connect to the RPC service on the DUT") |
| } |
| chromeService := ui.NewChromeServiceClient(cl.Conn) |
| if _, err := chromeService.New(ctx, &ui.NewRequest{}); err != nil { |
| return nil, errors.Wrap(err, "failed to log into Chrome") |
| } |
| return cl, nil |
| } |
| |
| // Perform initial Chrome login. |
| cl, err := loginChrome() |
| if err != nil { |
| s.Fatal("Failed to log in to Chrome: ", err) |
| } |
| |
| defer func(ctx context.Context) { |
| s.Log("Performing cleanup") |
| if !dut.Connected(ctx) { |
| if err := powercontrol.PowerOntoDUT(ctx, pxy, dut); err != nil { |
| s.Error("Failed to power on DUT at cleanup: ", err) |
| } |
| wtCtx, cancel := context.WithTimeout(ctx, 30*time.Second) |
| defer cancel() |
| if err := dut.WaitConnect(wtCtx); err != nil { |
| s.Error("Failed to wait connect to DUT at cleanup: ", err) |
| } |
| } |
| var stdoutBuf, stderrBuf bytes.Buffer |
| cmd := dut.Conn().CommandContext(ctx, "rm", "-rf", "/tmp/kbEventlog.txt") |
| cmd.Stdout = &stdoutBuf |
| cmd.Stderr = &stderrBuf |
| if err := cmd.Run(); err != nil { |
| s.Errorf("Failed to remove temporary file: stdout: %s; stderr: %s; error: %v", stdoutBuf.Bytes(), stderrBuf.Bytes(), err) |
| } |
| }(ctxForCleanUp) |
| |
| if err := performKeyboardEvents(ctx, dut, cl); err != nil { |
| s.Fatal("Failed to perform on-board keyboard evtest events: ", err) |
| } |
| |
| switch s.Param().(onboardKeyboardTestType) { |
| case suspendStressTest: |
| slpOpSetPre, pkgOpSetPre, err := powercontrol.SlpAndC10PackageValues(ctx, dut) |
| if err != nil { |
| s.Fatal("Failed to get SLP counter and C10 package values before suspend-resume: ", err) |
| } |
| |
| suspendStressTestCounter := 10 |
| if err := powercontrol.PerformSuspendStressTest(ctx, dut, suspendStressTestCounter); err != nil { |
| s.Fatal("Failed to perform suspend stress test: ", err) |
| } |
| |
| if err := performKeyboardEvents(ctx, dut, cl); err != nil { |
| s.Fatal("Failed to perform on-board keyboard evtest events after suspend stress test: ", err) |
| } |
| |
| slpOpSetPost, pkgOpSetPost, err := powercontrol.SlpAndC10PackageValues(ctx, dut) |
| if err != nil { |
| s.Fatal("Failed to get SLP counter and C10 package values after suspend-resume: ", err) |
| } |
| |
| if slpOpSetPre == slpOpSetPost { |
| s.Fatalf("Failed: SLP counter value %q should be different from the one before suspend %q", slpOpSetPost, slpOpSetPre) |
| } |
| |
| if slpOpSetPost == 0 { |
| s.Fatal("Failed: SLP counter value must be non-zero, got: ", slpOpSetPost) |
| } |
| |
| if pkgOpSetPre == pkgOpSetPost { |
| s.Fatalf("Failed: Package C10 value %q must be different from the one before suspend %q", pkgOpSetPost, pkgOpSetPre) |
| } |
| |
| if pkgOpSetPost == "0x0" || pkgOpSetPost == "0" { |
| s.Fatal("Failed: Package C10 should be non-zero, got: ", pkgOpSetPost) |
| } |
| |
| case shutdownStressTest: |
| iter := 10 |
| for i := 1; i <= iter; i++ { |
| s.Logf("Iteration: %d/%d", i, iter) |
| if err := performColdboot(ctx, dut, pxy); err != nil { |
| s.Fatal("Failed to perform coldboot: ", err) |
| } |
| |
| // Perform a Chrome login after power on from shutdown state. |
| cl, err = loginChrome() |
| if err != nil { |
| s.Fatal("Failed to login Chrome after shutdown: ", err) |
| } |
| |
| // After powering on from shutdown, perform Chrome login |
| // and perform on-board keyboard functional check. |
| if err := performKeyboardEvents(ctx, dut, cl); err != nil { |
| s.Fatal("Failed to perform on-board keyboard evtest events after shutdown: ", err) |
| } |
| |
| // Verifies prev_sleep_state is 5 for coldboot. |
| valid, err := powercontrol.IsPrevSleepStateAvailable(ctx, dut) |
| |
| if err != nil { |
| s.Fatal("Failed to determine if prev_sleep_state is available: ", err) |
| } |
| |
| if valid { |
| cbmemSleepState := 5 |
| if err := powercontrol.ValidatePrevSleepState(ctx, dut, cbmemSleepState); err != nil { |
| s.Fatal("Failed to validate previous sleep state: ", err) |
| } |
| } |
| } |
| } |
| } |
| |
| // performColdboot peforms shutdown, verifies S5 state and powers on DUT. |
| func performColdboot(ctx context.Context, dut *dut.DUT, pxy *servo.Proxy) error { |
| powerState := "S5" |
| if err := powercontrol.ShutdownAndWaitForPowerState(ctx, pxy, dut, powerState); err != nil { |
| return errors.Wrapf(err, "failed to shutdown and wait for %q powerstate", powerState) |
| } |
| |
| if err := powercontrol.PowerOntoDUT(ctx, pxy, dut); err != nil { |
| return errors.Wrap(err, "failed to power on DUT") |
| } |
| return nil |
| } |
| |
| // keyboardEventNumber returns USB Keyboard evtest event number. |
| func keyboardEventNumber(ctx context.Context, dut *dut.DUT) (int, error) { |
| out, err := dut.Conn().CommandContext(ctx, "evtest").CombinedOutput() |
| if err != nil { |
| testing.ContextLog(ctx, "Failed to execute evtest command") |
| } |
| re := regexp.MustCompile(`(?i)/dev/input/event([0-9]+):.*AT Translated Set . keyboard.*`) |
| result := re.FindStringSubmatch(string(out)) |
| if len(result) == 0 { |
| return 0, errors.New("failed to find keyboard in evtest command output") |
| } |
| kbEventNum, err := strconv.Atoi(result[1]) |
| if err != nil { |
| return 0, errors.Wrap(err, "failed to convert string to integer") |
| } |
| return kbEventNum, nil |
| } |
| |
| // performKeyboardEvents performs USB Keyboard key press events. |
| func performKeyboardEvents(ctx context.Context, dut *dut.DUT, cl *rpc.Client) error { |
| eventLogFile := "/tmp/kbEventlog.txt" |
| evtestRecordCmd := "evtest /dev/input/event" |
| |
| // Remove temporary log file, if any present before creating it. |
| var stdoutBuf, stderrBuf bytes.Buffer |
| cmd := dut.Conn().CommandContext(ctx, "rm", "-rf", eventLogFile) |
| cmd.Stdout = &stdoutBuf |
| cmd.Stderr = &stderrBuf |
| if err := cmd.Run(); err != nil { |
| return errors.Wrapf(err, "failed to remove temporary file: stdOut: %s; stdErr: %s; error", stdoutBuf.Bytes(), stderrBuf.Bytes()) |
| } |
| |
| eventNum, err := keyboardEventNumber(ctx, dut) |
| if err != nil { |
| return errors.Wrap(err, "failed to get on-board keyboard evtest event number") |
| } |
| |
| // Perform evtest command to record all events and save in temporary file. |
| go func() { |
| cmd = dut.Conn().CommandContext(ctx, "bash", "-c", fmt.Sprintf("%s%d > %s &", evtestRecordCmd, eventNum, eventLogFile)) |
| cmd.Stdout = &stdoutBuf |
| cmd.Stderr = &stderrBuf |
| err = cmd.Run() |
| }() |
| if err != nil { |
| return errors.Wrapf(err, "failed to perform evtest events record: stdOut: %s; stdErr: %s; error", stdoutBuf.Bytes(), stderrBuf.Bytes()) |
| } |
| |
| // Perform USB Key press. |
| audioService := ui.NewAudioServiceClient(cl.Conn) |
| pressKeys := []string{"c", "h", "r", "o", "m", "e"} |
| for _, key := range pressKeys { |
| accelKeys := &ui.AudioServiceRequest{Expr: key} |
| if _, err := audioService.KeyboardAccel(ctx, accelKeys); err != nil { |
| return errors.Wrapf(err, "failed to press on-board keyboard %q key", key) |
| } |
| } |
| |
| // Stopping evtest record process. |
| cmd = dut.Conn().CommandContext(ctx, "sudo", "pkill", "evtest") |
| cmd.Stdout = &stdoutBuf |
| cmd.Stderr = &stderrBuf |
| if err := cmd.Run(); err != nil { |
| return errors.Wrapf(err, "failed to kill evtest process: stdOut: %s; stdErr: %s; error", stdoutBuf.Bytes(), stderrBuf.Bytes()) |
| } |
| |
| catOutput, err := dut.Conn().CommandContext(ctx, "cat", eventLogFile).Output() |
| if err != nil { |
| return errors.Wrap(err, "failed to execute cat command") |
| } |
| |
| // Validating On-board Keyboard key press is recorded in log file output. |
| keysPattern := []string{"KEY_C", "KEY_H", "KEY_R", "KEY_O", "KEY_M", "KEY_E"} |
| for _, key := range keysPattern { |
| keyRe := regexp.MustCompile(fmt.Sprintf(`\(%s\).*value 0`, key)) |
| match := keyRe.FindAllString(string(catOutput), -1) |
| if len(match) == 0 { |
| return errors.Errorf("failed to press On-board Keyboard %q key", key) |
| } |
| } |
| return nil |
| } |