Skip to content

Commit

Permalink
feat: add wait for results flag in test run cmd (#652)
Browse files Browse the repository at this point in the history
* add wait for results flag in test run cmd

* fix tests

* apply nit
  • Loading branch information
mathnogueira authored Jun 2, 2022
1 parent baf8187 commit 4f6dff7
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 26 deletions.
102 changes: 82 additions & 20 deletions cli/actions/run_test_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"sync"
"time"

"github.com/kubeshop/tracetest/cli/config"
"github.com/kubeshop/tracetest/cli/conversion"
Expand All @@ -15,6 +17,7 @@ import (

type RunTestConfig struct {
DefinitionFile string
WaitForResult bool
}

type runTestAction struct {
Expand All @@ -26,9 +29,9 @@ type runTestAction struct {
var _ Action[RunTestConfig] = &runTestAction{}

type runTestOutput struct {
TestID string `json:"testId"`
RunID string `json:"testRunId"`
RunWebURL string `json:"testRunWebUrl"`
Test openapi.Test `json:"test"`
Run openapi.TestRun `json:"testRun"`
RunWebURL string `json:"testRunWebUrl"`
}

func NewRunTestAction(config config.Config, logger *zap.Logger, client *openapi.APIClient) runTestAction {
Expand All @@ -41,7 +44,7 @@ func (a runTestAction) Run(ctx context.Context, args RunTestConfig) error {
}

a.logger.Debug("Running test from definition", zap.String("definitionFile", args.DefinitionFile))
output, err := a.runDefinition(ctx, args.DefinitionFile)
output, err := a.runDefinition(ctx, args.DefinitionFile, args.WaitForResult)

if err != nil {
return fmt.Errorf("could not run definition: %w", err)
Expand All @@ -56,7 +59,7 @@ func (a runTestAction) Run(ctx context.Context, args RunTestConfig) error {
return nil
}

func (a runTestAction) runDefinition(ctx context.Context, definitionFile string) (runTestOutput, error) {
func (a runTestAction) runDefinition(ctx context.Context, definitionFile string, waitForResult bool) (runTestOutput, error) {
definition, err := file.LoadDefinition(definitionFile)
if err != nil {
return runTestOutput{}, err
Expand All @@ -67,21 +70,23 @@ func (a runTestAction) runDefinition(ctx context.Context, definitionFile string)
return runTestOutput{}, fmt.Errorf("invalid definition file: %w", err)
}

var test openapi.Test

if definition.Id == "" {
a.logger.Debug("test doesn't exist. Creating it")
testID, err := a.createTestFromDefinition(ctx, definition)
test, err = a.createTestFromDefinition(ctx, definition)
if err != nil {
return runTestOutput{}, fmt.Errorf("could not create test from definition: %w", err)
}

definition.Id = testID
definition.Id = *test.Id
err = file.SaveDefinition(definitionFile, definition)
if err != nil {
return runTestOutput{}, fmt.Errorf("could not save definition: %w", err)
}
} else {
a.logger.Debug("test exists. Updating it")
err = a.updateTestFromDefinition(ctx, definition)
test, err = a.updateTestFromDefinition(ctx, definition)
if err != nil {
return runTestOutput{}, fmt.Errorf("could not update test using definition: %w", err)
}
Expand All @@ -92,57 +97,66 @@ func (a runTestAction) runDefinition(ctx context.Context, definitionFile string)
return runTestOutput{}, fmt.Errorf("could not run test: %w", err)
}

if waitForResult {
updatedTestRun, err := a.waitForResult(ctx, definition.Id, *testRun.Id)
if err != nil {
return runTestOutput{}, fmt.Errorf("could not wait for result: %w", err)
}

testRun = updatedTestRun
}

return runTestOutput{
TestID: definition.Id,
RunID: *testRun.Id,
Test: test,
Run: testRun,
RunWebURL: fmt.Sprintf("%s://%s/test/%s/run/%s", a.config.Scheme, a.config.Endpoint, definition.Id, *testRun.Id),
}, nil
}

func (a runTestAction) createTestFromDefinition(ctx context.Context, definition definition.Test) (string, error) {
func (a runTestAction) createTestFromDefinition(ctx context.Context, definition definition.Test) (openapi.Test, error) {
openapiTest, err := conversion.ConvertTestDefinitionIntoOpenAPIObject(definition)
if err != nil {
return "", err
return openapi.Test{}, err
}

req := a.client.ApiApi.CreateTest(ctx)
req = req.Test(openapiTest)

testBytes, err := json.Marshal(openapiTest)
if err != nil {
return "", fmt.Errorf("could not marshal test: %w", err)
return openapi.Test{}, fmt.Errorf("could not marshal test: %w", err)
}

a.logger.Debug("Sending request to create test", zap.ByteString("test", testBytes))
createdTest, _, err := a.client.ApiApi.CreateTestExecute(req)
if err != nil {
return "", fmt.Errorf("could not execute request: %w", err)
return openapi.Test{}, fmt.Errorf("could not execute request: %w", err)
}

return *createdTest.Id, nil
return *createdTest, nil
}

func (a runTestAction) updateTestFromDefinition(ctx context.Context, definition definition.Test) error {
func (a runTestAction) updateTestFromDefinition(ctx context.Context, definition definition.Test) (openapi.Test, error) {
openapiTest, err := conversion.ConvertTestDefinitionIntoOpenAPIObject(definition)
if err != nil {
return err
return openapi.Test{}, err
}

req := a.client.ApiApi.UpdateTest(ctx, definition.Id)
req = req.Test(openapiTest)

testBytes, err := json.Marshal(openapiTest)
if err != nil {
return fmt.Errorf("could not marshal test: %w", err)
return openapi.Test{}, fmt.Errorf("could not marshal test: %w", err)
}

a.logger.Debug("Sending request to update test", zap.ByteString("test", testBytes))
_, err = a.client.ApiApi.UpdateTestExecute(req)
if err != nil {
return fmt.Errorf("could not execute request: %w", err)
return openapi.Test{}, fmt.Errorf("could not execute request: %w", err)
}

return nil
return openapiTest, nil
}

func (a runTestAction) runTest(ctx context.Context, testID string) (openapi.TestRun, error) {
Expand All @@ -154,3 +168,51 @@ func (a runTestAction) runTest(ctx context.Context, testID string) (openapi.Test

return *run, nil
}

func (a runTestAction) waitForResult(ctx context.Context, testId string, testRunId string) (openapi.TestRun, error) {
var testRun openapi.TestRun
var lastError error
var wg sync.WaitGroup
wg.Add(1)
ticker := time.NewTicker(1 * time.Second) // TODO: make this configurable
go func() {
for {
select {
case <-ticker.C:
readyTestRun, err := a.isTestReady(ctx, testId, testRunId)
if err != nil {
lastError = err
wg.Done()
return
}

if readyTestRun != nil {
testRun = *readyTestRun
wg.Done()
return
}
}
}
}()
wg.Wait()

if lastError != nil {
return openapi.TestRun{}, lastError
}

return testRun, nil
}

func (a runTestAction) isTestReady(ctx context.Context, testId string, testRunId string) (*openapi.TestRun, error) {
req := a.client.ApiApi.GetTestRun(ctx, testId, testRunId)
run, _, err := a.client.ApiApi.GetTestRunExecute(req)
if err != nil {
return &openapi.TestRun{}, fmt.Errorf("could not execute GetTestRun request: %w", err)
}

if *run.State == "FAILED" || *run.State == "FINISHED" {
return run, nil
}

return nil, nil
}
3 changes: 3 additions & 0 deletions cli/cmd/test_run_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

var runTestFileDefinition string
var runTestWaitForResult bool

var testRunCmd = &cobra.Command{
Use: "run",
Expand All @@ -22,6 +23,7 @@ var testRunCmd = &cobra.Command{
runTestAction := actions.NewRunTestAction(cliConfig, cliLogger, client)
actionArgs := actions.RunTestConfig{
DefinitionFile: runTestFileDefinition,
WaitForResult: runTestWaitForResult,
}

err := runTestAction.Run(ctx, actionArgs)
Expand All @@ -35,5 +37,6 @@ var testRunCmd = &cobra.Command{

func init() {
testRunCmd.PersistentFlags().StringVarP(&runTestFileDefinition, "definition", "d", "", "--definition <definition-file.yml>")
testRunCmd.PersistentFlags().BoolVarP(&runTestWaitForResult, "wait-for-result", "w", false, "")
testCmd.AddCommand(testRunCmd)
}
13 changes: 7 additions & 6 deletions cli/cmd/test_run_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import (

"github.com/kubeshop/tracetest/cli/cmd/e2e"
"github.com/kubeshop/tracetest/cli/file"
"github.com/kubeshop/tracetest/cli/openapi"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type cliOutput struct {
TestId string `json:"testId"`
TestRunId string `json:"testRunId"`
Test openapi.Test `json:"test"`
TestRun openapi.TestRun `json:"testRun"`
}

func TestRunTestCmd(t *testing.T) {
Expand All @@ -41,9 +42,9 @@ func TestRunTestCmd(t *testing.T) {
err = json.Unmarshal([]byte(output), &outputObject)
require.NoError(t, err)

assert.NotEmpty(t, outputObject.TestId)
assert.NotEmpty(t, outputObject.TestRunId)
assert.Equal(t, outputObject.TestId, definition.Id)
assert.NotEmpty(t, outputObject.Test.Id)
assert.NotEmpty(t, outputObject.TestRun.Id)
assert.Equal(t, *outputObject.Test.Id, definition.Id)
}

func TestRunTestCmdWhenEditingTest(t *testing.T) {
Expand Down Expand Up @@ -74,7 +75,7 @@ func TestRunTestCmdWhenEditingTest(t *testing.T) {
require.NoError(t, err)

// Assert a new test wasn't created
assert.Equal(t, outputObject.TestId, updateOutputObject.TestId)
assert.Equal(t, *outputObject.Test.Id, *updateOutputObject.Test.Id)
}

func copyFile(source string, destination string) error {
Expand Down
1 change: 1 addition & 0 deletions cli/conversion/definition_openapi_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func ConvertTestDefinitionIntoOpenAPIObject(definition definition.Test) (openapi
return openapi.Test{}, fmt.Errorf("could not convert test definition: %w", err)
}
return openapi.Test{
Id: ConvertStringIntoOpenAPIString(definition.Id),
Name: ConvertStringIntoOpenAPIString(definition.Name),
Description: ConvertStringIntoOpenAPIString(definition.Description),
ServiceUnderTest: &openapi.TestServiceUnderTest{
Expand Down

0 comments on commit 4f6dff7

Please sign in to comment.