Skip to content

Commit

Permalink
Merge pull request #192 from WillAbides/golden
Browse files Browse the repository at this point in the history
golden tests
  • Loading branch information
WillAbides authored Dec 9, 2023
2 parents 1969d79 + d58599d commit 958bef2
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 35 deletions.
14 changes: 5 additions & 9 deletions cmd/bindown/bootstrap_test.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
package main

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

"github.com/stretchr/testify/require"
"github.com/willabides/bindown/v4/internal/testutil"
)

func Test_bootstrapCmd(t *testing.T) {
output := filepath.Join(t.TempDir(), "foo", "bootstrap.sh")
targetDir := filepath.Join(t.TempDir(), "target")
output := filepath.Join(targetDir, "bootstrap.sh")
runner := newCmdRunner(t)

server := testutil.ServeFile(
t,
testdataPath("build-bootstrapper/checksums.txt"),
"testdata/bootstrap/checksums.txt",
"/WillAbides/bindown/releases/download/v4.8.0/checksums.txt",
"",
)
want, err := os.ReadFile(testdataPath("build-bootstrapper/bootstrap-bindown.sh"))
require.NoError(t, err)
result := runner.run("bootstrap", "--output", output, "--tag", "4.8.0", "--base-url", server.URL)
result.assertState(resultState{})
got, err := os.ReadFile(output)
require.NoError(t, err)
require.Equal(t, string(want), string(got))

testutil.CheckGoldenDir(t, targetDir, filepath.FromSlash("testdata/golden/bootstrap"))
}
46 changes: 33 additions & 13 deletions cmd/bindown/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -416,10 +417,7 @@ url_checksums:
stdout: `installed foo to`,
})
wantBin := filepath.Join(runner.tmpDir, "bin", "foo")
require.FileExists(t, wantBin)
stat, err := os.Stat(wantBin)
require.NoError(t, err)
testutil.AssertExecutable(t, stat.Mode())
testutil.AssertFile(t, wantBin, true, false)
})

t.Run("link raw file", func(t *testing.T) {
Expand All @@ -440,11 +438,7 @@ url_checksums:
stdout: `installed foo to`,
})
wantBin := filepath.Join(runner.tmpDir, "bin", "foo")
require.FileExists(t, wantBin)
stat, err := os.Lstat(wantBin)
require.NoError(t, err)
testutil.AssertExecutable(t, stat.Mode())
require.True(t, stat.Mode()&os.ModeSymlink != 0)
testutil.AssertFile(t, wantBin, true, true)
})

t.Run("bin in root", func(t *testing.T) {
Expand All @@ -464,10 +458,7 @@ url_checksums:
stdout: `installed foo to`,
})
wantBin := filepath.Join(runner.tmpDir, "bin", "foo")
require.FileExists(t, wantBin)
stat, err := os.Stat(wantBin)
require.NoError(t, err)
testutil.AssertExecutable(t, stat.Mode())
testutil.AssertFile(t, wantBin, true, false)
})

t.Run("wrong checksum", func(t *testing.T) {
Expand All @@ -488,3 +479,32 @@ url_checksums:
require.NoFileExists(t, filepath.Join(runner.tmpDir, "bin", "foo"))
})
}

func Test_wrapCmd(t *testing.T) {
t.Run("bindown path", func(t *testing.T) {
runner := newCmdRunner(t)
servePath := testdataPath("downloadables/runnable.tar.gz")
ts := testutil.ServeFile(t, servePath, "/runnable/runnable.tar.gz", "")
depURL := ts.URL + "/runnable/runnable.tar.gz"
runner.writeConfigYaml(fmt.Sprintf(`
dependencies:
runnable:
archive_path: bin/runnable.sh
url: %s
url_checksums:
%s: fb2fe41a34b77ee180def0cb9a222d8776a6e581106009b64f35983da291ab6e
`, depURL, depURL))
outputDir := filepath.Join(runner.tmpDir, "output")
runnable := filepath.Join(outputDir, "runnable")
result := runner.run("wrap", "runnable", "--bindown", testutil.BindownBin(), "--output", runnable)
result.assertState(resultState{stdout: runnable})
testutil.AssertFile(t, runnable, true, false)
testutil.CheckGoldenDir(t, outputDir, filepath.FromSlash("testdata/golden/wrap/bindown-path"))

// make sure it runs
cmd := exec.Command("sh", "-c", filepath.ToSlash(runnable))
out, err := cmd.Output()
require.NoError(t, err)
require.Equal(t, "Hello world", strings.TrimSpace(string(out)))
})
}
15 changes: 15 additions & 0 deletions cmd/bindown/testdata/golden/wrap/bindown-path/runnable
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
# Code generated by bindown. DO NOT EDIT.

set -e

bindown_bin="$(
CDPATH="" cd -- "$(dirname -- "$0")"
"../../bindown" install "runnable" \
--to-cache \
--configfile "../.bindown.yaml" \
--cache "../cache"
)"

exec "$bindown_bin" "$@"
20 changes: 19 additions & 1 deletion cmd/bindown/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type cmdRunner struct {

func newCmdRunner(t testing.TB) *cmdRunner {
t.Helper()
dir := t.TempDir()
dir := testTmp(t)
cacheDir := filepath.Join(dir, "cache")
configfile := filepath.Join(dir, ".bindown.yaml")
runner := &cmdRunner{
Expand Down Expand Up @@ -243,6 +243,24 @@ func testInDir(t testing.TB, dir string) {
assert.NoError(t, os.Chdir(dir))
}

// testTmp is like t.TempDir but it uses a directory in this repo's tmp directory.
// This is useful so that there can be a relative path from the resulting directory to
// directories in this repo.
func testTmp(t testing.TB) string {
t.Helper()
tmpDir := filepath.FromSlash("../../tmp/_test")
err := os.MkdirAll(tmpDir, 0o777)
require.NoError(t, err)
dir, err := os.MkdirTemp(tmpDir, "")
require.NoError(t, err)
t.Cleanup(func() {
assert.NoError(t, os.RemoveAll(dir))
})
abs, err := filepath.Abs(dir)
require.NoError(t, err)
return abs
}

func testdataPath(f string) string {
return filepath.Join(
filepath.FromSlash("../../internal/bindown/testdata"),
Expand Down
6 changes: 1 addition & 5 deletions internal/bindown/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,7 @@ dependencies:
})
require.NoError(t, err)
require.Equal(t, wantStdout, stdout.String())
require.True(t, FileExists(wantBin))
stat, err := os.Stat(wantBin)
require.NoError(t, err)
require.False(t, stat.IsDir())
testutil.AssertExecutable(t, stat.Mode())
testutil.AssertFile(t, wantBin, true, false)
})

t.Run("bin in root", func(t *testing.T) {
Expand Down
117 changes: 117 additions & 0 deletions internal/testutil/golden.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package testutil

import (
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func updateGoldenDir(t *testing.T, resultDir, goldenDir string) {
t.Helper()
if os.Getenv("UPDATE_GOLDEN") == "" {
return
}
require.NoError(t, os.RemoveAll(goldenDir))
err := filepath.WalkDir(resultDir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
fmt.Println(path)
relName := mustRel(t, resultDir, path)
return copyFile(path, filepath.Join(goldenDir, relName))
})
require.NoError(t, err)
}

func copyFile(src, dst string) (errOut error) {
err := os.MkdirAll(filepath.Dir(dst), 0o777)
if err != nil {
return err
}
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer func() {
e := dstFile.Close()
if errOut == nil {
errOut = e
}
}()
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer func() {
e := srcFile.Close()
if errOut == nil {
errOut = e
}
}()
_, err = io.Copy(dstFile, srcFile)
return err
}

func CheckGoldenDir(t *testing.T, resultDir, goldenDir string) {
t.Helper()
golden := true
t.Cleanup(func() {
t.Helper()
if !golden {
t.Log("To regenerate golden files run `UPDATE_GOLDEN=1 script/test`")
}
})
updateGoldenDir(t, resultDir, goldenDir)
checked := map[string]bool{}
_, err := os.Stat(goldenDir)
if err == nil {
assert.NoError(t, filepath.WalkDir(goldenDir, func(wantPath string, info fs.DirEntry, err error) error {
relPath := mustRel(t, goldenDir, wantPath)
if err != nil || info.IsDir() {
return err
}
if !assertEqualFiles(t, wantPath, filepath.Join(resultDir, relPath)) {
golden = false
}
checked[relPath] = true
return nil
}))
}
assert.NoError(t, filepath.Walk(resultDir, func(resultPath string, info fs.FileInfo, err error) error {
relPath := mustRel(t, resultDir, resultPath)
if err != nil || info.IsDir() || checked[relPath] {
return err
}
golden = false
return fmt.Errorf("found unexpected file:\n%s", relPath)
}))
}

func mustRel(t *testing.T, base, target string) string {
t.Helper()
rel, err := filepath.Rel(base, target)
require.NoError(t, err)
return rel
}

func assertEqualFiles(t *testing.T, want, got string) bool {
t.Helper()
wantBytes, err := os.ReadFile(want)
if !assert.NoError(t, err) {
return false
}
wantBytes = bytes.ReplaceAll(wantBytes, []byte("\r\n"), []byte("\n"))
gotBytes, err := os.ReadFile(got)
if !assert.NoError(t, err) {
return false
}
gotBytes = bytes.ReplaceAll(gotBytes, []byte("\r\n"), []byte("\n"))
return assert.Equal(t, string(wantBytes), string(gotBytes))
}
77 changes: 73 additions & 4 deletions internal/testutil/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,53 @@ package testutil

import (
"bytes"
"fmt"
"io/fs"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var bindownBinOnce sync.Once

func BindownBin() string {
bindownBinPath := filepath.Join(RepoRoot(), "tmp", "_test", "bindown")
bindownBinOnce.Do(func() {
cmd := exec.Command(goExec(), "build", "-o", bindownBinPath, "./cmd/bindown")
cmd.Dir = RepoRoot()
err := cmd.Run()
if err != nil {
panic(fmt.Sprintf("error building bindown: %v", err))
}
})
return bindownBinPath
}

// goExec returns te path to the go executable to use for tests.
func goExec() string {
goRoot := runtime.GOROOT()
if goRoot != "" {
p := filepath.Join(goRoot, "bin", "go")
info, err := os.Stat(p)
if err == nil && !info.IsDir() {
return p
}
}
p, err := exec.LookPath("go")
if err != nil {
panic("unable to find go executable")
}
return p
}

// ServeFile starts an HTTP server
func ServeFile(t *testing.T, file, path, query string) *httptest.Server {
t.Helper()
Expand Down Expand Up @@ -58,11 +93,45 @@ func ServeFiles(t *testing.T, files map[string]string) *httptest.Server {
return ts
}

func AssertExecutable(t *testing.T, mode fs.FileMode) {
// AssertFile asserts that the file at filename exists and has the given properties.
func AssertFile(t *testing.T, filename string, wantExecutable, wantLink bool) bool {
t.Helper()
// Windows doesn't have executable bits
linfo, err := os.Lstat(filename)
if !assert.NoError(t, err) {
return false
}
var ok bool
if wantLink {
ok = assert.True(t, linfo.Mode()&fs.ModeSymlink != 0, "expected %s to be a symlink", filename)
} else {
ok = assert.False(t, linfo.Mode()&fs.ModeSymlink != 0, "expected %s to not be a symlink", filename)
}
if !ok {
return false
}
// windows doesn't have executable bit so we can't check it
if runtime.GOOS == "windows" {
return
return false
}
info, err := os.Stat(filename)
if !assert.NoError(t, err) {
return false
}
if wantExecutable {
ok = assert.True(t, info.Mode()&0o110 != 0, "expected %s to be executable", filename)
} else {
ok = assert.False(t, info.Mode()&0o110 != 0, "expected %s to not be executable", filename)
}
return ok
}

// RepoRoot returns the absolute path to the root of this repo
func RepoRoot() string {
_, filename, _, _ := runtime.Caller(0)
dir := filepath.Join(filepath.Dir(filename), "..", "..")
abs, err := filepath.Abs(dir)
if err != nil {
panic(err)
}
assert.Equal(t, fs.FileMode(0o110), mode&0o110)
return abs
}
Loading

0 comments on commit 958bef2

Please sign in to comment.