-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[test] Improve test coverage and performance in apimachinery_status_conditions #172
Merged
art-tapin
merged 7 commits into
main
from
issue167/unit-tests-for-apimachinery_status_conditions
Apr 28, 2023
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
758b5a2
test: Add unit tests for CopyConditions function (#167)
art-tapin 4f65e53
refactor: change the test name according to function name (#167)
art-tapin f9a0e0c
test: Add unit tests for ConditionMarshal function (#167)
art-tapin 639dd1b
test: Refactor test namings (#167)
art-tapin 64b34c7
refactor: optimize CopyConditions and reuse it in ConditionMarshal
art-tapin 9362e55
refactor: fix test-case according to optimisation in ConditionMarshal…
art-tapin 379dcdc
refactor: run goimports && gofmt && refactoring according to lint
art-tapin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
//go:build unit | ||
|
||
package common | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
goCmp "github.com/google/go-cmp/cmp" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
func TestCopyConditions(t *testing.T) { | ||
now := metav1.Now() | ||
testCases := []struct { | ||
name string | ||
expectedConditions []metav1.Condition | ||
}{ | ||
{ | ||
name: "when multiple conditions then return copy", | ||
expectedConditions: []metav1.Condition{ | ||
{ | ||
Type: "Installed", | ||
Status: metav1.ConditionTrue, | ||
ObservedGeneration: 1, | ||
LastTransitionTime: now, | ||
Reason: "InstallSuccessful", | ||
Message: "Object successfully installed", | ||
}, | ||
{ | ||
Type: "Reconciled", | ||
Status: metav1.ConditionFalse, | ||
ObservedGeneration: 0, | ||
LastTransitionTime: now, | ||
Reason: "ReconcileError", | ||
Message: "Object failed to reconcile due to an error", | ||
}, | ||
{ | ||
Type: "Ready", | ||
Status: metav1.ConditionFalse, | ||
ObservedGeneration: 2, | ||
LastTransitionTime: now, | ||
Reason: "ComponentsNotReady", | ||
Message: "Object components are not ready", | ||
}, | ||
{ | ||
Type: "Validated", | ||
Status: metav1.ConditionUnknown, | ||
ObservedGeneration: 0, | ||
LastTransitionTime: now, | ||
Reason: "ValidationError", | ||
Message: "Object validation failed", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "when one condition then return copy", | ||
expectedConditions: []metav1.Condition{ | ||
{ | ||
Type: "Validated", | ||
Status: metav1.ConditionUnknown, | ||
ObservedGeneration: 0, | ||
LastTransitionTime: now, | ||
Reason: "ValidationError", | ||
Message: "Object validation failed", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "when empty conditions slice return empty slice", | ||
expectedConditions: []metav1.Condition{}, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(subT *testing.T) { | ||
actualConditions := CopyConditions(tc.expectedConditions) | ||
if diff := goCmp.Diff(tc.expectedConditions, actualConditions); diff != "" { | ||
subT.Errorf("Conditions mismatch (-want +got):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestCopyConditions_OriginalConditionsNotModified(t *testing.T) { | ||
now := metav1.Now() | ||
expectedConditions := []metav1.Condition{ | ||
{ | ||
Type: "Installed", | ||
Status: metav1.ConditionTrue, | ||
ObservedGeneration: 1, | ||
LastTransitionTime: now, | ||
Reason: "InstallSuccessful", | ||
Message: "Object successfully installed", | ||
}, | ||
{ | ||
Type: "Reconciled", | ||
Status: metav1.ConditionFalse, | ||
ObservedGeneration: 0, | ||
LastTransitionTime: now, | ||
Reason: "ReconcileError", | ||
Message: "Object failed to reconcile due to an error", | ||
}, | ||
} | ||
|
||
originalConditions := append([]metav1.Condition{}, expectedConditions...) | ||
actualConditions := CopyConditions(expectedConditions) | ||
|
||
// Check that the copied conditions are equal to the original conditions | ||
if diff := goCmp.Diff(expectedConditions, actualConditions); diff != "" { | ||
t.Errorf("Conditions mismatch (-want +got):\n%s", diff) | ||
} | ||
|
||
// Check that the original conditions were not modified | ||
if diff := goCmp.Diff(originalConditions, expectedConditions); diff != "" { | ||
t.Errorf("Original conditions were modified (-want +got):\n%s", diff) | ||
} | ||
} | ||
|
||
func TestConditionMarshal(t *testing.T) { | ||
now := metav1.Now() | ||
nowUTC := now.UTC().Format(time.RFC3339) | ||
|
||
testCases := []struct { | ||
name string | ||
conditions []metav1.Condition | ||
expectedJSONOutput string | ||
expectedError error | ||
}{ | ||
{ | ||
name: "when empty conditions slice then return empty slice", | ||
conditions: []metav1.Condition{}, | ||
expectedJSONOutput: "[]", | ||
expectedError: nil, | ||
}, | ||
{ | ||
name: "when single condition then return JSON", | ||
conditions: []metav1.Condition{ | ||
{ | ||
Type: "Installed", | ||
Status: metav1.ConditionTrue, | ||
ObservedGeneration: 1, | ||
LastTransitionTime: now, | ||
Reason: "InstallSuccessful", | ||
Message: "Object successfully installed", | ||
}, | ||
}, | ||
expectedJSONOutput: `[{"type":"Installed","status":"True","observedGeneration":1,"lastTransitionTime":"` + nowUTC + `","reason":"InstallSuccessful","message":"Object successfully installed"}]`, | ||
expectedError: nil, | ||
}, | ||
{ | ||
name: "when multiple conditions then return JSON", | ||
conditions: []metav1.Condition{ | ||
{ | ||
Type: "Ready", | ||
Status: metav1.ConditionFalse, | ||
ObservedGeneration: 2, | ||
LastTransitionTime: now, | ||
Reason: "ComponentsNotReady", | ||
Message: "Object components are not ready", | ||
}, | ||
{ | ||
Type: "Installed", | ||
Status: metav1.ConditionTrue, | ||
ObservedGeneration: 1, | ||
LastTransitionTime: now, | ||
Reason: "InstallSuccessful", | ||
Message: "Object successfully installed", | ||
}, | ||
{ | ||
Type: "Validated", | ||
Status: metav1.ConditionUnknown, | ||
ObservedGeneration: 3, | ||
LastTransitionTime: now, | ||
Reason: "ValidationError", | ||
Message: "Object validation failed", | ||
}, | ||
}, | ||
expectedJSONOutput: `[{"type":"Installed","status":"True","observedGeneration":1,"lastTransitionTime":"` + nowUTC + `","reason":"InstallSuccessful","message":"Object successfully installed"},{"type":"Ready","status":"False","observedGeneration":2,"lastTransitionTime":"` + nowUTC + `","reason":"ComponentsNotReady","message":"Object components are not ready"},{"type":"Validated","status":"Unknown","observedGeneration":3,"lastTransitionTime":"` + nowUTC + `","reason":"ValidationError","message":"Object validation failed"}]`, | ||
expectedError: nil, | ||
}, | ||
{ | ||
name: "when empty ObservedGeneration field (EQ 0) then it is omitted in JSON", | ||
conditions: []metav1.Condition{ | ||
{ | ||
Type: "Installed", | ||
Status: metav1.ConditionTrue, | ||
ObservedGeneration: 0, | ||
LastTransitionTime: now, | ||
Reason: "InstallSuccessful", | ||
Message: "Object successfully installed", | ||
}, | ||
{ | ||
Type: "Validated", | ||
Status: metav1.ConditionUnknown, | ||
ObservedGeneration: 0, | ||
LastTransitionTime: now, | ||
Reason: "ValidationError", | ||
Message: "Object validation failed", | ||
}, | ||
}, | ||
expectedJSONOutput: `[{"type":"Installed","status":"True","lastTransitionTime":"` + nowUTC + `","reason":"InstallSuccessful","message":"Object successfully installed"},{"type":"Validated","status":"Unknown","lastTransitionTime":"` + nowUTC + `","reason":"ValidationError","message":"Object validation failed"}]`, | ||
expectedError: nil, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(subT *testing.T) { | ||
actualJSONOutput, actualError := ConditionMarshal(tc.conditions) | ||
|
||
diff := goCmp.Diff(tc.expectedJSONOutput, string(actualJSONOutput)) | ||
if diff != "" { | ||
subT.Errorf("Unexpected JSON output (-want +got):\n%s", diff) | ||
} | ||
|
||
diff = goCmp.Diff(tc.expectedError, actualError) | ||
if diff != "" { | ||
subT.Errorf("Unexpected error (-want +got):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what? Order does not keep consistent?? First Ready, then Installed and lastly Validated. And for the serialized string: Installed, Ready, Validated. Weird.
HAve you run the test multiple times? if order is not maintained, on multiple runs, some of them could fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the concern! Conditions are sorted based on their
Condition.Type
field in theConditionMarshal
function before they're serialized. So, the order is consistent and not random. To answer your question, running the test multiple times should not result in any failures due to the consistent ordering provided by the sorting mechanism. Heer is ConditionMarhsal implementation, pay attention to the sorting part:Let me know if you have any other questions! 👼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦 Sorry for that. Once again I took some assumptions that are wrong.
Looking good