Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8d6b6bc
♻️ Capitalize defaultOptions to DefaultOptions
akm Dec 7, 2024
14ef344
✅ Add the simplest test scenario
akm Dec 7, 2024
f9dbaf3
✨ Add testdir package
akm Dec 8, 2024
af68e0c
✨ Add testexec package
akm Dec 8, 2024
8520e3a
🔨 Make test run also for sub directories
akm Dec 8, 2024
05ab823
✨ Use filepath.Abs to get srcDir name even if it is just a dot
akm Dec 8, 2024
0e97985
✅ Add tests for guard
akm Dec 8, 2024
98e8803
✅ Add tests for no changes
akm Dec 8, 2024
2efdede
♻️ Extract testground package
akm Dec 8, 2024
ac5504e
✨ Add logging around testdir.Setup
akm Dec 8, 2024
df1ea72
✨ Add testdir.FromGoModRoot function
akm Dec 8, 2024
437137b
✨ Use testdir.FromGoModRoot in testground.Setup
akm Dec 8, 2024
1f88cf2
✨ Skip .git directory in copyEntry
akm Dec 8, 2024
9e5f213
✨ Add git configuratoin on GitHub Actions
akm Dec 8, 2024
3067eeb
✨ git config --global init.defaultBranch
akm Dec 8, 2024
a300492
✅ Suppress make's output in test
akm Dec 8, 2024
868b24e
✅♻️ Define aliases for testexec to simplify test description
akm Dec 8, 2024
8efb46e
✅♻️ Extract AssertStringNotChanged function
akm Dec 8, 2024
94fcff3
✅ Add test about commit message
akm Dec 8, 2024
7d39108
✅ Add testground.GitDiff function
akm Dec 8, 2024
7840fa7
✅ Add directory_option_test.go
akm Dec 8, 2024
fae19ea
✅ Don't run git config --global if git config file exists
akm Dec 8, 2024
06880d5
✅ Setup git before running test
akm Dec 8, 2024
6d60b9e
🔨 Remove new lines in setup git
akm Dec 8, 2024
a2951a2
🔧 Use multi lines YAML
akm Dec 8, 2024
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,11 @@ jobs:
- name: lint
run: make lint

- name: setup git
run: |-
git config --global user.email foo@example.com && \
git config --global user.name "Foo Bar" && \
git config --global init.defaultBranch main

- name: test
run: make test
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build:

.PHONY: test
test:
go test -v .
go test -v ./...

GOLANGCI_LINT_VERSION=v1.61.0
GOLANGCI_LINT = $(shell go env GOPATH)/bin/golangci-lint
Expand Down
2 changes: 1 addition & 1 deletion core/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func Help() {
* Use interactive mode for command which requires input such as "npx sv create" for SvelteKit.
git exec -i npx sv create my-app
`
optionItems, envVarItems := opts.HelpItemsAndEnvVarMappings[Options](defaultOptions, optionTypes)
optionItems, envVarItems := opts.HelpItemsAndEnvVarMappings[Options](DefaultOptions, optionTypes)

options := "Options:\n" + strings.Join(optionItems, "\n")
envVars := "Environment variable mapping:\n" + strings.Join(envVarItems, "\n")
Expand Down
6 changes: 3 additions & 3 deletions core/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Options struct {
Interactive bool
}

var defaultOptions = &Options{
var DefaultOptions = &Options{
Help: false,
Version: false,
Directory: "",
Expand Down Expand Up @@ -79,9 +79,9 @@ var optionTypes = func() []*opts.Definition[Options] {
}()

func newOptions() *Options {
return opts.NewOptions(optionTypes, defaultOptions)
return opts.NewOptions(optionTypes, DefaultOptions)
}

func ParseOptions(args []string) (*Options, []string, error) {
return opts.Parse(defaultOptions, optionTypes, args...)
return opts.Parse(DefaultOptions, optionTypes, args...)
}
58 changes: 58 additions & 0 deletions testdir/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package testdir

import (
"os"
"path/filepath"
"testing"
)

// GitRoot searches for the directory containing the .git directory starting from the current directory and moving upwards.
// If the .git directory is found, it returns the path to that directory.
// If the .git directory is not found, it fails the test.
func GitRoot(t *testing.T) string {
dir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current directory: %v", err)
}

for {
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
return dir
}

parent := filepath.Dir(dir)
if parent == dir {
t.Fatalf(".git directory not found")
}
dir = parent
}
}

// FromGitRoot returns the absolute path by joining the Git root directory with the provided relative path.
// If the .git directory is not found, it fails the test.
func FromGitRoot(t *testing.T, relpath string) string {
return filepath.Join(GitRoot(t), relpath)
}

func GoModRoot(t *testing.T) string {
dir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current directory: %v", err)
}

for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}

parent := filepath.Dir(dir)
if parent == dir {
t.Fatalf("go.mod file not found")
}
dir = parent
}
}

func FromGoModRoot(t *testing.T, relpath string) string {
return filepath.Join(GoModRoot(t), relpath)
}
107 changes: 107 additions & 0 deletions testdir/setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package testdir

import (
"io"
"io/fs"
"os"
"path/filepath"
"testing"
)

func Setup(t testing.TB, srcDir, destDir string) func() {
// srcDir を destDir にコピーして、コピーされたディレクトリにカレントディレクトリを移動する
// 戻り値は カレントディレクトリを元のディレクトリに戻し、コピーされたディレクトリを削除する関数
t.Logf("srcDir: %s\n", srcDir)
t.Logf("destDir: %s\n", destDir)

absSrcDir, err := filepath.Abs(srcDir)
if err != nil {
t.Fatalf("Failed to get absolute path: %v", err)
}
t.Logf("absSrcDir: %s\n", absSrcDir)

groundDir := filepath.Join(destDir, filepath.Base(absSrcDir)) // srcDir に . が指定された場合でもそのディレクトリ名を取得する
t.Logf("groundDir: %s\n", groundDir)
if err := os.MkdirAll(destDir, os.ModePerm); err != nil {
t.Fatalf("Failed to create directory: %v", err)
}

// Copy srcDir to destDir
copyDir(t, srcDir, groundDir)

// Save the current working directory
origWd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current directory: %v", err)
}

// Change to the new directory
if err := os.Chdir(groundDir); err != nil {
t.Fatalf("Failed to change directory: %v", err)
}

// Return a function to restore the original directory and clean up
return func() {
// Change back to the original directory
if err := os.Chdir(origWd); err != nil {
t.Fatalf("Failed to change back to original directory: %v", err)
}

// Remove the copied directory
if err := os.RemoveAll(groundDir); err != nil {
t.Fatalf("Failed to remove directory %s: %v", groundDir, err)
}
}
}

// Helper function to copy a directory
func copyDir(t testing.TB, src string, dest string) {
t.Logf("copyDir: %s -> %s\n", src, dest)

entries, err := os.ReadDir(src)
if err != nil {
t.Fatalf("Failed to read directory: %v", err)
}

err = os.MkdirAll(dest, os.ModePerm)
if err != nil {
t.Fatalf("Failed to create directory: %v", err)
}

for _, entry := range entries {
copyEntry(t, src, dest, entry)
}
}

func copyEntry(t testing.TB, src string, dest string, entry fs.DirEntry) {
if entry.Name() == ".git" {
t.Logf("skip .git directory\n")
return
}

srcPath := filepath.Join(src, entry.Name())
destPath := filepath.Join(dest, entry.Name())

if entry.IsDir() {
copyDir(t, srcPath, destPath)
return
}

t.Logf("copyEntry: %s -> %s\n", srcPath, destPath)

reader, err := os.Open(srcPath)
if err != nil {
t.Fatalf("Failed to open source file: %v", err)
}
defer reader.Close()

writer, err := os.Create(destPath)
if err != nil {
t.Fatalf("Failed to create destination file: %v", err)
}
defer writer.Close()

if _, err := io.Copy(writer, reader); err != nil {
t.Fatalf("Failed to copy file: %v", err)
}
}
29 changes: 29 additions & 0 deletions testexec/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package testexec

import (
"os"
"os/exec"
"testing"
)

func Run(t testing.TB, command string, args ...string) {
t.Helper()

cmd := exec.Command(command, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatalf("failed to run %s %v: %s", command, args, err)
}
}

func Stdout(t testing.TB, command string, args ...string) string {
t.Helper()

cmd := exec.Command(command, args...)
out, err := cmd.Output()
if err != nil {
t.Fatalf("failed to run %s %v: %s", command, args, err)
}
return string(out)
}
4 changes: 4 additions & 0 deletions tests/grounds/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*
.*
*.*
!.gitignore
17 changes: 17 additions & 0 deletions tests/scenes/01_basic/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
README.md:
@echo "Generating README.md"
@echo "This is a test" > README.md

WORK_FILE=work.txt

.PHONY: add-one
add-one:
@echo "One" >> $(WORK_FILE)

.PHONY: add-two
add-two:
@echo "Two" >> $(WORK_FILE)

.PHONY: add-three
add-three:
@echo "Three" >> $(WORK_FILE)
10 changes: 10 additions & 0 deletions tests/scenes/01_basic/aliases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package basic

import (
"github.com/akm/git-exec/testexec"
)

var (
run = testexec.Run
stdout = testexec.Stdout
)
28 changes: 28 additions & 0 deletions tests/scenes/01_basic/commit_message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package basic

import (
"testing"

"github.com/akm/git-exec/core"
"github.com/akm/git-exec/tests/testground"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCommitMessage(t *testing.T) {
defer testground.Setup(t)()

opts := *core.DefaultOptions
opts.Emoji = "🏭️"
opts.Prompt = "%"
opts.Template = `{{.Emoji}} {{.Prompt}} {{.Command}} [at {{.Location}}]`
err := core.Run(&opts, []string{"make", "README.md"})
require.NoError(t, err)

commitMessage := stdout(t, "git", "log", "-1", "--pretty=%B")
assert.Equal(t, `🏭️ % make README.md [at 01_basic]

Generating README.md

`, commitMessage)
}
45 changes: 45 additions & 0 deletions tests/scenes/01_basic/guard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package basic

import (
"strings"
"testing"

"github.com/akm/git-exec/core"
"github.com/akm/git-exec/tests/testground"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGuardUntrackedFiles(t *testing.T) {
defer testground.Setup(t)()
defer testground.AssertStringNotChanged(t, testground.GitLastCommitHash)()

run(t, "make", "add-one") // Let it not be committed

err := core.Run(core.DefaultOptions, []string{"make", "README.md"})
require.Error(t, err)
assert.Equal(t, `Quit processing because There are untracked files

Untracked files:
work.txt`, err.Error())
}

func TestGuardUncommittedChanes(t *testing.T) {
defer testground.Setup(t)()

// commit add-one
run(t, "make", "add-one")
run(t, "git", "add", ".")
run(t, "git", "commit", "-m", "add one")

defer testground.AssertStringNotChanged(t, testground.GitLastCommitHash)()
run(t, "make", "add-two") // Let it not be committed

err := core.Run(core.DefaultOptions, []string{"make", "README.md"})
require.Error(t, err)
assert.Equal(t, `Quit processing because There are uncommitted changes

Uncommitted changes:
`+strings.TrimSpace(stdout(t, "git", "diff")),
err.Error())
}
25 changes: 25 additions & 0 deletions tests/scenes/01_basic/happy_path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package basic

import (
"testing"

"github.com/akm/git-exec/core"
"github.com/akm/git-exec/tests/testground"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestHappyPath(t *testing.T) {
defer testground.Setup(t)()

// run(t, "make", "README.md")
err := core.Run(core.DefaultOptions, []string{"make", "README.md"})
require.NoError(t, err)

commitMessage := stdout(t, "git", "log", "-1", "--pretty=%B")
assert.Equal(t, `🤖 [01_basic] $ make README.md

Generating README.md

`, commitMessage)
}
Loading
Loading