Skip to content

Commit d6574bb

Browse files
committed
POC conformance test suite
1 parent 933f2a0 commit d6574bb

File tree

1 file changed

+273
-0
lines changed

1 file changed

+273
-0
lines changed

test/poc_conformance_test.go

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
//go:build conformance
2+
// +build conformance
3+
4+
/*
5+
This serves as a POC for conformance test suite design including functionality,
6+
behavioural and fields population.
7+
It mocks the "black-box" execution of TaskRuns and PipelineRuns utilizing the
8+
Tekton clients to mock the controller of a conformant vendor service.
9+
10+
Please use the following for triggering the test:
11+
go test -v -tags=conformance -count=1 ./test -run ^TestConformance
12+
13+
The next step will be to integrate this test as POC with v2 API.
14+
*/
15+
16+
package test
17+
18+
import (
19+
"context"
20+
"fmt"
21+
"strconv"
22+
"testing"
23+
24+
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
25+
"github.com/tektoncd/pipeline/test/parse"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/client-go/kubernetes/scheme"
28+
knativetest "knative.dev/pkg/test"
29+
"knative.dev/pkg/test/helpers"
30+
"sigs.k8s.io/yaml"
31+
)
32+
33+
const (
34+
TaskRunInputType = "TaskRun"
35+
PipelineRunInputType = "PipelineRun"
36+
)
37+
38+
// TestConformanceShouldProvideTaskResult examines the TaskResult functionality
39+
// by creating a TaskRun that performs multiplication in Steps to write to the
40+
// Task-level result for validation.
41+
func TestConformanceShouldProvideTaskResult(t *testing.T) {
42+
var multiplicand, multipliper = 3, 5
43+
44+
inputYAML := fmt.Sprintf(`
45+
apiVersion: tekton.dev/v1
46+
kind: TaskRun
47+
metadata:
48+
name: %s
49+
spec:
50+
taskSpec:
51+
params:
52+
- name: multiplicand
53+
description: the first operand
54+
default: %s
55+
- name: multipliper
56+
description: the second operand
57+
default: %s
58+
results:
59+
- name: product
60+
description: the product of the first and second operand
61+
steps:
62+
- name: add
63+
image: alpine
64+
env:
65+
- name: OP1
66+
value: $(params.multiplicand)
67+
- name: OP2
68+
value: $(params.multipliper)
69+
command: ["/bin/sh", "-c"]
70+
args:
71+
- echo -n $((${OP1}*${OP2})) | tee $(results.product.path);
72+
`, helpers.ObjectNameForTest(t), strconv.Itoa(multiplicand), strconv.Itoa(multipliper))
73+
74+
// Black box execution of Pipeline CRDs that should be implemented by Vendor service
75+
outputYAML, err := ProcessAndSendToTekton(inputYAML, TaskRunInputType, t)
76+
if err != nil {
77+
t.Fatalf("Vendor service failed processing inputYAML: %s", err)
78+
}
79+
80+
// Parse and validate output YAML
81+
resolvedTR := parse.MustParseV1TaskRun(t, outputYAML)
82+
if len(resolvedTR.Status.Results) != 1 {
83+
t.Errorf("Expect vendor service to provide 1 result but not")
84+
}
85+
86+
if resolvedTR.Status.Results[0].Value.StringVal != strconv.Itoa(multiplicand*multipliper) {
87+
t.Errorf("Not producing correct result :%s", resolvedTR.Status.Results[0].Value.StringVal)
88+
}
89+
}
90+
91+
// TestConformanceShouldHonorTaskRunTimeout examines the Timeout behaviour for
92+
// TaskRun level. It creates a TaskRun with Timeout and wait in the Step of the
93+
// inline Task for the time length longer than the specified Timeout.
94+
// The TaskRun is expected to fail with the Reason `TaskRunTimeout`.
95+
func TestConformanceShouldHonorTaskRunTimeout(t *testing.T) {
96+
expectedFailedStatus := true
97+
inputYAML := fmt.Sprintf(`
98+
apiVersion: tekton.dev/v1
99+
kind: TaskRun
100+
metadata:
101+
name: %s
102+
spec:
103+
timeout: 15s
104+
taskSpec:
105+
steps:
106+
- image: busybox
107+
command: ['/bin/sh']
108+
args: ['-c', 'sleep 15001']
109+
`, helpers.ObjectNameForTest(t))
110+
111+
// Black box execution of Pipeline CRDs that should be implemented by Vendor service
112+
outputYAML, err := ProcessAndSendToTekton(inputYAML, TaskRunInputType, t, expectedFailedStatus)
113+
if err != nil {
114+
t.Fatalf("Vendor service failed processing inputYAML: %s", err)
115+
}
116+
117+
// Parse and validate output YAML
118+
resolvedTR := parse.MustParseV1TaskRun(t, outputYAML)
119+
if len(resolvedTR.Status.Conditions) != 1 {
120+
t.Errorf("Expect vendor service to populate 1 Condition but no")
121+
}
122+
123+
if resolvedTR.Status.Conditions[0].Type != "Succeeded" {
124+
t.Errorf("Expect vendor service to populate Condition `Succeeded` but got: %s", resolvedTR.Status.Conditions[0].Type)
125+
}
126+
127+
if resolvedTR.Status.Conditions[0].Status != "False" {
128+
t.Errorf("Expect vendor service to populate Condition `False` but got: %s", resolvedTR.Status.Conditions[0].Status)
129+
}
130+
131+
if resolvedTR.Status.Conditions[0].Reason != "TaskRunTimeout" {
132+
t.Errorf("Expect vendor service to populate Condition Reason `TaskRunTimeout` but got: %s", resolvedTR.Status.Conditions[0].Reason)
133+
}
134+
}
135+
136+
// TestConformanceShouldHonorTaskRunTimeout examines population of Conditions
137+
// fields. It creates the a TaskRun with minimal specifications and checks the
138+
// required Condition Status and Type.
139+
func TestConformanceShouldPopulateConditions(t *testing.T) {
140+
inputYAML := fmt.Sprintf(`
141+
apiVersion: tekton.dev/v1
142+
kind: TaskRun
143+
metadata:
144+
name: %s
145+
spec:
146+
taskSpec:
147+
steps:
148+
- name: add
149+
image: ubuntu
150+
script:
151+
echo Hello world!
152+
`, helpers.ObjectNameForTest(t))
153+
154+
// Black box execution of Pipeline CRDs that should be implemented by Vendor service
155+
outputYAML, err := ProcessAndSendToTekton(inputYAML, TaskRunInputType, t)
156+
if err != nil {
157+
t.Fatalf("Vendor service failed processing inputYAML: %s", err)
158+
}
159+
160+
// Parse and validate output YAML
161+
resolvedTR := parse.MustParseV1TaskRun(t, outputYAML)
162+
if len(resolvedTR.Status.Conditions) != 1 {
163+
t.Errorf("Expect vendor service to populate 1 Condition but no")
164+
}
165+
166+
if resolvedTR.Status.Conditions[0].Type != "Succeeded" {
167+
t.Errorf("Expect vendor service to populate Condition `Succeeded` but got: %s", resolvedTR.Status.Conditions[0].Type)
168+
}
169+
170+
if resolvedTR.Status.Conditions[0].Status != "True" {
171+
t.Errorf("Expect vendor service to populate Condition `True` but got: %s", resolvedTR.Status.Conditions[0].Status)
172+
}
173+
}
174+
175+
// ProcessAndSendToTekton takes in vanilla Tekton PipelineRun and TaskRun, waits for the object to succeed and outputs the final PipelineRun and TaskRun with status.
176+
// The parameters are inputYAML and its Primitive type {PipelineRun, TaskRun}
177+
// And the return values will be the output YAML string and errors.
178+
func ProcessAndSendToTekton(inputYAML, primitiveType string, customInputs ...interface{}) (string, error) {
179+
// Handle customInputs
180+
var t *testing.T
181+
var expectRunToFail bool
182+
for _, customInput := range customInputs {
183+
if ci, ok := customInput.(*testing.T); ok {
184+
t = ci
185+
}
186+
if ci, ok := customInput.(bool); ok {
187+
expectRunToFail = ci
188+
}
189+
}
190+
191+
return mockTektonPipelineController(t, inputYAML, primitiveType, expectRunToFail)
192+
}
193+
194+
// mockTektonPipelineController fakes the behaviour of a vendor service by utilizing the Tekton test infrastructure.
195+
// For the POC, it uses the Tetkon clients to Create, Wait for and Get the expected TaskRun.
196+
func mockTektonPipelineController(t *testing.T, inputYAML, primitiveType string, expectRunToFail bool) (string, error) {
197+
ctx := context.Background()
198+
ctx, cancel := context.WithCancel(ctx)
199+
defer cancel()
200+
201+
c, namespace := setup(ctx, t)
202+
knativetest.CleanupOnInterrupt(func() { tearDown(ctx, t, c, namespace) }, t.Logf)
203+
defer tearDown(ctx, t, c, namespace)
204+
205+
mvs := MockVendorSerivce{cs: c}
206+
207+
tr, err := mvs.CreateTaskRun(ctx, inputYAML)
208+
if err != nil {
209+
return "", err
210+
}
211+
212+
if err := mvs.WaitForTaskRun(ctx, tr.Name, expectRunToFail); err != nil {
213+
return "", err
214+
}
215+
216+
trGot, err := mvs.GetTaskRun(ctx, tr.Name)
217+
if err != nil {
218+
return "", err
219+
}
220+
221+
outputYAML, err := yaml.Marshal(trGot)
222+
if err != nil {
223+
return "", err
224+
}
225+
return string(outputYAML[:]), nil
226+
}
227+
228+
type VendorService interface {
229+
CreateTaskRun(ctx context.Context, yaml string) (*v1.TaskRun, error)
230+
WaitForTaskRun(ctx context.Context, name string) error
231+
GetTaskRun(ctx context.Context, name string) (*v1.TaskRun, error)
232+
}
233+
234+
type MockVendorSerivce struct {
235+
cs *clients
236+
}
237+
238+
// CreateTaskRun parses the inputYAML to a TaskRun and creates the TaskRun via TaskRunClient
239+
func (mvs MockVendorSerivce) CreateTaskRun(ctx context.Context, inputYAML string) (*v1.TaskRun, error) {
240+
var tr v1.TaskRun
241+
if _, _, err := scheme.Codecs.UniversalDeserializer().Decode([]byte(inputYAML), nil, &tr); err != nil {
242+
return nil, fmt.Errorf("must parse YAML (%s): %v", inputYAML, err)
243+
}
244+
245+
var trCreated *v1.TaskRun
246+
trCreated, err := mvs.cs.V1TaskRunClient.Create(ctx, &tr, metav1.CreateOptions{})
247+
if err != nil {
248+
return nil, fmt.Errorf("failed to create TaskRun `%v`: %w", tr, err)
249+
}
250+
return trCreated, nil
251+
}
252+
253+
// CreateTaskRun waits for the TaskRun to get done according to the expected Condition Accessor function
254+
func (mvs MockVendorSerivce) WaitForTaskRun(ctx context.Context, name string, expectRunToFail bool) error {
255+
var caf ConditionAccessorFn
256+
caf = Succeed(name)
257+
if expectRunToFail {
258+
caf = Failed(name)
259+
}
260+
if err := WaitForTaskRunState(ctx, mvs.cs, name, caf, "WaitTaskRunDone", v1Version); err != nil {
261+
return fmt.Errorf("error waiting for TaskRun to finish: %s", err)
262+
}
263+
return nil
264+
}
265+
266+
// CreateTaskRun retrieves the TaskRun via TaskRunClient
267+
func (mvs MockVendorSerivce) GetTaskRun(ctx context.Context, name string) (*v1.TaskRun, error) {
268+
trGot, err := mvs.cs.V1TaskRunClient.Get(ctx, name, metav1.GetOptions{})
269+
if err != nil {
270+
return nil, fmt.Errorf("failed to get TaskRun `%s`: %s", trGot.Name, err)
271+
}
272+
return trGot, nil
273+
}

0 commit comments

Comments
 (0)