Testing Guide
This guide covers testing strategies, patterns, and best practices for the Cloud Foundry Java Buildpack. The buildpack uses a comprehensive test suite with both unit tests and integration tests.
Table of Contents
- Overview
- Test Frameworks
- Unit Testing
- Integration Testing
- Testing Patterns
- Mocking and Stubbing
- Test Coverage
- Running Tests
- Writing New Tests
- Best Practices
- Troubleshooting
Overview
Test Types
The buildpack has two main types of tests:
-
Unit Tests - Test individual components in isolation
- Fast execution (~30 seconds for full suite)
- No external dependencies required
- Located in
src/java/**/*_test.go
-
Integration Tests - Test complete buildpack behavior
- Slower execution (~5-15 minutes)
- Require packaged buildpack and Docker/CF
- Located in
src/integration/*_test.go
Test Coverage
As of December 2024:
- 427 unit tests covering containers, frameworks, and JREs
- 50+ integration tests covering all container types and major frameworks
- ~85% code coverage across core components
Test Frameworks
Ginkgo v2
The primary test framework used for BDD-style tests.
Installation:
go install github.com/onsi/ginkgo/v2/ginkgo@latest
Why Ginkgo?
- Clean, readable BDD syntax
- Excellent test organization with
DescribeandContextblocks - Built-in parallel test execution
- Great integration with Gomega matchers
Example:
var _ = Describe("MyComponent", func() { Context("when configured correctly", func() { It("should succeed", func() { Expect(result).To(BeTrue()) }) }) })
Gomega
Assertion library with expressive matchers.
Common Matchers:
Expect(value).To(Equal(expected)) Expect(value).NotTo(BeNil()) Expect(string).To(ContainSubstring("text")) Expect(err).NotTo(HaveOccurred()) Expect(slice).To(HaveLen(5)) Expect(path).To(BeAnExistingFile())
Standard Testing Package
Used for simple unit tests that don't require BDD structure.
Example:
func TestMyFunction(t *testing.T) { result := MyFunction() if result != expected { t.Errorf("Expected %v, got %v", expected, result) } }
Switchblade
Integration testing framework for Cloud Foundry buildpacks.
Features:
- Deploy to Docker or Cloud Foundry
- Test with real applications
- Validate responses and logs
- Parallel test execution
Unit Testing
Test File Structure
Unit tests follow Go conventions:
src/java/
├── containers/
│ ├── spring_boot.go
│ ├── spring_boot_test.go # Tests for spring_boot.go
│ ├── tomcat.go
│ └── tomcat_test.go
├── frameworks/
│ ├── new_relic.go
│ └── framework_test.go # Tests for all frameworks
└── jres/
├── open_jdk.go
└── open_jdk_test.go
Basic Unit Test Structure (Standard Go)
File: src/java/frameworks/framework_test.go
package frameworks_test import ( "os" "testing" "github.com/cloudfoundry/java-buildpack/src/java/frameworks" "github.com/cloudfoundry/libbuildpack" ) func TestMyFrameworkDetect(t *testing.T) { // Setup: Create test context tmpDir, err := os.MkdirTemp("", "test-*") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) logger := libbuildpack.NewLogger(os.Stdout) stager := libbuildpack.NewStager( []string{tmpDir, "", "0"}, logger, &libbuildpack.Manifest{}, ) ctx := &frameworks.Context{ Stager: stager, Log: logger, } framework := frameworks.NewMyFramework(ctx) // Test: Execute detection name, err := framework.Detect() // Assert: Verify results if err != nil { t.Fatalf("Unexpected error: %v", err) } if name != "my-framework" { t.Errorf("Expected 'my-framework', got: %s", name) } }
Ginkgo/Gomega Unit Test Structure
File: src/java/containers/container_test.go
package containers_test import ( "os" "path/filepath" "testing" "github.com/cloudfoundry/java-buildpack/src/java/containers" "github.com/cloudfoundry/libbuildpack" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestContainers(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Containers Suite") } var _ = Describe("Spring Boot Container", func() { var ( ctx *containers.Context buildDir string depsDir string ) BeforeEach(func() { var err error buildDir, err = os.MkdirTemp("", "build") Expect(err).NotTo(HaveOccurred()) depsDir, err = os.MkdirTemp("", "deps") Expect(err).NotTo(HaveOccurred()) logger := libbuildpack.NewLogger(os.Stdout) stager := libbuildpack.NewStager( []string{buildDir, "", depsDir, "0"}, logger, &libbuildpack.Manifest{}, ) ctx = &containers.Context{ Stager: stager, Log: logger, } }) AfterEach(func() { os.RemoveAll(buildDir) os.RemoveAll(depsDir) }) Context("with BOOT-INF directory", func() { BeforeEach(func() { // Create Spring Boot structure os.MkdirAll(filepath.Join(buildDir, "BOOT-INF"), 0755) os.MkdirAll(filepath.Join(buildDir, "META-INF"), 0755) manifest := "Start-Class: com.example.App\n" manifestPath := filepath.Join(buildDir, "META-INF", "MANIFEST.MF") os.WriteFile(manifestPath, []byte(manifest), 0644) }) It("detects as Spring Boot", func() { container := containers.NewSpringBootContainer(ctx) name, err := container.Detect() Expect(err).NotTo(HaveOccurred()) Expect(name).To(Equal("Spring Boot")) }) }) Context("without Spring Boot indicators", func() { It("does not detect", func() { container := containers.NewSpringBootContainer(ctx) name, err := container.Detect() Expect(err).NotTo(HaveOccurred()) Expect(name).To(BeEmpty()) }) }) })
Testing with VCAP_SERVICES
Many frameworks detect based on bound services. Test this pattern:
func TestServiceBoundFramework(t *testing.T) { // Setup: Create VCAP_SERVICES JSON vcapJSON := `{ "newrelic": [{ "name": "newrelic-service", "label": "newrelic", "tags": ["apm", "monitoring"], "credentials": { "licenseKey": "test-key-123" } }] }` // Set environment variable os.Setenv("VCAP_SERVICES", vcapJSON) defer os.Unsetenv("VCAP_SERVICES") // Test framework detection ctx := createTestContext(t) framework := frameworks.NewNewRelicFramework(ctx) name, err := framework.Detect() if err != nil { t.Fatalf("Unexpected error: %v", err) } if name != "New Relic Agent" { t.Errorf("Expected 'New Relic Agent', got: %s", name) } // Verify credentials parsing services, _ := frameworks.GetVCAPServices() service := services.GetService("newrelic") licenseKey := service.Credentials["licenseKey"].(string) if licenseKey != "test-key-123" { t.Errorf("Expected license key 'test-key-123', got: %s", licenseKey) } }
Testing File-Based Detection
Test components that detect based on file presence:
func TestFileBasedDetection(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "test-*") defer os.RemoveAll(tmpDir) // Create marker file that triggers detection markerPath := filepath.Join(tmpDir, "WEB-INF", "web.xml") os.MkdirAll(filepath.Dir(markerPath), 0755) os.WriteFile(markerPath, []byte("<web-app/>"), 0644) // Create test context with temp directory as build dir logger := libbuildpack.NewLogger(os.Stdout) stager := libbuildpack.NewStager([]string{tmpDir, "", "0"}, logger, &libbuildpack.Manifest{}) ctx := &containers.Context{ Stager: stager, Log: logger, } // Test detection container := containers.NewTomcatContainer(ctx) name, err := container.Detect() if err != nil { t.Fatalf("Unexpected error: %v", err) } if name != "Tomcat" { t.Errorf("Expected 'Tomcat', got: %s", name) } }
Testing Configuration Parsing
Test components that parse configuration from environment variables:
func TestConfigurationParsing(t *testing.T) { tests := []struct { name string envValue string expected bool }{ { name: "enabled explicitly", envValue: "{enabled: true}", expected: true, }, { name: "disabled explicitly", envValue: "{enabled: false}", expected: false, }, { name: "empty config", envValue: "", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { os.Setenv("JBP_CONFIG_DEBUG", tt.envValue) defer os.Unsetenv("JBP_CONFIG_DEBUG") ctx := createTestContext(t) framework := frameworks.NewDebugFramework(ctx) name, _ := framework.Detect() detected := name != "" if detected != tt.expected { t.Errorf("Expected detected=%v, got %v", tt.expected, detected) } }) } }
Integration Testing
Integration Test Structure
File: src/integration/spring_boot_test.go
package integration_test import ( "path/filepath" "testing" "github.com/cloudfoundry/switchblade" "github.com/cloudfoundry/switchblade/matchers" "github.com/sclevine/spec" . "github.com/onsi/gomega" ) func testSpringBoot(platform switchblade.Platform, fixtures string) func(*testing.T, spec.G, spec.S) { return func(t *testing.T, context spec.G, it spec.S) { var ( Expect = NewWithT(t).Expect Eventually = NewWithT(t).Eventually name string ) it.Before(func() { var err error name, err = switchblade.RandomName() Expect(err).NotTo(HaveOccurred()) }) it.After(func() { if name != "" && (!settings.KeepFailedContainers || !t.Failed()) { Expect(platform.Delete.Execute(name)).To(Succeed()) } }) context("with a Spring Boot application", func() { it("successfully deploys and runs", func() { deployment, logs, err := platform.Deploy. WithEnv(map[string]string{ "BP_JAVA_VERSION": "11", }). Execute(name, filepath.Join(fixtures, "containers", "spring_boot_staged")) Expect(err).NotTo(HaveOccurred(), logs.String) Expect(logs.String()).To(ContainSubstring("Java Buildpack")) Eventually(deployment).Should(matchers.Serve(Not(BeEmpty()))) }) }) } }
Integration Test Setup
File: src/integration/init_test.go
package integration_test import ( "flag" "os" "testing" "time" "github.com/cloudfoundry/switchblade" "github.com/sclevine/spec" "github.com/sclevine/spec/report" . "github.com/onsi/gomega" ) var settings struct { Cached bool Serial bool KeepFailedContainers bool Platform string Stack string GitHubToken string } func init() { flag.BoolVar(&settings.Cached, "cached", false, "run cached buildpack tests") flag.BoolVar(&settings.Serial, "serial", false, "run tests serially") flag.BoolVar(&settings.KeepFailedContainers, "keep-failed-containers", false, "preserve failed containers") flag.StringVar(&settings.Platform, "platform", "cf", `platform to test ("cf" or "docker")`) flag.StringVar(&settings.Stack, "stack", "cflinuxfs4", "stack to use") flag.StringVar(&settings.GitHubToken, "github-token", "", "GitHub API token") } func TestIntegration(t *testing.T) { var Expect = NewWithT(t).Expect SetDefaultEventuallyTimeout(20 * time.Second) // Get buildpack file from environment buildpackFile := os.Getenv("BUILDPACK_FILE") if buildpackFile == "" { t.Fatal("BUILDPACK_FILE environment variable is required") } // Initialize platform platform, err := switchblade.NewPlatform(settings.Platform, settings.GitHubToken, settings.Stack) Expect(err).NotTo(HaveOccurred()) err = platform.Initialize( switchblade.Buildpack{ Name: "java_buildpack", URI: buildpackFile, }, ) Expect(err).NotTo(HaveOccurred()) // Create test suite var suite spec.Suite if settings.Serial { suite = spec.New("integration", spec.Report(report.Terminal{}), spec.Sequential()) } else { suite = spec.New("integration", spec.Report(report.Terminal{}), spec.Parallel()) } // Register test suites suite("SpringBoot", testSpringBoot(platform, fixtures)) suite("Tomcat", testTomcat(platform, fixtures)) suite("JavaMain", testJavaMain(platform, fixtures)) suite("Frameworks", testFrameworks(platform, fixtures)) suite.Run(t) Expect(platform.Deinitialize()).To(Succeed()) }
Running Integration Tests
Prerequisites:
- Package the buildpack
- Set BUILDPACK_FILE environment variable
- Have Docker running (for Docker platform tests)
Commands:
# Package buildpack ./scripts/package.sh --version dev # Run integration tests with Docker export BUILDPACK_FILE="${PWD}/build/buildpack.zip" ./scripts/integration.sh --platform docker # Run in parallel (faster) ./scripts/integration.sh --platform docker --parallel true # Keep failed containers for debugging ./scripts/integration.sh --platform docker --keep-failed-containers # Run specific test cd src/integration go test -v -run TestSpringBoot
Testing Patterns
Pattern 1: Table-Driven Tests
Test multiple scenarios with a single test function:
func TestVersionParsing(t *testing.T) { tests := []struct { name string input string expected string wantErr bool }{ { name: "valid version", input: "1.2.3", expected: "1.2.3", wantErr: false, }, { name: "version range", input: "1.+", expected: "1.", wantErr: false, }, { name: "invalid version", input: "invalid", expected: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ParseVersion(tt.input) if tt.wantErr { if err == nil { t.Error("Expected error, got nil") } return } if err != nil { t.Fatalf("Unexpected error: %v", err) } if result != tt.expected { t.Errorf("Expected %s, got %s", tt.expected, result) } }) } }
Pattern 2: Setup and Teardown
Use helper functions for common setup:
func createTestContext(t *testing.T) *frameworks.Context { tmpDir, err := os.MkdirTemp("", "test-*") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } t.Cleanup(func() { os.RemoveAll(tmpDir) }) logger := libbuildpack.NewLogger(os.Stdout) stager := libbuildpack.NewStager([]string{tmpDir, "", "0"}, logger, &libbuildpack.Manifest{}) return &frameworks.Context{ Stager: stager, Log: logger, } } func TestMyFramework(t *testing.T) { ctx := createTestContext(t) framework := frameworks.NewMyFramework(ctx) // ... test logic }
Pattern 3: Testing with Subtests
Organize related tests with subtests:
func TestFrameworkDetection(t *testing.T) { t.Run("with service bound", func(t *testing.T) { os.Setenv("VCAP_SERVICES", `{"newrelic":[{"name":"nr"}]}`) defer os.Unsetenv("VCAP_SERVICES") // Test detection }) t.Run("without service", func(t *testing.T) { os.Unsetenv("VCAP_SERVICES") // Test no detection }) t.Run("with invalid service", func(t *testing.T) { os.Setenv("VCAP_SERVICES", `{"newrelic":[]}`) defer os.Unsetenv("VCAP_SERVICES") // Test handling of invalid service }) }
Pattern 4: Testing Error Conditions
Verify proper error handling:
func TestErrorHandling(t *testing.T) { ctx := createTestContext(t) // Simulate error condition (missing required file) framework := frameworks.NewMyFramework(ctx) err := framework.Supply() if err == nil { t.Error("Expected error when required file is missing") } // Verify error message is helpful expectedMsg := "required file not found" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error containing '%s', got: %v", expectedMsg, err) } }
Pattern 5: Testing Filesystem Operations
Verify file creation, modification, and reading:
func TestFileOperations(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "test-*") defer os.RemoveAll(tmpDir) ctx := createTestContext(t) framework := frameworks.NewMyFramework(ctx) // Execute operation that creates files err := framework.Finalize() if err != nil { t.Fatalf("Finalize failed: %v", err) } // Verify file was created profilePath := filepath.Join(tmpDir, ".profile.d", "my_framework.sh") if _, err := os.Stat(profilePath); os.IsNotExist(err) { t.Errorf("Expected profile.d script to exist at %s", profilePath) } // Verify file contents content, _ := os.ReadFile(profilePath) if !strings.Contains(string(content), "export JAVA_OPTS") { t.Error("Profile script missing expected JAVA_OPTS export") } }
Mocking and Stubbing
Mocking External Dependencies
When testing components that download files or make HTTP requests:
type mockInstaller struct { installedDeps []string } func (m *mockInstaller) InstallDependency(dep libbuildpack.Dependency, targetDir string) error { m.installedDeps = append(m.installedDeps, dep.Name) // Simulate installation by creating a dummy file return os.WriteFile(filepath.Join(targetDir, dep.Name+".jar"), []byte("mock"), 0644) } func TestSupplyWithMock(t *testing.T) { mockInst := &mockInstaller{} ctx := &frameworks.Context{ Stager: createStager(t), Installer: mockInst, Log: libbuildpack.NewLogger(os.Stdout), } framework := frameworks.NewMyFramework(ctx) err := framework.Supply() if err != nil { t.Fatalf("Supply failed: %v", err) } // Verify mock was called if len(mockInst.installedDeps) != 1 { t.Errorf("Expected 1 dependency installed, got %d", len(mockInst.installedDeps)) } }
Stubbing Environment Variables
Use cleanup functions to ensure environment is reset:
func setEnvWithCleanup(t *testing.T, key, value string) { old := os.Getenv(key) os.Setenv(key, value) t.Cleanup(func() { if old == "" { os.Unsetenv(key) } else { os.Setenv(key, old) } }) } func TestWithEnvironment(t *testing.T) { setEnvWithCleanup(t, "JBP_CONFIG_DEBUG", "{enabled: true}") // Test with environment variable set // Cleanup happens automatically after test }
Test Coverage
Checking Coverage
# Run tests with coverage cd src/java go test -cover ./containers/... go test -cover ./frameworks/... go test -cover ./jres/... # Generate coverage report go test -coverprofile=coverage.out ./... go tool cover -html=coverage.out -o coverage.html # View coverage in terminal go tool cover -func=coverage.out
Coverage Goals
- Containers: >90% coverage (high confidence in detection and launch logic)
- Frameworks: >85% coverage (many frameworks have similar patterns)
- JREs: >80% coverage (JRE installation is well-tested)
- Utilities: >90% coverage (critical path code)
Improving Coverage
Identify uncovered code:
# Find uncovered lines go test -coverprofile=coverage.out ./frameworks/... go tool cover -func=coverage.out | grep -E "^.*\.go.*0\.0%"
Add tests for:
- Error conditions
- Edge cases (empty strings, nil values)
- Configuration variations
- Different file structures
Running Tests
Run All Unit Tests
# Using script (recommended) ./scripts/unit.sh # Using ginkgo directly cd src/java ginkgo -r --skip-package=integration # Using go test cd src/java go test ./...
Run Specific Tests
# Test a specific package cd src/java ginkgo frameworks/ # Test a specific file ginkgo frameworks/debug_test.go # Test by name pattern ginkgo --focus="Spring Boot" containers/ # Test with verbose output ginkgo -v frameworks/
Run Integration Tests
# Package buildpack first ./scripts/package.sh --version dev # Run integration tests export BUILDPACK_FILE="${PWD}/build/buildpack.zip" ./scripts/integration.sh --platform docker # Options ./scripts/integration.sh --platform docker --parallel true ./scripts/integration.sh --platform docker --keep-failed-containers ./scripts/integration.sh --platform docker --cached true
Run in Watch Mode
Automatically re-run tests when files change:
cd src/java
ginkgo watch -r frameworks/Run with Different Verbosity
# Quiet (only failures) ginkgo -succinct frameworks/ # Verbose (all output) ginkgo -v frameworks/ # Very verbose (includes Gomega details) ginkgo -vv frameworks/
Writing New Tests
Checklist for New Tests
When implementing a new component, write tests for:
-
Detection
- Detects when conditions are met
- Does not detect when conditions are not met
- Handles edge cases (missing files, invalid config)
-
Supply Phase
- Downloads correct dependencies
- Creates necessary directories
- Handles download failures gracefully
- Logs appropriate messages
-
Finalize Phase
- Creates profile.d scripts
- Sets environment variables correctly
- Generates correct JVM options
- Handles missing files gracefully
-
Configuration
- Parses configuration correctly
- Handles missing configuration
- Handles invalid configuration
- Respects user overrides
-
Integration
- Works with real applications
- Produces correct startup command
- Application runs successfully
Test Template for New Framework
package frameworks_test import ( "os" "testing" "github.com/cloudfoundry/java-buildpack/src/java/frameworks" "github.com/cloudfoundry/libbuildpack" ) func TestMyNewFrameworkDetect(t *testing.T) { t.Run("with service bound", func(t *testing.T) { vcapJSON := `{ "my-service": [{ "name": "my-service-instance", "credentials": {"api_key": "test-key"} }] }` os.Setenv("VCAP_SERVICES", vcapJSON) defer os.Unsetenv("VCAP_SERVICES") ctx := createTestContext(t) framework := frameworks.NewMyNewFramework(ctx) name, err := framework.Detect() if err != nil { t.Fatalf("Unexpected error: %v", err) } if name != "my-new-framework" { t.Errorf("Expected 'my-new-framework', got: %s", name) } }) t.Run("without service", func(t *testing.T) { os.Unsetenv("VCAP_SERVICES") ctx := createTestContext(t) framework := frameworks.NewMyNewFramework(ctx) name, err := framework.Detect() if err != nil { t.Fatalf("Unexpected error: %v", err) } if name != "" { t.Errorf("Expected no detection, got: %s", name) } }) } func TestMyNewFrameworkSupply(t *testing.T) { // Test supply phase } func TestMyNewFrameworkFinalize(t *testing.T) { // Test finalize phase } func createTestContext(t *testing.T) *frameworks.Context { tmpDir, err := os.MkdirTemp("", "test-*") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } t.Cleanup(func() { os.RemoveAll(tmpDir) }) logger := libbuildpack.NewLogger(os.Stdout) stager := libbuildpack.NewStager([]string{tmpDir, "", "0"}, logger, &libbuildpack.Manifest{}) return &frameworks.Context{ Stager: stager, Log: logger, } }
Best Practices
1. Test One Thing at a Time
// GOOD - Tests one specific behavior func TestDetectWithValidService(t *testing.T) { // Setup valid service // Test detection succeeds } func TestDetectWithInvalidService(t *testing.T) { // Setup invalid service // Test detection fails } // BAD - Tests multiple things func TestDetect(t *testing.T) { // Test with valid service // Test with invalid service // Test with no service // Test with multiple services // ... }
2. Use Descriptive Test Names
// GOOD func TestDetect_WithNewRelicService_ReturnsNewRelicAgent(t *testing.T) func TestSupply_WhenDependencyMissing_ReturnsError(t *testing.T) // BAD func TestDetect1(t *testing.T) func TestDetect2(t *testing.T)
3. Clean Up Resources
// GOOD - Use t.Cleanup for automatic cleanup func TestMyFunction(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "test-*") t.Cleanup(func() { os.RemoveAll(tmpDir) }) // Test logic } // GOOD - Use defer for immediate cleanup func TestMyFunction(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "test-*") defer os.RemoveAll(tmpDir) // Test logic }
4. Test Error Messages
func TestErrorMessage(t *testing.T) { err := MyFunction() if err == nil { t.Fatal("Expected error, got nil") } // Verify error is helpful if !strings.Contains(err.Error(), "required field missing") { t.Errorf("Error message not helpful: %v", err) } }
5. Avoid Hardcoded Paths
// BAD testFile := "/tmp/test/file.txt" // GOOD tmpDir, _ := os.MkdirTemp("", "test-*") testFile := filepath.Join(tmpDir, "file.txt")
6. Use Helper Functions
func createFrameworkContext(t *testing.T, buildDir string) *frameworks.Context { logger := libbuildpack.NewLogger(os.Stdout) stager := libbuildpack.NewStager([]string{buildDir, "", "0"}, logger, &libbuildpack.Manifest{}) return &frameworks.Context{ Stager: stager, Log: logger, } } func setVCAPServices(t *testing.T, json string) { os.Setenv("VCAP_SERVICES", json) t.Cleanup(func() { os.Unsetenv("VCAP_SERVICES") }) }
7. Test Parallel-Safe
Ensure tests can run in parallel:
func TestParallelSafe(t *testing.T) { t.Parallel() // Mark test as parallel-safe // Use unique temp directories tmpDir, _ := os.MkdirTemp("", "test-*") defer os.RemoveAll(tmpDir) // Avoid shared state // Use t.Cleanup for cleanup }
Troubleshooting
Tests Failing After Code Changes
-
Rebuild binaries:
-
Clear stale test cache:
-
Run with verbose output:
cd src/java ginkgo -v frameworks/
Integration Tests Timing Out
-
Increase timeout:
# In test code SetDefaultEventuallyTimeout(60 * time.Second)
-
Run serially:
./scripts/integration.sh --platform docker --parallel false -
Check Docker resources:
- Increase Docker memory/CPU limits
- Check for running containers:
docker ps
Test Fixtures Not Found
# Verify fixtures exist ls src/integration/testdata/ # Check paths in test code # Use filepath.Join with relative paths
Ginkgo Not Found
# Install Ginkgo go install github.com/onsi/ginkgo/v2/ginkgo@latest # Add to PATH export PATH="${PATH}:${HOME}/go/bin"
Permission Errors
# Ensure test directories are writable chmod -R 755 /tmp/test-* # Check temp directory location echo $TMPDIR
Flaky Tests
-
Identify flaky test:
# Run multiple times for i in {1..10}; do go test ./frameworks/...; done
-
Common causes:
- Race conditions (use
go test -race) - Filesystem timing issues (add small delays)
- Shared state between tests
- Network dependencies
- Race conditions (use
-
Fix strategies:
- Use
t.Parallel()to isolate tests - Use unique temp directories per test
- Add retries for network operations
- Mock external dependencies
- Use
Next Steps
- Implementing Frameworks - Learn framework patterns to test
- Implementing Containers - Learn container patterns to test
- Developer Guide - Development workflow and tools
- Contributing - Code standards and conventions
- Architecture - Understand system design for better tests