blob: b46098394b98f37f37b6515b23383f114a9309a0 [file] [log] [blame]
// Copyright 2020 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 crostini
// This file contains the generators for crostini test
// parameters. Crostini tests generally require multiple sub-tests
// parameterized over how crostini is set up. As these parameters are
// complex and *change over time* you should strongly prefer to use
// these generators rather then copying and pasting from other tests
// or writing your own. This allows for changes to be made in one
// central location (this file) and automatically propagated to all
// tests that rely on them.
//
// Usage:
//
// In general, to generate test parameters for a test you add a unit
// test file to the package containing the test (the name must end in
// "_test.go"). This file should contain a go unit test which creates
// a string containing the test parameters and passes it to
// genparams.Ensure, along with the name of the test file. For
// crostini tests, generate this string by calling one of the
// functions in this file.
//
// By default, this unit test will ensure that the parameters in the
// test match up with the string generated by the unit tests. These
// tests are run on the CQ, so unexpected changes will block CLs from
// landing. When run with TAST_GENERATE_UPDATE=1 set, it will instead
// update the parameters in the test file.
//
// go unit tests can be run using the command
// ~/trunk/src/platform/tast/tools/go.sh test -count=1 chromiumos/tast/local/bundles/...
// parameter regeneration can be done with
// TAST_GENERATE_UPDATE=1 ~/trunk/src/platform/tast/tools/go.sh test -count=1 chromiumos/tast/local/bundles/...
//
// The crostini package has a mega-test in params_test.go which
// generates parameters for most of its tests. If you are adding a
// test to that package, consider if it can be added to that
// file. This should be the case for most tests, which only need the
// "normal" set of parameters. Otherwise, or if you are writing a test
// outside of the crostini package, you will likely need to create a
// new test file. See any of the file in the crostini package ending
// in "_test.go" for examples.
//
// Tests using these generators should not specify timeouts or
// preconditions, as we expect to be able to specify those freely in
// the parameters.
//
// Any test which is controlled by a generator will have a comment
// above its test parameters indicating which file contains the
// relevant unit test. To modify the parameters, update the test and
// run the above command to regenerate the results.
import (
"fmt"
"strings"
"time"
"chromiumos/tast/common/genparams"
"chromiumos/tast/local/vm"
)
// Param specifies how each set of crostini tests should be generated.
type Param struct {
// Name of the test case. Generated tests will look like
// "name_amd64_buster_stable", "name_arm_stretch_unstable"
// etc.
Name string
// ExtraAttr contains additional attributes to add to the
// generated test's ExtraAttr field beyond what the generator
// function adds. For example, if you want this test case to
// be in the "graphics_daily" group without putting the whole
// test in, you can add that label here.
ExtraAttr []string
// ExtraData contains paths of additional data files needed by
// the test case. Note that data files required for specific
// crostini preconditions are added automatically to the
// generated tests and should not be added here.
ExtraData []string
// ExtraSoftwareDeps lists software features that are required
// to run this test case.
ExtraSoftwareDeps []string
// Timeout indicates the timeout for this test case. If
// unspecified, defaults to 7 * time.Minute.
Timeout time.Duration
// Val is a freeform value that can be retrieved from
// testing.State.Param() method. This string is inserted
// unmodified and unquoted into the generated test case code
// as the Val for each test case generated for this object.
Val string
// SelfManagedInstall indicates that this test will be
// installing crostini itself, and therefore there should be
// no crostini install precondition set.
SelfManagedInstall bool
// StableHardwareDep contains a go expression that evaluates
// to a hardware dependency which controls the collection of
// boards considered stable.
StableHardwareDep string
// UnstableHardwareDep contains a go expression that evaluates
// to a hardware dependency which controls the collection of
// boards considered unstable. It should be the inverse of
// StableHardwareDep.
UnstableHardwareDep string
// MinimalSet - if true, generate only a minimal set of test
// parameters such that each device will have at most one test
// case it can run. This is useful for things like performance
// tests, which are too expensive to be run in every possible
// configuration.
MinimalSet bool
// IsNotMainline indicates whether the test case is in
// group:mainline or not. This is important to get right
// because we can't add the "informational" attribute to
// non-mainline tests, but leaving it off of a mainline test
// will cause the test case to become CQ critical. If in
// doubt, set to false, and if you're wrong you will get an
// error message when you try to run your tests.
//
// This also controls whether separate stable/unstable tests
// are generated, since this distinction is only relevant to
// the CQ.
IsNotMainline bool
// UseLargeContainer controls whether to use the normal test
// container, or a larger container with more applications
// pre-installed.
UseLargeContainer bool
// OnlyStableBoards controls whether to only use the stable
// board variants and exclude all the unstable variants.
OnlyStableBoards bool
// UseGaiaLogin controls whether using gaia user to login
// to the DUT.
UseGaiaLogin bool
}
type generatedParam struct {
Name string
ExtraAttr []string
ExtraData []string
ExtraSoftwareDeps []string
ExtraHardwareDeps string
Pre string
Timeout time.Duration
Val string
}
const template = `{{range .}} {
{{if .Name}} Name: {{fmt .Name}}, {{end}}
{{if .ExtraAttr}} ExtraAttr: []string{ {{range .ExtraAttr}}{{fmt .}},{{end}} }, {{end}}
{{if .ExtraData}} ExtraData: []string{ {{range .ExtraData}} {{.}}, {{end}} }, {{end}}
{{if .ExtraSoftwareDeps}} ExtraSoftwareDeps: []string{ {{range .ExtraSoftwareDeps}}{{fmt .}},{{end}} }, {{end}}
{{if .ExtraHardwareDeps}} ExtraHardwareDeps: {{.ExtraHardwareDeps}}, {{end}}
{{if .Pre}} Pre: {{.Pre}}, {{end}}
{{if .Timeout}} Timeout: {{fmt .Timeout}}, {{end}}
{{if .Val}} Val: {{.Val}}, {{end}}
}, {{end}}`
func combineName(first, second string) string {
if first == "" {
return second
}
if second == "" {
return first
}
return first + "_" + second
}
// MakeTestParamsFromList takes a list of test cases (in the form of
// crostini.Param objects) and generates a set of crostini test
// parameters for each. See the documentation for crostini.Param for
// how these values effect the results. Each crostini.Param object is
// treated independently, producing its own set of sub-tests.
//
// Normally you should use MakeTestParams instead, but if your test is
// parameterized beyond which crostini preconditions it uses, you will
// need this.
func MakeTestParamsFromList(t genparams.TestingT, baseCases []Param) string {
var result []generatedParam
type iterator struct {
debianVersion vm.ContainerDebianVersion
stable bool
}
var it = []iterator{}
for _, debianVersion := range []vm.ContainerDebianVersion{vm.DebianStretch, vm.DebianBuster, vm.DebianBullseye} {
for _, stable := range []bool{true, false} {
it = append(it, iterator{
debianVersion: debianVersion,
stable: stable,
})
}
}
for _, testCase := range baseCases {
if testCase.UseLargeContainer && !testCase.MinimalSet {
t.Fatalf("Test %q: Testing apps on stretch is not supported", testCase.Name)
}
// Check here if it's possible for any iteration of
// this test to be critical, i.e. if it doesn't
// already have the "informational" attribute, and is
// a mainline test.
canBeCritical := true
for _, attr := range testCase.ExtraAttr {
if attr == "informational" {
canBeCritical = false
}
}
for _, i := range it {
if (testCase.IsNotMainline || testCase.OnlyStableBoards) && !i.stable {
// The stable/unstable distinction is only important for mainline tests
continue
}
if testCase.MinimalSet && i.debianVersion != vm.DebianBuster {
// The minimal set is currently buster
continue
}
name := testCase.Name
if !testCase.MinimalSet {
// If we're generating a minimal set
// then the debian version is always
// the same and we don't need to
// include it in the test name.
name = combineName(name, string(i.debianVersion))
}
if !testCase.IsNotMainline && !testCase.OnlyStableBoards {
if i.stable {
name = combineName(name, "stable")
} else {
name = combineName(name, "unstable")
}
}
if testCase.UseGaiaLogin {
name = combineName(name, "gaia")
}
// _unstable tests can never be CQ critical.
// stretch is informational while we phase it out.
var extraAttr []string
if (!i.stable || i.debianVersion == vm.DebianStretch) && canBeCritical {
extraAttr = append(extraAttr, "informational")
}
var extraData []string
extraData = append(extraData,
fmt.Sprintf("crostini.GetContainerMetadataArtifact(%q, %t)", i.debianVersion, testCase.UseLargeContainer),
fmt.Sprintf("crostini.GetContainerRootfsArtifact(%q, %t)", i.debianVersion, testCase.UseLargeContainer),
)
// Quote the extra data strings we got passed,
// so we can define the container and VM
// artifacts with runtime functions while
// still taking string literals from the
// outside world.
for _, data := range testCase.ExtraData {
extraData = append(extraData,
fmt.Sprintf("%q", data))
}
var extraSoftwareDeps []string
extraSoftwareDeps = append(extraSoftwareDeps, "dlc")
var hardwareDeps string
if !testCase.IsNotMainline {
if i.stable {
if testCase.StableHardwareDep != "" {
hardwareDeps = testCase.StableHardwareDep
} else if testCase.UseLargeContainer {
hardwareDeps = "crostini.CrostiniAppTest"
} else {
hardwareDeps = "crostini.CrostiniStable"
}
} else {
if testCase.UnstableHardwareDep != "" {
hardwareDeps = testCase.UnstableHardwareDep
} else {
hardwareDeps = "crostini.CrostiniUnstable"
}
}
}
var precondition string
if testCase.SelfManagedInstall {
precondition = ""
} else if testCase.UseLargeContainer {
precondition = fmt.Sprintf("crostini.StartedBy%s%sLargeContainer()", "Dlc", strings.Title(string(i.debianVersion)))
} else if testCase.UseGaiaLogin {
precondition = fmt.Sprintf("crostini.StartedBy%s%sGaia()", "Dlc", strings.Title(string(i.debianVersion)))
} else {
precondition = fmt.Sprintf("crostini.StartedBy%s%s()", "Dlc", strings.Title(string(i.debianVersion)))
}
var timeout time.Duration
if testCase.Timeout != time.Duration(0) {
timeout = testCase.Timeout
} else {
timeout = 7 * time.Minute
}
testParam := generatedParam{
Name: name,
ExtraAttr: append(testCase.ExtraAttr, extraAttr...),
ExtraData: extraData,
ExtraSoftwareDeps: append(testCase.ExtraSoftwareDeps, extraSoftwareDeps...),
ExtraHardwareDeps: hardwareDeps,
Pre: precondition,
Timeout: timeout,
Val: testCase.Val,
}
result = append(result, testParam)
}
}
return genparams.Template(t, template, result)
}
// MakeTestParams generates the default set of crostini test
// parameters using MakeTestParamsFromList. If your test only needs to
// be parameterized over how crostini is acquired and which version is
// installed, use this. Otherwise, you may need to use
// MakeTestParamsFromList.
//
// Sub-tests which are not eligible for being on the CQ (unstable or
// download tests) will be tagged informational. Whether the test as a
// whole is CQ-critical should be controlled by a test-level
// informational attribute.
func MakeTestParams(t genparams.TestingT) string {
defaultTest := Param{}
return MakeTestParamsFromList(t, []Param{defaultTest})
}