blob: 39fabca1a0a7c0a2c0d147d3e93c18c23c044280 [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 camera
import (
"bufio"
"context"
"os"
"path"
"regexp"
"strconv"
"time"
"chromiumos/tast/common/camera/chart"
"chromiumos/tast/common/media/caps"
"chromiumos/tast/common/testexec"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/remote/bundles/cros/camera/pre"
pb "chromiumos/tast/services/cros/camerabox"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
type itsParam struct {
// Scene is the ITS test scene number.
Scene int
// Facing is the test camera facing.
Facing pb.Facing
// ChartPath specify the chart path in ITS zip file. Maybe empty for no need to display chart.
ChartPath string
}
type rule struct {
pat *regexp.Regexp
handler func([]string) error
}
// match checks if the |line| matches this rule and runs corresponding handler
// when matched. Returns boolean for whether the rule is matched.
func (r *rule) match(line string) (bool, error) {
m := r.pat.FindStringSubmatch(line)
if len(m) == 0 {
return false, nil
}
if err := r.handler(m[1:]); err != nil {
return false, errors.Wrapf(err, "failed to execute rule handler with pattern %v", r.pat)
}
return true, nil
}
func init() {
testing.AddTest(&testing.Test{
Func: ITS,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Verifies camera HAL3 interface function on remote DUT",
Contacts: []string{"inker@chromium.org", "chromeos-camera-eng@google.com"},
Attr: []string{"group:camerabox"},
Data: []string{"adb", pre.SetupITSRepoScript, pre.ITSPy3Patch},
Vars: []string{"chart"},
SoftwareDeps: []string{"chrome", "android_p", "arc_camera3", caps.BuiltinCamera},
ServiceDeps: []string{"tast.cros.camerabox.ITSService"},
Timeout: 15 * time.Minute,
Params: []testing.Param{
// X86
{
Name: "scene0_back_x86",
ExtraAttr: []string{"camerabox_facing_back"},
ExtraData: append([]string{pre.CtsVerifierX86Zip}),
ExtraHardwareDeps: hwdep.D(hwdep.X86()),
Pre: pre.ITSX86Pre,
Val: itsParam{0, pb.Facing_FACING_BACK, ""},
},
},
})
}
// sceneResult is the summary of test result under same test scene.
type sceneResult struct {
// name is the test scene name.
name string
// total is the non-skipped test item count.
total int
// pass is the passed test item count.
pass int
}
func ITS(ctx context.Context, s *testing.State) {
// Reserve extra time for cleanup.
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, time.Minute)
defer cancel()
its := (s.PreValue().(*pre.ITSHelper))
param := s.Param().(itsParam)
camID, err := its.CameraID(ctx, param.Facing)
if err != nil {
s.Fatalf("Failed to get camera id of camera facing %s: %v", param.Facing, err)
}
// Prepare test scene chart.
if len(param.ChartPath) > 0 {
testing.ContextLogf(ctx, "Displaying chart for scene%d", param.Scene)
var altHostname string
if hostname, ok := s.Var("chart"); ok {
altHostname = hostname
}
c, namePaths, err := chart.New(ctx, s.DUT(), altHostname, s.OutDir(), []string{param.ChartPath})
if err != nil {
s.Fatal("Failed to prepare chart tablet: ", err)
}
defer func(ctx context.Context) {
if err := c.Close(ctx, s.OutDir()); err != nil {
s.Error("Failed to cleanup chart: ", err)
}
}(cleanupCtx)
if err := c.Display(ctx, namePaths[0]); err != nil {
s.Fatal("Failed to display chart on chart tablet: ", err)
}
}
testing.ContextLog(ctx, "Running ITS")
cmd := its.TestCmd(ctx, param.Scene, camID)
stdout, err := cmd.StdoutPipe()
if err != nil {
s.Fatal("Failed to get stdout pipe of ITS command: ", err)
}
defer stdout.Close()
stdoutLog, err := os.Create(path.Join(s.OutDir(), "its_stdout.log"))
if err != nil {
s.Fatal("Failed to create ITS stdout log file: ", err)
}
defer stdoutLog.Close()
stderrLog, err := os.Create(path.Join(s.OutDir(), "its_stderr.log"))
if err != nil {
s.Fatal("Failed to create ITS stderr log file: ", err)
}
defer stderrLog.Close()
cmd.Stderr = stderrLog
sceneResults := make(chan *sceneResult, 1)
itsLogPath := ""
// Goroutine for pulling stdout, the main thread waits for cmd exiting.
go func() {
defer close(sceneResults)
result := sceneResult{"", -1, -1}
// Rules for parsing lines from stdout/stderr of ITS test with |pat| and run the corresponding |handler|.
// Patterns are reference from https://cs.android.com/android/platform/superproject/+/android-9.0.0_r16:cts/apps/CameraITS/tools/run_all_tests.py
sequentialRules := []rule{
{regexp.MustCompile(`Saving output files to: (\S+)`), func(m []string) error {
itsLogPath = m[0]
testing.ContextLog(ctx, "ITS output log: ", itsLogPath)
return nil
}},
{regexp.MustCompile(`Start running ITS on camera \d+, (scene\d+)`), func(m []string) error {
result.name = m[0]
testing.ContextLog(ctx, "Starting test scene: ", result.name)
return nil
}},
{regexp.MustCompile(`(\d+) / (\d+) tests passed`), func(m []string) error {
pass, err := strconv.Atoi(m[0])
if err != nil {
return errors.Wrapf(err, "failed to convert parsed test passed count %v to number ", m[0])
}
total, err := strconv.Atoi(m[1])
if err != nil {
return errors.Wrapf(err, "failed to convert parsed test total count %v to number ", m[1])
}
testing.ContextLogf(ctx, "Test result %d passed in total %d tests", pass, total)
result.pass = pass
result.total = total
return nil
}},
}
repeatedRule := rule{regexp.MustCompile(`(\S+\s+scene\d+/\S+\s+\[[\d.]+s\])`), func(m []string) error {
// Example: SKIP scene0/test_unified_timestamps [1.9s]
testing.ContextLog(ctx, m[0])
return nil
}}
ruleIndex := 0
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
stdoutLog.Write([]byte(line + "\n"))
if ruleIndex < len(sequentialRules) {
r := sequentialRules[ruleIndex]
m, err := r.match(line)
if err != nil {
s.Fatal("Failed to match rule: ", err)
}
if m {
ruleIndex++
}
}
if _, err := repeatedRule.match(line); err != nil {
s.Fatal("Failed to match rule: ", err)
}
}
if err := scanner.Err(); err != nil {
s.Error("Encountered error when scanning output: ", err)
}
if result.name == "" {
s.Fatal("No result name presented")
}
if result.total == -1 {
s.Fatal("No result total number presented")
}
if result.total == -1 {
s.Fatal("No result pass number presented")
}
sceneResults <- &result
}()
// TODO(b/169303835): Collect /var/log/messages during test run.
if err := cmd.Start(); err != nil {
s.Fatal("Failed to start ITS: ", err)
}
defer func(ctx context.Context) {
if err := cmd.Wait(testexec.DumpLogOnError); err != nil {
s.Fatal("Failed to run ITS: ", err)
}
}(ctx)
defer func(ctx context.Context) {
if itsLogPath == "" {
s.Error("No ITS log path")
return
}
p := path.Join(s.OutDir(), "its")
if err := os.Rename(itsLogPath, p); err != nil {
s.Errorf("Failed to move ITS log path %v to test output directory: %v", itsLogPath, err)
}
}(cleanupCtx)
r, ok := <-sceneResults
if ok {
if r.total != r.pass {
s.Errorf("Failed in test scene %v with pass rate %d/%d", r.name, r.pass, r.total)
}
} else {
s.Error("No test result from ITS")
}
}