mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-01-26 11:34:03 +00:00
chore: modernize workflows, security scanning, and linting configuration (#50)
* build: update Go 1.25, CI workflows, and build tooling - Upgrade to Go 1.25 - Add benchmark targets to Makefile - Implement parallel gosec execution - Lock tool versions for reproducibility - Add shellcheck directives to scripts - Update CI workflows with improved caching * refactor: migrate from golangci-lint to revive - Replace golangci-lint with revive for linting - Configure comprehensive revive rules - Fix all EditorConfig violations - Add yamllint and yamlfmt support - Remove deprecated .golangci.yml * refactor: rename utils to shared and deduplicate code - Rename utils package to shared - Add shared constants package - Deduplicate constants across packages - Address CodeRabbit review feedback * fix: resolve SonarQube issues and add safety guards - Fix all 73 SonarQube OPEN issues - Add nil guards for resourceMonitor, backpressure, metricsCollector - Implement io.Closer for headerFileReader - Propagate errors from processing helpers - Add metrics and templates packages - Improve error handling across codebase * test: improve test infrastructure and coverage - Add benchmarks for cli, fileproc, metrics - Improve test coverage for cli, fileproc, config - Refactor tests with helper functions - Add shared test constants - Fix test function naming conventions - Reduce cognitive complexity in benchmark tests * docs: update documentation and configuration examples - Update CLAUDE.md with current project state - Refresh README with new features - Add usage and configuration examples - Add SonarQube project configuration - Consolidate config.example.yaml * fix: resolve shellcheck warnings in scripts - Use ./*.go instead of *.go to prevent dash-prefixed filenames from being interpreted as options (SC2035) - Remove unreachable return statement after exit (SC2317) - Remove obsolete gibidiutils/ directory reference * chore(deps): upgrade go dependencies * chore(lint): megalinter fixes * fix: improve test coverage and fix file descriptor leaks - Add defer r.Close() to fix pipe file descriptor leaks in benchmark tests - Refactor TestProcessorConfigureFileTypes with helper functions and assertions - Refactor TestProcessorLogFinalStats with output capture and keyword verification - Use shared constants instead of literal strings (TestFilePNG, FormatMarkdown, etc.) - Reduce cognitive complexity by extracting helper functions * fix: align test comments with function names Remove underscores from test comments to match actual function names: - benchmark/benchmark_test.go (2 fixes) - fileproc/filetypes_config_test.go (4 fixes) - fileproc/filetypes_registry_test.go (6 fixes) - fileproc/processor_test.go (6 fixes) - fileproc/resource_monitor_types_test.go (4 fixes) - fileproc/writer_test.go (3 fixes) * fix: various test improvements and bug fixes - Remove duplicate maxCacheSize check in filetypes_registry_test.go - Shorten long comment in processor_test.go to stay under 120 chars - Remove flaky time.Sleep in collector_test.go, use >= 0 assertion - Close pipe reader in benchmark_test.go to fix file descriptor leak - Use ContinueOnError in flags_test.go to match ResetFlags behavior - Add nil check for p.ui in processor_workers.go before UpdateProgress - Fix resource_monitor_validation_test.go by setting hardMemoryLimitBytes directly * chore(yaml): add missing document start markers Add --- document start to YAML files to satisfy yamllint: - .github/workflows/codeql.yml - .github/workflows/build-test-publish.yml - .github/workflows/security.yml - .github/actions/setup/action.yml * fix: guard nil resourceMonitor and fix test deadlock - Guard resourceMonitor before CreateFileProcessingContext call - Add ui.UpdateProgress on emergency stop and path error returns - Fix potential deadlock in TestProcessFile using wg.Go with defer close
This commit is contained in:
352
testutil/assertions_test.go
Normal file
352
testutil/assertions_test.go
Normal file
@@ -0,0 +1,352 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ivuorinen/gibidify/shared"
|
||||
)
|
||||
|
||||
// TestAssertError tests the AssertError function.
|
||||
func TestAssertError(t *testing.T) {
|
||||
t.Run(
|
||||
shared.TestCaseSuccessCases, func(t *testing.T) {
|
||||
// Test expect error and get error
|
||||
t.Run(
|
||||
"expect error and get error", func(t *testing.T) {
|
||||
AssertError(t, errors.New(shared.TestErrTestErrorMsg), true, shared.TestCaseTestOperation)
|
||||
// If we get here, the assertion passed
|
||||
},
|
||||
)
|
||||
|
||||
// Test expect no error and get no error
|
||||
t.Run(
|
||||
"expect no error and get no error", func(t *testing.T) {
|
||||
AssertError(t, nil, false, "successful operation")
|
||||
// If we get here, the assertion passed
|
||||
},
|
||||
)
|
||||
|
||||
// Test with empty operation name
|
||||
t.Run(
|
||||
shared.TestCaseEmptyOperationName, func(t *testing.T) {
|
||||
AssertError(t, nil, false, "")
|
||||
// Should still work with empty operation
|
||||
},
|
||||
)
|
||||
|
||||
// Test with complex operation name
|
||||
t.Run(
|
||||
"complex operation name", func(t *testing.T) {
|
||||
AssertError(t, nil, false, "complex operation with special chars: !@#$%^&*()")
|
||||
// Should handle special characters
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// Test edge cases
|
||||
t.Run(
|
||||
"edge cases", func(t *testing.T) {
|
||||
// Test various error types
|
||||
t.Run(
|
||||
shared.TestCaseDifferentErrorTypes, func(t *testing.T) {
|
||||
AssertError(t, shared.ErrTestError, true, "using shared.ErrTestError")
|
||||
AssertError(t, errors.New("wrapped: original"), true, "wrapped error")
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestAssertNoError tests the AssertNoError function.
|
||||
func TestAssertNoError(t *testing.T) {
|
||||
t.Run(
|
||||
shared.TestCaseSuccessCases, func(t *testing.T) {
|
||||
// Test with nil error
|
||||
t.Run(
|
||||
"nil error", func(t *testing.T) {
|
||||
AssertNoError(t, nil, "successful operation")
|
||||
},
|
||||
)
|
||||
|
||||
// Test with empty operation name
|
||||
t.Run(
|
||||
shared.TestCaseEmptyOperationName, func(t *testing.T) {
|
||||
AssertNoError(t, nil, "")
|
||||
},
|
||||
)
|
||||
|
||||
// Test with complex operation name
|
||||
t.Run(
|
||||
"complex operation", func(t *testing.T) {
|
||||
AssertNoError(t, nil, "complex operation with special chars: !@#$%^&*()")
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// We can't easily test the failure case in a unit test since it would cause test failure
|
||||
// But we can verify the function signature and basic functionality
|
||||
t.Run(
|
||||
shared.TestCaseFunctionAvailability, func(t *testing.T) {
|
||||
// Verify the function doesn't panic with valid inputs
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("AssertNoError should not panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Call with success case to ensure function works
|
||||
AssertNoError(t, nil, shared.TestCaseTestOperation)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestAssertExpectedError tests the AssertExpectedError function.
|
||||
func TestAssertExpectedError(t *testing.T) {
|
||||
t.Run(
|
||||
shared.TestCaseSuccessCases, func(t *testing.T) {
|
||||
// Test with error present
|
||||
t.Run(
|
||||
"error present as expected", func(t *testing.T) {
|
||||
AssertExpectedError(t, errors.New("expected error"), "operation that should fail")
|
||||
},
|
||||
)
|
||||
|
||||
// Test with different error types
|
||||
t.Run(
|
||||
shared.TestCaseDifferentErrorTypes, func(t *testing.T) {
|
||||
AssertExpectedError(t, shared.ErrTestError, "test error operation")
|
||||
AssertExpectedError(t, errors.New("complex error with details"), "complex operation")
|
||||
},
|
||||
)
|
||||
|
||||
// Test with empty operation name
|
||||
t.Run(
|
||||
shared.TestCaseEmptyOperationName, func(t *testing.T) {
|
||||
AssertExpectedError(t, errors.New("error"), "")
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// Verify function availability and basic properties
|
||||
t.Run(
|
||||
shared.TestCaseFunctionAvailability, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("AssertExpectedError should not panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Call with success case
|
||||
AssertExpectedError(t, errors.New("test"), shared.TestCaseTestOperation)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestAssertErrorContains tests the AssertErrorContains function.
|
||||
func TestAssertErrorContains(t *testing.T) {
|
||||
t.Run(
|
||||
shared.TestCaseSuccessCases, func(t *testing.T) {
|
||||
// Test error contains substring
|
||||
t.Run(
|
||||
"error contains substring", func(t *testing.T) {
|
||||
err := errors.New("database connection failed")
|
||||
AssertErrorContains(t, err, "connection", "database operation")
|
||||
},
|
||||
)
|
||||
|
||||
// Test exact match
|
||||
t.Run(
|
||||
"exact match", func(t *testing.T) {
|
||||
err := errors.New("exact error message")
|
||||
AssertErrorContains(t, err, "exact error message", "exact operation")
|
||||
},
|
||||
)
|
||||
|
||||
// Test empty substring (should match any error)
|
||||
t.Run(
|
||||
"empty substring matches any error", func(t *testing.T) {
|
||||
err := errors.New("any error")
|
||||
AssertErrorContains(t, err, "", "any operation")
|
||||
},
|
||||
)
|
||||
|
||||
// Test special characters
|
||||
t.Run(
|
||||
"special characters in substring", func(t *testing.T) {
|
||||
err := errors.New("error: failed with code 500")
|
||||
AssertErrorContains(t, err, "code 500", "special chars operation")
|
||||
},
|
||||
)
|
||||
|
||||
// Test case sensitivity
|
||||
t.Run(
|
||||
"case sensitive operations", func(t *testing.T) {
|
||||
err := errors.New("error Message")
|
||||
AssertErrorContains(t, err, "error Message", "case operation")
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// Test with various error types
|
||||
t.Run(
|
||||
shared.TestCaseDifferentErrorTypes, func(t *testing.T) {
|
||||
t.Run(
|
||||
"standard error", func(t *testing.T) {
|
||||
AssertErrorContains(
|
||||
t, shared.ErrTestError, shared.TestErrTestErrorMsg, shared.TestCaseTestOperation,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"wrapped error", func(t *testing.T) {
|
||||
wrappedErr := errors.New("wrapped: original error")
|
||||
AssertErrorContains(t, wrappedErr, "original", "wrapped operation")
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// Verify function availability
|
||||
t.Run(
|
||||
shared.TestCaseFunctionAvailability, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("AssertErrorContains should not panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Call with success case
|
||||
err := errors.New(shared.TestErrTestErrorMsg)
|
||||
AssertErrorContains(t, err, "test", "availability check")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestAssertionHelpers tests edge cases and combinations of assertion helpers.
|
||||
func TestAssertionHelpers(t *testing.T) {
|
||||
t.Run(
|
||||
"error types compatibility", func(t *testing.T) {
|
||||
// Test that all assertion functions work with shared.ErrTestError
|
||||
AssertError(t, shared.ErrTestError, true, "shared.ErrTestError compatibility")
|
||||
AssertExpectedError(t, shared.ErrTestError, "shared.ErrTestError expected")
|
||||
AssertErrorContains(t, shared.ErrTestError, "test", "shared.ErrTestError contains")
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"operation name handling", func(t *testing.T) {
|
||||
operations := []string{
|
||||
"",
|
||||
"simple operation",
|
||||
"operation with spaces",
|
||||
"operation-with-dashes",
|
||||
"operation_with_underscores",
|
||||
"operation.with.dots",
|
||||
"operation/with/slashes",
|
||||
"operation\\with\\backslashes",
|
||||
"operation with special chars: !@#$%^&*()",
|
||||
"operation with unicode: αβγ",
|
||||
strings.Repeat("very long operation name ", 10),
|
||||
}
|
||||
|
||||
for i, op := range operations {
|
||||
t.Run(
|
||||
"operation_"+string(rune(i+'a')), func(t *testing.T) {
|
||||
// Test each assertion function with this operation name
|
||||
AssertError(t, nil, false, op)
|
||||
AssertNoError(t, nil, op)
|
||||
AssertExpectedError(t, errors.New("test"), op)
|
||||
AssertErrorContains(t, errors.New(shared.TestErrTestErrorMsg), "test", op)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"error message variations", func(t *testing.T) {
|
||||
errorMessages := []string{
|
||||
"",
|
||||
"simple error",
|
||||
"error with spaces",
|
||||
"error\nwith\nnewlines",
|
||||
"error\twith\ttabs",
|
||||
"error with unicode: αβγδε",
|
||||
"error: with: colons: everywhere",
|
||||
strings.Repeat("very long error message ", 20),
|
||||
"error with special chars: !@#$%^&*()",
|
||||
}
|
||||
|
||||
for i, msg := range errorMessages {
|
||||
t.Run(
|
||||
"error_message_"+string(rune(i+'a')), func(t *testing.T) {
|
||||
err := errors.New(msg)
|
||||
AssertError(t, err, true, shared.TestCaseMessageTest)
|
||||
AssertExpectedError(t, err, shared.TestCaseMessageTest)
|
||||
if msg != "" {
|
||||
// Only test contains if message is not empty
|
||||
AssertErrorContains(t, err, msg, shared.TestCaseMessageTest)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// BenchmarkStringOperations benchmarks string operations used by assertion functions.
|
||||
func BenchmarkStringOperations(b *testing.B) {
|
||||
testErr := errors.New("this is a long error message with many words for testing performance of substring matching")
|
||||
errorMessage := testErr.Error()
|
||||
substring := "error message"
|
||||
|
||||
b.Run(
|
||||
"contains_operation", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = strings.Contains(errorMessage, substring)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
b.Run(
|
||||
"error_to_string", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = testErr.Error()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// BenchmarkAssertionLogic benchmarks the core logic of assertion functions.
|
||||
func BenchmarkAssertionLogic(b *testing.B) {
|
||||
testErr := errors.New("benchmark error")
|
||||
|
||||
b.Run(
|
||||
"error_nil_check", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = testErr != nil
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
b.Run(
|
||||
"boolean_comparison", func(b *testing.B) {
|
||||
hasErr := testErr != nil
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = !hasErr
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ivuorinen/gibidify/shared"
|
||||
)
|
||||
|
||||
// Test thread safety of functions that might be called concurrently
|
||||
// Test thread safety of functions that might be called concurrently.
|
||||
func TestConcurrentOperations(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
done := make(chan bool)
|
||||
@@ -34,7 +36,7 @@ func TestConcurrentOperations(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks
|
||||
// Benchmarks.
|
||||
func BenchmarkCreateTestFile(b *testing.B) {
|
||||
tempDir := b.TempDir()
|
||||
content := []byte("benchmark content")
|
||||
@@ -44,7 +46,7 @@ func BenchmarkCreateTestFile(b *testing.B) {
|
||||
// Use a unique filename for each iteration to avoid conflicts
|
||||
filename := "bench" + string(rune(i%26+'a')) + ".txt"
|
||||
filePath := filepath.Join(tempDir, filename)
|
||||
if err := os.WriteFile(filePath, content, FilePermission); err != nil {
|
||||
if err := os.WriteFile(filePath, content, shared.TestFilePermission); err != nil {
|
||||
b.Fatalf("Failed to write file: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -64,7 +66,7 @@ func BenchmarkCreateTestFiles(b *testing.B) {
|
||||
|
||||
for _, spec := range specs {
|
||||
filePath := filepath.Join(tempDir, spec.Name)
|
||||
if err := os.WriteFile(filePath, []byte(spec.Content), FilePermission); err != nil {
|
||||
if err := os.WriteFile(filePath, []byte(spec.Content), shared.TestFilePermission); err != nil {
|
||||
b.Fatalf("Failed to write file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/ivuorinen/gibidify/shared"
|
||||
)
|
||||
|
||||
func TestResetViperConfig(t *testing.T) {
|
||||
@@ -18,10 +20,11 @@ func TestResetViperConfig(t *testing.T) {
|
||||
name: "reset with empty config path",
|
||||
configPath: "",
|
||||
preSetup: func() {
|
||||
viper.Set("test.key", "value")
|
||||
viper.Set(shared.TestKeyName, "value")
|
||||
},
|
||||
verify: func(t *testing.T) {
|
||||
if viper.IsSet("test.key") {
|
||||
t.Helper()
|
||||
if viper.IsSet(shared.TestKeyName) {
|
||||
t.Error("Viper config not reset properly")
|
||||
}
|
||||
},
|
||||
@@ -30,10 +33,11 @@ func TestResetViperConfig(t *testing.T) {
|
||||
name: "reset with config path",
|
||||
configPath: t.TempDir(),
|
||||
preSetup: func() {
|
||||
viper.Set("test.key", "value")
|
||||
viper.Set(shared.TestKeyName, "value")
|
||||
},
|
||||
verify: func(t *testing.T) {
|
||||
if viper.IsSet("test.key") {
|
||||
t.Helper()
|
||||
if viper.IsSet(shared.TestKeyName) {
|
||||
t.Error("Viper config not reset properly")
|
||||
}
|
||||
// Verify config path was added
|
||||
@@ -47,11 +51,13 @@ func TestResetViperConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.preSetup()
|
||||
ResetViperConfig(t, tt.configPath)
|
||||
tt.verify(t)
|
||||
})
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
tt.preSetup()
|
||||
ResetViperConfig(t, tt.configPath)
|
||||
tt.verify(t)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +84,7 @@ func TestSetupCLIArgs(t *testing.T) {
|
||||
prefix: "PREFIX",
|
||||
suffix: "SUFFIX",
|
||||
concurrency: 4,
|
||||
wantLen: 11,
|
||||
wantLen: 12,
|
||||
},
|
||||
{
|
||||
name: "empty strings",
|
||||
@@ -87,7 +93,7 @@ func TestSetupCLIArgs(t *testing.T) {
|
||||
prefix: "",
|
||||
suffix: "",
|
||||
concurrency: 1,
|
||||
wantLen: 11,
|
||||
wantLen: 12,
|
||||
},
|
||||
{
|
||||
name: "special characters in args",
|
||||
@@ -96,37 +102,50 @@ func TestSetupCLIArgs(t *testing.T) {
|
||||
prefix: "Prefix with\nnewline",
|
||||
suffix: "Suffix with\ttab",
|
||||
concurrency: 8,
|
||||
wantLen: 11,
|
||||
wantLen: 12,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
SetupCLIArgs(tt.srcDir, tt.outFile, tt.prefix, tt.suffix, tt.concurrency)
|
||||
|
||||
if len(os.Args) != tt.wantLen {
|
||||
t.Errorf("os.Args length = %d, want %d", len(os.Args), tt.wantLen)
|
||||
}
|
||||
|
||||
// Verify specific args
|
||||
if os.Args[0] != "gibidify" {
|
||||
t.Errorf("Program name = %s, want gibidify", os.Args[0])
|
||||
}
|
||||
if os.Args[2] != tt.srcDir {
|
||||
t.Errorf("Source dir = %s, want %s", os.Args[2], tt.srcDir)
|
||||
}
|
||||
if os.Args[4] != tt.outFile {
|
||||
t.Errorf("Output file = %s, want %s", os.Args[4], tt.outFile)
|
||||
}
|
||||
if os.Args[6] != tt.prefix {
|
||||
t.Errorf("Prefix = %s, want %s", os.Args[6], tt.prefix)
|
||||
}
|
||||
if os.Args[8] != tt.suffix {
|
||||
t.Errorf("Suffix = %s, want %s", os.Args[8], tt.suffix)
|
||||
}
|
||||
if os.Args[10] != string(rune(tt.concurrency+'0')) {
|
||||
t.Errorf("Concurrency = %s, want %d", os.Args[10], tt.concurrency)
|
||||
}
|
||||
})
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
SetupCLIArgs(tt.srcDir, tt.outFile, tt.prefix, tt.suffix, tt.concurrency)
|
||||
verifySetupCLIArgs(t, tt.srcDir, tt.outFile, tt.prefix, tt.suffix, tt.concurrency, tt.wantLen)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// verifySetupCLIArgs verifies that CLI arguments are set correctly.
|
||||
func verifySetupCLIArgs(t *testing.T, srcDir, outFile, prefix, suffix string, concurrency, wantLen int) {
|
||||
t.Helper()
|
||||
|
||||
if len(os.Args) != wantLen {
|
||||
t.Errorf("os.Args length = %d, want %d", len(os.Args), wantLen)
|
||||
}
|
||||
|
||||
// Verify specific args
|
||||
if os.Args[0] != "gibidify" {
|
||||
t.Errorf("Program name = %s, want gibidify", os.Args[0])
|
||||
}
|
||||
if os.Args[2] != srcDir {
|
||||
t.Errorf("Source dir = %s, want %s", os.Args[2], srcDir)
|
||||
}
|
||||
if os.Args[4] != outFile {
|
||||
t.Errorf("Output file = %s, want %s", os.Args[4], outFile)
|
||||
}
|
||||
if os.Args[6] != prefix {
|
||||
t.Errorf("Prefix = %s, want %s", os.Args[6], prefix)
|
||||
}
|
||||
if os.Args[8] != suffix {
|
||||
t.Errorf("Suffix = %s, want %s", os.Args[8], suffix)
|
||||
}
|
||||
if os.Args[10] != string(rune(concurrency+'0')) {
|
||||
t.Errorf("Concurrency = %s, want %d", os.Args[10], concurrency)
|
||||
}
|
||||
|
||||
// Verify the -no-ui flag is present
|
||||
if os.Args[11] != "-no-ui" {
|
||||
t.Errorf("NoUI flag = %s, want -no-ui", os.Args[11])
|
||||
}
|
||||
}
|
||||
|
||||
495
testutil/directory_structure_test.go
Normal file
495
testutil/directory_structure_test.go
Normal file
@@ -0,0 +1,495 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ivuorinen/gibidify/shared"
|
||||
)
|
||||
|
||||
// verifySingleDirectoryFiles verifies single directory with files test case.
|
||||
func verifySingleDirectoryFiles(t *testing.T, rootDir string, _ []string) {
|
||||
t.Helper()
|
||||
|
||||
srcDir := filepath.Join(rootDir, "src")
|
||||
if _, err := os.Stat(srcDir); err != nil {
|
||||
t.Errorf("Directory %s should exist", srcDir)
|
||||
}
|
||||
|
||||
mainFile := filepath.Join(srcDir, shared.TestFileMainGo)
|
||||
content, err := os.ReadFile(mainFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read %s: %v", shared.TestFileMainGo, err)
|
||||
}
|
||||
if string(content) != shared.LiteralPackageMain {
|
||||
t.Errorf("%s content = %q, want %q", shared.TestFileMainGo, content, shared.LiteralPackageMain)
|
||||
}
|
||||
|
||||
utilsFile := filepath.Join(srcDir, shared.TestFileSharedGo)
|
||||
content, err = os.ReadFile(utilsFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read shared.go: %v", err)
|
||||
}
|
||||
if string(content) != shared.TestSharedGoContent {
|
||||
t.Errorf("shared.go content = %q, want %q", content, shared.TestSharedGoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyMultipleDirectories verifies multiple directories with nested structure.
|
||||
func verifyMultipleDirectories(t *testing.T, rootDir string, _ []string) {
|
||||
t.Helper()
|
||||
|
||||
expectedDirs := []string{
|
||||
filepath.Join(rootDir, "src"),
|
||||
filepath.Join(rootDir, "src", "handlers"),
|
||||
filepath.Join(rootDir, "test"),
|
||||
}
|
||||
for _, dir := range expectedDirs {
|
||||
if info, err := os.Stat(dir); err != nil {
|
||||
t.Errorf(shared.TestFmtDirectoryShouldExist, dir, err)
|
||||
} else if !info.IsDir() {
|
||||
t.Errorf(shared.TestFmtPathShouldBeDirectory, dir)
|
||||
}
|
||||
}
|
||||
|
||||
handlerFile := filepath.Join(rootDir, "src", "handlers", "handler.go")
|
||||
content, err := os.ReadFile(handlerFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read handler.go: %v", err)
|
||||
}
|
||||
if string(content) != shared.TestContentPackageHandlers {
|
||||
t.Errorf("handler.go content = %q, want 'package handlers'", content)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyEmptyDirectory verifies directory with no files.
|
||||
func verifyEmptyDirectory(t *testing.T, rootDir string, _ []string) {
|
||||
t.Helper()
|
||||
|
||||
emptyDir := filepath.Join(rootDir, "empty")
|
||||
if info, err := os.Stat(emptyDir); err != nil {
|
||||
t.Errorf(shared.TestFmtDirectoryShouldExist, emptyDir, err)
|
||||
} else if !info.IsDir() {
|
||||
t.Errorf(shared.TestFmtPathShouldBeDirectory, emptyDir)
|
||||
}
|
||||
}
|
||||
|
||||
// verifySpecialCharacters verifies directories with special characters.
|
||||
func verifySpecialCharacters(t *testing.T, rootDir string, _ []string) {
|
||||
t.Helper()
|
||||
|
||||
specialDir := filepath.Join(rootDir, "special-dir_123")
|
||||
if _, err := os.Stat(specialDir); err != nil {
|
||||
t.Errorf("Special directory should exist: %v", err)
|
||||
}
|
||||
|
||||
spacedDir := filepath.Join(rootDir, "dir with spaces")
|
||||
if _, err := os.Stat(spacedDir); err != nil {
|
||||
t.Errorf("Spaced directory should exist: %v", err)
|
||||
}
|
||||
|
||||
spacedFile := filepath.Join(spacedDir, "file with spaces.txt")
|
||||
content, err := os.ReadFile(spacedFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read spaced file: %v", err)
|
||||
}
|
||||
if string(content) != "spaced content" {
|
||||
t.Errorf("Spaced file content = %q, want 'spaced content'", content)
|
||||
}
|
||||
}
|
||||
|
||||
// runCreateDirectoryTest runs a single create directory structure test.
|
||||
func runCreateDirectoryTest(
|
||||
t *testing.T,
|
||||
dirSpecs []DirSpec,
|
||||
wantPaths int,
|
||||
verifyFunc func(t *testing.T, rootDir string, createdPaths []string),
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
rootDir := t.TempDir()
|
||||
createdPaths := CreateTestDirectoryStructure(t, rootDir, dirSpecs)
|
||||
|
||||
if len(createdPaths) != wantPaths {
|
||||
t.Errorf("Created %d paths, want %d", len(createdPaths), wantPaths)
|
||||
}
|
||||
|
||||
for _, path := range createdPaths {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Errorf("Created path %s should exist: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
verifyFunc(t, rootDir, createdPaths)
|
||||
}
|
||||
|
||||
// TestCreateTestDirectoryStructure tests the CreateTestDirectoryStructure function.
|
||||
func TestCreateTestDirectoryStructure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dirSpecs []DirSpec
|
||||
wantPaths int
|
||||
verifyFunc func(t *testing.T, rootDir string, createdPaths []string)
|
||||
}{
|
||||
{
|
||||
name: "single directory with files",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "src",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileMainGo, Content: shared.LiteralPackageMain},
|
||||
{Name: shared.TestFileSharedGo, Content: shared.TestSharedGoContent},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantPaths: 3, // 1 directory + 2 files
|
||||
verifyFunc: verifySingleDirectoryFiles,
|
||||
},
|
||||
{
|
||||
name: "multiple directories with nested structure",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "src",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileMainGo, Content: shared.LiteralPackageMain},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "src/handlers",
|
||||
Files: []FileSpec{
|
||||
{Name: "handler.go", Content: shared.TestContentPackageHandlers},
|
||||
{Name: "middleware.go", Content: "package handlers\n\ntype Middleware struct {}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "test",
|
||||
Files: []FileSpec{
|
||||
{Name: "main_test.go", Content: "package main\n\nimport \"testing\""},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantPaths: 7, // 3 directories + 4 files
|
||||
verifyFunc: verifyMultipleDirectories,
|
||||
},
|
||||
{
|
||||
name: "directory with no files",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "empty",
|
||||
Files: []FileSpec{},
|
||||
},
|
||||
},
|
||||
wantPaths: 1, // 1 directory only
|
||||
verifyFunc: verifyEmptyDirectory,
|
||||
},
|
||||
{
|
||||
name: "empty directory specs",
|
||||
dirSpecs: []DirSpec{},
|
||||
wantPaths: 0,
|
||||
verifyFunc: func(t *testing.T, _ string, _ []string) {
|
||||
t.Helper()
|
||||
// Nothing to verify for empty specs
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "directories with special characters and edge cases",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "special-dir_123",
|
||||
Files: []FileSpec{
|
||||
{Name: "file-with-dashes.txt", Content: "content"},
|
||||
{Name: "file_with_underscores.go", Content: "package main"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "dir with spaces",
|
||||
Files: []FileSpec{
|
||||
{Name: "file with spaces.txt", Content: "spaced content"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantPaths: 5, // 2 directories + 3 files
|
||||
verifyFunc: verifySpecialCharacters,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
runCreateDirectoryTest(t, tt.dirSpecs, tt.wantPaths, tt.verifyFunc)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyBasicDirectoryStructure verifies basic directory structure.
|
||||
func verifyBasicDirectoryStructure(t *testing.T, rootDir string) {
|
||||
t.Helper()
|
||||
|
||||
if !strings.Contains(rootDir, os.TempDir()) {
|
||||
t.Errorf("Root directory %s should be in temp directory", rootDir)
|
||||
}
|
||||
|
||||
appDir := filepath.Join(rootDir, "app")
|
||||
if info, err := os.Stat(appDir); err != nil {
|
||||
t.Errorf("App directory should exist: %v", err)
|
||||
} else if !info.IsDir() {
|
||||
t.Error("App path should be a directory")
|
||||
}
|
||||
|
||||
mainFile := filepath.Join(appDir, shared.TestFileMainGo)
|
||||
content, err := os.ReadFile(mainFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read %s: %v", shared.TestFileMainGo, err)
|
||||
}
|
||||
expectedMain := "package main\n\nfunc main() {}"
|
||||
if string(content) != expectedMain {
|
||||
t.Errorf("%s content = %q, want %q", shared.TestFileMainGo, content, expectedMain)
|
||||
}
|
||||
|
||||
configFile := filepath.Join(appDir, shared.TestFileConfigJSON)
|
||||
content, err = os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read %s: %v", shared.TestFileConfigJSON, err)
|
||||
}
|
||||
if string(content) != `{"debug": true}` {
|
||||
t.Errorf("%s content = %q, want %q", shared.TestFileConfigJSON, content, `{"debug": true}`)
|
||||
}
|
||||
|
||||
docsDir := filepath.Join(rootDir, "docs")
|
||||
if info, err := os.Stat(docsDir); err != nil {
|
||||
t.Errorf("Docs directory should exist: %v", err)
|
||||
} else if !info.IsDir() {
|
||||
t.Error("Docs path should be a directory")
|
||||
}
|
||||
|
||||
readmeFile := filepath.Join(docsDir, shared.TestFileReadmeMD)
|
||||
content, err = os.ReadFile(readmeFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read %s: %v", shared.TestFileReadmeMD, err)
|
||||
}
|
||||
if string(content) != shared.TestContentDocumentation {
|
||||
t.Errorf("%s content = %q, want '# Documentation'", shared.TestFileReadmeMD, content)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyEmptyDirectorySpecs verifies empty directory specs.
|
||||
func verifyEmptyDirectorySpecs(t *testing.T, rootDir string) {
|
||||
t.Helper()
|
||||
|
||||
if info, err := os.Stat(rootDir); err != nil {
|
||||
t.Errorf("Root directory should exist: %v", err)
|
||||
} else if !info.IsDir() {
|
||||
t.Error("Root path should be a directory")
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(rootDir)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read root directory: %v", err)
|
||||
}
|
||||
if len(entries) != 0 {
|
||||
t.Errorf("Root directory should be empty, but has %d entries", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
// verifyComplexNestedStructure verifies complex nested structure.
|
||||
func verifyComplexNestedStructure(t *testing.T, rootDir string) {
|
||||
t.Helper()
|
||||
|
||||
deepPath := filepath.Join(rootDir, "project", "internal", "handlers", "auth.go")
|
||||
content, err := os.ReadFile(deepPath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read deep nested file: %v", err)
|
||||
}
|
||||
expectedContent := "package handlers\n\ntype AuthHandler struct{}"
|
||||
if string(content) != expectedContent {
|
||||
t.Errorf("Deep nested file content = %q, want %q", content, expectedContent)
|
||||
}
|
||||
|
||||
expectedDirs := []string{
|
||||
"project",
|
||||
"project/cmd",
|
||||
"project/cmd/server",
|
||||
"project/internal",
|
||||
"project/internal/handlers",
|
||||
"project/test",
|
||||
"project/test/integration",
|
||||
}
|
||||
for _, dir := range expectedDirs {
|
||||
fullPath := filepath.Join(rootDir, dir)
|
||||
if info, err := os.Stat(fullPath); err != nil {
|
||||
t.Errorf(shared.TestFmtDirectoryShouldExist, fullPath, err)
|
||||
} else if !info.IsDir() {
|
||||
t.Errorf(shared.TestFmtPathShouldBeDirectory, fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runSetupTempDirTest runs a single setup temp dir test.
|
||||
func runSetupTempDirTest(t *testing.T, dirSpecs []DirSpec, verifyFunc func(t *testing.T, rootDir string)) {
|
||||
t.Helper()
|
||||
|
||||
rootDir := SetupTempDirWithStructure(t, dirSpecs)
|
||||
|
||||
if info, err := os.Stat(rootDir); err != nil {
|
||||
t.Fatalf("Root directory should exist: %v", err)
|
||||
} else if !info.IsDir() {
|
||||
t.Fatal("Root path should be a directory")
|
||||
}
|
||||
|
||||
verifyFunc(t, rootDir)
|
||||
}
|
||||
|
||||
// TestSetupTempDirWithStructure tests the SetupTempDirWithStructure function.
|
||||
func TestSetupTempDirWithStructure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dirSpecs []DirSpec
|
||||
verifyFunc func(t *testing.T, rootDir string)
|
||||
}{
|
||||
{
|
||||
name: "basic directory structure",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "app",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileMainGo, Content: "package main\n\nfunc main() {}"},
|
||||
{Name: shared.TestFileConfigJSON, Content: `{"debug": true}`},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "docs",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileReadmeMD, Content: shared.TestContentDocumentation},
|
||||
},
|
||||
},
|
||||
},
|
||||
verifyFunc: verifyBasicDirectoryStructure,
|
||||
},
|
||||
{
|
||||
name: "empty directory specs",
|
||||
dirSpecs: []DirSpec{},
|
||||
verifyFunc: verifyEmptyDirectorySpecs,
|
||||
},
|
||||
{
|
||||
name: "complex nested structure",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "project",
|
||||
Files: []FileSpec{
|
||||
{Name: "go.mod", Content: "module test\n\ngo 1.21"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "project/cmd/server",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileMainGo, Content: shared.LiteralPackageMain},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "project/internal/handlers",
|
||||
Files: []FileSpec{
|
||||
{Name: "health.go", Content: shared.TestContentPackageHandlers},
|
||||
{Name: "auth.go", Content: "package handlers\n\ntype AuthHandler struct{}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "project/test/integration",
|
||||
Files: []FileSpec{
|
||||
{Name: "server_test.go", Content: "package integration\n\nimport \"testing\""},
|
||||
},
|
||||
},
|
||||
},
|
||||
verifyFunc: verifyComplexNestedStructure,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
runSetupTempDirTest(t, tt.dirSpecs, tt.verifyFunc)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// benchmarkDirectoryStructure benchmarks creation of a single directory structure.
|
||||
func benchmarkDirectoryStructure(b *testing.B, dirSpecs []DirSpec) {
|
||||
b.Helper()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
rootDir := b.TempDir()
|
||||
b.StartTimer()
|
||||
|
||||
for _, dirSpec := range dirSpecs {
|
||||
dirPath := filepath.Join(rootDir, dirSpec.Path)
|
||||
if err := os.MkdirAll(dirPath, shared.TestDirPermission); err != nil {
|
||||
b.Fatalf("Failed to create directory: %v", err)
|
||||
}
|
||||
|
||||
for _, fileSpec := range dirSpec.Files {
|
||||
filePath := filepath.Join(dirPath, fileSpec.Name)
|
||||
if err := os.WriteFile(filePath, []byte(fileSpec.Content), shared.TestFilePermission); err != nil {
|
||||
b.Fatalf("Failed to create file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDirectoryCreation benchmarks directory structure creation with different specs.
|
||||
func BenchmarkDirectoryCreation(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
dirSpecs []DirSpec
|
||||
}{
|
||||
{
|
||||
name: "simple_source_structure",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "src",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileMainGo, Content: shared.LiteralPackageMain},
|
||||
{Name: shared.TestFileSharedGo, Content: shared.TestSharedGoContent},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "test",
|
||||
Files: []FileSpec{
|
||||
{Name: "main_test.go", Content: "package main\n\nimport \"testing\""},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "application_structure",
|
||||
dirSpecs: []DirSpec{
|
||||
{
|
||||
Path: "app",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileMainGo, Content: shared.LiteralPackageMain},
|
||||
{Name: shared.TestFileConfigJSON, Content: `{"debug": true}`},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "docs",
|
||||
Files: []FileSpec{
|
||||
{Name: shared.TestFileReadmeMD, Content: shared.TestContentDocumentation},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(
|
||||
tc.name, func(b *testing.B) {
|
||||
benchmarkDirectoryStructure(b, tc.dirSpecs)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
314
testutil/error_scenarios_test.go
Normal file
314
testutil/error_scenarios_test.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ivuorinen/gibidify/shared"
|
||||
)
|
||||
|
||||
// testResetViperConfigVariations tests ResetViperConfig with different paths.
|
||||
func testResetViperConfigVariations(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
testCases := []string{
|
||||
"", // Empty path
|
||||
"/nonexistent/path", // Non-existent path
|
||||
t.TempDir(), // Valid temporary directory
|
||||
}
|
||||
|
||||
for _, configPath := range testCases {
|
||||
t.Run(
|
||||
"path_"+strings.ReplaceAll(configPath, "/", "_"), func(t *testing.T) {
|
||||
ResetViperConfig(t, configPath)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// testGetBaseNameEdgeCases tests GetBaseName with various edge cases.
|
||||
func testGetBaseNameEdgeCases(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
edgeCases := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"", "."},
|
||||
{".", "."},
|
||||
{"..", ".."},
|
||||
{"/", "/"},
|
||||
{"//", "/"},
|
||||
{"///", "/"},
|
||||
{"file", "file"},
|
||||
{"./file", "file"},
|
||||
{"../file", "file"},
|
||||
{"/a", "a"},
|
||||
{"/a/", "a"},
|
||||
{"/a//", "a"},
|
||||
{"a/b/c", "c"},
|
||||
{"a/b/c/", "c"},
|
||||
}
|
||||
|
||||
for _, tc := range edgeCases {
|
||||
result := BaseName(tc.input)
|
||||
expected := filepath.Base(tc.input)
|
||||
if result != expected {
|
||||
t.Errorf("BaseName(%q) = %q, want %q", tc.input, result, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testVerifyContentContainsScenarios tests VerifyContentContains scenarios.
|
||||
func testVerifyContentContainsScenarios(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
content string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
"all_substrings_found",
|
||||
"This is a comprehensive test with multiple search terms",
|
||||
[]string{"comprehensive", "test", "multiple", "search"},
|
||||
},
|
||||
{"empty_expected_list", "Any content here", []string{}},
|
||||
{"single_character_matches", "abcdefg", []string{"a", "c", "g"}},
|
||||
{"repeated_substrings", "test test test", []string{"test", "test", "test"}},
|
||||
{"case_sensitive_matching", "Test TEST tEsT", []string{"Test", "TEST"}},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(
|
||||
scenario.name, func(t *testing.T) {
|
||||
VerifyContentContains(t, scenario.content, scenario.expected)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// testMustSucceedCases tests MustSucceed with various operations.
|
||||
func testMustSucceedCases(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
operations := []string{
|
||||
"simple operation",
|
||||
"",
|
||||
"operation with special chars: !@#$%",
|
||||
"very " + strings.Repeat("long ", 50) + "operation name",
|
||||
}
|
||||
|
||||
for i, op := range operations {
|
||||
t.Run(
|
||||
"operation_"+string(rune(i+'a')), func(t *testing.T) {
|
||||
MustSucceed(t, nil, op)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// testCloseFileScenarios tests CloseFile with different file scenarios.
|
||||
func testCloseFileScenarios(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
t.Run(
|
||||
"close_regular_file", func(t *testing.T) {
|
||||
file, err := os.CreateTemp(t.TempDir(), "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
|
||||
if _, err = file.WriteString("test content"); err != nil {
|
||||
t.Fatalf("Failed to write to file: %v", err)
|
||||
}
|
||||
|
||||
CloseFile(t, file)
|
||||
|
||||
if _, writeErr := file.Write([]byte("should fail")); writeErr == nil {
|
||||
t.Error("Expected write to fail after close")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"close_empty_file", func(t *testing.T) {
|
||||
file, err := os.CreateTemp(t.TempDir(), "empty")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
CloseFile(t, file)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestCoverageImprovements focuses on improving coverage for existing functions.
|
||||
func TestCoverageImprovements(t *testing.T) {
|
||||
t.Run("ResetViperConfig_variations", testResetViperConfigVariations)
|
||||
t.Run("GetBaseName_comprehensive", testGetBaseNameEdgeCases)
|
||||
t.Run("VerifyContentContains_comprehensive", testVerifyContentContainsScenarios)
|
||||
t.Run("MustSucceed_success_cases", testMustSucceedCases)
|
||||
t.Run("CloseFile_success_cases", testCloseFileScenarios)
|
||||
}
|
||||
|
||||
// attemptFileCreation attempts to create a file with error handling.
|
||||
func attemptFileCreation(t *testing.T, tempDir, specName string) {
|
||||
t.Helper()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Logf("File creation panicked (expected for some edge cases): %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := os.Create(filepath.Join(tempDir, specName)); err != nil {
|
||||
t.Logf("File creation failed (expected for some edge cases): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// createDirectoryIfNeeded creates directory if file path contains separators.
|
||||
func createDirectoryIfNeeded(t *testing.T, tempDir, specName string) {
|
||||
t.Helper()
|
||||
|
||||
if strings.Contains(specName, "/") || strings.Contains(specName, "\\") {
|
||||
dirPath := filepath.Dir(filepath.Join(tempDir, specName))
|
||||
if err := os.MkdirAll(dirPath, shared.TestDirPermission); err != nil {
|
||||
t.Skipf("Cannot create directory %s: %v", dirPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testFileSpecVariations tests FileSpec with various edge cases.
|
||||
func testFileSpecVariations(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
specs := []FileSpec{
|
||||
{Name: "", Content: ""},
|
||||
{Name: "simple.txt", Content: "simple content"},
|
||||
{Name: "with spaces.txt", Content: "content with spaces"},
|
||||
{Name: "unicode-file-αβγ.txt", Content: "unicode content: αβγδε"},
|
||||
{Name: "very-long-filename-" + strings.Repeat("x", 100) + ".txt", Content: "long filename test"},
|
||||
{Name: "file.with.many.dots.txt", Content: "dotted filename"},
|
||||
{Name: "special/chars\\file<>:\"|?*.txt", Content: "special characters"},
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
|
||||
for i, spec := range specs {
|
||||
t.Run(
|
||||
"spec_"+string(rune(i+'a')), func(t *testing.T) {
|
||||
createDirectoryIfNeeded(t, tempDir, spec.Name)
|
||||
attemptFileCreation(t, tempDir, spec.Name)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// testDirSpecVariations tests DirSpec with various configurations.
|
||||
func testDirSpecVariations(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
specs := []DirSpec{
|
||||
{Path: "empty-dir", Files: []FileSpec{}},
|
||||
{Path: "single-file-dir", Files: []FileSpec{{Name: "single.txt", Content: "single file"}}},
|
||||
{
|
||||
Path: "multi-file-dir", Files: []FileSpec{
|
||||
{Name: "file1.txt", Content: "content1"},
|
||||
{Name: "file2.txt", Content: "content2"},
|
||||
{Name: "file3.txt", Content: "content3"},
|
||||
},
|
||||
},
|
||||
{Path: "nested/deep/structure", Files: []FileSpec{{Name: "deep.txt", Content: "deep content"}}},
|
||||
{Path: "unicode-αβγ", Files: []FileSpec{{Name: "unicode-file.txt", Content: "unicode directory content"}}},
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
createdPaths := CreateTestDirectoryStructure(t, tempDir, specs)
|
||||
|
||||
if len(createdPaths) == 0 && len(specs) > 0 {
|
||||
t.Error("Expected some paths to be created")
|
||||
}
|
||||
|
||||
for _, path := range createdPaths {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Errorf("Created path should exist: %s, error: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestStructOperations tests operations with FileSpec and DirSpec.
|
||||
func TestStructOperations(t *testing.T) {
|
||||
t.Run("FileSpec_comprehensive", testFileSpecVariations)
|
||||
t.Run("DirSpec_comprehensive", testDirSpecVariations)
|
||||
}
|
||||
|
||||
// BenchmarkUtilityFunctions provides comprehensive benchmarks.
|
||||
func BenchmarkUtilityFunctions(b *testing.B) {
|
||||
b.Run(
|
||||
"GetBaseName_variations", func(b *testing.B) {
|
||||
paths := []string{
|
||||
"",
|
||||
"simple.txt",
|
||||
"/path/to/file.go",
|
||||
"/very/deep/nested/path/to/file.json",
|
||||
"relative/path/file.txt",
|
||||
".",
|
||||
"..",
|
||||
"/",
|
||||
strings.Repeat("/very/long/path", 10) + "/file.txt",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
path := paths[i%len(paths)]
|
||||
_ = BaseName(path)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
b.Run(
|
||||
"StringOperations", func(b *testing.B) {
|
||||
content := strings.Repeat("benchmark content with search terms ", 100)
|
||||
searchTerms := []string{"benchmark", "content", "search", "terms", "not found"}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
term := searchTerms[i%len(searchTerms)]
|
||||
_ = strings.Contains(content, term)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
b.Run(
|
||||
"FileSpec_creation", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
spec := FileSpec{
|
||||
Name: "benchmark-file-" + string(rune(i%26+'a')) + ".txt",
|
||||
Content: "benchmark content for iteration " + string(rune(i%10+'0')),
|
||||
}
|
||||
_ = len(spec.Name)
|
||||
_ = len(spec.Content)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
b.Run(
|
||||
"DirSpec_creation", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
spec := DirSpec{
|
||||
Path: "benchmark-dir-" + string(rune(i%26+'a')),
|
||||
Files: []FileSpec{
|
||||
{Name: "file1.txt", Content: "content1"},
|
||||
{Name: "file2.txt", Content: "content2"},
|
||||
},
|
||||
}
|
||||
_ = len(spec.Path)
|
||||
_ = len(spec.Files)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -5,10 +5,31 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ivuorinen/gibidify/shared"
|
||||
)
|
||||
|
||||
func TestCreateTestFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
tests := createTestFileTestCases()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
runCreateTestFileTest(t, tt.dir, tt.filename, tt.content)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// createTestFileTestCases creates test cases for TestCreateTestFile.
|
||||
func createTestFileTestCases() []struct {
|
||||
name string
|
||||
dir string
|
||||
filename string
|
||||
content []byte
|
||||
wantErr bool
|
||||
} {
|
||||
return []struct {
|
||||
name string
|
||||
dir string
|
||||
filename string
|
||||
@@ -42,55 +63,88 @@ func TestCreateTestFile(t *testing.T) {
|
||||
{
|
||||
name: "create file with special characters",
|
||||
filename: "special-file_123.go",
|
||||
content: []byte("package main"),
|
||||
content: []byte(shared.LiteralPackageMain),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Use a temporary directory for each test
|
||||
tempDir := t.TempDir()
|
||||
if tt.dir == "" {
|
||||
tt.dir = tempDir
|
||||
}
|
||||
// runCreateTestFileTest runs a single test case for CreateTestFile.
|
||||
func runCreateTestFileTest(t *testing.T, dir, filename string, content []byte) {
|
||||
t.Helper()
|
||||
|
||||
// Create subdirectory if needed
|
||||
if strings.Contains(tt.filename, "/") {
|
||||
subdir := filepath.Join(tt.dir, filepath.Dir(tt.filename))
|
||||
if err := os.MkdirAll(subdir, DirPermission); err != nil {
|
||||
t.Fatalf("Failed to create subdirectory: %v", err)
|
||||
}
|
||||
}
|
||||
tempDir := t.TempDir()
|
||||
if dir == "" {
|
||||
dir = tempDir
|
||||
}
|
||||
|
||||
// Test CreateTestFile
|
||||
filePath := CreateTestFile(t, tt.dir, tt.filename, tt.content)
|
||||
createSubdirectoryIfNeeded(t, dir, filename)
|
||||
filePath := CreateTestFile(t, dir, filename, content)
|
||||
verifyCreatedFile(t, filePath, content)
|
||||
}
|
||||
|
||||
// Verify file exists
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Created file does not exist: %v", err)
|
||||
}
|
||||
// createSubdirectoryIfNeeded creates subdirectory if the filename contains a path separator.
|
||||
func createSubdirectoryIfNeeded(t *testing.T, dir, filename string) {
|
||||
t.Helper()
|
||||
|
||||
// Verify it's a regular file
|
||||
if !info.Mode().IsRegular() {
|
||||
t.Errorf("Created path is not a regular file")
|
||||
}
|
||||
if strings.ContainsRune(filename, filepath.Separator) {
|
||||
subdir := filepath.Join(dir, filepath.Dir(filename))
|
||||
if err := os.MkdirAll(subdir, shared.TestDirPermission); err != nil {
|
||||
t.Fatalf("Failed to create subdirectory: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify permissions
|
||||
if info.Mode().Perm() != FilePermission {
|
||||
t.Errorf("File permissions = %v, want %v", info.Mode().Perm(), FilePermission)
|
||||
}
|
||||
// verifyCreatedFile verifies that the created file has correct properties.
|
||||
func verifyCreatedFile(t *testing.T, filePath string, expectedContent []byte) {
|
||||
t.Helper()
|
||||
|
||||
// Verify content
|
||||
readContent, err := os.ReadFile(filePath) // #nosec G304 - test file path is controlled
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read created file: %v", err)
|
||||
}
|
||||
if string(readContent) != string(tt.content) {
|
||||
t.Errorf("File content = %q, want %q", readContent, tt.content)
|
||||
}
|
||||
})
|
||||
info := verifyFileExists(t, filePath)
|
||||
verifyFileType(t, info)
|
||||
verifyFilePermissions(t, info)
|
||||
verifyFileContent(t, filePath, expectedContent)
|
||||
}
|
||||
|
||||
// verifyFileExists verifies that the file exists and returns its info.
|
||||
func verifyFileExists(t *testing.T, filePath string) os.FileInfo {
|
||||
t.Helper()
|
||||
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Created file does not exist: %v", err)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// verifyFileType verifies that the file is a regular file.
|
||||
func verifyFileType(t *testing.T, info os.FileInfo) {
|
||||
t.Helper()
|
||||
|
||||
if !info.Mode().IsRegular() {
|
||||
t.Error("Created path is not a regular file")
|
||||
}
|
||||
}
|
||||
|
||||
// verifyFilePermissions verifies that the file has correct permissions.
|
||||
func verifyFilePermissions(t *testing.T, info os.FileInfo) {
|
||||
t.Helper()
|
||||
|
||||
if info.Mode().Perm() != shared.TestFilePermission {
|
||||
t.Errorf("File permissions = %v, want %v", info.Mode().Perm(), shared.TestFilePermission)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyFileContent verifies that the file has the expected content.
|
||||
func verifyFileContent(t *testing.T, filePath string, expectedContent []byte) {
|
||||
t.Helper()
|
||||
|
||||
readContent, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read created file: %v", err)
|
||||
}
|
||||
if string(readContent) != string(expectedContent) {
|
||||
t.Errorf("File content = %q, want %q", readContent, expectedContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,37 +172,56 @@ func TestCreateTempOutputFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
file, path := CreateTempOutputFile(t, tt.pattern)
|
||||
defer CloseFile(t, file)
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
file, path := CreateTempOutputFile(t, tt.pattern)
|
||||
defer CloseFile(t, file)
|
||||
|
||||
// Verify file exists
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Temp file does not exist: %v", err)
|
||||
}
|
||||
// Verify file exists
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Temp file does not exist: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's a regular file
|
||||
if !info.Mode().IsRegular() {
|
||||
t.Errorf("Created path is not a regular file")
|
||||
}
|
||||
// Verify it's a regular file
|
||||
if !info.Mode().IsRegular() {
|
||||
t.Error("Created path is not a regular file")
|
||||
}
|
||||
|
||||
// Verify we can write to it
|
||||
testContent := []byte("test content")
|
||||
if _, err := file.Write(testContent); err != nil {
|
||||
t.Errorf("Failed to write to temp file: %v", err)
|
||||
}
|
||||
// Verify we can write to it
|
||||
testContent := []byte("test content")
|
||||
if _, err := file.Write(testContent); err != nil {
|
||||
t.Errorf("Failed to write to temp file: %v", err)
|
||||
}
|
||||
|
||||
// Verify the path is in a temp directory (any temp directory)
|
||||
if !strings.Contains(path, os.TempDir()) {
|
||||
t.Errorf("Temp file not in temp directory: %s", path)
|
||||
}
|
||||
})
|
||||
// Verify the path is in a temp directory (any temp directory)
|
||||
if !strings.Contains(path, os.TempDir()) {
|
||||
t.Errorf("Temp file not in temp directory: %s", path)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTestDirectory(t *testing.T) {
|
||||
tests := []struct {
|
||||
tests := createTestDirectoryTestCases()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
runCreateTestDirectoryTest(t, tt.parent, tt.dir)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// createTestDirectoryTestCases creates test cases for TestCreateTestDirectory.
|
||||
func createTestDirectoryTestCases() []struct {
|
||||
name string
|
||||
parent string
|
||||
dir string
|
||||
} {
|
||||
return []struct {
|
||||
name string
|
||||
parent string
|
||||
dir string
|
||||
@@ -166,53 +239,107 @@ func TestCreateTestDirectory(t *testing.T) {
|
||||
dir: "nested/dir",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
if tt.parent == "" {
|
||||
tt.parent = tempDir
|
||||
}
|
||||
// runCreateTestDirectoryTest runs a single test case for CreateTestDirectory.
|
||||
func runCreateTestDirectoryTest(t *testing.T, parent, dir string) {
|
||||
t.Helper()
|
||||
|
||||
// For nested directories, create parent first
|
||||
if strings.Contains(tt.dir, "/") {
|
||||
parentPath := filepath.Join(tt.parent, filepath.Dir(tt.dir))
|
||||
if err := os.MkdirAll(parentPath, DirPermission); err != nil {
|
||||
t.Fatalf("Failed to create parent directory: %v", err)
|
||||
}
|
||||
tt.dir = filepath.Base(tt.dir)
|
||||
tt.parent = parentPath
|
||||
}
|
||||
tempDir := t.TempDir()
|
||||
if parent == "" {
|
||||
parent = tempDir
|
||||
}
|
||||
|
||||
dirPath := CreateTestDirectory(t, tt.parent, tt.dir)
|
||||
parent, dir = prepareNestedDirectoryPath(t, parent, dir)
|
||||
dirPath := CreateTestDirectory(t, parent, dir)
|
||||
verifyCreatedDirectory(t, dirPath)
|
||||
}
|
||||
|
||||
// Verify directory exists
|
||||
info, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Created directory does not exist: %v", err)
|
||||
}
|
||||
// prepareNestedDirectoryPath prepares parent and directory paths for nested directories.
|
||||
func prepareNestedDirectoryPath(t *testing.T, parent, dir string) (parentPath, fullPath string) {
|
||||
t.Helper()
|
||||
|
||||
// Verify it's a directory
|
||||
if !info.IsDir() {
|
||||
t.Errorf("Created path is not a directory")
|
||||
}
|
||||
if strings.Contains(dir, "/") {
|
||||
parentPath := filepath.Join(parent, filepath.Dir(dir))
|
||||
if err := os.MkdirAll(parentPath, shared.TestDirPermission); err != nil {
|
||||
t.Fatalf("Failed to create parent directory: %v", err)
|
||||
}
|
||||
|
||||
// Verify permissions
|
||||
if info.Mode().Perm() != DirPermission {
|
||||
t.Errorf("Directory permissions = %v, want %v", info.Mode().Perm(), DirPermission)
|
||||
}
|
||||
return parentPath, filepath.Base(dir)
|
||||
}
|
||||
|
||||
// Verify we can create files in it
|
||||
testFile := filepath.Join(dirPath, "test.txt")
|
||||
if err := os.WriteFile(testFile, []byte("test"), FilePermission); err != nil {
|
||||
t.Errorf("Cannot create file in directory: %v", err)
|
||||
}
|
||||
})
|
||||
return parent, dir
|
||||
}
|
||||
|
||||
// verifyCreatedDirectory verifies that the created directory has correct properties.
|
||||
func verifyCreatedDirectory(t *testing.T, dirPath string) {
|
||||
t.Helper()
|
||||
|
||||
info := verifyDirectoryExists(t, dirPath)
|
||||
verifyIsDirectory(t, info)
|
||||
verifyDirectoryPermissions(t, info)
|
||||
verifyDirectoryUsability(t, dirPath)
|
||||
}
|
||||
|
||||
// verifyDirectoryExists verifies that the directory exists and returns its info.
|
||||
func verifyDirectoryExists(t *testing.T, dirPath string) os.FileInfo {
|
||||
t.Helper()
|
||||
|
||||
info, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Created directory does not exist: %v", err)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// verifyIsDirectory verifies that the path is a directory.
|
||||
func verifyIsDirectory(t *testing.T, info os.FileInfo) {
|
||||
t.Helper()
|
||||
|
||||
if !info.IsDir() {
|
||||
t.Error("Created path is not a directory")
|
||||
}
|
||||
}
|
||||
|
||||
// verifyDirectoryPermissions verifies that the directory has correct permissions.
|
||||
func verifyDirectoryPermissions(t *testing.T, info os.FileInfo) {
|
||||
t.Helper()
|
||||
|
||||
if info.Mode().Perm() != shared.TestDirPermission {
|
||||
t.Errorf("Directory permissions = %v, want %v", info.Mode().Perm(), shared.TestDirPermission)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyDirectoryUsability verifies that files can be created in the directory.
|
||||
func verifyDirectoryUsability(t *testing.T, dirPath string) {
|
||||
t.Helper()
|
||||
|
||||
testFile := filepath.Join(dirPath, "test.txt")
|
||||
if err := os.WriteFile(testFile, []byte("test"), shared.TestFilePermission); err != nil {
|
||||
t.Errorf("Cannot create file in directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTestFiles(t *testing.T) {
|
||||
tests := []struct {
|
||||
tests := createTestFilesTestCases()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
runTestFilesTest(t, tt.fileSpecs, tt.wantCount)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// createTestFilesTestCases creates test cases for TestCreateTestFiles.
|
||||
func createTestFilesTestCases() []struct {
|
||||
name string
|
||||
fileSpecs []FileSpec
|
||||
wantCount int
|
||||
} {
|
||||
return []struct {
|
||||
name string
|
||||
fileSpecs []FileSpec
|
||||
wantCount int
|
||||
@@ -221,7 +348,7 @@ func TestCreateTestFiles(t *testing.T) {
|
||||
name: "create multiple files",
|
||||
fileSpecs: []FileSpec{
|
||||
{Name: "file1.txt", Content: "content1"},
|
||||
{Name: "file2.go", Content: "package main"},
|
||||
{Name: "file2.go", Content: shared.LiteralPackageMain},
|
||||
{Name: "file3.json", Content: `{"key": "value"}`},
|
||||
},
|
||||
wantCount: 3,
|
||||
@@ -229,7 +356,7 @@ func TestCreateTestFiles(t *testing.T) {
|
||||
{
|
||||
name: "create files with subdirectories",
|
||||
fileSpecs: []FileSpec{
|
||||
{Name: "src/main.go", Content: "package main"},
|
||||
{Name: "src/main.go", Content: shared.LiteralPackageMain},
|
||||
{Name: "test/test.go", Content: "package test"},
|
||||
},
|
||||
wantCount: 2,
|
||||
@@ -248,39 +375,56 @@ func TestCreateTestFiles(t *testing.T) {
|
||||
wantCount: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rootDir := t.TempDir()
|
||||
// runTestFilesTest runs a single test case for CreateTestFiles.
|
||||
func runTestFilesTest(t *testing.T, fileSpecs []FileSpec, wantCount int) {
|
||||
t.Helper()
|
||||
|
||||
// Create necessary subdirectories
|
||||
for _, spec := range tt.fileSpecs {
|
||||
if strings.Contains(spec.Name, "/") {
|
||||
subdir := filepath.Join(rootDir, filepath.Dir(spec.Name))
|
||||
if err := os.MkdirAll(subdir, DirPermission); err != nil {
|
||||
t.Fatalf("Failed to create subdirectory: %v", err)
|
||||
}
|
||||
}
|
||||
rootDir := t.TempDir()
|
||||
|
||||
createNecessarySubdirectories(t, rootDir, fileSpecs)
|
||||
createdFiles := CreateTestFiles(t, rootDir, fileSpecs)
|
||||
verifyCreatedFilesCount(t, createdFiles, wantCount)
|
||||
verifyCreatedFilesContent(t, createdFiles, fileSpecs)
|
||||
}
|
||||
|
||||
// createNecessarySubdirectories creates subdirectories for file specs that need them.
|
||||
func createNecessarySubdirectories(t *testing.T, rootDir string, fileSpecs []FileSpec) {
|
||||
t.Helper()
|
||||
|
||||
for _, spec := range fileSpecs {
|
||||
if strings.Contains(spec.Name, "/") {
|
||||
subdir := filepath.Join(rootDir, filepath.Dir(spec.Name))
|
||||
if err := os.MkdirAll(subdir, shared.TestDirPermission); err != nil {
|
||||
t.Fatalf("Failed to create subdirectory: %v", err)
|
||||
}
|
||||
|
||||
createdFiles := CreateTestFiles(t, rootDir, tt.fileSpecs)
|
||||
|
||||
// Verify count
|
||||
if len(createdFiles) != tt.wantCount {
|
||||
t.Errorf("Created %d files, want %d", len(createdFiles), tt.wantCount)
|
||||
}
|
||||
|
||||
// Verify each file
|
||||
for i, filePath := range createdFiles {
|
||||
content, err := os.ReadFile(filePath) // #nosec G304 - test file path is controlled
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read file %s: %v", filePath, err)
|
||||
continue
|
||||
}
|
||||
if string(content) != tt.fileSpecs[i].Content {
|
||||
t.Errorf("File %s content = %q, want %q", filePath, content, tt.fileSpecs[i].Content)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifyCreatedFilesCount verifies the count of created files.
|
||||
func verifyCreatedFilesCount(t *testing.T, createdFiles []string, wantCount int) {
|
||||
t.Helper()
|
||||
|
||||
if len(createdFiles) != wantCount {
|
||||
t.Errorf("Created %d files, want %d", len(createdFiles), wantCount)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyCreatedFilesContent verifies the content of created files.
|
||||
func verifyCreatedFilesContent(t *testing.T, createdFiles []string, fileSpecs []FileSpec) {
|
||||
t.Helper()
|
||||
|
||||
for i, filePath := range createdFiles {
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read file %s: %v", filePath, err)
|
||||
|
||||
continue
|
||||
}
|
||||
if string(content) != fileSpecs[i].Content {
|
||||
t.Errorf("File %s content = %q, want %q", filePath, content, fileSpecs[i].Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,34 @@
|
||||
// Package testutil provides common testing utilities and helper functions.
|
||||
//
|
||||
// Testing Patterns and Conventions:
|
||||
//
|
||||
// File Setup:
|
||||
// - Use CreateTestFile() for individual files
|
||||
// - Use CreateTestFiles() for multiple files from FileSpec
|
||||
// - Use CreateTestDirectoryStructure() for complex directory trees
|
||||
// - Use SetupTempDirWithStructure() for complete test environments
|
||||
//
|
||||
// Error Assertions:
|
||||
// - Use AssertError() for conditional error checking
|
||||
// - Use AssertNoError() when expecting success
|
||||
// - Use AssertExpectedError() when expecting failure
|
||||
// - Use AssertErrorContains() for substring validation
|
||||
//
|
||||
// Configuration:
|
||||
// - Use ResetViperConfig() to reset between tests
|
||||
// - Remember to call config.LoadConfig() after ResetViperConfig()
|
||||
//
|
||||
// Best Practices:
|
||||
// - Always use t.Helper() in test helper functions
|
||||
// - Use descriptive operation names in assertions
|
||||
// - Prefer table-driven tests for multiple scenarios
|
||||
// - Use testutil.ErrTestError for standard test errors
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -11,22 +38,143 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/ivuorinen/gibidify/config"
|
||||
"github.com/ivuorinen/gibidify/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
// FilePermission is the default file permission for test files.
|
||||
FilePermission = 0o644
|
||||
// DirPermission is the default directory permission for test directories.
|
||||
DirPermission = 0o755
|
||||
)
|
||||
// SuppressLogs suppresses logger output during testing to keep test output clean.
|
||||
// Returns a function that should be called to restore the original log output.
|
||||
func SuppressLogs(t *testing.T) func() {
|
||||
t.Helper()
|
||||
logger := shared.GetLogger()
|
||||
|
||||
// Capture original output by temporarily setting it to discard
|
||||
logger.SetOutput(io.Discard)
|
||||
|
||||
// Return function to restore original settings (stderr)
|
||||
return func() {
|
||||
logger.SetOutput(os.Stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// OutputRestoreFunc represents a function that restores output after suppression.
|
||||
type OutputRestoreFunc func()
|
||||
|
||||
// SuppressAllOutput suppresses both stdout and stderr during testing.
|
||||
// This captures all output including UI messages, progress bars, and direct prints.
|
||||
// Returns a function that should be called to restore original output.
|
||||
func SuppressAllOutput(t *testing.T) OutputRestoreFunc {
|
||||
t.Helper()
|
||||
|
||||
// Save original stdout and stderr
|
||||
originalStdout := os.Stdout
|
||||
originalStderr := os.Stderr
|
||||
|
||||
// Suppress logger output as well
|
||||
logger := shared.GetLogger()
|
||||
logger.SetOutput(io.Discard)
|
||||
|
||||
// Open /dev/null for safe redirection
|
||||
devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open devnull: %v", err)
|
||||
}
|
||||
|
||||
// Redirect both stdout and stderr to /dev/null
|
||||
os.Stdout = devNull
|
||||
os.Stderr = devNull
|
||||
|
||||
// Return restore function
|
||||
return func() {
|
||||
// Close devNull first
|
||||
if devNull != nil {
|
||||
_ = devNull.Close() // Ignore close errors in cleanup
|
||||
}
|
||||
|
||||
// Restore original outputs
|
||||
os.Stdout = originalStdout
|
||||
os.Stderr = originalStderr
|
||||
logger.SetOutput(originalStderr)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureOutput captures both stdout and stderr during test execution.
|
||||
// Returns the captured output as strings and a restore function.
|
||||
func CaptureOutput(t *testing.T) (getStdout func() string, getStderr func() string, restore OutputRestoreFunc) {
|
||||
t.Helper()
|
||||
|
||||
// Save original outputs
|
||||
originalStdout := os.Stdout
|
||||
originalStderr := os.Stderr
|
||||
|
||||
// Create pipes for stdout
|
||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create stdout pipe: %v", err)
|
||||
}
|
||||
|
||||
// Create pipes for stderr
|
||||
stderrReader, stderrWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create stderr pipe: %v", err)
|
||||
}
|
||||
|
||||
// Redirect outputs
|
||||
os.Stdout = stdoutWriter
|
||||
os.Stderr = stderrWriter
|
||||
|
||||
// Suppress logger output to stderr
|
||||
logger := shared.GetLogger()
|
||||
logger.SetOutput(stderrWriter)
|
||||
|
||||
// Buffers to collect output
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
|
||||
// Start goroutines to read from pipes
|
||||
stdoutDone := make(chan struct{})
|
||||
stderrDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(stdoutDone)
|
||||
_, _ = io.Copy(&stdoutBuf, stdoutReader) //nolint:errcheck // Ignore errors during test output capture shutdown
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer close(stderrDone)
|
||||
_, _ = io.Copy(&stderrBuf, stderrReader) //nolint:errcheck // Ignore errors during test output capture shutdown
|
||||
}()
|
||||
|
||||
return func() string {
|
||||
return stdoutBuf.String()
|
||||
}, func() string {
|
||||
return stderrBuf.String()
|
||||
}, func() {
|
||||
// Close writers first to signal EOF
|
||||
_ = stdoutWriter.Close() // Ignore close errors in cleanup
|
||||
_ = stderrWriter.Close() // Ignore close errors in cleanup
|
||||
|
||||
// Wait for readers to finish
|
||||
<-stdoutDone
|
||||
<-stderrDone
|
||||
|
||||
// Close readers
|
||||
_ = stdoutReader.Close() // Ignore close errors in cleanup
|
||||
_ = stderrReader.Close() // Ignore close errors in cleanup
|
||||
|
||||
// Restore original outputs
|
||||
os.Stdout = originalStdout
|
||||
os.Stderr = originalStderr
|
||||
logger.SetOutput(originalStderr)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTestFile creates a test file with the given content and returns its path.
|
||||
func CreateTestFile(t *testing.T, dir, filename string, content []byte) string {
|
||||
t.Helper()
|
||||
filePath := filepath.Join(dir, filename)
|
||||
if err := os.WriteFile(filePath, content, FilePermission); err != nil {
|
||||
if err := os.WriteFile(filePath, content, shared.TestFilePermission); err != nil {
|
||||
t.Fatalf("Failed to write file %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
return filePath
|
||||
}
|
||||
|
||||
@@ -38,6 +186,7 @@ func CreateTempOutputFile(t *testing.T, pattern string) (file *os.File, path str
|
||||
t.Fatalf("Failed to create temp output file: %v", err)
|
||||
}
|
||||
path = outFile.Name()
|
||||
|
||||
return outFile, path
|
||||
}
|
||||
|
||||
@@ -45,9 +194,10 @@ func CreateTempOutputFile(t *testing.T, pattern string) (file *os.File, path str
|
||||
func CreateTestDirectory(t *testing.T, parent, name string) string {
|
||||
t.Helper()
|
||||
dirPath := filepath.Join(parent, name)
|
||||
if err := os.Mkdir(dirPath, DirPermission); err != nil {
|
||||
if err := os.Mkdir(dirPath, shared.TestDirPermission); err != nil {
|
||||
t.Fatalf("Failed to create directory %s: %v", dirPath, err)
|
||||
}
|
||||
|
||||
return dirPath
|
||||
}
|
||||
|
||||
@@ -65,6 +215,7 @@ func CreateTestFiles(t *testing.T, rootDir string, fileSpecs []FileSpec) []strin
|
||||
filePath := CreateTestFile(t, rootDir, spec.Name, []byte(spec.Content))
|
||||
createdFiles = append(createdFiles, filePath)
|
||||
}
|
||||
|
||||
return createdFiles
|
||||
}
|
||||
|
||||
@@ -78,6 +229,23 @@ func ResetViperConfig(t *testing.T, configPath string) {
|
||||
config.LoadConfig()
|
||||
}
|
||||
|
||||
// SetViperKeys sets specific configuration keys for testing.
|
||||
func SetViperKeys(t *testing.T, keyValues map[string]any) {
|
||||
t.Helper()
|
||||
viper.Reset()
|
||||
for key, value := range keyValues {
|
||||
viper.Set(key, value)
|
||||
}
|
||||
config.LoadConfig()
|
||||
}
|
||||
|
||||
// ApplyBackpressureOverrides applies backpressure configuration overrides for testing.
|
||||
// This is a convenience wrapper around SetViperKeys specifically for backpressure tests.
|
||||
func ApplyBackpressureOverrides(t *testing.T, overrides map[string]any) {
|
||||
t.Helper()
|
||||
SetViperKeys(t, overrides)
|
||||
}
|
||||
|
||||
// SetupCLIArgs configures os.Args for CLI testing.
|
||||
func SetupCLIArgs(srcDir, outFilePath, prefix, suffix string, concurrency int) {
|
||||
os.Args = []string{
|
||||
@@ -87,6 +255,7 @@ func SetupCLIArgs(srcDir, outFilePath, prefix, suffix string, concurrency int) {
|
||||
"-prefix", prefix,
|
||||
"-suffix", suffix,
|
||||
"-concurrency", strconv.Itoa(concurrency),
|
||||
"-no-ui", // Suppress UI output during tests
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +273,7 @@ func VerifyContentContains(t *testing.T, content string, expectedSubstrings []st
|
||||
func MustSucceed(t *testing.T, err error, operation string) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatalf("Operation %s failed: %v", operation, err)
|
||||
t.Fatalf(shared.TestMsgOperationFailed, operation, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,3 +284,130 @@ func CloseFile(t *testing.T, file *os.File) {
|
||||
t.Errorf("Failed to close file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// BaseName returns the base name of a file path (filename without directory).
|
||||
func BaseName(path string) string {
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
||||
// Advanced directory setup patterns.
|
||||
|
||||
// DirSpec represents a directory specification for creating test directory structures.
|
||||
type DirSpec struct {
|
||||
Path string
|
||||
Files []FileSpec
|
||||
}
|
||||
|
||||
// CreateTestDirectoryStructure creates multiple directories with files.
|
||||
func CreateTestDirectoryStructure(t *testing.T, rootDir string, dirSpecs []DirSpec) []string {
|
||||
t.Helper()
|
||||
createdPaths := make([]string, 0)
|
||||
|
||||
for _, dirSpec := range dirSpecs {
|
||||
dirPath := filepath.Join(rootDir, dirSpec.Path)
|
||||
if err := os.MkdirAll(dirPath, shared.TestDirPermission); err != nil {
|
||||
t.Fatalf("Failed to create directory structure %s: %v", dirPath, err)
|
||||
}
|
||||
createdPaths = append(createdPaths, dirPath)
|
||||
|
||||
// Create files in the directory
|
||||
for _, fileSpec := range dirSpec.Files {
|
||||
filePath := CreateTestFile(t, dirPath, fileSpec.Name, []byte(fileSpec.Content))
|
||||
createdPaths = append(createdPaths, filePath)
|
||||
}
|
||||
}
|
||||
|
||||
return createdPaths
|
||||
}
|
||||
|
||||
// SetupTempDirWithStructure creates a temp directory with a structured layout.
|
||||
func SetupTempDirWithStructure(t *testing.T, dirSpecs []DirSpec) string {
|
||||
t.Helper()
|
||||
rootDir := t.TempDir()
|
||||
CreateTestDirectoryStructure(t, rootDir, dirSpecs)
|
||||
|
||||
return rootDir
|
||||
}
|
||||
|
||||
// Error assertion helpers - safe to use across packages.
|
||||
|
||||
// AssertError checks if an error matches the expected state.
|
||||
// If wantErr is true, expects err to be non-nil.
|
||||
// If wantErr is false, expects err to be nil and fails if it's not.
|
||||
func AssertError(t *testing.T, err error, wantErr bool, operation string) {
|
||||
t.Helper()
|
||||
if (err != nil) != wantErr {
|
||||
if wantErr {
|
||||
t.Errorf(shared.TestMsgOperationNoError, operation)
|
||||
} else {
|
||||
t.Errorf("Operation %s unexpected error: %v", operation, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AssertNoError fails the test if err is not nil.
|
||||
func AssertNoError(t *testing.T, err error, operation string) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Errorf(shared.TestMsgOperationFailed, operation, err)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertExpectedError fails the test if err is nil when an error is expected.
|
||||
func AssertExpectedError(t *testing.T, err error, operation string) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Errorf(shared.TestMsgOperationNoError, operation)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertErrorContains checks that error contains the expected substring.
|
||||
func AssertErrorContains(t *testing.T, err error, expectedSubstring, operation string) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Errorf("Operation %s expected error containing %q but got none", operation, expectedSubstring)
|
||||
|
||||
return
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedSubstring) {
|
||||
t.Errorf("Operation %s error %q should contain %q", operation, err.Error(), expectedSubstring)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateErrorCase checks error expectations and optionally validates error message content.
|
||||
// This is a comprehensive helper that combines error checking with substring matching.
|
||||
func ValidateErrorCase(t *testing.T, err error, wantErr bool, errContains string, operation string) {
|
||||
t.Helper()
|
||||
if wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("%s: expected error but got none", operation)
|
||||
|
||||
return
|
||||
}
|
||||
if errContains != "" && !strings.Contains(err.Error(), errContains) {
|
||||
t.Errorf("%s: expected error containing %q, got: %v", operation, errContains, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", operation, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyStructuredError validates StructuredError properties.
|
||||
// This helper ensures structured errors have the expected Type and Code values.
|
||||
func VerifyStructuredError(t *testing.T, err error, expectedType shared.ErrorType, expectedCode string) {
|
||||
t.Helper()
|
||||
var structErr *shared.StructuredError
|
||||
if !errors.As(err, &structErr) {
|
||||
t.Errorf("expected StructuredError, got: %T", err)
|
||||
|
||||
return
|
||||
}
|
||||
if structErr.Type != expectedType {
|
||||
t.Errorf("expected Type %v, got %v", expectedType, structErr.Type)
|
||||
}
|
||||
if structErr.Code != expectedCode {
|
||||
t.Errorf("expected Code %q, got %q", expectedCode, structErr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
119
testutil/utility_test.go
Normal file
119
testutil/utility_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGetBaseName tests the GetBaseName utility function.
|
||||
func TestBaseName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "simple filename",
|
||||
path: "test.txt",
|
||||
expected: "test.txt",
|
||||
},
|
||||
{
|
||||
name: "absolute path",
|
||||
path: "/path/to/file.go",
|
||||
expected: "file.go",
|
||||
},
|
||||
{
|
||||
name: "relative path",
|
||||
path: "src/main.go",
|
||||
expected: "main.go",
|
||||
},
|
||||
{
|
||||
name: "nested path",
|
||||
path: "/deep/nested/path/to/file.json",
|
||||
expected: "file.json",
|
||||
},
|
||||
{
|
||||
name: "path with trailing slash",
|
||||
path: "/path/to/dir/",
|
||||
expected: "dir",
|
||||
},
|
||||
{
|
||||
name: "empty path",
|
||||
path: "",
|
||||
expected: ".",
|
||||
},
|
||||
{
|
||||
name: "root path",
|
||||
path: "/",
|
||||
expected: "/",
|
||||
},
|
||||
{
|
||||
name: "current directory",
|
||||
path: ".",
|
||||
expected: ".",
|
||||
},
|
||||
{
|
||||
name: "parent directory",
|
||||
path: "..",
|
||||
expected: "..",
|
||||
},
|
||||
{
|
||||
name: "hidden file",
|
||||
path: "/path/to/.hidden",
|
||||
expected: ".hidden",
|
||||
},
|
||||
{
|
||||
name: "file with multiple dots",
|
||||
path: "/path/file.test.go",
|
||||
expected: "file.test.go",
|
||||
},
|
||||
{
|
||||
name: "windows-style path",
|
||||
path: "C:\\Windows\\System32\\file.dll",
|
||||
expected: filepath.Base("C:\\Windows\\System32\\file.dll"), // Platform-specific result
|
||||
},
|
||||
{
|
||||
name: "mixed path separators",
|
||||
path: "/path\\to/file.txt",
|
||||
expected: "file.txt",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
result := BaseName(tt.path)
|
||||
if result != tt.expected {
|
||||
t.Errorf("BaseName(%q) = %q, want %q", tt.path, result, tt.expected)
|
||||
}
|
||||
|
||||
// Also verify against Go's filepath.Base for consistency
|
||||
expected := filepath.Base(tt.path)
|
||||
if result != expected {
|
||||
t.Errorf(
|
||||
"BaseName(%q) = %q, filepath.Base = %q, should be consistent",
|
||||
tt.path, result, expected,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetBaseName benchmarks the GetBaseName function.
|
||||
func BenchmarkBaseName(b *testing.B) {
|
||||
testPaths := []string{
|
||||
"simple.txt",
|
||||
"/path/to/file.go",
|
||||
"/very/deep/nested/path/to/some/file.json",
|
||||
"../relative/path.txt",
|
||||
"",
|
||||
"/",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
path := testPaths[i%len(testPaths)]
|
||||
_ = BaseName(path)
|
||||
}
|
||||
}
|
||||
@@ -8,100 +8,118 @@ import (
|
||||
|
||||
func TestVerifyContentContains(t *testing.T) {
|
||||
// Test successful verification
|
||||
t.Run("all substrings present", func(t *testing.T) {
|
||||
content := "This is a test file with multiple lines"
|
||||
VerifyContentContains(t, content, []string{"test file", "multiple lines"})
|
||||
// If we get here, the test passed
|
||||
})
|
||||
t.Run(
|
||||
"all substrings present", func(t *testing.T) {
|
||||
content := "This is a test file with multiple lines"
|
||||
VerifyContentContains(t, content, []string{"test file", "multiple lines"})
|
||||
// If we get here, the test passed
|
||||
},
|
||||
)
|
||||
|
||||
// Test empty expected substrings
|
||||
t.Run("empty expected substrings", func(t *testing.T) {
|
||||
content := "Any content"
|
||||
VerifyContentContains(t, content, []string{})
|
||||
// Should pass with no expected strings
|
||||
})
|
||||
t.Run(
|
||||
"empty expected substrings", func(t *testing.T) {
|
||||
content := "Any content"
|
||||
VerifyContentContains(t, content, []string{})
|
||||
// Should pass with no expected strings
|
||||
},
|
||||
)
|
||||
|
||||
// For failure cases, we'll test indirectly by verifying behavior
|
||||
t.Run("verify error reporting", func(t *testing.T) {
|
||||
// We can't easily test the failure case directly since it calls t.Errorf
|
||||
// But we can at least verify the function doesn't panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("VerifyContentContains panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
t.Run(
|
||||
"verify error reporting", func(t *testing.T) {
|
||||
// We can't easily test the failure case directly since it calls t.Errorf
|
||||
// But we can at least verify the function doesn't panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("VerifyContentContains panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// This would normally fail, but we're just checking it doesn't panic
|
||||
content := "test"
|
||||
expected := []string{"not found"}
|
||||
// Create a subtest that we expect to fail
|
||||
t.Run("expected_failure", func(t *testing.T) {
|
||||
t.Skip("Skipping actual failure test")
|
||||
VerifyContentContains(t, content, expected)
|
||||
})
|
||||
})
|
||||
// This would normally fail, but we're just checking it doesn't panic
|
||||
content := "test"
|
||||
expected := []string{"not found"}
|
||||
// Create a subtest that we expect to fail
|
||||
t.Run(
|
||||
"expected_failure", func(t *testing.T) {
|
||||
t.Skip("Skipping actual failure test")
|
||||
VerifyContentContains(t, content, expected)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestMustSucceed(t *testing.T) {
|
||||
// Test with nil error (should succeed)
|
||||
t.Run("nil error", func(t *testing.T) {
|
||||
MustSucceed(t, nil, "successful operation")
|
||||
// If we get here, the test passed
|
||||
})
|
||||
t.Run(
|
||||
"nil error", func(t *testing.T) {
|
||||
MustSucceed(t, nil, "successful operation")
|
||||
// If we get here, the test passed
|
||||
},
|
||||
)
|
||||
|
||||
// Test error behavior without causing test failure
|
||||
t.Run("verify error handling", func(t *testing.T) {
|
||||
// We can't test the failure case directly since it calls t.Fatalf
|
||||
// But we can verify the function exists and is callable
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("MustSucceed panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
t.Run(
|
||||
"verify error handling", func(t *testing.T) {
|
||||
// We can't test the failure case directly since it calls t.Fatalf
|
||||
// But we can verify the function exists and is callable
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("MustSucceed panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a subtest that we expect to fail
|
||||
t.Run("expected_failure", func(t *testing.T) {
|
||||
t.Skip("Skipping actual failure test")
|
||||
MustSucceed(t, errors.New("test error"), "failed operation")
|
||||
})
|
||||
})
|
||||
// Create a subtest that we expect to fail
|
||||
t.Run(
|
||||
"expected_failure", func(t *testing.T) {
|
||||
t.Skip("Skipping actual failure test")
|
||||
MustSucceed(t, errors.New("test error"), "failed operation")
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestCloseFile(t *testing.T) {
|
||||
// Test closing a normal file
|
||||
t.Run("close normal file", func(t *testing.T) {
|
||||
file, err := os.CreateTemp(t.TempDir(), "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
t.Run(
|
||||
"close normal file", func(t *testing.T) {
|
||||
file, err := os.CreateTemp(t.TempDir(), "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
CloseFile(t, file)
|
||||
CloseFile(t, file)
|
||||
|
||||
// Verify file is closed by trying to write to it
|
||||
_, writeErr := file.Write([]byte("test"))
|
||||
if writeErr == nil {
|
||||
t.Error("Expected write to fail on closed file")
|
||||
}
|
||||
})
|
||||
// Verify file is closed by trying to write to it
|
||||
_, writeErr := file.Write([]byte("test"))
|
||||
if writeErr == nil {
|
||||
t.Error("Expected write to fail on closed file")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Test that CloseFile doesn't panic on already closed files
|
||||
// Note: We can't easily test the error case without causing test failure
|
||||
// since CloseFile calls t.Errorf, which is the expected behavior
|
||||
t.Run("verify CloseFile function exists and is callable", func(t *testing.T) {
|
||||
// This test just verifies the function signature and basic functionality
|
||||
// The error case is tested in integration tests where failures are expected
|
||||
file, err := os.CreateTemp(t.TempDir(), "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
t.Run(
|
||||
"verify CloseFile function exists and is callable", func(t *testing.T) {
|
||||
// This test just verifies the function signature and basic functionality
|
||||
// The error case is tested in integration tests where failures are expected
|
||||
file, err := os.CreateTemp(t.TempDir(), "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
// Test normal case - file should close successfully
|
||||
CloseFile(t, file)
|
||||
// Test normal case - file should close successfully
|
||||
CloseFile(t, file)
|
||||
|
||||
// Verify file is closed
|
||||
_, writeErr := file.Write([]byte("test"))
|
||||
if writeErr == nil {
|
||||
t.Error("Expected write to fail on closed file")
|
||||
}
|
||||
})
|
||||
// Verify file is closed
|
||||
_, writeErr := file.Write([]byte("test"))
|
||||
if writeErr == nil {
|
||||
t.Error("Expected write to fail on closed file")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user