blob: 9f5b597f50e3ba8f8e414b74fd9498ca51f6be18 [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 graphics
import (
"context"
"fmt"
"image"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"chromiumos/tast/common/perf"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/input"
"chromiumos/tast/local/screenshot"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: VTSwitch,
LacrosStatus: testing.LacrosVariantUnknown,
Desc: "Switch between VT-2 shell and GUI multiple times",
Contacts: []string{"ambalavanan.m.m@intel.com", "intel-chrome-system-automation-team@intel.com"},
SoftwareDeps: []string{"chrome"},
//TODO(198837833): Remove hwdep.InternalKeyboard() and use argument to frecon to do vt switching instead of typing keys.
HardwareDeps: hwdep.D(hwdep.InternalDisplay(), hwdep.InternalKeyboard()),
Fixture: "chromeGraphics",
Params: []testing.Param{{
Name: "smoke",
ExtraAttr: []string{"group:mainline", "informational", "group:graphics", "graphics_nightly"},
Val: 2,
}, {
Name: "stress",
ExtraAttr: []string{"group:graphics", "graphics_weekly"},
Val: 25,
Timeout: 22 * time.Minute,
}},
})
}
const (
waitTime = 5 * time.Second
samenessRatio = 0.05
)
var (
perceptualDiffRe = regexp.MustCompile((`(\d+) pixels are different`))
)
func inputCheck(ctx context.Context) (*input.KeyboardEventWriter, error) {
kb, err := input.Keyboard(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to open the keyboard")
}
return kb, nil
}
func openVT1(ctx context.Context) error {
testing.ContextLog(ctx, "Switching to VT1")
kb, err := inputCheck(ctx)
if err != nil {
return errors.Wrap(err, "failed to open VT1")
}
keyboardKey := "ctrl+alt+back"
if err = kb.Accel(ctx, keyboardKey); err != nil {
return errors.Wrapf(err, "failed to press key %q", keyboardKey)
}
// Allowing some wait time for switching to happen.
// TODO(b:198837833): Replace with testing.Poll to query the current vts node.
if err = testing.Sleep(ctx, waitTime); err != nil {
return errors.Wrap(err, "error while waiting for switching to VT1")
}
return nil
}
func openVT2(ctx context.Context) error {
testing.ContextLog(ctx, "Switching to VT2")
kb, err := inputCheck(ctx)
if err != nil {
return errors.Wrap(err, "failed to open VT2")
}
keyboardKey := "ctrl+alt+refresh"
if err = kb.Accel(ctx, keyboardKey); err != nil {
return errors.Wrapf(err, "failed to press key %q", keyboardKey)
}
// Allowing some wait time for switching to happen.
// TODO(b:198837833): Replace with testing.Poll to query the current vts node.
if err = testing.Sleep(ctx, waitTime); err != nil {
return errors.Wrap(err, "error while waiting for switching to VT2")
}
return nil
}
func savePerf(number float64, name, unit string, pv *perf.Values) {
direction := perf.BiggerIsBetter
if unit == "percent" {
direction = perf.SmallerIsBetter
}
pv.Set(perf.Metric{
Name: name,
Unit: unit,
Direction: direction,
}, float64(number))
}
// isPerceptuallySame opens a terminal and runs perceptualdiff between two images.
func isPerceptuallySame(ctx context.Context, file1, file2 string, thresholdRatio float64) (bool, float64, error) {
numPix := 0.0
convErr := error(nil)
isSame := false
fs, err := os.Open(file1)
defer fs.Close()
if err != nil {
return isSame, numPix, errors.Wrap(err, "failed to open vt1 file to compare")
}
img, _, err := image.Decode(fs)
if err != nil {
return isSame, numPix, errors.Wrap(err, "failed to decode vt1 image")
}
imagePixels := img.Bounds().Max.X * img.Bounds().Max.Y
thresholdPixels := fmt.Sprintf("%f", thresholdRatio*float64(imagePixels))
stdout, stderr, err := testexec.CommandContext(ctx, "perceptualdiff", "-verbose", "-threshold", thresholdPixels, file1, file2).SeparatedOutput(testexec.DumpLogOnError)
// If images were different this regex would have some match.
matched := perceptualDiffRe.FindStringSubmatch(string(stdout))
// If some error occurred and it was not due to images being different.
if (err != nil && len(matched) == 0) || len(string(stderr)) > 0 {
return isSame, numPix, errors.Wrap(err, "error occurred while running perceptual diff")
}
// Try to find the number of pixels different, if its in the output.
if len(matched) > 1 {
differentPixels := matched[1]
numPix, convErr = strconv.ParseFloat(differentPixels, 64)
// If converting from string to int didn't work raise a error.
if convErr != nil {
return isSame, numPix, errors.Wrap(err, "failed to convert pixels from string to int")
}
}
pixelDifferenceRatio := (float64(numPix) / float64(imagePixels))
// At this stage the command has ran successfully and can either be a match or no match.
isSame = strings.Contains(string(stdout), "PASS") && pixelDifferenceRatio < thresholdRatio
return isSame, pixelDifferenceRatio, nil
}
func max(first, second float64) float64 {
if first > second {
return first
}
return second
}
// VTSwitch will switch between VT-1 and VT-2 for multiple times.
func VTSwitch(ctx context.Context, s *testing.State) {
iterations := s.Param().(int)
s.Logf("No. of iterations: %d", iterations)
numErrors := 0
_ = s.FixtValue().(*chrome.Chrome)
defer func(ctx context.Context) {
if err := openVT1(ctx); err != nil {
s.Fatal("Failed to open VT1: ", err)
}
}(ctx)
// Make sure we start in VT1.
if err := openVT1(ctx); err != nil {
s.Fatal("Failed to open VT1: ", err)
}
// Take VT1 screenshot
vt1Screenshot := filepath.Join(s.OutDir(), "Initial_VTSwitch_VT1.png")
if err := screenshot.Capture(ctx, vt1Screenshot); err != nil {
s.Error("Failed to take screenshot: ", err)
}
// Go to VT2 and take screenshot
if err := openVT2(ctx); err != nil {
s.Fatal("Failed to open VT2: ", err)
}
vt2Screenshot := filepath.Join(s.OutDir(), "Initial_VTSwitch_VT2.png")
if err := screenshot.Capture(ctx, vt2Screenshot); err != nil {
s.Error("Failed to take screenshot: ", err)
}
// Make sure VT1 and VT2 are sufficiently different.
isSame, initialRatio, err := isPerceptuallySame(ctx, vt1Screenshot, vt2Screenshot, 0.0)
samenessThreshold := samenessRatio * initialRatio
s.Logf("The initial samenessThreshold is %f and the initialRatio is %f", samenessThreshold, samenessRatio)
if err != nil {
s.Fatal("Error occurred while comparing Initial VT1 and VT2 screenshots")
}
if isSame {
numErrors++
s.Fatal("Initial VT1 and VT2 screenshots are perceptually similar")
}
pv := perf.NewValues()
defer func() {
if err := pv.Save(s.OutDir()); err != nil {
s.Error("Failed to save perf data: ", err)
}
}()
var identicalScreenshots [3]int
var maxDifferenceRatio [3]float64
captureAndCompare := func(vt, id int, original string) {
fileName := fmt.Sprintf("VTSwitch_VT%d_%d.png", vt, id)
currtVTScreenshot := filepath.Join(s.OutDir(), fileName)
if err := screenshot.Capture(ctx, currtVTScreenshot); err != nil {
s.Error("Failed to take screenshot: ", err)
}
isSame, diffPixelsRatio, err := isPerceptuallySame(ctx, original, currtVTScreenshot, samenessThreshold)
if err != nil {
s.Errorf("Perceptual difference failed to run when testing Initial and current VT%d in iteration %d, %d", vt, id, err)
return
}
if isSame {
identicalScreenshots[vt]++
err := os.Remove(currtVTScreenshot)
if err != nil {
s.Errorf("Error deleting file %s", currtVTScreenshot)
}
} else {
s.Errorf("Initial and current VT %d are different in iteration %d by %f ratio, whereas the ratio must be less than %f", vt, id, diffPixelsRatio, samenessThreshold)
maxDifferenceRatio[vt] = max(maxDifferenceRatio[vt], diffPixelsRatio)
}
return
}
// Repeatedly switch between VT1 and VT2 images.
for i := 0; i < iterations; i++ {
if err := openVT1(ctx); err != nil {
s.Fatalf("Failed to open vt1 at iteration %d", i)
}
captureAndCompare(1, i, vt1Screenshot)
if err := openVT2(ctx); err != nil {
s.Fatalf("Failed to open vt2 at iteration %d", i)
}
captureAndCompare(2, i, vt2Screenshot)
}
savePerf(100.00*maxDifferenceRatio[1], "percent_VT1_screenshot_max_difference", "percent", pv)
savePerf(100.00*maxDifferenceRatio[2], "percent_VT2_screenshot_max_difference", "percent", pv)
savePerf(float64(identicalScreenshots[1]), "num_identical_vt1_screenshots", "count", pv)
savePerf(float64(identicalScreenshots[2]), "num_identical_vt2_screenshots", "count", pv)
if numErrors > 0 {
s.Fatalf("Failed %d/%d switches", numErrors, iterations)
}
}