From ed2cc0df941246b38305f2e32b5e6a32a89a7c28 Mon Sep 17 00:00:00 2001 From: Guiheux Steven Date: Fri, 2 Jun 2017 17:17:11 +0200 Subject: [PATCH] feat (api): upload unit test result (#670) --- engine/api/workflow/dao.go | 1 - engine/api/workflow/dao_join.go | 2 +- engine/api/workflow/dao_node_run.go | 15 +++- engine/api/workflow/dao_trigger.go | 1 - engine/api/workflow/execute_node_job_run.go | 2 +- engine/api/workflow/execute_node_run.go | 2 +- engine/api/workflow_queue.go | 65 ++++++++++++++ engine/api/workflow_queue_test.go | 88 ++++++++++++++++++- .../runabove/venom/executors/web/types.go | 6 +- .../github.com/spf13/viper/remote/remote.go | 13 ++- 10 files changed, 177 insertions(+), 18 deletions(-) diff --git a/engine/api/workflow/dao.go b/engine/api/workflow/dao.go index c0e43a5f2b..22e198f9cd 100644 --- a/engine/api/workflow/dao.go +++ b/engine/api/workflow/dao.go @@ -175,7 +175,6 @@ func Update(db gorp.SqlExecutor, w *sdk.Workflow, oldWorkflow *sdk.Workflow, u * w.RootID = w.Root.ID - // Insert new JOIN for i := range w.Joins { j := &w.Joins[i] diff --git a/engine/api/workflow/dao_join.go b/engine/api/workflow/dao_join.go index 0d48670305..844ec20fac 100644 --- a/engine/api/workflow/dao_join.go +++ b/engine/api/workflow/dao_join.go @@ -236,4 +236,4 @@ func deleteJoin(db gorp.SqlExecutor, n sdk.WorkflowNodeJoin) error { return sdk.WrapError(err, "deleteJoin> Unable to delete join %d", j.ID) } return nil -} \ No newline at end of file +} diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index 25824eb442..9f73c1cf5e 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -33,6 +33,19 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i return &r, nil } +//LoadAndLockNodeRunByID load and lock a specific node run on a workflow +func LoadAndLockNodeRunByID(db gorp.SqlExecutor, id int64) (*sdk.WorkflowNodeRun, error) { + var rr = NodeRun{} + query := `select workflow_node_run.* + from workflow_node_run + where workflow_node_run.id = $1 for update nowait` + if err := db.SelectOne(&rr, query, id); err != nil { + return nil, sdk.WrapError(err, "workflow.LoadAndLockNodeRunByID> Unable to load workflow_node_run node=%d", id) + } + r := sdk.WorkflowNodeRun(rr) + return &r, nil +} + //LoadNodeRunByID load a specific node run on a workflow func LoadNodeRunByID(db gorp.SqlExecutor, id int64) (*sdk.WorkflowNodeRun, error) { var rr = NodeRun{} @@ -57,7 +70,7 @@ func insertWorkflowNodeRun(db gorp.SqlExecutor, n *sdk.WorkflowNodeRun) error { } //updateWorkflowNodeRun updates in table workflow_node_run -func updateWorkflowNodeRun(db gorp.SqlExecutor, n *sdk.WorkflowNodeRun) error { +func UpdateWorkflowNodeRun(db gorp.SqlExecutor, n *sdk.WorkflowNodeRun) error { nodeRunDB := NodeRun(*n) if _, err := db.Update(&nodeRunDB); err != nil { return err diff --git a/engine/api/workflow/dao_trigger.go b/engine/api/workflow/dao_trigger.go index cef84a0747..e09226410e 100644 --- a/engine/api/workflow/dao_trigger.go +++ b/engine/api/workflow/dao_trigger.go @@ -38,7 +38,6 @@ func insertTrigger(db gorp.SqlExecutor, w *sdk.Workflow, node *sdk.WorkflowNode, return sdk.WrapError(err, "insertTrigger> Unable to update node %d for trigger %d", trigger.WorkflowDestNode.ID, trigger.ID) } - //Manage conditions b, err := json.Marshal(trigger.Conditions) if err != nil { diff --git a/engine/api/workflow/execute_node_job_run.go b/engine/api/workflow/execute_node_job_run.go index 0449cd4162..58a2d57e9b 100644 --- a/engine/api/workflow/execute_node_job_run.go +++ b/engine/api/workflow/execute_node_job_run.go @@ -72,7 +72,7 @@ func UpdateNodeJobRunStatus(db gorp.SqlExecutor, job *sdk.WorkflowNodeJobRun, st } if stageUpdated { - if err := updateWorkflowNodeRun(db, node); err != nil { + if err := UpdateWorkflowNodeRun(db, node); err != nil { return sdk.WrapError(err, "workflow.UpdateNodeJobRunStatus> Unable to update workflow node run %d", node.ID) } } diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index 72b5f93e46..0c23bd15d1 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -102,7 +102,7 @@ func execute(db *gorp.DbMap, n *sdk.WorkflowNodeRun) error { n.Status = newStatus // Save the node run in database - if err := updateWorkflowNodeRun(tx, n); err != nil { + if err := UpdateWorkflowNodeRun(tx, n); err != nil { return sdk.WrapError(fmt.Errorf("Unable to update node id=%d", n.ID), "workflow.execute> Unable to execute node") } diff --git a/engine/api/workflow_queue.go b/engine/api/workflow_queue.go index 119aadd647..1daf1cafff 100644 --- a/engine/api/workflow_queue.go +++ b/engine/api/workflow_queue.go @@ -1,11 +1,13 @@ package main import ( + "fmt" "io/ioutil" "net/http" "time" "github.com/go-gorp/gorp" + "github.com/runabove/venom" "github.com/ovh/cds/engine/api/context" "github.com/ovh/cds/engine/api/worker" @@ -282,6 +284,69 @@ func getWorkflowJobQueueHandler(w http.ResponseWriter, r *http.Request, db *gorp } func postWorkflowJobTestsResultsHandler(w http.ResponseWriter, r *http.Request, db *gorp.DbMap, c *context.Ctx) error { + // Unmarshal into results + var new venom.Tests + if err := UnmarshalBody(r, &new); err != nil { + return sdk.WrapError(err, "postWorkflowJobTestsResultsHandler> cannot unmarshal request") + } + + // Load and lock Existing workflow Run Job + id, errI := requestVarInt(r, "permID") + if errI != nil { + return sdk.WrapError(errI, "postWorkflowJobTestsResultsHandler> Invalid node job run ID") + } + + nodeRunJob, errJobRun := workflow.LoadNodeJobRun(db, id) + if errJobRun != nil { + return sdk.WrapError(errJobRun, "postWorkflowJobTestsResultsHandler> Cannot load node run job") + } + + tx, errB := db.Begin() + if errB != nil { + return sdk.WrapError(errB, "postWorkflowJobTestsResultsHandler> Cannot start transaction") + } + defer tx.Rollback() + + wnjr, err := workflow.LoadAndLockNodeRunByID(tx, nodeRunJob.WorkflowNodeRunID) + if err != nil { + return sdk.WrapError(err, "postWorkflowJobTestsResultsHandler> Cannot load node job") + } + + if wnjr.Tests == nil { + wnjr.Tests = &venom.Tests{} + } + + for k := range new.TestSuites { + for i := range wnjr.Tests.TestSuites { + if wnjr.Tests.TestSuites[i].Name == new.TestSuites[k].Name { + // testsuite with same name already exists, + // Create a unique name + new.TestSuites[k].Name = fmt.Sprintf("%s.%d", new.TestSuites[k].Name, id) + break + } + } + wnjr.Tests.TestSuites = append(wnjr.Tests.TestSuites, new.TestSuites[k]) + } + + // update total values + wnjr.Tests.Total = 0 + wnjr.Tests.TotalOK = 0 + wnjr.Tests.TotalKO = 0 + wnjr.Tests.TotalSkipped = 0 + for _, ts := range wnjr.Tests.TestSuites { + wnjr.Tests.Total += ts.Total + wnjr.Tests.TotalKO += ts.Failures + ts.Errors + wnjr.Tests.TotalOK += ts.Total - ts.Skipped - ts.Failures - ts.Errors + wnjr.Tests.TotalSkipped += ts.Skipped + } + + if err := workflow.UpdateWorkflowNodeRun(tx, wnjr); err != nil { + return sdk.WrapError(err, "postWorkflowJobTestsResultsHandler> Cannot update node run") + } + + if err := tx.Commit(); err != nil { + return sdk.WrapError(err, "postWorkflowJobTestsResultsHandler> Cannot update node run") + } return nil } diff --git a/engine/api/workflow_queue_test.go b/engine/api/workflow_queue_test.go index 37749f828d..78ef76b04e 100644 --- a/engine/api/workflow_queue_test.go +++ b/engine/api/workflow_queue_test.go @@ -23,6 +23,7 @@ import ( "github.com/ovh/cds/engine/api/worker" "github.com/ovh/cds/engine/api/workflow" "github.com/ovh/cds/sdk" + "github.com/runabove/venom" ) type test_runWorkflowCtx struct { @@ -349,8 +350,91 @@ func Test_postWorkflowJobStepStatusHandler(t *testing.T) { //ctx := runWorkflow(t, db, "Test_postWorkflowJobRequirementsErrorHandler") } func Test_postWorkflowJobTestsResultsHandler(t *testing.T) { - //db := test.SetupPG(t) - //ctx := runWorkflow(t, db, "Test_postWorkflowJobRequirementsErrorHandler") + db := test.SetupPG(t) + ctx := test_runWorkflow(t, db, "/Test_postTakeWorkflowJobHandler") + test_getWorkflowJob(t, db, &ctx) + assert.NotNil(t, ctx.job) + + //Prepare request + vars := map[string]string{ + "permProjectKey": ctx.project.Key, + "workflowName": ctx.workflow.Name, + "id": fmt.Sprintf("%d", ctx.job.ID), + } + + //Register the worker + test_registerWorker(t, db, &ctx) + + //Take + uri := router.getRoute("POST", postTakeWorkflowJobHandler, vars) + test.NotEmpty(t, uri) + + takeForm := worker.TakeForm{ + BookedJobID: ctx.job.ID, + Time: time.Now(), + } + + req := assets.NewAuthentifiedRequestFromWorker(t, ctx.worker, "POST", uri, takeForm) + rec := httptest.NewRecorder() + router.mux.ServeHTTP(rec, req) + assert.Equal(t, 200, rec.Code) + + vars = map[string]string{ + "permID": fmt.Sprintf("%d", ctx.job.ID), + } + + //Send test + tests := venom.Tests{ + Total: 2, + TotalKO: 1, + TotalOK: 1, + TotalSkipped: 0, + TestSuites: []venom.TestSuite{ + { + Total: 1, + Name: "TestSuite1", + TestCases: []venom.TestCase{ + { + Name: "TestCase1", + Status: "OK", + }, + }, + }, + { + Total: 1, + Name: "TestSuite2", + TestCases: []venom.TestCase{ + { + Name: "TestCase1", + Status: "KO", + Failures: []venom.Failure{ + { + Value: "Fail", + Type: "Assertion error", + Message: "Error occured", + }, + }, + }, + }, + }, + }, + } + + uri = router.getRoute("POST", postWorkflowJobTestsResultsHandler, vars) + test.NotEmpty(t, uri) + + req = assets.NewAuthentifiedRequestFromWorker(t, ctx.worker, "POST", uri, tests) + rec = httptest.NewRecorder() + router.mux.ServeHTTP(rec, req) + assert.Equal(t, 200, rec.Code) + + wNodeJobRun, errJ := workflow.LoadNodeJobRun(db, ctx.job.ID) + test.NoError(t, errJ) + nodeRun, errN := workflow.LoadNodeRunByID(db, wNodeJobRun.WorkflowNodeRunID) + test.NoError(t, errN) + + assert.NotNil(t, nodeRun.Tests) + assert.Equal(t, 2, nodeRun.Tests.Total) } func Test_postWorkflowJobVariableHandler(t *testing.T) { //db := test.SetupPG(t) diff --git a/vendor/github.com/runabove/venom/executors/web/types.go b/vendor/github.com/runabove/venom/executors/web/types.go index dcf8662b63..80c17daba9 100644 --- a/vendor/github.com/runabove/venom/executors/web/types.go +++ b/vendor/github.com/runabove/venom/executors/web/types.go @@ -2,9 +2,9 @@ package web // Action represents what can be done with web executor type Action struct { - Click *Click `yaml:"click,omitempty"` - Fill []Fill `yaml:"fill,omitempty"` - Find string `yaml:"find,omitempty"` + Click *Click `yaml:"click,omitempty"` + Fill []Fill `yaml:"fill,omitempty"` + Find string `yaml:"find,omitempty"` Navigate *Navigate `yaml:"navigate,omitempty"` } diff --git a/vendor/github.com/spf13/viper/remote/remote.go b/vendor/github.com/spf13/viper/remote/remote.go index f100a9c7f9..a4e4ddcc0a 100644 --- a/vendor/github.com/spf13/viper/remote/remote.go +++ b/vendor/github.com/spf13/viper/remote/remote.go @@ -33,7 +33,7 @@ func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) if err != nil { return nil, err } - resp,err := cm.Get(rp.Path()) + resp, err := cm.Get(rp.Path()) if err != nil { return nil, err } @@ -47,13 +47,13 @@ func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *vi } quit := make(chan bool) quitwc := make(chan bool) - viperResponsCh := make(chan *viper.RemoteResponse) + viperResponsCh := make(chan *viper.RemoteResponse) cryptoResponseCh := cm.Watch(rp.Path(), quit) // need this function to convert the Channel response form crypt.Response to viper.Response - go func(cr <-chan *crypt.Response,vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) { + go func(cr <-chan *crypt.Response, vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) { for { select { - case <- quitwc: + case <-quitwc: quit <- true return case resp := <-cr: @@ -65,13 +65,12 @@ func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *vi } } - }(cryptoResponseCh,viperResponsCh,quitwc,quit) + }(cryptoResponseCh, viperResponsCh, quitwc, quit) - return viperResponsCh,quitwc + return viperResponsCh, quitwc } - func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) { var cm crypt.ConfigManager