Skip to content

Commit e320523

Browse files
committed
Add variables tests step
Add e2e TestVariables that checks step variables Signed-off-by: Ilya <rihter007@inbox.ru>
1 parent 5b8e421 commit e320523

File tree

6 files changed

+392
-41
lines changed

6 files changed

+392
-41
lines changed

pkg/runner/test_steps_variables.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,21 @@ func newStepVariablesAccessor(stepLabel string, tsv *testStepsVariables) *stepVa
126126
}
127127

128128
func (sva *stepVariablesAccessor) Add(tgtID string, name string, in interface{}) error {
129-
if len(sva.stepLabel) == 0 {
130-
return nil
131-
}
132-
b, err := json.Marshal(in)
133-
if err != nil {
134-
return fmt.Errorf("failed to serialize variable: %v", in)
129+
var marshalled []byte
130+
if raw, ok := in.(json.RawMessage); ok {
131+
var v interface{}
132+
if err := json.Unmarshal(raw, &v); err != nil {
133+
return fmt.Errorf("invalid input, failed to unmarshal: %v", err)
134+
}
135+
marshalled = raw
136+
} else {
137+
var err error
138+
marshalled, err = json.Marshal(in)
139+
if err != nil {
140+
return fmt.Errorf("failed to serialize variable: %v", in)
141+
}
135142
}
136-
return sva.tsv.Add(tgtID, sva.stepLabel, name, b)
143+
return sva.tsv.Add(tgtID, sva.stepLabel, name, marshalled)
137144
}
138145

139146
func (sva *stepVariablesAccessor) Get(tgtID string, stepLabel, name string, out interface{}) error {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Variables plugin
2+
3+
The *variables* plugin adds its input parameters as step plugin variables that could be later referred to by other plugins.
4+
5+
## Parameters
6+
7+
Any parameter should be a single-value parameter that will be added as a test-variable.
8+
For example:
9+
10+
{
11+
"name": "variables",
12+
"label": "variablesstep"
13+
"parameters": {
14+
"string_variable": ["Hello"],
15+
"int_variable": [123],
16+
"complex_variable": [{"hello": "world"}]
17+
}
18+
}
19+
20+
Will generate a string, int and a json-object variables respectively.
21+
22+
These parameters could be later accessed in the following manner in accordance with step variables guidance:
23+
24+
{
25+
"name": "cmd",
26+
"label": "cmdstep",
27+
"parameters": {
28+
"executable": [echo],
29+
"args": ["{{ StringVar \"variablesstep.string_variable\" }} world number {{ IntVar \"variablesstep.int_variable\" }}"],
30+
"emit_stdout": [true],
31+
"emit_stderr": [true]
32+
}
33+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package variables
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/linuxboot/contest/pkg/event"
7+
"github.com/linuxboot/contest/pkg/event/testevent"
8+
"github.com/linuxboot/contest/pkg/target"
9+
"github.com/linuxboot/contest/pkg/test"
10+
"github.com/linuxboot/contest/pkg/xcontext"
11+
"github.com/linuxboot/contest/plugins/teststeps"
12+
)
13+
14+
// Name is the name used to look this plugin up.
15+
const Name = "variables"
16+
17+
// Events defines the events that a TestStep is allowed to emit
18+
var Events []event.Name
19+
20+
// Variables creates variables that can be used by other test steps
21+
type Variables struct {
22+
}
23+
24+
// Name returns the plugin name.
25+
func (ts *Variables) Name() string {
26+
return Name
27+
}
28+
29+
// Run executes the cmd step.
30+
func (ts *Variables) Run(
31+
ctx xcontext.Context,
32+
ch test.TestStepChannels,
33+
ev testevent.Emitter,
34+
stepsVars test.StepsVariables,
35+
inputParams test.TestStepParameters,
36+
resumeState json.RawMessage,
37+
) (json.RawMessage, error) {
38+
if err := ts.ValidateParameters(ctx, inputParams); err != nil {
39+
return nil, err
40+
}
41+
return teststeps.ForEachTarget(Name, ctx, ch, func(ctx xcontext.Context, target *target.Target) error {
42+
for name, ps := range inputParams {
43+
ctx.Debugf("add variable %s, value: %s", name, ps[0])
44+
if err := stepsVars.Add(target.ID, name, ps[0].RawMessage); err != nil {
45+
return err
46+
}
47+
}
48+
return nil
49+
})
50+
}
51+
52+
// ValidateParameters validates the parameters associated to the TestStep
53+
func (ts *Variables) ValidateParameters(ctx xcontext.Context, params test.TestStepParameters) error {
54+
for name, ps := range params {
55+
if err := test.CheckIdentifier(name); err != nil {
56+
return fmt.Errorf("invalid variable name: '%s': %w", name, err)
57+
}
58+
if len(ps) != 1 {
59+
return fmt.Errorf("invalid number of parameter '%s' values: %d (expected 1)", name, len(ps))
60+
}
61+
62+
var res interface{}
63+
if err := json.Unmarshal(ps[0].RawMessage, &res); err != nil {
64+
return fmt.Errorf("invalid json '%s': %w", ps[0].RawMessage, err)
65+
}
66+
}
67+
return nil
68+
}
69+
70+
// New initializes and returns a new Variables test step.
71+
func New() test.TestStep {
72+
return &Variables{}
73+
}
74+
75+
// Load returns the name, factory and events which are needed to register the step.
76+
func Load() (string, test.TestStepFactory, []event.Name) {
77+
return Name, New, Events
78+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package variables
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"sync"
7+
"testing"
8+
9+
"github.com/linuxboot/contest/pkg/event/testevent"
10+
"github.com/linuxboot/contest/pkg/storage"
11+
"github.com/linuxboot/contest/pkg/target"
12+
"github.com/linuxboot/contest/pkg/test"
13+
"github.com/linuxboot/contest/pkg/xcontext"
14+
"github.com/linuxboot/contest/plugins/storage/memory"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestCreation(t *testing.T) {
19+
obj := New()
20+
require.NotNil(t, obj)
21+
require.Equal(t, Name, obj.Name())
22+
}
23+
24+
func TestValidateParameters(t *testing.T) {
25+
obj := New()
26+
require.NotNil(t, obj)
27+
28+
require.NoError(t, obj.ValidateParameters(xcontext.Background(), nil))
29+
require.NoError(t, obj.ValidateParameters(xcontext.Background(), test.TestStepParameters{
30+
"var1": []test.Param{
31+
{
32+
RawMessage: json.RawMessage("123"),
33+
},
34+
},
35+
}))
36+
// invalid variable name
37+
require.Error(t, obj.ValidateParameters(xcontext.Background(), test.TestStepParameters{
38+
"var var": []test.Param{
39+
{
40+
RawMessage: json.RawMessage("123"),
41+
},
42+
},
43+
}))
44+
// invalid value
45+
require.Error(t, obj.ValidateParameters(xcontext.Background(), test.TestStepParameters{
46+
"var1": []test.Param{
47+
{
48+
RawMessage: json.RawMessage("ALALALALA[}"),
49+
},
50+
},
51+
}))
52+
}
53+
54+
func TestVariablesEmission(t *testing.T) {
55+
ctx, cancel := xcontext.WithCancel(xcontext.Background())
56+
defer cancel()
57+
58+
obj := New()
59+
require.NotNil(t, obj)
60+
61+
in := make(chan *target.Target, 1)
62+
out := make(chan test.TestStepResult, 1)
63+
64+
m, err := memory.New()
65+
if err != nil {
66+
t.Fatalf("could not initialize memory storage: '%v'", err)
67+
}
68+
storageEngineVault := storage.NewSimpleEngineVault()
69+
if err := storageEngineVault.StoreEngine(m, storage.SyncEngine); err != nil {
70+
t.Fatalf("Failed to set memory storage: '%v'", err)
71+
}
72+
ev := storage.NewTestEventEmitterFetcher(storageEngineVault, testevent.Header{
73+
JobID: 12345,
74+
TestName: "variables_tests",
75+
TestStepLabel: "variables",
76+
})
77+
78+
svm := newStepsVariablesMock()
79+
80+
tgt := target.Target{ID: "id1"}
81+
in <- &tgt
82+
close(in)
83+
84+
state, err := obj.Run(ctx, test.TestStepChannels{In: in, Out: out}, ev, svm, test.TestStepParameters{
85+
"str_variable": []test.Param{
86+
{
87+
RawMessage: json.RawMessage("\"dummy\""),
88+
},
89+
},
90+
"int_variable": []test.Param{
91+
{
92+
RawMessage: json.RawMessage("123"),
93+
},
94+
},
95+
"complex_variable": []test.Param{
96+
{
97+
RawMessage: json.RawMessage("{\"name\":\"value\"}"),
98+
},
99+
},
100+
}, nil)
101+
require.NoError(t, err)
102+
require.Empty(t, state)
103+
104+
stepResult := <-out
105+
require.Equal(t, tgt, *stepResult.Target)
106+
require.NoError(t, stepResult.Err)
107+
108+
var strVar string
109+
require.NoError(t, svm.get(tgt.ID, "str_variable", &strVar))
110+
require.Equal(t, "dummy", strVar)
111+
112+
var intVar int
113+
require.NoError(t, svm.get(tgt.ID, "int_variable", &intVar))
114+
require.Equal(t, 123, intVar)
115+
116+
var complexVar dummyStruct
117+
require.NoError(t, svm.get(tgt.ID, "complex_variable", &complexVar))
118+
require.Equal(t, dummyStruct{Name: "value"}, complexVar)
119+
}
120+
121+
type dummyStruct struct {
122+
Name string `json:"name"`
123+
}
124+
125+
type stepsVariablesMock struct {
126+
mu sync.Mutex
127+
variables map[string]map[string]json.RawMessage
128+
}
129+
130+
func newStepsVariablesMock() *stepsVariablesMock {
131+
return &stepsVariablesMock{
132+
variables: make(map[string]map[string]json.RawMessage),
133+
}
134+
}
135+
136+
func (svm *stepsVariablesMock) AddRaw(tgtID string, name string, b json.RawMessage) error {
137+
svm.mu.Lock()
138+
defer svm.mu.Unlock()
139+
140+
targetVars := svm.variables[tgtID]
141+
if targetVars == nil {
142+
targetVars = make(map[string]json.RawMessage)
143+
svm.variables[tgtID] = targetVars
144+
}
145+
targetVars[name] = b
146+
return nil
147+
}
148+
149+
func (svm *stepsVariablesMock) Add(tgtID string, name string, v interface{}) error {
150+
b, err := json.Marshal(v)
151+
if err != nil {
152+
return err
153+
}
154+
155+
return svm.AddRaw(tgtID, name, b)
156+
}
157+
158+
func (svm *stepsVariablesMock) Get(tgtID string, stepLabel, name string, out interface{}) error {
159+
panic("not implemented")
160+
}
161+
162+
func (svm *stepsVariablesMock) get(tgtID string, name string, out interface{}) error {
163+
svm.mu.Lock()
164+
defer svm.mu.Unlock()
165+
166+
targetVars := svm.variables[tgtID]
167+
if targetVars == nil {
168+
return fmt.Errorf("no target: %s", tgtID)
169+
}
170+
b, found := targetVars[name]
171+
if !found {
172+
return fmt.Errorf("no variable: %s", name)
173+
}
174+
return json.Unmarshal(b, out)
175+
}

0 commit comments

Comments
 (0)