From 791983faae671303a63b9df91a07ca17b36d476e Mon Sep 17 00:00:00 2001 From: Nightapes Date: Sun, 21 Jul 2019 15:07:13 +0200 Subject: [PATCH] test(*): add unit tests --- .gitignore | 1 + cmd/go-semantic-release/commands/ci.go | 37 +++++ go.mod | 2 +- internal/analyzer/analyzer.go | 14 +- internal/analyzer/analyzer_test.go | 16 +++ internal/analyzer/angular.go | 8 +- internal/analyzer/angular_test.go | 173 ++++++++++++++++++++++++ internal/cache/cache_test.go | 70 ++++++++++ internal/changelog/changelog_test.go | 120 ++++++++++++++++ internal/ci/ci_test.go | 58 +++++--- internal/ci/git.go | 2 +- pkg/semanticrelease/semantic-release.go | 3 +- 12 files changed, 477 insertions(+), 27 deletions(-) create mode 100644 cmd/go-semantic-release/commands/ci.go create mode 100644 internal/analyzer/analyzer_test.go create mode 100644 internal/analyzer/angular_test.go create mode 100644 internal/cache/cache_test.go create mode 100644 internal/changelog/changelog_test.go diff --git a/.gitignore b/.gitignore index 068d872..e623bb9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ go-semantic-release .version .vscode/settings.json CHANGELOG.md +cover.html diff --git a/cmd/go-semantic-release/commands/ci.go b/cmd/go-semantic-release/commands/ci.go new file mode 100644 index 0000000..8ff26dd --- /dev/null +++ b/cmd/go-semantic-release/commands/ci.go @@ -0,0 +1,37 @@ +package commands + +import ( + "github.com/Nightapes/go-semantic-release/internal/ci" + "github.com/Nightapes/go-semantic-release/internal/gitutil" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(ciCmd) +} + +var ciCmd = &cobra.Command{ + Use: "ci", + Short: "ci configured artifact from release config", + RunE: func(cmd *cobra.Command, args []string) error { + + repository, err := cmd.Flags().GetString("repository") + if err != nil { + return err + } + + util, err := gitutil.New(repository) + if err != nil { + return err + } + + config, err := ci.GetCIProvider(util, ci.ReadAllEnvs()) + if err != nil { + return err + } + log.Infof("Found ci %v", config) + + return nil + }, +} diff --git a/go.mod b/go.mod index f966aa2..6d9f669 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect google.golang.org/appengine v1.6.1 // indirect - gopkg.in/src-d/go-billy.v4 v4.3.1 // indirect + gopkg.in/src-d/go-billy.v4 v4.3.1 gopkg.in/src-d/go-git.v4 v4.12.0 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/internal/analyzer/analyzer.go b/internal/analyzer/analyzer.go index 7f1f21f..8e6d1be 100644 --- a/internal/analyzer/analyzer.go +++ b/internal/analyzer/analyzer.go @@ -15,11 +15,17 @@ type Analyzer struct { Config config.ChangelogConfig } +//Release types, like major +type Release string + +//Scope of the commit, like feat, fix,.. +type Scope string + //Rule for commits type Rule struct { Tag string TagString string - Release string + Release Release Changelog bool } @@ -32,7 +38,7 @@ type analyzeCommit interface { type AnalyzedCommit struct { Commit gitutil.Commit ParsedMessage string - Scope string + Scope Scope ParsedBreakingChangeMessage string Tag string TagString string @@ -62,9 +68,9 @@ func (a *Analyzer) GetRules() []Rule { } // Analyze commits and return commits splitted by major,minor,patch -func (a *Analyzer) Analyze(commits []gitutil.Commit) map[string][]AnalyzedCommit { +func (a *Analyzer) Analyze(commits []gitutil.Commit) map[Release][]AnalyzedCommit { - analyzedCommits := make(map[string][]AnalyzedCommit) + analyzedCommits := make(map[Release][]AnalyzedCommit) analyzedCommits["major"] = make([]AnalyzedCommit, 0) analyzedCommits["minor"] = make([]AnalyzedCommit, 0) analyzedCommits["patch"] = make([]AnalyzedCommit, 0) diff --git a/internal/analyzer/analyzer_test.go b/internal/analyzer/analyzer_test.go new file mode 100644 index 0000000..0002a73 --- /dev/null +++ b/internal/analyzer/analyzer_test.go @@ -0,0 +1,16 @@ +package analyzer_test + +import ( + "testing" + + "github.com/Nightapes/go-semantic-release/internal/analyzer" + "github.com/Nightapes/go-semantic-release/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestAnalyzer(t *testing.T) { + + _, err := analyzer.New("unknown", config.ChangelogConfig{}) + assert.Error(t, err) + +} diff --git a/internal/analyzer/angular.go b/internal/analyzer/angular.go index 391085c..47fe6d2 100644 --- a/internal/analyzer/angular.go +++ b/internal/analyzer/angular.go @@ -89,19 +89,19 @@ func (a *angular) analyze(commit gitutil.Commit, rule Rule) (AnalyzedCommit, boo if len(matches) >= 1 { if len(matches[0]) >= 3 { - analyzed.Scope = matches[0][2] + analyzed.Scope = Scope(matches[0][2]) message := strings.Join(matches[0][3:], "") if !strings.Contains(message, "BREAKING CHANGE:") { - analyzed.ParsedMessage = message + analyzed.ParsedMessage = strings.Trim(message, " ") log.Tracef("%s: found %s", commit.Message, rule.Tag) return analyzed, false, nil } breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2) - analyzed.ParsedMessage = breakingChange[0] - analyzed.ParsedBreakingChangeMessage = breakingChange[1] + analyzed.ParsedMessage = strings.Trim(breakingChange[0], " ") + analyzed.ParsedBreakingChangeMessage = strings.Trim(breakingChange[1], " ") log.Tracef(" %s, BREAKING CHANGE found", commit.Message) return analyzed, true, nil diff --git a/internal/analyzer/angular_test.go b/internal/analyzer/angular_test.go new file mode 100644 index 0000000..c49b046 --- /dev/null +++ b/internal/analyzer/angular_test.go @@ -0,0 +1,173 @@ +package analyzer_test + +import ( + "testing" + + "github.com/Nightapes/go-semantic-release/internal/analyzer" + "github.com/Nightapes/go-semantic-release/internal/gitutil" + "github.com/Nightapes/go-semantic-release/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestAngular(t *testing.T) { + + testConfigs := []struct { + testCase string + commits []gitutil.Commit + analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit + }{ + { + testCase: "feat", + analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ + "minor": []analyzer.AnalyzedCommit{ + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "feat(internal/changelog): my first commit", + Author: "me", + Hash: "12345667", + }, + Scope: "internal/changelog", + ParsedMessage: "my first commit", + Tag: "feat", + TagString: "Features", + Print: true, + }, + }, + "major": []analyzer.AnalyzedCommit{}, + "patch": []analyzer.AnalyzedCommit{}, + "none": []analyzer.AnalyzedCommit{}, + }, + commits: []gitutil.Commit{ + gitutil.Commit{ + Message: "feat(internal/changelog): my first commit", + Author: "me", + Hash: "12345667", + }, + }, + }, + { + testCase: "feat breaking change", + analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ + "minor": []analyzer.AnalyzedCommit{ + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "feat(internal/changelog): my first commit", + Author: "me", + Hash: "12345667", + }, + Scope: "internal/changelog", + ParsedMessage: "my first commit", + Tag: "feat", + TagString: "Features", + Print: true, + }, + }, + "major": []analyzer.AnalyzedCommit{ + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2", + Author: "me", + Hash: "12345668", + }, + Scope: "internal/changelog", + ParsedMessage: "my first break", + Tag: "feat", + TagString: "Features", + Print: true, + ParsedBreakingChangeMessage: "change api to v2", + }, + }, + "patch": []analyzer.AnalyzedCommit{}, + "none": []analyzer.AnalyzedCommit{}, + }, + commits: []gitutil.Commit{ + gitutil.Commit{ + Message: "feat(internal/changelog): my first commit", + Author: "me", + Hash: "12345667", + }, + gitutil.Commit{ + Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2", + Author: "me", + Hash: "12345668", + }, + }, + }, + { + testCase: "invalid", + analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ + "minor": []analyzer.AnalyzedCommit{}, + "major": []analyzer.AnalyzedCommit{}, + "patch": []analyzer.AnalyzedCommit{}, + "none": []analyzer.AnalyzedCommit{}, + }, + commits: []gitutil.Commit{ + gitutil.Commit{ + Message: "internal/changelog: my first commit", + Author: "me", + Hash: "12345667", + }, + }, + }, + { + testCase: "feat and build", + analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ + "minor": []analyzer.AnalyzedCommit{ + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "feat(internal/changelog): my first commit", + Author: "me", + Hash: "12345667", + }, + Scope: "internal/changelog", + ParsedMessage: "my first commit", + Tag: "feat", + TagString: "Features", + Print: true, + }, + }, + "none": []analyzer.AnalyzedCommit{ + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "build(internal/changelog): my first build", + Author: "me", + Hash: "12345668", + }, + Scope: "internal/changelog", + ParsedMessage: "my first build", + Tag: "build", + TagString: "Changes to CI/CD", + Print: false, + ParsedBreakingChangeMessage: "", + }, + }, + "patch": []analyzer.AnalyzedCommit{}, + "major": []analyzer.AnalyzedCommit{}, + }, + commits: []gitutil.Commit{ + gitutil.Commit{ + Message: "feat(internal/changelog): my first commit", + Author: "me", + Hash: "12345667", + }, + gitutil.Commit{ + Message: "build(internal/changelog): my first build", + Author: "me", + Hash: "12345668", + }, + }, + }, + } + + angular, err := analyzer.New("angular", config.ChangelogConfig{}) + assert.NoError(t, err) + + for _, test := range testConfigs { + analyzedCommits := angular.Analyze(test.commits) + assert.Equalf(t, test.analyzedCommits["major"], analyzedCommits["major"], "Testcase %s should have major commits", test.testCase) + assert.Equalf(t, test.analyzedCommits["minor"], analyzedCommits["minor"], "Testcase %s should have minor commits", test.testCase) + assert.Equalf(t, test.analyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase) + assert.Equalf(t, test.analyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase) + } + +} diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go new file mode 100644 index 0000000..7a7b8b2 --- /dev/null +++ b/internal/cache/cache_test.go @@ -0,0 +1,70 @@ +package cache_test + +import ( + "testing" + + "github.com/Nightapes/go-semantic-release/internal/cache" + "github.com/stretchr/testify/assert" + "io/ioutil" + "os" + "path" +) + +func TestReadCacheNotFound(t *testing.T) { + + _, err := cache.Read("notfound/dir") + assert.Errorf(t, err, "Read non exsiting file") + +} + +func TestReadCacheInvalidContent(t *testing.T) { + + dir, err := ioutil.TempDir("", "prefix") + assert.NoError(t, err) + defer os.RemoveAll(dir) + + completePath := path.Join(path.Dir(dir), ".version") + brokenContent := []byte("hello broken\ngo: lang\n") + ioutil.WriteFile(completePath, brokenContent, 0644) + + _, readError := cache.Read(dir) + assert.Errorf(t, readError, "Should give error, when broken content") + +} + +func TestWriteAndReadCache(t *testing.T) { + + dir, err := ioutil.TempDir("", "prefix") + + assert.NoError(t, err) + + content := cache.ReleaseVersion{ + Last: cache.ReleaseVersionEntry{ + Commit: "12345", + Version: "1.0.0", + }, + Next: cache.ReleaseVersionEntry{ + Commit: "12346", + Version: "1.1.0", + }, + Branch: "master", + Draft: true, + } + + defer os.RemoveAll(dir) + + writeError := cache.Write(dir, content) + assert.NoErrorf(t, writeError, "Should write file") + result, readError := cache.Read(dir) + assert.NoErrorf(t, readError, "Should read file") + + assert.Equal(t, &content, result) + +} + +func TestWriteNotFound(t *testing.T) { + + err := cache.Write("notfound/dir", cache.ReleaseVersion{}) + assert.Errorf(t, err, "Write non exsiting file") + +} diff --git a/internal/changelog/changelog_test.go b/internal/changelog/changelog_test.go new file mode 100644 index 0000000..b4a473a --- /dev/null +++ b/internal/changelog/changelog_test.go @@ -0,0 +1,120 @@ +package changelog_test + +import ( + "testing" + + "github.com/Nightapes/go-semantic-release/internal/analyzer" + "github.com/Nightapes/go-semantic-release/internal/changelog" + "github.com/Nightapes/go-semantic-release/internal/gitutil" + "github.com/Nightapes/go-semantic-release/internal/shared" + "github.com/Nightapes/go-semantic-release/pkg/config" + "github.com/stretchr/testify/assert" + "time" +) + +func TestChangelog(t *testing.T) { + + templateConfig := shared.ChangelogTemplateConfig{ + CommitURL: "https://commit.url", + CompareURL: "https://compare.url", + Hash: "hash", + Version: "1.0.0", + } + + testConfigs := []struct { + testCase string + analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit + result *shared.GeneratedChangelog + hasError bool + }{ + { + testCase: "feat", + analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ + "minor": []analyzer.AnalyzedCommit{ + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "feat(test): my first commit", + Author: "me", + Hash: "12345667", + }, + Scope: "internal/changelog", + ParsedMessage: "my first commit", + Tag: "feat", + TagString: "Features", + Print: true, + }, + }, + }, + result: &shared.GeneratedChangelog{ + Title: "v1.0.0 (2019-07-19)", + Content: "# v1.0.0 (2019-07-19)\n\n ### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url)) \n\n ", + }, + hasError: false, + }, + { + testCase: "feat breaking change", + analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ + "minor": []analyzer.AnalyzedCommit{ + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "feat(test): my first commit", + Author: "me", + Hash: "12345667", + }, + Scope: "internal/changelog", + ParsedMessage: "my first commit", + Tag: "feat", + TagString: "Features", + Print: true, + }, + analyzer.AnalyzedCommit{ + Commit: gitutil.Commit{ + Message: "feat(test): my first break: BREAKING CHANGE: change api to v2", + Author: "me", + Hash: "12345668", + }, + Scope: "internal/changelog", + ParsedMessage: "my first break", + Tag: "feat", + TagString: "Features", + Print: true, + ParsedBreakingChangeMessage: "change api to v2", + }, + }, + }, + result: &shared.GeneratedChangelog{ + Title: "v1.0.0 (2019-07-19)", + Content: "# v1.0.0 (2019-07-19)\n\n## BREAKING CHANGES\n\n* **`internal/changelog`** change api to v2 \nintroduced by commit: \nmy first break ([1234566](https://commit.url)) \n\n ### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url)) \n\n ", + }, + hasError: false, + }, + } + + cl := changelog.New(&config.ReleaseConfig{}, []analyzer.Rule{ + { + Tag: "feat", + TagString: "Features", + Release: "minor", + Changelog: true, + }, + { + Tag: "fix", + TagString: "Bug fixes", + Release: "patch", + Changelog: true, + }, + { + Tag: "build", + TagString: "Build", + Release: "none", + Changelog: false, + }, + }, time.Date(2019, 7, 19, 0, 0, 0, 0, time.UTC)) + + for _, config := range testConfigs { + generatedChangelog, err := cl.GenerateChanglog(templateConfig, config.analyzedCommits) + assert.Equalf(t, config.hasError, err != nil, "Testcase %s should have error: %t -> %s", config.testCase, config.hasError, err) + assert.Equalf(t, config.result, generatedChangelog, "Testcase %s should have generated changelog", config.testCase) + } + +} diff --git a/internal/ci/ci_test.go b/internal/ci/ci_test.go index b1e46bb..99205b0 100644 --- a/internal/ci/ci_test.go +++ b/internal/ci/ci_test.go @@ -1,16 +1,49 @@ package ci_test import ( + "fmt" "testing" + "time" "github.com/Nightapes/go-semantic-release/internal/ci" "github.com/Nightapes/go-semantic-release/internal/gitutil" "github.com/stretchr/testify/assert" + "gopkg.in/src-d/go-billy.v4/memfs" "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/storage/memory" ) -func TestSum(t *testing.T) { +func TestCi(t *testing.T) { + + fs := memfs.New() + + repository, err := git.Init(memory.NewStorage(), fs) + assert.NoError(t, err, "should open git repository") + + file, err := fs.Create("README.md") + assert.NoError(t, err, "should create file") + + w, err := repository.Worktree() + assert.NoError(t, err, "should get worktree") + + w.Add(file.Name()) + + status, err := w.Status() + fmt.Println(status) + + gitUtilInMemory := &gitutil.GitUtil{ + Repository: repository, + } + + newCommit, err := w.Commit("fix(test): add a commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "John Doe", + Email: "john@doe.org", + When: time.Now(), + }, + }) + testConfigs := []struct { service string envs map[string]string @@ -23,14 +56,14 @@ func TestSum(t *testing.T) { result: nil, hasError: true, }, - // { - // service: "Git", - // envs: map[string]string{ - // "CI": "true", - // }, - // result: &ci.ProviderConfig{IsPR: true, PR: "10", PRBranch: "pr", Branch: "master", Tag: "TAG", Commit: "190bfd6aa60022afd0ef830342cfb07e33c45f37", BuildURL: "https://travis-ci.com/owner/repo/builds/1234", Service: "travis", Name: "Travis CI"}, - // hasError: false, - // }, + { + service: "Git", + envs: map[string]string{ + "CI": "true", + }, + result: &ci.ProviderConfig{IsPR: false, PR: "", PRBranch: "", Branch: "master", Tag: "", Commit: newCommit.String(), BuildURL: "", Service: "git", Name: "Git only"}, + hasError: false, + }, { service: "Travis PR", envs: map[string]string{ @@ -60,13 +93,6 @@ func TestSum(t *testing.T) { }, } - repository, err := git.Init(memory.NewStorage(), nil) - assert.NoError(t, err, "should open git repository") - - gitUtilInMemory := &gitutil.GitUtil{ - Repository: repository, - } - for _, config := range testConfigs { provider, err := ci.GetCIProvider(gitUtilInMemory, config.envs) assert.Equalf(t, config.hasError, err != nil, "Service %s should have error: %t -> %s", config.service, config.hasError, err) diff --git a/internal/ci/git.go b/internal/ci/git.go index 075cb83..7f814a1 100644 --- a/internal/ci/git.go +++ b/internal/ci/git.go @@ -28,7 +28,7 @@ func (t Git) detect(envs map[string]string) (*ProviderConfig, error) { } return &ProviderConfig{ - Service: "Git", + Service: "git", Name: "Git only", Commit: hash, Branch: currentBranch, diff --git a/pkg/semanticrelease/semantic-release.go b/pkg/semanticrelease/semantic-release.go index b5a96b1..979ba2b 100644 --- a/pkg/semanticrelease/semantic-release.go +++ b/pkg/semanticrelease/semantic-release.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "strings" + "time" "github.com/Masterminds/semver" "github.com/Nightapes/go-semantic-release/internal/analyzer" @@ -199,7 +200,7 @@ func (s *SemanticRelease) GetChangelog(releaseVersion *shared.ReleaseVersion) (* log.Debugf("Found %d commits till last release", len(commits)) - c := changelog.New(s.config, s.analyzer.GetRules()) + c := changelog.New(s.config, s.analyzer.GetRules(), time.Now()) return c.GenerateChanglog(shared.ChangelogTemplateConfig{ Version: releaseVersion.Next.Version.String(), Hash: releaseVersion.Last.Commit,