Skip to content

Commit

Permalink
Scanner error mapping [ACT-630][ACT-865] (#126)
Browse files Browse the repository at this point in the history
* Git clone step error mapping init.

* feat: Introduce PatternErrorMatcher for error mapping

* refactor: separate out error_matcher from steperror

* fix: Add DetailedError to branch recommendation

* fix: use nil instead of empty map

* refactor: separate error mapping into its own package

* Fixed missing space

Co-authored-by: Krisztián Gödrei <[email protected]>
Co-authored-by: Tamas Chrenoczy-Nagy <[email protected]>
  • Loading branch information
3 people authored Nov 9, 2020
1 parent 9876195 commit c33b9a8
Show file tree
Hide file tree
Showing 6 changed files with 789 additions and 10 deletions.
68 changes: 68 additions & 0 deletions errormapper/errormapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package errormapper

import (
"regexp"

"github.com/bitrise-io/bitrise-init/step"
)

const (
// UnknownParam ...
UnknownParam = "::unknown::"
// DetailedErrorRecKey ...
DetailedErrorRecKey = "DetailedError"
)

// DetailedError ...
type DetailedError struct {
Title string
Description string
}

// NewDetailedErrorRecommendation ...
func NewDetailedErrorRecommendation(detailedError DetailedError) step.Recommendation {
return step.Recommendation{
DetailedErrorRecKey: detailedError,
}
}

// DetailedErrorBuilder ...
type DetailedErrorBuilder = func(...string) DetailedError

// GetParamAt ...
func GetParamAt(index int, params []string) string {
res := UnknownParam
if index >= 0 && len(params) > index {
res = params[index]
}
return res
}

// PatternToDetailedErrorBuilder ...
type PatternToDetailedErrorBuilder map[string]DetailedErrorBuilder

// PatternErrorMatcher ...
type PatternErrorMatcher struct {
DefaultBuilder DetailedErrorBuilder
PatternToBuilder PatternToDetailedErrorBuilder
}

// Run ...
func (m *PatternErrorMatcher) Run(msg string) step.Recommendation {
for pattern, builder := range m.PatternToBuilder {
re := regexp.MustCompile(pattern)
if re.MatchString(msg) {
// [search_string, match1, match2, ...]
matches := re.FindStringSubmatch((msg))
// Drop the first item, which is always the search_string itself
// [search_string] -> []
// [search_string, match1, ...] -> [match1, ...]
params := matches[1:]
detail := builder(params...)
return NewDetailedErrorRecommendation(detail)
}
}

detail := m.DefaultBuilder(msg)
return NewDetailedErrorRecommendation(detail)
}
225 changes: 225 additions & 0 deletions errormapper/errormapper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package errormapper

import (
"fmt"
"reflect"
"testing"

"github.com/bitrise-io/bitrise-init/step"
)

func Test_newDetailedErrorRecommendation(t *testing.T) {
type args struct {
detailedError DetailedError
}
tests := []struct {
name string
args args
want step.Recommendation
}{
{
name: "newDetailedErrorRecommendation with nil",
args: args{
detailedError: DetailedError{
Title: "TestTitle",
Description: "TestDesciption",
},
},
want: step.Recommendation{
DetailedErrorRecKey: DetailedError{
Title: "TestTitle",
Description: "TestDesciption",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewDetailedErrorRecommendation(tt.args.detailedError); !reflect.DeepEqual(got, tt.want) {
t.Errorf("newDetailedErrorRecommendation() = %v, want %v", got, tt.want)
}
})
}
}

func Test_getParamAt(t *testing.T) {
type args struct {
index int
params []string
}
tests := []struct {
name string
args args
want string
}{
{
name: "getParamsAt(0, nil)",
args: args{
index: 0,
params: nil,
},
want: UnknownParam,
},
{
name: "getParamsAt(0, [])",
args: args{
index: 0,
params: []string{},
},
want: UnknownParam,
},
{
name: "getParamsAt(-1, ['1', '2', '3', '4', '5'])",
args: args{
index: -1,
params: []string{"1", "2", "3", "4", "5"},
},
want: UnknownParam,
},
{
name: "getParamsAt(5, ['1', '2', '3', '4', '5'])",
args: args{
index: 5,
params: []string{"1", "2", "3", "4", "5"},
},
want: UnknownParam,
},
{
name: "getParamsAt(0, ['1', '2', '3', '4', '5'])",
args: args{
index: 0,
params: []string{"1", "2", "3", "4", "5"},
},
want: "1",
},
{
name: "getParamsAt(4, ['1', '2', '3', '4', '5'])",
args: args{
index: 4,
params: []string{"1", "2", "3", "4", "5"},
},
want: "5",
},
{
name: "getParamsAt(2, ['1', '2', '3', '4', '5'])",
args: args{
index: 2,
params: []string{"1", "2", "3", "4", "5"},
},
want: "3",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetParamAt(tt.args.index, tt.args.params); got != tt.want {
t.Errorf("getParamAt() = %v, want %v", got, tt.want)
}
})
}
}

func TestPatternErrorMatcher_Run(t *testing.T) {
type fields struct {
defaultBuilder DetailedErrorBuilder
patternToBuilder PatternToDetailedErrorBuilder
}
type args struct {
msg string
}
tests := []struct {
name string
fields fields
args args
want step.Recommendation
}{
{
name: "Run with defaultBuilder",
fields: fields{
defaultBuilder: func(params ...string) DetailedError {
return DetailedError{
Title: "T",
Description: "D",
}
},
patternToBuilder: map[string]DetailedErrorBuilder{},
},
args: args{
msg: "Test",
},
want: step.Recommendation{
DetailedErrorRecKey: DetailedError{
Title: "T",
Description: "D",
},
},
},
{
name: "Run with patternBuilder",
fields: fields{
defaultBuilder: func(params ...string) DetailedError {
return DetailedError{
Title: "DefaultTitle",
Description: "DefaultDesc",
}
},
patternToBuilder: map[string]DetailedErrorBuilder{
"Test": func(params ...string) DetailedError {
return DetailedError{
Title: "PatternTitle",
Description: "PatternDesc",
}
},
},
},
args: args{
msg: "Test",
},
want: step.Recommendation{
DetailedErrorRecKey: DetailedError{
Title: "PatternTitle",
Description: "PatternDesc",
},
},
},
{
name: "Run with patternBuilder with param",
fields: fields{
defaultBuilder: func(params ...string) DetailedError {
return DetailedError{
Title: "DefaultTitle",
Description: "DefaultDesc",
}
},
patternToBuilder: map[string]DetailedErrorBuilder{
"Test (.+)!": func(params ...string) DetailedError {
p := GetParamAt(0, params)
return DetailedError{
Title: "PatternTitle",
Description: fmt.Sprintf("PatternDesc: '%s'", p),
}
},
},
},
args: args{
msg: "Test WithPatternParam!",
},
want: step.Recommendation{
DetailedErrorRecKey: DetailedError{
Title: "PatternTitle",
Description: "PatternDesc: 'WithPatternParam'",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &PatternErrorMatcher{
DefaultBuilder: tt.fields.defaultBuilder,
PatternToBuilder: tt.fields.patternToBuilder,
}
if got := m.Run(tt.args.msg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("PatternErrorMatcher.Run() = %v, want %v", got, tt.want)
}
})
}
}
20 changes: 14 additions & 6 deletions gitclone/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ import (
"github.com/bitrise-io/go-utils/pathutil"
"github.com/bitrise-io/go-utils/retry"
"github.com/bitrise-io/go-utils/sliceutil"
"github.com/bitrise-steplib/steps-git-clone/errormapper"
)

const (
checkoutFailedTag = "checkout_failed"
fetchFailedTag = "fetch_failed"
branchRecKey = "BranchRecommendation"
)

func isOriginPresent(gitCmd git.Git, dir, repoURL string) (bool, error) {
Expand Down Expand Up @@ -291,14 +298,14 @@ func manualMerge(gitCmd git.Git, repoURL, prRepoURL, branch, commit, branchDest

func parseListBranchesOutput(output string) map[string][]string {
lines := strings.Split(output, "\n")
branchesByRemote := map[string][]string {}
branchesByRemote := map[string][]string{}
for _, line := range lines {
line = strings.Trim(line, " ")
split := strings.Split(line, "/")

remote := split[0]
branch := ""
if (len(split) > 1) {
if len(split) > 1 {
branch = strings.Join(split[1:], "/")
branches := branchesByRemote[remote]
branches = append(branches, branch)
Expand Down Expand Up @@ -339,17 +346,18 @@ func checkout(gitCmd git.Git, arg, branch string, depth int, isTag bool) *step.E
branches := branchesByRemote[defaultRemoteName]
if branchesErr == nil && !sliceutil.IsStringInSlice(branch, branches) {
return newStepErrorWithRecommendations(
"fetch_failed",
fetchFailedTag,
fmt.Errorf("fetch failed: invalid branch selected: %s, available branches: %s: %v", branch, strings.Join(branches, ", "), err),
"Fetching repository has failed",
step.Recommendation{
"BranchRecommendation": branches,
branchRecKey: branches,
errormapper.DetailedErrorRecKey: newFetchFailedInvalidBranchDetailedError(branch),
},
)
}
}
return newStepError(
"fetch_failed",
fetchFailedTag,
fmt.Errorf("fetch failed, error: %v", err),
"Fetching repository has failed",
)
Expand All @@ -358,7 +366,7 @@ func checkout(gitCmd git.Git, arg, branch string, depth int, isTag bool) *step.E
if err := run(gitCmd.Checkout(arg)); err != nil {
if depth == 0 {
return newStepError(
"checkout_failed",
checkoutFailedTag,
fmt.Errorf("checkout failed (%s), error: %v", arg, err),
"Checkout has failed",
)
Expand Down
7 changes: 4 additions & 3 deletions gitclone/gitclone.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ type Config struct {
}

const (
trimEnding = "..."
defaultRemoteName = "origin"
trimEnding = "..."
defaultRemoteName = "origin"
updateSubmodelFailedTag = "update_submodule_failed"
)

func printLogAndExportEnv(gitCmd git.Git, format, env string, maxEnvLength int) error {
Expand Down Expand Up @@ -160,7 +161,7 @@ func Execute(cfg Config) *step.Error {
if cfg.UpdateSubmodules {
if err := run(gitCmd.SubmoduleUpdate()); err != nil {
return newStepError(
"update_submodule_failed",
updateSubmodelFailedTag,
fmt.Errorf("submodule update: %v", err),
"Updating submodules has failed",
)
Expand Down
Loading

0 comments on commit c33b9a8

Please sign in to comment.