Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
61 changes: 58 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,61 @@ jobs:
tests/artifacts/baselines/
retention-days: 7

e2e-test:
name: E2E Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false

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

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum

- name: Install asciinema (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y asciinema
# Install Xvfb for potential TUI tests
sudo apt-get install -y xvfb

- name: Install asciinema (macOS)
if: matrix.os == 'macos-latest'
run: brew install asciinema

- name: Configure PowerShell for E2E tests (Windows)
if: matrix.os == 'windows-latest'
shell: powershell
run: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process

- name: Run E2E tests
shell: bash
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
# Use Xvfb to run tests in a virtual display environment
xvfb-run go test -v -timeout=300s ./tests/e2e/...
else
go test -v -timeout=300s ./tests/e2e/...
fi

- name: Upload E2E artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: e2e-results-${{ matrix.os }}
path: |
tests/artifacts/e2e/
retention-days: 30

test-matrix:
name: Comprehensive Testing Matrix
runs-on: ubuntu-latest
Expand Down Expand Up @@ -325,12 +380,12 @@ jobs:
notify:
name: Notify
runs-on: ubuntu-latest
needs: [test-matrix, coverage-report, performance-benchmark, security-scan]
needs: [test, e2e-test, test-matrix, coverage-report, performance-benchmark, security-scan]
if: always() && (github.event_name == 'push' || github.event_name == 'pull_request')

steps:
- name: Notify on test failure
if: needs.test.result == 'failure' || needs.test-matrix.result == 'failure'
if: needs.test.result == 'failure' || needs.e2e-test.result == 'failure' || needs.test-matrix.result == 'failure'
run: |
echo "::warning ::Some tests failed. Please check the logs above."

Expand All @@ -340,6 +395,6 @@ jobs:
echo "::warning ::Coverage generation failed or threshold not met."

- name: Success notification
if: needs.test.result == 'success' && needs.test-matrix.result == 'success'
if: needs.test.result == 'success' && needs.e2e-test.result == 'success' && needs.test-matrix.result == 'success'
run: |
echo "::notice ::All tests passed successfully!"
32 changes: 29 additions & 3 deletions docs/testing-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ make test-unit
# Integration tests only
make test-integration

# E2E tests only
go test -v ./tests/e2e/...

# Benchmarks only
make test-benchmark

Expand Down Expand Up @@ -91,7 +94,10 @@ make quick-bench
tests/
├── unit/ # Fast, isolated unit tests
├── integration/ # Component interaction tests
├── e2e/ # End-to-end tests (future)
├── e2e/ # End-to-end tests simulating user workflows
├── recording/ # Terminal recording utilities
├── cross_platform/ # Cross-platform compatibility tests
├── reporting/ # Test reporting generation
└── artifacts/ # Test reports and coverage
```

Expand All @@ -103,6 +109,9 @@ cd tests/unit && go test -v -cover
# Run integration tests
cd tests/integration && go test -v

# Run E2E tests
cd tests/e2e && go test -v

# Run tests in parallel
cd tests/unit && go test -parallel=4
```
Expand Down Expand Up @@ -131,6 +140,23 @@ open tests/artifacts/coverage/coverage.html
cat tests/artifacts/coverage/coverage_summary.txt
```

## E2E Testing

### Running E2E Tests
To run the end-to-end tests, you can execute the following command:
```bash
# Run all E2E tests
go test -v ./tests/e2e/...
```

### E2E Artifacts
After running the E2E tests, you can find the terminal recordings in the `tests/artifacts/e2e/` directory. Each test run will have its own subdirectory containing the `.cast` file for the recording.

An HTML report summarizing all E2E test runs is also generated. You can find it at:
```
tests/artifacts/reports/e2e_report.html
```

## Performance Testing

### Run Benchmarks
Expand Down Expand Up @@ -231,10 +257,10 @@ make monitor-performance

### GitHub Actions
The testing framework is integrated with GitHub Actions and automatically:
- Runs tests on Linux, macOS, and Windows
- Runs unit, integration, and E2E tests on Linux, macOS, and Windows
- Generates coverage reports (Unix only)
- Runs benchmarks (Linux only)
- Uploads test artifacts
- Uploads test and E2E artifacts, including terminal recordings
- Checks coverage thresholds

### Quality Gates
Expand Down
63 changes: 50 additions & 13 deletions docs/testing-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Unit Tests (70%)

- **Unit Tests (70%)**: Fast, isolated tests of individual functions and methods
- **Integration Tests (25%)**: Tests of component interactions and integration points
- **E2E Tests (5%)**: Full workflow tests (framework established for future use)
- **E2E Tests (5%)**: Full workflow tests that simulate real user scenarios from start to finish across all supported platforms.

## Test Organization Structure

Expand All @@ -43,17 +43,22 @@ tests/
│ ├── component_interaction_test.go # Component interaction scenarios
│ ├── integration_fixtures.go # Test fixtures and mock objects
│ └── isolation_test.go # Test isolation and cleanup procedures
├── e2e/ # End-to-end tests (framework for 0.2.3)
│ └── workflow_test.go # Complete workflow testing
├── e2e/ # End-to-end tests simulating user workflows
│ ├── workflow_test.go # Core user journey tests
│ ├── platform_test.go # Platform-specific E2E tests
│ └── e2e_utils.go # E2E test utilities and orchestration
├── recording/ # Terminal recording utilities and tests
│ ├── capture_utils.go # Core recording functionality
│ └── capture_test.go # Tests for recording utilities
├── cross_platform/ # Cross-platform compatibility tests
│ └── platform_compat_test.go # Tests for platform-specific behavior
├── reporting/ # Test reporting generation and utilities
│ ├── reporting_utils.go # Report generation logic
│ └── aggregator_test.go # Tests for report data aggregation
├── artifacts/ # Generated test evidence and reports
│ ├── coverage/ # Coverage reports and historical data
│ │ ├── coverage.html # HTML coverage report
│ │ ├── coverage_summary.txt # Text coverage summary
│ │ └── combined_coverage.out # Combined coverage profile
│ ├── reports/ # Test execution reports
│ │ ├── benchmark_results.txt # Benchmark results
│ │ ├── performance_metrics.txt # Performance metrics
│ │ └── unix_test_report.txt # Unix-specific test report
│ ├── e2e/ # E2E test recordings and reports
│ ├── reports/ # Other test execution reports
│ └── platform_results/ # Platform-specific test results
└── scripts/ # Test execution utilities
├── unix_test_tools.sh # Unix-specific testing tools
Expand Down Expand Up @@ -100,6 +105,38 @@ func TestCalculatorAddition(t *testing.T) {
}
```

### End-to-End (E2E) Tests

**Purpose**: Validate complete user workflows from start to finish across all supported platforms.

**Characteristics**:
- Slower execution time, as they involve building and running the actual application binary.
- Test the application as a whole, including its interaction with the operating system.
- Provide terminal recordings as artifacts for visual validation and debugging.

**Best Practices**:
```go
// Good E2E test
func TestE2EWorkflow(t *testing.T) {
t.Run("TestSimpleAddition", func(t *testing.T) {
run := NewE2ETestRun(t)

expression := "2 + 2"
expected := "4"

// Record the command
run.RecordCommand(expression)

// Run the command for assertion
output := run.RunCommand(expression)

if !strings.Contains(output, expected) {
t.Errorf("Expected output to contain '%s', but got '%s'", expected, output)
}
})
}
```

## Test Types and Categories

### Unit Tests
Expand Down Expand Up @@ -313,7 +350,7 @@ The Unix environment validation script checks:
|----------|---------------|-----------------|------------|-------|
| Linux (Ubuntu) | ✅ Full | ✅ HTML + Text | ✅ Detailed | Primary platform |
| macOS | ✅ Full | ✅ HTML + Text | ✅ Basic | Development platform |
| Windows | ✅ Core | ❌ Limited | ❌ Limited | CI validation only |
| Windows | ✅ Full | ❌ Limited | ❌ Limited | Full CI validation including E2E |

### CI Pipeline

Expand Down Expand Up @@ -492,8 +529,8 @@ make coverage-html

### Planned Features

1. **Visual Testing**: Screenshot capture and comparison (Story 0.2.2)
2. **E2E Testing**: Complete workflow testing (Story 0.2.3)
1. **Visual Testing**: Screenshot capture and comparison (✅ **Completed** in Story 0.2.2)
2. **E2E Testing**: Complete workflow testing (✅ **Completed** in Story 0.2.3)
3. **Performance Regression**: Historical performance tracking
4. **Test Analytics**: Test execution analytics and insights
5. **Advanced CI/CD**: Enhanced CI/CD features and integrations
Expand Down
79 changes: 79 additions & 0 deletions tests/cross_platform/platform_compat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cross_platform

import (
"os"
"runtime"
"strings"
"testing"

"github.com/dmisiuk/acousticalc/tests/recording"
)

func TestPlatformIdentification(t *testing.T) {
t.Run("TestIdentifyOperatingSystem", func(t *testing.T) {
switch runtime.GOOS {
case "linux":
t.Log("Running on Linux")
case "darwin":
t.Log("Running on macOS")
case "windows":
t.Log("Running on Windows")
default:
t.Logf("Running on an unrecognized operating system: %s", runtime.GOOS)
}
})

t.Run("TestPathSeparator", func(t *testing.T) {
expectedSeparator := "/"
if runtime.GOOS == "windows" {
expectedSeparator = "\\"
}

// In Go, os.PathSeparator is a rune (int32). We need to convert it to a string for comparison.
actualSeparator := string(os.PathSeparator)

if actualSeparator != expectedSeparator {
t.Errorf("Expected path separator to be '%s' on %s, but got '%s'", expectedSeparator, runtime.GOOS, actualSeparator)
} else {
t.Logf("Correct path separator ('%s') found for %s", actualSeparator, runtime.GOOS)
}
})

t.Run("TestRecordingMechanism", func(t *testing.T) {
tempDir := t.TempDir()
recorder := recording.NewRecorder(tempDir, "TestRecording")

var expectedExtension string
if runtime.GOOS == "windows" {
expectedExtension = ".txt"
} else {
expectedExtension = ".cast"
}

// Use 'go version' as a simple, cross-platform command that produces output.
err := recorder.RecordCommand("go", "version")
if err != nil {
t.Fatalf("RecordCommand failed: %v", err)
}

files, err := os.ReadDir(tempDir)
if err != nil {
t.Fatalf("Failed to read temp dir: %v", err)
}

if len(files) != 1 {
var createdFiles []string
for _, f := range files {
createdFiles = append(createdFiles, f.Name())
}
t.Fatalf("Expected 1 file in temp dir, but got %d. Files: %v", len(files), createdFiles)
}

createdFile := files[0].Name()
if !strings.HasSuffix(createdFile, expectedExtension) {
t.Errorf("Expected file with extension '%s', but got '%s'", expectedExtension, createdFile)
} else {
t.Logf("Correct recording artifact found: %s", createdFile)
}
})
}
Loading
Loading