testutil

package
v1.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2025 License: MIT Imports: 0 Imported by: 0

Documentation

Overview

Package testutil provides testing utilities and helpers for Hitch's test suite.

Overview

This package implements a comprehensive testing infrastructure that enables isolated, reproducible testing of Git operations. It provides test harnesses, mock repositories, and helper functions that make writing tests easier and more reliable.

Core Purpose

The package serves several key purposes:

  • Create isolated Git repositories for testing
  • Provide helper methods for common test operations
  • Ensure tests don't interfere with each other
  • Enable Docker-based integration testing
  • Simplify test setup and teardown

TestRepo Type

The primary type is TestRepo, which represents an isolated test repository:

type TestRepo struct {
    Path  string              // Temporary directory path
    Repo  *git.Repo           // Hitch's Repo wrapper
    T     *testing.T          // Test instance
    GoGit *git.Repository     // Underlying go-git repository
}

Creating Test Repositories

Create a fresh, isolated repository for each test:

func TestMyFeature(t *testing.T) {
    testRepo := testutil.NewTestRepo(t)
    defer testRepo.Cleanup()

    // Test repository is ready to use
    // - Has main branch
    // - Has initial commit
    // - Git user configured
    // - Clean working directory

    // Run your tests...
}

The test repository is created in a temporary directory and is completely isolated from other tests and the host system.

Initial Repository State

NewTestRepo creates a repository with:

  • Initialized git repository (git init)
  • Default branch set to 'main'
  • Git user configured (Test User <test@example.com>)
  • GPG signing disabled (for faster commits)
  • Initial commit with README.md
  • Clean working directory

Helper Methods

TestRepo provides many helper methods for common operations:

Branch Operations:

// Create and optionally switch to branch
err := testRepo.CreateBranch("feature/test", false)

// Switch to existing branch
err := testRepo.Checkout("feature/test")

// Check if branch exists
exists := testRepo.BranchExists("feature/test")

Commit Operations:

// Create a file and commit it
err := testRepo.CommitFile("test.txt", "content", "Add test file")

// Commit multiple files
files := map[string]string{
    "file1.txt": "content1",
    "file2.txt": "content2",
}
err := testRepo.CommitFiles(files, "Add multiple files")

Metadata Operations:

// Initialize Hitch metadata
meta := testRepo.InitializeMetadata([]string{"dev", "qa"}, "main")

// Write metadata
err := testRepo.WriteMetadata(meta, "Update metadata")

Cleanup

Always clean up test repositories:

testRepo := testutil.NewTestRepo(t)
defer testRepo.Cleanup()

Cleanup:

  • Removes the temporary directory
  • Deletes all files and git data
  • Frees disk space
  • Prevents test pollution

Using defer ensures cleanup happens even if test panics.

Complete Test Example

func TestPromoteOperation(t *testing.T) {
    // Create isolated repository
    testRepo := testutil.NewTestRepo(t)
    defer testRepo.Cleanup()

    // Initialize metadata
    meta := metadata.NewMetadata([]string{"dev"}, "main", "test@example.com")
    writer := metadata.NewWriter(testRepo.Repo)
    err := writer.WriteInitial(meta, "Test User", "test@example.com")
    if err != nil {
        t.Fatalf("Failed to initialize metadata: %v", err)
    }

    // Create feature branch
    err = testRepo.CreateBranch("feature/login", true)
    if err != nil {
        t.Fatalf("Failed to create branch: %v", err)
    }

    // Make changes
    err = testRepo.CommitFile("login.go", "package main", "Add login feature")
    if err != nil {
        t.Fatalf("Failed to commit: %v", err)
    }

    // Switch back to main
    err = testRepo.Checkout("main")
    if err != nil {
        t.Fatalf("Failed to checkout main: %v", err)
    }

    // Test promote operation
    err = meta.AddBranchToEnvironment("dev", "feature/login", "test@example.com")
    if err != nil {
        t.Errorf("Promote failed: %v", err)
    }

    // Verify
    if !meta.IsBranchInEnvironment("dev", "feature/login") {
        t.Error("Branch should be in dev environment")
    }
}

Docker Integration

Tests using testutil should use the dockertest build tag:

//go:build dockertest

package mypackage_test

Run with:

go test -tags=dockertest -v ./...

This ensures tests run in isolated Docker containers with:

  • Clean Git environment
  • Consistent Git version
  • No host system interference
  • Reproducible results

Test Isolation

TestRepo ensures perfect test isolation:

  • Each test gets its own temporary directory
  • No shared state between tests
  • Tests can run in parallel
  • No leftover files after tests

Enable parallel tests:

func TestSomething(t *testing.T) {
    t.Parallel()
    testRepo := testutil.NewTestRepo(t)
    defer testRepo.Cleanup()
    // Test runs in parallel with others
}

Helper Functions

Additional helper functions:

File operations:

// Write file to repository
err := testRepo.WriteFile("path/to/file.txt", "content")

// Read file from repository
content, err := testRepo.ReadFile("path/to/file.txt")

Git operations:

// Get current commit SHA
sha, err := testRepo.CurrentCommit()

// Get commit count
count, err := testRepo.CommitCount()

// Check if file exists
exists := testRepo.FileExists("test.txt")

Test Patterns

Common test patterns using testutil:

Table-Driven Tests:

func TestBranchOperations(t *testing.T) {
    testCases := []struct {
        name     string
        branch   string
        expected bool
    }{
        {"valid branch", "feature/test", true},
        {"invalid branch", "feat/test;", false},
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            testRepo := testutil.NewTestRepo(t)
            defer testRepo.Cleanup()

            // Test with isolated repository
        })
    }
}

Setup/Teardown Pattern:

func TestWithSetup(t *testing.T) {
    testRepo := testutil.NewTestRepo(t)
    defer testRepo.Cleanup()

    // Setup
    setupTestData(testRepo)

    // Execute test
    runTest(testRepo)

    // Verify
    verifyResults(testRepo)

    // Cleanup happens automatically via defer
}

Performance Considerations

Test repository creation is fast:

  • Uses os.MkdirTemp for temporary directories
  • Minimal git initialization
  • No network operations
  • Typical creation time: 10-50ms

For tests that need multiple repositories:

func TestMultiRepo(t *testing.T) {
    repo1 := testutil.NewTestRepo(t)
    defer repo1.Cleanup()

    repo2 := testutil.NewTestRepo(t)
    defer repo2.Cleanup()

    // Test interactions between repositories
}

Debugging Tests

When tests fail, TestRepo provides helpful information:

t.Logf("Test repository: %s", testRepo.Path)
t.Logf("Current branch: %s", testRepo.Repo.CurrentBranch())
t.Logf("Branches: %v", testRepo.Repo.Branches())

To inspect test repository after failure:

func TestDebug(t *testing.T) {
    testRepo := testutil.NewTestRepo(t)
    // Comment out cleanup to inspect repository
    // defer testRepo.Cleanup()

    // Test code...

    t.Logf("Repository at: %s", testRepo.Path)
    // Can cd to path and inspect git state
}

Best Practices

  1. Always use defer for Cleanup()
  2. Create new TestRepo for each test (don't share)
  3. Use t.Helper() in helper functions
  4. Use t.Fatal for setup errors, t.Error for test failures
  5. Keep tests focused and isolated
  6. Use descriptive test names
  7. Clean up even if test fails

Error Handling

TestRepo methods that can fail return errors:

err := testRepo.CreateBranch("feature/test", false)
if err != nil {
    t.Fatalf("Setup failed: %v", err)
}

Use t.Fatalf for setup errors (stop test immediately) Use t.Errorf for test assertions (continue to see all failures)

Integration with Other Packages

TestRepo integrates seamlessly with other Hitch packages:

// With metadata package
meta := metadata.NewMetadata(envs, "main", "test@example.com")
writer := metadata.NewWriter(testRepo.Repo)

// With safety package
tester := safety.NewTempBranchTester(testRepo.Repo)

// With git package
validator := git.NewRepositoryValidator(testRepo.Repo)

Test Coverage

Using testutil enables high test coverage:

  • Unit tests: Test individual functions
  • Integration tests: Test component interactions
  • End-to-end tests: Test complete workflows

Measure coverage:

go test -tags=dockertest -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

Example: Complex Integration Test

func TestCompleteWorkflow(t *testing.T) {
    // Create repository
    testRepo := testutil.NewTestRepo(t)
    defer testRepo.Cleanup()

    // Initialize Hitch
    meta := metadata.NewMetadata([]string{"dev", "qa"}, "main", "test@example.com")
    writer := metadata.NewWriter(testRepo.Repo)
    err := writer.WriteInitial(meta, "Test User", "test@example.com")
    require.NoError(t, err)

    // Create feature
    err = testRepo.CreateBranch("feature/workflow", true)
    require.NoError(t, err)

    err = testRepo.CommitFile("feature.go", "package main", "Add feature")
    require.NoError(t, err)

    // Promote to dev
    err = meta.AddBranchToEnvironment("dev", "feature/workflow", "test@example.com")
    require.NoError(t, err)

    // Promote to qa
    err = meta.AddBranchToEnvironment("qa", "feature/workflow", "test@example.com")
    require.NoError(t, err)

    // Verify final state
    assert.True(t, meta.IsBranchInEnvironment("dev", "feature/workflow"))
    assert.True(t, meta.IsBranchInEnvironment("qa", "feature/workflow"))
}

This package is the foundation of Hitch's testing infrastructure, enabling comprehensive, reliable, and isolated testing of all Git operations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL