Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/76-inconsistent-version-prediction #81

Merged
merged 3 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions pkg/modes/detect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package modes

import (
"fmt"

"github.com/restechnica/semverbot/internal/util"
"github.com/restechnica/semverbot/pkg/semver"
)

func DetectModeFromString(str string, semverMap semver.Map, delimiters string) (detected Mode, err error) {
var modes []Mode

if modes, err = DetectModesFromString(str, semverMap, delimiters); err != nil {
return nil, err
}

if len(modes) == 0 {
return nil, fmt.Errorf(`failed to detect mode from string '%s' with delimiters '%s'`, str, delimiters)
}

var priority = map[string]int{
Patch: 1,
Minor: 2,
Major: 3,
}

for _, mode := range modes {
if detected == nil {
detected = mode
}

if priority[mode.String()] > priority[detected.String()] {
detected = mode
}
}

return detected, err
}

// DetectModesFromString detects multiple modes based on a string.
// Mode detection is limited to PatchMode, MinorMode, MajorMode.
// The order of a detected modes is relative to their position in the string.
// Returns a slice of the detected modes.
func DetectModesFromString(str string, semverMap semver.Map, delimiters string) (detected []Mode, err error) {
var substrings = util.SplitByDelimiterString(str, delimiters)

for _, substring := range substrings {
for level, values := range semverMap {
if util.SliceContainsString(values, substring) {
switch level {
case Patch:
detected = append(detected, NewPatchMode())
case Minor:
detected = append(detected, NewMinorMode())
case Major:
detected = append(detected, NewMajorMode())
default:
return nil, fmt.Errorf("failed to detect mode due to unsupported semver level: '%s'", level)
}
}
}
}

return detected, err
}
175 changes: 175 additions & 0 deletions pkg/modes/detect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package modes

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/restechnica/semverbot/pkg/semver"
)

func TestDetectModeFromString(t *testing.T) {
var semverMap = semver.Map{
Patch: {"fix", "bug", "patch"},
Minor: {"feature", "feat", "minor"},
Major: {"release", "major"},
}

type Test struct {
String string
Delimiters string
Name string
SemverMap semver.Map
Want Mode
}

var tests = []Test{
{Name: "DetectPatchModeWithSlash", String: "fix/some-bug", Delimiters: "/", SemverMap: semverMap, Want: NewPatchMode()},
{Name: "DetectPatchModeWithSquareBrackets", String: "[bug] some fix", Delimiters: "[]", SemverMap: semverMap, Want: NewPatchMode()},
{Name: "DetectMinorModeWithSlash", String: "feature/some-bug", Delimiters: "/", SemverMap: semverMap, Want: NewMinorMode()},
{Name: "DetectMinorModeWithRoundBrackets", String: "feat(subject): some changes", Delimiters: "():", SemverMap: semverMap, Want: NewMinorMode()},
{Name: "DetectMinorModeWithSquareBrackets", String: "[feature] some changes", Delimiters: "[]", SemverMap: semverMap, Want: NewMinorMode()},
{Name: "DetectMajorModeWithSlash", String: "release/some-bug", Delimiters: "/", SemverMap: semverMap, Want: NewMajorMode()},
{Name: "DetectMinorWithMultipleModes", String: "some [fix] and release/feat(subject)", Delimiters: "()[]/", SemverMap: semverMap, Want: NewMinorMode()},
{Name: "DetectMajorWithMultipleModes0", String: "[fix] some [feature] and [release]", Delimiters: "[]", SemverMap: semverMap, Want: NewMajorMode()},
{Name: "DetectMajorWithMultipleModes1", String: "[release] some [fix] test [feature]", Delimiters: "[]", SemverMap: semverMap, Want: NewMajorMode()},
{Name: "DetectMajorWithMultipleModes2", String: "[feature] some [release] test [fix]", Delimiters: "[]", SemverMap: semverMap, Want: NewMajorMode()},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
var got, err = DetectModeFromString(test.String, test.SemverMap, test.Delimiters)

assert.NoError(t, err)
assert.IsType(t, test.Want, got, `want: '%s, got: '%s'`, test.Want, got)
})
}

type ErrorTest struct {
String string
Delimiters string
Error error
Name string
SemverMap semver.Map
}

var errorTests = []ErrorTest{
{
Name: "DetectNothingWithEmptySemverMap",
String: "[feature] some changes",
Delimiters: "[]",
Error: fmt.Errorf(`failed to detect mode from string '[feature] some changes' with delimiters '[]'`),
SemverMap: semver.Map{},
},
{
Name: "DetectNothingWithEmptyDelimiters",
String: "[feature] some changes",
Delimiters: "",
Error: fmt.Errorf(`failed to detect mode from string '[feature] some changes' with delimiters ''`),
SemverMap: semverMap,
},
{
Name: "DetectNothingWithEmptyCommitMessage",
String: "",
Delimiters: "/",
Error: fmt.Errorf(`failed to detect mode from string '' with delimiters '/'`),
SemverMap: semverMap,
},
{
Name: "DetectNothingWithFaultySemverMap",
String: "[feature] some changes",
Delimiters: "[]",
Error: fmt.Errorf(`failed to detect mode due to unsupported semver level: 'mnr'`),
SemverMap: semver.Map{
"mnr": {"feature"},
},
},
}

for _, test := range errorTests {
t.Run(test.Name, func(t *testing.T) {
var _, got = DetectModeFromString(test.String, test.SemverMap, test.Delimiters)

assert.Error(t, got)
assert.Equal(t, test.Error, got, `want: '%s', got: '%s'`, test.Error, got)
})
}
}

func TestDetectModesFromString(t *testing.T) {
var semverMap = semver.Map{
Patch: {"fix", "bug", "patch"},
Minor: {"feature", "feat", "minor"},
Major: {"release", "major"},
}

type Test struct {
String string
Delimiters string
Name string
SemverMap semver.Map
Want []Mode
}

var tests = []Test{
{Name: "DetectPatchModeWithSlash", String: "fix/some-bug", Delimiters: "/", SemverMap: semverMap, Want: []Mode{NewPatchMode()}},
{Name: "DetectPatchModeWithSquareBrackets", String: "[bug] some fix", Delimiters: "[]", SemverMap: semverMap, Want: []Mode{NewPatchMode()}},
{Name: "DetectMinorModeWithSlash", String: "feature/some-bug", Delimiters: "/", SemverMap: semverMap, Want: []Mode{NewMinorMode()}},
{Name: "DetectMinorModeWithRoundBrackets", String: "feat(subject): some changes", Delimiters: "():", SemverMap: semverMap, Want: []Mode{NewMinorMode()}},
{Name: "DetectMinorModeWithSquareBrackets", String: "[feature] some changes", Delimiters: "[]", SemverMap: semverMap, Want: []Mode{NewMinorMode()}},
{Name: "DetectMajorModeWithSlash", String: "release/some-bug", Delimiters: "/", SemverMap: semverMap, Want: []Mode{NewMajorMode()}},
{Name: "DetectMultipleModes0", String: "[fix] some [feature] and [release]", Delimiters: "[]", SemverMap: semverMap, Want: []Mode{NewPatchMode(), NewMinorMode(), NewMajorMode()}},
{Name: "DetectMultipleModes1", String: "some [feature] and release/test and fix(subject)", Delimiters: "()[]/", SemverMap: semverMap, Want: []Mode{NewMinorMode()}},
{Name: "DetectMultipleModes2", String: "some [fix] and release/test and feat(subject)", Delimiters: "()[]/", SemverMap: semverMap, Want: []Mode{NewPatchMode()}},
{Name: "DetectMultipleModes3", String: "some [fix] and release/feat(subject)", Delimiters: "()[]/", SemverMap: semverMap, Want: []Mode{NewPatchMode(), NewMinorMode()}},
{Name: "DetectMultipleModesInOrder0", String: "[release] some [fix] test [feature]", Delimiters: "[]", SemverMap: semverMap, Want: []Mode{NewMajorMode(), NewPatchMode(), NewMinorMode()}},
{Name: "DetectMultipleModesInOrder1", String: "[feature] some [release] test [fix]", Delimiters: "[]", SemverMap: semverMap, Want: []Mode{NewMinorMode(), NewMajorMode(), NewPatchMode()}},
{Name: "DetectNothingWithEmptySemverMap", String: "feature/some-feature", Delimiters: "/", SemverMap: semver.Map{}, Want: []Mode{}},
{Name: "DetectNothingWithEmptyDelimiters", String: "feature/some-feature", Delimiters: "", SemverMap: semverMap, Want: []Mode{}},
{Name: "DetectNothingWithEmptyString", String: "", Delimiters: "/", SemverMap: semverMap, Want: []Mode{}},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
var got, err = DetectModesFromString(test.String, test.SemverMap, test.Delimiters)

assert.NoError(t, err)

assert.Equal(t, len(test.Want), len(got), `want: '%s, got: '%s'`, test.Want, got)

for i, mode := range got {
assert.IsType(t, test.Want[i], mode, `want: '%s, got: '%s'`, test.Want, got)
}
})
}

type ErrorTest struct {
String string
Delimiters string
Error error
Name string
SemverMap semver.Map
}

var errorTests = []ErrorTest{
{
Name: "DetectNothingWithFaultySemverMap",
String: "feature/some-feature",
Delimiters: "/",
Error: fmt.Errorf(`failed to detect mode due to unsupported semver level: 'mnr'`),
SemverMap: semver.Map{
"mnr": {"feature"},
},
},
}

for _, test := range errorTests {
t.Run(test.Name, func(t *testing.T) {
var _, got = DetectModesFromString(test.String, test.SemverMap, test.Delimiters)

assert.Error(t, got)
assert.Equal(t, test.Error, got, `want: '%s, got: '%s'`, test.Error, got)
})
}
}
30 changes: 1 addition & 29 deletions pkg/modes/gitbranch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package modes
import (
"fmt"

"github.com/restechnica/semverbot/internal/util"
"github.com/restechnica/semverbot/pkg/git"
"github.com/restechnica/semverbot/pkg/semver"
)
Expand Down Expand Up @@ -42,40 +41,13 @@ func (mode GitBranchMode) Increment(prefix string, suffix string, targetVersion
return nextVersion, fmt.Errorf("failed to increment version because the latest git commit is not a merge commit")
}

if matchedMode, err = mode.DetectMode(branchName); err != nil {
if matchedMode, err = DetectModeFromString(branchName, mode.SemverMap, mode.Delimiters); err != nil {
return nextVersion, err
}

return matchedMode.Increment(prefix, suffix, targetVersion)
}

// DetectMode detects the mode (patch, minor, major) based on a git branch name.
// Returns the detected mode.
func (mode GitBranchMode) DetectMode(branchName string) (detected Mode, err error) {
for key, values := range mode.SemverMap {
for _, value := range values {
if mode.isMatch(branchName, value) {
switch key {
case Patch:
return NewPatchMode(), err
case Minor:
return NewMinorMode(), err
case Major:
return NewMajorMode(), err
}
}
}
}

return detected, fmt.Errorf(`failed to detect mode from git branch name '%s' with delimiters '%s'`,
branchName, mode.Delimiters)
}

// isMatch returns true if a string is part of branch name, after splitting the branch name with delimiters
func (mode GitBranchMode) isMatch(branchName string, value string) bool {
return util.Contains(branchName, value, mode.Delimiters)
}

// String returns a string representation of an instance.
func (mode GitBranchMode) String() string {
return GitBranch
Expand Down
83 changes: 0 additions & 83 deletions pkg/modes/gitbranch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,89 +19,6 @@ func TestGitBranchMode_GitBranchConstant(t *testing.T) {
})
}

func TestGitBranchMode_DetectMode(t *testing.T) {
var semverMap = semver.Map{
Patch: {"fix", "bug"},
Minor: {"feature"},
Major: {"release"},
}

type Test struct {
BranchName string
Delimiters string
Name string
SemverMap semver.Map
Want Mode
}

var tests = []Test{
{Name: "DetectPatchMode", BranchName: "fix/some-bug", Delimiters: "/", SemverMap: semverMap, Want: NewPatchMode()},
{Name: "DetectMinorMode", BranchName: "feature/some-bug", Delimiters: "/", SemverMap: semverMap, Want: NewMinorMode()},
{Name: "DetectMajorMode", BranchName: "release/some-bug", Delimiters: "/", SemverMap: semverMap, Want: NewMajorMode()},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
var mode = NewGitBranchMode(test.Delimiters, test.SemverMap)
var got, err = mode.DetectMode(test.BranchName)

assert.NoError(t, err)
assert.IsType(t, test.Want, got, `want: '%s, got: '%s'`, test.Want, got)
})
}

type ErrorTest struct {
BranchName string
Delimiters string
Error error
Name string
SemverMap semver.Map
}

var errorTests = []ErrorTest{
{
Name: "DetectNothingWithEmptySemverMap",
BranchName: "feature/some-feature",
Delimiters: "/",
Error: fmt.Errorf(`failed to detect mode from git branch name 'feature/some-feature' with delimiters '/'`),
SemverMap: semver.Map{},
},
{
Name: "DetectNothingWithEmptyDelimiters",
BranchName: "feature/some-feature",
Delimiters: "",
Error: fmt.Errorf(`failed to detect mode from git branch name 'feature/some-feature' with delimiters ''`),
SemverMap: semverMap,
},
{
Name: "DetectNothingWithEmptyBranchName",
BranchName: "",
Delimiters: "/",
Error: fmt.Errorf(`failed to detect mode from git branch name '' with delimiters '/'`),
SemverMap: semverMap,
},
{
Name: "DetectNothingWithFaultySemverMap",
BranchName: "feature/some-feature",
Delimiters: "/",
Error: fmt.Errorf(`failed to detect mode from git branch name 'feature/some-feature' with delimiters '/'`),
SemverMap: semver.Map{
"mnr": {"feature"},
},
},
}

for _, test := range errorTests {
t.Run(test.Name, func(t *testing.T) {
var mode = NewGitBranchMode(test.Delimiters, test.SemverMap)
var _, got = mode.DetectMode(test.BranchName)

assert.Error(t, got)
assert.Equal(t, test.Error, got, `want: '%s, got: '%s'`, test.Error, got)
})
}
}

func TestGitBranchMode_Increment(t *testing.T) {
var semverMap = semver.Map{
Patch: {"fix", "bug"},
Expand Down
Loading