Skip to content

Commit 29d7b6d

Browse files
authored
Merge pull request #122 from linuxboot/feature/expand_variables_to_parameters
Parameter.Expand consumes optional StepsVariables argument
2 parents 768d780 + 642b26f commit 29d7b6d

File tree

14 files changed

+175
-41
lines changed

14 files changed

+175
-41
lines changed

pkg/test/functions.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,47 @@ func getFuncMap() map[string]interface{} {
3232
return mapCopy
3333
}
3434

35+
func parseLabelDotVariable(labelDotVariable string) (string, string, error) {
36+
parts := strings.SplitN(labelDotVariable, ".", 2)
37+
if len(parts) < 2 {
38+
return "", "", fmt.Errorf("not 'label.variable' format of '%s'", labelDotVariable)
39+
}
40+
stepLabel, varname := parts[0], parts[1]
41+
if err := CheckIdentifier(stepLabel); err != nil {
42+
return "", "", fmt.Errorf("invalid step label: '%s': %w", stepLabel, err)
43+
}
44+
if err := CheckIdentifier(varname); err != nil {
45+
return "", "", fmt.Errorf("invalid variable name: '%s': %w", varname, err)
46+
}
47+
return stepLabel, varname, nil
48+
}
49+
50+
func registerStepVariableAccessor(fm map[string]interface{}, tgtID string, vars StepsVariablesReader) {
51+
fm["StringVar"] = func(labelDotVariable string) (string, error) {
52+
stepLabel, varName, err := parseLabelDotVariable(labelDotVariable)
53+
if err != nil {
54+
return "", err
55+
}
56+
57+
var s string
58+
if err := vars.Get(tgtID, stepLabel, varName, &s); err != nil {
59+
return "", err
60+
}
61+
return s, nil
62+
}
63+
fm["IntVar"] = func(labelDotVariable string) (int, error) {
64+
stepLabel, varName, err := parseLabelDotVariable(labelDotVariable)
65+
if err != nil {
66+
return 0, err
67+
}
68+
var i int
69+
if err := vars.Get(tgtID, stepLabel, varName, &i); err != nil {
70+
return 0, err
71+
}
72+
return i, nil
73+
}
74+
}
75+
3576
// RegisterFunction registers a template function suitable for text/template.
3677
// It can be either a func(string) string or a func(string) (string, error),
3778
// hence it's passed as an empty interface.

pkg/test/param_expander.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ import (
1313
)
1414

1515
type ParamExpander struct {
16-
t *target.Target
16+
t *target.Target
17+
vars StepsVariablesReader
1718
}
1819

19-
func NewParamExpander(target *target.Target) *ParamExpander {
20-
return &ParamExpander{target}
20+
func NewParamExpander(target *target.Target, vars StepsVariablesReader) *ParamExpander {
21+
return &ParamExpander{t: target, vars: vars}
2122
}
2223

2324
func (pe *ParamExpander) Expand(value string) (string, error) {
2425
p := NewParam(value)
25-
return p.Expand(pe.t)
26+
return p.Expand(pe.t, pe.vars)
2627
}
2728

2829
func (pe *ParamExpander) ExpandObject(obj interface{}, out interface{}) error {

pkg/test/parameter.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@ func (p Param) JSON() json.RawMessage {
5757

5858
// Expand evaluates the raw expression and applies the necessary manipulation,
5959
// if any.
60-
func (p *Param) Expand(target *target.Target) (string, error) {
60+
func (p *Param) Expand(target *target.Target, vars StepsVariablesReader) (string, error) {
6161
if p == nil {
6262
return "", errors.New("parameter cannot be nil")
6363
}
64+
funcs := getFuncMap()
65+
if vars != nil {
66+
registerStepVariableAccessor(funcs, target.ID, vars)
67+
}
6468
// use Go text/template from here
65-
tmpl, err := template.New("").Funcs(getFuncMap()).Parse(p.String())
69+
tmpl, err := template.New("").Funcs(funcs).Parse(p.String())
6670
if err != nil {
6771
return "", fmt.Errorf("failed to parse template: %v", err)
6872
}

pkg/test/parameter_test.go

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
package test
77

88
import (
9+
"encoding/json"
910
"errors"
11+
"fmt"
1012
"strings"
1113
"testing"
1214

@@ -24,7 +26,7 @@ func TestParameterExpand(t *testing.T) {
2426
}
2527
for _, x := range validExprs {
2628
p := NewParam(x[0])
27-
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]})
29+
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]}, nil)
2830
require.NoError(t, err, x[0])
2931
require.Equal(t, x[3], res, x[0])
3032
}
@@ -46,10 +48,91 @@ func TestParameterExpandUserFunctions(t *testing.T) {
4648
}
4749
for _, x := range validExprs {
4850
p := NewParam(x[0])
49-
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]})
51+
res, err := p.Expand(&target.Target{FQDN: x[1], ID: x[2]}, nil)
5052
require.NoError(t, err, x[0])
5153
require.Equal(t, x[3], res, x[0])
5254
}
5355
require.NoError(t, UnregisterFunction("CustomFunc"))
5456
require.Error(t, UnregisterFunction("NoSuchFunction"))
5557
}
58+
59+
func TestStepVariablesExpand(t *testing.T) {
60+
p := NewParam("{{ StringVar \"step1.string_var\" }}: {{ IntVar \"step1.int_var\" }}")
61+
svm := newStepsVariablesMock()
62+
63+
tgt := target.Target{ID: "1"}
64+
require.NoError(t, svm.add(tgt.ID, "step1", "string_var", "Hello"))
65+
require.NoError(t, svm.add(tgt.ID, "step1", "int_var", 42))
66+
67+
res, err := p.Expand(&tgt, svm)
68+
require.NoError(t, err)
69+
require.Equal(t, "Hello: 42", res)
70+
}
71+
72+
func TestInvalidStepVariablesExpand(t *testing.T) {
73+
t.Run("no_dot", func(t *testing.T) {
74+
p := NewParam("{{ StringVar \"step1string_var\" }}")
75+
_, err := p.Expand(&target.Target{ID: "1"}, newStepsVariablesMock())
76+
require.Error(t, err)
77+
})
78+
79+
t.Run("just_variable_name", func(t *testing.T) {
80+
p := NewParam("{{ StringVar \"string_var\" }}")
81+
82+
svm := newStepsVariablesMock()
83+
tgt := target.Target{ID: "1"}
84+
require.NoError(t, svm.add(tgt.ID, "step1", "string_var", "Hello"))
85+
86+
_, err := p.Expand(&tgt, svm)
87+
require.Error(t, err)
88+
})
89+
90+
t.Run("invalid_variable_name", func(t *testing.T) {
91+
p := NewParam("{{ StringVar \"step1.22string_var\" }}")
92+
93+
svm := newStepsVariablesMock()
94+
tgt := target.Target{ID: "1"}
95+
// we can add invalid values to our mock
96+
require.NoError(t, svm.add(tgt.ID, "step1", "22string_var", "Hello"))
97+
98+
_, err := p.Expand(&tgt, svm)
99+
require.Error(t, err)
100+
})
101+
}
102+
103+
type stepsVariablesMock struct {
104+
variables map[string]map[string]json.RawMessage
105+
}
106+
107+
func newStepsVariablesMock() *stepsVariablesMock {
108+
return &stepsVariablesMock{
109+
variables: make(map[string]map[string]json.RawMessage),
110+
}
111+
}
112+
113+
func (svm *stepsVariablesMock) add(tgtID string, label, name string, in interface{}) error {
114+
b, err := json.Marshal(in)
115+
if err != nil {
116+
return err
117+
}
118+
119+
targetVars := svm.variables[tgtID]
120+
if targetVars == nil {
121+
targetVars = make(map[string]json.RawMessage)
122+
svm.variables[tgtID] = targetVars
123+
}
124+
targetVars[label+"."+name] = b
125+
return nil
126+
}
127+
128+
func (svm *stepsVariablesMock) Get(tgtID string, stepLabel, name string, out interface{}) error {
129+
targetVars := svm.variables[tgtID]
130+
if targetVars == nil {
131+
return fmt.Errorf("no target: %s", tgtID)
132+
}
133+
b, found := targetVars[stepLabel+"."+name]
134+
if !found {
135+
return fmt.Errorf("no variable %s %s", stepLabel, name)
136+
}
137+
return json.Unmarshal(b, out)
138+
}

pkg/test/step.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ type TestStepChannels struct {
109109
Out chan<- TestStepResult
110110
}
111111

112-
// StepsVariables represents a read/write access for step variables
112+
// StepsVariablesReader represents a read access for step variables
113113
// Example:
114114
// var sv StepsVariables
115115
// intVar := 42
@@ -118,12 +118,16 @@ type TestStepChannels struct {
118118
// var recvIntVar int
119119
// sv.Get(("dummy-target-id", "varname", &recvIntVar)
120120
// assert recvIntVar == 42
121+
type StepsVariablesReader interface {
122+
// Get obtains existing variable that was added in one of the previous steps
123+
Get(tgtID string, stepLabel, name string, out interface{}) error
124+
}
125+
126+
// StepsVariables represents a read/write access for step variables
121127
type StepsVariables interface {
128+
StepsVariablesReader
122129
// Add adds a new or replaces existing variable associated with current test step and target
123130
Add(tgtID string, name string, in interface{}) error
124-
125-
// Get obtains existing variable by a mappedName which should be specified in variables mapping
126-
Get(tgtID string, stepLabel, name string, out interface{}) error
127131
}
128132

129133
// TestStep is the interface that all steps need to implement to be executed

plugins/teststeps/cmd/cmd.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ func (ts *Cmd) Run(
110110
// expand args
111111
var args []string
112112
for _, arg := range ts.args {
113-
expArg, err := arg.Expand(target)
113+
expArg, err := arg.Expand(target, stepsVars)
114114
if err != nil {
115115
return fmt.Errorf("failed to expand argument '%s': %v", arg, err)
116116
}
117117
args = append(args, expArg)
118118
}
119119
cmd := exec.CommandContext(ctx, ts.executable, args...)
120-
pwd, err := ts.dir.Expand(target)
120+
pwd, err := ts.dir.Expand(target, stepsVars)
121121
if err != nil {
122122
return fmt.Errorf("failed to expand argument dir '%s': %v", ts.dir, err)
123123
}

plugins/teststeps/cpucmd/cpucmd.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (ts *CPUCmd) Run(
9999
// return fmt.Errorf("cannot expand user parameter: %v", err)
100100
// }
101101

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

123-
portStr, err := ts.Port.Expand(target)
123+
portStr, err := ts.Port.Expand(target, stepsVars)
124124
if err != nil {
125125
return fmt.Errorf("cannot expand port parameter: %v", err)
126126
}
@@ -130,7 +130,7 @@ func (ts *CPUCmd) Run(
130130
if err != nil {
131131
return fmt.Errorf("Can not expand %q:%q to a cpu port", host, portStr)
132132
}
133-
timeoutStr, err := ts.Timeout.Expand(target)
133+
timeoutStr, err := ts.Timeout.Expand(target, stepsVars)
134134
if err != nil {
135135
return fmt.Errorf("cannot expand timeout parameter %s: %v", timeoutStr, err)
136136
}
@@ -142,20 +142,20 @@ func (ts *CPUCmd) Run(
142142

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

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

150-
executable, err := ts.Executable.Expand(target)
150+
executable, err := ts.Executable.Expand(target, stepsVars)
151151
if err != nil {
152152
return fmt.Errorf("cannot expand executable parameter: %v", err)
153153
}
154154

155155
// apply functions to the command args, if any
156156
args := []string{executable}
157157
for _, arg := range ts.Args {
158-
earg, err := arg.Expand(target)
158+
earg, err := arg.Expand(target, stepsVars)
159159
if err != nil {
160160
return fmt.Errorf("cannot expand command argument '%s': %v", arg, err)
161161
}

plugins/teststeps/echo/echo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func (e Step) Run(
6565
if !ok {
6666
return nil, nil
6767
}
68-
output, err := params.GetOne("text").Expand(target)
68+
output, err := params.GetOne("text").Expand(target, stepsVars)
6969
if err != nil {
7070
return nil, err
7171
}

plugins/teststeps/exec/exec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (ts *TestStep) Run(
6464
return nil, err
6565
}
6666

67-
tr := NewTargetRunner(ts, ev)
67+
tr := NewTargetRunner(ts, ev, stepsVars)
6868
return teststeps.ForEachTarget(Name, ctx, ch, tr.Run)
6969
}
7070

plugins/teststeps/exec/runner.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import (
2121
type outcome error
2222

2323
type TargetRunner struct {
24-
ts *TestStep
25-
ev testevent.Emitter
24+
ts *TestStep
25+
ev testevent.Emitter
26+
stepsVars test.StepsVariablesReader
2627
}
2728

28-
func NewTargetRunner(ts *TestStep, ev testevent.Emitter) *TargetRunner {
29+
func NewTargetRunner(ts *TestStep, ev testevent.Emitter, stepsVars test.StepsVariablesReader) *TargetRunner {
2930
return &TargetRunner{
3031
ts: ts,
3132
ev: ev,
@@ -127,7 +128,7 @@ func (r *TargetRunner) Run(ctx xcontext.Context, target *target.Target) error {
127128
defer cancel()
128129
}
129130

130-
pe := test.NewParamExpander(target)
131+
pe := test.NewParamExpander(target, r.stepsVars)
131132

132133
var params stepParams
133134
if err := pe.ExpandObject(r.ts.stepParams, &params); err != nil {

0 commit comments

Comments
 (0)