mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-08 04:57:36 +00:00
shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 16 cross-shell utilities converted to sh/bash chezmoi executables: ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj (ghg-clone not named ghg: unrelated cargo binary owns that name) - cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files - fish keeps only shell-state functions; converted ones now hit PATH - cp-appid ignore gating moved to .local/bin path - dot_agents/skills: 82 skills materialized from dotstate storage so dot_claude skill symlinks resolve on fresh bootstrap; dotstate profile/symlinks.json released .agents/skills .bashrc .zshrc
This commit is contained in:
1 parent
6776366ce6
commit
6406208b35
637 files changed
+100297
-497
No files matched your search
@@ -0,0 +1,451 @@
|
||||
# Testing and Benchmarking
|
||||
|
||||
## Table-Driven Tests
|
||||
|
||||
```go
|
||||
package math
|
||||
|
||||
import "testing"
|
||||
|
||||
func Add(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b int
|
||||
expected int
|
||||
}{
|
||||
{"positive numbers", 2, 3, 5},
|
||||
{"negative numbers", -2, -3, -5},
|
||||
{"mixed signs", -2, 3, 1},
|
||||
{"zeros", 0, 0, 0},
|
||||
{"large numbers", 1000000, 2000000, 3000000},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Add(tt.a, tt.b)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Subtests and Parallel Execution
|
||||
|
||||
```go
|
||||
func TestParallel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"lowercase", "hello", "HELLO"},
|
||||
{"uppercase", "WORLD", "WORLD"},
|
||||
{"mixed", "HeLLo", "HELLO"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt // Capture range variable for parallel tests
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel() // Run subtests in parallel
|
||||
|
||||
result := strings.ToUpper(tt.input)
|
||||
if result != tt.want {
|
||||
t.Errorf("got %q, want %q", result, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Helpers and Setup/Teardown
|
||||
|
||||
```go
|
||||
func TestWithSetup(t *testing.T) {
|
||||
// Setup
|
||||
db := setupTestDB(t)
|
||||
defer cleanupTestDB(t, db)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
user User
|
||||
}{
|
||||
{"valid user", User{Name: "John", Email: "john@example.com"}},
|
||||
{"empty name", User{Name: "", Email: "test@example.com"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := db.SaveUser(tt.user)
|
||||
if err != nil {
|
||||
t.Fatalf("SaveUser failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function (doesn't show in stack trace)
|
||||
func setupTestDB(t *testing.T) *DB {
|
||||
t.Helper()
|
||||
|
||||
db, err := NewDB(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test DB: %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func cleanupTestDB(t *testing.T, db *DB) {
|
||||
t.Helper()
|
||||
|
||||
if err := db.Close(); err != nil {
|
||||
t.Errorf("failed to close DB: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mocking with Interfaces
|
||||
|
||||
```go
|
||||
// Interface to mock
|
||||
type EmailSender interface {
|
||||
Send(to, subject, body string) error
|
||||
}
|
||||
|
||||
// Mock implementation
|
||||
type MockEmailSender struct {
|
||||
SentEmails []Email
|
||||
ShouldFail bool
|
||||
}
|
||||
|
||||
type Email struct {
|
||||
To, Subject, Body string
|
||||
}
|
||||
|
||||
func (m *MockEmailSender) Send(to, subject, body string) error {
|
||||
if m.ShouldFail {
|
||||
return fmt.Errorf("failed to send email")
|
||||
}
|
||||
m.SentEmails = append(m.SentEmails, Email{to, subject, body})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test using mock
|
||||
func TestUserService_Register(t *testing.T) {
|
||||
mockSender := &MockEmailSender{}
|
||||
service := NewUserService(mockSender)
|
||||
|
||||
err := service.Register("user@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Register failed: %v", err)
|
||||
}
|
||||
|
||||
if len(mockSender.SentEmails) != 1 {
|
||||
t.Errorf("expected 1 email sent, got %d", len(mockSender.SentEmails))
|
||||
}
|
||||
|
||||
email := mockSender.SentEmails[0]
|
||||
if email.To != "user@example.com" {
|
||||
t.Errorf("expected email to user@example.com, got %s", email.To)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Benchmarking
|
||||
|
||||
```go
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Add(100, 200)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark with subtests
|
||||
func BenchmarkStringOperations(b *testing.B) {
|
||||
benchmarks := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{"short", "hello"},
|
||||
{"medium", strings.Repeat("hello", 10)},
|
||||
{"long", strings.Repeat("hello", 100)},
|
||||
}
|
||||
|
||||
for _, bm := range benchmarks {
|
||||
b.Run(bm.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = strings.ToUpper(bm.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark with setup
|
||||
func BenchmarkMapOperations(b *testing.B) {
|
||||
m := make(map[string]int)
|
||||
for i := 0; i < 1000; i++ {
|
||||
m[fmt.Sprintf("key%d", i)] = i
|
||||
}
|
||||
|
||||
b.ResetTimer() // Don't count setup time
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = m["key500"]
|
||||
}
|
||||
}
|
||||
|
||||
// Parallel benchmark
|
||||
func BenchmarkConcurrentAccess(b *testing.B) {
|
||||
var counter int64
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
atomic.AddInt64(&counter, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Memory allocation benchmark
|
||||
func BenchmarkAllocation(b *testing.B) {
|
||||
b.ReportAllocs() // Report allocations
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
s := make([]int, 1000)
|
||||
_ = s
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fuzzing (Go 1.18+)
|
||||
|
||||
```go
|
||||
func FuzzReverse(f *testing.F) {
|
||||
// Seed corpus
|
||||
testcases := []string{"hello", "world", "123", ""}
|
||||
for _, tc := range testcases {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, input string) {
|
||||
reversed := Reverse(input)
|
||||
doubleReversed := Reverse(reversed)
|
||||
|
||||
if input != doubleReversed {
|
||||
t.Errorf("Reverse(Reverse(%q)) = %q, want %q", input, doubleReversed, input)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Fuzz with multiple parameters
|
||||
func FuzzAdd(f *testing.F) {
|
||||
f.Add(1, 2)
|
||||
f.Add(0, 0)
|
||||
f.Add(-1, 1)
|
||||
|
||||
f.Fuzz(func(t *testing.T, a, b int) {
|
||||
result := Add(a, b)
|
||||
|
||||
// Properties that should always hold
|
||||
if result < a && b >= 0 {
|
||||
t.Errorf("Add(%d, %d) = %d; result should be >= a when b >= 0", a, b, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Test Coverage
|
||||
|
||||
```go
|
||||
// Run tests with coverage:
|
||||
// go test -cover
|
||||
// go test -coverprofile=coverage.out
|
||||
// go tool cover -html=coverage.out
|
||||
|
||||
func TestCalculate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input int
|
||||
expected int
|
||||
}{
|
||||
{"zero", 0, 0},
|
||||
{"positive", 5, 25},
|
||||
{"negative", -3, 9},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Calculate(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Calculate(%d) = %d; want %d", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Race Detector
|
||||
|
||||
```go
|
||||
// Run with: go test -race
|
||||
|
||||
func TestConcurrentAccess(t *testing.T) {
|
||||
var counter int
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// This will fail with -race if not synchronized
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
counter++ // Data race!
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Fixed version with mutex
|
||||
func TestConcurrentAccessSafe(t *testing.T) {
|
||||
var counter int
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mu.Lock()
|
||||
counter++
|
||||
mu.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if counter != 10 {
|
||||
t.Errorf("expected 10, got %d", counter)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Golden Files
|
||||
|
||||
```go
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderHTML(t *testing.T) {
|
||||
data := Data{Title: "Test", Content: "Hello"}
|
||||
result := RenderHTML(data)
|
||||
|
||||
goldenFile := filepath.Join("testdata", "expected.html")
|
||||
|
||||
if *update {
|
||||
// Update golden file: go test -update
|
||||
os.WriteFile(goldenFile, []byte(result), 0644)
|
||||
}
|
||||
|
||||
expected, err := os.ReadFile(goldenFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read golden file: %v", err)
|
||||
}
|
||||
|
||||
if result != string(expected) {
|
||||
t.Errorf("output doesn't match golden file\ngot:\n%s\nwant:\n%s", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
var update = flag.Bool("update", false, "update golden files")
|
||||
```
|
||||
|
||||
## Integration Tests
|
||||
|
||||
```go
|
||||
// integration_test.go
|
||||
// +build integration
|
||||
|
||||
package myapp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test in short mode")
|
||||
}
|
||||
|
||||
// Long-running integration test
|
||||
server := startTestServer(t)
|
||||
defer server.Stop()
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Wait for server
|
||||
|
||||
client := NewClient(server.URL)
|
||||
resp, err := client.Get("/health")
|
||||
if err != nil {
|
||||
t.Fatalf("health check failed: %v", err)
|
||||
}
|
||||
|
||||
if resp.Status != "ok" {
|
||||
t.Errorf("expected status ok, got %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// Run: go test -tags=integration
|
||||
// Run short tests only: go test -short
|
||||
```
|
||||
|
||||
## Testable Examples
|
||||
|
||||
```go
|
||||
// Example tests that appear in godoc
|
||||
func ExampleAdd() {
|
||||
result := Add(2, 3)
|
||||
fmt.Println(result)
|
||||
// Output: 5
|
||||
}
|
||||
|
||||
func ExampleAdd_negative() {
|
||||
result := Add(-2, -3)
|
||||
fmt.Println(result)
|
||||
// Output: -5
|
||||
}
|
||||
|
||||
// Unordered output
|
||||
func ExampleKeys() {
|
||||
m := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
keys := Keys(m)
|
||||
for _, k := range keys {
|
||||
fmt.Println(k)
|
||||
}
|
||||
// Unordered output:
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `go test` | Run tests |
|
||||
| `go test -v` | Verbose output |
|
||||
| `go test -run TestName` | Run specific test |
|
||||
| `go test -bench .` | Run benchmarks |
|
||||
| `go test -cover` | Show coverage |
|
||||
| `go test -race` | Run race detector |
|
||||
| `go test -short` | Skip long tests |
|
||||
| `go test -fuzz FuzzName` | Run fuzzing |
|
||||
| `go test -cpuprofile cpu.prof` | CPU profiling |
|
||||
| `go test -memprofile mem.prof` | Memory profiling |
|
||||
Reference in new issue
Block a user