Files
gibidify/testutil/directory_structure_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

496 lines
13 KiB
Go

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