diff --git a/pkg/launcher/jvmLauncher.go b/pkg/launcher/jvmLauncher.go index d8295d48..709abd29 100644 --- a/pkg/launcher/jvmLauncher.go +++ b/pkg/launcher/jvmLauncher.go @@ -12,6 +12,8 @@ import ( "strconv" "strings" + "github.com/google/uuid" + "github.com/galasa-dev/cli/pkg/api" "github.com/galasa-dev/cli/pkg/embedded" galasaErrors "github.com/galasa-dev/cli/pkg/errors" @@ -301,6 +303,7 @@ func (launcher *JvmLauncher) SubmitTestRun( localTest.testRun.SetTrace(isTraceEnabled) localTest.testRun.SetType(requestType) localTest.testRun.SetName(localTest.runId) + localTest.testRun.SetSubmissionId(uuid.New().String()) // The test run we started can be returned to the submitter. testRuns.Runs = append(testRuns.Runs, *localTest.testRun) @@ -527,6 +530,15 @@ func (launcher *JvmLauncher) GetRunsById(runId string) (*galasaapi.Run, error) { return run, err } +// Gets a run based on the submission ID of that run. +// For local runs, the submission ID is the same as the test run id. +func (launcher *JvmLauncher) GetRunsBySubmissionId(submissionId string, groupId string) (*galasaapi.Run, error) { + log.Printf("JvmLauncher: GetRunsBySubmissionId entered. runId=%s", submissionId) + + log.Printf("JvmLauncher: Local runs cannot find tests based on submission ID") + return nil, nil +} + func createRunFromLocalTest(localTest *LocalTest) (*galasaapi.Run, error) { var run = galasaapi.NewRun() diff --git a/pkg/launcher/launcher.go b/pkg/launcher/launcher.go index 73de1ce5..1edef366 100644 --- a/pkg/launcher/launcher.go +++ b/pkg/launcher/launcher.go @@ -35,6 +35,9 @@ type Launcher interface { // GetRunsById gets the Run information for the run with a specific run identifier GetRunsById(runId string) (*galasaapi.Run, error) + // Gets a run based on the submission ID of that run. + GetRunsBySubmissionId(submissionId string, groupId string) (*galasaapi.Run, error) + // GetStreams gets a list of streams available on this launcher GetStreams() ([]string, error) diff --git a/pkg/launcher/launcherMock.go b/pkg/launcher/launcherMock.go index fd4f8c90..bb597967 100644 --- a/pkg/launcher/launcherMock.go +++ b/pkg/launcher/launcherMock.go @@ -7,6 +7,7 @@ package launcher import ( "fmt" + "strconv" "strings" "github.com/galasa-dev/cli/pkg/galasaapi" @@ -28,9 +29,10 @@ type LaunchParameters struct { } type MockLauncher struct { - allTestRuns *galasaapi.TestRuns - nextRunId int - launches []LaunchParameters + allTestRuns *galasaapi.TestRuns + nextRunId int + launches []LaunchParameters + submissionId int } func NewMockLauncher() *MockLauncher { @@ -38,6 +40,7 @@ func NewMockLauncher() *MockLauncher { launcher.allTestRuns = newEmptyTestRun() launcher.allTestRuns.Runs = make([]galasaapi.TestRun, 0) launcher.nextRunId = 100 + launcher.submissionId = 0 return launcher } @@ -71,7 +74,7 @@ func (launcher *MockLauncher) SubmitTestRun( stream string, obrFromPortfolio string, isTraceEnabled bool, - GherkinURL string, + GherkinURL string, GherkinFeature string, overrides map[string]interface{}, ) (*galasaapi.TestRuns, error) { @@ -93,6 +96,7 @@ func (launcher *MockLauncher) SubmitTestRun( newTestRun := galasaapi.NewTestRun() newTestRun.SetGroup(groupName) + newTestRun.SetSubmissionId(strconv.Itoa(launcher.submissionId)) classNameParts := strings.Split(className, "/") bundleName := classNameParts[0] @@ -123,6 +127,11 @@ func (launcher *MockLauncher) GetRunsById(runId string) (*galasaapi.Run, error) return &galasaapi.Run{}, nil } +// Gets a run based on the submission ID of that run. +func (launcher *MockLauncher) GetRunsBySubmissionId(submissionId string, groupId string) (*galasaapi.Run, error) { + return &galasaapi.Run{}, nil +} + // GetStreams gets a list of streams available on this launcher func (launcher *MockLauncher) GetStreams() ([]string, error) { return make([]string, 0), nil diff --git a/pkg/launcher/remoteLauncher.go b/pkg/launcher/remoteLauncher.go index 5c9418f4..c5f8cab5 100644 --- a/pkg/launcher/remoteLauncher.go +++ b/pkg/launcher/remoteLauncher.go @@ -11,6 +11,7 @@ import ( "io" "log" "net/http" + "strconv" "strings" "github.com/galasa-dev/cli/pkg/api" @@ -36,7 +37,7 @@ func NewRemoteLauncher(commsClient api.APICommsClient) *RemoteLauncher { // A comms client that communicates with the API server in a Galasa service. launcher.commsClient = commsClient - + return launcher } @@ -125,6 +126,60 @@ func (launcher *RemoteLauncher) GetRunsById(runId string) (*galasaapi.Run, error return rasRun, err } +// Gets the latest run based on the submission ID of that run. +// For local runs, the submission ID is the same as the test run id. +func (launcher *RemoteLauncher) GetRunsBySubmissionId(submissionId string, groupId string) (*galasaapi.Run, error) { + log.Printf("RemoteLauncher: GetRunsBySubmissionId entered. runId=%v groupId=%v", submissionId, groupId) + var err error + var rasRun *galasaapi.Run + var restApiVersion string + + restApiVersion, err = embedded.GetGalasactlRestApiVersion() + + if err == nil { + var runData *galasaapi.RunResults + + err = launcher.commsClient.RunAuthenticatedCommandWithRateLimitRetries(func(apiClient *galasaapi.APIClient) error { + var err error + var httpResponse *http.Response + var context context.Context = nil + + apicall := apiClient.ResultArchiveStoreAPIApi.GetRasSearchRuns(context).ClientApiVersion(restApiVersion). + IncludeCursor("true"). + SubmissionId(submissionId).Group(groupId).Sort("from:desc") + + runData, httpResponse, err = apicall.Execute() + + var statusCode int + if httpResponse != nil { + defer httpResponse.Body.Close() + statusCode = httpResponse.StatusCode + } + + if err != nil { + err = galasaErrors.NewGalasaErrorWithHttpStatusCode(statusCode, galasaErrors.GALASA_ERROR_QUERY_RUNS_FAILED, err.Error()) + } else { + if statusCode != http.StatusOK { + httpError := "\nhttp response status code: " + strconv.Itoa(statusCode) + errString := httpError + err = galasaErrors.NewGalasaErrorWithHttpStatusCode(statusCode, galasaErrors.GALASA_ERROR_QUERY_RUNS_FAILED, errString) + } else { + + log.Printf("HTTP status was OK") + + if runData.GetAmountOfRuns() > 0 { + runs := runData.GetRuns() + rasRun = &runs[0] + } + + } + } + return err + }) + } + return rasRun, err +} + func (launcher *RemoteLauncher) GetStreams() ([]string, error) { var streams []string @@ -179,14 +234,14 @@ func (launcher *RemoteLauncher) GetTestCatalog(stream string) (TestCatalog, erro if err == nil { var cpsResponse *http.Response err = launcher.commsClient.RunAuthenticatedCommandWithRateLimitRetries(func(apiClient *galasaapi.APIClient) error { - cpsProperty, cpsResponse, err = apiClient.ConfigurationPropertyStoreAPIApi.QueryCpsNamespaceProperties(context.TODO(), "framework").Prefix("test.stream."+stream).Suffix("location").ClientApiVersion(restApiVersion).Execute() - + cpsProperty, cpsResponse, err = apiClient.ConfigurationPropertyStoreAPIApi.QueryCpsNamespaceProperties(context.TODO(), "framework").Prefix("test.stream." + stream).Suffix("location").ClientApiVersion(restApiVersion).Execute() + var statusCode int if cpsResponse != nil { defer cpsResponse.Body.Close() statusCode = cpsResponse.StatusCode } - + if err != nil { err = galasaErrors.NewGalasaErrorWithHttpStatusCode(statusCode, galasaErrors.GALASA_ERROR_PROPERTY_GET_FAILED, stream, err) } else if len(cpsProperty) < 1 { @@ -196,7 +251,7 @@ func (launcher *RemoteLauncher) GetTestCatalog(stream string) (TestCatalog, erro }) if err == nil { - streamLocation :=cpsProperty[0].Data.Value + streamLocation := cpsProperty[0].Data.Value catalogString := new(strings.Builder) var resp *http.Response resp, err = http.Get(*streamLocation) diff --git a/pkg/launcher/remoteLauncher_test.go b/pkg/launcher/remoteLauncher_test.go index 1ca56dc2..354778b8 100644 --- a/pkg/launcher/remoteLauncher_test.go +++ b/pkg/launcher/remoteLauncher_test.go @@ -124,7 +124,6 @@ func TestGetTestCatalogHttpErrorGetsReported(t *testing.T) { })) defer server.Close() - mockFactory := utils.NewMockFactory() apiServerUrl := server.URL apiClient := api.InitialiseAPI(apiServerUrl) @@ -134,11 +133,11 @@ func TestGetTestCatalogHttpErrorGetsReported(t *testing.T) { mockFileSystem := mockFactory.GetFileSystem() mockEnvironment := mockFactory.GetEnvironment() mockGalasaHome, _ := utils.NewGalasaHome(mockFileSystem, mockEnvironment, "") - mockFileSystem.WriteTextFile(mockGalasaHome.GetUrlFolderPath() + "/bootstrap.properties", "") + mockFileSystem.WriteTextFile(mockGalasaHome.GetUrlFolderPath()+"/bootstrap.properties", "") bootstrap := "" - maxAttempts := 3 - retryBackoffSeconds := 1 + maxAttempts := 3 + retryBackoffSeconds := 1 commsClient, _ := api.NewAPICommsClient(bootstrap, maxAttempts, float64(retryBackoffSeconds), mockFactory, mockGalasaHome) @@ -153,56 +152,56 @@ func TestGetTestCatalogHttpErrorGetsReported(t *testing.T) { func TestGetRunsByGroupWithInvalidBearerTokenGetsNewTokenOk(t *testing.T) { groupId := "group1" - initialLoginOperation := utils.NewHttpInteraction("/auth/tokens", http.MethodPost) - initialLoginOperation.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { - writer.Header().Set("Content-Type", "application/json") - writer.WriteHeader(http.StatusCreated) + initialLoginOperation := utils.NewHttpInteraction("/auth/tokens", http.MethodPost) + initialLoginOperation.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusCreated) mockResponse := fmt.Sprintf(`{"jwt": "%s", "refresh_token": "abc"}`, mockExpiredJwt) - writer.Write([]byte(mockResponse)) - } + writer.Write([]byte(mockResponse)) + } - unauthorizedGetRunsInteraction := utils.NewHttpInteraction("/runs/" + groupId, http.MethodGet) - unauthorizedGetRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { - writer.Header().Set("Content-Type", "application/json") - writer.WriteHeader(http.StatusUnauthorized) - writer.Write([]byte(`{ "error_message": "Invalid bearer token provided!" }`)) - } + unauthorizedGetRunsInteraction := utils.NewHttpInteraction("/runs/"+groupId, http.MethodGet) + unauthorizedGetRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusUnauthorized) + writer.Write([]byte(`{ "error_message": "Invalid bearer token provided!" }`)) + } newLoginInteraction := utils.NewHttpInteraction("/auth/tokens", http.MethodPost) - newLoginInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { - writer.Header().Set("Content-Type", "application/json") - writer.WriteHeader(http.StatusCreated) + newLoginInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusCreated) newJwt := createValidMockJwt() fmt.Println(newJwt) mockResponse := fmt.Sprintf(`{"jwt": "%s", "refresh_token": "abc"}`, newJwt) - writer.Write([]byte(mockResponse)) - } + writer.Write([]byte(mockResponse)) + } - getRunsInteraction := utils.NewHttpInteraction("/runs/" + groupId, http.MethodGet) - getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { - writer.Header().Set("Content-Type", "application/json") - writer.WriteHeader(http.StatusOK) + getRunsInteraction := utils.NewHttpInteraction("/runs/"+groupId, http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) mockRun := galasaapi.NewTestRun() mockRuns := galasaapi.NewTestRuns() - mockRuns.Runs = []galasaapi.TestRun{ *mockRun } + mockRuns.Runs = []galasaapi.TestRun{*mockRun} mockRunsBytes, _ := json.Marshal(mockRuns) - writer.Write(mockRunsBytes) - } + writer.Write(mockRunsBytes) + } - interactions := []utils.HttpInteraction{ + interactions := []utils.HttpInteraction{ initialLoginOperation, - unauthorizedGetRunsInteraction, + unauthorizedGetRunsInteraction, newLoginInteraction, getRunsInteraction, - } + } - server := utils.NewMockHttpServer(t, interactions) - defer server.Server.Close() + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() mockFactory := utils.NewMockFactory() @@ -212,16 +211,16 @@ func TestGetRunsByGroupWithInvalidBearerTokenGetsNewTokenOk(t *testing.T) { mockGalasaHome, _ := utils.NewGalasaHome(mockFileSystem, mockEnvironment, "") mockTimeService := mockFactory.GetTimeService() jwtCache := auth.NewJwtCache(mockFileSystem, mockGalasaHome, mockTimeService) - - mockFileSystem.WriteTextFile(mockGalasaHome.GetUrlFolderPath() + "/galasactl.properties", "GALASA_TOKEN=my:token") - mockFileSystem.WriteTextFile(mockGalasaHome.GetUrlFolderPath() + "/bootstrap.properties", "") + + mockFileSystem.WriteTextFile(mockGalasaHome.GetUrlFolderPath()+"/galasactl.properties", "GALASA_TOKEN=my:token") + mockFileSystem.WriteTextFile(mockGalasaHome.GetUrlFolderPath()+"/bootstrap.properties", "") authenticator := auth.NewAuthenticator(apiServerUrl, mockFileSystem, mockGalasaHome, mockTimeService, mockEnvironment, jwtCache) mockFactory.Authenticator = authenticator bootstrap := "" - maxAttempts := 3 - retryBackoffSeconds := 1 + maxAttempts := 3 + retryBackoffSeconds := 1 commsClient, _ := api.NewAPICommsClient(bootstrap, maxAttempts, float64(retryBackoffSeconds), mockFactory, mockGalasaHome) diff --git a/pkg/runs/jsonReporter_test.go b/pkg/runs/jsonReporter_test.go index c6e6eae7..ba79b961 100644 --- a/pkg/runs/jsonReporter_test.go +++ b/pkg/runs/jsonReporter_test.go @@ -29,6 +29,7 @@ func TestJsonReportWorks(t *testing.T) { Tests: []TestMethod{{Method: "method1", Result: "passed"}, {Method: "method2", Result: "passed"}}, GherkinUrl: "file:///my.feature", GherkinFeature: "my", + SubmissionId: "123", } finishedRunsMap := make(map[string]*TestRun, 1) @@ -79,7 +80,8 @@ func TestJsonReportWorks(t *testing.T) { ], "GherkinUrl":"file:///my.feature", "GherkinFeature":"my", - "group":"" + "group":"", + "submissionId":"123" } ] }` diff --git a/pkg/runs/runTypes.go b/pkg/runs/runTypes.go index 9292517c..0e8b04f6 100644 --- a/pkg/runs/runTypes.go +++ b/pkg/runs/runTypes.go @@ -20,6 +20,7 @@ type TestRun struct { GherkinUrl string `yaml:"gherkin"` GherkinFeature string `yaml:"feature"` Group string `yaml:"group" json:"group"` + SubmissionId string `yaml:"submissionId" json:"submissionId"` RunId string `yaml:"runId,omitempty" json:"runId,omitempty"` } diff --git a/pkg/runs/submitter.go b/pkg/runs/submitter.go index 22504f69..ba527ec0 100644 --- a/pkg/runs/submitter.go +++ b/pkg/runs/submitter.go @@ -348,6 +348,7 @@ func (submitter *Submitter) submitRun( if err == nil { submittedRun := resultGroup.GetRuns()[0] nextRun.Group = *submittedRun.Group + nextRun.SubmissionId = *submittedRun.SubmissionId nextRun.Name = *submittedRun.Name submittedRuns[nextRun.Name] = &nextRun @@ -439,24 +440,42 @@ func (submitter *Submitter) processLostRuns( for runName, possiblyLostRun := range runsToCheck { isRunLost := true - - if possiblyLostRun.RunId != "" { - // Check the RAS to see if the run has been saved - var rasRun *galasaapi.Run - rasRun, err = submitter.launcher.GetRunsById(possiblyLostRun.RunId) - if err != nil { - log.Printf("processLostRuns - Failed to retrieve RAS run for %v - %v\n", possiblyLostRun.Name, err) - } else { - if rasRun != nil { - // The run was found in the RAS, not in the DSS - isRunLost = false - - testStructure := rasRun.GetTestStructure() - runStatus := testStructure.GetStatus() - if runStatus == "finished" { - - // The run has finished, so we no longer need to check its status - submitter.markRunFinished(possiblyLostRun, testStructure.GetResult(), submittedRuns, finishedRuns, fetchRas) + + log.Printf("processLostRuns - entered : name:%v runId:%v submissionId:%v \n", possiblyLostRun.Name, possiblyLostRun.RunId, possiblyLostRun.SubmissionId) + + if possiblyLostRun.RunId == "" { + if possiblyLostRun.SubmissionId != "" { + // We don't know this runs' RunId yet + // so lets try to find it in the RAS + var rasRun *galasaapi.Run + rasRun, err = submitter.launcher.GetRunsBySubmissionId(possiblyLostRun.SubmissionId, possiblyLostRun.Group) + if err != nil { + log.Printf("processLostRuns - Failed to retrieve RAS run by submissionId %v - %v\n", possiblyLostRun.Name, err) + } else { + log.Printf("processLostRuns - GetRunsBySubmissionId worked, rasRun:%v \n", rasRun) + if rasRun != nil { + // The run was found in the RAS, not in the DSS + isRunLost = false + + submitter.markRunIfFinished(possiblyLostRun, rasRun, submittedRuns, finishedRuns, fetchRas) + } + } + } + } + + if isRunLost { + if possiblyLostRun.RunId != "" { + // Check the RAS to see if the run has been saved, as we know it's run id. + var rasRun *galasaapi.Run + rasRun, err = submitter.launcher.GetRunsById(possiblyLostRun.RunId) + if err != nil { + log.Printf("processLostRuns - Failed to retrieve RAS run for %v - %v\n", possiblyLostRun.Name, err) + } else { + if rasRun != nil { + // The run was found in the RAS, not in the DSS + isRunLost = false + + submitter.markRunIfFinished(possiblyLostRun, rasRun, submittedRuns, finishedRuns, fetchRas) } } } @@ -468,6 +487,18 @@ func (submitter *Submitter) processLostRuns( delete(submittedRuns, runName) log.Printf("Run %v was lost - %v/%v/%v\n", runName, possiblyLostRun.Stream, possiblyLostRun.Bundle, possiblyLostRun.Class) } + log.Printf("processLostRuns - exiting\n") + } +} + +func (submitter *Submitter) markRunIfFinished(possiblyLostRun *TestRun, rasRun *galasaapi.Run, submittedRuns map[string]*TestRun, finishedRuns map[string]*TestRun, fetchRas bool) { + + testStructure := rasRun.GetTestStructure() + runStatus := testStructure.GetStatus() + if runStatus == "finished" { + log.Printf("run is finished\n") + // The run has finished, so we no longer need to check its status + submitter.markRunFinished(possiblyLostRun, testStructure.GetResult(), submittedRuns, finishedRuns, fetchRas) } } diff --git a/pkg/runs/yamlReporter_test.go b/pkg/runs/yamlReporter_test.go index 0dc8320a..e8926844 100644 --- a/pkg/runs/yamlReporter_test.go +++ b/pkg/runs/yamlReporter_test.go @@ -32,6 +32,7 @@ func TestYamlReportWorks(t *testing.T) { Tests: []TestMethod{{Method: "method1", Result: "passed"}, {Method: "method2", Result: "passed"}}, GherkinUrl: "file:///my.feature", GherkinFeature: "my", + SubmissionId: "123", } finishedRunsMap := make(map[string]*TestRun, 1) @@ -74,7 +75,8 @@ func TestYamlReportWorks(t *testing.T) { result: passed gherkin: file:///my.feature feature: my - group:""` + group:"" + submissionId:"123"` actualContents, err := mockFileSystem.ReadTextFile("myReportYamlFilename") if err != nil { diff --git a/test-scripts/run-single-test-remotely.sh b/test-scripts/run-single-test-remotely.sh index a0c813ec..783851e9 100755 --- a/test-scripts/run-single-test-remotely.sh +++ b/test-scripts/run-single-test-remotely.sh @@ -17,15 +17,45 @@ export GALASA_HOME=$(pwd) cd "${BASEDIR}/.." -galasactl runs submit \ ---class dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu \ ---stream inttests \ ---throttle 1 \ ---poll 10 \ ---progress 1 \ ---noexitcodeontestfailures \ ---log - \ ---overridefile ${GALASA_HOME}/overrides.properties +# This one invokes the sub-ecosystem +# galasactl runs submit \ +# --class dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu \ +# --stream inttests \ +# --throttle 1 \ +# --poll 10 \ +# --progress 1 \ +# --noexitcodeontestfailures \ +# --log - \ +# --overridefile ${GALASA_HOME}/overrides.properties + +# Run a quick stand-alone test which doesn't do much. + +count=0 +while [ $count -lt "1" ]; do + count=$(($count+1)); + + galasactl runs submit \ + --class dev.galasa.ivts/dev.galasa.ivts.core.CoreManagerIVT \ + --stream ivts \ + --throttle 3 \ + --poll 10 \ + --progress 1 \ + --noexitcodeontestfailures \ + --group mcobbett-$count \ + --overridefile ${GALASA_HOME}/overrides.properties \ + --log - --trace + + echo "Hello $count" +done + +# galasactl runs submit \ +# --class dev.galasa.ivts/dev.galasa.ivts.core.CoreManagerIVT \ +# --stream ivts \ +# --throttle 3 \ +# --poll 10 \ +# --progress 1 \ +# --noexitcodeontestfailures \ +# --overridefile ${GALASA_HOME}/overrides.properties # galasactl runs prepare --portfolio my.portfolio --class dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu --stream inttests # galasactl runs submit --portfolio my.portfolio --log - \ No newline at end of file