blob: a819c8a581d9dfab5cfd209dbb99b80638bb4261 [file] [log] [blame]
// Copyright 2021 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 power
import (
"context"
"regexp"
"strconv"
"time"
"chromiumos/tast/common/chameleon"
"chromiumos/tast/common/testexec"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
type displayTestParams struct {
tabletMode bool
displayInfoRe map[string]*regexp.Regexp
}
func init() {
testing.AddTest(&testing.Test{
Func: DisplayDetection,
Desc: "Verifies external display detection",
Contacts: []string{"pathan.jilani@intel.com", "intel-chrome-system-automation-team@intel.com"},
SoftwareDeps: []string{"chrome"},
Vars: []string{
"power.iterations",
"power.chameleon_addr",
"power.chameleon_display_port",
},
// To skip on duffy(Chromebox) with no internal display.
HardwareDeps: hwdep.D(hwdep.InternalDisplay()),
Params: []testing.Param{{
Name: "dp_clamshell_mode",
Fixture: "chromeLoggedIn",
Val: displayTestParams{
tabletMode: false,
displayInfoRe: map[string]*regexp.Regexp{
"connectorInfoPtrns": regexp.MustCompile(`.*: connectors:\n.\s+\[CONNECTOR:\d+:[DP]+.*`),
"connectedPtrns": regexp.MustCompile(`\[CONNECTOR:\d+:DP.*status: connected`),
"modesPtrns": regexp.MustCompile(`modes:\n.*"1920x1080":.60`),
},
},
Timeout: 10 * time.Minute,
}, {
Name: "dp_tablet_mode",
Fixture: "chromeLoggedIn",
Val: displayTestParams{
tabletMode: true,
displayInfoRe: map[string]*regexp.Regexp{
"connectorInfoPtrns": regexp.MustCompile(`.*: connectors:\n.\s+\[CONNECTOR:\d+:[DP]+.*`),
"connectedPtrns": regexp.MustCompile(`\[CONNECTOR:\d+:DP.*status: connected`),
"modesPtrns": regexp.MustCompile(`modes:\n.*"1920x1080":.60`),
},
},
Timeout: 10 * time.Minute,
}, {
Name: "hdmi_clamshell_mode",
Fixture: "chromeLoggedIn",
Val: displayTestParams{
tabletMode: false,
displayInfoRe: map[string]*regexp.Regexp{
"connectorInfoPtrns": regexp.MustCompile(`.*: connectors:\n.\s+\[CONNECTOR:\d+:[HDMI]+.*`),
"connectedPtrns": regexp.MustCompile(`\[CONNECTOR:\d+:HDMI.*status: connected`),
"modesPtrns": regexp.MustCompile(`modes:\n.*"1920x1080":.60`),
},
},
Timeout: 10 * time.Minute,
}, {
Name: "hdmi_tablet_mode",
Fixture: "chromeLoggedIn",
Val: displayTestParams{
tabletMode: true,
displayInfoRe: map[string]*regexp.Regexp{
"connectorInfoPtrns": regexp.MustCompile(`.*: connectors:\n.\s+\[CONNECTOR:\d+:[HDMI]+.*`),
"connectedPtrns": regexp.MustCompile(`\[CONNECTOR:\d+:HDMI.*status: connected`),
"modesPtrns": regexp.MustCompile(`modes:\n.*"1920x1080":.60`),
},
},
Timeout: 10 * time.Minute,
}},
})
}
func DisplayDetection(ctx context.Context, s *testing.State) {
cr := s.FixtValue().(*chrome.Chrome)
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to connect to test API: ", err)
}
testOpt := s.Param().(displayTestParams)
iterCount := 2 // Use default iteration 2 to plug-unplug external display.
if iter, ok := s.Var("power.iterations"); ok {
if iterCount, err = strconv.Atoi(iter); err != nil {
s.Fatalf("Failed to parse iteration value %q: %v", iter, err)
}
}
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 10*time.Second)
defer cancel()
if testOpt.tabletMode {
cleanup, err := ash.EnsureTabletModeEnabled(ctx, tconn, true)
if err != nil {
s.Fatal("Failed to enable tablet mode: ", err)
}
defer cleanup(cleanupCtx)
}
chameleonAddr := s.RequiredVar("power.chameleon_addr")
// Use chameleon board as extended display. Make sure chameleon is connected.
che, err := chameleon.New(ctx, chameleonAddr)
if err != nil {
s.Fatal("Failed to connect to chameleon board: ", err)
}
defer che.Close(cleanupCtx)
portID := 3 // Use default port 3 for display.
if port, ok := s.Var("power.chameleon_display_port"); ok {
if portID, err = strconv.Atoi(port); err != nil {
s.Fatalf("Failed to parse chameleon display port %q: %v", port, err)
}
}
dp, err := che.NewPort(ctx, portID)
if err != nil {
s.Fatalf("Failed to create chameleon port %d: %v", portID, err)
}
// Cleanup
defer dp.Unplug(cleanupCtx)
for i := 1; i <= iterCount; i++ {
s.Logf("Iteration: %d/%d", i, iterCount)
s.Log("Plugging external display to DUT")
if err := dp.Plug(ctx); err != nil {
s.Fatal("Failed to plug chameleon port: ", err)
}
s.Log("chameleon plugged successfully")
// Wait for DUT to detect external display.
if err := dp.WaitVideoInputStable(ctx, 10*time.Second); err != nil {
s.Fatal("Failed to wait for video input on chameleon board: ", err)
}
displayInfoPatterns := []*regexp.Regexp{
testOpt.displayInfoRe["connectorInfoPtrns"],
testOpt.displayInfoRe["connectedPtrns"],
testOpt.displayInfoRe["modesPtrns"],
}
if err := waitForExternalMonitorCount(ctx, 1, displayInfoPatterns); err != nil {
s.Fatal("Failed connecting external display: ", err)
}
if err := dp.Unplug(ctx); err != nil {
s.Fatal("Failed to unplug chameleon port: ", err)
}
if err := waitForExternalMonitorCount(ctx, 0, nil); err != nil {
s.Fatal("Failed unplugging external display: ", err)
}
s.Log("chameleon unplugged successfully")
}
}
// waitForExternalMonitorCount verifies for the connected numberOfDisplays.
func waitForExternalMonitorCount(ctx context.Context, numberOfDisplays int, regexpPatterns []*regexp.Regexp) error {
const DisplayInfoFile = "/sys/kernel/debug/dri/0/i915_display_info"
// This regexp will skip pipe A since that's the internal display detection.
displayInfo := regexp.MustCompile(`.*pipe\s+[BCD]\]:\n.*active=yes, mode=.[0-9]+x[0-9]+.: [0-9]+.*\s+[hw: active=yes]+`)
if err := testing.Poll(ctx, func(ctx context.Context) error {
out, err := testexec.CommandContext(ctx, "cat", DisplayInfoFile).Output()
if err != nil {
return errors.Wrap(err, "failed to run display info command ")
}
matchedString := displayInfo.FindAllString(string(out), -1)
if len(matchedString) != numberOfDisplays {
return errors.New("connected external display info not found")
}
if regexpPatterns != nil {
for _, pattern := range regexpPatterns {
if !pattern.MatchString(string(out)) {
return errors.Errorf("failed %q error message", pattern)
}
}
}
return nil
}, &testing.PollOptions{
Timeout: 15 * time.Second,
}); err != nil {
return errors.Wrap(err, "please connect external display as required")
}
return nil
}