blob: e51d581b5b4ae59dacc236186baf97ca72a7f823 [file] [log] [blame]
// Copyright 2022 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 fixture contains Telemetry Extension fixture.
package fixture
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"chromiumos/tast/common/fixture"
"chromiumos/tast/common/policy"
"chromiumos/tast/common/policy/fakedms"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/fsutil"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/policyutil"
"chromiumos/tast/local/sysutil"
"chromiumos/tast/local/upstart"
"chromiumos/tast/testing"
)
const (
cleanupTimeout = chrome.ResetTimeout + 20*time.Second
crosHealthdJobName = "cros_healthd"
)
func init() {
testing.AddFixture(&testing.Fixture{
Name: "telemetryExtension",
Desc: "Telemetry Extension fixture with running PWA and companion Telemetry Extension",
Contacts: []string{
"lamzin@google.com", // Fixture and Telemetry Extension author
"mgawad@google.com", // Telemetry Extension author
"cros-oem-services-team@google.com",
},
Impl: newTelemetryExtensionFixture(),
SetUpTimeout: chrome.LoginTimeout + 30*time.Second + cleanupTimeout,
TearDownTimeout: cleanupTimeout,
PreTestTimeout: 10 * time.Second,
PostTestTimeout: 10 * time.Second,
Data: extFiles(false),
})
testing.AddFixture(&testing.Fixture{
Name: "telemetryExtensionOptionsPage",
Desc: "Telemetry Extension fixture with running PWA and companion Telemetry Extension with options page",
Contacts: []string{
"lamzin@google.com", // Fixture and Telemetry Extension author
"mgawad@google.com", // Telemetry Extension author
"cros-oem-services-team@google.com",
},
Impl: newTelemetryExtensionFixture(optionsPage()),
SetUpTimeout: chrome.LoginTimeout + 30*time.Second + cleanupTimeout,
TearDownTimeout: cleanupTimeout,
PreTestTimeout: 10 * time.Second,
PostTestTimeout: 10 * time.Second,
Data: extFiles(true),
})
testing.AddFixture(&testing.Fixture{
Name: "telemetryExtensionManaged",
Desc: "Telemetry Extension fixture with running PWA and companion Telemetry Extension for managed device",
Contacts: []string{
"lamzin@google.com", // Fixture and Telemetry Extension author
"mgawad@google.com", // Telemetry Extension author
"cros-oem-services-team@google.com",
},
Impl: newTelemetryExtensionFixture(managed()),
Parent: fixture.FakeDMSEnrolled,
SetUpTimeout: chrome.LoginTimeout + 30*time.Second + cleanupTimeout,
TearDownTimeout: cleanupTimeout,
PreTestTimeout: 10 * time.Second,
PostTestTimeout: 10 * time.Second,
Vars: []string{"policy.ManagedUser.accountPool"},
})
}
func manifestFile(optionsPage bool) string {
if optionsPage {
return "manifest_with_options_page.json"
}
return "manifest_without_options_page.json"
}
func extFiles(optionsPage bool) []string {
files := []string{manifestFile(optionsPage), "sw.js"}
if optionsPage {
files = append(files, "options.html")
}
return files
}
type option func(*telemetryExtensionFixture)
func optionsPage() func(*telemetryExtensionFixture) {
return func(f *telemetryExtensionFixture) {
f.optionsPage = true
}
}
func managed() func(*telemetryExtensionFixture) {
return func(f *telemetryExtensionFixture) {
f.managed = true
}
}
func newTelemetryExtensionFixture(opts ...option) *telemetryExtensionFixture {
f := &telemetryExtensionFixture{}
f.v.ExtID = "gogonhoemckpdpadfnjnpgbjpbjnodgc"
for _, opt := range opts {
opt(f)
}
return f
}
// telemetryExtensionFixture implements testing.FixtureImpl.
type telemetryExtensionFixture struct {
optionsPage bool
managed bool
dir string
cr *chrome.Chrome
healthdPID int
v Value
}
// Value is a value exposed by fixture to tests.
type Value struct {
ExtID string
PwaConn *chrome.Conn
ExtConn *chrome.Conn
TConn *chrome.TestConn
}
func (f *telemetryExtensionFixture) SetUp(ctx context.Context, s *testing.FixtState) interface{} {
cleanupCtx, cancel := ctxutil.Shorten(ctx, cleanupTimeout)
defer cancel()
defer func(ctx context.Context) {
if s.HasError() {
f.TearDown(ctx, s)
}
}(cleanupCtx)
if f.managed {
fdms, ok := s.ParentValue().(*fakedms.FakeDMS)
if !ok {
s.Fatal("Parent is not a FakeDMS fixture")
}
gaiaCreds, err := chrome.PickRandomCreds(s.RequiredVar("policy.ManagedUser.accountPool"))
if err != nil {
s.Fatal("Failed to parse managed user creds: ", err)
}
if err := f.setupChromeForManagedUsers(ctx, fdms, gaiaCreds.User, gaiaCreds.Pass); err != nil {
s.Fatal("Failed to setup Chrome for managed users: ", err)
}
} else {
if err := f.setupChromeForConsumers(ctx, s.DataPath); err != nil {
s.Fatal("Failed to setup Chrome for consumers: ", err)
}
}
pwaConn, err := f.cr.NewConn(ctx, "https://www.google.com")
if err != nil {
s.Fatal("Failed to create connection to google.com: ", err)
}
f.v.PwaConn = pwaConn
if err := chrome.AddTastLibrary(ctx, pwaConn); err != nil {
s.Fatal("Failed to add Tast library to google.com: ", err)
}
extConn, err := f.cr.NewConn(ctx, fmt.Sprintf("chrome-extension://%s/sw.js", f.v.ExtID))
if err != nil {
s.Fatal("Failed to create connection to Telemetry Extension: ", err)
}
f.v.ExtConn = extConn
if err := chrome.AddTastLibrary(ctx, extConn); err != nil {
s.Fatal("Failed to add Tast library to Telemetry Extension: ", err)
}
tconn, err := f.cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to get test API connections: ", err)
}
f.v.TConn = tconn
if err := upstart.EnsureJobRunning(ctx, crosHealthdJobName); err != nil {
s.Fatalf("Failed to start %s daemon", crosHealthdJobName)
}
return &f.v
}
func (f *telemetryExtensionFixture) TearDown(ctx context.Context, s *testing.FixtState) {
if f.v.ExtConn != nil {
if err := f.v.ExtConn.Close(); err != nil {
s.Error("Failed to close connection to Telemetry Extension: ", err)
}
f.v.ExtConn = nil
}
if f.v.PwaConn != nil {
if err := f.v.PwaConn.Close(); err != nil {
s.Error("Failed to close connection to google.com: ", err)
}
f.v.PwaConn = nil
}
if f.cr != nil {
if err := f.cr.Close(ctx); err != nil {
s.Error("Failed to close Chrome: ", err)
}
f.cr = nil
}
if f.dir != "" {
if err := os.RemoveAll(f.dir); err != nil {
s.Error("Failed to remove directory with Telemetry Extension: ", err)
}
f.dir = ""
}
}
func (f *telemetryExtensionFixture) PreTest(ctx context.Context, s *testing.FixtTestState) {
_, _, pid, err := upstart.JobStatus(ctx, crosHealthdJobName)
if err != nil {
s.Fatalf("Unable to get %s PID: %s", crosHealthdJobName, err)
}
f.healthdPID = pid
}
func (f *telemetryExtensionFixture) PostTest(ctx context.Context, s *testing.FixtTestState) {
_, _, pid, err := upstart.JobStatus(ctx, crosHealthdJobName)
if err != nil {
s.Fatalf("Unable to get %s PID: %s", crosHealthdJobName, err)
}
if pid != f.healthdPID {
s.Fatalf("%s PID changed: got %d, want %d", crosHealthdJobName, pid, f.healthdPID)
}
}
func (f *telemetryExtensionFixture) Reset(ctx context.Context) error {
return nil
}
func (f *telemetryExtensionFixture) setupChromeForConsumers(ctx context.Context, dataPathFunc func(string) string) error {
dir, err := ioutil.TempDir("", "telemetry_extension")
if err != nil {
return errors.Wrap(err, "failed to create temporary directory for TelemetryExtension")
}
f.dir = dir
if err := os.Chown(dir, int(sysutil.ChronosUID), int(sysutil.ChronosGID)); err != nil {
return errors.Wrap(err, "failed to chown TelemetryExtension dir")
}
for _, file := range extFiles(f.optionsPage) {
if err := fsutil.CopyFile(dataPathFunc(file), filepath.Join(dir, file)); err != nil {
return errors.Wrapf(err, "failed to copy %q file to %q", file, dir)
}
if err := os.Chown(filepath.Join(dir, file), int(sysutil.ChronosUID), int(sysutil.ChronosGID)); err != nil {
return errors.Wrapf(err, "failed to chown %q", file)
}
}
if err := os.Rename(filepath.Join(dir, manifestFile(f.optionsPage)), filepath.Join(dir, "manifest.json")); err != nil {
return errors.Wrap(err, "failed to rename manifest file")
}
cr, err := chrome.New(ctx, chrome.UnpackedExtension(dir))
if err != nil {
return errors.Wrap(err, "failed to start Chrome")
}
f.cr = cr
return nil
}
func (f *telemetryExtensionFixture) setupChromeForManagedUsers(ctx context.Context, fdms *fakedms.FakeDMS, username, password string) error {
pb := policy.NewBlob()
pb.PolicyUser = username
// Telemetry Extension works only for affiliated users.
pb.DeviceAffiliationIds = []string{"default_affiliation_id"}
pb.UserAffiliationIds = []string{"default_affiliation_id"}
// We have to update fake DMS policy user and affiliation IDs before starting Chrome.
if err := fdms.WritePolicyBlob(pb); err != nil {
return errors.Wrap(err, "failed to write policy blob before starting Chrome")
}
cr, err := chrome.New(ctx,
chrome.KeepEnrollment(),
chrome.GAIALogin(chrome.Creds{User: username, Pass: password}),
chrome.DMSPolicy(fdms.URL),
chrome.CustomLoginTimeout(chrome.ManagedUserLoginTimeout))
if err != nil {
return errors.Wrap(err, "Chrome startup failed")
}
f.cr = cr
// Force install Telemetry Extension by policy.
pb.AddPolicy(&policy.ExtensionInstallForcelist{Val: []string{f.v.ExtID}})
// Allow DevTools on force installed extensions. Value 1 here means "allowed".
pb.AddPolicy(&policy.DeveloperToolsAvailability{Val: 1})
if err := policyutil.ServeBlobAndRefresh(ctx, fdms, cr, pb); err != nil {
return errors.Wrap(err, "failed to serve and refresh")
}
return nil
}