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 }