Skip to content

Commit

Permalink
chore: Add tests on runner (#304)
Browse files Browse the repository at this point in the history
* chore: Add tests on runner

Signed-off-by: Valentin Kiselev <mrexox@evilmartians.com>

* fix: Linting issues

Signed-off-by: Valentin Kiselev <mrexox@evilmartians.com>

* chore: Update goreleaser ldflags with commit hash

Signed-off-by: Valentin Kiselev <mrexox@evilmartians.com>
  • Loading branch information
mrexox authored Aug 6, 2022
1 parent b1c0c9d commit 4217296
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ jobs:
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.47.3
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ builds:
goarch:
- amd64
- arm64
ldflags:
- -s -w -X github.com/evilmartians/lefthook/internal/version.commit={{.Commit}}
archives:
- id: lefthook
format: binary
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
COMMIT_HASH = $(shell git rev-parse HEAD)

build:
go build -ldflags "-X github.com/evilmartians/lefthook/internal/version.commit=$(COMMIT_HASH)" -o lefthook
go build -ldflags "-s -w -X github.com/evilmartians/lefthook/internal/version.commit=$(COMMIT_HASH)" -o lefthook

test:
go test -cpu 24 -race -count=1 -timeout=30s ./...
Expand All @@ -11,7 +11,7 @@ bench:

bin/golangci-lint:
@test -x $$(go env GOPATH)/bin/golangci-lint || \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.43.0
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.47.3

lint: bin/golangci-lint
$$(go env GOPATH)/bin/golangci-lint run
16 changes: 10 additions & 6 deletions internal/config/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,20 @@ func unmarshalScripts(s map[string]interface{}) (map[string]*Script, error) {
// `scripts` are unmarshalled manually because viper
// uses "." as a key delimiter. So, this definition:
//
// scripts:
// "example.sh":
// ```yaml
// scripts:
// "example.sh":
// runner: bash
// ```
//
// Unmarshals into this:
//
// scripts:
// example:
// sh:
// runner: bash
// ```yaml
// scripts:
// example:
// sh:
// runner: bash
// ```
//
// This is not an expected behavior and cannot be controlled yet
// Working with GetStringMap is the only way to get the structure "as is".
Expand Down
4 changes: 3 additions & 1 deletion internal/lefthook/runner/execute_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
"github.com/creack/pty"
)

func Execute(root string, args []string) (*bytes.Buffer, error) {
type CommandExecutor struct{}

func (e CommandExecutor) Execute(root string, args []string) (*bytes.Buffer, error) {
command := exec.Command("sh", "-c", strings.Join(args, " "))
rootDir, _ := filepath.Abs(root)
command.Dir = rootDir
Expand Down
4 changes: 3 additions & 1 deletion internal/lefthook/runner/execute_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"path/filepath"
)

func Execute(root string, args []string) (*bytes.Buffer, error) {
type CommandExecutor struct{}

func (e CommandExecutor) Execute(root string, args []string) (*bytes.Buffer, error) {
command := exec.Command(args[0], args[1:]...)
rootDir, _ := filepath.Abs(root)
command.Dir = rootDir
Expand Down
11 changes: 11 additions & 0 deletions internal/lefthook/runner/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package runner

import (
"bytes"
)

// Executor provides an interface for command execution.
// It is used here for testing purpose mostly.
type Executor interface {
Execute(root string, args []string) (*bytes.Buffer, error)
}
4 changes: 3 additions & 1 deletion internal/lefthook/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Runner struct {
args []string
failed bool
resultChan chan Result
exec Executor
}

func NewRunner(
Expand All @@ -45,6 +46,7 @@ func NewRunner(
hook: hook,
args: args,
resultChan: resultChan,
exec: CommandExecutor{},
}
}

Expand Down Expand Up @@ -270,7 +272,7 @@ func prepareFiles(command *config.Command, files []string) string {
}

func (r *Runner) run(name, root, failText string, args []string) {
out, err := Execute(root, args)
out, err := r.exec.Execute(root, args)

var execName string
if err != nil {
Expand Down
266 changes: 266 additions & 0 deletions internal/lefthook/runner/runner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package runner

import (
"bytes"
"errors"
"fmt"
"path/filepath"
"testing"

"github.com/spf13/afero"

"github.com/evilmartians/lefthook/internal/config"
"github.com/evilmartians/lefthook/internal/git"
)

type TestExecutor struct{}

func (e TestExecutor) Execute(root string, args []string) (out *bytes.Buffer, err error) {
out = bytes.NewBuffer(make([]byte, 0))

if args[0] == "success" {
err = nil
} else {
err = errors.New(args[0])
}

return
}

func TestRunAll(t *testing.T) {
root, err := filepath.Abs("src")
if err != nil {
t.Errorf("unexpected error: %s", err)
}

gitPath := filepath.Join(root, ".git")
repo := &git.Repository{
HooksPath: filepath.Join(gitPath, "hooks"),
RootPath: root,
GitPath: gitPath,
InfoPath: filepath.Join(gitPath, "info"),
}

for i, tt := range [...]struct {
name string
args []string
scriptDirs []string
existingFiles []string
hook *config.Hook
success, fail []Result
}{
{
name: "empty hook",
hook: &config.Hook{
Commands: map[string]*config.Command{},
Scripts: map[string]*config.Script{},
Piped: true,
},
},
{
name: "with simple command",
hook: &config.Hook{
Commands: map[string]*config.Command{
"test": {
Run: "success",
},
},
Scripts: map[string]*config.Script{},
},
success: []Result{{Name: "test", Status: StatusOk}},
},
{
name: "with multiple commands ran in parallel",
hook: &config.Hook{
Parallel: true,
Commands: map[string]*config.Command{
"test": {
Run: "success",
},
"lint": {
Run: "success",
},
"type-check": {
Run: "fail",
},
},
Scripts: map[string]*config.Script{},
},
success: []Result{
{Name: "lint", Status: StatusOk},
{Name: "test", Status: StatusOk},
},
fail: []Result{{Name: "type-check", Status: StatusErr}},
},
{
name: "with exclude tags",
hook: &config.Hook{
ExcludeTags: []string{"tests"},
Commands: map[string]*config.Command{
"test": {
Run: "success",
Tags: []string{"tests"},
},
"lint": {
Run: "success",
Tags: []string{"linters"},
},
},
Scripts: map[string]*config.Script{},
},
success: []Result{{Name: "lint", Status: StatusOk}},
},
{
name: "with skip boolean option",
hook: &config.Hook{
Commands: map[string]*config.Command{
"test": {
Run: "success",
Skip: true,
},
"lint": {
Run: "success",
},
},
Scripts: map[string]*config.Script{},
},
success: []Result{{Name: "lint", Status: StatusOk}},
},
{
name: "with skip merge",
existingFiles: []string{
filepath.Join(gitPath, "MERGE_HEAD"),
},
hook: &config.Hook{
Commands: map[string]*config.Command{
"test": {
Run: "success",
Skip: "merge",
},
"lint": {
Run: "success",
},
},
Scripts: map[string]*config.Script{},
},
success: []Result{{Name: "lint", Status: StatusOk}},
},
{
name: "with skip rebase and merge in an array",
existingFiles: []string{
filepath.Join(gitPath, "rebase-merge"),
filepath.Join(gitPath, "rebase-apply"),
},
hook: &config.Hook{
Commands: map[string]*config.Command{
"test": {
Run: "success",
Skip: []interface{}{"merge", "rebase"},
},
"lint": {
Run: "success",
},
},
Scripts: map[string]*config.Script{},
},
success: []Result{{Name: "lint", Status: StatusOk}},
},
{
name: "with fail test",
hook: &config.Hook{
Commands: map[string]*config.Command{
"test": {
Run: "fail",
FailText: "try 'success'",
},
},
Scripts: map[string]*config.Script{},
},
fail: []Result{{Name: "test", Status: StatusErr, Text: "try 'success'"}},
},
{
name: "with simple scripts",
scriptDirs: []string{
filepath.Join(root, config.DefaultSourceDir),
},
existingFiles: []string{
filepath.Join(root, config.DefaultSourceDir, "script.sh"),
filepath.Join(root, config.DefaultSourceDir, "failing.js"),
},
hook: &config.Hook{
Commands: map[string]*config.Command{},
Scripts: map[string]*config.Script{
"script.sh": {
Runner: "success",
},
"failing.js": {
Runner: "fail",
FailText: "install node",
},
},
},
success: []Result{{Name: "script.sh", Status: StatusOk}},
fail: []Result{{Name: "failing.js", Status: StatusErr, Text: "install node"}},
},
} {
fs := afero.NewMemMapFs()
repo.Fs = fs
resultChan := make(chan Result, len(tt.hook.Commands)+len(tt.hook.Scripts))
executor := TestExecutor{}
runner := &Runner{
fs: fs,
repo: repo,
hook: tt.hook,
args: tt.args,
resultChan: resultChan,
exec: executor,
}

for _, file := range tt.existingFiles {
if err := fs.MkdirAll(filepath.Base(file), 0o755); err != nil {
t.Errorf("unexpected error: %s", err)
}
if err := afero.WriteFile(fs, file, []byte{}, 0o755); err != nil {
t.Errorf("unexpected error: %s", err)
}
}

t.Run(fmt.Sprintf("%d: %s", i, tt.name), func(t *testing.T) {
runner.RunAll(tt.scriptDirs)
close(resultChan)

var success, fail []Result
for res := range resultChan {
if res.Status == StatusOk {
success = append(success, res)
} else {
fail = append(fail, res)
}
}

if !resultsEqual(success, tt.success) {
t.Errorf("success results are not matching")
}

if !resultsEqual(fail, tt.fail) {
t.Errorf("fail results are not matching")
}
})
}
}

func resultsEqual(a, b []Result) bool {
if len(a) != len(b) {
return false
}

for i, item := range a {
if item.Name != b[i].Name ||
item.Status != b[i].Status ||
item.Text != b[i].Text {
return false
}
}

return true
}

0 comments on commit 4217296

Please sign in to comment.