Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: E2E Framework [Background steps] #2541

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions test/e2e/framework/types/examples/background_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package types

import (
"fmt"
"log"
"sync"
"testing"
"time"

"github.com/Azure/azure-container-networking/test/e2e/framework/types"
)

func TestFramework(t *testing.T) {
job := types.NewJob("Validate that drop metrics are present in the prometheus endpoint")
runner := types.NewRunner(t, job)
defer runner.Run()

job.AddStep(&TestBackground{
CounterName: "Example Counter",
}, &types.StepOptions{
ExpectError: false,
RunInBackgroundWithID: "TestStep",
})

job.AddStep(&types.Sleep{
Duration: 1 * time.Second,
}, nil)

job.AddStep(&types.Stop{
BackgroundID: "TestStep",
}, nil)
}

type TestBackground struct {
CounterName string
c *counter
}

func (t *TestBackground) Run() error {
t.c = newCounter()
err := t.c.Start()
if err != nil {
return fmt.Errorf("failed to start counter: %w", err)
}
log.Println("running counter: " + t.CounterName)
return nil
}

func (t *TestBackground) Stop() error {
log.Println("stopping counter: " + t.CounterName)
err := t.c.Stop()
if err != nil {
return fmt.Errorf("failed to stop counter: %w", err)
}
log.Println("count:", t.c.count)
return nil
}

func (t *TestBackground) Prevalidate() error {
return nil
}

func (t *TestBackground) Postvalidate() error {
return nil
}

type counter struct {
ticker *time.Ticker
count int
ch chan struct{}
wg sync.WaitGroup
}

func newCounter() *counter {
return &counter{
ch: make(chan struct{}),
}
}

func (c *counter) Start() error {
c.ticker = time.NewTicker(1 * time.Millisecond)
c.wg.Add(1)
go func() {
for {
select {
case <-c.ticker.C:
c.count++
case <-c.ch:
c.wg.Done()
return
}
}
}()

return nil
}

func (c *counter) Stop() error {
close(c.ch)
c.wg.Wait()
return nil
}
119 changes: 91 additions & 28 deletions test/e2e/framework/types/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ var (
ErrNilError = fmt.Errorf("expected error to be nil")
ErrMissingParameter = fmt.Errorf("missing parameter")
ErrParameterAlreadySet = fmt.Errorf("parameter already set")
ErrOrphanSteps = fmt.Errorf("background steps with no corresponding stop")
ErrCannotStopStep = fmt.Errorf("cannot stop step")
)

type Job struct {
Values *JobValues
Description string
Steps []*StepWrapper
Values *JobValues
Description string
Steps []*StepWrapper
BackgroundSteps map[string]*StepWrapper
}

type StepWrapper struct {
Expand Down Expand Up @@ -45,7 +48,8 @@ func NewJob(description string) *Job {
Values: &JobValues{
kv: make(map[string]string),
},
Description: description,
BackgroundSteps: make(map[string]*StepWrapper),
Description: description,
}
}

Expand All @@ -56,17 +60,19 @@ func (j *Job) AddScenario(steps ...StepWrapper) {
}

func (j *Job) AddStep(step Step, opts *StepOptions) {
j.Steps = append(j.Steps, &StepWrapper{
stepw := &StepWrapper{
Step: step,
Opts: opts,
})
}
j.Steps = append(j.Steps, stepw)
}

func (j *Job) Run() error {
if j.Description == "" {
return ErrEmptyDescription
}

// validate all steps in the job, making sure parameters are set/validated etc.
err := j.Validate()
if err != nil {
return err // nolint:wrapcheck // don't wrap error, wouldn't provide any more context than the error itself
Expand Down Expand Up @@ -100,11 +106,58 @@ func (j *Job) Run() error {
}

func (j *Job) Validate() error {
// ensure that there are no background steps left after running

for _, wrapper := range j.Steps {
err := j.validateStep(wrapper)
if err != nil {
return err
}

}

err := j.validateBackgroundSteps()
if err != nil {
return err
}

return nil
}

func (j *Job) validateBackgroundSteps() error {
stoppedBackgroundSteps := make(map[string]bool)

for _, stepw := range j.Steps {
switch s := stepw.Step.(type) {
case *Stop:
if j.BackgroundSteps[s.BackgroundID] == nil {
return fmt.Errorf("cannot stop step %s, as it won't be started by this time; %w", s.BackgroundID, ErrCannotStopStep)
}
if stopped := stoppedBackgroundSteps[s.BackgroundID]; stopped {
return fmt.Errorf("cannot stop step %s, as it has already been stopped; %w", s.BackgroundID, ErrCannotStopStep)
}

// track for later on if the stop step is called
stoppedBackgroundSteps[s.BackgroundID] = true

// set the stop step within the step
s.Step = j.BackgroundSteps[s.BackgroundID].Step

default:
if stepw.Opts.RunInBackgroundWithID != "" {
if _, exists := j.BackgroundSteps[stepw.Opts.RunInBackgroundWithID]; exists {
log.Fatalf("step with id %s already exists", stepw.Opts.RunInBackgroundWithID)
}
j.BackgroundSteps[stepw.Opts.RunInBackgroundWithID] = stepw
stoppedBackgroundSteps[stepw.Opts.RunInBackgroundWithID] = false
}
}
}

for stepName, stopped := range stoppedBackgroundSteps {
if !stopped {
return fmt.Errorf("step %s was not stopped; %w", stepName, ErrOrphanSteps)
}
}

return nil
Expand All @@ -119,41 +172,51 @@ func (j *Job) validateStep(stepw *StepWrapper) error {
stepw.Opts = &DefaultOpts
}

for i, f := range reflect.VisibleFields(val.Type()) {
switch stepw.Step.(type) {
case *Stop:
// don't validate stop steps
return nil

// skip saving unexported fields
if !f.IsExported() {
continue
}
case *Sleep:
// don't validate sleep steps
return nil

default:
for i, f := range reflect.VisibleFields(val.Type()) {

k := reflect.Indirect(val.Field(i)).Kind()
// skip saving unexported fields
if !f.IsExported() {
continue
}

if k == reflect.String {
parameter := val.Type().Field(i).Name
value := val.Field(i).Interface().(string)
storedValue := j.Values.Get(parameter)
k := reflect.Indirect(val.Field(i)).Kind()

if k == reflect.String {
parameter := val.Type().Field(i).Name
value := val.Field(i).Interface().(string)
storedValue := j.Values.Get(parameter)

if storedValue == "" {
if value != "" {

if storedValue == "" {
if value != "" {
if stepw.Opts.SaveParametersToJob {
fmt.Printf("%s setting parameter %s in job context to %s\n", stepName, parameter, value)
j.Values.Set(parameter, value)

} else {
return fmt.Errorf("missing parameter %s for step %s: %w", parameter, stepName, ErrMissingParameter)
}
continue
}
return fmt.Errorf("missing parameter %s for step %s: %w", parameter, stepName, ErrMissingParameter)

}
if value != "" {
return fmt.Errorf("parameter %s for step %s is already set from previous step: %w", parameter, stepName, ErrParameterAlreadySet)
}

if value != "" {
return fmt.Errorf("parameter %s for step %s is already set from previous step: %w", parameter, stepName, ErrParameterAlreadySet)
// don't use log format since this is technically preexecution and easier to read
fmt.Println(stepName, "using previously stored value for parameter", parameter, "set as", j.Values.Get(parameter))
val.Field(i).SetString(storedValue)
}

// don't use log format since this is technically preexecution and easier to read
fmt.Println(stepName, "using previously stored value for parameter", parameter, "set as", j.Values.Get(parameter))
val.Field(i).SetString(storedValue)
}
}

return nil
}
12 changes: 9 additions & 3 deletions test/e2e/framework/types/step.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package types

var DefaultOpts = StepOptions{
ExpectError: false,
SaveParametersToJob: true,
ExpectError: false,
SkipSavingParamatersToJob: false,
}

type Step interface {
Prevalidate() error
Run() error
Postvalidate() error
Stop() error
}

type StepOptions struct {
Expand All @@ -18,5 +19,10 @@ type StepOptions struct {
// a step, but you don't want to save the parameters
// ex: Sleep for 15 seconds, then Sleep for 10 seconds,
// you don't want to save the parameters
SaveParametersToJob bool
SkipSavingParamatersToJob bool

// Will save this step to the job's steps
// and then later on when Stop is called with job name,
// it will call Stop() on the step
RunInBackgroundWithID string
}
4 changes: 4 additions & 0 deletions test/e2e/framework/types/step_sleep.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ func (c *Sleep) Run() error {
return nil
}

func (c *Sleep) Stop() error {
return nil
}

func (c *Sleep) Prevalidate() error {
return nil
}
Expand Down
32 changes: 32 additions & 0 deletions test/e2e/framework/types/step_stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package types

import (
"fmt"
"reflect"
)

type Stop struct {
BackgroundID string
Step Step
}

func (c *Stop) Run() error {
err := c.Step.Stop()
if err != nil {
stepName := reflect.TypeOf(c.Step).Elem().Name()
return fmt.Errorf("failed to stop step: %s with err %w", stepName, err)
}
return nil
}

func (c *Stop) Stop() error {
return nil
}

func (c *Stop) Prevalidate() error {
return nil
}

func (c *Stop) Postvalidate() error {
return nil
}
Loading