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) {}