Skip to content

Commit

Permalink
Test against mock release API (#19)
Browse files Browse the repository at this point in the history
* account for Windows line endings

* Test against mock release API

* Improve input and HTTP response validation

* work around mime type differences

* make BinaryName platform-dependent to account for Windows

* run E2E tests in CI

* bump build timeout

* set default missing baseURL

* account for executables on Windows
  • Loading branch information
radeksimko committed Oct 27, 2021
1 parent 6727710 commit 9fcc92b
Show file tree
Hide file tree
Showing 48 changed files with 1,671 additions and 154 deletions.
13 changes: 10 additions & 3 deletions .github/workflows/test.yml
Expand Up @@ -46,7 +46,7 @@ jobs:
name: Test
needs: build
runs-on: ${{ matrix.os }}
timeout-minutes: 15
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
Expand All @@ -62,7 +62,14 @@ jobs:
- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Go tests
timeout-minutes: 10
- name: Unit tests
timeout-minutes: 5
run: |
go test -v ./...
- name: E2E tests
timeout-minutes: 15
env:
E2E_TESTING: 1
run: |
go test -v ./...
13 changes: 7 additions & 6 deletions build/git_revision.go
Expand Up @@ -11,12 +11,13 @@ import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
isrc "github.com/hashicorp/hc-install/internal/src"
"github.com/hashicorp/hc-install/internal/validators"
"github.com/hashicorp/hc-install/product"
)

var (
cloneTimeout = 1 * time.Minute
buildTimeout = 2 * time.Minute
buildTimeout = 5 * time.Minute
discardLogger = log.New(ioutil.Discard, "", 0)
)

Expand Down Expand Up @@ -49,11 +50,11 @@ func (gr *GitRevision) log() *log.Logger {
}

func (gr *GitRevision) Validate() error {
if gr.Product.Name == "" {
return fmt.Errorf("unknown product name")
if !validators.IsProductNameValid(gr.Product.Name) {
return fmt.Errorf("invalid product name: %q", gr.Product.Name)
}
if gr.Product.BinaryName == "" {
return fmt.Errorf("unknown binary name")
if !validators.IsBinaryNameValid(gr.Product.BinaryName()) {
return fmt.Errorf("invalid binary name: %q", gr.Product.BinaryName())
}

bi := gr.Product.BuildInstructions
Expand Down Expand Up @@ -151,7 +152,7 @@ func (gr *GitRevision) Build(ctx context.Context) (string, error) {
}

gr.log().Printf("building (timeout: %s)", buildTimeout)
return bi.Build.Build(buildCtx, repoDir, installDir, gr.Product.BinaryName)
return bi.Build.Build(buildCtx, repoDir, installDir, gr.Product.BinaryName())
}

func (gr *GitRevision) Remove(ctx context.Context) error {
Expand Down
12 changes: 7 additions & 5 deletions checkpoint/latest_version.go
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/hashicorp/hc-install/internal/pubkey"
rjson "github.com/hashicorp/hc-install/internal/releasesjson"
isrc "github.com/hashicorp/hc-install/internal/src"
"github.com/hashicorp/hc-install/internal/validators"
"github.com/hashicorp/hc-install/product"
)

Expand Down Expand Up @@ -55,11 +56,11 @@ func (lv *LatestVersion) log() *log.Logger {
}

func (lv *LatestVersion) Validate() error {
if lv.Product.Name == "" {
return fmt.Errorf("unknown product name")
if !validators.IsProductNameValid(lv.Product.Name) {
return fmt.Errorf("invalid product name: %q", lv.Product.Name)
}
if lv.Product.BinaryName == "" {
return fmt.Errorf("unknown binary name")
if !validators.IsBinaryNameValid(lv.Product.BinaryName()) {
return fmt.Errorf("invalid binary name: %q", lv.Product.BinaryName())
}

return nil
Expand Down Expand Up @@ -117,6 +118,7 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) {
Logger: lv.log(),
VerifyChecksum: !lv.SkipChecksumVerification,
ArmoredPublicKey: pubkey.DefaultPublicKey,
BaseURL: rels.BaseURL,
}
if lv.ArmoredPublicKey != "" {
d.ArmoredPublicKey = lv.ArmoredPublicKey
Expand All @@ -126,7 +128,7 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) {
return "", err
}

execPath := filepath.Join(dstDir, lv.Product.BinaryName)
execPath := filepath.Join(dstDir, lv.Product.BinaryName())

lv.pathsToRemove = append(lv.pathsToRemove, execPath)

Expand Down
7 changes: 4 additions & 3 deletions fs/any_version.go
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/hashicorp/hc-install/errors"
"github.com/hashicorp/hc-install/internal/src"
"github.com/hashicorp/hc-install/internal/validators"
"github.com/hashicorp/hc-install/product"
)

Expand All @@ -26,8 +27,8 @@ func (*AnyVersion) IsSourceImpl() src.InstallSrcSigil {
}

func (av *AnyVersion) Validate() error {
if av.Product.BinaryName == "" {
return fmt.Errorf("unknown binary name")
if !validators.IsBinaryNameValid(av.Product.BinaryName()) {
return fmt.Errorf("invalid binary name: %q", av.Product.BinaryName())
}
return nil
}
Expand All @@ -44,7 +45,7 @@ func (av *AnyVersion) log() *log.Logger {
}

func (av *AnyVersion) Find(ctx context.Context) (string, error) {
execPath, err := findFile(lookupDirs(av.ExtraPaths), av.Product.BinaryName, checkExecutable)
execPath, err := findFile(lookupDirs(av.ExtraPaths), av.Product.BinaryName(), checkExecutable)
if err != nil {
return "", errors.SkippableErr(err)
}
Expand Down
7 changes: 4 additions & 3 deletions fs/exact_version.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/hashicorp/go-version"
"github.com/hashicorp/hc-install/errors"
"github.com/hashicorp/hc-install/internal/src"
"github.com/hashicorp/hc-install/internal/validators"
"github.com/hashicorp/hc-install/product"
)

Expand Down Expand Up @@ -41,8 +42,8 @@ func (ev *ExactVersion) log() *log.Logger {
}

func (ev *ExactVersion) Validate() error {
if ev.Product.BinaryName == "" {
return fmt.Errorf("undeclared binary name")
if !validators.IsBinaryNameValid(ev.Product.BinaryName()) {
return fmt.Errorf("invalid binary name: %q", ev.Product.BinaryName())
}
if ev.Version == nil {
return fmt.Errorf("undeclared version")
Expand All @@ -61,7 +62,7 @@ func (ev *ExactVersion) Find(ctx context.Context) (string, error) {
ctx, cancelFunc := context.WithTimeout(ctx, timeout)
defer cancelFunc()

execPath, err := findFile(lookupDirs(ev.ExtraPaths), ev.Product.BinaryName, func(file string) error {
execPath, err := findFile(lookupDirs(ev.ExtraPaths), ev.Product.BinaryName(), func(file string) error {
err := checkExecutable(file)
if err != nil {
return err
Expand Down
37 changes: 0 additions & 37 deletions fs/fs.go
Expand Up @@ -3,9 +3,6 @@ package fs
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"time"
)

Expand All @@ -14,38 +11,4 @@ var (
discardLogger = log.New(ioutil.Discard, "", 0)
)

func lookupDirs(extraDirs []string) []string {
pathVar := os.Getenv("PATH")
dirs := filepath.SplitList(pathVar)
for _, ep := range extraDirs {
dirs = append(dirs, ep)
}
return dirs
}

type fileCheckFunc func(path string) error

func findFile(dirs []string, file string, f fileCheckFunc) (string, error) {
for _, dir := range dirs {
if dir == "" {
// Unix shell semantics: path element "" means "."
dir = "."
}
path := filepath.Join(dir, file)
if err := f(path); err == nil {
return path, nil
}
}
return "", exec.ErrNotFound
}

func checkExecutable(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return nil
}
return os.ErrPermission
}
59 changes: 5 additions & 54 deletions fs/fs_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/hashicorp/go-version"
Expand All @@ -20,60 +21,6 @@ var (
_ src.LoggerSettable = &ExactVersion{}
)

func TestAnyVersion_notExecutable(t *testing.T) {
testutil.EndToEndTest(t)

originalPath := os.Getenv("PATH")
os.Setenv("PATH", "")
t.Cleanup(func() {
os.Setenv("PATH", originalPath)
})

dirPath, fileName := createTempFile(t, "")
os.Setenv("PATH", dirPath)

av := &AnyVersion{
Product: product.Product{
BinaryName: fileName,
},
}
av.SetLogger(testutil.TestLogger())
_, err := av.Find(context.Background())
if err == nil {
t.Fatalf("expected %s not to be found in %s", fileName, dirPath)
}
}

func TestAnyVersion_executable(t *testing.T) {
testutil.EndToEndTest(t)

originalPath := os.Getenv("PATH")
os.Setenv("PATH", "")
t.Cleanup(func() {
os.Setenv("PATH", originalPath)
})

dirPath, fileName := createTempFile(t, "")
os.Setenv("PATH", dirPath)

fullPath := filepath.Join(dirPath, fileName)
err := os.Chmod(fullPath, 0700)
if err != nil {
t.Fatal(err)
}

av := &AnyVersion{
Product: product.Product{
BinaryName: fileName,
},
}
av.SetLogger(testutil.TestLogger())
_, err = av.Find(context.Background())
if err != nil {
t.Fatal(err)
}
}

func TestExactVersion(t *testing.T) {
t.Skip("TODO")
testutil.EndToEndTest(t)
Expand Down Expand Up @@ -101,6 +48,10 @@ func createTempFile(t *testing.T, content string) (string, string) {
tmpDir := t.TempDir()
fileName := t.Name()

if runtime.GOOS == "windows" {
fileName += ".exe"
}

filePath := filepath.Join(tmpDir, fileName)
f, err := os.Create(filePath)
if err != nil {
Expand Down
44 changes: 44 additions & 0 deletions fs/fs_unix.go
@@ -0,0 +1,44 @@
//go:build !windows

package fs

import (
"fmt"
"os"
"os/exec"
"path/filepath"
)

func lookupDirs(extraDirs []string) []string {
pathVar := os.Getenv("PATH")
dirs := filepath.SplitList(pathVar)
for _, ep := range extraDirs {
dirs = append(dirs, ep)
}
return dirs
}

func findFile(dirs []string, file string, f fileCheckFunc) (string, error) {
for _, dir := range dirs {
if dir == "" {
// Unix shell semantics: path element "" means "."
dir = "."
}
path := filepath.Join(dir, file)
if err := f(path); err == nil {
return path, nil
}
}
return "", fmt.Errorf("%s: %w", file, exec.ErrNotFound)
}

func checkExecutable(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return nil
}
return os.ErrPermission
}

0 comments on commit 9fcc92b

Please sign in to comment.