From 953f68d40d78bc2e09bfa90e3af953cb7fcc56d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0ar=C4=8Devi=C4=87?= Date: Thu, 28 Jan 2021 12:04:02 +0100 Subject: [PATCH 1/5] Parse when errors --- .../{error_invalid_change_in.go => errors.go} | 9 +++ pkg/pipelines/extract_when_list.go | 2 +- pkg/pipelines/model.go | 21 ------ pkg/when/whencli/list_inputs.go | 44 +++++++++--- pkg/when/whencli/list_inputs_test.go | 47 ++++++++++++ test/e2e/change_in_invalid_when.rb | 71 +++++++++++++++++++ 6 files changed, 162 insertions(+), 32 deletions(-) rename pkg/logs/{error_invalid_change_in.go => errors.go} (60%) create mode 100644 pkg/when/whencli/list_inputs_test.go create mode 100644 test/e2e/change_in_invalid_when.rb diff --git a/pkg/logs/error_invalid_change_in.go b/pkg/logs/errors.go similarity index 60% rename from pkg/logs/error_invalid_change_in.go rename to pkg/logs/errors.go index a5e5f59..cd51d4c 100644 --- a/pkg/logs/error_invalid_change_in.go +++ b/pkg/logs/errors.go @@ -13,3 +13,12 @@ type ErrorChangeInMissingBranch struct { func (e *ErrorChangeInMissingBranch) Error() string { return e.Message } + +type ErrorInvalidWhenExpression struct { + Message string `json:"message"` + Location Location `json:"location"` +} + +func (e *ErrorInvalidWhenExpression) Error() string { + return e.Message +} diff --git a/pkg/pipelines/extract_when_list.go b/pkg/pipelines/extract_when_list.go index 0819602..21aa011 100644 --- a/pkg/pipelines/extract_when_list.go +++ b/pkg/pipelines/extract_when_list.go @@ -35,7 +35,7 @@ func (e *whenExtractor) Parse() ([]when.WhenExpression, error) { } for index := range e.list { - e.list[index].Requirments = requirments[index] + e.list[index].Requirments = requirments[index].Inputs } return e.list, nil diff --git a/pkg/pipelines/model.go b/pkg/pipelines/model.go index 9674525..a1cd2e4 100644 --- a/pkg/pipelines/model.go +++ b/pkg/pipelines/model.go @@ -2,7 +2,6 @@ package pipelines import ( "encoding/json" - "fmt" "time" gabs "github.com/Jeffail/gabs/v2" @@ -25,19 +24,11 @@ var TotalEval int64 var TotalReduce int64 func (p *Pipeline) EvaluateChangeIns() error { - fmt.Println("Evaluating start.") - - start1 := n() - list, err := p.ExtractWhenConditions() if err != nil { return err } - TotalList = n() - start1 - - start2 := n() - for index := range list { err := list[index].Eval() if err != nil { @@ -45,10 +36,6 @@ func (p *Pipeline) EvaluateChangeIns() error { } } - TotalEval = n() - start2 - - start3 := n() - expressions := []string{} inputs := []whencli.ReduceInputs{} @@ -66,14 +53,6 @@ func (p *Pipeline) EvaluateChangeIns() error { p.raw.Set(expressions[index], list[index].Path...) } - TotalReduce = n() - start3 - - fmt.Println("Evaluating end.") - - fmt.Printf("Parse When Expressions: %dms\n", TotalList) - fmt.Printf("Evaluated change_in: %dms\n", TotalEval) - fmt.Printf("Reduce When Expressions: %dms\n", TotalReduce) - return nil } diff --git a/pkg/when/whencli/list_inputs.go b/pkg/when/whencli/list_inputs.go index 55fdf8f..5339a64 100644 --- a/pkg/when/whencli/list_inputs.go +++ b/pkg/when/whencli/list_inputs.go @@ -4,31 +4,55 @@ import ( "encoding/json" "fmt" "io/ioutil" + "log" "os" "os/exec" gabs "github.com/Jeffail/gabs/v2" ) -func ListInputs(expressions []string) ([]*gabs.Container, error) { +type ListInputsResult struct { + Expression string + Inputs *gabs.Container + Error string +} + +func ListInputs(expressions []string) ([]ListInputsResult, error) { var err error + res := []ListInputsResult{} inputPath := "/tmp/when-expressions" outputPath := "/tmp/parsed-when-expressions" err = ListInputsPrepareInputFile(inputPath, expressions) if err != nil { - return []*gabs.Container{}, nil + return res, nil } output, err := exec.Command("when", "list-inputs", "--input", inputPath, "--output", outputPath).CombinedOutput() if err != nil { - return []*gabs.Container{}, fmt.Errorf("unprecessable when expressions %s", string(output)) + return res, fmt.Errorf("unprecessable when expressions %s", string(output)) } - result, err := ListInputsLoadResults(outputPath) + results, err := ListInputsLoadResults(outputPath) if err != nil { - return result, fmt.Errorf("unprocessable when expressions %s, when CLI output: %s", err.Error(), output) + return res, fmt.Errorf("unprocessable when expressions %s, when CLI output: %s", err.Error(), output) + } + + return prepareResults(expressions, results) +} + +func prepareResults(expressions []string, results *gabs.Container) ([]ListInputsResult, error) { + result := []ListInputsResult{} + + for index, el := range results.Children() { + result = append(result, ListInputsResult{ + Expression: expressions[index], + Inputs: el.Search("inputs"), + Error: el.Search("error").Data().(string), + }) + log.Println(index) + log.Println(el) } return result, nil @@ -48,22 +72,22 @@ func ListInputsPrepareInputFile(path string, expressions []string) error { return nil } -func ListInputsLoadResults(path string) ([]*gabs.Container, error) { +func ListInputsLoadResults(path string) (*gabs.Container, error) { file, err := os.Open(path) if err != nil { - return []*gabs.Container{}, err + return nil, err } defer file.Close() content, err := ioutil.ReadAll(file) if err != nil { - return []*gabs.Container{}, err + return nil, err } inputs, err := gabs.ParseJSON(content) if err != nil { - return []*gabs.Container{}, err + return nil, err } - return inputs.Children(), nil + return inputs, nil } diff --git a/pkg/when/whencli/list_inputs_test.go b/pkg/when/whencli/list_inputs_test.go new file mode 100644 index 0000000..c97ce37 --- /dev/null +++ b/pkg/when/whencli/list_inputs_test.go @@ -0,0 +1,47 @@ +package whencli + +import ( + "testing" + + gabs "github.com/Jeffail/gabs/v2" + assert "github.com/stretchr/testify/assert" +) + +func Test__ListInputs(t *testing.T) { + expressions := []string{ + "branch = 'master'", + "change_in('lib')", + "branch = ", + } + + results, err := ListInputs(expressions) + + assert.Nil(t, err) + assert.Equal(t, 3, len(results)) + assert.Equal(t, []ListInputsResult{ + ListInputsResult{ + Expression: expressions[0], + Error: "", + Inputs: fromJSON(`[{"name": "branch", "type": "keyword"}]`), + }, + ListInputsResult{ + Expression: expressions[1], + Error: "", + Inputs: fromJSON(`[{"name": "change_in", "params": ["lib"], "type": "fun"}]`), + }, + ListInputsResult{ + Expression: expressions[2], + Error: "Invalid or incomplete expression at the end of the line.", + Inputs: fromJSON(`[]`), + }, + }, results) +} + +func fromJSON(s string) *gabs.Container { + res, err := gabs.ParseJSON([]byte(s)) + if err != nil { + panic(err) + } + + return res +} diff --git a/test/e2e/change_in_invalid_when.rb b/test/e2e/change_in_invalid_when.rb new file mode 100644 index 0000000..a9724ce --- /dev/null +++ b/test/e2e/change_in_invalid_when.rb @@ -0,0 +1,71 @@ +# rubocop:disable all + +require_relative "../e2e" + +system "rm -f /tmp/output.yml" +system "rm -f /tmp/logs.jsonl" + +pipeline = %{ +version: v1.0 +name: Test +agent: + machine: + type: e1-standard-2 + +blocks: + - name: Test + skip: + when: "branch = 'master' and ahahahaha and change_in('/lib')" + task: + jobs: + - name: Hello + commands: + - echo "Hello World" + + - name: Test + skip: + when: "branch =" + task: + jobs: + - name: Hello + commands: + - echo "Hello World" +} + +origin = TestRepoForChangeIn.setup() + +origin.add_file('.semaphore/semaphore.yml', pipeline) +origin.commit!("Bootstrap") + +origin.add_file("lib/A.txt", "hello") +origin.commit!("Changes on master") + +origin.create_branch("dev") +origin.add_file("lib/B.txt", "hello") +origin.commit!("Changes in dev") + +repo = origin.clone_local_copy(branch: "dev") +repo.run("#{spc} evaluate change-in --input .semaphore/semaphore.yml --output /tmp/output.yml --logs /tmp/logs.jsonl", fail: false) + +assert_eq($?.exitstatus, 1) + +errors = File.read('/tmp/logs.jsonl').lines.map { |l| JSON.parse(l) } +assert_eq(errors.size, 2) + +assert_eq(errors[0], { + "type" => "ErrorInvalidWhenExpression", + "message" => "Invalid when expression: branch = 'master' and ahahahaha and change_in('/lib')", + "location" => { + "file" => ".semaphore/semaphore.yml", + "path" => ["blocks", "0", "skip", "when"] + } +}) + +assert_eq(errors[1], { + "type" => "ErrorInvalidWhenExpression", + "message" => "Invalid when expression: branch =", + "location" => { + "file" => ".semaphore/semaphore.yml", + "path" => ["blocks", "1", "skip", "when"] + } +}) From 19a74df14dcfa257bb42921a3274a9e3ebdf2b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0ar=C4=8Devi=C4=87?= Date: Thu, 28 Jan 2021 14:14:47 +0100 Subject: [PATCH 2/5] Log when parsing errors --- pkg/cli/evaluate.go | 4 ++++ pkg/pipelines/extract_when_list.go | 34 +++++++++++++++++++++++++++++- pkg/when/whencli/list_inputs.go | 3 --- test/e2e/change_in_invalid_when.rb | 4 ++-- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/pkg/cli/evaluate.go b/pkg/cli/evaluate.go index 6db0455..e8d1fe3 100644 --- a/pkg/cli/evaluate.go +++ b/pkg/cli/evaluate.go @@ -66,6 +66,10 @@ func check(err error) { os.Exit(1) } + if _, ok := err.(*logs.ErrorInvalidWhenExpression); ok { + os.Exit(1) + } + panic(err) } diff --git a/pkg/pipelines/extract_when_list.go b/pkg/pipelines/extract_when_list.go index 21aa011..5d0f7ed 100644 --- a/pkg/pipelines/extract_when_list.go +++ b/pkg/pipelines/extract_when_list.go @@ -3,6 +3,7 @@ package pipelines import ( "strconv" + logs "github.com/semaphoreci/spc/pkg/logs" when "github.com/semaphoreci/spc/pkg/when" whencli "github.com/semaphoreci/spc/pkg/when/whencli" ) @@ -29,9 +30,16 @@ func (e *whenExtractor) Parse() ([]when.WhenExpression, error) { expressions = append(expressions, e.Expression) } + res := []when.WhenExpression{} + requirments, err := whencli.ListInputs(expressions) if err != nil { - return []when.WhenExpression{}, err + return res, err + } + + err = e.verifyParsed(requirments) + if err != nil { + return res, err } for index := range e.list { @@ -41,6 +49,30 @@ func (e *whenExtractor) Parse() ([]when.WhenExpression, error) { return e.list, nil } +func (e *whenExtractor) verifyParsed(requirments []whencli.ListInputsResult) error { + var err error + + for index, r := range requirments { + if r.Error != "" { + loc := logs.Location{ + Path: e.list[index].Path, + File: e.pipeline.yamlPath, + } + + logError := logs.ErrorInvalidWhenExpression{ + Message: r.Error, + Location: loc, + } + + logs.Log(logError) + + err = &logError + } + } + + return err +} + func (e *whenExtractor) ExtractAutoCancel() { e.tryExtractingFromPath([]string{"auto_cancel", "queued", "when"}) e.tryExtractingFromPath([]string{"auto_cancel", "running", "when"}) diff --git a/pkg/when/whencli/list_inputs.go b/pkg/when/whencli/list_inputs.go index 5339a64..ba05bfb 100644 --- a/pkg/when/whencli/list_inputs.go +++ b/pkg/when/whencli/list_inputs.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "os" "os/exec" @@ -51,8 +50,6 @@ func prepareResults(expressions []string, results *gabs.Container) ([]ListInputs Inputs: el.Search("inputs"), Error: el.Search("error").Data().(string), }) - log.Println(index) - log.Println(el) } return result, nil diff --git a/test/e2e/change_in_invalid_when.rb b/test/e2e/change_in_invalid_when.rb index a9724ce..05a37e6 100644 --- a/test/e2e/change_in_invalid_when.rb +++ b/test/e2e/change_in_invalid_when.rb @@ -54,7 +54,7 @@ assert_eq(errors[0], { "type" => "ErrorInvalidWhenExpression", - "message" => "Invalid when expression: branch = 'master' and ahahahaha and change_in('/lib')", + "message" => "Invalid expression on the left of 'and' operator.", "location" => { "file" => ".semaphore/semaphore.yml", "path" => ["blocks", "0", "skip", "when"] @@ -63,7 +63,7 @@ assert_eq(errors[1], { "type" => "ErrorInvalidWhenExpression", - "message" => "Invalid when expression: branch =", + "message" => "Invalid or incomplete expression at the end of the line.", "location" => { "file" => ".semaphore/semaphore.yml", "path" => ["blocks", "1", "skip", "when"] From 5d3c8868d7bf1743a9dcc23b33e0d1c3b87634d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0ar=C4=8Devi=C4=87?= Date: Thu, 28 Jan 2021 15:46:25 +0100 Subject: [PATCH 3/5] Use v0.0.3-alpha to run SPC --- .semaphore/semaphore.yml | 6 +++--- pkg/when/whencli/list_inputs_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index e7cb5df..a6052ed 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -12,6 +12,9 @@ global_job_config: - export GO111MODULE=on - export GOPATH=~/go - 'export PATH=/home/semaphore/go/bin:$PATH' + - curl -LO https://github.com/renderedtext/when/releases/download/v0.0.3-alpha/when + - sudo chmod +x when + - sudo mv when /usr/bin/when - checkout - go get ./... @@ -30,9 +33,6 @@ blocks: task: prologue: commands: - - curl -LO https://github.com/renderedtext/when/releases/download/v0.0.2-alpha/when - - sudo chmod +x when - - sudo mv when /usr/bin/when - git config --global user.email "you@example.com" - git config --global user.name "Your Name" - unset SEMAPHORE_GIT_REF_TYPE diff --git a/pkg/when/whencli/list_inputs_test.go b/pkg/when/whencli/list_inputs_test.go index c97ce37..9aa0229 100644 --- a/pkg/when/whencli/list_inputs_test.go +++ b/pkg/when/whencli/list_inputs_test.go @@ -17,7 +17,7 @@ func Test__ListInputs(t *testing.T) { results, err := ListInputs(expressions) assert.Nil(t, err) - assert.Equal(t, 3, len(results)) + assert.Equal(t, len(expressions), len(results)) assert.Equal(t, []ListInputsResult{ ListInputsResult{ Expression: expressions[0], From 810cf37a78c172d260b25f1229a34dd518651e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0ar=C4=8Devi=C4=87?= Date: Thu, 28 Jan 2021 15:58:36 +0100 Subject: [PATCH 4/5] Run all e2e tests --- .semaphore/semaphore.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index a6052ed..a13f3d1 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -13,8 +13,8 @@ global_job_config: - export GOPATH=~/go - 'export PATH=/home/semaphore/go/bin:$PATH' - curl -LO https://github.com/renderedtext/when/releases/download/v0.0.3-alpha/when - - sudo chmod +x when - - sudo mv when /usr/bin/when + - sudo mv when /usr/local/bin/when + - sudo chmod +x /usr/local/bin/when - checkout - go get ./... @@ -57,16 +57,17 @@ blocks: - test/e2e/change_in_default_range.rb - test/e2e/change_in_excluded_paths.rb - test/e2e/change_in_glob.rb + - test/e2e/change_in_invalid_when.rb - test/e2e/change_in_missing_branch.rb - test/e2e/change_in_multiple_paths.rb - - test/e2e/change_in_on_tags.rb - - test/e2e/change_in_on_prs.rb - test/e2e/change_in_on_forked_prs.rb + - test/e2e/change_in_on_prs.rb + - test/e2e/change_in_on_tags.rb + - test/e2e/change_in_performance.rb - test/e2e/change_in_pipeline_file_tracking.rb - test/e2e/change_in_relative_paths.rb - test/e2e/change_in_simple.rb - test/e2e/change_in_with_default_branch.rb - - test/e2e/change_in_performance.rb commands: - make build From 1fb85cf155b74c161bb14885ec95139095efdb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0ar=C4=8Devi=C4=87?= Date: Thu, 28 Jan 2021 16:20:30 +0100 Subject: [PATCH 5/5] Tests for reduce --- pkg/when/whencli/list_inputs_test.go | 10 ------- pkg/when/whencli/reduce.go | 12 +++++++- pkg/when/whencli/reduce_test.go | 43 ++++++++++++++++++++++++++++ pkg/when/whencli/test_main.go | 12 ++++++++ 4 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 pkg/when/whencli/reduce_test.go create mode 100644 pkg/when/whencli/test_main.go diff --git a/pkg/when/whencli/list_inputs_test.go b/pkg/when/whencli/list_inputs_test.go index 9aa0229..9acc938 100644 --- a/pkg/when/whencli/list_inputs_test.go +++ b/pkg/when/whencli/list_inputs_test.go @@ -3,7 +3,6 @@ package whencli import ( "testing" - gabs "github.com/Jeffail/gabs/v2" assert "github.com/stretchr/testify/assert" ) @@ -36,12 +35,3 @@ func Test__ListInputs(t *testing.T) { }, }, results) } - -func fromJSON(s string) *gabs.Container { - res, err := gabs.ParseJSON([]byte(s)) - if err != nil { - panic(err) - } - - return res -} diff --git a/pkg/when/whencli/reduce.go b/pkg/when/whencli/reduce.go index 47a9367..5d60953 100644 --- a/pkg/when/whencli/reduce.go +++ b/pkg/when/whencli/reduce.go @@ -77,6 +77,8 @@ func ReduceLoadOutput(path string) ([]string, error) { return []string{}, err } + fmt.Println(string(content)) + inputs, err := gabs.ParseJSON(content) if err != nil { return []string{}, err @@ -85,7 +87,15 @@ func ReduceLoadOutput(path string) ([]string, error) { exprs := []string{} for index := range inputs.Children() { - exprs = append(exprs, inputs.Children()[index].Data().(string)) + el := inputs.Children()[index] + result := el.Search("result").Data().(string) + errString := el.Search("error").Data().(string) + + if errString != "" { + return []string{}, fmt.Errorf("unprocessable when expression %s", errString) + } + + exprs = append(exprs, result) } return exprs, nil diff --git a/pkg/when/whencli/reduce_test.go b/pkg/when/whencli/reduce_test.go new file mode 100644 index 0000000..9fbf78d --- /dev/null +++ b/pkg/when/whencli/reduce_test.go @@ -0,0 +1,43 @@ +package whencli + +import ( + "testing" + + assert "github.com/stretchr/testify/assert" +) + +func Test__Reduce(t *testing.T) { + expressions := []string{ + "branch = 'master'", + "change_in('lib')", + } + + inputs := []ReduceInputs{ + ReduceInputs{ + Keywords: map[string]interface{}{ + "branch": "master", + }, + Functions: []interface{}{}, + }, + ReduceInputs{ + Keywords: map[string]interface{}{}, + Functions: []interface{}{ + fromJSON(`{"name": "change_in", "params": ["lib"], "result": false}`), + }, + }, + ReduceInputs{ + Keywords: map[string]interface{}{}, + Functions: []interface{}{}, + }, + } + + results, err := Reduce(expressions, inputs) + + assert.Nil(t, err) + assert.Equal(t, len(expressions), len(results)) + + assert.Equal(t, []string{ + "true", + "false", + }, results) +} diff --git a/pkg/when/whencli/test_main.go b/pkg/when/whencli/test_main.go new file mode 100644 index 0000000..c97488f --- /dev/null +++ b/pkg/when/whencli/test_main.go @@ -0,0 +1,12 @@ +package whencli + +import gabs "github.com/Jeffail/gabs/v2" + +func fromJSON(s string) *gabs.Container { + res, err := gabs.ParseJSON([]byte(s)) + if err != nil { + panic(err) + } + + return res +}