package builders import ( "context" "os" "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" ) func setupFakeLinuxKitToolchain(t *testing.T, binDir string) { t.Helper() script := `#!/bin/sh set -eu if [ "${1:-}" != "build" ]; then exit 1 fi config="" dir="" name="" while [ $# -gt 0 ]; do if [ "$1" = "--dir" ]; then shift dir="${1:-}" elif [ "$1" = "--name" ]; then shift name="${1:-}" fi shift done if [ -n "$dir" ] && [ -n "$name" ]; then mkdir -p "$dir" printf 'linuxkit image\n' > "$dir/$name.iso" fi ` require.NoError(t, ax.WriteFile(ax.Join(binDir, "linuxkit"), []byte(script), 0o755)) } func TestLinuxKit_LinuxKitBuilderName_Good(t *testing.T) { builder := NewLinuxKitBuilder() assert.Equal(t, "linuxkit", builder.Name()) } func TestLinuxKit_LinuxKitBuilderDetect_Good(t *testing.T) { fs := io.Local t.Run("detects linuxkit.yml in root", func(t *testing.T) { dir := t.TempDir() err := ax.WriteFile(ax.Join(dir, "linuxkit.yml"), []byte("kernel:\n image: test\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.True(t, detected) }) t.Run("detects linuxkit.yaml in root", func(t *testing.T) { dir := t.TempDir() err := ax.WriteFile(ax.Join(dir, "linuxkit.yaml"), []byte("kernel:\n image: test\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.True(t, detected) }) t.Run("detects .core/linuxkit/*.yml", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) err := ax.WriteFile(ax.Join(lkDir, "server.yml"), []byte("kernel:\n image: test\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.True(t, detected) }) t.Run("detects .core/linuxkit/*.yaml", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) err := ax.WriteFile(ax.Join(lkDir, "server.yaml"), []byte("kernel:\n image: test\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.True(t, detected) }) t.Run("detects .core/linuxkit with multiple yml files", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) err := ax.WriteFile(ax.Join(lkDir, "server.yml"), []byte("kernel:\n"), 0644) require.NoError(t, err) err = ax.WriteFile(ax.Join(lkDir, "desktop.yml"), []byte("kernel:\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.True(t, detected) }) t.Run("returns false for empty directory", func(t *testing.T) { dir := t.TempDir() builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.False(t, detected) }) t.Run("returns false for non-LinuxKit project", func(t *testing.T) { dir := t.TempDir() err := ax.WriteFile(ax.Join(dir, "go.mod"), []byte("module test"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.False(t, detected) }) t.Run("returns false for empty .core/linuxkit directory", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.False(t, detected) }) t.Run("returns false when .core/linuxkit has only non-yml files", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) err := ax.WriteFile(ax.Join(lkDir, "README.md"), []byte("# LinuxKit\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.False(t, detected) }) t.Run("returns false when .core/linuxkit has only non-yaml files", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) err := ax.WriteFile(ax.Join(lkDir, "README.md"), []byte("# LinuxKit\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.False(t, detected) }) t.Run("ignores subdirectories in .core/linuxkit", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") subDir := ax.Join(lkDir, "subdir") require.NoError(t, ax.MkdirAll(subDir, 0755)) // Put yml in subdir only, not in lkDir itself err := ax.WriteFile(ax.Join(subDir, "server.yml"), []byte("kernel:\n"), 0644) require.NoError(t, err) builder := NewLinuxKitBuilder() detected, err := builder.Detect(fs, dir) assert.NoError(t, err) assert.False(t, detected) }) } func TestLinuxKit_LinuxKitBuilderGetFormatExtension_Good(t *testing.T) { builder := NewLinuxKitBuilder() tests := []struct { format string expected string }{ {"iso", ".iso"}, {"iso-bios", ".iso"}, {"iso-efi", ".iso"}, {"raw", ".raw"}, {"raw-bios", ".raw"}, {"raw-efi", ".raw"}, {"qcow2", ".qcow2"}, {"qcow2-bios", ".qcow2"}, {"qcow2-efi", ".qcow2"}, {"vmdk", ".vmdk"}, {"vhd", ".vhd"}, {"gcp", ".img.tar.gz"}, {"aws", ".raw"}, {"docker", ".docker.tar"}, {"tar", ".tar"}, {"kernel+initrd", "-initrd.img"}, {"custom", ".custom"}, } for _, tc := range tests { t.Run(tc.format, func(t *testing.T) { ext := builder.getFormatExtension(tc.format) assert.Equal(t, tc.expected, ext) }) } } func TestLinuxKit_LinuxKitBuilderGetArtifactPath_Good(t *testing.T) { builder := NewLinuxKitBuilder() t.Run("constructs correct path", func(t *testing.T) { path := builder.getArtifactPath("/dist", "server-amd64", "iso") assert.Equal(t, "/dist/server-amd64.iso", path) }) t.Run("constructs correct path for qcow2", func(t *testing.T) { path := builder.getArtifactPath("/output/linuxkit", "server-arm64", "qcow2-bios") assert.Equal(t, "/output/linuxkit/server-arm64.qcow2", path) }) t.Run("constructs correct path for docker images", func(t *testing.T) { path := builder.getArtifactPath("/output/linuxkit", "server-amd64", "docker") assert.Equal(t, "/output/linuxkit/server-amd64.docker.tar", path) }) t.Run("constructs correct path for kernel+initrd images", func(t *testing.T) { path := builder.getArtifactPath("/output/linuxkit", "server-amd64", "kernel+initrd") assert.Equal(t, "/output/linuxkit/server-amd64-initrd.img", path) }) } func TestLinuxKit_LinuxKitBuilderBuildLinuxKitArgs_Good(t *testing.T) { builder := NewLinuxKitBuilder() t.Run("builds args for amd64 without --arch", func(t *testing.T) { args := builder.buildLinuxKitArgs("/config.yml", "iso", "output", "/dist", "amd64") assert.Contains(t, args, "build") assert.Contains(t, args, "--format") assert.Contains(t, args, "iso") assert.Contains(t, args, "--name") assert.Contains(t, args, "output") assert.Contains(t, args, "--dir") assert.Contains(t, args, "/dist") assert.Contains(t, args, "/config.yml") assert.NotContains(t, args, "--arch") }) t.Run("builds args for arm64 with --arch", func(t *testing.T) { args := builder.buildLinuxKitArgs("/config.yml", "qcow2", "output", "/dist", "arm64") assert.Contains(t, args, "--arch") assert.Contains(t, args, "arm64") }) } func TestLinuxKit_LinuxKitBuilderBuild_ResolvesRelativeConfigPath_Good(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } binDir := t.TempDir() setupFakeLinuxKitToolchain(t, binDir) t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) projectDir := t.TempDir() configPath := ax.Join(projectDir, "deploy", "linuxkit.yml") require.NoError(t, ax.MkdirAll(ax.Dir(configPath), 0o755)) require.NoError(t, ax.WriteFile(configPath, []byte("kernel:\n image: test\n"), 0o644)) outputDir := t.TempDir() builder := NewLinuxKitBuilder() cfg := &build.Config{ FS: io.Local, ProjectDir: projectDir, OutputDir: outputDir, Name: "sample", LinuxKitConfig: "deploy/linuxkit.yml", Formats: []string{"iso"}, } artifacts, err := builder.Build(context.Background(), cfg, []build.Target{{OS: "linux", Arch: "amd64"}}) require.NoError(t, err) require.Len(t, artifacts, 1) expectedPath := ax.Join(outputDir, "sample-amd64.iso") assert.Equal(t, expectedPath, artifacts[0].Path) assert.FileExists(t, expectedPath) } func TestLinuxKit_LinuxKitBuilderFindArtifact_Good(t *testing.T) { fs := io.Local builder := NewLinuxKitBuilder() t.Run("finds artifact with exact extension", func(t *testing.T) { dir := t.TempDir() artifactPath := ax.Join(dir, "server-amd64.iso") require.NoError(t, ax.WriteFile(artifactPath, []byte("fake iso"), 0644)) found := builder.findArtifact(fs, dir, "server-amd64", "iso") assert.Equal(t, artifactPath, found) }) t.Run("returns empty for missing artifact", func(t *testing.T) { dir := t.TempDir() found := builder.findArtifact(fs, dir, "nonexistent", "iso") assert.Empty(t, found) }) t.Run("finds artifact with alternate naming", func(t *testing.T) { dir := t.TempDir() // Create file matching the name prefix + known image extension artifactPath := ax.Join(dir, "server-amd64.qcow2") require.NoError(t, ax.WriteFile(artifactPath, []byte("fake qcow2"), 0644)) found := builder.findArtifact(fs, dir, "server-amd64", "qcow2") assert.Equal(t, artifactPath, found) }) t.Run("finds cloud image artifacts", func(t *testing.T) { dir := t.TempDir() artifactPath := ax.Join(dir, "server-amd64-gcp.img.tar.gz") require.NoError(t, ax.WriteFile(artifactPath, []byte("fake gcp image"), 0644)) found := builder.findArtifact(fs, dir, "server-amd64", "gcp") assert.Equal(t, artifactPath, found) }) t.Run("finds docker artifacts", func(t *testing.T) { dir := t.TempDir() artifactPath := ax.Join(dir, "server-amd64.docker.tar") require.NoError(t, ax.WriteFile(artifactPath, []byte("fake docker tar"), 0644)) found := builder.findArtifact(fs, dir, "server-amd64", "docker") assert.Equal(t, artifactPath, found) }) t.Run("finds kernel+initrd artifacts", func(t *testing.T) { dir := t.TempDir() artifactPath := ax.Join(dir, "server-amd64-initrd.img") require.NoError(t, ax.WriteFile(artifactPath, []byte("fake initrd"), 0644)) found := builder.findArtifact(fs, dir, "server-amd64", "kernel+initrd") assert.Equal(t, artifactPath, found) }) } func TestLinuxKit_LinuxKitBuilderInterface_Good(t *testing.T) { // Verify LinuxKitBuilder implements Builder interface var _ build.Builder = (*LinuxKitBuilder)(nil) var _ build.Builder = NewLinuxKitBuilder() } func TestLinuxKit_LinuxKitBuilderResolveLinuxKitCli_Good(t *testing.T) { builder := NewLinuxKitBuilder() fallbackDir := t.TempDir() fallbackPath := ax.Join(fallbackDir, "linuxkit") require.NoError(t, ax.WriteFile(fallbackPath, []byte("#!/bin/sh\nexit 0\n"), 0o755)) t.Setenv("PATH", "") command, err := builder.resolveLinuxKitCli(fallbackPath) require.NoError(t, err) assert.Equal(t, fallbackPath, command) } func TestLinuxKit_LinuxKitBuilderResolveLinuxKitCli_Bad(t *testing.T) { builder := NewLinuxKitBuilder() t.Setenv("PATH", "") _, err := builder.resolveLinuxKitCli(ax.Join(t.TempDir(), "missing-linuxkit")) require.Error(t, err) assert.Contains(t, err.Error(), "linuxkit CLI not found") }