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
60 changes: 56 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ on:
required: false
default: false
type: boolean
skip_build:
description: "Skip build and use local image (for local testing with act)"
required: false
default: false
type: boolean

jobs:
# ============================================
Expand Down Expand Up @@ -121,6 +126,7 @@ jobs:
name: Integration Tests
runs-on: ubuntu-latest
needs: build-test-image
if: ${{ !inputs.skip_build }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -146,19 +152,65 @@ jobs:
sudo ./install.sh /usr/local

- name: Run integration tests
env:
CI: true
run: bats tests/integration/

integration-tests-local:
name: Integration Tests (Local Image)
runs-on: ubuntu-latest
if: ${{ inputs.skip_build }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Verify Docker image exists
run: |
echo "Checking for locally-built Docker image..."
if docker images | grep -q "jmcombs/powershell.*latest"; then
echo "✅ Found jmcombs/powershell:latest"
docker images | grep powershell
else
echo "❌ Error: jmcombs/powershell:latest not found"
echo ""
echo "Please build the image first:"
echo " ./scripts/build-local-arm64.sh"
exit 1
fi

- name: Install bats-core
run: |
git clone https://github.com/bats-core/bats-core.git /tmp/bats-core
cd /tmp/bats-core
sudo ./install.sh /usr/local

- name: Run integration tests
run: bats tests/integration/

- name: Test summary
if: always()
run: |
echo ""
echo "================================================"
echo "Integration Tests Complete"
echo "================================================"
echo ""
echo "Image tested: jmcombs/powershell:latest"
docker images | grep powershell || true

# ============================================
# Stage 4: Publish (only on main, after tests)
# ============================================

publish:
name: Build and Publish
runs-on: ubuntu-latest
needs: integration-tests
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: [integration-tests, integration-tests-local]
if: |
always() &&
github.ref == 'refs/heads/main' &&
github.event_name != 'pull_request' &&
!inputs.skip_build &&
(needs.integration-tests.result == 'success' || needs.integration-tests.result == 'skipped') &&
(needs.integration-tests-local.result == 'success' || needs.integration-tests-local.result == 'skipped')
permissions:
contents: write
steps:
Expand Down
53 changes: 32 additions & 21 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,27 @@ This project maintains LTS versions of PowerShell Core and .NET Core in Linux co
### Local Development

1. **Clone the repository**:

```bash
git clone https://github.com/jmcombs/powershell.git
cd powershell
```

2. **Install testing dependencies**:

```bash
# Install bats-core for testing
git clone https://github.com/bats-core/bats-core.git
cd bats-core && sudo ./install.sh /usr/local
cd .. && rm -rf bats-core

# Install shellcheck for script validation
sudo apt-get install shellcheck # Ubuntu/Debian
brew install shellcheck # macOS
```

3. **Make scripts executable**:

```bash
chmod +x scripts/*.sh
```
Expand All @@ -60,16 +63,12 @@ This project uses a comprehensive testing strategy with multiple test types:

### Test Structure

```
```text
tests/
├── test_helper.bash # Common test utilities and setup
├── mocks/ # Mock data for testing
│ ├── dotnet_releases_index.json
│ ├── dotnet_releases.json
│ └── powershell_release.json
├── unit/ # Unit tests with mocked dependencies
├── unit/ # Offline unit tests (no network calls)
│ └── test_get_net_pwsh_versions.bats
└── integration/ # Integration tests with real network calls
└── integration/ # Integration tests with live network calls
└── test_script_integration.bats
```

Expand All @@ -90,17 +89,25 @@ bats tests/unit/test_get_net_pwsh_versions.bats
bats -t tests/
```

### Local Testing on Apple Silicon

For developers using Apple Silicon (M-series) Macs:

- **Automated build and test**: Run `./scripts/build-local-arm64.sh` to build the Docker image natively for ARM64 and automatically run all integration tests using `act`
- **Why this is needed**: The main CI workflow builds for AMD64 (x86_64), which causes QEMU emulation issues on Apple Silicon. The build script uses a consolidated workflow with `skip_build=true` to test against locally-built ARM64 images
- **Manual testing**: After building, run `act workflow_dispatch --pull=false --input skip_build=true -j integration-tests-local` to execute integration tests independently

### Test Categories

1. **Unit Tests**: Test individual functions with mocked dependencies
1. **Unit Tests**: Test individual helper functions and local behavior
- Fast execution
- No network dependencies
- Test edge cases and error conditions
- Validate script structure, env-file helpers, and version format logic

2. **Integration Tests**: Test complete workflows with real API calls
2. **Integration Tests**: Test complete workflows with live API calls and the built container image
- Slower execution
- Require network access
- Test real-world scenarios
- Require network access and Docker
- Validate real-world .NET and PowerShell LTS discovery and build args

3. **Script Validation**: Static analysis and syntax checking
- Shellcheck for bash script quality
Expand All @@ -111,19 +118,19 @@ bats -t tests/

When adding new functionality:

1. **Write unit tests first** for new functions
2. **Add integration tests** for end-to-end workflows
3. **Update mock data** if API responses change
4. **Test error conditions** and edge cases
1. **Write unit tests first** for new helper functions and local behavior
2. **Add integration tests** for end-to-end workflows that depend on external services
3. **Test error conditions** and edge cases, especially around network failures and malformed responses

Example test structure:

```bash
@test "descriptive test name" {
# Arrange: Set up test conditions

# Act: Execute the code being tested
run your_function_or_command

# Assert: Verify the results
[ "$status" -eq 0 ]
[[ "$output" =~ "expected pattern" ]]
Expand All @@ -141,7 +148,7 @@ Example test structure:
- Use meaningful function and variable names
- Add comments for complex logic

### Testing
### Test Guidelines

- Use descriptive test names that explain what is being tested
- Group related tests in the same file
Expand All @@ -153,6 +160,7 @@ Example test structure:
### Branch Naming

Use descriptive branch names with prefixes:

- `feature/description` - New features
- `fix/description` - Bug fixes
- `test/description` - Test improvements
Expand All @@ -161,7 +169,8 @@ Use descriptive branch names with prefixes:
### Commit Messages

Follow conventional commit format:
```

```text
type(scope): description

Longer explanation if needed
Expand Down Expand Up @@ -197,6 +206,7 @@ Types: `feat`, `fix`, `test`, `docs`, `ci`, `refactor`
### Bug Reports

Use the bug report template and include:

- Clear description of the issue
- Steps to reproduce
- Expected vs actual behavior
Expand All @@ -206,6 +216,7 @@ Use the bug report template and include:
### Feature Requests

Use the feature request template and include:

- Clear description of the proposed feature
- Use case and motivation
- Possible implementation approach
Expand Down
89 changes: 89 additions & 0 deletions scripts/build-local-arm64.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/bin/bash
set -e

# Build Docker image locally for ARM64 (Apple Silicon)
# This script builds the image natively without QEMU emulation issues

echo "🔧 Building PowerShell Docker image for ARM64 (Apple Silicon)..."

# Get version information
echo "📋 Getting .NET and PowerShell versions..."
chmod +x ./scripts/get-net-pwsh-versions.sh
./scripts/get-net-pwsh-versions.sh

# Source the environment variables
if [ -f /tmp/env_vars ]; then
echo "✅ Loading build arguments from /tmp/env_vars"
export $(cat /tmp/env_vars | xargs)
else
echo "❌ Error: /tmp/env_vars not found"
exit 1
fi

# Build the image for ARM64
echo "🐳 Building Docker image for linux/arm64..."
docker buildx build \
--platform linux/arm64 \
--build-arg NET_RUNTIME_LTS_VERSION="${NET_RUNTIME_LTS_VERSION}" \
--build-arg NET_RUNTIME_URL_arm="${NET_RUNTIME_URL_arm}" \
--build-arg NET_RUNTIME_PACKAGE_NAME_arm="${NET_RUNTIME_PACKAGE_NAME_arm}" \
--build-arg NET_RUNTIME_URL_arm64="${NET_RUNTIME_URL_arm64}" \
--build-arg NET_RUNTIME_PACKAGE_NAME_arm64="${NET_RUNTIME_PACKAGE_NAME_arm64}" \
--build-arg NET_RUNTIME_URL_x64="${NET_RUNTIME_URL_x64}" \
--build-arg NET_RUNTIME_PACKAGE_NAME_x64="${NET_RUNTIME_PACKAGE_NAME_x64}" \
--build-arg PWSH_LTS_URL_arm32="${PWSH_LTS_URL_arm32}" \
--build-arg PWSH_LTS_PACKAGE_NAME_arm32="${PWSH_LTS_PACKAGE_NAME_arm32}" \
--build-arg PWSH_LTS_URL_arm64="${PWSH_LTS_URL_arm64}" \
--build-arg PWSH_LTS_PACKAGE_NAME_arm64="${PWSH_LTS_PACKAGE_NAME_arm64}" \
--build-arg PWSH_LTS_URL_x64="${PWSH_LTS_URL_x64}" \
--build-arg PWSH_LTS_PACKAGE_NAME_x64="${PWSH_LTS_PACKAGE_NAME_x64}" \
--build-arg PWSH_LTS_VERSION="${PWSH_LTS_VERSION}" \
--build-arg PWSH_LTS_MAJOR_VERSION="${PWSH_LTS_MAJOR_VERSION}" \
--load \
--tag jmcombs/powershell:test \
--tag jmcombs/powershell:latest \
.

echo ""
echo "✅ Build complete!"
echo ""
echo "📦 Tagged images:"
echo " - jmcombs/powershell:test"
echo " - jmcombs/powershell:latest"
echo ""

# Tag the image as 'latest' for integration tests
echo "🏷️ Tagging image as latest for integration tests..."
docker tag jmcombs/powershell:test jmcombs/powershell:latest

# Run integration tests with act using the main CI workflow with skip_build input
echo "🧪 Running integration tests with act..."
echo ""

if command -v act &> /dev/null; then
# Use the main ci.yml workflow with skip_build=true to bypass the build job
# This runs the integration-tests-local job which assumes the image already exists
act workflow_dispatch --pull=false --input skip_build=true -j integration-tests-local
TEST_EXIT_CODE=$?

echo ""
if [ $TEST_EXIT_CODE -eq 0 ]; then
echo "✅ All integration tests passed!"
else
echo "❌ Integration tests failed with exit code: $TEST_EXIT_CODE"
exit $TEST_EXIT_CODE
fi
else
echo "⚠️ Warning: 'act' is not installed. Skipping integration tests."
echo ""
echo " Install act with:"
echo " brew install act"
echo ""
echo " To run tests manually:"
echo " act workflow_dispatch --pull=false --input skip_build=true -j integration-tests-local"
fi

echo ""
echo "🚀 To run the container:"
echo " docker run -it jmcombs/powershell:latest"