github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/across_step_test.go (about)

     1  package exec_test
     2  
     3  import (
     4  	"context"
     5  
     6  	"code.cloudfoundry.org/lager/lagerctx"
     7  	"github.com/pf-qiu/concourse/v6/atc"
     8  	"github.com/pf-qiu/concourse/v6/atc/exec"
     9  	"github.com/pf-qiu/concourse/v6/atc/exec/execfakes"
    10  	"github.com/pf-qiu/concourse/v6/vars"
    11  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/gomega"
    13  	"github.com/onsi/gomega/gbytes"
    14  )
    15  
    16  var _ = Describe("AcrossStep", func() {
    17  	type vals [3]interface{}
    18  
    19  	var (
    20  		ctx    context.Context
    21  		cancel func()
    22  
    23  		fakeDelegateFactory *execfakes.FakeBuildStepDelegateFactory
    24  		fakeDelegate        *execfakes.FakeBuildStepDelegate
    25  
    26  		step exec.AcrossStep
    27  
    28  		acrossVars []atc.AcrossVar
    29  		steps      []exec.ScopedStep
    30  		state      exec.RunState
    31  		failFast   bool
    32  
    33  		allVals []vals
    34  
    35  		started   chan vals
    36  		terminate map[vals]chan error
    37  
    38  		stepMetadata = exec.StepMetadata{
    39  			TeamID:       123,
    40  			TeamName:     "some-team",
    41  			BuildID:      42,
    42  			BuildName:    "some-build",
    43  			PipelineID:   4567,
    44  			PipelineName: "some-pipeline",
    45  		}
    46  
    47  		stderr *gbytes.Buffer
    48  	)
    49  
    50  	stepRun := func(succeeded bool, values vals) func(context.Context, exec.RunState) (bool, error) {
    51  		started := started
    52  		terminate := terminate
    53  
    54  		return func(ctx context.Context, childState exec.RunState) (bool, error) {
    55  			defer GinkgoRecover()
    56  
    57  			By("having the correct var values")
    58  			for i, v := range acrossVars {
    59  				val, found, _ := childState.Get(vars.Reference{Source: ".", Path: v.Var})
    60  				Expect(found).To(BeTrue(), "unset variable "+v.Var)
    61  				Expect(val).To(Equal(values[i]), "invalid value for variable "+v.Var)
    62  			}
    63  
    64  			By("running with a child scope")
    65  			Expect(childState.Parent()).To(Equal(state))
    66  
    67  			started <- values
    68  			if c, ok := terminate[values]; ok {
    69  				select {
    70  				case err := <-c:
    71  					return false, err
    72  				case <-ctx.Done():
    73  					return false, ctx.Err()
    74  				}
    75  			}
    76  			return succeeded, nil
    77  		}
    78  	}
    79  
    80  	scopedStepFactory := func(acrossVars []atc.AcrossVar, values vals) exec.ScopedStep {
    81  		s := new(execfakes.FakeStep)
    82  		s.RunStub = stepRun(true, values)
    83  		return exec.ScopedStep{
    84  			Values: values[:],
    85  			Step:   s,
    86  		}
    87  	}
    88  
    89  	BeforeEach(func() {
    90  		ctx, cancel = context.WithCancel(context.Background())
    91  		ctx = lagerctx.NewContext(ctx, testLogger)
    92  
    93  		state = exec.NewRunState(noopStepper, vars.StaticVariables{}, false)
    94  
    95  		stderr = gbytes.NewBuffer()
    96  
    97  		fakeDelegate = new(execfakes.FakeBuildStepDelegate)
    98  		fakeDelegate.StderrReturns(stderr)
    99  
   100  		fakeDelegateFactory = new(execfakes.FakeBuildStepDelegateFactory)
   101  		fakeDelegateFactory.BuildStepDelegateReturns(fakeDelegate)
   102  
   103  		acrossVars = []atc.AcrossVar{
   104  			{
   105  				Var:         "var1",
   106  				Values:      []interface{}{"a1", "a2"},
   107  				MaxInFlight: &atc.MaxInFlightConfig{All: true},
   108  			},
   109  			{
   110  				Var:    "var2",
   111  				Values: []interface{}{"b1", "b2"},
   112  			},
   113  			{
   114  				Var:         "var3",
   115  				Values:      []interface{}{"c1", "c2"},
   116  				MaxInFlight: &atc.MaxInFlightConfig{Limit: 2},
   117  			},
   118  		}
   119  		started = make(chan vals, 8)
   120  		terminate = map[vals]chan error{}
   121  
   122  		allVals = []vals{
   123  			{"a1", "b1", "c1"},
   124  			{"a1", "b1", "c2"},
   125  
   126  			{"a1", "b2", "c1"},
   127  			{"a1", "b2", "c2"},
   128  
   129  			{"a2", "b1", "c1"},
   130  			{"a2", "b1", "c2"},
   131  
   132  			{"a2", "b2", "c1"},
   133  			{"a2", "b2", "c2"},
   134  		}
   135  
   136  		steps = make([]exec.ScopedStep, len(allVals))
   137  		for i, v := range allVals {
   138  			steps[i] = scopedStepFactory(acrossVars, v)
   139  		}
   140  
   141  		failFast = false
   142  	})
   143  
   144  	AfterEach(func() {
   145  		cancel()
   146  	})
   147  
   148  	JustBeforeEach(func() {
   149  		step = exec.Across(
   150  			acrossVars,
   151  			steps,
   152  			failFast,
   153  			fakeDelegateFactory,
   154  			stepMetadata,
   155  		)
   156  	})
   157  
   158  	It("logs a warning to stderr", func() {
   159  		_, err := step.Run(ctx, state)
   160  		Expect(err).ToNot(HaveOccurred())
   161  
   162  		Expect(stderr).To(gbytes.Say("WARNING: the across step is experimental"))
   163  		Expect(stderr).To(gbytes.Say("follow RFC #29 for updates"))
   164  	})
   165  
   166  	It("initializes the step", func() {
   167  		step.Run(ctx, state)
   168  
   169  		Expect(fakeDelegate.InitializingCallCount()).To(Equal(1))
   170  	})
   171  
   172  	It("starts the step", func() {
   173  		step.Run(ctx, state)
   174  
   175  		Expect(fakeDelegate.StartingCallCount()).To(Equal(1))
   176  	})
   177  
   178  	It("finishes the step", func() {
   179  		step.Run(ctx, state)
   180  
   181  		Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
   182  	})
   183  
   184  	Context("when a var shadows an existing local var", func() {
   185  		BeforeEach(func() {
   186  			state.AddLocalVar("var2", 123, false)
   187  		})
   188  
   189  		It("logs a warning to stderr", func() {
   190  			_, err := step.Run(ctx, state)
   191  			Expect(err).ToNot(HaveOccurred())
   192  
   193  			Expect(stderr).To(gbytes.Say("WARNING: across step shadows local var 'var2'"))
   194  		})
   195  	})
   196  
   197  	Describe("parallel execution", func() {
   198  		BeforeEach(func() {
   199  			for _, v := range allVals {
   200  				terminate[v] = make(chan error, 1)
   201  			}
   202  		})
   203  
   204  		It("steps are run in parallel according to the MaxInFlight for each var", func() {
   205  			go step.Run(ctx, state)
   206  
   207  			By("running the first stage")
   208  			var receivedVals []vals
   209  			for i := 0; i < 4; i++ {
   210  				receivedVals = append(receivedVals, <-started)
   211  			}
   212  			Expect(receivedVals).To(ConsistOf(
   213  				vals{"a1", "b1", "c1"},
   214  				vals{"a1", "b1", "c2"},
   215  				vals{"a2", "b1", "c1"},
   216  				vals{"a2", "b1", "c2"},
   217  			))
   218  			Consistently(started).ShouldNot(Receive())
   219  
   220  			By("the first stage completing successfully")
   221  			for _, v := range receivedVals {
   222  				terminate[v] <- nil
   223  			}
   224  
   225  			By("running the second stage")
   226  			receivedVals = []vals{}
   227  			for i := 0; i < 4; i++ {
   228  				receivedVals = append(receivedVals, <-started)
   229  			}
   230  			Expect(receivedVals).To(ConsistOf(
   231  				vals{"a1", "b2", "c1"},
   232  				vals{"a1", "b2", "c2"},
   233  				vals{"a2", "b2", "c1"},
   234  				vals{"a2", "b2", "c2"},
   235  			))
   236  		})
   237  
   238  		Context("when fail fast is true", func() {
   239  			BeforeEach(func() {
   240  				failFast = true
   241  			})
   242  
   243  			It("stops running steps after a failure", func() {
   244  				By("a step in the first stage failing")
   245  				terminate[allVals[1]] <- nil
   246  				steps[1].Step.(*execfakes.FakeStep).RunStub = stepRun(false, allVals[1])
   247  
   248  				By("running the step")
   249  				ok, err := step.Run(ctx, state)
   250  				Expect(err).ToNot(HaveOccurred())
   251  				Expect(ok).To(BeFalse())
   252  
   253  				By("ensuring not all steps were started")
   254  				Expect(started).ToNot(HaveLen(8))
   255  			})
   256  		})
   257  
   258  		Context("when fail fast is false", func() {
   259  			BeforeEach(func() {
   260  				failFast = false
   261  			})
   262  
   263  			It("allows all steps to run before failing", func() {
   264  				By("a step in the first stage failing")
   265  				steps[1].Step.(*execfakes.FakeStep).RunStub = stepRun(false, allVals[1])
   266  
   267  				for _, v := range allVals {
   268  					terminate[v] <- nil
   269  				}
   270  
   271  				By("running the step")
   272  				ok, err := step.Run(ctx, state)
   273  				Expect(err).ToNot(HaveOccurred())
   274  				Expect(ok).To(BeFalse())
   275  
   276  				By("ensuring all steps were run")
   277  				Expect(started).To(HaveLen(8))
   278  			})
   279  		})
   280  	})
   281  
   282  	Describe("panic recovery", func() {
   283  		Context("when one step panics", func() {
   284  			BeforeEach(func() {
   285  				steps[1].Step.(*execfakes.FakeStep).RunStub = func(context.Context, exec.RunState) (bool, error) {
   286  					panic("something went wrong")
   287  				}
   288  			})
   289  
   290  			It("handles it gracefully", func() {
   291  				_, err := step.Run(ctx, state)
   292  				Expect(err).To(HaveOccurred())
   293  				Expect(err.Error()).To(ContainSubstring("something went wrong"))
   294  			})
   295  		})
   296  	})
   297  })