Skip to content

Commit

Permalink
feat(STONEINTG-523): extend details with start and completion time
Browse files Browse the repository at this point in the history
This commit extends IntegrationTestStatusDetail with:
* StartTime: when testing started
* CompletionTime: when testing stopped

Signed-off-by: Martin Basti <[email protected]>
  • Loading branch information
MartinBasti committed Sep 7, 2023
1 parent acc4b6a commit 5b53aee
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 27 deletions.
80 changes: 57 additions & 23 deletions gitops/snapshot_integration_tests_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ const integrationTestStatusesSchema = `{
},
"details": {
"type": "string"
}
},
"startTime": {
"type": "string"
},
"completionTime": {
"type": "string"
}
},
"required": ["scenario", "status", "lastUpdateTime"]
}
Expand All @@ -62,6 +68,10 @@ type IntegrationTestStatusDetail struct {
LastUpdateTime time.Time `json:"lastUpdateTime"`
// The details of reported status
Details string `json:"details"`
// Startime when we moved to inProgress
StartTime *time.Time `json:"startTime,omitempty"` // pointer to make omitempty work
// Completion time when test failed or passed
CompletionTime *time.Time `json:"completionTime,omitempty"` // pointer to make omitempty work
}

// SnapshotIntegrationTestStatuses type handles details about snapshot tests
Expand All @@ -86,25 +96,49 @@ func (sits *SnapshotIntegrationTestStatuses) ResetDirty() {

// UpdateTestStatusIfChanged updates status of scenario test when status or details changed
func (sits *SnapshotIntegrationTestStatuses) UpdateTestStatusIfChanged(scenarioName string, status IntegrationTestStatus, details string) {
var detail *IntegrationTestStatusDetail
detail, ok := sits.statuses[scenarioName]
timestamp := time.Now().UTC()
if ok {
// update only when status or details changed, otherwise it's a no-op
// to preserve timestamps
if detail.Status != status || detail.Details != details {
detail.Status = status
detail.Details = details
detail.LastUpdateTime = timestamp
sits.dirty = true
}
} else {
if !ok {
newDetail := IntegrationTestStatusDetail{
ScenarioName: scenarioName,
Status: status,
Status: -1, // undefined, must be udpated within function
Details: details,
LastUpdateTime: timestamp,
}
sits.statuses[scenarioName] = &newDetail
detail = &newDetail
sits.statuses[scenarioName] = detail
sits.dirty = true
}

// update only when status or details changed, otherwise it's a no-op
// to preserve timestamps
if detail.Status != status {
detail.Status = status
detail.LastUpdateTime = timestamp
sits.dirty = true

// update start and completion time if needed, only when status changed
switch status {
case IntegrationTestStatusInProgress:
detail.StartTime = &timestamp
// null CompletionTime because testing started again
detail.CompletionTime = nil
case IntegrationTestStatusPending:
// null all timestamps as test is not inProgress neither in final state
detail.StartTime = nil
detail.CompletionTime = nil
case IntegrationTestStatusDeploymentError,
IntegrationTestStatusEnvironmentProvisionError,
IntegrationTestStatusTestFail,
IntegrationTestStatusTestPassed:
detail.CompletionTime = &timestamp
}
}

if detail.Details != details {
detail.Details = details
detail.LastUpdateTime = timestamp
sits.dirty = true
}

Expand Down Expand Up @@ -166,17 +200,17 @@ func (sits *SnapshotIntegrationTestStatuses) GetScenarioStatus(scenarioName stri
// MarshalJSON converts data to JSON
// Please note that internal representation of data differs from marshalled output
// Example:
// [
//
// {
// "scenario": "scenario-1",
// "status": "EnvironmentProvisionError",
// "lastUpdateTime": "2023-07-26T16:57:49+02:00",
// "details": "Failed ..."
// }
//
// ]

// [
// {
// "scenario": "scenario-1",
// "status": "EnvironmentProvisionError",
// "lastUpdateTime": "2023-07-26T16:57:49+02:00",
// "details": "Failed ...",
// "startTime": "2023-07-26T14:57:49+02:00",
// "completionTime": "2023-07-26T16:57:49+02:00"
// }
// ]
func (sits *SnapshotIntegrationTestStatuses) MarshalJSON() ([]byte, error) {
result := sits.GetStatuses()
return json.Marshal(result)
Expand Down
163 changes: 159 additions & 4 deletions gitops/snapshot_integration_tests_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,76 @@ var _ = Describe("Snapshot integration test statuses", func() {
Expect(detail.Status).To(Equal(gitops.IntegrationTestStatusPending))
})

It("Can export valid JSON", func() {
DescribeTable("Test expected additons of startTime",
func(st gitops.IntegrationTestStatus, shouldAdd bool) {
sits.UpdateTestStatusIfChanged(testScenarioName, st, testDetails)
detail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())
if shouldAdd {
Expect(detail.StartTime).NotTo(BeNil())
} else {
Expect(detail.StartTime).To(BeNil())
}
},
Entry("When status is Pending", gitops.IntegrationTestStatusPending, false),
Entry("When status is InProgress", gitops.IntegrationTestStatusInProgress, true),
Entry("When status is EnvironmentProvisionError", gitops.IntegrationTestStatusEnvironmentProvisionError, false),
Entry("When status is DeploymentError", gitops.IntegrationTestStatusDeploymentError, false),
Entry("When status is TestFail", gitops.IntegrationTestStatusTestFail, false),
Entry("When status is TestPass", gitops.IntegrationTestStatusTestPassed, false),
)

DescribeTable("Test expected additons of completionTime",
func(st gitops.IntegrationTestStatus, shouldAdd bool) {
sits.UpdateTestStatusIfChanged(testScenarioName, st, testDetails)
detail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())
if shouldAdd {
Expect(detail.CompletionTime).NotTo(BeNil())
} else {
Expect(detail.CompletionTime).To(BeNil())
}
},
Entry("When status is Pending", gitops.IntegrationTestStatusPending, false),
Entry("When status is InProgress", gitops.IntegrationTestStatusInProgress, false),
Entry("When status is EnvironmentProvisionError", gitops.IntegrationTestStatusEnvironmentProvisionError, true),
Entry("When status is DeploymentError", gitops.IntegrationTestStatusDeploymentError, true),
Entry("When status is TestFail", gitops.IntegrationTestStatusTestFail, true),
Entry("When status is TestPass", gitops.IntegrationTestStatusTestPassed, true),
)

It("Change back to InProgress updates timestamps accordingly", func() {
sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusTestPassed, testDetails)
originalDetail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())
Expect(originalDetail.CompletionTime).ToNot(BeNil())
originalStartTime := originalDetail.StartTime // copy time, it's all in pointers

sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusInProgress, testDetails)
newDetail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())
Expect(originalDetail.CompletionTime).To(BeNil())
Expect(newDetail.StartTime).NotTo(Equal(originalStartTime))
})

It("not changing status keeps starting time the same", func() {
newDetails := "something important"
sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusInProgress, testDetails)
originalDetail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())
originalStartTime := originalDetail.StartTime // copy time, it's all in pointers
originalLastUpdateTime := originalDetail.LastUpdateTime

sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusInProgress, newDetails)
newDetail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())
Expect(newDetail.StartTime).To(Equal(originalStartTime))
// but details and lastUpdateTimestamp must changed
Expect(newDetail.Details).To(Equal(newDetails))
Expect(newDetail.LastUpdateTime).NotTo(Equal(originalLastUpdateTime))
})

It("Can export valid JSON without start and completion time (Pending)", func() {
sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusPending, testDetails)
detail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())
Expand All @@ -172,6 +241,85 @@ var _ = Describe("Snapshot integration test statuses", func() {
Expect(json.Marshal(sits)).To(MatchJSON(expected))
})

It("Can export valid JSON with start time (InProgress)", func() {
sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusInProgress, testDetails)

detail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())

expectedFormatStr := `[
{
"scenario": "%s",
"status": "InProgress",
"lastUpdateTime": "%s",
"details": "%s",
"startTime": "%s"
}
]`
marshaledTime, err := detail.LastUpdateTime.MarshalText()
Expect(err).To(BeNil())
marshaledStartTime, err := detail.StartTime.MarshalText()
Expect(err).To(BeNil())
expectedStr := fmt.Sprintf(expectedFormatStr, testScenarioName, marshaledTime, testDetails, marshaledStartTime)
expected := []byte(expectedStr)

Expect(json.Marshal(sits)).To(MatchJSON(expected))
})

It("Can export valid JSON with completion time (TestFailed)", func() {
sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusTestFail, testDetails)

detail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())

expectedFormatStr := `[
{
"scenario": "%s",
"status": "TestFail",
"lastUpdateTime": "%s",
"details": "%s",
"completionTime": "%s"
}
]`
marshaledTime, err := detail.LastUpdateTime.MarshalText()
Expect(err).To(BeNil())
marshaledCompletionTime, err := detail.CompletionTime.MarshalText()
Expect(err).To(BeNil())
expectedStr := fmt.Sprintf(expectedFormatStr, testScenarioName, marshaledTime, testDetails, marshaledCompletionTime)
expected := []byte(expectedStr)

Expect(json.Marshal(sits)).To(MatchJSON(expected))
})

It("Can export valid JSON with startTime and completion time", func() {
sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusInProgress, "yolo")
sits.UpdateTestStatusIfChanged(testScenarioName, gitops.IntegrationTestStatusTestFail, testDetails)

detail, ok := sits.GetScenarioStatus(testScenarioName)
Expect(ok).To(BeTrue())

expectedFormatStr := `[
{
"scenario": "%s",
"status": "TestFail",
"lastUpdateTime": "%s",
"details": "%s",
"startTime": "%s",
"completionTime": "%s"
}
]`
marshaledTime, err := detail.LastUpdateTime.MarshalText()
Expect(err).To(BeNil())
marshaledStartTime, err := detail.StartTime.MarshalText()
Expect(err).To(BeNil())
marshaledCompletionTime, err := detail.CompletionTime.MarshalText()
Expect(err).To(BeNil())
expectedStr := fmt.Sprintf(expectedFormatStr, testScenarioName, marshaledTime, testDetails, marshaledStartTime, marshaledCompletionTime)
expected := []byte(expectedStr)

Expect(json.Marshal(sits)).To(MatchJSON(expected))
})

When("Contains updates to status", func() {

BeforeEach(func() {
Expand Down Expand Up @@ -373,11 +521,18 @@ var _ = Describe("Snapshot integration test statuses", func() {

// fetch updated snapshot
Eventually(func() error {
err := k8sClient.Get(ctx, types.NamespacedName{
if err := k8sClient.Get(ctx, types.NamespacedName{
Name: snapshot.Name,
Namespace: namespace,
}, snapshot)
return err
}, snapshot); err != nil {
return err
}
// race condition, sometimes it fetched old object
annotations := snapshot.GetAnnotations()
if _, ok := annotations[gitops.SnapshotTestsStatusAnnotation]; ok != true {
return fmt.Errorf("Snapshot doesn't contain the expected annotation")
}
return nil
}, time.Second*10).ShouldNot(HaveOccurred())

statuses, err := gitops.NewSnapshotIntegrationTestStatusesFromSnapshot(snapshot)
Expand Down

0 comments on commit 5b53aee

Please sign in to comment.