blob: 45b4e4e4e70dc323fe63d159b796e649bc85cf55 [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 lacrosfixt
import (
"context"
"path/filepath"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/fsutil"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/lacros"
"chromiumos/tast/testing"
"chromiumos/tast/timing"
)
// LacrosDeployedBinary contains the Fixture Var necessary to run lacros.
// This should be used by any lacros fixtures defined outside this file.
const LacrosDeployedBinary = "lacrosDeployedBinary"
func init() {
// lacros uses rootfs lacros, which is the recommend way to use lacros
// in Tast tests, unless you have a specific use case for using lacros from
// another source.
testing.AddFixture(&testing.Fixture{
Name: "lacros",
Desc: "Lacros Chrome from a pre-built image",
Contacts: []string{"hyungtaekim@chromium.org", "lacros-team@google.com"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 1*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosAudio is the same as lacros but has some special flags for audio
// tests.
testing.AddFixture(&testing.Fixture{
Name: "lacrosAudio",
Desc: "Lacros Chrome from a pre-built image with camera/microphone permissions",
Contacts: []string{"hidehiko@chromium.org", "edcourtney@chromium.org"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s, ChromeOptions(
chrome.ExtraArgs("--use-fake-ui-for-media-stream"),
chrome.ExtraArgs("--autoplay-policy=no-user-gesture-required"), // Allow media autoplay.
chrome.LacrosExtraArgs("--use-fake-ui-for-media-stream"),
chrome.LacrosExtraArgs("--autoplay-policy=no-user-gesture-required"))).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosWith100FakeApps is the same as lacros but
// creates 100 fake apps that are shown in the ash-chrome launcher.
testing.AddFixture(&testing.Fixture{
Name: "lacrosWith100FakeApps",
Desc: "Lacros Chrome from a pre-built image with 100 fake apps installed",
Contacts: []string{"hidehiko@chromium.org", "edcourtney@chromium.org"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s).Opts()
}),
Parent: "install100Apps",
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosForceComposition is the same as lacros but
// forces composition for ash-chrome.
testing.AddFixture(&testing.Fixture{
Name: "lacrosForceComposition",
Desc: "Lacros Chrome from a pre-built image with composition forced on",
Contacts: []string{"hidehiko@chromium.org", "edcourtney@chromium.org"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s, ChromeOptions(chrome.ExtraArgs("--enable-hardware-overlays=\"\""))).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosForceDelegation is the same as lacros but
// forces delegated composition.
testing.AddFixture(&testing.Fixture{
Name: "lacrosForceDelegated",
Desc: "Lacros Chrome from a pre-built image with delegated compositing forced on",
Contacts: []string{"petermcneeley@chromium.org", "edcourtney@chromium.org"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s, ChromeOptions(
chrome.LacrosExtraArgs("--enable-gpu-memory-buffer-compositor-resources"),
chrome.LacrosEnableFeatures("DelegatedCompositing"))).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosWithArcEnabled is the same as lacros but with ARC enabled.
// See also lacrosWithArcBooted in src/chromiumos/tast/local/arc/fixture.go.
testing.AddFixture(&testing.Fixture{
Name: "lacrosWithArcEnabled",
Desc: "Lacros Chrome from a pre-built image with ARC enabled",
Contacts: []string{"amusbach@chromium.org", "xiyuan@chromium.org"},
Impl: NewFixture(lacros.Rootfs, func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return []chrome.Option{chrome.ARCEnabled(),
chrome.ExtraArgs("--disable-lacros-keep-alive"),
chrome.LacrosExtraArgs("--no-first-run")}, nil
}),
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosOmaha is a fixture to enable Lacros by feature flag in Chrome.
// This does not require downloading a binary from Google Storage before the test.
// It will use the currently available fishfood release of Lacros from Omaha.
testing.AddFixture(&testing.Fixture{
Name: "lacrosOmaha",
Desc: "Lacros Chrome from omaha",
Contacts: []string{"hidehiko@chromium.org", "edcourtney@chromium.org"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s, Selection(lacros.Omaha)).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosPrimary is a fixture to bring up Lacros as a primary browser from the rootfs partition by default.
testing.AddFixture(&testing.Fixture{
Name: "lacrosPrimary",
Desc: "Lacros Chrome from rootfs as a primary browser",
Contacts: []string{"hyungtaekim@chromium.org", "lacros-team@google.com"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s, Mode(lacros.LacrosPrimary)).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 1*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosKeepAlive is similar to lacros but should be used
// by tests that will launch lacros from the ChromeOS UI (e.g shelf) instead
// of by command line, and this test assuming that Lacros will be keep alive
// in the background even if the browser is turned off.
testing.AddFixture(&testing.Fixture{
Name: "lacrosKeepAlive",
Desc: "Lacros Chrome from a pre-built image using the UI and the Lacros chrome will stay alive even when the browser terminated",
Contacts: []string{"mxcai@chromium.org", "hidehiko@chromium.org"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s, KeepAlive(true), Mode(lacros.LacrosPrimary)).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
// lacrosVariation is similar to lacros but should be used
// by variation smoke tests that will launch lacros with variation service enabled,
testing.AddFixture(&testing.Fixture{
Name: "lacrosVariationEnabled",
Desc: "Lacros with variation service enabled",
Contacts: []string{"yjt@google.com", "lacros-team@google.com"},
Impl: chrome.NewLoggedInFixture(func(ctx context.Context, s *testing.FixtState) ([]chrome.Option, error) {
return NewConfigFromState(s, Mode(lacros.LacrosPrimary), ChromeOptions(
chrome.LacrosExtraArgs("--fake-variations-channel=beta"),
chrome.LacrosExtraArgs("--variations-server-url=https://clients4.google.com/chrome-variations/seed"))).Opts()
}),
SetUpTimeout: chrome.LoginTimeout + 7*time.Minute,
ResetTimeout: chrome.ResetTimeout,
TearDownTimeout: chrome.ResetTimeout,
Vars: []string{LacrosDeployedBinary},
})
}
const (
// LacrosSquashFSPath indicates the location of the rootfs lacros squashfs filesystem.
LacrosSquashFSPath = "/opt/google/lacros/lacros.squash"
)
// Verify that *fixtValueImpl implements FixtValue interface.
var _ FixtValue = (*fixtValueImpl)(nil)
// fixtValueImpl holds values related to the lacros instance and connection.
// Tests should not use this directly, unless they are composing fixtures
// and need to embed this struct in their own FixtValue. Instead, access
// needed values through the lacros FixtValue interface.
type fixtValueImpl struct {
chrome *chrome.Chrome
testAPIConn *chrome.TestConn
selection lacros.Selection
}
// Chrome gets the CrOS-chrome instance.
func (f *fixtValueImpl) Chrome() *chrome.Chrome {
return f.chrome
}
// TestAPIConn gets the CrOS-chrome test connection.
func (f *fixtValueImpl) TestAPIConn() *chrome.TestConn {
return f.testAPIConn
}
// fixtImpl is a fixture that allows Lacros chrome to be launched.
type fixtImpl struct {
selection lacros.Selection // How (pre exist/to be downloaded/) the container image is obtained.
cr *chrome.Chrome // Connection to CrOS-chrome.
tconn *chrome.TestConn // Test-connection for CrOS-chrome.
prepared bool // Set to true if Prepare() succeeds, so that future calls can avoid unnecessary work.
fOpt chrome.OptionsCallback // Function to generate Chrome Options
makeValue func(v FixtValue, pv interface{}) interface{} // Closure to create FixtValue to return from SetUp. Used for composable fixtures.
}
// SetUp is called by tast before each test is run. We use this method to initialize
// the fixture data, or return early if the fixture is already active.
func (f *fixtImpl) SetUp(ctx context.Context, s *testing.FixtState) interface{} {
ctx, st := timing.Start(ctx, "SetUp")
defer st.End()
if f.prepared {
s.Log("Fixture has already been prepared. Returning a cached one. mode: ", f.selection)
return f.buildFixtData(ctx, s)
}
// If initialization fails, this defer is used to clean-up the partially-initialized pre.
// Stolen verbatim from arc/pre.go
shouldClose := true
defer func() {
if shouldClose {
f.cleanUp(ctx, s)
}
}()
opts, err := f.fOpt(ctx, s)
if err != nil {
s.Fatal("Failed to obtain fixture options: ", err)
}
// If there's a parent fixture and the fixture supplies extra options, use them.
if extraOpts, ok := s.ParentValue().([]chrome.Option); ok {
opts = append(opts, extraOpts...)
}
// Set opts for Lacros based on the selection and the runtime var.
cfg := NewConfigFromState(s, Selection(f.selection))
lacrosOpts, err := cfg.Opts()
if err != nil {
s.Fatal("Failed to set default options: ", err)
}
opts = append(opts, lacrosOpts...)
if f.cr, err = chrome.New(ctx, opts...); err != nil {
s.Fatal("Failed to connect to Chrome: ", err)
}
if f.tconn, err = f.cr.TestAPIConn(ctx); err != nil {
s.Fatal("Failed to create test API connection: ", err)
}
if cfg.deployed {
testing.ContextLog(ctx, "Using lacros located at ", cfg.deployedPath)
}
val := f.buildFixtData(ctx, s)
chrome.Lock()
f.prepared = true
shouldClose = false
return f.makeValue(val, s.ParentValue())
}
// TearDown is called after all tests involving this fixture have been run,
// (or failed to be run if the fixture itself fails). Unlocks Chrome's and
// the container's constructors.
func (f *fixtImpl) TearDown(ctx context.Context, s *testing.FixtState) {
ctx, st := timing.Start(ctx, "TearDown")
defer st.End()
chrome.Unlock()
f.cleanUp(ctx, s)
}
func (f *fixtImpl) Reset(ctx context.Context) error {
if err := f.cr.Responded(ctx); err != nil {
return errors.Wrap(err, "existing Chrome connection is unusable")
}
if err := f.cr.ResetState(ctx); err != nil {
return errors.Wrap(err, "failed resetting existing Chrome session")
}
return nil
}
func (f *fixtImpl) PreTest(ctx context.Context, s *testing.FixtTestState) {}
func (f *fixtImpl) PostTest(ctx context.Context, s *testing.FixtTestState) {
if out, ok := testing.ContextOutDir(ctx); !ok {
testing.ContextLog(ctx, "OutDir not found")
} else {
if err := fsutil.CopyFile(LacrosLogPath, filepath.Join(out, "lacros.log")); err != nil {
testing.ContextLog(ctx, "Failed to save lacros logs: ", err)
}
}
}
// cleanUp de-initializes the fixture by closing/cleaning-up the relevant
// fields and resetting the struct's fields.
func (f *fixtImpl) cleanUp(ctx context.Context, s *testing.FixtState) {
// Nothing special needs to be done to close the test API connection.
f.tconn = nil
if f.cr != nil {
if err := f.cr.Close(ctx); err != nil {
s.Error("Failure closing chrome: ", err)
}
f.cr = nil
}
f.prepared = false
}
// buildFixtData is a helper method that resets the machine state in
// advance of building the fixture data for the actual tests.
func (f *fixtImpl) buildFixtData(ctx context.Context, s *testing.FixtState) *fixtValueImpl {
if err := f.cr.ResetState(ctx); err != nil {
s.Fatal("Failed to reset chrome's state: ", err)
}
return &fixtValueImpl{f.cr, f.tconn, f.selection}
}