mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-01-26 03:24:05 +00:00
* 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
315 lines
8.1 KiB
Go
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)
|
|
}
|
|
},
|
|
)
|
|
}
|