Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 73 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Integration Tests

on:
push:
branches:
- main
- dev
pull_request:
branches:
- main
- dev

jobs:
test-linux:
name: Linux Integration - ${{ matrix.os }}
runs-on: ubuntu-latest
strategy:
matrix:
os: [debian, arch, fedora]
fail-fast: false

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25.x'

- name: Run Dagger Integration Pipeline
run: |
go run ci/main.go --os ${{ matrix.os }}

test-mac:
name: macOS Integration
runs-on: macos-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25.x'

- name: Build bootstrap binary
run: |
go build -o bootstrap_environment .

- name: Pre-create ~/.pyenv directory
run: |
mkdir -p ~/.pyenv

- name: Run bootstrap tool
run: |
echo y | ./bootstrap_environment --only custom --no-vm --no-ai

conclusion:
name: Final Status Check
needs: [test-linux, test-mac]
runs-on: ubuntu-latest
if: always()
steps:
- name: Check dependency jobs
run: |
# Check the results of matrix and mac jobs
# GHA will fail the workflow if this step exits with error
if [ "${{ needs.test-linux.result }}" != "success" ] || [ "${{ needs.test-mac.result }}" != "success" ]; then
echo "One or more integration checks failed!"
exit 1
fi
echo "All integration checks passed!"
61 changes: 61 additions & 0 deletions .github/workflows/macos-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: macOS Manual Integration

on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to run against'
required: true
default: 'main'
type: choice
options:
- main
- dev
- fix-add-unit-testing
workflow_definition:
description: 'Workflow Run Configuration'
required: true
default: 'custom-only'
type: choice
options:
- 'custom-only'
- 'system-only'
- 'full-setup'

jobs:
test-mac-manual:
name: macOS Manual Run
runs-on: macos-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25.x'

- name: Build bootstrap binary
run: |
go build -o bootstrap_environment .

- name: Pre-create ~/.pyenv directory
run: |
mkdir -p ~/.pyenv

- name: Run bootstrap tool (custom-only)
if: ${{ inputs.workflow_definition == 'custom-only' }}
run: |
echo y | ./bootstrap_environment --only custom --no-vm --no-ai

- name: Run bootstrap tool (system-only)
if: ${{ inputs.workflow_definition == 'system-only' }}
run: |
echo y | ./bootstrap_environment --only system --no-vm --no-ai

- name: Run bootstrap tool (full-setup)
if: ${{ inputs.workflow_definition == 'full-setup' }}
run: |
echo y | ./bootstrap_environment --no-vm --no-ai
125 changes: 125 additions & 0 deletions ci/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package main

import (
"context"
"flag"
"fmt"
"os"
"strings"

"dagger.io/dagger"
"golang.org/x/sync/errgroup"
)

func main() {
osFlag := flag.String("os", "all", "OS to test (debian, arch, fedora, or all)")
flag.Parse()

ctx := context.Background()

// Initialize Dagger Client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to Dagger: %v\n", err)
os.Exit(1)
}
defer client.Close()

// Get reference to the project source directory
src := client.Host().Directory(".")

// Build the bootstrapping binary inside Go container
fmt.Println("Building bootstrap_environment binary for Linux...")
builder := client.Container().
From("golang:1.25").
WithMountedDirectory("/src", src).
WithWorkdir("/src").
WithExec([]string{"go", "build", "-o", "bootstrap_environment", "."})

binaryFile := builder.File("bootstrap_environment")

// Target OS list
var targets []string
switch strings.ToLower(*osFlag) {
case "debian":
targets = []string{"debian:latest"}
case "arch":
targets = []string{"archlinux:latest"}
case "fedora":
targets = []string{"fedora:latest"}
case "all":
targets = []string{"debian:latest", "archlinux:latest", "fedora:latest"}
default:
fmt.Fprintf(os.Stderr, "Unsupported OS: %s. Supported: debian, arch, fedora, all\n", *osFlag)
os.Exit(1)
}

g, ctx := errgroup.WithContext(ctx)

for _, target := range targets {
target := target // capture loop variable
g.Go(func() error {
fmt.Printf("=== Starting integration test on target OS: %s ===\n", target)

// 1. Prepare target container base and setup script based on OS distro
var testContainer *dagger.Container
if strings.Contains(target, "debian") {
testContainer = client.Container().
From(target).
WithExec([]string{"apt-get", "update"}).
WithExec([]string{"apt-get", "install", "-y", "sudo", "curl", "git", "wget", "tar", "unzip", "xz-utils", "make", "python3", "which"})
} else if strings.Contains(target, "fedora") {
testContainer = client.Container().
From(target).
WithExec([]string{"dnf", "install", "-y", "sudo", "curl", "git", "wget", "tar", "unzip", "xz", "make", "python3", "which"})
} else if strings.Contains(target, "archlinux") {
testContainer = client.Container().
From(target).
WithExec([]string{"pacman", "-Sy", "--noconfirm", "sudo", "curl", "git", "wget", "tar", "unzip", "xz", "make", "python", "which"})
} else {
return fmt.Errorf("unsupported target OS: %s", target)
}

// 2. Pre-create the ~/.pyenv directory to skip python source compilation in integration test (saves ~10 minutes)
testContainer = testContainer.WithExec([]string{"mkdir", "-p", "/root/.pyenv"})

// 3. Mount the built binary
testContainer = testContainer.
WithFile("/usr/local/bin/bootstrap_environment", binaryFile).
WithWorkdir("/tmp")

// 4. Run bootstrap binary with safe args: --only custom --no-vm --no-ai
// We pipe 'y' to satisfy the "Proceed? [y/N]" prompt.
fmt.Printf("[%s] Executing bootstrap_environment...\n", target)
testContainer = testContainer.WithExec([]string{"sh", "-c", "echo y | bootstrap_environment --only custom --no-vm --no-ai"})

// 5. Verify all installed custom packages return a path and zero exit code from version command
fmt.Printf("[%s] Verifying package installations on PATH and running version checks...\n", target)
verifyCmd := []string{
"sh", "-c",
"export PATH=$PATH:/usr/local/go/bin; " +
"which go && go version && " +
"which nvim && nvim --version && " +
"which zig && zig version && " +
"which firecracker && firecracker --version",
}
verifyOutput, err := testContainer.WithExec(verifyCmd).Stdout(ctx)
if err != nil {
return fmt.Errorf("verification failed on %s: package not found on PATH or exited with error (%w)", target, err)
}


fmt.Printf("[%s] Verification Output:\n%s\n", target, verifyOutput)
fmt.Printf("--- PASS: Integration test on %s completed successfully ---\n", target)
return nil

})
}

if err := g.Wait(); err != nil {
fmt.Fprintf(os.Stderr, "One or more integration tests failed: %v\n", err)
os.Exit(1)
}

fmt.Println("\nAll parallel integration tests passed successfully!")
}
34 changes: 34 additions & 0 deletions compat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"io"
"os"
)

var (
// Exec redirects
runCmd = runCmdReal
runShell = runShellReal
hasCmd = hasCmdReal
probe = probeReal

// Net redirects
download = downloadReal
fetchJSON = fetchJSONReal
fetchText = fetchTextReal

// OS redirects
osStat = os.Stat
osReadFile = os.ReadFile
osWriteFile = os.WriteFile
osMkdirAll = os.MkdirAll
osRemove = os.Remove
osRemoveAll = os.RemoveAll
osRename = os.Rename
osExit = os.Exit
stdin io.Reader = os.Stdin

// Filesystem paths
osReleasePath = "/etc/os-release"
passwdPath = "/etc/passwd"
)
Loading
Loading