924 lines
24 KiB
Go
924 lines
24 KiB
Go
package builders
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"dappco.re/go/core/build/internal/ax"
|
|
|
|
"dappco.re/go/core/build/pkg/build"
|
|
"dappco.re/go/core/io"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// setupGoTestProject creates a minimal Go project for testing.
|
|
func setupGoTestProject(t *testing.T) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
|
|
// Create a minimal go.mod
|
|
goMod := `module testproject
|
|
|
|
go 1.21
|
|
`
|
|
err := ax.WriteFile(ax.Join(dir, "go.mod"), []byte(goMod), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Create a minimal main.go
|
|
mainGo := `package main
|
|
|
|
func main() {
|
|
println("hello")
|
|
}
|
|
`
|
|
err = ax.WriteFile(ax.Join(dir, "main.go"), []byte(mainGo), 0644)
|
|
require.NoError(t, err)
|
|
|
|
return dir
|
|
}
|
|
|
|
func setupFakeBuildToolchain(t *testing.T, binDir string) {
|
|
t.Helper()
|
|
|
|
goScript := `#!/bin/sh
|
|
set -eu
|
|
|
|
log_file="${GO_BUILD_LOG_FILE:-}"
|
|
if [ -n "$log_file" ]; then
|
|
printf '%s\n' "$@" > "$log_file"
|
|
fi
|
|
|
|
env_log_file="${GO_BUILD_ENV_LOG_FILE:-}"
|
|
if [ -n "$env_log_file" ]; then
|
|
env | sort > "$env_log_file"
|
|
fi
|
|
|
|
if [ "${GOARCH:-}" = "invalid_arch" ]; then
|
|
exit 1
|
|
fi
|
|
|
|
if [ -f main.go ] && grep -q "not valid go code" main.go; then
|
|
exit 1
|
|
fi
|
|
|
|
output=""
|
|
previous=""
|
|
for argument in "$@"; do
|
|
if [ "$previous" = "-o" ]; then
|
|
output="$argument"
|
|
break
|
|
fi
|
|
previous="$argument"
|
|
done
|
|
|
|
if [ -n "$output" ]; then
|
|
mkdir -p "$(dirname "$output")"
|
|
printf 'fake binary\n' > "$output"
|
|
chmod +x "$output"
|
|
fi
|
|
`
|
|
|
|
err := ax.WriteFile(ax.Join(binDir, "go"), []byte(goScript), 0o755)
|
|
require.NoError(t, err)
|
|
|
|
garbleScript := `#!/bin/sh
|
|
set -eu
|
|
|
|
log_file="${GARBLE_LOG_FILE:-}"
|
|
if [ -n "$log_file" ]; then
|
|
printf '%s\n' "$@" > "$log_file"
|
|
fi
|
|
|
|
exec go "$@"
|
|
`
|
|
|
|
err = ax.WriteFile(ax.Join(binDir, "garble"), []byte(garbleScript), 0o755)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func setupFakeGoBinary(t *testing.T, binDir string) {
|
|
t.Helper()
|
|
|
|
goScript := `#!/bin/sh
|
|
set -eu
|
|
|
|
log_file="${GO_BUILD_LOG_FILE:-}"
|
|
if [ -n "$log_file" ]; then
|
|
printf '%s\n' "$@" > "$log_file"
|
|
fi
|
|
|
|
env_log_file="${GO_BUILD_ENV_LOG_FILE:-}"
|
|
if [ -n "$env_log_file" ]; then
|
|
env | sort > "$env_log_file"
|
|
fi
|
|
|
|
if [ "${GOARCH:-}" = "invalid_arch" ]; then
|
|
exit 1
|
|
fi
|
|
|
|
if [ -f main.go ] && grep -q "not valid go code" main.go; then
|
|
exit 1
|
|
fi
|
|
|
|
output=""
|
|
previous=""
|
|
for argument in "$@"; do
|
|
if [ "$previous" = "-o" ]; then
|
|
output="$argument"
|
|
break
|
|
fi
|
|
previous="$argument"
|
|
done
|
|
|
|
if [ -n "$output" ]; then
|
|
mkdir -p "$(dirname "$output")"
|
|
printf 'fake binary\n' > "$output"
|
|
chmod +x "$output"
|
|
fi
|
|
`
|
|
|
|
err := ax.WriteFile(ax.Join(binDir, "go"), []byte(goScript), 0o755)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func setupFakeGarbleBinary(t *testing.T, binDir string) {
|
|
t.Helper()
|
|
|
|
garbleScript := `#!/bin/sh
|
|
set -eu
|
|
|
|
log_file="${GARBLE_LOG_FILE:-}"
|
|
if [ -n "$log_file" ]; then
|
|
printf '%s\n' "$@" > "$log_file"
|
|
fi
|
|
|
|
exec go "$@"
|
|
`
|
|
|
|
err := ax.WriteFile(ax.Join(binDir, "garble"), []byte(garbleScript), 0o755)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestGo_GoBuilderName_Good(t *testing.T) {
|
|
builder := NewGoBuilder()
|
|
assert.Equal(t, "go", builder.Name())
|
|
}
|
|
|
|
func TestGo_GoBuilderDetect_Good(t *testing.T) {
|
|
fs := io.Local
|
|
t.Run("detects Go project with go.mod", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
err := ax.WriteFile(ax.Join(dir, "go.mod"), []byte("module test"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
builder := NewGoBuilder()
|
|
detected, err := builder.Detect(fs, dir)
|
|
assert.NoError(t, err)
|
|
assert.True(t, detected)
|
|
})
|
|
|
|
t.Run("detects Wails project", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
err := ax.WriteFile(ax.Join(dir, "wails.json"), []byte("{}"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
builder := NewGoBuilder()
|
|
detected, err := builder.Detect(fs, dir)
|
|
assert.NoError(t, err)
|
|
assert.True(t, detected)
|
|
})
|
|
|
|
t.Run("returns false for non-Go project", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Create a Node.js project instead
|
|
err := ax.WriteFile(ax.Join(dir, "package.json"), []byte("{}"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
builder := NewGoBuilder()
|
|
detected, err := builder.Detect(fs, dir)
|
|
assert.NoError(t, err)
|
|
assert.False(t, detected)
|
|
})
|
|
|
|
t.Run("returns false for empty directory", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
detected, err := builder.Detect(fs, dir)
|
|
assert.NoError(t, err)
|
|
assert.False(t, detected)
|
|
})
|
|
}
|
|
|
|
func TestGo_GoBuilderBuild_Good(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
binDir := t.TempDir()
|
|
setupFakeBuildToolchain(t, binDir)
|
|
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
|
|
t.Run("builds for current platform", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "testbinary",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
|
|
// Verify artifact properties
|
|
artifact := artifacts[0]
|
|
assert.Equal(t, runtime.GOOS, artifact.OS)
|
|
assert.Equal(t, runtime.GOARCH, artifact.Arch)
|
|
|
|
// Verify binary was created
|
|
assert.FileExists(t, artifact.Path)
|
|
|
|
// Verify the path is in the expected location
|
|
expectedName := "testbinary"
|
|
if runtime.GOOS == "windows" {
|
|
expectedName += ".exe"
|
|
}
|
|
assert.Contains(t, artifact.Path, expectedName)
|
|
})
|
|
|
|
t.Run("defaults to current platform when targets are empty", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "fallback",
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.Equal(t, runtime.GOOS, artifacts[0].OS)
|
|
assert.Equal(t, runtime.GOARCH, artifacts[0].Arch)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
})
|
|
|
|
t.Run("does not mutate the caller output directory when using defaults", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
Name: "mutability",
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, []build.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}})
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.Empty(t, cfg.OutputDir)
|
|
assert.Equal(t, ax.Join(projectDir, "dist"), ax.Dir(ax.Dir(artifacts[0].Path)))
|
|
})
|
|
|
|
t.Run("builds multiple targets", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "multitest",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: "linux", Arch: "amd64"},
|
|
{OS: "linux", Arch: "arm64"},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 2)
|
|
|
|
// Verify both artifacts were created
|
|
for i, artifact := range artifacts {
|
|
assert.Equal(t, targets[i].OS, artifact.OS)
|
|
assert.Equal(t, targets[i].Arch, artifact.Arch)
|
|
assert.FileExists(t, artifact.Path)
|
|
}
|
|
})
|
|
|
|
t.Run("adds .exe extension for Windows", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "wintest",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: "windows", Arch: "amd64"},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
|
|
// Verify .exe extension
|
|
assert.True(t, ax.Ext(artifacts[0].Path) == ".exe")
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
})
|
|
|
|
t.Run("uses directory name when Name not specified", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "", // Empty name
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
|
|
// Binary should use the project directory base name
|
|
baseName := ax.Base(projectDir)
|
|
if runtime.GOOS == "windows" {
|
|
baseName += ".exe"
|
|
}
|
|
assert.Contains(t, artifacts[0].Path, baseName)
|
|
})
|
|
|
|
t.Run("uses configured project binary when Name not specified", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
}
|
|
cfg.Project.Binary = "example-binary"
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
|
|
expectedName := "example-binary"
|
|
if runtime.GOOS == "windows" {
|
|
expectedName += ".exe"
|
|
}
|
|
assert.Contains(t, artifacts[0].Path, expectedName)
|
|
})
|
|
|
|
t.Run("uses configured project name when Binary not specified", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
}
|
|
cfg.Project.Name = "example-name"
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
|
|
expectedName := "example-name"
|
|
if runtime.GOOS == "windows" {
|
|
expectedName += ".exe"
|
|
}
|
|
assert.Contains(t, artifacts[0].Path, expectedName)
|
|
})
|
|
|
|
t.Run("applies ldflags", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "ldflagstest",
|
|
LDFlags: []string{"-s", "-w"}, // Strip debug info
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
})
|
|
|
|
t.Run("applies config flags and env", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
logDir := t.TempDir()
|
|
argsLogPath := ax.Join(logDir, "go-args.log")
|
|
envLogPath := ax.Join(logDir, "go-env.log")
|
|
|
|
t.Setenv("GO_BUILD_LOG_FILE", argsLogPath)
|
|
t.Setenv("GO_BUILD_ENV_LOG_FILE", envLogPath)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "envflags",
|
|
Version: "v1.2.3",
|
|
Flags: []string{"-race"},
|
|
Env: []string{"FOO=bar", "BAR=baz"},
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
|
|
argsContent, err := ax.ReadFile(argsLogPath)
|
|
require.NoError(t, err)
|
|
args := strings.Split(strings.TrimSpace(string(argsContent)), "\n")
|
|
require.NotEmpty(t, args)
|
|
assert.Equal(t, "build", args[0])
|
|
assert.Contains(t, args, "-trimpath")
|
|
assert.Contains(t, args, "-race")
|
|
|
|
envContent, err := ax.ReadFile(envLogPath)
|
|
require.NoError(t, err)
|
|
envLines := strings.Split(strings.TrimSpace(string(envContent)), "\n")
|
|
assert.Contains(t, envLines, "BAR=baz")
|
|
assert.Contains(t, envLines, "FOO=bar")
|
|
assert.Contains(t, envLines, "TARGET_OS="+runtime.GOOS)
|
|
assert.Contains(t, envLines, "TARGET_ARCH="+runtime.GOARCH)
|
|
assert.Contains(t, envLines, "OUTPUT_DIR="+outputDir)
|
|
assert.Contains(t, envLines, "TARGET_DIR="+ax.Join(outputDir, runtime.GOOS+"_"+runtime.GOARCH))
|
|
assert.Contains(t, envLines, "GOOS="+runtime.GOOS)
|
|
assert.Contains(t, envLines, "GOARCH="+runtime.GOARCH)
|
|
assert.Contains(t, envLines, "NAME=envflags")
|
|
assert.Contains(t, envLines, "VERSION=v1.2.3")
|
|
assert.Contains(t, envLines, "CGO_ENABLED=0")
|
|
})
|
|
|
|
t.Run("applies configured cache paths to go cache env vars", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
logDir := t.TempDir()
|
|
envLogPath := ax.Join(logDir, "go-cache-env.log")
|
|
|
|
t.Setenv("GO_BUILD_ENV_LOG_FILE", envLogPath)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "cachetest",
|
|
Cache: build.CacheConfig{
|
|
Enabled: true,
|
|
Paths: []string{
|
|
ax.Join(outputDir, "cache", "go-build"),
|
|
ax.Join(outputDir, "cache", "go-mod"),
|
|
},
|
|
},
|
|
}
|
|
targets := []build.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
|
|
envContent, err := ax.ReadFile(envLogPath)
|
|
require.NoError(t, err)
|
|
|
|
envLines := strings.Split(strings.TrimSpace(string(envContent)), "\n")
|
|
assert.Contains(t, envLines, "GOCACHE="+ax.Join(outputDir, "cache", "go-build"))
|
|
assert.Contains(t, envLines, "GOMODCACHE="+ax.Join(outputDir, "cache", "go-mod"))
|
|
})
|
|
|
|
t.Run("passes build tags through to go build", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
logPath := ax.Join(t.TempDir(), "go-tags.log")
|
|
t.Setenv("GO_BUILD_LOG_FILE", logPath)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "tagged",
|
|
BuildTags: []string{"webkit2_41", "integration"},
|
|
}
|
|
targets := []build.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
|
|
content, err := ax.ReadFile(logPath)
|
|
require.NoError(t, err)
|
|
|
|
args := strings.Split(strings.TrimSpace(string(content)), "\n")
|
|
require.NotEmpty(t, args)
|
|
assert.Equal(t, "build", args[0])
|
|
assert.Contains(t, args, "-tags")
|
|
assert.Contains(t, args, "webkit2_41,integration")
|
|
})
|
|
|
|
t.Run("injects version into ldflags and environment", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
argsLogPath := ax.Join(t.TempDir(), "go-version-args.log")
|
|
envLogPath := ax.Join(t.TempDir(), "go-version-env.log")
|
|
|
|
t.Setenv("GO_BUILD_LOG_FILE", argsLogPath)
|
|
t.Setenv("GO_BUILD_ENV_LOG_FILE", envLogPath)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "versioned",
|
|
Version: "v1.2.3",
|
|
}
|
|
targets := []build.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
|
|
argsContent, err := ax.ReadFile(argsLogPath)
|
|
require.NoError(t, err)
|
|
|
|
args := strings.Split(strings.TrimSpace(string(argsContent)), "\n")
|
|
require.NotEmpty(t, args)
|
|
assert.Contains(t, args, "-ldflags")
|
|
assert.Contains(t, args, "-X main.version=v1.2.3")
|
|
|
|
envContent, err := ax.ReadFile(envLogPath)
|
|
require.NoError(t, err)
|
|
|
|
envLines := strings.Split(strings.TrimSpace(string(envContent)), "\n")
|
|
assert.Contains(t, envLines, "VERSION=v1.2.3")
|
|
})
|
|
|
|
t.Run("uses garble when obfuscation is enabled", func(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("garble test helper uses a shell script")
|
|
}
|
|
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
logDir := t.TempDir()
|
|
logPath := ax.Join(logDir, "garble.log")
|
|
|
|
t.Setenv("GARBLE_LOG_FILE", logPath)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "obfuscated",
|
|
Obfuscate: true,
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
|
|
content, err := ax.ReadFile(logPath)
|
|
require.NoError(t, err)
|
|
|
|
args := strings.Split(strings.TrimSpace(string(content)), "\n")
|
|
require.NotEmpty(t, args)
|
|
assert.Equal(t, "build", args[0])
|
|
assert.Contains(t, args, "-trimpath")
|
|
assert.Contains(t, args, "-o")
|
|
assert.Contains(t, args, ".")
|
|
})
|
|
|
|
t.Run("finds garble in GOBIN when it is not on PATH", func(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("garble test helper uses a shell script")
|
|
}
|
|
|
|
goDir := t.TempDir()
|
|
setupFakeGoBinary(t, goDir)
|
|
t.Setenv("PATH", goDir+string(os.PathListSeparator)+"/usr/bin"+string(os.PathListSeparator)+"/bin")
|
|
|
|
garbleDir := t.TempDir()
|
|
setupFakeGarbleBinary(t, garbleDir)
|
|
t.Setenv("GOBIN", garbleDir)
|
|
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
logDir := t.TempDir()
|
|
logPath := ax.Join(logDir, "garble-gobin.log")
|
|
|
|
t.Setenv("GARBLE_LOG_FILE", logPath)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "obfuscated-gobin",
|
|
Obfuscate: true,
|
|
}
|
|
targets := []build.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
|
|
content, err := ax.ReadFile(logPath)
|
|
require.NoError(t, err)
|
|
|
|
args := strings.Split(strings.TrimSpace(string(content)), "\n")
|
|
require.NotEmpty(t, args)
|
|
assert.Equal(t, "build", args[0])
|
|
assert.Contains(t, args, "-trimpath")
|
|
})
|
|
|
|
t.Run("builds the configured main package path", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
err := ax.MkdirAll(ax.Join(projectDir, "cmd", "myapp"), 0755)
|
|
require.NoError(t, err)
|
|
err = ax.WriteFile(ax.Join(projectDir, "cmd", "myapp", "main.go"), []byte("package main\n\nfunc main() {}\n"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
outputDir := t.TempDir()
|
|
logPath := ax.Join(t.TempDir(), "go-build-args.log")
|
|
t.Setenv("GO_BUILD_LOG_FILE", logPath)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "mainpackage",
|
|
}
|
|
cfg.Project.Main = "./cmd/myapp"
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
|
|
content, err := ax.ReadFile(logPath)
|
|
require.NoError(t, err)
|
|
|
|
args := strings.Split(strings.TrimSpace(string(content)), "\n")
|
|
require.NotEmpty(t, args)
|
|
assert.Contains(t, args, "./cmd/myapp")
|
|
assert.NotContains(t, args, ".")
|
|
})
|
|
|
|
t.Run("creates output directory if missing", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := ax.Join(t.TempDir(), "nested", "output")
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "nestedtest",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
assert.DirExists(t, outputDir)
|
|
})
|
|
|
|
t.Run("defaults output directory to project dist when not specified", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
Name: "defaultoutput",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
require.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
|
|
expectedDir := ax.Join(projectDir, "dist")
|
|
assert.DirExists(t, expectedDir)
|
|
assert.Contains(t, artifacts[0].Path, expectedDir)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
})
|
|
}
|
|
|
|
func TestGo_GoBuilderBuild_Bad(t *testing.T) {
|
|
binDir := t.TempDir()
|
|
setupFakeBuildToolchain(t, binDir)
|
|
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
|
|
t.Run("returns error for nil config", func(t *testing.T) {
|
|
builder := NewGoBuilder()
|
|
|
|
artifacts, err := builder.Build(context.Background(), nil, []build.Target{{OS: "linux", Arch: "amd64"}})
|
|
assert.Error(t, err)
|
|
assert.Nil(t, artifacts)
|
|
assert.Contains(t, err.Error(), "config is nil")
|
|
})
|
|
|
|
t.Run("defaults to current platform when targets are empty", func(t *testing.T) {
|
|
projectDir := setupGoTestProject(t)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: t.TempDir(),
|
|
Name: "test",
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, []build.Target{})
|
|
assert.NoError(t, err)
|
|
require.Len(t, artifacts, 1)
|
|
assert.Equal(t, runtime.GOOS, artifacts[0].OS)
|
|
assert.Equal(t, runtime.GOARCH, artifacts[0].Arch)
|
|
assert.FileExists(t, artifacts[0].Path)
|
|
})
|
|
|
|
t.Run("returns error for invalid project directory", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: "/nonexistent/path",
|
|
OutputDir: t.TempDir(),
|
|
Name: "test",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
assert.Error(t, err)
|
|
assert.Empty(t, artifacts)
|
|
})
|
|
|
|
t.Run("returns error for invalid Go code", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
|
|
// Create go.mod
|
|
err := ax.WriteFile(ax.Join(dir, "go.mod"), []byte("module test\n\ngo 1.21"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Create invalid Go code
|
|
err = ax.WriteFile(ax.Join(dir, "main.go"), []byte("this is not valid go code"), 0644)
|
|
require.NoError(t, err)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: dir,
|
|
OutputDir: t.TempDir(),
|
|
Name: "test",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "go build failed")
|
|
assert.Empty(t, artifacts)
|
|
})
|
|
|
|
t.Run("returns partial artifacts on partial failure", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
// Create a project that will fail on one target
|
|
// Using an invalid arch for linux
|
|
projectDir := setupGoTestProject(t)
|
|
outputDir := t.TempDir()
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: outputDir,
|
|
Name: "partialtest",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH}, // This should succeed
|
|
{OS: "linux", Arch: "invalid_arch"}, // This should fail
|
|
}
|
|
|
|
artifacts, err := builder.Build(context.Background(), cfg, targets)
|
|
// Should return error for the failed build
|
|
assert.Error(t, err)
|
|
// Should have the successful artifact
|
|
assert.Len(t, artifacts, 1)
|
|
})
|
|
|
|
t.Run("respects context cancellation", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
projectDir := setupGoTestProject(t)
|
|
|
|
builder := NewGoBuilder()
|
|
cfg := &build.Config{
|
|
FS: io.Local,
|
|
ProjectDir: projectDir,
|
|
OutputDir: t.TempDir(),
|
|
Name: "canceltest",
|
|
}
|
|
targets := []build.Target{
|
|
{OS: runtime.GOOS, Arch: runtime.GOARCH},
|
|
}
|
|
|
|
// Create an already cancelled context
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
artifacts, err := builder.Build(ctx, cfg, targets)
|
|
assert.Error(t, err)
|
|
assert.Empty(t, artifacts)
|
|
})
|
|
}
|
|
|
|
func TestGo_GoBuilderInterface_Good(t *testing.T) {
|
|
// Verify GoBuilder implements Builder interface
|
|
var _ build.Builder = (*GoBuilder)(nil)
|
|
var _ build.Builder = NewGoBuilder()
|
|
}
|