Skip to content

Commit 0b96ed2

Browse files
All Resources Unit Testing Set Up + Example (#76)
Note that this PR is meant to be merged on top of PRs [#66](#66) and [#68](#68) Summary: Added example Create with invalid input unit test for all resources. Code Coverage: 26.0% New Files Per Resource [resource-name]: (4 files/feature * 14 features = 56 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 1367c76 commit 0b96ed2

File tree

58 files changed

+3088
-90
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3088
-90
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 data_quality_job_definition
15+
16+
import (
17+
"errors"
18+
"fmt"
19+
20+
mocksvcsdkapi "github.com/aws-controllers-k8s/sagemaker-controller/test/mocks/aws-sdk-go/sagemaker"
21+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
22+
"github.com/ghodss/yaml"
23+
"github.com/aws-controllers-k8s/sagemaker-controller/pkg/testutil"
24+
acktypes "github.com/aws-controllers-k8s/runtime/pkg/types"
25+
svcsdk "github.com/aws/aws-sdk-go/service/sagemaker"
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 MockSageMakerAPI and returns pointer to resourceManager
36+
// the returned resourceManager is configured to use mockapi api.
37+
func provideResourceManagerWithMockSDKAPI(mockSageMakerAPI *mocksvcsdkapi.SageMakerAPI) *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: mockSageMakerAPI,
49+
log: fakeLogger,
50+
metrics: ackmetrics.NewMetrics("sagemaker"),
51+
}
52+
}
53+
54+
// TestDataQualityJobDefinitionTestSuite runs the test suite for data quality job definition
55+
func TestDataQualityJobDefinitionTestSuite(t *testing.T) {
56+
defer func() {
57+
if r := recover(); r != nil {
58+
fmt.Println(testutil.RecoverPanicString, r)
59+
t.Fail()
60+
}
61+
}()
62+
var ts = testutil.TestSuite{}
63+
testutil.LoadFromFixture(filepath.Join("testdata", "test_suite.yaml"), &ts)
64+
var delegate = testRunnerDelegate{t: t}
65+
var runner = testutil.TestSuiteRunner{TestSuite: &ts, Delegate: &delegate}
66+
runner.RunTests()
67+
}
68+
69+
// testRunnerDelegate implements testutil.TestRunnerDelegate
70+
type testRunnerDelegate struct {
71+
t *testing.T
72+
}
73+
74+
func (d *testRunnerDelegate) ResourceDescriptor() acktypes.AWSResourceDescriptor {
75+
return &resourceDescriptor{}
76+
}
77+
78+
func (d *testRunnerDelegate) ResourceManager(mocksdkapi *mocksvcsdkapi.SageMakerAPI) acktypes.AWSResourceManager {
79+
return provideResourceManagerWithMockSDKAPI(mocksdkapi)
80+
}
81+
82+
func (d *testRunnerDelegate) GoTestRunner() *testing.T {
83+
return d.t
84+
}
85+
86+
func (d *testRunnerDelegate) EmptyServiceAPIOutput(apiName string) (interface{}, error) {
87+
if apiName == "" {
88+
return nil, errors.New("no API name specified")
89+
}
90+
//TODO: use reflection, template to auto generate this block/method.
91+
switch apiName {
92+
case "CreateDataQualityJobDefinitionWithContext":
93+
var output svcsdk.CreateDataQualityJobDefinitionOutput
94+
return &output, nil
95+
case "DescribeDataQualityJobDefinitionWithContext":
96+
var output svcsdk.DescribeDataQualityJobDefinitionOutput
97+
return &output, nil
98+
case "DeleteDataQualityJobDefinitionWithContext":
99+
var output svcsdk.DeleteDataQualityJobDefinitionOutput
100+
return &output, nil
101+
}
102+
return nil, errors.New(fmt.Sprintf("no matching API name found for: %s", apiName))
103+
}
104+
105+
func (d *testRunnerDelegate) Equal(a acktypes.AWSResource, b acktypes.AWSResource) bool {
106+
ac := a.(*resource)
107+
bc := b.(*resource)
108+
// Ignore LastTransitionTime since it gets updated each run.
109+
opts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(ackv1alpha1.Condition{}, "LastTransitionTime")}
110+
111+
if cmp.Equal(ac.ko.Status, bc.ko.Status, opts...) {
112+
return true
113+
} else {
114+
fmt.Printf("Difference (-expected +actual):\n\n")
115+
fmt.Println(cmp.Diff(ac.ko.Status, bc.ko.Status, opts...))
116+
return false
117+
}
118+
}
119+
120+
// Checks to see if the given yaml file, with name stored as expectation,
121+
// matches the yaml marshal of the AWSResource stored as actual.
122+
func (d *testRunnerDelegate) YamlEqual(expectation string, actual acktypes.AWSResource) bool {
123+
// Build a tmp file for the actual yaml.
124+
actualResource := actual.(*resource)
125+
actualYamlByteArray, _ := yaml.Marshal(actualResource.ko)
126+
return testutil.IsYamlEqual(&expectation, &actualYamlByteArray)
127+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
apiVersion: sagemaker.services.k8s.aws/v1alpha1
2+
kind: DataQualityJobDefinition
3+
metadata:
4+
name: unit_testing_job_definition
5+
spec:
6+
jobDefinitionName: !-intentionally-invalid-name
7+
jobResources:
8+
clusterConfig:
9+
instanceCount: 1
10+
instanceType: ml.m5.large
11+
volumeSizeInGB: 20
12+
dataQualityAppSpecification:
13+
imageURI: 159807026194.dkr.ecr.us-west-2.amazonaws.com
14+
postAnalyticsProcessorSourceURI: "s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/code/postprocessor.py"
15+
dataQualityBaselineConfig:
16+
constraintsResource:
17+
s3URI: "s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/baselining/constraints.json"
18+
statisticsResource:
19+
s3URI: "s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/baselining/statistics.json"
20+
dataQualityJobInput:
21+
endpointInput:
22+
endpointName: unit_testing_endpoint
23+
localPath: "/opt/ml/processing/input/endpoint"
24+
s3InputMode: File
25+
s3DataDistributionType: FullyReplicated
26+
dataQualityJobOutputConfig:
27+
monitoringOutputs:
28+
- s3Output:
29+
localPath: "/opt/ml/processing/output"
30+
s3URI: "s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/reports"
31+
s3UploadMode: Continuous
32+
stoppingCondition:
33+
maxRuntimeInSeconds: 1800
34+
roleARN: arn:aws:iam::123456789012:role/ack-sagemaker-execution-role
35+
tags:
36+
- key: confidentiality
37+
value: public
38+
- key: environment
39+
value: testing
40+
- key: customer
41+
value: test-user
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
apiVersion: sagemaker.services.k8s.aws/v1alpha1
2+
kind: DataQualityJobDefinition
3+
metadata:
4+
creationTimestamp: null
5+
name: unit_testing_job_definition
6+
spec:
7+
dataQualityAppSpecification:
8+
imageURI: 159807026194.dkr.ecr.us-west-2.amazonaws.com
9+
postAnalyticsProcessorSourceURI: s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/code/postprocessor.py
10+
dataQualityBaselineConfig:
11+
constraintsResource:
12+
s3URI: s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/baselining/constraints.json
13+
statisticsResource:
14+
s3URI: s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/baselining/statistics.json
15+
dataQualityJobInput:
16+
endpointInput:
17+
endpointName: unit_testing_endpoint
18+
localPath: /opt/ml/processing/input/endpoint
19+
s3DataDistributionType: FullyReplicated
20+
s3InputMode: File
21+
dataQualityJobOutputConfig:
22+
monitoringOutputs:
23+
- s3Output:
24+
localPath: /opt/ml/processing/output
25+
s3URI: s3://source-data-bucket-592697580195-us-west-2/sagemaker/data_quality_job_definition/reports
26+
s3UploadMode: Continuous
27+
jobDefinitionName: ""
28+
jobResources:
29+
clusterConfig:
30+
instanceCount: 1
31+
instanceType: ml.m5.large
32+
volumeSizeInGB: 20
33+
roleARN: arn:aws:iam::123456789012:role/ack-sagemaker-execution-role
34+
stoppingCondition:
35+
maxRuntimeInSeconds: 1800
36+
tags:
37+
- key: confidentiality
38+
value: public
39+
- key: environment
40+
value: testing
41+
- key: customer
42+
value: test-user
43+
status:
44+
ackResourceMetadata:
45+
ownerAccountID: ""
46+
conditions:
47+
- message: The job definition name must start with an alphanumeric character.
48+
status: "True"
49+
type: ACK.Terminal
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests:
2+
- name: "Data quality job definition create tests"
3+
description: "Part of data quality job definition CRD tests."
4+
scenarios:
5+
- name: "Create=InvalidInput"
6+
description: "Given one of the parameters is invalid, ko.Status shows a terminal condition"
7+
given:
8+
desired_state: "data_quality_job_definition/v1alpha1/dqjd_invalid_before_create.yaml"
9+
svc_api:
10+
- operation: CreateDataQualityJobDefinitionWithContext
11+
error:
12+
code: InvalidParameterValue
13+
message: "The job definition name must start with an alphanumeric character."
14+
invoke: Create
15+
expect:
16+
latest_state: "data_quality_job_definition/v1alpha1/dqjd_invalid_create_attempted.yaml"
17+
error: resource is in terminal condition
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 endpoint
15+
16+
import (
17+
"errors"
18+
"fmt"
19+
20+
mocksvcsdkapi "github.com/aws-controllers-k8s/sagemaker-controller/test/mocks/aws-sdk-go/sagemaker"
21+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
22+
"github.com/ghodss/yaml"
23+
"github.com/aws-controllers-k8s/sagemaker-controller/pkg/testutil"
24+
acktypes "github.com/aws-controllers-k8s/runtime/pkg/types"
25+
svcsdk "github.com/aws/aws-sdk-go/service/sagemaker"
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 MockSageMakerAPI and returns pointer to resourceManager
36+
// the returned resourceManager is configured to use mockapi api.
37+
func provideResourceManagerWithMockSDKAPI(mockSageMakerAPI *mocksvcsdkapi.SageMakerAPI) *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: mockSageMakerAPI,
49+
log: fakeLogger,
50+
metrics: ackmetrics.NewMetrics("sagemaker"),
51+
}
52+
}
53+
54+
// TestEndpointTestSuite runs the test suite for endpoint
55+
func TestEndpointTestSuite(t *testing.T) {
56+
defer func() {
57+
if r := recover(); r != nil {
58+
fmt.Println(testutil.RecoverPanicString, r)
59+
t.Fail()
60+
}
61+
}()
62+
var ts = testutil.TestSuite{}
63+
testutil.LoadFromFixture(filepath.Join("testdata", "test_suite.yaml"), &ts)
64+
var delegate = testRunnerDelegate{t: t}
65+
var runner = testutil.TestSuiteRunner{TestSuite: &ts, Delegate: &delegate}
66+
runner.RunTests()
67+
}
68+
69+
// testRunnerDelegate implements testutil.TestRunnerDelegate
70+
type testRunnerDelegate struct {
71+
t *testing.T
72+
}
73+
74+
func (d *testRunnerDelegate) ResourceDescriptor() acktypes.AWSResourceDescriptor {
75+
return &resourceDescriptor{}
76+
}
77+
78+
func (d *testRunnerDelegate) ResourceManager(mocksdkapi *mocksvcsdkapi.SageMakerAPI) acktypes.AWSResourceManager {
79+
return provideResourceManagerWithMockSDKAPI(mocksdkapi)
80+
}
81+
82+
func (d *testRunnerDelegate) GoTestRunner() *testing.T {
83+
return d.t
84+
}
85+
86+
func (d *testRunnerDelegate) EmptyServiceAPIOutput(apiName string) (interface{}, error) {
87+
if apiName == "" {
88+
return nil, errors.New("no API name specified")
89+
}
90+
//TODO: use reflection, template to auto generate this block/method.
91+
switch apiName {
92+
case "CreateEndpointWithContext":
93+
var output svcsdk.CreateEndpointOutput
94+
return &output, nil
95+
case "DeleteEndpointWithContext":
96+
var output svcsdk.DeleteEndpointOutput
97+
return &output, nil
98+
case "DescribeEndpointWithContext":
99+
var output svcsdk.DescribeEndpointOutput
100+
return &output, nil
101+
case "UpdateEndpointWithContext":
102+
var output svcsdk.UpdateEndpointOutput
103+
return &output, nil
104+
}
105+
return nil, errors.New(fmt.Sprintf("no matching API name found for: %s", apiName))
106+
}
107+
108+
func (d *testRunnerDelegate) Equal(a acktypes.AWSResource, b acktypes.AWSResource) bool {
109+
ac := a.(*resource)
110+
bc := b.(*resource)
111+
// Ignore LastTransitionTime since it gets updated each run.
112+
opts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(ackv1alpha1.Condition{}, "LastTransitionTime")}
113+
114+
if cmp.Equal(ac.ko.Status, bc.ko.Status, opts...) {
115+
return true
116+
} else {
117+
fmt.Printf("Difference (-expected +actual):\n\n")
118+
fmt.Println(cmp.Diff(ac.ko.Status, bc.ko.Status, opts...))
119+
return false
120+
}
121+
}
122+
123+
// Checks to see if the given yaml file, with name stored as expectation,
124+
// matches the yaml marshal of the AWSResource stored as actual.
125+
func (d *testRunnerDelegate) YamlEqual(expectation string, actual acktypes.AWSResource) bool {
126+
// Build a tmp file for the actual yaml.
127+
actualResource := actual.(*resource)
128+
actualYamlByteArray, _ := yaml.Marshal(actualResource.ko)
129+
return testutil.IsYamlEqual(&expectation, &actualYamlByteArray)
130+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: sagemaker.services.k8s.aws/v1alpha1
2+
kind: Endpoint
3+
metadata:
4+
name: unit_testing_endpoint
5+
spec:
6+
endpointName: !-intentionally-invalid-name
7+
endpointConfigName: unit_testing_endpoint
8+
tags:
9+
- key: confidentiality
10+
value: public
11+
- key: environment
12+
value: testing
13+
- key: customer
14+
value: test-user

0 commit comments

Comments
 (0)