github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/planner/run.go (about)

     1  // Copyright 2020 The ChromiumOS Authors
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package planner
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"context"
    11  	"fmt"
    12  	"path/filepath"
    13  	"runtime"
    14  	"runtime/pprof"
    15  	"sort"
    16  	"sync"
    17  	"time"
    18  
    19  	"go.chromium.org/tast/core/errors"
    20  	"go.chromium.org/tast/core/internal/logging"
    21  	"go.chromium.org/tast/core/internal/planner/internal/entity"
    22  	"go.chromium.org/tast/core/internal/planner/internal/fixture"
    23  	"go.chromium.org/tast/core/internal/planner/internal/output"
    24  	"go.chromium.org/tast/core/internal/protocol"
    25  	"go.chromium.org/tast/core/internal/testcontext"
    26  	"go.chromium.org/tast/core/internal/testing"
    27  	"go.chromium.org/tast/core/internal/timing"
    28  	"go.chromium.org/tast/core/internal/usercode"
    29  
    30  	frameworkprotocol "go.chromium.org/tast/core/framework/protocol"
    31  )
    32  
    33  // OutputStream is an interface to report streamed outputs of multiple entity runs.
    34  type OutputStream = output.Stream
    35  
    36  // FixtureStack maintains a stack of fixtures and their states.
    37  type FixtureStack = fixture.InternalStack
    38  
    39  // NewFixtureStack creates a new empty fixture stack.
    40  func NewFixtureStack(cfg *Config, out OutputStream) *FixtureStack {
    41  	return fixture.NewInternalStack(cfg.FixtureConfig(), out)
    42  }
    43  
    44  const (
    45  	preTestTimeout  = 3 * time.Minute // timeout for RuntimeConfig.TestHook
    46  	postTestTimeout = 3 * time.Minute // timeout for a closure returned by RuntimeConfig.TestHook
    47  
    48  	// DefaultGracePeriod is default recommended grace period for SafeCall.
    49  	DefaultGracePeriod = 30 * time.Second
    50  )
    51  
    52  // Config contains details about how the planner should run tests.
    53  type Config struct {
    54  	// Dirs holds several directory paths important for running tests.
    55  	Dirs *protocol.RunDirectories
    56  
    57  	// Features contains software/hardware features the DUT has, and runtime variables.
    58  	Features *protocol.Features
    59  
    60  	// ServiceConfig contains configurations of external services available to
    61  	// Tast framework and Tast tests.
    62  	Service *protocol.ServiceConfig
    63  
    64  	// DataFileConfig contains configurations about data files.
    65  	DataFile *protocol.DataFileConfig
    66  
    67  	// RemoteData contains information relevant to remote tests.
    68  	// It is nil for local tests.
    69  	RemoteData *testing.RemoteData
    70  	// TestHook is run before TestInstance.Func (and TestInstance.Pre.Prepare, when applicable) if non-nil.
    71  	// The returned closure is executed after a test if not nil.
    72  	TestHook func(context.Context, *testing.TestHookState) func(context.Context, *testing.TestHookState)
    73  
    74  	// BeforeDownload specifies a function called before downloading external data files.
    75  	// It is ignored if it is nil.
    76  	BeforeDownload func(context.Context)
    77  	// Tests is a map from a test name to its metadata.
    78  	Tests map[string]*testing.TestInstance
    79  	// Fixtures is a map from a fixture name to its metadata.
    80  	Fixtures map[string]*testing.FixtureInstance
    81  	// StartFixtureName is a name of a fixture to start test execution.
    82  	// Tests requested to run should depend on the start fixture directly or
    83  	// indirectly.
    84  	// Since a start fixture is treated specially (e.g. no output directory is
    85  	// created), metadata of a start fixture must not be contained in
    86  	// Config.Fixtures. Instead, StartFixtureImpl gives an implementation of
    87  	// a start fixture.
    88  	StartFixtureName string
    89  	// StartFixtureImpl gives an implementation of a start fixture.
    90  	// If it is nil, a default stub implementation is used.
    91  	StartFixtureImpl testing.FixtureImpl
    92  	// CustomGracePeriod specifies custom grace period after entity timeout.
    93  	// If nil reasonable default will be used. Config.GracePeriod() returns
    94  	// the grace period to use. This field exists for unit testing.
    95  	CustomGracePeriod *time.Duration
    96  
    97  	// ExternalTarget represents configs for running an external bundle from
    98  	// current bundle. (i.e. local bundle from remote bundle).
    99  	ExternalTarget *ExternalTarget
   100  
   101  	//MaxSysMsgLogSize is a size of flag for truncate log file.
   102  	MaxSysMsgLogSize int64
   103  }
   104  
   105  // GracePeriod returns grace period after entity timeout.
   106  func (c *Config) GracePeriod() time.Duration {
   107  	if c.CustomGracePeriod != nil {
   108  		return *c.CustomGracePeriod
   109  	}
   110  	return DefaultGracePeriod
   111  }
   112  
   113  // FixtureConfig returns a fixture config derived from c.
   114  func (c *Config) FixtureConfig() *fixture.Config {
   115  	// Features contains software/hardware features each DUT has, and runtime variables.
   116  	// Its key for the map is the role of the DUT such as "cd1".
   117  	// The role for primary DUT should be "".
   118  	features := make(map[string]*frameworkprotocol.DUTFeatures)
   119  	features[""] = c.Features.GetDut()
   120  	for role, dutFeatures := range c.Features.GetCompanionFeatures() {
   121  		features[role] = dutFeatures
   122  	}
   123  	return &fixture.Config{
   124  		DataDir:           c.Dirs.GetDataDir(),
   125  		OutDir:            c.Dirs.GetOutDir(),
   126  		Vars:              c.Features.GetInfra().GetVars(),
   127  		Service:           c.Service,
   128  		BuildArtifactsURL: c.DataFile.GetBuildArtifactsUrl(),
   129  		RemoteData:        c.RemoteData,
   130  		StartFixtureName:  c.StartFixtureName,
   131  		Features:          features,
   132  		GracePeriod:       c.GracePeriod(),
   133  	}
   134  }
   135  
   136  // RunTestsLegacy runs a set of tests, writing outputs to out.
   137  //
   138  // RunTestsLegacy is responsible for building an efficient plan to run the given tests.
   139  // Therefore the order of tests in the argument is ignored; it just specifies
   140  // a set of tests to run.
   141  //
   142  // RunTestsLegacy runs tests on goroutines. If a test does not finish after reaching
   143  // its timeout, this function returns with an error without waiting for its finish.
   144  func RunTestsLegacy(ctx context.Context, tests []*testing.TestInstance, out OutputStream, pcfg *Config) error {
   145  	if pcfg.Tests != nil {
   146  		return fmt.Errorf("BUG: RunTestsLegacy pcfg.Tests = %v, want nil", pcfg.Tests)
   147  	}
   148  	// HACK: modify Tests field. This code should soon go away along with the
   149  	// removal of this function.
   150  	defer func() {
   151  		pcfg.Tests = nil
   152  	}()
   153  	pcfg.Tests = make(map[string]*testing.TestInstance)
   154  	for _, t := range tests {
   155  		pcfg.Tests[t.Name] = t
   156  	}
   157  	ts := make([]*protocol.ResolvedEntity, len(tests))
   158  	for i, t := range tests {
   159  		ts[i] = &protocol.ResolvedEntity{
   160  			Hops:   0,
   161  			Entity: t.EntityProto(),
   162  		}
   163  	}
   164  	return RunTests(ctx, ts, out, pcfg)
   165  }
   166  
   167  // RunTests runs a set of tests, writing outputs to out.
   168  //
   169  // RunTests is responsible for building an efficient plan to run the given tests.
   170  // Therefore the order of tests in the argument is ignored; it just specifies
   171  // a set of tests to run.
   172  //
   173  // RunTests runs tests on goroutines. If a test does not finish after reaching
   174  // its timeout, this function returns with an error without waiting for its finish.
   175  func RunTests(ctx context.Context, tests []*protocol.ResolvedEntity, out OutputStream, pcfg *Config) error {
   176  	plan, err := buildPlan(tests, pcfg)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	return plan.run(ctx, out)
   181  }
   182  
   183  // plan holds a top-level plan of test execution.
   184  type plan struct {
   185  	skips    []*skippedTest
   186  	fixtPlan *fixtPlan
   187  	prePlans []*prePlan
   188  	pcfg     *Config
   189  }
   190  
   191  type skippedTest struct {
   192  	test    *testing.TestInstance
   193  	reasons []string
   194  	err     error
   195  }
   196  
   197  func buildPlan(tests []*protocol.ResolvedEntity, pcfg *Config) (*plan, error) {
   198  	var testsWithFixture []*protocol.ResolvedEntity
   199  	preMap := make(map[string][]*testing.TestInstance)
   200  	var skips []*skippedTest
   201  	for _, t := range tests {
   202  		if t.GetHops() > 0 {
   203  			testsWithFixture = append(testsWithFixture, t)
   204  			continue
   205  		}
   206  		ti, ok := pcfg.Tests[t.GetEntity().GetName()]
   207  		if !ok {
   208  			return nil, fmt.Errorf("BUG: test %v does not exist", t.GetEntity().GetName())
   209  		}
   210  		reasons, err := ti.Deps().Check(pcfg.Features)
   211  		if err != nil || len(reasons) > 0 {
   212  			skips = append(skips, &skippedTest{test: ti, reasons: reasons, err: err})
   213  			continue
   214  		}
   215  		if ti.Pre != nil {
   216  			preName := ti.Pre.String()
   217  			preMap[preName] = append(preMap[preName], ti)
   218  			continue
   219  		}
   220  		// A test which is not skipped nor depending on a precondition is
   221  		// fixture-ready, possibly depending on an empty fixture.
   222  		testsWithFixture = append(testsWithFixture, t)
   223  	}
   224  	sort.Slice(skips, func(i, j int) bool {
   225  		return skips[i].test.Name < skips[j].test.Name
   226  	})
   227  
   228  	preNames := make([]string, 0, len(preMap))
   229  	for preName := range preMap {
   230  		preNames = append(preNames, preName)
   231  	}
   232  	sort.Strings(preNames)
   233  
   234  	prePlans := make([]*prePlan, len(preNames))
   235  	for i, preName := range preNames {
   236  		prePlans[i] = buildPrePlan(preMap[preName], pcfg)
   237  	}
   238  
   239  	fixtPlan, err := buildFixtPlan(testsWithFixture, pcfg)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	return &plan{skips, fixtPlan, prePlans, pcfg}, nil
   244  }
   245  
   246  func (p *plan) run(ctx context.Context, out output.Stream) error {
   247  	dl, err := newDownloader(ctx, p.pcfg)
   248  	if err != nil {
   249  		return errors.Wrap(err, "failed to create new downloader")
   250  	}
   251  	defer dl.TearDown()
   252  	dl.BeforeRun(ctx, p.entitiesToRun())
   253  
   254  	for _, s := range p.skips {
   255  		tout := output.NewEntityStream(out, s.test.EntityProto())
   256  		reportSkippedTest(tout, s.reasons, s.err)
   257  	}
   258  
   259  	if err := p.fixtPlan.run(ctx, out, dl); err != nil {
   260  		return err
   261  	}
   262  
   263  	for _, pp := range p.prePlans {
   264  		if err := pp.run(ctx, out, dl); err != nil {
   265  			return err
   266  		}
   267  	}
   268  	return nil
   269  }
   270  
   271  func (p *plan) entitiesToRun() []*protocol.Entity {
   272  	var res = p.fixtPlan.entitiesToRun()
   273  	for _, pp := range p.prePlans {
   274  		for _, t := range pp.testsToRun() {
   275  			res = append(res, t.EntityProto())
   276  		}
   277  	}
   278  	return res
   279  }
   280  
   281  // fixtTree represents a fixture tree.
   282  // At the beginning of fixture plan execution, a clone of a fixture tree is
   283  // created, and finished tests are removed from the tree as we execute the plan
   284  // to remember which tests are still to be run.
   285  // A fixture tree is considered empty if it contains no test. An empty fixture
   286  // tree must not appear as a subtree of another fixture tree so that we can
   287  // check if a fixture tree is empty in O(1).
   288  type fixtTree struct {
   289  	fixt *testing.FixtureInstance
   290  
   291  	// Following fields are updated as we execute a plan.
   292  	tests         []*testing.TestInstance
   293  	externalTests []string
   294  	children      []*fixtTree
   295  }
   296  
   297  // Empty returns if a tree has no test.
   298  // An empty fixture tree must not appear as a subtree of another fixture tree
   299  // so that we can check if a fixture tree is empty in O(1).
   300  func (t *fixtTree) Empty() bool {
   301  	return len(t.tests) == 0 && len(t.externalTests) == 0 && len(t.children) == 0
   302  }
   303  
   304  // Clone returns a deep copy of fixtTree.
   305  func (t *fixtTree) Clone() *fixtTree {
   306  	children := make([]*fixtTree, len(t.children))
   307  	for i, child := range t.children {
   308  		children[i] = child.Clone()
   309  	}
   310  	return &fixtTree{
   311  		fixt:          t.fixt,
   312  		tests:         append([]*testing.TestInstance(nil), t.tests...),
   313  		externalTests: append([]string(nil), t.externalTests...),
   314  		children:      children,
   315  	}
   316  }
   317  
   318  // orphanTest represents a test that depends on a missing fixture directly or
   319  // indirectly.
   320  type orphanTest struct {
   321  	test            *protocol.ResolvedEntity
   322  	missingFixtName string
   323  }
   324  
   325  // fixtPlan holds an execution plan of fixture-ready tests.
   326  type fixtPlan struct {
   327  	pcfg    *Config
   328  	tree    *fixtTree // original fixture tree; must not be modified
   329  	orphans []*orphanTest
   330  }
   331  
   332  // buildFixtPlan builds an execution plan of fixture-ready tests. Tests passed
   333  // to this function must not depend on preconditions.
   334  func buildFixtPlan(tests []*protocol.ResolvedEntity, pcfg *Config) (*fixtPlan, error) {
   335  	var orphans []*orphanTest
   336  	testsToRun := make(map[string][]*testing.TestInstance) // keyed by fixture
   337  	externalTestsToRun := make(map[string][]string)
   338  
   339  	// Build a graph of fixtures relevant to the given tests.
   340  	graph := make(map[string][]string) // fixture name to its children names
   341  	added := make(map[string]struct{}) // set of fixtures added to graph as children
   342  	var traverse func(string) (bool, string)
   343  	traverse = func(fixture string) (rooted bool, missingFixtureName string) {
   344  		if fixture == pcfg.StartFixtureName {
   345  			return true, ""
   346  		}
   347  		if _, ok := added[fixture]; ok {
   348  			return true, ""
   349  		}
   350  		f, ok := pcfg.Fixtures[fixture]
   351  		if !ok {
   352  			return false, fixture
   353  		}
   354  		rooted, missing := traverse(f.Parent)
   355  		if rooted {
   356  			added[fixture] = struct{}{}
   357  			graph[f.Parent] = append(graph[f.Parent], fixture)
   358  		}
   359  		return rooted, missing
   360  	}
   361  	for _, t := range tests {
   362  		f := fixtTreeParent(t)
   363  		rooted, missing := traverse(f)
   364  		if !rooted {
   365  			orphans = append(orphans, &orphanTest{
   366  				test:            t,
   367  				missingFixtName: missing,
   368  			})
   369  		} else if t.Hops == 0 {
   370  			// Existence of the test instance is already checked in buildPlan.
   371  			testsToRun[f] = append(testsToRun[f], pcfg.Tests[t.GetEntity().GetName()])
   372  		} else {
   373  			externalTestsToRun[f] = append(externalTestsToRun[f], t.GetEntity().GetName())
   374  		}
   375  	}
   376  	// Normalize
   377  	sort.Slice(orphans, func(i, j int) bool {
   378  		ei := orphans[i].test
   379  		ej := orphans[j].test
   380  		if ei.Hops != ej.Hops {
   381  			return ei.Hops < ej.Hops
   382  		}
   383  		return ei.Entity.Name < ej.Entity.Name
   384  	})
   385  	for _, ts := range testsToRun {
   386  		sort.Slice(ts, func(i, j int) bool {
   387  			return ts[i].Name < ts[j].Name
   388  		})
   389  	}
   390  	for _, ts := range externalTestsToRun {
   391  		sort.Strings(ts)
   392  	}
   393  	for _, children := range graph {
   394  		sort.Strings(children)
   395  	}
   396  
   397  	impl := pcfg.StartFixtureImpl
   398  	if impl == nil {
   399  		impl = &stubFixture{}
   400  	}
   401  	const infinite = 24 * time.Hour // a day is considered infinite
   402  	rootFixture := &testing.FixtureInstance{
   403  		// Do not set Name of a start fixture. output.EntityOutputStream do not
   404  		// emit EntityStart/EntityEnd for unnamed entities.
   405  		Impl: impl,
   406  		// Set infinite timeouts to all lifecycle methods. In production, the
   407  		// start fixture may communicate with the host machine to trigger remote
   408  		// fixtures, which would take quite some time but timeouts are responsibly
   409  		// handled by the host binary. In unit tests, we may set the custom grace
   410  		// period to very small values (like a millisecond) to test the behavior
   411  		// when user code ignore timeouts, where we need long timeouts to avoid
   412  		// hitting timeouts in the start fixture.
   413  		SetUpTimeout:    infinite,
   414  		TearDownTimeout: infinite,
   415  		ResetTimeout:    infinite,
   416  		PreTestTimeout:  infinite,
   417  		PostTestTimeout: infinite,
   418  	}
   419  
   420  	// Traverse the graph to build a fixture tree.
   421  	var newTree func(name string) *fixtTree
   422  	newTree = func(name string) *fixtTree {
   423  		var f *testing.FixtureInstance
   424  		if name == pcfg.StartFixtureName {
   425  			f = rootFixture
   426  		} else {
   427  			f = pcfg.Fixtures[name]
   428  		}
   429  		var children []*fixtTree
   430  		for _, c := range graph[name] {
   431  			children = append(children, newTree(c))
   432  		}
   433  		return &fixtTree{
   434  			fixt:          f,
   435  			tests:         testsToRun[name],
   436  			externalTests: externalTestsToRun[name],
   437  			children:      children,
   438  		}
   439  	}
   440  	tree := newTree(pcfg.StartFixtureName)
   441  	return &fixtPlan{pcfg: pcfg, tree: tree, orphans: orphans}, nil
   442  }
   443  
   444  func fixtTreeParent(test *protocol.ResolvedEntity) string {
   445  	if test.Hops > 0 {
   446  		return test.StartFixtureName
   447  	}
   448  	return test.Entity.Fixture
   449  }
   450  
   451  func (p *fixtPlan) run(ctx context.Context, out output.Stream, dl *downloader) error {
   452  	for _, o := range p.orphans {
   453  		tout := output.NewEntityStream(out, o.test.GetEntity())
   454  		reportOrphanTest(tout, o.missingFixtName)
   455  	}
   456  
   457  	tree := p.tree.Clone()
   458  	internalStack := fixture.NewInternalStack(p.pcfg.FixtureConfig(), out)
   459  
   460  	var stack *internalOrCombinedStack
   461  	if p.pcfg.ExternalTarget != nil {
   462  		externalStack, err := fixture.NewExternalStack(ctx, out)
   463  		if err != nil {
   464  			return err
   465  		}
   466  		combinedStack := fixture.NewCombinedStack(externalStack, internalStack)
   467  		stack = &internalOrCombinedStack{combined: combinedStack}
   468  		internalStack.ParentFixtSerializedValue = func() ([]byte, error) {
   469  			return externalStack.SerializedVal(ctx)
   470  		}
   471  	} else {
   472  		// TODO(oka): Remove this code after migration for full remote fixtures.
   473  		// After migration is fully finished, only CombinedStack should be used.
   474  		stack = &internalOrCombinedStack{internal: internalStack}
   475  	}
   476  
   477  	return runFixtTree(ctx, tree, stack, p.pcfg, out, dl)
   478  }
   479  
   480  func (p *fixtPlan) entitiesToRun() []*protocol.Entity {
   481  	var entities []*protocol.Entity
   482  
   483  	for _, o := range p.orphans {
   484  		entities = append(entities, o.test.GetEntity())
   485  	}
   486  
   487  	var traverse func(tree *fixtTree)
   488  	traverse = func(tree *fixtTree) {
   489  		if tree.fixt.Name != "" {
   490  			entities = append(entities, tree.fixt.EntityProto())
   491  		}
   492  		for _, t := range tree.tests {
   493  			entities = append(entities, t.EntityProto())
   494  		}
   495  		for _, subtree := range tree.children {
   496  			traverse(subtree)
   497  		}
   498  	}
   499  	traverse(p.tree)
   500  
   501  	return entities
   502  }
   503  
   504  // runFixtTree runs tests in a fixture tree.
   505  // tree is modified as tests are run.
   506  func runFixtTree(ctx context.Context, tree *fixtTree, stack *internalOrCombinedStack, pcfg *Config, out output.Stream, dl *downloader) error {
   507  	// Note about invariants:
   508  	// On entering this function, if the fixture stack is green, it is clean.
   509  	// Thus we don't need to reset fixtures before running a next test.
   510  	// On returning from this function, if the fixture stack was green and the
   511  	// fixture tree was non-empty on entering this function, the stack is dirty.
   512  	for !tree.Empty() && stack.Status() != fixture.StatusYellow {
   513  		if err := func() error {
   514  			// Create a fixture-scoped context.
   515  			ctx, cancel := context.WithCancel(ctx)
   516  			defer cancel()
   517  
   518  			release := dl.BeforeEntity(ctx, tree.fixt.EntityProto())
   519  			defer release()
   520  
   521  			// Push a fixture to the stack. This will call SetUp if the fixture stack is green.
   522  			if err := stack.Push(ctx, tree.fixt); err != nil {
   523  				return err
   524  			}
   525  			// Do not defer stack.Pop call here. It is correct to not call TearDown when
   526  			// returning an error because it happens only when the timeout is ignored.
   527  
   528  			// Run direct child tests first.
   529  			for stack.Status() != fixture.StatusYellow && len(tree.tests) > 0 {
   530  				t := tree.tests[0]
   531  				tree.tests = tree.tests[1:]
   532  				tout := output.NewEntityStream(out, t.EntityProto())
   533  				if err := runTest(ctx, t, tout, pcfg, &preConfig{}, stack, dl); err != nil {
   534  					return err
   535  				}
   536  				if !tree.Empty() {
   537  					if err := stack.Reset(ctx); err != nil {
   538  						return err
   539  					}
   540  				}
   541  			}
   542  			// Run external tests then.
   543  			for stack.Status() != fixture.StatusYellow && len(tree.externalTests) > 0 {
   544  				unstarted, err := runExternalTests(ctx, tree.externalTests, stack.combined, pcfg, out)
   545  				if err != nil {
   546  					return err
   547  				}
   548  				if len(unstarted) == len(tree.externalTests) {
   549  					return fmt.Errorf("BUG: runExternalTests succeeds but no external test has run")
   550  				}
   551  				tree.externalTests = unstarted
   552  			}
   553  
   554  			// Run child fixtures recursively.
   555  			for stack.Status() != fixture.StatusYellow && len(tree.children) > 0 {
   556  				subtree := tree.children[0]
   557  				if err := runFixtTree(ctx, subtree, stack, pcfg, out, dl); err != nil {
   558  					return err
   559  				}
   560  				// It is possible that a recursive call of runFixtTree aborted in middle of
   561  				// execution due to reset failures. Remove the subtree only when it is empty.
   562  				if subtree.Empty() {
   563  					tree.children = tree.children[1:]
   564  				}
   565  				if stack.Status() != fixture.StatusYellow && !tree.Empty() {
   566  					if err := stack.Reset(ctx); err != nil {
   567  						return err
   568  					}
   569  				}
   570  			}
   571  
   572  			// Pop the fixture from the stack. This will call TearDown if it is not red.
   573  			if err := stack.Pop(ctx); err != nil {
   574  				return err
   575  			}
   576  			return nil
   577  		}(); err != nil {
   578  			return err
   579  		}
   580  	}
   581  	return nil
   582  }
   583  
   584  // prePlan holds execution plan of tests using the same precondition.
   585  type prePlan struct {
   586  	pre   testing.Precondition
   587  	tests []*testing.TestInstance
   588  	pcfg  *Config
   589  }
   590  
   591  func buildPrePlan(tests []*testing.TestInstance, pcfg *Config) *prePlan {
   592  	sort.Slice(tests, func(i, j int) bool {
   593  		return tests[i].Name < tests[j].Name
   594  	})
   595  	return &prePlan{tests[0].Pre, tests, pcfg}
   596  }
   597  
   598  func (p *prePlan) run(ctx context.Context, out output.Stream, dl *downloader) error {
   599  	// Create a precondition-scoped context.
   600  	ec := &testcontext.CurrentEntity{
   601  		// OutDir is not available for a precondition-scoped context.
   602  		HasSoftwareDeps: true,
   603  		SoftwareDeps:    append([]string(nil), p.tests[0].SoftwareDeps[""]...),
   604  		ServiceDeps:     append([]string(nil), p.tests[0].ServiceDeps...),
   605  		PrivateAttr:     append([]string(nil), p.tests[0].PrivateAttr...),
   606  	}
   607  	plog := newPreLogger(out)
   608  	pctx, cancel := context.WithCancel(testing.NewContext(ctx, ec, logging.NewFuncSink(plog.Log)))
   609  	defer cancel()
   610  
   611  	// Create an empty fixture stack. Tests using preconditions can't depend on
   612  	// fixture.
   613  	internalStack := fixture.NewInternalStack(p.pcfg.FixtureConfig(), out)
   614  	stack := &internalOrCombinedStack{internal: internalStack}
   615  
   616  	for i, t := range p.tests {
   617  		ti := t.EntityProto()
   618  		plog.SetCurrentTest(ti)
   619  		tout := output.NewEntityStream(out, ti)
   620  		precfg := &preConfig{
   621  			ctx:   pctx,
   622  			close: p.pre != nil && i == len(p.tests)-1,
   623  		}
   624  		if err := runTest(ctx, t, tout, p.pcfg, precfg, stack, dl); err != nil {
   625  			return err
   626  		}
   627  		if i < len(p.tests)-1 {
   628  			if err := stack.Reset(ctx); err != nil {
   629  				return err
   630  			}
   631  		}
   632  	}
   633  	return nil
   634  }
   635  
   636  func (p *prePlan) testsToRun() []*testing.TestInstance {
   637  	return append([]*testing.TestInstance(nil), p.tests...)
   638  }
   639  
   640  // preLogger is a logger behind precondition-scoped contexts. It emits
   641  // precondition logs to output.OutputStream just as if they are emitted by a currently
   642  // running test. Call SetCurrentTest to set a current test.
   643  type preLogger struct {
   644  	out output.Stream
   645  
   646  	mu sync.Mutex
   647  	ti *protocol.Entity
   648  }
   649  
   650  func newPreLogger(out output.Stream) *preLogger {
   651  	return &preLogger{out: out}
   652  }
   653  
   654  // Log emits a log message to output.OutputStream just as if it is emitted by the
   655  // current test. SetCurrentTest must be called before calling this method.
   656  func (l *preLogger) Log(msg string) {
   657  	l.mu.Lock()
   658  	defer l.mu.Unlock()
   659  	l.out.EntityLog(l.ti, msg)
   660  }
   661  
   662  // SetCurrentTest sets the current test.
   663  func (l *preLogger) SetCurrentTest(ti *protocol.Entity) {
   664  	l.mu.Lock()
   665  	defer l.mu.Unlock()
   666  	l.ti = ti
   667  }
   668  
   669  // preConfig contains information needed to interact with a precondition for
   670  // a single test.
   671  type preConfig struct {
   672  	// ctx is a context that lives as long as the precondition. It is available
   673  	// to preconditions as testing.PreState.PreCtx.
   674  	ctx context.Context
   675  	// close is true if the test is the last test using the precondition and thus
   676  	// it should be closed.
   677  	close bool
   678  }
   679  
   680  // runTest runs a single test, writing outputs messages to tout.
   681  //
   682  // runTest runs a test on a goroutine. If a test does not finish after reaching
   683  // its timeout, this function returns with an error without waiting for its finish.
   684  func runTest(ctx context.Context, t *testing.TestInstance, tout *output.EntityStream, pcfg *Config, precfg *preConfig, stack *internalOrCombinedStack, dl *downloader) error {
   685  	fixtCtx := ctx
   686  
   687  	// Attach a log that the test can use to report timing events.
   688  	timingLog := timing.NewLog()
   689  	ctx = timing.NewContext(ctx, timingLog)
   690  
   691  	outDir, err := entity.CreateOutDir(pcfg.Dirs.GetOutDir(), t.Name)
   692  	if err != nil {
   693  		return err
   694  	}
   695  
   696  	tout.Start(outDir)
   697  	defer tout.End(nil, timingLog)
   698  
   699  	afterTest := dl.BeforeEntity(ctx, t.EntityProto())
   700  	defer afterTest()
   701  
   702  	if err := stack.MarkDirty(ctx); err != nil {
   703  		return err
   704  	}
   705  
   706  	switch stack.Status() {
   707  	case fixture.StatusGreen:
   708  	case fixture.StatusRed:
   709  		for _, e := range stack.Errors() {
   710  			tout.Error(e)
   711  		}
   712  		return nil
   713  	case fixture.StatusYellow:
   714  		return errors.New("BUG: Cannot run a test on a yellow fixture stack")
   715  	}
   716  
   717  	tcfg := &testConfig{
   718  		test:    t,
   719  		outDir:  outDir,
   720  		fixtCtx: fixtCtx,
   721  		// TODO(crbug.com/1106218): Make sure this approach is scalable.
   722  		// Recomputing purgeable on each test costs O(|purgeable| * |tests|) overall.
   723  		purgeable: dl.m.Purgeable(),
   724  	}
   725  	if err := runTestWithConfig(ctx, tcfg, pcfg, stack, precfg, tout); err != nil {
   726  		// If runTestWithRoot reported that the test didn't finish, print diagnostic messages.
   727  		msg := fmt.Sprintf("%v (see log for goroutine dump)", err)
   728  		tout.Error(testing.NewError(nil, msg, msg, 0))
   729  		dumpGoroutines(tout)
   730  		return err
   731  	}
   732  
   733  	return nil
   734  }
   735  
   736  type testConfig struct {
   737  	test      *testing.TestInstance
   738  	outDir    string
   739  	fixtCtx   context.Context
   740  	purgeable []string
   741  }
   742  
   743  // runTestWithConfig runs a test on the given configs.
   744  //
   745  // The time allotted to the test is generally the sum of t.Timeout and t.ExitTimeout, but
   746  // additional time may be allotted for preconditions and pre/post-test hooks.
   747  func runTestWithConfig(ctx context.Context, tcfg *testConfig, pcfg *Config, stack *internalOrCombinedStack, precfg *preConfig, out testing.OutputStream) error {
   748  	// codeName is included in error messages if the user code ignores the timeout.
   749  	// For compatibility, the same fixed name is used for tests, preconditions and test hooks.
   750  	const codeName = "Test"
   751  
   752  	var postTestFunc func(ctx context.Context, s *testing.TestHookState)
   753  
   754  	condition := testing.NewEntityCondition()
   755  
   756  	features := make(map[string]*frameworkprotocol.DUTFeatures)
   757  	features[""] = pcfg.Features.GetDut()
   758  	for role, dutFeatures := range pcfg.Features.GetCompanionFeatures() {
   759  		features[role] = dutFeatures
   760  	}
   761  	rcfg := &testing.RuntimeConfig{
   762  		DataDir:  filepath.Join(pcfg.Dirs.GetDataDir(), testing.RelativeDataDir(tcfg.test.Pkg)),
   763  		OutDir:   tcfg.outDir,
   764  		Vars:     pcfg.Features.GetInfra().GetVars(),
   765  		Features: features,
   766  		CloudStorage: testing.NewCloudStorage(
   767  			pcfg.Service.GetDevservers(),
   768  			pcfg.Service.GetTlwServer(),
   769  			pcfg.Service.GetTlwSelfName(),
   770  			pcfg.Service.GetDutServer(),
   771  			pcfg.DataFile.GetBuildArtifactsUrl(),
   772  			pcfg.Service.GetSwarmingTaskID(),
   773  			pcfg.Service.GetBuildBucketID(),
   774  		),
   775  		RemoteData: pcfg.RemoteData,
   776  		FixtCtx:    tcfg.fixtCtx,
   777  		FixtValue:  stack.Val(),
   778  		FixtSerializedValue: func() ([]byte, error) {
   779  			return stack.SerializedVal(ctx)
   780  		},
   781  		PreCtx:           precfg.ctx,
   782  		Purgeable:        tcfg.purgeable,
   783  		MaxSysMsgLogSize: pcfg.MaxSysMsgLogSize,
   784  	}
   785  	troot := testing.NewTestEntityRoot(tcfg.test, rcfg, out, condition)
   786  	ctx = troot.NewContext(ctx)
   787  	testState := troot.NewTestState()
   788  
   789  	// First, perform setup and run the pre-test function.
   790  	if err := usercode.SafeCall(ctx, codeName, preTestTimeout, pcfg.GracePeriod(), usercode.ErrorOnPanic(testState), func(ctx context.Context) {
   791  		// The test bundle is responsible for ensuring t.Timeout is nonzero before calling Run,
   792  		// but we call s.Fatal instead of panicking since it's arguably nicer to report individual
   793  		// test failures instead of aborting the entire run.
   794  		if tcfg.test.Timeout <= 0 {
   795  			testState.Fatal("Invalid timeout ", tcfg.test.Timeout)
   796  		}
   797  
   798  		entity.PreCheck(tcfg.test.Data, testState)
   799  		if testState.HasError() {
   800  			return
   801  		}
   802  
   803  		// In remote tests, reconnect to the DUT if needed.
   804  		if pcfg.RemoteData != nil && testState.DUT() != nil {
   805  			dt := testState.DUT()
   806  			if !dt.Connected(ctx) {
   807  				testState.Log("Reconnecting to DUT")
   808  				if err := dt.Connect(ctx); err != nil {
   809  					testState.Error(testing.TestDidNotRunMsg)
   810  					testState.Fatal("Failed to reconnect to DUT: ", err)
   811  				}
   812  			}
   813  		}
   814  
   815  		if pcfg.TestHook != nil {
   816  			postTestFunc = pcfg.TestHook(ctx, troot.NewTestHookState())
   817  		}
   818  	}); err != nil {
   819  		return err
   820  	}
   821  
   822  	// Prepare the test's precondition (if any) if setup was successful.
   823  	if !condition.HasError() && tcfg.test.Pre != nil {
   824  		preState := troot.NewPreState()
   825  		if err := usercode.SafeCall(ctx, codeName, tcfg.test.Pre.Timeout(), pcfg.GracePeriod(), usercode.ErrorOnPanic(preState), func(ctx context.Context) {
   826  			preState.Logf("Preparing precondition %q", tcfg.test.Pre)
   827  			troot.SetPreValue(tcfg.test.Pre.Prepare(ctx, preState))
   828  		}); err != nil {
   829  			return err
   830  		}
   831  	}
   832  
   833  	if err := func() error {
   834  		ctx, cancel := context.WithCancel(ctx)
   835  		defer cancel()
   836  
   837  		// Run fixture pre-test hooks.
   838  		postTest, err := stack.PreTest(ctx, tcfg.test.EntityProto(), tcfg.outDir, out, condition)
   839  		if err != nil {
   840  			return err
   841  		}
   842  
   843  		if !condition.HasError() {
   844  			// Run the test function itself.
   845  			if err := usercode.SafeCall(ctx, codeName, tcfg.test.Timeout, timeoutOrDefault(tcfg.test.ExitTimeout, pcfg.GracePeriod()), usercode.ErrorOnPanic(testState), func(ctx context.Context) {
   846  				tcfg.test.Func(ctx, testState)
   847  			}); err != nil {
   848  				return err
   849  			}
   850  		}
   851  
   852  		// Run fixture post-test hooks.
   853  		if err := postTest(ctx); err != nil {
   854  			return err
   855  		}
   856  		return nil
   857  	}(); err != nil {
   858  		return err
   859  	}
   860  
   861  	// If this is the final test using this precondition, close it
   862  	// (even if setup, tcfg.test.Pre.Prepare, or tcfg.test.Func failed).
   863  	if precfg.close {
   864  		preState := troot.NewPreState()
   865  		if err := usercode.SafeCall(ctx, codeName, tcfg.test.Pre.Timeout(), pcfg.GracePeriod(), usercode.ErrorOnPanic(preState), func(ctx context.Context) {
   866  			preState.Logf("Closing precondition %q", tcfg.test.Pre.String())
   867  			tcfg.test.Pre.Close(ctx, preState)
   868  		}); err != nil {
   869  			return err
   870  		}
   871  	}
   872  
   873  	// Finally, run the post-test functions unconditionally.
   874  	if postTestFunc != nil {
   875  		if err := usercode.SafeCall(ctx, codeName, postTestTimeout, pcfg.GracePeriod(), usercode.ErrorOnPanic(testState), func(ctx context.Context) {
   876  			postTestFunc(ctx, troot.NewTestHookState())
   877  		}); err != nil {
   878  			return err
   879  		}
   880  	}
   881  
   882  	return nil
   883  }
   884  
   885  // timeoutOrDefault returns timeout if positive or def otherwise.
   886  func timeoutOrDefault(timeout, def time.Duration) time.Duration {
   887  	if timeout > 0 {
   888  		return timeout
   889  	}
   890  	return def
   891  }
   892  
   893  // reportOrphanTest is called instead of runTest for a test that depends on
   894  // a missing fixture directly or indirectly.
   895  func reportOrphanTest(tout *output.EntityStream, missingFixtName string) {
   896  	tout.Start("")
   897  	_, fn, ln, _ := runtime.Caller(0)
   898  	tout.Error(&protocol.Error{
   899  		Reason: fmt.Sprintf("Fixture %q not found", missingFixtName),
   900  		Location: &protocol.ErrorLocation{
   901  			File: fn,
   902  			Line: int64(ln),
   903  		},
   904  	})
   905  	tout.End(nil, timing.NewLog())
   906  }
   907  
   908  // reportSkippedTest is called instead of runTest for a test that is skipped due to
   909  // having unsatisfied dependencies.
   910  func reportSkippedTest(tout *output.EntityStream, reasons []string, err error) {
   911  	tout.Start("")
   912  	if err == nil {
   913  		tout.End(reasons, timing.NewLog())
   914  		return
   915  	}
   916  
   917  	_, fn, ln, _ := runtime.Caller(0)
   918  	tout.Error(&protocol.Error{
   919  		Reason: err.Error(),
   920  		Location: &protocol.ErrorLocation{
   921  			File: fn,
   922  			Line: int64(ln),
   923  		},
   924  	})
   925  	// Do not report a test as skipped if we encounter errors while checking
   926  	// dependencies. There is ambiguity if a test is skipped while reporting
   927  	// errors, and in the worst case, dependency check errors can be
   928  	// silently discarded.
   929  	tout.End(nil, timing.NewLog())
   930  }
   931  
   932  // dumpGoroutines dumps all goroutines to tout.
   933  func dumpGoroutines(tout *output.EntityStream) {
   934  	tout.Log("Dumping all goroutines")
   935  	if err := func() error {
   936  		p := pprof.Lookup("goroutine")
   937  		if p == nil {
   938  			return errors.New("goroutine pprof not found")
   939  		}
   940  		var buf bytes.Buffer
   941  		if err := p.WriteTo(&buf, 2); err != nil {
   942  			return err
   943  		}
   944  		sc := bufio.NewScanner(&buf)
   945  		for sc.Scan() {
   946  			tout.Log(sc.Text())
   947  		}
   948  		return sc.Err()
   949  	}(); err != nil {
   950  		tout.Error(&protocol.Error{
   951  			Reason: fmt.Sprintf("Failed to dump goroutines: %v", err),
   952  		})
   953  	}
   954  }
   955  
   956  // stubFixture is a stub implementation of testing.FixtureImpl.
   957  type stubFixture struct{}
   958  
   959  func (f *stubFixture) SetUp(ctx context.Context, s *testing.FixtState) interface{} { return nil }
   960  func (f *stubFixture) Reset(ctx context.Context) error                             { return nil }
   961  func (f *stubFixture) PreTest(ctx context.Context, s *testing.FixtTestState)       {}
   962  func (f *stubFixture) PostTest(ctx context.Context, s *testing.FixtTestState)      {}
   963  func (f *stubFixture) TearDown(ctx context.Context, s *testing.FixtState)          {}