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 ¶
- Always use defer for Cleanup()
- Create new TestRepo for each test (don't share)
- Use t.Helper() in helper functions
- Use t.Fatal for setup errors, t.Error for test failures
- Keep tests focused and isolated
- Use descriptive test names
- 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.