Skip to content

Commit

Permalink
Add execution time in test run response (#612)
Browse files Browse the repository at this point in the history
  • Loading branch information
schoren authored Jun 1, 2022
1 parent 1cf9d86 commit af44643
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 217 deletions.
3 changes: 3 additions & 0 deletions api/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ components:
lastErrorState:
type: string
description: Details of the cause for the last `FAILED` state
exectutionTime:
type: integer
description: time it took for the test to complete, either success or fail. If the test is still running, it will show the time up to the time of the request
createdAt:
type: string
format: date-time
Expand Down
19 changes: 7 additions & 12 deletions server/executor/assertion_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,9 @@ func (e *defaultAssertionRunner) startWorker(ctx context.Context) {
func (e *defaultAssertionRunner) runAssertionsAndUpdateResult(ctx context.Context, request AssertionRequest) error {
run, err := e.executeAssertions(ctx, request)
if err != nil {
run.State = model.RunStateFailed
run.LastError = err
run.CompletedAt = time.Now()
return e.db.UpdateRun(ctx, run)
return e.db.UpdateRun(ctx, run.Failed(err))
}

run.State = model.RunStateFinished
err = e.db.UpdateRun(ctx, run)
if err != nil {
return fmt.Errorf("could not save result on database: %w", err)
Expand All @@ -89,17 +85,16 @@ func (e *defaultAssertionRunner) runAssertionsAndUpdateResult(ctx context.Contex
}

func (e *defaultAssertionRunner) executeAssertions(ctx context.Context, req AssertionRequest) (model.Run, error) {
if req.Run.Trace == nil {
run := req.Run
if run.Trace == nil {
return model.Run{}, fmt.Errorf("trace not available")
}

results, allPassed := assertions.Assert(req.Test.Definition, *req.Run.Trace)
req.Run.Results = &model.RunResults{
AllPassed: allPassed,
Results: results,
}
run = run.SuccessfullyAsserted(
assertions.Assert(req.Test.Definition, *run.Trace),
)

return req.Run, nil
return run, nil
}

func (e *defaultAssertionRunner) RunAssertions(request AssertionRequest) {
Expand Down
16 changes: 4 additions & 12 deletions server/executor/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ func (r persistentRunner) Run(test model.Test) model.Run {
}

func (r persistentRunner) processExecQueue(job execReq) {
run := job.run
run.State = model.RunStateExecuting
run.ServiceTriggeredAt = time.Now()
run := job.run.Start()
r.handleDBError(r.tests.UpdateRun(job.ctx, run))

response, err := r.executor.Execute(job.test, job.run.TraceID, job.run.SpanID)
Expand All @@ -119,25 +117,19 @@ func (r persistentRunner) processExecQueue(job execReq) {
r.handleDBError(r.tests.UpdateTestVersion(job.ctx, job.test))
}

run.ServiceTriggerCompletedAt = time.Now()

r.handleDBError(r.tests.UpdateRun(job.ctx, run))
if run.State == model.RunStateAwaitingTrace {
// start a new context
r.tp.Poll(job.ctx, job.test, run)
}
}

func (r persistentRunner) handleExecutionResult(run model.Run, resp model.HTTPResponse, err error) model.Run {
run.Response = resp
if err != nil {
run.State = model.RunStateFailed
run.LastError = err
run.CompletedAt = time.Now()
} else {
run.State = model.RunStateAwaitingTrace
return run.Failed(err)
}
return run

return run.SuccessfullyExecuted()
}

func (r persistentRunner) newTestRun() model.Run {
Expand Down
12 changes: 2 additions & 10 deletions server/executor/trace_poller.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ func (tp tracePoller) processJob(job tracePollReq) {
return
}

run.State = model.RunStateAwaitingTestResults

trace := traces.FromOtel(otelTrace)
trace.ID = run.TraceID
if !tp.donePollingTraces(job, trace) {
Expand All @@ -138,9 +136,7 @@ func (tp tracePoller) processJob(job tracePollReq) {
return
}

run.Trace = augmentData(&trace, run.Response)
run.State = model.RunStateAwaitingTestResults
run.ObtainedTraceAt = time.Now()
run = run.SuccessfullyPolledTraces(augmentData(&trace, run.Response))

fmt.Printf("completed polling result %s after %d times, number of spans: %d \n", job.run.ID, job.count, len(run.Trace.Flat))

Expand Down Expand Up @@ -213,11 +209,7 @@ func (tp tracePoller) handleTraceDBError(job tracePollReq, err error) {
fmt.Println("other", err)
}

run.State = model.RunStateFailed
run.LastError = err
run.CompletedAt = time.Now()

tp.handleDBError(tp.tests.UpdateRun(job.ctx, run))
tp.handleDBError(tp.tests.UpdateRun(job.ctx, run.Failed(err)))

}

Expand Down
8 changes: 2 additions & 6 deletions server/http/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,16 +215,12 @@ func (c *controller) RerunTestRun(ctx context.Context, testID string, runID stri
return handleDBError(err), err
}

newTestRun := run
newTestRun.Results = nil
newTestRun.TestVersion = test.Version

newTestRun, err = c.testDB.CreateRun(ctx, test, newTestRun)
newTestRun, err := c.testDB.CreateRun(ctx, test, run.Copy(test.Version))
if err != nil {
return openapi.Response(http.StatusUnprocessableEntity, err.Error()), err
}

newTestRun.State = model.RunStateAwaitingTestResults
newTestRun = newTestRun.SuccessfullyPolledTraces(run.Trace)
err = c.testDB.UpdateRun(ctx, newTestRun)
if err != nil {
return openapi.Response(http.StatusInternalServerError, err.Error()), err
Expand Down
1 change: 1 addition & 0 deletions server/http/mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ func (m openapiMapper) Run(in *model.Run) openapi.TestRun {
SpanId: in.SpanID.String(),
State: string(in.State),
LastErrorState: errToString(in.LastError),
ExectutionTime: int32(in.ExecutionTime()),
CreatedAt: in.CreatedAt,
ServiceTriggeredAt: in.ServiceTriggeredAt,
ServiceTriggerCompletedAt: in.ServiceTriggerCompletedAt,
Expand Down
187 changes: 187 additions & 0 deletions server/model/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package model

import (
"encoding/json"
"errors"
"fmt"
"time"

"github.com/google/uuid"
"github.com/kubeshop/tracetest/server/assertions/comparator"
"github.com/kubeshop/tracetest/server/traces"
"go.opentelemetry.io/otel/trace"
)

func (sar SpanAssertionResult) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
SpanID string
ObservedValue string
CompareErr string
}{
SpanID: sar.SpanID.String(),
ObservedValue: sar.ObservedValue,
CompareErr: errToString(sar.CompareErr),
})
}

func (sar *SpanAssertionResult) UnmarshalJSON(data []byte) error {
aux := struct {
SpanID string
ObservedValue string
CompareErr string
}{}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}

sid, err := trace.SpanIDFromHex(aux.SpanID)
if err != nil {
return err
}

sar.SpanID = sid
sar.ObservedValue = aux.ObservedValue
sar.CompareErr = stringToErr(aux.CompareErr)

return nil
}

func (a Assertion) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Attribute string
Comparator string
Value string
}{
Attribute: a.Attribute,
Comparator: a.Comparator.String(),
Value: a.Value,
})
}

func (a *Assertion) UnmarshalJSON(data []byte) error {
aux := struct {
Attribute string
Comparator string
Value string
}{}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}

c, err := comparator.DefaultRegistry().Get(aux.Comparator)
if err != nil {
return err
}

a.Attribute = aux.Attribute
a.Value = aux.Value
a.Comparator = c

return nil
}

func (r *Run) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
ID string
TraceID string
SpanID string
State string
LastErrorString string
CreatedAt time.Time
ServiceTriggeredAt time.Time
ServiceTriggerCompletedAt time.Time
ObtainedTraceAt time.Time
CompletedAt time.Time
Request HTTPRequest
Response HTTPResponse
Trace *traces.Trace
Results *RunResults
}{
ID: r.ID.String(),
TraceID: r.TraceID.String(),
SpanID: r.SpanID.String(),
State: string(r.State),
LastErrorString: errToString(r.LastError),
CreatedAt: r.CreatedAt,
ServiceTriggeredAt: r.ServiceTriggeredAt,
ServiceTriggerCompletedAt: r.ServiceTriggerCompletedAt,
ObtainedTraceAt: r.ObtainedTraceAt,
CompletedAt: r.CompletedAt,
Request: r.Request,
Response: r.Response,
Trace: r.Trace,
Results: r.Results,
})
}

func (r *Run) UnmarshalJSON(data []byte) error {
aux := struct {
ID string
TraceID string
SpanID string
State string
LastErrorString string
CreatedAt time.Time
ServiceTriggeredAt time.Time
ServiceTriggerCompletedAt time.Time
ObtainedTraceAt time.Time
CompletedAt time.Time
TestVersion int
Request HTTPRequest
Response HTTPResponse
Trace *traces.Trace
Results *RunResults
}{}

if err := json.Unmarshal(data, &aux); err != nil {
return fmt.Errorf("unmarshal run: %w", err)
}

id, err := uuid.Parse(aux.ID)
if err != nil {
return fmt.Errorf("unmarshal run: %w", err)
}

tid, err := trace.TraceIDFromHex(aux.TraceID)
if err != nil {
return fmt.Errorf("unmarshal run: %w", err)
}

sid, err := trace.SpanIDFromHex(aux.SpanID)
if err != nil {
return fmt.Errorf("unmarshal run: %w", err)
}

r.ID = id
r.TraceID = tid
r.SpanID = sid
r.State = RunState(aux.State)
r.LastError = stringToErr(aux.LastErrorString)
r.CreatedAt = aux.CreatedAt
r.ServiceTriggeredAt = aux.ServiceTriggeredAt
r.ObtainedTraceAt = aux.ObtainedTraceAt
r.CompletedAt = aux.CompletedAt
r.TestVersion = aux.TestVersion
r.Request = aux.Request
r.Response = aux.Response
r.Trace = aux.Trace
r.Results = aux.Results

return nil
}

func errToString(err error) string {
if err != nil {
return err.Error()
}

return ""
}

func stringToErr(s string) error {
if s != "" {
return errors.New(s)
}

return nil
}
Loading

0 comments on commit af44643

Please sign in to comment.