github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/testing/state.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 testing implements public framework APIs, as well as
     6  // framework-internal facility to run an entity.
     7  //
     8  // An entity is a piece of user code registered to the framework with metadata.
     9  // Currently there are three types of entities: tests, fixtures, and services.
    10  // Entities are registered to the framework by calling testing.Add* at the test
    11  // bundle initialization time. Entity metadata contain various information the
    12  // framework needs to know to call into an entity properly. For example: an
    13  // entity name is used to mention it in other entities' metadata and command
    14  // line arguments; dependencies (data files, services, ...) specify requirements
    15  // that must be prepared before running an entity. When a test bundle is
    16  // started, the framework builds an execution plan of entities according to the
    17  // request, and prepare necessary dependencies before calling into entities.
    18  //
    19  // A semi-entity is a piece of user code known indirectly to the framework
    20  // without explicit registration. Currently there are three types of
    21  // semi-entities: preconditions, test hooks, and subtests.
    22  // Since semi-entities are not explicitly registered to the framework, they do
    23  // not have associated metadata. As an important consequence, semi-entities
    24  // can't declare their own dependencies.
    25  //
    26  // Entities and semi-entities are implemented either as a simple user function
    27  // or an interface (that is, a set of user functions). The framework typically
    28  // calls into user functions with two arguments: context.Context and
    29  // testing.*State (the exact State type varies).
    30  //
    31  // context.Context is associated with an entity for which a user function is
    32  // called. Note that an associated entity might not be the one closest to
    33  // a function being called, in terms of code location; for example,
    34  // context.Context passed to a gRPC service method is associated with a test or
    35  // a fixture calling into the method, not the service implementing the method.
    36  // One can call testing.Context* functions with a given context to query
    37  // an entity metadata (e.g. testcontext.ServiceDeps), or emit logs for an
    38  // entity (testing.ContextLog).
    39  //
    40  // A new State object is created by the framework every time on calling into
    41  // a (semi-)entity. This means that there might be multiple State objects for
    42  // an entity at a time. To maintain states common to multiple State objects for
    43  // the same entity, a single EntityRoot object (and additionally
    44  // a TestEntityRoot object in the case of a test) is allocated. Root objects are
    45  // private to the framework, and user code always access Root objects indirectly
    46  // via State objects.
    47  //
    48  // Since there are many State types that provide similar but different sets of
    49  // methods, State types typically embed mix-in types that actually implements
    50  // API methods.
    51  package testing
    52  
    53  import (
    54  	"context"
    55  	"encoding/json"
    56  	"fmt"
    57  	"net/http"
    58  	"os"
    59  	"path/filepath"
    60  	"regexp"
    61  	"runtime"
    62  	"sort"
    63  	"strings"
    64  	"sync"
    65  
    66  	"go.chromium.org/tast/core/dut"
    67  	"go.chromium.org/tast/core/errors"
    68  	"go.chromium.org/tast/core/internal/logging"
    69  	"go.chromium.org/tast/core/internal/protocol"
    70  	"go.chromium.org/tast/core/internal/testcontext"
    71  	"go.chromium.org/tast/core/internal/timing"
    72  	"go.chromium.org/tast/core/internal/usercode"
    73  
    74  	frameworkprotocol "go.chromium.org/tast/core/framework/protocol"
    75  )
    76  
    77  const (
    78  	metaCategory  = "meta"                    // category for remote tests exercising Tast, as in "meta.TestName".
    79  	preFailPrefix = "[Precondition failure] " // the prefix used then a precondition failure is logged.
    80  )
    81  
    82  // EntityCondition stores mutable condition of an entity.
    83  type EntityCondition struct {
    84  	mu       sync.Mutex
    85  	hasError bool
    86  }
    87  
    88  // NewEntityCondition creates a new EntityCondition
    89  func NewEntityCondition() *EntityCondition {
    90  	return &EntityCondition{hasError: false}
    91  }
    92  
    93  // RecordError record that an error has been reported for the entity.
    94  func (c *EntityCondition) RecordError() {
    95  	c.mu.Lock()
    96  	c.hasError = true
    97  	c.mu.Unlock()
    98  }
    99  
   100  // HasError returns whether an error has been reported for the entity.
   101  func (c *EntityCondition) HasError() bool {
   102  	c.mu.Lock()
   103  	res := c.hasError
   104  	c.mu.Unlock()
   105  	return res
   106  }
   107  
   108  // EntityConstraints represents constraints imposed to an entity.
   109  // For example, a test can only access runtime variables declared on its
   110  // registration. This struct carries a list of declared runtime variables to be
   111  // checked against in State.Var.
   112  type EntityConstraints struct {
   113  	allVars []string
   114  	allData []string
   115  }
   116  
   117  // EntityRoot is the root of all State objects associated with an entity.
   118  // EntityRoot keeps track of states shared among all State objects associated
   119  // with an entity (e.g. whether any error has been reported), as well as
   120  // immutable entity information such as RuntimeConfig. Make sure to create State
   121  // objects for an entity from the same EntityRoot.
   122  // EntityRoot must be kept private to the framework.
   123  type EntityRoot struct {
   124  	ce        *testcontext.CurrentEntity // current entity info to be available via context.Context
   125  	cst       *EntityConstraints         // constraints for the entity
   126  	cfg       *RuntimeConfig             // details about how to run an entity
   127  	out       OutputStream               // stream to which logging messages and errors are reported
   128  	condition *EntityCondition
   129  }
   130  
   131  // NewEntityRoot returns a new EntityRoot object.
   132  func NewEntityRoot(ce *testcontext.CurrentEntity, cst *EntityConstraints, cfg *RuntimeConfig, out OutputStream, condition *EntityCondition) *EntityRoot {
   133  	return &EntityRoot{
   134  		ce:        ce,
   135  		cst:       cst,
   136  		cfg:       cfg,
   137  		out:       out,
   138  		condition: condition,
   139  	}
   140  }
   141  
   142  func (r *EntityRoot) newGlobalMixin(errPrefix string, hasError bool) *globalMixin {
   143  	return &globalMixin{
   144  		entityRoot: r,
   145  		errPrefix:  errPrefix,
   146  		hasError:   hasError,
   147  	}
   148  }
   149  
   150  func (r *EntityRoot) newEntityMixin() *entityMixin {
   151  	return &entityMixin{
   152  		entityRoot: r,
   153  	}
   154  }
   155  
   156  // NewFixtState creates a FixtState for a fixture.
   157  func (r *EntityRoot) NewFixtState() *FixtState {
   158  	return &FixtState{
   159  		globalMixin: r.newGlobalMixin("", r.hasError()),
   160  		entityMixin: r.newEntityMixin(),
   161  		entityRoot:  r,
   162  	}
   163  }
   164  
   165  // NewContext creates a new context associated with the entity.
   166  func (r *EntityRoot) NewContext(ctx context.Context) context.Context {
   167  	return NewContext(ctx, r.ce, logging.NewFuncSink(func(msg string) { r.out.Log(msg) }))
   168  }
   169  
   170  // hasError checks if any error has been reported.
   171  func (r *EntityRoot) hasError() bool {
   172  	return r.condition.HasError()
   173  }
   174  
   175  // recordError records that the entity has reported an error.
   176  func (r *EntityRoot) recordError() {
   177  	r.condition.RecordError()
   178  }
   179  
   180  // TestEntityRoot is the root of all State objects associated with a test.
   181  // TestEntityRoot is very similar to EntityRoot, but it contains additional states and
   182  // immutable test information.
   183  // TestEntityRoot must be kept private to the framework.
   184  type TestEntityRoot struct {
   185  	entityRoot *EntityRoot
   186  	test       *TestInstance // test being run
   187  
   188  	preValue interface{} // value returned by test.Pre.Prepare; may be nil
   189  }
   190  
   191  // NewTestEntityRoot returns a new TestEntityRoot object.
   192  func NewTestEntityRoot(test *TestInstance, cfg *RuntimeConfig, out OutputStream, condition *EntityCondition) *TestEntityRoot {
   193  	ce := &testcontext.CurrentEntity{
   194  		OutDir:          cfg.OutDir,
   195  		HasSoftwareDeps: true,
   196  		SoftwareDeps:    append([]string(nil), test.SoftwareDeps[""]...),
   197  		ServiceDeps:     test.ServiceDeps,
   198  		PrivateAttr:     test.PrivateAttr,
   199  	}
   200  	return &TestEntityRoot{
   201  		entityRoot: NewEntityRoot(ce, test.Constraints(), cfg, out, condition),
   202  		test:       test,
   203  	}
   204  }
   205  
   206  // EntityProto returns the test being run as a protocol.Entity.
   207  func (r *TestEntityRoot) EntityProto() *protocol.Entity {
   208  	return r.test.EntityProto()
   209  }
   210  
   211  func (r *TestEntityRoot) newTestMixin() *testMixin {
   212  	return &testMixin{
   213  		testRoot: r,
   214  	}
   215  }
   216  
   217  // NewTestState creates a State for a test.
   218  func (r *TestEntityRoot) NewTestState() *State {
   219  	return &State{
   220  		globalMixin: r.entityRoot.newGlobalMixin("", r.hasError()),
   221  		entityMixin: r.entityRoot.newEntityMixin(),
   222  		testMixin:   r.newTestMixin(),
   223  		testRoot:    r,
   224  	}
   225  }
   226  
   227  // NewPreState creates a PreState for a precondition.
   228  func (r *TestEntityRoot) NewPreState() *PreState {
   229  	return &PreState{
   230  		globalMixin: r.entityRoot.newGlobalMixin(preFailPrefix, r.hasError()),
   231  		entityMixin: r.entityRoot.newEntityMixin(),
   232  		testMixin:   r.newTestMixin(),
   233  	}
   234  }
   235  
   236  // NewTestHookState creates a TestHookState for a test hook.
   237  func (r *TestEntityRoot) NewTestHookState() *TestHookState {
   238  	return &TestHookState{
   239  		globalMixin: r.entityRoot.newGlobalMixin("", r.hasError()),
   240  		entityMixin: r.entityRoot.newEntityMixin(),
   241  		testMixin:   r.newTestMixin(),
   242  	}
   243  }
   244  
   245  // NewContext creates a new context associated with the entity.
   246  func (r *TestEntityRoot) NewContext(ctx context.Context) context.Context {
   247  	return r.entityRoot.NewContext(ctx)
   248  }
   249  
   250  func (r *TestEntityRoot) hasError() bool {
   251  	return r.entityRoot.hasError()
   252  }
   253  
   254  // SetPreValue sets a precondition value available to the test.
   255  func (r *TestEntityRoot) SetPreValue(val interface{}) {
   256  	r.preValue = val
   257  }
   258  
   259  // LogSink returns a logging sink for the test entity.
   260  func (r *TestEntityRoot) LogSink() logging.Sink {
   261  	return logging.NewFuncSink(func(msg string) { r.entityRoot.out.Log(msg) })
   262  }
   263  
   264  // FixtTestEntityRoot is the root of all State objects associated with a test
   265  // and a fixture. Such state is only FixtTestState.
   266  // FixtTestEntityRoot must be kept private to the framework.
   267  type FixtTestEntityRoot struct {
   268  	entityRoot *EntityRoot
   269  }
   270  
   271  // NewFixtTestEntityRoot creates a new FixtTestEntityRoot.
   272  func NewFixtTestEntityRoot(fixture *FixtureInstance, cfg *RuntimeConfig, out OutputStream, condition *EntityCondition) *FixtTestEntityRoot {
   273  	ce := &testcontext.CurrentEntity{
   274  		OutDir:          cfg.OutDir, // test outDir
   275  		HasSoftwareDeps: false,
   276  		PrivateAttr:     fixture.PrivateAttr,
   277  	}
   278  	return &FixtTestEntityRoot{
   279  		entityRoot: NewEntityRoot(ce, fixture.Constraints(), cfg, out, condition),
   280  	}
   281  }
   282  
   283  func (r *FixtTestEntityRoot) hasError() bool {
   284  	return r.entityRoot.hasError()
   285  }
   286  
   287  // LogSink returns a logging sink for the entity.
   288  func (r *FixtTestEntityRoot) LogSink() logging.Sink {
   289  	return logging.NewFuncSink(func(msg string) { r.entityRoot.out.Log(msg) })
   290  }
   291  
   292  // OutDir returns a directory into which the entity may place arbitrary files
   293  // that should be included with the test results.
   294  func (r *FixtTestEntityRoot) OutDir() string {
   295  	return r.entityRoot.cfg.OutDir
   296  }
   297  
   298  // NewFixtTestState creates a FixtTestState.
   299  // ctx should have the same lifetime as the test, including PreTest and PostTest.
   300  func (r *FixtTestEntityRoot) NewFixtTestState(ctx context.Context, name string) *FixtTestState {
   301  	return &FixtTestState{
   302  		globalMixin: r.entityRoot.newGlobalMixin("", r.hasError()),
   303  		testCtx:     ctx,
   304  		testName:    name,
   305  	}
   306  }
   307  
   308  // NewContext returns a context.Context to be used for the entity.
   309  func NewContext(ctx context.Context, ec *testcontext.CurrentEntity, sink logging.Sink) context.Context {
   310  	logger := logging.NewSinkLogger(logging.LevelInfo, false, sink)
   311  	ctx = logging.AttachLoggerNoPropagation(ctx, logger)
   312  	ctx = testcontext.WithCurrentEntity(ctx, ec)
   313  	return ctx
   314  }
   315  
   316  // globalMixin implements common methods for all State types.
   317  // A globalMixin object must not be shared among multiple State objects.
   318  type globalMixin struct {
   319  	entityRoot *EntityRoot
   320  	errPrefix  string // prefix to be added to error messages
   321  
   322  	mu       sync.Mutex // protects hasError
   323  	hasError bool       // true if any error was reported from this State object or subtests' State objects
   324  }
   325  
   326  // CloudStorage returns a client for Google Cloud Storage.
   327  func (s *globalMixin) CloudStorage() *CloudStorage {
   328  	return s.entityRoot.cfg.CloudStorage
   329  }
   330  
   331  // RPCHint returns information needed to establish gRPC connections.
   332  // It can only be called by remote entities.
   333  func (s *globalMixin) RPCHint() *RPCHint {
   334  	if s.entityRoot.cfg.RemoteData == nil {
   335  		panic("RPCHint unavailable (running non-remote?)")
   336  	}
   337  	// Return a copy to make sure the entity doesn't modify the original struct.
   338  	return s.entityRoot.cfg.RemoteData.RPCHint.clone()
   339  }
   340  
   341  // DUT returns a shared SSH connection.
   342  // It can only be called by remote entities.
   343  func (s *globalMixin) DUT() *dut.DUT {
   344  	if s.entityRoot.cfg.RemoteData == nil {
   345  		panic("DUT unavailable (running non-remote?)")
   346  	}
   347  	return s.entityRoot.cfg.RemoteData.DUT
   348  }
   349  
   350  // CompanionDUT returns a shared SSH connection for a companion DUT.
   351  // It can only be called by remote entities.
   352  func (s *globalMixin) CompanionDUT(role string) *dut.DUT {
   353  	if s.entityRoot.cfg.RemoteData == nil {
   354  		panic("Companion DUT unavailable (running non-remote?)")
   355  	}
   356  	dut, ok := s.entityRoot.cfg.RemoteData.CompanionDUTs[role]
   357  	if !ok {
   358  		panic(fmt.Sprintf("Companion DUT %q cannot be found", role))
   359  	}
   360  	return dut
   361  }
   362  
   363  // Log formats its arguments using default formatting and logs them.
   364  func (s *globalMixin) Log(args ...interface{}) {
   365  	s.entityRoot.out.Log(fmt.Sprint(args...))
   366  }
   367  
   368  // Logf is similar to Log but formats its arguments using fmt.Sprintf.
   369  func (s *globalMixin) Logf(format string, args ...interface{}) {
   370  	s.entityRoot.out.Log(fmt.Sprintf(format, args...))
   371  }
   372  
   373  // Error formats its arguments using default formatting and marks the entity
   374  // as having failed (using the arguments as a reason for the failure)
   375  // while letting the entity continue execution.
   376  func (s *globalMixin) Error(args ...interface{}) {
   377  	s.recordError()
   378  	fullMsg, lastMsg, err := s.formatError(args...)
   379  	e := NewError(err, fullMsg, lastMsg, 1)
   380  	s.entityRoot.out.Error(e)
   381  }
   382  
   383  // Errorf is similar to Error but formats its arguments using fmt.Sprintf.
   384  func (s *globalMixin) Errorf(format string, args ...interface{}) {
   385  	s.recordError()
   386  	fullMsg, lastMsg, err := s.formatErrorf(format, args...)
   387  	e := NewError(err, fullMsg, lastMsg, 1)
   388  	s.entityRoot.out.Error(e)
   389  }
   390  
   391  // Fatal is similar to Error but additionally immediately ends the entity.
   392  func (s *globalMixin) Fatal(args ...interface{}) {
   393  	s.recordError()
   394  	fullMsg, lastMsg, err := s.formatError(args...)
   395  	e := NewError(err, fullMsg, lastMsg, 1)
   396  	s.entityRoot.out.Error(e)
   397  	runtime.Goexit()
   398  }
   399  
   400  // Fatalf is similar to Fatal but formats its arguments using fmt.Sprintf.
   401  func (s *globalMixin) Fatalf(format string, args ...interface{}) {
   402  	s.recordError()
   403  	fullMsg, lastMsg, err := s.formatErrorf(format, args...)
   404  	e := NewError(err, fullMsg, lastMsg, 1)
   405  	s.entityRoot.out.Error(e)
   406  	runtime.Goexit()
   407  }
   408  
   409  // HasError reports whether the entity has already reported errors.
   410  func (s *globalMixin) HasError() bool {
   411  	s.mu.Lock()
   412  	defer s.mu.Unlock()
   413  	return s.hasError
   414  }
   415  
   416  // errorSuffix matches the well-known error message suffixes for formatError.
   417  var errorSuffix = regexp.MustCompile(`(\s*:\s*|\s+)$`)
   418  
   419  // formatError formats an error message using fmt.Sprint.
   420  // If the format is well-known one, such as:
   421  //
   422  //	formatError("Failed something: ", err)
   423  //
   424  // then this function extracts an error object and returns parsed error messages
   425  // in the following way:
   426  //
   427  //	lastMsg = "Failed something"
   428  //	fullMsg = "Failed something: <error message>"
   429  func (s *globalMixin) formatError(args ...interface{}) (fullMsg, lastMsg string, err error) {
   430  	fullMsg = s.errPrefix + fmt.Sprint(args...)
   431  	if len(args) == 1 {
   432  		if e, ok := args[0].(error); ok {
   433  			err = e
   434  		}
   435  	} else if len(args) >= 2 {
   436  		if e, ok := args[len(args)-1].(error); ok {
   437  			if s, ok := args[len(args)-2].(string); ok {
   438  				if m := errorSuffix.FindStringIndex(s); m != nil {
   439  					err = e
   440  					args = append(args[:len(args)-2], s[:m[0]])
   441  				}
   442  			}
   443  		}
   444  	}
   445  	lastMsg = s.errPrefix + fmt.Sprint(args...)
   446  	return fullMsg, lastMsg, err
   447  }
   448  
   449  // errorfSuffix matches the well-known error message suffix for formatErrorf.
   450  var errorfSuffix = regexp.MustCompile(`\s*:?\s*%v$`)
   451  
   452  // formatErrorf formats an error message using fmt.Sprintf.
   453  // If the format is the following well-known one:
   454  //
   455  //	formatErrorf("Failed something: %v", err)
   456  //
   457  // then this function extracts an error object and returns parsed error messages
   458  // in the following way:
   459  //
   460  //	lastMsg = "Failed something"
   461  //	fullMsg = "Failed something: <error message>"
   462  func (s *globalMixin) formatErrorf(format string, args ...interface{}) (fullMsg, lastMsg string, err error) {
   463  	fullMsg = s.errPrefix + fmt.Sprintf(format, args...)
   464  	if len(args) >= 1 {
   465  		if e, ok := args[len(args)-1].(error); ok {
   466  			if m := errorfSuffix.FindStringIndex(format); m != nil {
   467  				err = e
   468  				args = args[:len(args)-1]
   469  				format = format[:m[0]]
   470  			}
   471  		}
   472  	}
   473  	lastMsg = s.errPrefix + fmt.Sprintf(format, args...)
   474  	return fullMsg, lastMsg, err
   475  }
   476  
   477  // recordError records that the entity has reported an error.
   478  func (s *globalMixin) recordError() {
   479  	s.entityRoot.recordError()
   480  	s.mu.Lock()
   481  	defer s.mu.Unlock()
   482  	s.hasError = true
   483  }
   484  
   485  // Features returns the features of a DUT based on role name.
   486  // For primary DUT, use "" as the role name.
   487  func (s *globalMixin) Features(role string) *frameworkprotocol.DUTFeatures {
   488  	return s.entityRoot.cfg.Features[role]
   489  }
   490  
   491  // entityMixin implements common methods for State types allowing to access
   492  // values entity declares, e.g. runtime variables.
   493  // A entityMixin object must not be shared among multiple State objects.
   494  type entityMixin struct {
   495  	entityRoot *EntityRoot
   496  }
   497  
   498  // Var returns the value for the named variable, which must have been registered via Vars.
   499  // If a value was not supplied at runtime via the -var flag to "tast run", ok will be false.
   500  func (s *entityMixin) Var(name string) (val string, ok bool) {
   501  	seen := false
   502  	for _, n := range s.entityRoot.cst.allVars {
   503  		if n == name {
   504  			seen = true
   505  			break
   506  		}
   507  	}
   508  	if !seen {
   509  		panic(fmt.Sprintf("Variable %q was not registered in testing.Test.Vars. Try adding the line 'Vars: []string{%q},' to your testing.Test{}", name, name))
   510  	}
   511  
   512  	val, ok = s.entityRoot.cfg.Vars[name]
   513  	return val, ok
   514  }
   515  
   516  // RequiredVar is similar to Var but aborts the entity if the named variable was not supplied.
   517  func (s *entityMixin) RequiredVar(name string) string {
   518  	val, ok := s.Var(name)
   519  	if !ok {
   520  		panic(fmt.Sprintf("Required variable %q not supplied via -var or -varsfile", name))
   521  	}
   522  	return val
   523  }
   524  
   525  // DataPath returns the absolute path to use to access a data file previously
   526  // registered via Data. It aborts the entity if the p was not declared.
   527  func (s *entityMixin) DataPath(p string) string {
   528  	for _, f := range s.entityRoot.cst.allData {
   529  		if f == p {
   530  			return filepath.Join(s.entityRoot.cfg.DataDir, p)
   531  		}
   532  	}
   533  	panic(fmt.Sprintf("Data %q wasn't declared on registration", p))
   534  }
   535  
   536  // DataFileSystem returns an http.FileSystem implementation that serves an entity's data files.
   537  //
   538  //	srv := httptest.NewServer(http.FileServer(s.DataFileSystem()))
   539  //	defer srv.Close()
   540  //	resp, err := http.Get(srv.URL+"/data_file.html")
   541  func (s *entityMixin) DataFileSystem() *dataFS { return (*dataFS)(s) }
   542  
   543  // dataFS implements http.FileSystem.
   544  type dataFS entityMixin
   545  
   546  // Open opens the file at name, a path that would be passed to DataPath.
   547  func (d *dataFS) Open(name string) (http.File, error) {
   548  	// DataPath doesn't want a leading slash, so strip it off if present.
   549  	if filepath.IsAbs(name) {
   550  		var err error
   551  		if name, err = filepath.Rel("/", name); err != nil {
   552  			return nil, err
   553  		}
   554  	}
   555  
   556  	// Chrome requests favicons automatically, but DataPath fails when asked for an unregistered file.
   557  	// Report an error for undeclared files to avoid making tests fail (or create favicon files) unnecessarily.
   558  	// DataPath will panic if it attempts to use a file that exists but that wasn't declared as a dependency.
   559  	path, err := func() (path string, err error) {
   560  		defer func() {
   561  			if recover() != nil {
   562  				err = errors.New("not found")
   563  			}
   564  		}()
   565  		return (*entityMixin)(d).DataPath(name), nil
   566  	}()
   567  	if err != nil {
   568  		return nil, err
   569  	}
   570  	return os.Open(path)
   571  }
   572  
   573  // testMixin implements common methods for State types associated with a test.
   574  // A testMixin object must not be shared among multiple State objects.
   575  type testMixin struct {
   576  	testRoot *TestEntityRoot
   577  }
   578  
   579  // OutDir returns a directory into which the entity may place arbitrary files
   580  // that should be included with the entity results.
   581  func (s *testMixin) OutDir() string { return s.testRoot.entityRoot.cfg.OutDir }
   582  
   583  // SoftwareDeps returns software dependencies declared in the currently running entity.
   584  func (s *testMixin) SoftwareDeps() []string {
   585  	return append([]string(nil), s.testRoot.test.SoftwareDeps[""]...)
   586  }
   587  
   588  // ServiceDeps returns service dependencies declared in the currently running entity.
   589  func (s *testMixin) ServiceDeps() []string {
   590  	return append([]string(nil), s.testRoot.test.ServiceDeps...)
   591  }
   592  
   593  // TestName returns the name of the currently running test.
   594  func (s *testMixin) TestName() string {
   595  	return s.testRoot.test.Name
   596  }
   597  
   598  // State holds state relevant to the execution of a single test.
   599  //
   600  // Parts of its interface are patterned after Go's testing.T type.
   601  //
   602  // State contains many pieces of data, and it's unclear which are actually being
   603  // used when it's passed to a function. You should minimize the number of
   604  // functions taking State as an argument. Instead you can pass State's derived
   605  // values (e.g. s.DataPath("file.txt")) or ctx (to use with ContextLog or
   606  // ContextOutDir etc.).
   607  //
   608  // It is intended to be safe when called concurrently by multiple goroutines
   609  // while a test is running.
   610  type State struct {
   611  	*globalMixin
   612  	*entityMixin
   613  	*testMixin
   614  	testRoot *TestEntityRoot
   615  	subtests []string // subtest names
   616  }
   617  
   618  // Param returns Val specified at the Param struct for the current test case.
   619  func (s *State) Param() interface{} {
   620  	return s.testRoot.test.Val
   621  }
   622  
   623  // Run starts a new subtest with a unique name. Error messages are prepended with the subtest
   624  // name during its execution. If Fatal/Fatalf is called from inside a subtest, only that subtest
   625  // is stopped; its parent continues. Returns true if the subtest passed.
   626  func (s *State) Run(ctx context.Context, name string, run func(context.Context, *State)) bool {
   627  	subtests := append([]string(nil), s.subtests...)
   628  	subtests = append(subtests, name)
   629  	ns := &State{
   630  		// Set hasError to false; State for a subtest always starts with no error.
   631  		globalMixin: s.testRoot.entityRoot.newGlobalMixin(strings.Join(subtests, "/")+": ", false),
   632  		entityMixin: s.testRoot.entityRoot.newEntityMixin(),
   633  		testMixin:   s.testRoot.newTestMixin(),
   634  		testRoot:    s.testRoot,
   635  		subtests:    subtests,
   636  	}
   637  
   638  	finished := make(chan struct{})
   639  
   640  	go func() {
   641  		defer func() {
   642  			if r := recover(); r != nil {
   643  				usercode.ErrorOnPanic(ns)(r)
   644  			}
   645  		}()
   646  
   647  		ctx, cancel := context.WithCancel(ctx)
   648  		defer cancel()
   649  
   650  		ctx, st := timing.Start(ctx, name)
   651  		defer func() {
   652  			st.End()
   653  			close(finished)
   654  		}()
   655  
   656  		s.Logf("Starting subtest %s", strings.Join(subtests, "/"))
   657  		run(ctx, ns)
   658  	}()
   659  
   660  	<-finished
   661  
   662  	ns.mu.Lock()
   663  	defer ns.mu.Unlock()
   664  	// Bubble up errors to the parent State. Note that errors are already
   665  	// reported to TestEntityRoot, so it is sufficient to set hasError directly.
   666  	if ns.hasError {
   667  		s.mu.Lock()
   668  		defer s.mu.Unlock()
   669  		s.hasError = true
   670  	}
   671  
   672  	return !ns.hasError
   673  }
   674  
   675  // PreValue returns a value supplied by the test's precondition, which must have been declared via Test.Pre
   676  // when the test was registered. Callers should cast the returned empty interface to the correct pointer
   677  // type; see the relevant precondition's documentation for specifics.
   678  // nil will be returned if the test did not declare a precondition.
   679  func (s *State) PreValue() interface{} { return s.testRoot.preValue }
   680  
   681  // Meta returns information about how the "tast" process used to initiate testing was run.
   682  // It can only be called by remote tests in the "meta" category.
   683  func (s *State) Meta() *Meta {
   684  	if parts := strings.SplitN(s.testRoot.test.Name, ".", 2); len(parts) != 2 || parts[0] != metaCategory {
   685  		panic(fmt.Sprintf("Meta info unavailable since test doesn't have category %q", metaCategory))
   686  	}
   687  	if s.testRoot.entityRoot.cfg.RemoteData == nil {
   688  		panic("Meta info unavailable (is test non-remote?)")
   689  	}
   690  	// Return a copy to make sure the test doesn't modify the original struct.
   691  	return s.testRoot.entityRoot.cfg.RemoteData.Meta.clone()
   692  }
   693  
   694  // FixtValue returns the fixture value if the test depends on a fixture in the same process.
   695  // FixtValue returns nil otherwise.
   696  func (s *State) FixtValue() interface{} {
   697  	return s.testRoot.entityRoot.cfg.FixtValue
   698  }
   699  
   700  // FixtFillValue stores the deserialized result in the value pointed to by v.
   701  func (s *State) FixtFillValue(v any) error {
   702  	data, err := s.testRoot.entityRoot.cfg.FixtSerializedValue()
   703  	if err != nil {
   704  		return errors.Wrap(err, "failed to get serialize fixture value")
   705  	}
   706  	if err := json.Unmarshal(data, v); err != nil {
   707  		return errors.Wrap(err, "failed to deserialize fixture value")
   708  	}
   709  	return nil
   710  }
   711  
   712  // PreState holds state relevant to the execution of a single precondition.
   713  //
   714  // This is a State for preconditions. See State's documentation for general
   715  // guidance on how to treat PreState in preconditions.
   716  type PreState struct {
   717  	*globalMixin
   718  	*entityMixin
   719  	*testMixin
   720  }
   721  
   722  // PreCtx returns a context that lives as long as the precondition.
   723  // Can only be called from inside a precondition; it panics otherwise.
   724  func (s *PreState) PreCtx() context.Context {
   725  	return s.testRoot.entityRoot.cfg.PreCtx
   726  }
   727  
   728  // TestHookState holds state relevant to the execution of a test hook.
   729  //
   730  // This is a State for test hooks. See State's documentation for general
   731  // guidance on how to treat TestHookState in test hooks.
   732  type TestHookState struct {
   733  	*globalMixin
   734  	*entityMixin
   735  	*testMixin
   736  }
   737  
   738  // Purgeable returns a list of paths of purgeable cache files. This list may
   739  // contain external data files downloaded previously but unused in the next
   740  // test, and the like. Test hooks can delete those files safely without
   741  // disrupting test execution if the disk space is low.
   742  // Some files might be already removed, so test hooks should ignore "file not
   743  // found" errors. Some files might have hard links, so test hooks should not
   744  // assume that deleting an 1GB file frees 1GB space.
   745  func (s *TestHookState) Purgeable() []string {
   746  	return append([]string(nil), s.testRoot.entityRoot.cfg.Purgeable...)
   747  }
   748  
   749  // MaxSysMsgLogSize return the flag size of truncate log file.
   750  func (s *TestHookState) MaxSysMsgLogSize() int64 {
   751  	return s.testRoot.entityRoot.cfg.MaxSysMsgLogSize
   752  }
   753  
   754  // CompanionDUTRoles returns an alphabetically sorted array of
   755  // companion DUT roles.
   756  func (s *TestHookState) CompanionDUTRoles() []string {
   757  	duts := s.testRoot.entityRoot.cfg.RemoteData.CompanionDUTs
   758  
   759  	roles := make([]string, 0, len(duts))
   760  	for k := range duts {
   761  		roles = append(roles, k)
   762  	}
   763  	sort.Strings(roles)
   764  	return roles
   765  }
   766  
   767  // FixtState is the state the framework passes to Fixture.SetUp and Fixture.TearDown.
   768  type FixtState struct {
   769  	*globalMixin
   770  	*entityMixin
   771  
   772  	entityRoot *EntityRoot
   773  }
   774  
   775  // FixtContext returns fixture-scoped context. i.e. the context is alive until TearDown returns.
   776  // The context is also associated with the fixture metadata. For example,
   777  // testing.ContextOutDir(ctx) returns the output directory allocated to the fixture.
   778  func (s *FixtState) FixtContext() context.Context {
   779  	return s.entityRoot.cfg.FixtCtx
   780  }
   781  
   782  // Param returns Val specified at the Param struct for the current fixture.
   783  func (s *FixtState) Param() interface{} {
   784  	// TODO(oka): Implement it.
   785  	panic("to be implemented")
   786  }
   787  
   788  // ParentFillValue stores the parent fixture value iin the value pointed to by v.
   789  func (s *FixtState) ParentFillValue(v any) error {
   790  	data, err := s.entityRoot.cfg.FixtSerializedValue()
   791  	if err != nil {
   792  		return errors.Wrap(err, "failed to get serialize fixture value")
   793  	}
   794  	if err := json.Unmarshal(data, v); err != nil {
   795  		return errors.Wrap(err, "failed to deserialize fixture value")
   796  	}
   797  	return nil
   798  }
   799  
   800  // ParentValue returns the parent fixture value if the fixture has a parent in the same process.
   801  // ParentValue returns nil otherwise.
   802  func (s *FixtState) ParentValue() interface{} {
   803  	return s.entityRoot.cfg.FixtValue
   804  }
   805  
   806  // OutDir returns a directory into which the entity may place arbitrary files
   807  // that should be included with the entity results.
   808  func (s *FixtState) OutDir() string {
   809  	return s.entityRoot.cfg.OutDir
   810  }
   811  
   812  // FixtTestState is the state the framework passes to PreTest and PostTest.
   813  type FixtTestState struct {
   814  	*globalMixin
   815  	testCtx  context.Context
   816  	testName string
   817  }
   818  
   819  // OutDir returns a directory into which the entity may place arbitrary files
   820  // that should be included with the entity results.
   821  func (s *FixtTestState) OutDir() string {
   822  	return s.entityRoot.cfg.OutDir
   823  }
   824  
   825  // TestContext returns context associated with the test.
   826  // It has the same lifetime as the test (including PreTest and PostTest), and
   827  // the same metadata as the ctx passed to PreTest and PostTest.
   828  func (s *FixtTestState) TestContext() context.Context {
   829  	return s.testCtx
   830  }
   831  
   832  // TestName returns test name of the test.
   833  func (s *FixtTestState) TestName() string {
   834  	return s.testName
   835  }