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:
2025-12-10 19:07:11 +02:00
committed by GitHub
parent ea4a39a360
commit 95b7ef6dd3
149 changed files with 22990 additions and 8976 deletions

352
testutil/assertions_test.go Normal file
View 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
}
},
)
}

View File

@@ -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)
}
}

View File

@@ -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])
}
}

View 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)
},
)
}
}

View 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)
}
},
)
}

View File

@@ -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)
}
}
}

View File

@@ -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
View 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)
}
}

View File

@@ -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")
}
},
)
}