Files
gibidify/testutil/error_scenarios_test.go
Ismo Vuorinen 95b7ef6dd3 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
2025-12-10 19:07:11 +02:00

315 lines
8.1 KiB
Go

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