diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82085e8..e55f9c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: # ============================================ @@ -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 @@ -146,10 +152,50 @@ 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) # ============================================ @@ -157,8 +203,14 @@ jobs: 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: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4fde587..146d5c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ``` @@ -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 ``` @@ -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 @@ -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" ]] @@ -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 @@ -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 @@ -161,7 +169,8 @@ Use descriptive branch names with prefixes: ### Commit Messages Follow conventional commit format: -``` + +```text type(scope): description Longer explanation if needed @@ -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 @@ -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 diff --git a/scripts/build-local-arm64.sh b/scripts/build-local-arm64.sh new file mode 100755 index 0000000..7c066d7 --- /dev/null +++ b/scripts/build-local-arm64.sh @@ -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" +