Skip to content

feat: add Windows PowerShell integration and e2e tests #74

feat: add Windows PowerShell integration and e2e tests

feat: add Windows PowerShell integration and e2e tests #74

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.24', '1.25']
steps:
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Download dependencies
run: go mod download
- name: Verify dependencies
run: go mod verify
- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage to Codecov
if: matrix.go-version == '1.25'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: timvw/wt
files: ./coverage.out
fail_ci_if_error: false
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Build
run: go build -v ./...
- name: Build binary
run: |
mkdir -p bin
go build -o bin/wt .
- name: Verify binary
run: |
./bin/wt --help
file bin/wt
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: latest
cross-compile:
name: Cross Compile
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Cross compile
run: |
mkdir -p bin
GOOS=linux GOARCH=amd64 go build -o bin/wt-linux-amd64 .
GOOS=darwin GOARCH=amd64 go build -o bin/wt-darwin-amd64 .
GOOS=darwin GOARCH=arm64 go build -o bin/wt-darwin-arm64 .
GOOS=windows GOARCH=amd64 go build -o bin/wt-windows-amd64.exe .
- name: Verify binaries
run: |
ls -lh bin/
file bin/*
e2e-macos:
name: E2E Tests (macOS)
runs-on: macos-latest
strategy:
matrix:
shell: ['bash', 'zsh']
steps:
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Download dependencies
run: go mod download
- name: Build wt binary
run: |
mkdir -p bin
go build -o bin/wt .
- name: Verify binary
run: |
./bin/wt --help
file bin/wt
- name: Run e2e tests for ${{ matrix.shell }}
run: |
# Set up isolated test environment
export ISOLATED_TMPDIR=$(mktemp -d)
export HOME=$ISOLATED_TMPDIR
# Run specific e2e tests (not ./... to avoid redundancy)
if [ "${{ matrix.shell }}" = "bash" ]; then
go test -v -run 'TestE2EAutoCdWithNonInteractiveCommand|TestE2EAutoCdWithCreate' .
elif [ "${{ matrix.shell }}" = "zsh" ]; then
go test -v -run TestE2EAutoCdInZsh .
fi
# Cleanup (best effort - ignore errors from Go cache files)
rm -rf "$ISOLATED_TMPDIR" 2>/dev/null || true
- name: Test shellenv output
run: |
# Capture shellenv output for inspection
./bin/wt shellenv > shellenv-${{ matrix.shell }}.txt
# Verify shellenv produces output
if [ ! -s shellenv-${{ matrix.shell }}.txt ]; then
echo "ERROR: shellenv produced no output"
exit 1
fi
# Check for key components (wt function)
if ! grep -q "wt()" shellenv-${{ matrix.shell }}.txt; then
echo "ERROR: wt function not found in shellenv output"
exit 1
fi
# Verify shell-specific features
if [ "${{ matrix.shell }}" = "bash" ]; then
grep -q "BASH_VERSION" shellenv-${{ matrix.shell }}.txt || (echo "ERROR: bash-specific code not found" && exit 1)
elif [ "${{ matrix.shell }}" = "zsh" ]; then
grep -q "ZSH_VERSION" shellenv-${{ matrix.shell }}.txt || (echo "ERROR: zsh-specific code not found" && exit 1)
fi
echo "shellenv validation passed for ${{ matrix.shell }}"
- name: Test worktree CRUD operations with auto-cd
run: |
# Save absolute paths BEFORE changing directories
WORK_DIR=$(pwd)
WT_BIN="$WORK_DIR/bin/wt"
WT_BIN_DIR="$WORK_DIR/bin"
# Create isolated test environment with separate HOME
ISOLATED_HOME=$(mktemp -d)
WORKTREE_ROOT=$(mktemp -d)/worktrees
TEST_REPO=$(mktemp -d)/test-repo
# Generate shellenv script
$WT_BIN shellenv > /tmp/wt-shellenv-${{ matrix.shell }}.sh
# Create test git repo
mkdir -p $TEST_REPO
cd $TEST_REPO
export HOME=$ISOLATED_HOME
git init
git config user.email "test@example.com"
git config user.name "Test User"
git commit --allow-empty -m "initial commit"
git branch -M main
# Create a test branch
git checkout -b test-branch
git commit --allow-empty -m "test commit"
git checkout main
# Determine shell command
if [ "${{ matrix.shell }}" = "bash" ]; then
SHELL_CMD="bash"
else
SHELL_CMD="zsh"
fi
# Save the current PATH so git is available in the subshell
CURRENT_PATH="$PATH"
# Run test in a subshell with shellenv sourced
$SHELL_CMD -c "
export HOME='$ISOLATED_HOME'
export WORKTREE_ROOT='$WORKTREE_ROOT'
export PATH='$WT_BIN_DIR:$CURRENT_PATH'
cd '$TEST_REPO'
source /tmp/wt-shellenv-${{ matrix.shell }}.sh
echo 'Testing: wt checkout test-branch'
wt checkout test-branch
# Verify we're in the worktree directory
CURRENT_DIR=\$(pwd)
EXPECTED_DIR='$WORKTREE_ROOT/test-repo/test-branch'
if [[ \"\$CURRENT_DIR\" != \"\$EXPECTED_DIR\" ]]; then
echo \"ERROR: Auto-cd failed. Expected: \$EXPECTED_DIR, Got: \$CURRENT_DIR\"
exit 1
fi
echo \"✓ Auto-cd to worktree verified: \$CURRENT_DIR\"
# Verify worktree was created
wt list | grep test-branch || (echo 'ERROR: worktree not in list' && exit 1)
echo '✓ Worktree created and listed'
# Go back to original repo to test create
cd '$TEST_REPO'
# Test create command (from main repo)
echo 'Testing: wt create feature-test'
wt create feature-test
CURRENT_DIR=\$(pwd)
EXPECTED_DIR='$WORKTREE_ROOT/test-repo/feature-test'
if [[ \"\$CURRENT_DIR\" != \"\$EXPECTED_DIR\" ]]; then
echo \"ERROR: Auto-cd failed for create. Expected: \$EXPECTED_DIR, Got: \$CURRENT_DIR\"
exit 1
fi
echo \"✓ Auto-cd to new worktree verified: \$CURRENT_DIR\"
# Go back to original repo to test remove
cd '$TEST_REPO'
# Test remove command
echo 'Testing: wt remove test-branch'
wt remove test-branch
# Verify worktree was removed
if wt list | grep test-branch; then
echo 'ERROR: worktree not removed'
exit 1
fi
echo '✓ Worktree removed successfully'
echo 'CRUD operations with auto-cd test passed!'
"
# Cleanup all temporary directories (best effort)
cd "$WORK_DIR"
rm -rf "$ISOLATED_HOME" "$WORKTREE_ROOT" "$TEST_REPO" /tmp/wt-shellenv-${{ matrix.shell }}.sh 2>/dev/null || true
- name: Upload shellenv output as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: shellenv-output-${{ matrix.shell }}
path: shellenv-${{ matrix.shell }}.txt
retention-days: 7
if-no-files-found: ignore
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-logs-${{ matrix.shell }}
path: |
/tmp/*.log
/var/tmp/*.log
retention-days: 7
if-no-files-found: ignore
e2e-windows:
name: E2E Tests (Windows)
runs-on: windows-latest
strategy:
matrix:
shell: ['powershell', 'pwsh']
steps:
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Download dependencies
run: go mod download
- name: Build wt binary
run: |
mkdir -p bin
go build -o bin/wt.exe .
- name: Verify binary
run: |
.\bin\wt.exe --help
- name: Install PowerShell Core
if: matrix.shell == 'pwsh'
run: |
# Check if pwsh is already available
if (Get-Command pwsh -ErrorAction SilentlyContinue) {
Write-Output "PowerShell Core already installed"
} else {
Write-Output "Installing PowerShell Core..."
winget install --id Microsoft.PowerShell --source winget --silent --accept-source-agreements --accept-package-agreements
}
shell: powershell
- name: Run Windows e2e tests for ${{ matrix.shell }}
run: |
# Run the appropriate tests based on shell type
if ("${{ matrix.shell }}" -eq "powershell") {
go test -v -run 'TestE2EAutoCdWithPowerShell|TestE2EAutoCdWithCreatePowerShell' .
} else {
# For pwsh, run the same tests but pwsh.exe must be available
go test -v -run 'TestE2EAutoCdWithPowerShell|TestE2EAutoCdWithCreatePowerShell' .
}
shell: powershell
- name: Test shellenv output for ${{ matrix.shell }}
run: |
$shellType = "${{ matrix.shell }}"
# Capture shellenv output for inspection
.\bin\wt.exe shellenv | Out-File -FilePath "shellenv-$shellType.txt"
# Verify shellenv produces output
if (-not (Test-Path "shellenv-$shellType.txt") -or (Get-Item "shellenv-$shellType.txt").Length -eq 0) {
Write-Error "ERROR: shellenv produced no output"
exit 1
}
# Check for key components (wt function and PowerShell detection)
$content = Get-Content "shellenv-$shellType.txt" -Raw
if ($content -notmatch 'function wt') {
Write-Error "ERROR: wt function not found in shellenv output"
exit 1
}
if ($content -notmatch '\$PSVersionTable') {
Write-Error "ERROR: PowerShell detection not found in shellenv output"
exit 1
}
Write-Output "shellenv validation passed for $shellType"
shell: powershell
- name: Test shellenv integration
run: |
$ErrorActionPreference = 'Stop'
# Set up isolated test environment
$WORK_DIR = (Get-Location).Path
$WT_BIN_DIR = Join-Path $WORK_DIR "bin"
$WT_BIN = Join-Path $WT_BIN_DIR "wt.exe"
$ISOLATED_HOME = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("test-home-" + (Get-Random)))
$WORKTREE_ROOT = Join-Path $env:TEMP ("worktrees-" + (Get-Random))
$TEST_REPO = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("test-repo-" + (Get-Random)))
$TEST_REPO_NAME = Split-Path -Leaf $TEST_REPO
try {
# Create test git repo
Set-Location $TEST_REPO
$env:HOME = $ISOLATED_HOME.FullName
git init
git config user.email "test@example.com"
git config user.name "Test User"
git commit --allow-empty -m "initial commit"
git branch -M main
# Create a test branch
git checkout -b test-branch
git commit --allow-empty -m "test commit"
git checkout main
# Add bin to PATH (like a real user would)
$env:PATH = "$WT_BIN_DIR;$env:PATH"
# Load shellenv
$env:WORKTREE_ROOT = $WORKTREE_ROOT
Invoke-Expression ((& $WT_BIN shellenv) -join "`n")
# Test checkout
Write-Output "Testing: wt checkout test-branch"
wt checkout test-branch
$CURRENT_DIR = (Get-Location).Path
$EXPECTED_DIR = Join-Path $WORKTREE_ROOT "$TEST_REPO_NAME\test-branch"
if ($CURRENT_DIR -ne $EXPECTED_DIR) {
Write-Error "ERROR: Auto-cd failed. Expected: $EXPECTED_DIR, Got: $CURRENT_DIR"
exit 1
}
Write-Output "✓ Auto-cd to worktree verified: $CURRENT_DIR"
# Verify worktree was created
$listOutput = wt list
if ($listOutput -notmatch 'test-branch') {
Write-Error "ERROR: worktree not in list"
exit 1
}
Write-Output "✓ Worktree created and listed"
# Go back to original repo to test create
Set-Location $TEST_REPO
# Test create command
Write-Output "Testing: wt create feature-test"
wt create feature-test
$CURRENT_DIR = (Get-Location).Path
$EXPECTED_DIR = Join-Path $WORKTREE_ROOT "$TEST_REPO_NAME\feature-test"
if ($CURRENT_DIR -ne $EXPECTED_DIR) {
Write-Error "ERROR: Auto-cd failed for create. Expected: $EXPECTED_DIR, Got: $CURRENT_DIR"
exit 1
}
Write-Output "✓ Auto-cd to new worktree verified: $CURRENT_DIR"
# Go back to original repo to test remove
Set-Location $TEST_REPO
# Test remove command
Write-Output "Testing: wt remove test-branch"
wt remove test-branch
# Verify worktree was removed
$listOutput = wt list
if ($listOutput -match 'test-branch') {
Write-Error "ERROR: worktree not removed"
exit 1
}
Write-Output "✓ Worktree removed successfully"
Write-Output "CRUD operations with auto-cd test passed!"
} finally {
# Cleanup
Set-Location $WORK_DIR
Remove-Item -Recurse -Force $ISOLATED_HOME -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $WORKTREE_ROOT -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $TEST_REPO -ErrorAction SilentlyContinue
}
shell: powershell
- name: Upload shellenv output as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: shellenv-output-${{ matrix.shell }}-windows
path: shellenv-*.txt
retention-days: 7
if-no-files-found: ignore
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-logs-${{ matrix.shell }}-windows
path: |
${{ runner.temp }}/*.log
retention-days: 7
if-no-files-found: ignore