...

Source file src/edge-infra.dev/test/f2/feature.go

Documentation: edge-infra.dev/test/f2

     1  package f2
     2  
     3  import (
     4  	"strings"
     5  )
     6  
     7  type Feature interface {
     8  	Name() string
     9  	Labels() map[string]string
    10  	Steps() []Step
    11  }
    12  
    13  type feature struct {
    14  	name   string
    15  	labels map[string]string
    16  	steps  []Step
    17  }
    18  
    19  type Step struct {
    20  	Name  string
    21  	Fn    StepFn
    22  	Phase Phase
    23  }
    24  
    25  func (f *feature) Name() string {
    26  	return f.name
    27  }
    28  
    29  func (f *feature) Labels() map[string]string {
    30  	return f.labels
    31  }
    32  
    33  func (f *feature) Steps() []Step {
    34  	return f.steps
    35  }
    36  
    37  // FeatureBuilder allows concise test definition with the builder pattern without
    38  // increasing the surface area of the [Feature] interface
    39  type FeatureBuilder struct {
    40  	f *feature
    41  }
    42  
    43  func NewFeature(name string) *FeatureBuilder {
    44  	return &FeatureBuilder{f: &feature{
    45  		name:   name,
    46  		steps:  make([]Step, 0),
    47  		labels: make(map[string]string),
    48  	}}
    49  }
    50  
    51  func (b *FeatureBuilder) Test(name string, t StepFn) *FeatureBuilder {
    52  	b.f.steps = append(b.f.steps, Step{name, t, phaseTest})
    53  	return b
    54  }
    55  
    56  func (b *FeatureBuilder) Setup(name string, fn StepFn) *FeatureBuilder {
    57  	b.f.steps = append(b.f.steps, Step{name, fn, phaseBeforeFeature})
    58  	return b
    59  }
    60  
    61  func (b *FeatureBuilder) Teardown(name string, fn StepFn) *FeatureBuilder {
    62  	b.f.steps = append(b.f.steps, Step{name, fn, phaseAfterFeature})
    63  	return b
    64  }
    65  
    66  func (b *FeatureBuilder) WithStep(name string, p Phase, fn StepFn) *FeatureBuilder {
    67  	b.f.steps = append(b.f.steps, Step{name, fn, p})
    68  	return b
    69  }
    70  
    71  func (b *FeatureBuilder) Feature() Feature {
    72  	return b.f
    73  }
    74  
    75  func getStepsInPhase(f Feature, Phase Phase) []Step {
    76  	if f.Steps() == nil {
    77  		return nil
    78  	}
    79  
    80  	result := make([]Step, 0)
    81  	for _, s := range f.Steps() {
    82  		if s.Phase == Phase {
    83  			result = append(result, s)
    84  		}
    85  	}
    86  
    87  	return result
    88  }
    89  
    90  // deepCopyFeature just copies the values from the Feature but creates a deep
    91  // copy to avoid mutation when we just want an informational copy.
    92  func deepCopyFeature(f Feature) Feature {
    93  	fcopy := NewFeature(f.Name())
    94  	for k, v := range f.Labels() {
    95  		fcopy = fcopy.WithLabel(k, v)
    96  	}
    97  	for _, step := range f.Steps() {
    98  		fcopy = fcopy.WithStep(step.Name, step.Phase, nil)
    99  	}
   100  	return fcopy.Feature()
   101  }
   102  
   103  // you can find a list of labels here:
   104  // https://docs.edge-infra.dev/dev/contributing/testing/conventions/
   105  
   106  // WithLabel adds a label with one or more values to the framework to describe the block of tests
   107  // the framework instance will be used for. Returns a pointer to itself so it
   108  // can be chained together to add multiple labels:
   109  //
   110  // fin := f2.NewFeature("hello feature").WithLabel("bar", "boo", "baz", "boz").Flaky().Feature()
   111  func (b *FeatureBuilder) WithLabel(key string, values ...string) *FeatureBuilder {
   112  	if b.f.labels == nil {
   113  		b.f.labels = map[string]string{}
   114  	}
   115  	b.f.labels[key] = commaSepList(values...)
   116  
   117  	return b
   118  }
   119  
   120  // WithID is a label used to associate Tests with specific user-facing features.
   121  // Should be identified by unique identifier (GitHub issue number or JIRA ticket)
   122  // Can have multiple values, separated by comma.
   123  func (b *FeatureBuilder) WithID(f ...string) *FeatureBuilder {
   124  	return b.WithLabel("id", f...)
   125  }
   126  
   127  // Priviledged is a label that defines tests that require escalted privileges in various contexts.
   128  // Examples: FolderAdmin, BillingAdmin, ProjectAdmin describe GCP privileges that tests may need.
   129  func (b *FeatureBuilder) Priviledged(p ...string) *FeatureBuilder {
   130  	return b.WithLabel("priviledged", p...)
   131  }
   132  
   133  // Component is syntactic sugar for adding a component label to the test block.
   134  // Returns a pointer to itself for function chaining.
   135  func (b *FeatureBuilder) Component(c ...string) *FeatureBuilder {
   136  	return b.WithLabel("component", c...)
   137  }
   138  
   139  // Slow is a label that should be applied to all test suites which take longer
   140  // than 5 minutes to execute.
   141  func (b *FeatureBuilder) Slow() *FeatureBuilder {
   142  	return b.WithLabel("slow", "true")
   143  }
   144  
   145  // Disruptive is a label that should be applied to all test suites that are
   146  // likely to disrupt other test cases being ran at the same time.
   147  func (b *FeatureBuilder) Disruptive() *FeatureBuilder {
   148  	return b.WithLabel("disruptive", "true")
   149  }
   150  
   151  // Serial is a label that should be applied to all test suites that can't be
   152  // run in parallel.
   153  func (b *FeatureBuilder) Serial() *FeatureBuilder {
   154  	return b.WithLabel("serial", "true")
   155  }
   156  
   157  // Flaky is a label that should be applied to tests with inconsistent results.
   158  func (b *FeatureBuilder) Flaky() *FeatureBuilder {
   159  	return b.WithLabel("flaky", "true")
   160  }
   161  
   162  // commaSepList takes in a list of strings and returns a comma separated string
   163  //
   164  // ex: [a b c d e] --> a,b,c,d,e
   165  func commaSepList(list ...string) string {
   166  	var values strings.Builder
   167  
   168  	for i, x := range list {
   169  		if i == 0 {
   170  			values.WriteString(x)
   171  		} else {
   172  			values.WriteString("," + x)
   173  		}
   174  	}
   175  
   176  	return values.String()
   177  }
   178  

View as plain text