Skip to content

Commit 804f233

Browse files
All Resources Unit Testing Set Up + Example (#26)
Note that this PR is meant to be merged on top of [PRs #25](#25) Code Coverage: 26.3% New Files Per Resource [resource-name]: (4 files/feature * 2 features = 8 new files) - pkg/resource/[resource-name]/manager_test_suite_test.go - Contains resource specific helper functions for unit testing - pkg/resource/[resource-name]/testdata/test_suite.yaml - Contains the written out unit test - pkg/resource/[resource-name]/testdata/endpoint/v1alpha1/[resource-abbreviation]_cmd_invalid_before_create.yaml - Example input / desired state file that is used for testing Create functionality with an InvalidParameterValue. - pkg/resource/[resource-name]/testdata/endpoint/v1alpha1/[resource-abbreviation]_cmd_invalid_create_attempted.yaml - Example output / expected state file that is used for testing Create functionality with an InvalidParameterValue.
1 parent 3a98650 commit 804f233

File tree

8 files changed

+346
-0
lines changed

8 files changed

+346
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package scalable_target
15+
16+
import (
17+
"errors"
18+
"fmt"
19+
20+
mocksvcsdkapi "github.com/aws-controllers-k8s/applicationautoscaling-controller/test/mocks/aws-sdk-go/applicationautoscaling"
21+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
22+
"github.com/ghodss/yaml"
23+
"github.com/aws-controllers-k8s/applicationautoscaling-controller/pkg/testutil"
24+
acktypes "github.com/aws-controllers-k8s/runtime/pkg/types"
25+
svcsdk "github.com/aws/aws-sdk-go/service/applicationautoscaling"
26+
"github.com/google/go-cmp/cmp"
27+
"github.com/google/go-cmp/cmp/cmpopts"
28+
"path/filepath"
29+
"testing"
30+
ctrlrtzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
31+
ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics"
32+
"go.uber.org/zap/zapcore"
33+
)
34+
35+
// provideResourceManagerWithMockSDKAPI accepts MockApplicationAutoScalingAPI and returns pointer to resourceManager
36+
// the returned resourceManager is configured to use mockapi api.
37+
func provideResourceManagerWithMockSDKAPI(mockApplicationAutoScalingAPI *mocksvcsdkapi.ApplicationAutoScalingAPI) *resourceManager {
38+
zapOptions := ctrlrtzap.Options{
39+
Development: true,
40+
Level: zapcore.InfoLevel,
41+
}
42+
fakeLogger := ctrlrtzap.New(ctrlrtzap.UseFlagOptions(&zapOptions))
43+
return &resourceManager{
44+
rr: nil,
45+
awsAccountID: "",
46+
awsRegion: "",
47+
sess: nil,
48+
sdkapi: mockApplicationAutoScalingAPI,
49+
log: fakeLogger,
50+
metrics: ackmetrics.NewMetrics("applicationautoscaling"),
51+
}
52+
}
53+
54+
// TestScalableTargetTestSuite runs the test suite for scalable target
55+
func TestScalableTargetTestSuite(t *testing.T) {
56+
var ts = testutil.TestSuite{}
57+
testutil.LoadFromFixture(filepath.Join("testdata", "test_suite.yaml"), &ts)
58+
var delegate = testRunnerDelegate{t: t}
59+
var runner = testutil.TestSuiteRunner{TestSuite: &ts, Delegate: &delegate}
60+
runner.RunTests()
61+
}
62+
63+
// testRunnerDelegate implements testutil.TestRunnerDelegate
64+
type testRunnerDelegate struct {
65+
t *testing.T
66+
}
67+
68+
func (d *testRunnerDelegate) ResourceDescriptor() acktypes.AWSResourceDescriptor {
69+
return &resourceDescriptor{}
70+
}
71+
72+
func (d *testRunnerDelegate) ResourceManager(mocksdkapi *mocksvcsdkapi.ApplicationAutoScalingAPI) acktypes.AWSResourceManager {
73+
return provideResourceManagerWithMockSDKAPI(mocksdkapi)
74+
}
75+
76+
func (d *testRunnerDelegate) GoTestRunner() *testing.T {
77+
return d.t
78+
}
79+
80+
func (d *testRunnerDelegate) EmptyServiceAPIOutput(apiName string) (interface{}, error) {
81+
if apiName == "" {
82+
return nil, errors.New("no API name specified")
83+
}
84+
//TODO: use reflection, template to auto generate this block/method.
85+
switch apiName {
86+
case "DescribeScalableTargetsWithContext":
87+
var output svcsdk.DescribeScalableTargetsOutput
88+
return &output, nil
89+
case "RegisterScalableTargetWithContext":
90+
var output svcsdk.RegisterScalableTargetOutput
91+
return &output, nil
92+
case "DeregisterScalableTargetWithContext":
93+
var output svcsdk.DeregisterScalableTargetOutput
94+
return &output, nil
95+
}
96+
return nil, errors.New(fmt.Sprintf("no matching API name found for: %s", apiName))
97+
}
98+
99+
func (d *testRunnerDelegate) Equal(a acktypes.AWSResource, b acktypes.AWSResource) bool {
100+
ac := a.(*resource)
101+
bc := b.(*resource)
102+
// Ignore LastTransitionTime since it gets updated each run.
103+
opts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(ackv1alpha1.Condition{}, "LastTransitionTime")}
104+
105+
if cmp.Equal(ac.ko.Status, bc.ko.Status, opts...) {
106+
return true
107+
} else {
108+
fmt.Printf("Difference (-expected +actual):\n\n")
109+
fmt.Println(cmp.Diff(ac.ko.Status, bc.ko.Status, opts...))
110+
return false
111+
}
112+
}
113+
114+
// Checks to see if the given yaml file, with name stored as expectation,
115+
// matches the yaml marshal of the AWSResource stored as actual.
116+
func (d *testRunnerDelegate) YamlEqual(expectation string, actual acktypes.AWSResource) bool {
117+
// Build a tmp file for the actual yaml.
118+
actualResource := actual.(*resource)
119+
actualYamlByteArray, _ := yaml.Marshal(actualResource.ko)
120+
return testutil.IsYamlEqual(&expectation, &actualYamlByteArray)
121+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: applicationautoscaling.services.k8s.aws/v1alpha1
2+
kind: ScalableTarget
3+
metadata:
4+
name: unit-testing-scalable-target
5+
spec:
6+
maxCapacity: 2
7+
minCapacity: 1
8+
resourceID: endpoint/unit-testing-endpoint/variant/variant-1
9+
scalableDimension: "sagemaker:variant:DesiredInstanceCount"
10+
serviceNamespace: !-intentionally-invalid-service-namespace
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apiVersion: applicationautoscaling.services.k8s.aws/v1alpha1
2+
kind: ScalableTarget
3+
metadata:
4+
creationTimestamp: null
5+
name: unit-testing-scalable-target
6+
spec:
7+
maxCapacity: 2
8+
minCapacity: 1
9+
resourceID: endpoint/unit-testing-endpoint/variant/variant-1
10+
scalableDimension: sagemaker:variant:DesiredInstanceCount
11+
serviceNamespace: ""
12+
status:
13+
ackResourceMetadata:
14+
ownerAccountID: ""
15+
conditions:
16+
- message: 'The service namespace must be one of the following: appstream | cassandra
17+
| comprehend | custom-resource | dynamodb | ec2 | ecs | elasticmapreduce | kafka
18+
| lambda | rds | sagemaker.'
19+
status: "True"
20+
type: ACK.Recoverable
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests:
2+
- name: "Scalable target demo test"
3+
description: "Scalable target CRD tests"
4+
scenarios:
5+
- name: "Put=InvalidInput"
6+
description: "Given one of the parameters is invalid, ko.Status shows a terminal condition"
7+
given:
8+
desired_state: "scalable_target/v1alpha1/st_invalid_before_create.yaml"
9+
svc_api:
10+
- operation: RegisterScalableTargetWithContext
11+
error:
12+
code: InvalidParameterValue
13+
message: "The service namespace must be one of the following: appstream | cassandra | comprehend | custom-resource | dynamodb | ec2 | ecs | elasticmapreduce | kafka | lambda | rds | sagemaker."
14+
invoke: Create
15+
expect:
16+
latest_state: "scalable_target/v1alpha1/st_invalid_create_attempted.yaml"
17+
error: "InvalidParameterValue: The service namespace must be one of the following: appstream | cassandra | comprehend | custom-resource | dynamodb | ec2 | ecs | elasticmapreduce | kafka | lambda | rds | sagemaker.\n\tstatus code: 0, request id: "
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package scaling_policy
15+
16+
import (
17+
"errors"
18+
"fmt"
19+
20+
mocksvcsdkapi "github.com/aws-controllers-k8s/applicationautoscaling-controller/test/mocks/aws-sdk-go/applicationautoscaling"
21+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
22+
"github.com/ghodss/yaml"
23+
"github.com/aws-controllers-k8s/applicationautoscaling-controller/pkg/testutil"
24+
acktypes "github.com/aws-controllers-k8s/runtime/pkg/types"
25+
svcsdk "github.com/aws/aws-sdk-go/service/applicationautoscaling"
26+
"github.com/google/go-cmp/cmp"
27+
"github.com/google/go-cmp/cmp/cmpopts"
28+
"path/filepath"
29+
"testing"
30+
ctrlrtzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
31+
ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics"
32+
"go.uber.org/zap/zapcore"
33+
)
34+
35+
// provideResourceManagerWithMockSDKAPI accepts MockApplicationAutoScalingAPI and returns pointer to resourceManager
36+
// the returned resourceManager is configured to use mockapi api.
37+
func provideResourceManagerWithMockSDKAPI(mockApplicationAutoScalingAPI *mocksvcsdkapi.ApplicationAutoScalingAPI) *resourceManager {
38+
zapOptions := ctrlrtzap.Options{
39+
Development: true,
40+
Level: zapcore.InfoLevel,
41+
}
42+
fakeLogger := ctrlrtzap.New(ctrlrtzap.UseFlagOptions(&zapOptions))
43+
return &resourceManager{
44+
rr: nil,
45+
awsAccountID: "",
46+
awsRegion: "",
47+
sess: nil,
48+
sdkapi: mockApplicationAutoScalingAPI,
49+
log: fakeLogger,
50+
metrics: ackmetrics.NewMetrics("applicationautoscaling"),
51+
}
52+
}
53+
54+
// TestScalingPolicyTestSuite runs the test suite for scaling policy
55+
func TestScalingPolicyTestSuite(t *testing.T) {
56+
var ts = testutil.TestSuite{}
57+
testutil.LoadFromFixture(filepath.Join("testdata", "test_suite.yaml"), &ts)
58+
var delegate = testRunnerDelegate{t: t}
59+
var runner = testutil.TestSuiteRunner{TestSuite: &ts, Delegate: &delegate}
60+
runner.RunTests()
61+
}
62+
63+
// testRunnerDelegate implements testutil.TestRunnerDelegate
64+
type testRunnerDelegate struct {
65+
t *testing.T
66+
}
67+
68+
func (d *testRunnerDelegate) ResourceDescriptor() acktypes.AWSResourceDescriptor {
69+
return &resourceDescriptor{}
70+
}
71+
72+
func (d *testRunnerDelegate) ResourceManager(mocksdkapi *mocksvcsdkapi.ApplicationAutoScalingAPI) acktypes.AWSResourceManager {
73+
return provideResourceManagerWithMockSDKAPI(mocksdkapi)
74+
}
75+
76+
func (d *testRunnerDelegate) GoTestRunner() *testing.T {
77+
return d.t
78+
}
79+
80+
func (d *testRunnerDelegate) EmptyServiceAPIOutput(apiName string) (interface{}, error) {
81+
if apiName == "" {
82+
return nil, errors.New("no API name specified")
83+
}
84+
//TODO: use reflection, template to auto generate this block/method.
85+
switch apiName {
86+
case "DescribeScalingPoliciesWithContext":
87+
var output svcsdk.DescribeScalingPoliciesOutput
88+
return &output, nil
89+
case "PutScalingPolicyWithContext":
90+
var output svcsdk.PutScalingPolicyOutput
91+
return &output, nil
92+
case "DeleteScalingPolicyWithContext":
93+
var output svcsdk.DeleteScalingPolicyOutput
94+
return &output, nil
95+
}
96+
return nil, errors.New(fmt.Sprintf("no matching API name found for: %s", apiName))
97+
}
98+
99+
func (d *testRunnerDelegate) Equal(a acktypes.AWSResource, b acktypes.AWSResource) bool {
100+
ac := a.(*resource)
101+
bc := b.(*resource)
102+
// Ignore LastTransitionTime since it gets updated each run.
103+
opts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(ackv1alpha1.Condition{}, "LastTransitionTime")}
104+
105+
if cmp.Equal(ac.ko.Status, bc.ko.Status, opts...) {
106+
return true
107+
} else {
108+
fmt.Printf("Difference (-expected +actual):\n\n")
109+
fmt.Println(cmp.Diff(ac.ko.Status, bc.ko.Status, opts...))
110+
return false
111+
}
112+
}
113+
114+
// Checks to see if the given yaml file, with name stored as expectation,
115+
// matches the yaml marshal of the AWSResource stored as actual.
116+
func (d *testRunnerDelegate) YamlEqual(expectation string, actual acktypes.AWSResource) bool {
117+
// Build a tmp file for the actual yaml.
118+
actualResource := actual.(*resource)
119+
actualYamlByteArray, _ := yaml.Marshal(actualResource.ko)
120+
return testutil.IsYamlEqual(&expectation, &actualYamlByteArray)
121+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: applicationautoscaling.services.k8s.aws/v1alpha1
2+
kind: ScalingPolicy
3+
metadata:
4+
name: unit-testing-scaling-policy
5+
spec:
6+
policyName: !-intentionally-invalid-name
7+
policyType: TargetTrackingScaling
8+
resourceID: endpoint/unit-testing-endpoint/variant/variant-1
9+
scalableDimension: "sagemaker:variant:DesiredInstanceCount"
10+
serviceNamespace: sagemaker
11+
targetTrackingScalingPolicyConfiguration:
12+
targetValue: 60
13+
scaleInCooldown: 700
14+
scaleOutCooldown: 300
15+
predefinedMetricSpecification:
16+
predefinedMetricType: SageMakerVariantInvocationsPerInstance
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
apiVersion: applicationautoscaling.services.k8s.aws/v1alpha1
2+
kind: ScalingPolicy
3+
metadata:
4+
creationTimestamp: null
5+
name: unit-testing-scaling-policy
6+
spec:
7+
policyName: ""
8+
policyType: TargetTrackingScaling
9+
resourceID: endpoint/unit-testing-endpoint/variant/variant-1
10+
scalableDimension: sagemaker:variant:DesiredInstanceCount
11+
serviceNamespace: sagemaker
12+
targetTrackingScalingPolicyConfiguration:
13+
predefinedMetricSpecification:
14+
predefinedMetricType: SageMakerVariantInvocationsPerInstance
15+
scaleInCooldown: 700
16+
scaleOutCooldown: 300
17+
targetValue: 60
18+
status:
19+
ackResourceMetadata:
20+
ownerAccountID: ""
21+
conditions:
22+
- message: The policy name must start with an alphanumeric character.
23+
status: "True"
24+
type: ACK.Recoverable
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests:
2+
- name: "Scaling policy demo test"
3+
description: "Scaling policy CRD tests"
4+
scenarios:
5+
- name: "Put=InvalidInput"
6+
description: "Given one of the parameters is invalid, ko.Status shows a terminal condition"
7+
given:
8+
desired_state: "scaling_policy/v1alpha1/sp_invalid_before_create.yaml"
9+
svc_api:
10+
- operation: PutScalingPolicyWithContext
11+
error:
12+
code: InvalidParameterValue
13+
message: "The policy name must start with an alphanumeric character."
14+
invoke: Create
15+
expect:
16+
latest_state: "scaling_policy/v1alpha1/sp_invalid_create_attempted.yaml"
17+
error: "InvalidParameterValue: The policy name must start with an alphanumeric character.\n\tstatus code: 0, request id: "

0 commit comments

Comments
 (0)