| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package planner |
| |
| import ( |
| "context" |
| "fmt" |
| "os" |
| "path/filepath" |
| gotesting "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| |
| "chromiumos/tast/errors" |
| "chromiumos/tast/internal/logging" |
| "chromiumos/tast/internal/protocol" |
| "chromiumos/tast/internal/testcontext" |
| "chromiumos/tast/internal/testing" |
| "chromiumos/tast/internal/testing/testfixture" |
| "chromiumos/tast/testutil" |
| ) |
| |
| // TestFixtureStackInitStatus checks the initial status of a fixture stack. |
| func TestFixtureStackInitStatus(t *gotesting.T) { |
| stack := NewFixtureStack(&Config{}, newOutputSink()) |
| |
| if got := stack.Status(); got != statusGreen { |
| t.Fatalf("Initial status is %v; want %v", got, statusGreen) |
| } |
| } |
| |
| // TestFixtureStackStatusTransitionGreen tests status transition of a fixture |
| // stack on pushing healthy fixtures. |
| func TestFixtureStackStatusTransitionGreen(t *gotesting.T) { |
| ctx := context.Background() |
| stack := NewFixtureStack(&Config{}, newOutputSink()) |
| |
| pushGreen := func() error { |
| return stack.Push(ctx, &testing.FixtureInstance{Impl: testfixture.New()}) |
| } |
| reset := func() error { |
| return stack.Reset(ctx) |
| } |
| pop := func() error { |
| return stack.Pop(ctx) |
| } |
| |
| for i, step := range []struct { |
| f func() error |
| want fixtureStatus |
| }{ |
| {pushGreen, statusGreen}, |
| {pushGreen, statusGreen}, |
| {pushGreen, statusGreen}, |
| {reset, statusGreen}, |
| {pop, statusGreen}, |
| {pop, statusGreen}, |
| {pop, statusGreen}, |
| } { |
| if err := step.f(); err != nil { |
| t.Fatalf("Step %d: %v", i, err) |
| } |
| if got := stack.Status(); got != step.want { |
| t.Fatalf("Step %d: got %v, want %v", i, got, step.want) |
| } |
| } |
| } |
| |
| // TestFixtureStackStatusTransitionRed tests status transition of a fixture |
| // stack on pushing a fixture that fails to set up. |
| func TestFixtureStackStatusTransitionRed(t *gotesting.T) { |
| ctx := context.Background() |
| stack := NewFixtureStack(&Config{}, newOutputSink()) |
| |
| pushGreen := func() error { |
| return stack.Push(ctx, &testing.FixtureInstance{Impl: testfixture.New()}) |
| } |
| pushRed := func() error { |
| return stack.Push(ctx, &testing.FixtureInstance{ |
| Impl: testfixture.New(testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| s.Error("Failed") |
| return nil |
| })), |
| }) |
| } |
| pop := func() error { |
| return stack.Pop(ctx) |
| } |
| |
| for i, step := range []struct { |
| f func() error |
| want fixtureStatus |
| }{ |
| {pushGreen, statusGreen}, |
| {pushGreen, statusGreen}, |
| {pushGreen, statusGreen}, |
| {pushRed, statusRed}, |
| {pushGreen, statusRed}, |
| {pushGreen, statusRed}, |
| {pushGreen, statusRed}, |
| {pop, statusRed}, |
| {pop, statusRed}, |
| {pop, statusRed}, |
| {pop, statusGreen}, |
| {pop, statusGreen}, |
| {pop, statusGreen}, |
| {pop, statusGreen}, |
| } { |
| if err := step.f(); err != nil { |
| t.Fatalf("Step %d: %v", i, err) |
| } |
| if got := stack.Status(); got != step.want { |
| t.Fatalf("Step %d: got %v, want %v", i, got, step.want) |
| } |
| } |
| } |
| |
| // TestFixtureStackStatusTransitionYellow tests status transition of a fixture |
| // stack on pushing a fixture that fails to reset. |
| func TestFixtureStackStatusTransitionYellow(t *gotesting.T) { |
| ctx := context.Background() |
| stack := NewFixtureStack(&Config{}, newOutputSink()) |
| |
| pushGreen := func() error { |
| return stack.Push(ctx, &testing.FixtureInstance{Impl: testfixture.New()}) |
| } |
| pushYellow := func() error { |
| return stack.Push(ctx, &testing.FixtureInstance{ |
| Impl: testfixture.New(testfixture.WithReset(func(ctx context.Context) error { |
| return errors.New("failed") |
| })), |
| }) |
| } |
| reset := func() error { |
| return stack.Reset(ctx) |
| } |
| pop := func() error { |
| return stack.Pop(ctx) |
| } |
| |
| for i, step := range []struct { |
| f func() error |
| want fixtureStatus |
| }{ |
| {pushGreen, statusGreen}, |
| {pushYellow, statusGreen}, |
| {pushGreen, statusGreen}, |
| {reset, statusYellow}, |
| {pop, statusYellow}, |
| {pop, statusGreen}, |
| {pop, statusGreen}, |
| } { |
| if err := step.f(); err != nil { |
| t.Fatalf("Step %d: %v", i, err) |
| } |
| if got := stack.Status(); got != step.want { |
| t.Fatalf("Step %d: got %v, want %v", i, got, step.want) |
| } |
| } |
| } |
| |
| // TestFixtureStackMarkDirty tests dirtiness check of fixture stacks. |
| func TestFixtureStackMarkDirty(t *gotesting.T) { |
| ctx := context.Background() |
| stack := NewFixtureStack(&Config{}, newOutputSink()) |
| |
| if err := stack.MarkDirty(); err != nil { |
| t.Errorf("MarkDirty failed for initial stack: %v", err) |
| } |
| if err := stack.MarkDirty(); err == nil { |
| t.Error("MarkDirty unexpectedly succeeded for a dirty stack") |
| } |
| |
| if err := stack.Reset(ctx); err != nil { |
| t.Errorf("Reset failed: %v", err) |
| } |
| |
| if err := stack.MarkDirty(); err != nil { |
| t.Errorf("MarkDirty failed for reset stack: %v", err) |
| } |
| if err := stack.MarkDirty(); err == nil { |
| t.Error("MarkDirty unexpectedly succeeded for a dirty stack") |
| } |
| } |
| |
| // TestFixtureStackContext checks context.Context passed to fixture methods. |
| func TestFixtureStackContext(t *gotesting.T) { |
| const fixtureName = "fixt" |
| serviceDeps := []string{"svc1", "svc2"} |
| |
| ctx := context.Background() |
| |
| baseOutDir := testutil.TempDir(t) |
| defer os.RemoveAll(baseOutDir) |
| |
| stack := NewFixtureStack(&Config{OutDir: baseOutDir}, newOutputSink()) |
| |
| fixtureOutDir := filepath.Join(baseOutDir, fixtureName) |
| testOutDir := filepath.Join(baseOutDir, "pkg.Test") |
| |
| verifyContext := func(t *gotesting.T, ctx context.Context, wantOutDir string) { |
| t.Helper() |
| if svcs, ok := testcontext.ServiceDeps(ctx); !ok { |
| t.Error("ServiceDeps not available") |
| } else if diff := cmp.Diff(svcs, serviceDeps); diff != "" { |
| t.Errorf("ServiceDeps mismatch (-got +want):\n%s", diff) |
| } |
| if _, ok := testcontext.SoftwareDeps(ctx); ok { |
| t.Error("SoftwareDeps unexpectedly available") |
| } |
| outDir, ok := testcontext.OutDir(ctx) |
| if !ok { |
| t.Error("OutDir not available") |
| } else if outDir != wantOutDir { |
| t.Errorf("OutDir = %q; want %q", outDir, wantOutDir) |
| } |
| } |
| |
| ff := testfixture.New( |
| testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| t.Run("SetUp", func(t *gotesting.T) { |
| verifyContext(t, ctx, fixtureOutDir) |
| verifyContext(t, s.FixtContext(), fixtureOutDir) |
| }) |
| return nil |
| }), |
| testfixture.WithReset(func(ctx context.Context) error { |
| t.Run("Reset", func(t *gotesting.T) { |
| verifyContext(t, ctx, fixtureOutDir) |
| }) |
| return nil |
| }), |
| testfixture.WithPreTest(func(ctx context.Context, s *testing.FixtTestState) { |
| t.Run("PreTest", func(t *gotesting.T) { |
| verifyContext(t, ctx, testOutDir) |
| verifyContext(t, s.TestContext(), testOutDir) |
| }) |
| }), |
| testfixture.WithPostTest(func(ctx context.Context, s *testing.FixtTestState) { |
| t.Run("PostTest", func(t *gotesting.T) { |
| verifyContext(t, ctx, testOutDir) |
| verifyContext(t, s.TestContext(), testOutDir) |
| }) |
| }), |
| testfixture.WithTearDown(func(ctx context.Context, s *testing.FixtState) { |
| t.Run("TearDown", func(t *gotesting.T) { |
| verifyContext(t, ctx, fixtureOutDir) |
| verifyContext(t, s.FixtContext(), fixtureOutDir) |
| }) |
| })) |
| |
| fixt := &testing.FixtureInstance{ |
| Name: fixtureName, |
| Impl: ff, |
| ServiceDeps: serviceDeps, |
| } |
| |
| troot := testing.NewTestEntityRoot(&testing.TestInstance{}, &testing.RuntimeConfig{OutDir: testOutDir}, nil) |
| |
| if err := stack.Push(ctx, fixt); err != nil { |
| t.Fatal("Push: ", err) |
| } |
| if err := stack.Reset(ctx); err != nil { |
| t.Fatal("Reset: ", err) |
| } |
| postTest, err := stack.PreTest(ctx, troot) |
| if err != nil { |
| t.Fatal("PreTest: ", err) |
| } |
| if err := postTest(ctx, troot); err != nil { |
| t.Fatal("PostTest: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop: ", err) |
| } |
| } |
| |
| // TestFixtureStackState checks state objects passed to fixture methods. |
| func TestFixtureStackState(t *gotesting.T) { |
| const localBundleDir = "/path/to/local/bundles" |
| |
| rd := &testing.RemoteData{ |
| RPCHint: testing.NewRPCHint(localBundleDir, nil), |
| } |
| |
| ctx := context.Background() |
| stack := NewFixtureStack(&Config{RemoteData: rd}, newOutputSink()) |
| |
| type stateLike interface { |
| RPCHint() *testing.RPCHint |
| } |
| verifyState := func(t *gotesting.T, s stateLike) { |
| if dir := testing.ExtractLocalBundleDir(s.RPCHint()); dir != localBundleDir { |
| t.Errorf("localBundleDir of RPCHint = %q; want %q", dir, localBundleDir) |
| } |
| } |
| |
| ff := testfixture.New( |
| testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| t.Run("SetUp", func(t *gotesting.T) { |
| verifyState(t, s) |
| }) |
| return nil |
| }), |
| testfixture.WithPreTest(func(ctx context.Context, s *testing.FixtTestState) { |
| t.Run("PreTest", func(t *gotesting.T) { |
| verifyState(t, s) |
| }) |
| }), |
| testfixture.WithPostTest(func(ctx context.Context, s *testing.FixtTestState) { |
| t.Run("PostTest", func(t *gotesting.T) { |
| verifyState(t, s) |
| }) |
| }), |
| testfixture.WithTearDown(func(ctx context.Context, s *testing.FixtState) { |
| t.Run("TearDown", func(t *gotesting.T) { |
| verifyState(t, s) |
| }) |
| })) |
| |
| fixt := &testing.FixtureInstance{ |
| Impl: ff, |
| } |
| |
| troot := testing.NewTestEntityRoot(&testing.TestInstance{}, &testing.RuntimeConfig{RemoteData: rd}, nil) |
| |
| if err := stack.Push(ctx, fixt); err != nil { |
| t.Fatal("Push: ", err) |
| } |
| postTest, err := stack.PreTest(ctx, troot) |
| if err != nil { |
| t.Fatal("PreTest: ", err) |
| } |
| if err := postTest(ctx, troot); err != nil { |
| t.Fatal("PostTest: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop: ", err) |
| } |
| } |
| |
| // TestFixtureStackVal tests that fixture values are passed around correctly. |
| func TestFixtureStackVal(t *gotesting.T) { |
| const ( |
| val1 = "val1" |
| val2 = "val2" |
| ) |
| |
| ctx := context.Background() |
| stack := NewFixtureStack(&Config{}, newOutputSink()) |
| |
| if val := stack.Val(); val != nil { |
| t.Errorf("Init: Val() = %v; want nil", val) |
| } |
| |
| // Push fixtures and check values. |
| if err := stack.Push(ctx, &testing.FixtureInstance{ |
| Impl: testfixture.New( |
| testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| if val := s.ParentValue(); val != nil { |
| t.Errorf("SetUp 1: ParentValue() = %v; want nil", val) |
| } |
| return val1 |
| }))}); err != nil { |
| t.Fatal("Push 1: ", err) |
| } |
| if val := stack.Val(); val != val1 { |
| t.Errorf("After Push 1: Val() = %v; want %v", val, val1) |
| } |
| |
| if err := stack.Push(ctx, &testing.FixtureInstance{ |
| Impl: testfixture.New( |
| testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| if val := s.ParentValue(); val != val1 { |
| t.Errorf("SetUp 2: ParentValue() = %v; want %v", val, val1) |
| } |
| return val2 |
| }), |
| testfixture.WithReset(func(ctx context.Context) error { |
| return errors.New("failure") |
| }))}); err != nil { |
| t.Fatal("Push 2: ", err) |
| } |
| if val := stack.Val(); val != val2 { |
| t.Errorf("After Push 2: Val() = %v; want %v", val, val2) |
| } |
| |
| // Call Reset. Even if the stack is yellow, Val still succeeds. |
| if s := stack.Status(); s != statusGreen { |
| t.Errorf("After Push 2: Status() = %v; want %v", s, statusGreen) |
| } |
| if err := stack.Reset(ctx); err != nil { |
| t.Fatal("Reset: ", err) |
| } |
| if s := stack.Status(); s != statusYellow { |
| t.Errorf("After Reset: Status() = %v; want %v", s, statusYellow) |
| } |
| if val := stack.Val(); val != val2 { |
| t.Errorf("After Reset: Val() = %v; want %v", val, val2) |
| } |
| |
| // Pop fixtures. |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 2: ", err) |
| } |
| if val := stack.Val(); val != val1 { |
| t.Errorf("After Pop 2: Val() = %v; want %v", val, val1) |
| } |
| |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 1: ", err) |
| } |
| if val := stack.Val(); val != nil { |
| t.Errorf("After Pop 1: Val() = %v; want nil", val) |
| } |
| } |
| |
| // TestFixtureStackErrors tests Errors method. |
| func TestFixtureStackErrors(t *gotesting.T) { |
| ctx := context.Background() |
| stack := NewFixtureStack(&Config{}, newOutputSink()) |
| |
| id := 0 |
| pushGreen := func() error { |
| name := fmt.Sprintf("fixt.Green%d", id) |
| id++ |
| return stack.Push(ctx, &testing.FixtureInstance{ |
| Name: name, |
| Impl: testfixture.New(), |
| }) |
| } |
| pushRed := func() error { |
| name := fmt.Sprintf("fixt.Red%d", id) |
| id++ |
| return stack.Push(ctx, &testing.FixtureInstance{ |
| Name: name, |
| Impl: testfixture.New(testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| s.Error("Setup failure 1") |
| s.Error("Setup failure 2") |
| return nil |
| })), |
| }) |
| } |
| pop := func() error { |
| return stack.Pop(ctx) |
| } |
| |
| wantErrs := []*protocol.Error{ |
| {Reason: "[Fixture failure] fixt.Red1: Setup failure 1"}, |
| {Reason: "[Fixture failure] fixt.Red1: Setup failure 2"}, |
| } |
| |
| for i, step := range []struct { |
| f func() error |
| want []*protocol.Error |
| }{ |
| {pushGreen, nil}, |
| {pushRed, wantErrs}, |
| {pushRed, wantErrs}, |
| {pop, wantErrs}, |
| {pop, nil}, |
| {pop, nil}, |
| } { |
| if err := step.f(); err != nil { |
| t.Fatalf("Step %d: %v", i, err) |
| } |
| got := stack.Errors() |
| if diff := cmp.Diff(got, step.want, cmpopts.IgnoreFields(protocol.Error{}, "Location")); diff != "" { |
| t.Fatalf("Step %d: Errors mismatch (-got +want):\n%s", i, diff) |
| } |
| } |
| } |
| |
| // TestFixtureStackOutputGreen tests control message outputs when all fixtures |
| // are healthy. |
| func TestFixtureStackOutputGreen(t *gotesting.T) { |
| ctx := context.Background() |
| sink := newOutputSink() |
| ti := &testing.TestInstance{Name: "pkg.Test"} |
| troot := testing.NewTestEntityRoot(ti, &testing.RuntimeConfig{}, newEntityOutputStream(sink, ti.EntityProto())) |
| stack := NewFixtureStack(&Config{}, sink) |
| |
| newLoggingFixture := func(id int) *testing.FixtureInstance { |
| return &testing.FixtureInstance{ |
| Name: fmt.Sprintf("fixt%d", id), |
| Impl: testfixture.New( |
| testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| logging.Infof(ctx, "SetUp %d via Context", id) |
| logging.Infof(s.FixtContext(), "SetUp %d via Fixture-scoped Context", id) |
| s.Logf("SetUp %d via State", id) |
| return nil |
| }), |
| testfixture.WithReset(func(ctx context.Context) error { |
| logging.Infof(ctx, "Reset %d via Context", id) |
| return nil |
| }), |
| testfixture.WithPreTest(func(ctx context.Context, s *testing.FixtTestState) { |
| logging.Infof(ctx, "PreTest %d via Context", id) |
| s.Logf("PreTest %d via State", id) |
| }), |
| testfixture.WithPostTest(func(ctx context.Context, s *testing.FixtTestState) { |
| logging.Infof(ctx, "PostTest %d via Context", id) |
| s.Logf("PostTest %d via State", id) |
| }), |
| testfixture.WithTearDown(func(ctx context.Context, s *testing.FixtState) { |
| logging.Infof(ctx, "TearDown %d via Context", id) |
| logging.Infof(s.FixtContext(), "TearDown %d via Fixture-scoped Context", id) |
| s.Logf("TearDown %d via State", id) |
| })), |
| } |
| } |
| fixt1 := newLoggingFixture(1) |
| fixt2 := newLoggingFixture(2) |
| |
| if err := stack.Push(ctx, fixt1); err != nil { |
| t.Fatal("Push 1: ", err) |
| } |
| if err := stack.Push(ctx, fixt2); err != nil { |
| t.Fatal("Push 2: ", err) |
| } |
| postTest, err := stack.PreTest(ctx, troot) |
| if err != nil { |
| t.Fatal("PreTest: ", err) |
| } |
| if err := postTest(ctx, troot); err != nil { |
| t.Fatal("PostTest: ", err) |
| } |
| if err := stack.Reset(ctx); err != nil { |
| t.Fatal("Reset: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 2: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 1: ", err) |
| } |
| |
| msgs := sink.ReadAll() |
| |
| want := []protocol.Event{ |
| &protocol.EntityStartEvent{Entity: fixt1.EntityProto()}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "SetUp 1 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "SetUp 1 via Fixture-scoped Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "SetUp 1 via State"}, |
| &protocol.EntityStartEvent{Entity: fixt2.EntityProto()}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "SetUp 2 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "SetUp 2 via Fixture-scoped Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "SetUp 2 via State"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PreTest 1 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PreTest 1 via State"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PreTest 2 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PreTest 2 via State"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PostTest 2 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PostTest 2 via State"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PostTest 1 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "pkg.Test", Text: "PostTest 1 via State"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "Reset 1 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "Reset 2 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "TearDown 2 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "TearDown 2 via Fixture-scoped Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "TearDown 2 via State"}, |
| &protocol.EntityEndEvent{EntityName: "fixt2"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "TearDown 1 via Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "TearDown 1 via Fixture-scoped Context"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "TearDown 1 via State"}, |
| &protocol.EntityEndEvent{EntityName: "fixt1"}, |
| } |
| if diff := cmp.Diff(msgs, want); diff != "" { |
| t.Error("Output mismatch (-got +want):\n", diff) |
| } |
| } |
| |
| // TestFixtureStackOutputRed tests control message outputs when a fixture fails |
| // to set up. |
| func TestFixtureStackOutputRed(t *gotesting.T) { |
| ctx := context.Background() |
| sink := newOutputSink() |
| stack := NewFixtureStack(&Config{}, sink) |
| |
| newLoggingFixture := func(id int, setUp bool) *testing.FixtureInstance { |
| return &testing.FixtureInstance{ |
| Name: fmt.Sprintf("fixt%d", id), |
| Impl: testfixture.New( |
| testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| logging.Infof(ctx, "SetUp %d", id) |
| if !setUp { |
| s.Errorf("SetUp %d failure", id) |
| } |
| return nil |
| }), |
| testfixture.WithReset(func(ctx context.Context) error { |
| logging.Infof(ctx, "Reset %d", id) |
| return nil |
| }), |
| testfixture.WithTearDown(func(ctx context.Context, s *testing.FixtState) { |
| logging.Infof(ctx, "TearDown %d", id) |
| })), |
| } |
| } |
| fixt1 := newLoggingFixture(1, true) |
| fixt2 := newLoggingFixture(2, false) |
| fixt3 := newLoggingFixture(3, true) |
| |
| // Push and pop three fixtures. Second fixture fails to set up. |
| if err := stack.Push(ctx, fixt1); err != nil { |
| t.Fatal("Push 1: ", err) |
| } |
| if err := stack.Push(ctx, fixt2); err != nil { |
| t.Fatal("Push 2: ", err) |
| } |
| if err := stack.Push(ctx, fixt3); err != nil { |
| t.Fatal("Push 3: ", err) |
| } |
| if err := stack.Reset(ctx); err != nil { |
| t.Fatal("Reset: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 3: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 2: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 1: ", err) |
| } |
| |
| msgs := sink.ReadAll() |
| |
| want := []protocol.Event{ |
| &protocol.EntityStartEvent{Entity: fixt1.EntityProto()}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "SetUp 1"}, |
| &protocol.EntityStartEvent{Entity: fixt2.EntityProto()}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "SetUp 2"}, |
| &protocol.EntityErrorEvent{EntityName: "fixt2", Error: &protocol.Error{Reason: "SetUp 2 failure"}}, |
| &protocol.EntityEndEvent{EntityName: "fixt2"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "TearDown 1"}, |
| &protocol.EntityEndEvent{EntityName: "fixt1"}, |
| } |
| if diff := cmp.Diff(msgs, want); diff != "" { |
| t.Error("Output mismatch (-got +want):\n", diff) |
| } |
| } |
| |
| // TestFixtureStackOutputYellow tests control message outputs when a fixture |
| // fails to reset. |
| func TestFixtureStackOutputYellow(t *gotesting.T) { |
| ctx := context.Background() |
| sink := newOutputSink() |
| stack := NewFixtureStack(&Config{}, sink) |
| |
| newLoggingFixture := func(id int, reset bool) *testing.FixtureInstance { |
| return &testing.FixtureInstance{ |
| Name: fmt.Sprintf("fixt%d", id), |
| Impl: testfixture.New( |
| testfixture.WithSetUp(func(ctx context.Context, s *testing.FixtState) interface{} { |
| logging.Infof(ctx, "SetUp %d", id) |
| return nil |
| }), |
| testfixture.WithReset(func(ctx context.Context) error { |
| logging.Infof(ctx, "Reset %d", id) |
| if !reset { |
| return errors.New("failure") |
| } |
| return nil |
| }), |
| testfixture.WithTearDown(func(ctx context.Context, s *testing.FixtState) { |
| logging.Infof(ctx, "TearDown %d", id) |
| })), |
| } |
| } |
| fixt1 := newLoggingFixture(1, true) |
| fixt2 := newLoggingFixture(2, false) |
| fixt3 := newLoggingFixture(3, true) |
| |
| // Push and pop three fixtures. Second fixture fails to reset. |
| if err := stack.Push(ctx, fixt1); err != nil { |
| t.Fatal("Push 1: ", err) |
| } |
| if err := stack.Push(ctx, fixt2); err != nil { |
| t.Fatal("Push 2: ", err) |
| } |
| if err := stack.Push(ctx, fixt3); err != nil { |
| t.Fatal("Push 3: ", err) |
| } |
| if err := stack.Reset(ctx); err != nil { |
| t.Fatal("Reset: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 3: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 2: ", err) |
| } |
| if err := stack.Pop(ctx); err != nil { |
| t.Fatal("Pop 1: ", err) |
| } |
| |
| msgs := sink.ReadAll() |
| |
| want := []protocol.Event{ |
| &protocol.EntityStartEvent{Entity: fixt1.EntityProto()}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "SetUp 1"}, |
| &protocol.EntityStartEvent{Entity: fixt2.EntityProto()}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "SetUp 2"}, |
| &protocol.EntityStartEvent{Entity: fixt3.EntityProto()}, |
| &protocol.EntityLogEvent{EntityName: "fixt3", Text: "SetUp 3"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "Reset 1"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "Reset 2"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "Fixture failed to reset: failure; recovering"}, |
| &protocol.EntityLogEvent{EntityName: "fixt3", Text: "TearDown 3"}, |
| &protocol.EntityEndEvent{EntityName: "fixt3"}, |
| &protocol.EntityLogEvent{EntityName: "fixt2", Text: "TearDown 2"}, |
| &protocol.EntityEndEvent{EntityName: "fixt2"}, |
| &protocol.EntityLogEvent{EntityName: "fixt1", Text: "TearDown 1"}, |
| &protocol.EntityEndEvent{EntityName: "fixt1"}, |
| } |
| if diff := cmp.Diff(msgs, want); diff != "" { |
| t.Error("Output mismatch (-got +want):\n", diff) |
| } |
| } |