Skip to content

Commit

Permalink
Merge pull request #122 from linuxboot/feature/expand_variables_to_pa…
Browse files Browse the repository at this point in the history
…rameters

Parameter.Expand consumes optional StepsVariables argument
  • Loading branch information
mimir-d authored Aug 8, 2022
2 parents 768d780 + 642b26f commit 29d7b6d
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 41 deletions.
41 changes: 41 additions & 0 deletions pkg/test/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,47 @@ func getFuncMap() map[string]interface{} {
return mapCopy
}

func parseLabelDotVariable(labelDotVariable string) (string, string, error) {
parts := strings.SplitN(labelDotVariable, ".", 2)
if len(parts) < 2 {
return "", "", fmt.Errorf("not 'label.variable' format of '%s'", labelDotVariable)
}
stepLabel, varname := parts[0], parts[1]
if err := CheckIdentifier(stepLabel); err != nil {
return "", "", fmt.Errorf("invalid step label: '%s': %w", stepLabel, err)
}
if err := CheckIdentifier(varname); err != nil {
return "", "", fmt.Errorf("invalid variable name: '%s': %w", varname, err)
}
return stepLabel, varname, nil
}

func registerStepVariableAccessor(fm map[string]interface{}, tgtID string, vars StepsVariablesReader) {
fm["StringVar"] = func(labelDotVariable string) (string, error) {
stepLabel, varName, err := parseLabelDotVariable(labelDotVariable)
if err != nil {
return "", err
}

var s string
if err := vars.Get(tgtID, stepLabel, varName, &s); err != nil {
return "", err
}
return s, nil
}
fm["IntVar"] = func(labelDotVariable string) (int, error) {
stepLabel, varName, err := parseLabelDotVariable(labelDotVariable)
if err != nil {
return 0, err
}
var i int
if err := vars.Get(tgtID, stepLabel, varName, &i); err != nil {
return 0, err
}
return i, nil
}
}

// RegisterFunction registers a template function suitable for text/template.
// It can be either a func(string) string or a func(string) (string, error),
// hence it's passed as an empty interface.
Expand Down
9 changes: 5 additions & 4 deletions pkg/test/param_expander.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ import (
)

type ParamExpander struct {
t *target.Target
t *target.Target
vars StepsVariablesReader
}

func NewParamExpander(target *target.Target) *ParamExpander {
return &ParamExpander{target}
func NewParamExpander(target *target.Target, vars StepsVariablesReader) *ParamExpander {
return &ParamExpander{t: target, vars: vars}
}

func (pe *ParamExpander) Expand(value string) (string, error) {
p := NewParam(value)
return p.Expand(pe.t)
return p.Expand(pe.t, pe.vars)
}

func (pe *ParamExpander) ExpandObject(obj interface{}, out interface{}) error {
Expand Down
8 changes: 6 additions & 2 deletions pkg/test/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,16 @@ func (p Param) JSON() json.RawMessage {

// Expand evaluates the raw expression and applies the necessary manipulation,
// if any.
func (p *Param) Expand(target *target.Target) (string, error) {
func (p *Param) Expand(target *target.Target, vars StepsVariablesReader) (string, error) {
if p == nil {
return "", errors.New("parameter cannot be nil")
}
funcs := getFuncMap()
if vars != nil {
registerStepVariableAccessor(funcs, target.ID, vars)
}
// use Go text/template from here
tmpl, err := template.New("").Funcs(getFuncMap()).Parse(p.String())
tmpl, err := template.New("").Funcs(funcs).Parse(p.String())
if err != nil {
return "", fmt.Errorf("failed to parse template: %v", err)
}
Expand Down
87 changes: 85 additions & 2 deletions pkg/test/parameter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
package test

import (
"encoding/json"
"errors"
"fmt"
"strings"
"testing"

Expand All @@ -24,7 +26,7 @@ func TestParameterExpand(t *testing.T) {
}
for _, x := range validExprs {
p := NewParam(x[0])
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]})
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]}, nil)
require.NoError(t, err, x[0])
require.Equal(t, x[3], res, x[0])
}
Expand All @@ -46,10 +48,91 @@ func TestParameterExpandUserFunctions(t *testing.T) {
}
for _, x := range validExprs {
p := NewParam(x[0])
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]})
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]}, nil)
require.NoError(t, err, x[0])
require.Equal(t, x[3], res, x[0])
}
require.NoError(t, UnregisterFunction("CustomFunc"))
require.Error(t, UnregisterFunction("NoSuchFunction"))
}

func TestStepVariablesExpand(t *testing.T) {
p := NewParam("{{ StringVar \"step1.string_var\" }}: {{ IntVar \"step1.int_var\" }}")
svm := newStepsVariablesMock()

tgt := target.Target{ID: "1"}
require.NoError(t, svm.add(tgt.ID, "step1", "string_var", "Hello"))
require.NoError(t, svm.add(tgt.ID, "step1", "int_var", 42))

res, err := p.Expand(&tgt, svm)
require.NoError(t, err)
require.Equal(t, "Hello: 42", res)
}

func TestInvalidStepVariablesExpand(t *testing.T) {
t.Run("no_dot", func(t *testing.T) {
p := NewParam("{{ StringVar \"step1string_var\" }}")
_, err := p.Expand(&target.Target{ID: "1"}, newStepsVariablesMock())
require.Error(t, err)
})

t.Run("just_variable_name", func(t *testing.T) {
p := NewParam("{{ StringVar \"string_var\" }}")

svm := newStepsVariablesMock()
tgt := target.Target{ID: "1"}
require.NoError(t, svm.add(tgt.ID, "step1", "string_var", "Hello"))

_, err := p.Expand(&tgt, svm)
require.Error(t, err)
})

t.Run("invalid_variable_name", func(t *testing.T) {
p := NewParam("{{ StringVar \"step1.22string_var\" }}")

svm := newStepsVariablesMock()
tgt := target.Target{ID: "1"}
// we can add invalid values to our mock
require.NoError(t, svm.add(tgt.ID, "step1", "22string_var", "Hello"))

_, err := p.Expand(&tgt, svm)
require.Error(t, err)
})
}

type stepsVariablesMock struct {
variables map[string]map[string]json.RawMessage
}

func newStepsVariablesMock() *stepsVariablesMock {
return &stepsVariablesMock{
variables: make(map[string]map[string]json.RawMessage),
}
}

func (svm *stepsVariablesMock) add(tgtID string, label, name string, in interface{}) error {
b, err := json.Marshal(in)
if err != nil {
return err
}

targetVars := svm.variables[tgtID]
if targetVars == nil {
targetVars = make(map[string]json.RawMessage)
svm.variables[tgtID] = targetVars
}
targetVars[label+"."+name] = b
return nil
}

func (svm *stepsVariablesMock) Get(tgtID string, stepLabel, name string, out interface{}) error {
targetVars := svm.variables[tgtID]
if targetVars == nil {
return fmt.Errorf("no target: %s", tgtID)
}
b, found := targetVars[stepLabel+"."+name]
if !found {
return fmt.Errorf("no variable %s %s", stepLabel, name)
}
return json.Unmarshal(b, out)
}
12 changes: 8 additions & 4 deletions pkg/test/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ type TestStepChannels struct {
Out chan<- TestStepResult
}

// StepsVariables represents a read/write access for step variables
// StepsVariablesReader represents a read access for step variables
// Example:
// var sv StepsVariables
// intVar := 42
Expand All @@ -118,12 +118,16 @@ type TestStepChannels struct {
// var recvIntVar int
// sv.Get(("dummy-target-id", "varname", &recvIntVar)
// assert recvIntVar == 42
type StepsVariablesReader interface {
// Get obtains existing variable that was added in one of the previous steps
Get(tgtID string, stepLabel, name string, out interface{}) error
}

// StepsVariables represents a read/write access for step variables
type StepsVariables interface {
StepsVariablesReader
// Add adds a new or replaces existing variable associated with current test step and target
Add(tgtID string, name string, in interface{}) error

// Get obtains existing variable by a mappedName which should be specified in variables mapping
Get(tgtID string, stepLabel, name string, out interface{}) error
}

// TestStep is the interface that all steps need to implement to be executed
Expand Down
4 changes: 2 additions & 2 deletions plugins/teststeps/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ func (ts *Cmd) Run(
// expand args
var args []string
for _, arg := range ts.args {
expArg, err := arg.Expand(target)
expArg, err := arg.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("failed to expand argument '%s': %v", arg, err)
}
args = append(args, expArg)
}
cmd := exec.CommandContext(ctx, ts.executable, args...)
pwd, err := ts.dir.Expand(target)
pwd, err := ts.dir.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("failed to expand argument dir '%s': %v", ts.dir, err)
}
Expand Down
12 changes: 6 additions & 6 deletions plugins/teststeps/cpucmd/cpucmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (ts *CPUCmd) Run(
// return fmt.Errorf("cannot expand user parameter: %v", err)
// }

host, err := ts.Host.Expand(target)
host, err := ts.Host.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("cannot expand host parameter: %v", err)
}
Expand All @@ -120,7 +120,7 @@ func (ts *CPUCmd) Run(
return fmt.Errorf("host value is empty")
}

portStr, err := ts.Port.Expand(target)
portStr, err := ts.Port.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("cannot expand port parameter: %v", err)
}
Expand All @@ -130,7 +130,7 @@ func (ts *CPUCmd) Run(
if err != nil {
return fmt.Errorf("Can not expand %q:%q to a cpu port", host, portStr)
}
timeoutStr, err := ts.Timeout.Expand(target)
timeoutStr, err := ts.Timeout.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("cannot expand timeout parameter %s: %v", timeoutStr, err)
}
Expand All @@ -142,20 +142,20 @@ func (ts *CPUCmd) Run(

timeTimeout := time.Now().Add(timeout)

privKeyFile, err := ts.PrivateKeyFile.Expand(target)
privKeyFile, err := ts.PrivateKeyFile.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("cannot expand private key file parameter: %v", err)
}

executable, err := ts.Executable.Expand(target)
executable, err := ts.Executable.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("cannot expand executable parameter: %v", err)
}

// apply functions to the command args, if any
args := []string{executable}
for _, arg := range ts.Args {
earg, err := arg.Expand(target)
earg, err := arg.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("cannot expand command argument '%s': %v", arg, err)
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/teststeps/echo/echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (e Step) Run(
if !ok {
return nil, nil
}
output, err := params.GetOne("text").Expand(target)
output, err := params.GetOne("text").Expand(target, stepsVars)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/teststeps/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (ts *TestStep) Run(
return nil, err
}

tr := NewTargetRunner(ts, ev)
tr := NewTargetRunner(ts, ev, stepsVars)
return teststeps.ForEachTarget(Name, ctx, ch, tr.Run)
}

Expand Down
9 changes: 5 additions & 4 deletions plugins/teststeps/exec/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import (
type outcome error

type TargetRunner struct {
ts *TestStep
ev testevent.Emitter
ts *TestStep
ev testevent.Emitter
stepsVars test.StepsVariablesReader
}

func NewTargetRunner(ts *TestStep, ev testevent.Emitter) *TargetRunner {
func NewTargetRunner(ts *TestStep, ev testevent.Emitter, stepsVars test.StepsVariablesReader) *TargetRunner {
return &TargetRunner{
ts: ts,
ev: ev,
Expand Down Expand Up @@ -127,7 +128,7 @@ func (r *TargetRunner) Run(ctx xcontext.Context, target *target.Target) error {
defer cancel()
}

pe := test.NewParamExpander(target)
pe := test.NewParamExpander(target, r.stepsVars)

var params stepParams
if err := pe.ExpandObject(r.ts.stepParams, &params); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions plugins/teststeps/gathercmd/gathercmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,9 @@ func (ts *GatherCmd) setParams(params test.TestStepParameters) error {

ts.args = []string{}
args := params.Get("args")
// expand args in case they use functions, but they shouldnt be target aware
// expand args in case they use functions, but they shouldn't be target aware
for _, arg := range args {
expanded, err := arg.Expand(&target.Target{})
expanded, err := arg.Expand(&target.Target{}, nil)
if err != nil {
return fmt.Errorf("failed to expand argument: %s -> %v", arg, err)
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/teststeps/s3fileupload/s3fileupload.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ func (ts *FileUpload) Run(
}
f := func(ctx xcontext.Context, target *target.Target) error {
// expand args
path, err := ts.localPath.Expand(target)
path, err := ts.localPath.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("failed to expand argument '%s': %v", ts.localPath, err)
}
filename, err := ts.fileName.Expand(target)
filename, err := ts.fileName.Expand(target, stepsVars)
if err != nil {
return fmt.Errorf("failed to expand argument dir '%s': %v", ts.fileName, err)
}
Expand Down
Loading

0 comments on commit 29d7b6d

Please sign in to comment.