From a3cb920cc5668a43d740f55eff759450ea8a71f2 Mon Sep 17 00:00:00 2001 From: Delqhi Date: Sun, 14 Jun 2026 21:39:30 +0200 Subject: [PATCH] =?UTF-8?q?test(coverage):=20st-cov2=20cleanup=20=E2=80=94?= =?UTF-8?q?=20read/edit/map=20tests=20+=20test=20hooks=20for=20applyEdit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Salvages working-tree coverage changes from the PR-merge cleanup: - edit.go: add editWriteFile test hook to make applyEdit testable - map.go: track per-module file counts and add sliceContains helper - map_test.go: add CLI invalid-path, hot-path truncation, module-sort tests - read_test.go: add read.go CLI/error-branch coverage tests - edit_test.go: add edit.go CLI/error-branch coverage tests - stcov2_coverage_test.go: targeted tests for remaining sub-100% branches --- cmd/sin-code/internal/edit.go | 3 +- cmd/sin-code/internal/edit_test.go | 284 ++++++ cmd/sin-code/internal/map.go | 18 + cmd/sin-code/internal/map_test.go | 42 + cmd/sin-code/internal/read_test.go | 147 +++ cmd/sin-code/internal/stcov2_coverage_test.go | 859 ++++++++++++++++++ 6 files changed, 1352 insertions(+), 1 deletion(-) create mode 100644 cmd/sin-code/internal/edit_test.go create mode 100644 cmd/sin-code/internal/read_test.go create mode 100644 cmd/sin-code/internal/stcov2_coverage_test.go diff --git a/cmd/sin-code/internal/edit.go b/cmd/sin-code/internal/edit.go index b1cf0b1..b26d693 100644 --- a/cmd/sin-code/internal/edit.go +++ b/cmd/sin-code/internal/edit.go @@ -32,6 +32,7 @@ var ( editFormat string editSymbol string editGetwd = os.Getwd + editWriteFile = writeFileAtomic ) var EditCmd = &cobra.Command{ @@ -174,7 +175,7 @@ func applyEdit(path string, req editRequest) (*editResult, error) { if req.DryRun { return res, nil } - if _, err := writeFileAtomic(path, newContent, writeOpts{validate: false}); err != nil { + if _, err := editWriteFile(path, newContent, writeOpts{validate: false}); err != nil { return nil, err } return res, nil diff --git a/cmd/sin-code/internal/edit_test.go b/cmd/sin-code/internal/edit_test.go new file mode 100644 index 0000000..18cc4fe --- /dev/null +++ b/cmd/sin-code/internal/edit_test.go @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: MIT +// Purpose: coverage tests for edit.go CLI and error branches (st-cov2). +package internal + +import ( + "io" + "os" + "path/filepath" + "strings" + "testing" +) + +func captureEditCmd(t *testing.T, args []string, setup func()) string { + t.Helper() + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + setup() + err := EditCmd.RunE(EditCmd, args) + w.Close() + os.Stdout = oldStdout + out, _ := io.ReadAll(r) + if err != nil { + t.Fatalf("EditCmd failed: %v", err) + } + return string(out) +} + +func TestEditCmd_RunE_Text(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n\nfunc Hello() {}\n"), 0644) + + oldAnchor := editAnchor + oldNewText := editNewText + oldFormat := editFormat + oldGetwd := editGetwd + defer func() { editAnchor = oldAnchor; editNewText = oldNewText; editFormat = oldFormat; editGetwd = oldGetwd }() + + editGetwd = func() (string, error) { return dir, nil } + editAnchor = "3:" + LineHash("func Hello() {}") + editNewText = "func World() {}" + editFormat = "text" + + out := captureEditCmd(t, []string{"f.go"}, func() {}) + if !strings.Contains(out, "edited") { + t.Errorf("expected edited output, got %q", out) + } + data, _ := os.ReadFile(p) + if !strings.Contains(string(data), "func World()") { + t.Errorf("expected edit applied, got %q", data) + } +} + +func TestEditCmd_RunE_JSON(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n\nfunc Hello() {}\n"), 0644) + + oldAnchor := editAnchor + oldNewText := editNewText + oldFormat := editFormat + oldGetwd := editGetwd + defer func() { editAnchor = oldAnchor; editNewText = oldNewText; editFormat = oldFormat; editGetwd = oldGetwd }() + + editGetwd = func() (string, error) { return dir, nil } + editAnchor = "3:" + LineHash("func Hello() {}") + editNewText = "func World() {}" + editFormat = "json" + + out := captureEditCmd(t, []string{"f.go"}, func() {}) + if !strings.Contains(out, "\"path\"") { + t.Errorf("expected JSON output, got %q", out) + } +} + +func TestEditCmd_RunE_DryRun(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n\nfunc Hello() {}\n"), 0644) + + oldAnchor := editAnchor + oldNewText := editNewText + oldDryRun := editDryRun + oldGetwd := editGetwd + defer func() { editAnchor = oldAnchor; editNewText = oldNewText; editDryRun = oldDryRun; editGetwd = oldGetwd }() + + editGetwd = func() (string, error) { return dir, nil } + editAnchor = "3:" + LineHash("func Hello() {}") + editNewText = "func World() {}" + editDryRun = true + + out := captureEditCmd(t, []string{"f.go"}, func() {}) + if !strings.Contains(out, "---") { + t.Errorf("expected diff output, got %q", out) + } + data, _ := os.ReadFile(p) + if strings.Contains(string(data), "func World()") { + t.Error("dry run must not modify the file") + } +} + +func TestEditCmd_RunE_InvalidPath(t *testing.T) { + if err := EditCmd.RunE(EditCmd, []string{"\x00invalid"}); err == nil { + t.Fatal("expected error for invalid path") + } +} + +func TestApplyAnchorEdit_ParseAnchorError(t *testing.T) { + _, err := applyAnchorEdit([]string{"a"}, editRequest{Anchor: "bad"}, &editResult{}) + if err == nil { + t.Fatal("expected error for invalid anchor") + } +} + +func TestApplyAnchorEdit_ResolveError(t *testing.T) { + _, err := applyAnchorEdit([]string{"a"}, editRequest{Anchor: "1:deadbeef"}, &editResult{}) + if err == nil { + t.Fatal("expected error for unresolvable anchor") + } +} + +func TestApplyAnchorEdit_EndAnchorParseError(t *testing.T) { + lines := []string{"a", "b", "c"} + _, err := applyAnchorEdit(lines, editRequest{ + Anchor: "1:" + LineHash("a"), + EndAnchor: "bad", + }, &editResult{}) + if err == nil { + t.Fatal("expected error for invalid end anchor") + } +} + +func TestApplyAnchorEdit_EndAnchorResolveError(t *testing.T) { + lines := []string{"a", "b", "c"} + _, err := applyAnchorEdit(lines, editRequest{ + Anchor: "1:" + LineHash("a"), + EndAnchor: "2:deadbeef", + }, &editResult{}) + if err == nil { + t.Fatal("expected error for unresolvable end anchor") + } +} + +func TestApplyAnchorEdit_InvalidInsert_Cli(t *testing.T) { + lines := []string{"a", "b", "c"} + _, err := applyAnchorEdit(lines, editRequest{ + Anchor: "1:" + LineHash("a"), + NewText: "x", + Insert: "middle", + }, &editResult{}) + if err == nil { + t.Fatal("expected error for invalid insert") + } +} + +func TestApplyStringEdit_OldStringNotFound(t *testing.T) { + _, err := applyStringEdit([]string{"a"}, "abc", editRequest{OldString: "x"}, &editResult{}, new(bool)) + if err == nil { + t.Fatal("expected error when old string not found") + } +} + +func TestApplyStringEdit_ReplaceAll(t *testing.T) { + res := &editResult{} + updated, err := applyStringEdit([]string{"x", "x"}, "x\nx\n", editRequest{OldString: "x", NewString: "y", ReplaceAll: true}, res, new(bool)) + if err != nil { + t.Fatal(err) + } + if len(updated) != 2 || updated[0] != "y" || updated[1] != "y" { + t.Fatalf("expected all replaced, got %v", updated) + } +} + +func TestApplySymbolEdit_NoEngine(t *testing.T) { + _, err := applySymbolEdit([]string{"x"}, "x.txt", "x", editRequest{Symbol: "foo"}, &editResult{}) + if err == nil { + t.Fatal("expected error for unsupported language") + } +} + +func TestApplySymbolEdit_NotFound_Cli(t *testing.T) { + p := filepath.Join(t.TempDir(), "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + data, _ := os.ReadFile(p) + _, err := applySymbolEdit([]string{"package main", "func Hello() {}"}, p, string(data), editRequest{Symbol: "Missing"}, &editResult{}) + if err == nil { + t.Fatal("expected error for missing symbol") + } +} + +func TestApplySymbolEdit_Ambiguous_Cli(t *testing.T) { + p := filepath.Join(t.TempDir(), "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\nfunc Hello() {}\n"), 0644) + data, _ := os.ReadFile(p) + _, err := applySymbolEdit([]string{"package main", "func Hello() {}", "func Hello() {}"}, p, string(data), editRequest{Symbol: "Hello"}, &editResult{}) + if err == nil { + t.Fatal("expected error for ambiguous symbol") + } +} + +func TestApplySymbolEdit_InvalidInsert_Cli(t *testing.T) { + p := filepath.Join(t.TempDir(), "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + data, _ := os.ReadFile(p) + _, err := applySymbolEdit([]string{"package main", "func Hello() {}"}, p, string(data), editRequest{Symbol: "Hello", Insert: "middle"}, &editResult{}) + if err == nil { + t.Fatal("expected error for invalid insert") + } +} + +func TestApplySymbolEdit_InvalidRange(t *testing.T) { + p := filepath.Join(t.TempDir(), "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + data, _ := os.ReadFile(p) + // Force an invalid range by overriding the parsed outline via a malformed file? Instead, use a small + // trick: the symbol's EndLine will be within the line slice, but the function checks startIdx<0 etc. + // We can pass a single-line content so EndLine-1 > len(lines)-1. + _, err := applySymbolEdit([]string{"package main"}, p, string(data), editRequest{Symbol: "Hello"}, &editResult{}) + if err == nil { + t.Fatal("expected error for invalid range") + } +} + +func TestApplyEdit_FileNotFound(t *testing.T) { + _, err := applyEdit("/nonexistent/path.go", editRequest{Anchor: "1:deadbeef"}) + if err == nil { + t.Fatal("expected error for missing file") + } +} + +func TestApplyEdit_SymbolError(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "x.txt") + os.WriteFile(p, []byte("hello\n"), 0644) + _, err := applyEdit(p, editRequest{Symbol: "Foo", NewText: "bar"}) + if err == nil { + t.Fatal("expected error for unsupported language") + } +} + +func TestApplyEdit_InsertAfterMissingNewText(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{ + Anchor: "2:" + LineHash("func Hello() {}"), + Insert: "after", + }) + if err == nil { + t.Fatal("expected error for insert without new-text") + } +} + +func TestApplyEdit_SymbolInsertMissingNewText(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{ + Symbol: "Hello", + Insert: "before", + }) + if err == nil { + t.Fatal("expected error for symbol insert without new-text") + } +} + +func TestApplyEdit_SymbolReplaceMissingNewText(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{Symbol: "Hello"}) + if err == nil { + t.Fatal("expected error for symbol replace without new-text") + } +} + +func TestUnifiedDiff_NoChange(t *testing.T) { + before := []string{"a", "b", "c"} + after := []string{"a", "b", "c"} + if diff := unifiedDiff("f.txt", before, after); diff != "" { + t.Fatalf("expected empty diff, got %q", diff) + } +} diff --git a/cmd/sin-code/internal/map.go b/cmd/sin-code/internal/map.go index 6b34827..8d931cf 100644 --- a/cmd/sin-code/internal/map.go +++ b/cmd/sin-code/internal/map.go @@ -127,6 +127,15 @@ func mapArchitecture(root, action string) (*mapResult, error) { lang := detectLanguage(path) languages[lang]++ + // Track per-module file counts and languages. + dir := filepath.Dir(rel) + if m, ok := modules[dir]; ok && dir != "." && dir != "" { + m.Files++ + if !sliceContains(m.Languages, lang) { + m.Languages = append(m.Languages, lang) + } + } + if strings.Contains(strings.ToLower(rel), "_test") || strings.Contains(strings.ToLower(rel), "test_") || strings.Contains(strings.ToLower(rel), ".spec.") || strings.Contains(strings.ToLower(rel), ".test.") { testFiles++ } @@ -324,6 +333,15 @@ func min(a, b int) int { return b } +func sliceContains(haystack []string, needle string) bool { + for _, s := range haystack { + if s == needle { + return true + } + } + return false +} + func init() { RegisterVersionCmd(MapCmd) MapCmd.Flags().StringVarP(&mapAction, "action", "a", "map", "Action: map|summary|graph|hotpaths") diff --git a/cmd/sin-code/internal/map_test.go b/cmd/sin-code/internal/map_test.go index 4076ed6..424c067 100644 --- a/cmd/sin-code/internal/map_test.go +++ b/cmd/sin-code/internal/map_test.go @@ -778,3 +778,45 @@ func TestMapArchitecture_LargeFileSkip(t *testing.T) { } _ = result.Summary } + +func TestMapCmd_InvalidPath_Cli(t *testing.T) { + mapFormat = "text" + mapAction = "map" + if err := MapCmd.RunE(MapCmd, []string{"\x00invalid"}); err == nil { + t.Fatal("expected error for invalid path") + } +} + +func TestMapArchitecture_HotPathsTruncated(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "shared.go"), []byte("package main\nfunc Shared() {}\n"), 0644) + for i := 0; i < 25; i++ { + os.WriteFile(filepath.Join(dir, fmt.Sprintf("f%d.go", i)), []byte(fmt.Sprintf("package main\nimport _ \"pkg%d\"\nfunc F%d() {}\n", i, i)), 0644) + } + result, err := mapArchitecture(dir, "map") + if err != nil { + t.Fatalf("mapArchitecture failed: %v", err) + } + if len(result.HotPaths) > 20 { + t.Errorf("expected hot paths capped at 20, got %d", len(result.HotPaths)) + } +} + +func TestMapArchitecture_ModulesSorted(t *testing.T) { + dir := t.TempDir() + os.MkdirAll(filepath.Join(dir, "a"), 0755) + os.MkdirAll(filepath.Join(dir, "b"), 0755) + os.WriteFile(filepath.Join(dir, "a", "a.go"), []byte("package a\n"), 0644) + os.WriteFile(filepath.Join(dir, "b", "b1.go"), []byte("package b\n"), 0644) + os.WriteFile(filepath.Join(dir, "b", "b2.go"), []byte("package b\n"), 0644) + result, err := mapArchitecture(dir, "map") + if err != nil { + t.Fatalf("mapArchitecture failed: %v", err) + } + if len(result.Modules) < 2 { + t.Fatalf("expected modules, got %d", len(result.Modules)) + } + if result.Modules[0].Files < result.Modules[1].Files { + t.Errorf("modules not sorted by file count: %v", result.Modules) + } +} diff --git a/cmd/sin-code/internal/read_test.go b/cmd/sin-code/internal/read_test.go new file mode 100644 index 0000000..20eda22 --- /dev/null +++ b/cmd/sin-code/internal/read_test.go @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +// Purpose: coverage tests for read.go (st-cov2). +package internal + +import ( + "bytes" + "io" + "os" + "path/filepath" + "strings" + "testing" +) + +func captureReadCmd(t *testing.T, args []string, setup func()) string { + t.Helper() + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + setup() + err := ReadCmd.RunE(ReadCmd, args) + w.Close() + os.Stdout = oldStdout + out, _ := io.ReadAll(r) + if err != nil { + t.Fatalf("ReadCmd failed: %v", err) + } + return string(out) +} + +func TestReadCmd_RunE_Text(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n"), 0644) + + oldMode := readMode + oldFormat := readFormat + defer func() { readMode = oldMode; readFormat = oldFormat }() + readMode = "raw" + readFormat = "text" + + out := captureReadCmd(t, []string{p}, func() {}) + if !strings.Contains(out, "package main") { + t.Errorf("expected file content, got %q", out) + } +} + +func TestReadCmd_RunE_JSON(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n"), 0644) + + oldFormat := readFormat + defer func() { readFormat = oldFormat }() + readFormat = "json" + + out := captureReadCmd(t, []string{p}, func() {}) + if !strings.Contains(out, `"path"`) { + t.Errorf("expected JSON output, got %q", out) + } +} + +func TestReadCmd_RunE_Truncated(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\nb\nc\nd\n"), 0644) + + oldLimit := readLimit + oldFormat := readFormat + defer func() { readLimit = oldLimit; readFormat = oldFormat }() + readLimit = 2 + readFormat = "text" + + oldStdout := os.Stdout + oldStderr := os.Stderr + outR, outW, _ := os.Pipe() + errR, errW, _ := os.Pipe() + os.Stdout = outW + os.Stderr = errW + err := ReadCmd.RunE(ReadCmd, []string{p}) + outW.Close() + errW.Close() + os.Stdout = oldStdout + os.Stderr = oldStderr + if err != nil { + t.Fatalf("ReadCmd failed: %v", err) + } + stdout, _ := io.ReadAll(outR) + stderr, _ := io.ReadAll(errR) + if !bytes.Contains(stderr, []byte("truncated")) { + t.Errorf("expected truncated stderr, got stdout=%q stderr=%q", stdout, stderr) + } +} + +func TestReadFile_NotFound(t *testing.T) { + _, err := readFile("/nonexistent/file.go", "raw", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for missing file") + } +} + +func TestReadFile_Directory(t *testing.T) { + dir := t.TempDir() + _, err := readFile(dir, "raw", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for directory") + } +} + +func TestReadFile_TooLarge(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "big.bin") + os.WriteFile(p, make([]byte, 1024), 0644) + _, err := readFile(p, "raw", 1, 100, 512) + if err == nil { + t.Fatal("expected error for file over max-bytes") + } +} + +func TestReadFile_OffsetBeyondEnd_Cli(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\n"), 0644) + _, err := readFile(p, "raw", 10, 100, 1<<20) + if err == nil { + t.Fatal("expected error for offset beyond end") + } +} + +func TestReadFile_UnknownMode(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\n"), 0644) + _, err := readFile(p, "invalid", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for unknown mode") + } +} + +func TestReadFile_Binary(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.bin") + os.WriteFile(p, []byte{0xff, 0xfe}, 0644) + _, err := readFile(p, "raw", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for binary file") + } +} diff --git a/cmd/sin-code/internal/stcov2_coverage_test.go b/cmd/sin-code/internal/stcov2_coverage_test.go new file mode 100644 index 0000000..817750e --- /dev/null +++ b/cmd/sin-code/internal/stcov2_coverage_test.go @@ -0,0 +1,859 @@ +// SPDX-License-Identifier: MIT +// Purpose: st-cov2 coverage push — targeted tests for the remaining sub-100% branches in cmd/sin-code/internal. +package internal + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestValidateSyntax_Markdown_stcov2(t *testing.T) { + if err := validateSyntax("readme.md", "# Hello"); err != nil { + t.Fatalf("markdown should be skipped: %v", err) + } + if err := validateSyntax("log.txt", "hello"); err != nil { + t.Fatalf("txt should be skipped: %v", err) + } + if err := validateSyntax("config.json", `{"ok": true}`); err != nil { + t.Fatalf("valid JSON should pass: %v", err) + } +} + +func TestGoASTProvider_ParseBlankIdentifier_stcov2(t *testing.T) { + p := goASTProvider{} + out, err := p.parse("x.go", []byte("package x\nvar _ = 1\nconst _ = 2\n")) + if err != nil { + t.Fatalf("parse error: %v", err) + } + for _, sym := range out.Symbols { + if sym.Name == "_" { + t.Errorf("blank identifier should be skipped, got %v", sym) + } + } +} + +func TestUnifiedDiff_ChangeAtEnd_stcov2(t *testing.T) { + before := []string{"a", "b", "c"} + after := []string{"a", "b", "d"} + diff := unifiedDiff("f.txt", before, after) + if !strings.Contains(diff, "-c") || !strings.Contains(diff, "+d") { + t.Fatalf("expected change at end, got:\n%s", diff) + } +} + +func TestUnifiedDiff_NoChange_stcov2(t *testing.T) { + before := []string{"a", "b", "c"} + if diff := unifiedDiff("f.txt", before, before); diff != "" { + t.Fatalf("expected empty diff, got %q", diff) + } +} + +func TestReadCmd_RunE_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n"), 0644) + + oldMode := readMode + oldFormat := readFormat + defer func() { readMode = oldMode; readFormat = oldFormat }() + + readMode = "raw" + readFormat = "text" + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + err := ReadCmd.RunE(ReadCmd, []string{p}) + w.Close() + os.Stdout = oldStdout + if err != nil { + t.Fatalf("ReadCmd failed: %v", err) + } + out, _ := io.ReadAll(r) + if !strings.Contains(string(out), "package main") { + t.Errorf("expected content, got %q", out) + } +} + +func TestReadCmd_RunE_JSON_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n"), 0644) + + oldFormat := readFormat + defer func() { readFormat = oldFormat }() + readFormat = "json" + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + err := ReadCmd.RunE(ReadCmd, []string{p}) + w.Close() + os.Stdout = oldStdout + if err != nil { + t.Fatalf("ReadCmd failed: %v", err) + } + out, _ := io.ReadAll(r) + if !strings.Contains(string(out), `"path"`) { + t.Errorf("expected JSON output, got %q", out) + } +} + +func TestReadCmd_RunE_Truncated_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\nb\nc\nd\n"), 0644) + + oldLimit := readLimit + oldFormat := readFormat + defer func() { readLimit = oldLimit; readFormat = oldFormat }() + readLimit = 2 + readFormat = "text" + + oldStdout := os.Stdout + oldStderr := os.Stderr + outR, outW, _ := os.Pipe() + errR, errW, _ := os.Pipe() + os.Stdout = outW + os.Stderr = errW + err := ReadCmd.RunE(ReadCmd, []string{p}) + outW.Close() + errW.Close() + os.Stdout = oldStdout + os.Stderr = oldStderr + if err != nil { + t.Fatalf("ReadCmd failed: %v", err) + } + stdout, _ := io.ReadAll(outR) + stderr, _ := io.ReadAll(errR) + if !bytes.Contains(stderr, []byte("truncated")) { + t.Errorf("expected truncated stderr, got stdout=%q stderr=%q", stdout, stderr) + } +} + +func TestReadFile_UnknownMode_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\n"), 0644) + _, err := readFile(p, "invalid", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for unknown mode") + } +} + +func TestReadFile_Binary_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.bin") + os.WriteFile(p, []byte{0xff, 0xfe}, 0644) + _, err := readFile(p, "raw", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for binary file") + } +} + +func TestEditCmd_RunE_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n\nfunc Hello() {}\n"), 0644) + + oldAnchor := editAnchor + oldNewText := editNewText + oldFormat := editFormat + oldGetwd := editGetwd + defer func() { editAnchor = oldAnchor; editNewText = oldNewText; editFormat = oldFormat; editGetwd = oldGetwd }() + + editGetwd = func() (string, error) { return dir, nil } + editAnchor = "3:" + LineHash("func Hello() {}") + editNewText = "func World() {}" + editFormat = "text" + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + err := EditCmd.RunE(EditCmd, []string{"f.go"}) + w.Close() + os.Stdout = oldStdout + if err != nil { + t.Fatalf("EditCmd failed: %v", err) + } + out, _ := io.ReadAll(r) + if !strings.Contains(string(out), "edited") { + t.Errorf("expected edited output, got %q", out) + } + data, _ := os.ReadFile(p) + if !strings.Contains(string(data), "func World()") { + t.Errorf("expected edit applied, got %q", data) + } +} + +func TestEditCmd_RunE_DryRun_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n\nfunc Hello() {}\n"), 0644) + + oldAnchor := editAnchor + oldNewText := editNewText + oldDryRun := editDryRun + oldGetwd := editGetwd + defer func() { editAnchor = oldAnchor; editNewText = oldNewText; editDryRun = oldDryRun; editGetwd = oldGetwd }() + + editGetwd = func() (string, error) { return dir, nil } + editAnchor = "3:" + LineHash("func Hello() {}") + editNewText = "func World() {}" + editDryRun = true + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + err := EditCmd.RunE(EditCmd, []string{"f.go"}) + w.Close() + os.Stdout = oldStdout + if err != nil { + t.Fatalf("EditCmd failed: %v", err) + } + out, _ := io.ReadAll(r) + if !strings.Contains(string(out), "---") { + t.Errorf("expected diff output, got %q", out) + } + data, _ := os.ReadFile(p) + if strings.Contains(string(data), "func World()") { + t.Error("dry run must not modify the file") + } +} + +func TestApplyEdit_FileNotFound_stcov2(t *testing.T) { + _, err := applyEdit("/nonexistent/path.go", editRequest{Anchor: "1:deadbeef"}) + if err == nil { + t.Fatal("expected error for missing file") + } +} + +func TestApplyEdit_SymbolError_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "x.txt") + os.WriteFile(p, []byte("hello\n"), 0644) + _, err := applyEdit(p, editRequest{Symbol: "Foo", NewText: "bar"}) + if err == nil { + t.Fatal("expected error for unsupported language") + } +} + +func TestApplyAnchorEdit_InsertAfterMissingNewText_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{ + Anchor: "2:" + LineHash("func Hello() {}"), + Insert: "after", + }) + if err == nil { + t.Fatal("expected error for insert without new-text") + } +} + +func TestApplySymbolEdit_NoEngine_stcov2(t *testing.T) { + _, err := applySymbolEdit([]string{"x"}, "x.txt", "x", editRequest{Symbol: "foo"}, &editResult{}) + if err == nil { + t.Fatal("expected error for unsupported language") + } +} + +func TestApplySymbolEdit_InvalidRange_stcov2(t *testing.T) { + p := filepath.Join(t.TempDir(), "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + data, _ := os.ReadFile(p) + _, err := applySymbolEdit([]string{"package main"}, p, string(data), editRequest{Symbol: "Hello"}, &editResult{}) + if err == nil { + t.Fatal("expected error for invalid range") + } +} + +func TestApplyStringEdit_OldStringNotFound_stcov2(t *testing.T) { + _, err := applyStringEdit([]string{"a"}, "abc", editRequest{OldString: "x"}, &editResult{}, new(bool)) + if err == nil { + t.Fatal("expected error when old string not found") + } +} + +func TestApplyStringEdit_ReplaceAll_stcov2(t *testing.T) { + res := &editResult{} + updated, err := applyStringEdit([]string{"x", "x"}, "x\nx\n", editRequest{OldString: "x", NewString: "y", ReplaceAll: true}, res, new(bool)) + if err != nil { + t.Fatal(err) + } + if len(updated) != 2 || updated[0] != "y" || updated[1] != "y" { + t.Fatalf("expected all replaced, got %v", updated) + } +} + +func TestHarvestCmd_RunE_stcov2(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, `{"ok":true}`) + })) + defer server.Close() + + oldURL := harvestURL + oldMethod := harvestMethod + oldFormat := harvestFormat + defer func() { harvestURL = oldURL; harvestMethod = oldMethod; harvestFormat = oldFormat }() + + harvestURL = server.URL + harvestMethod = "GET" + harvestFormat = "text" + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + defer r.Close() + os.Stdout = w + err := HarvestCmd.RunE(HarvestCmd, []string{}) + w.Close() + os.Stdout = oldStdout + + if err != nil { + t.Fatalf("HarvestCmd.RunE failed: %v", err) + } + out, _ := io.ReadAll(r) + if !strings.Contains(string(out), "ok") { + t.Errorf("expected output, got %q", out) + } +} + +func TestHarvestURLFetch_InvalidURLError_stcov2(t *testing.T) { + err := harvestURLFetch("http://host:abc", "GET", 5, "text") + if err == nil { + t.Fatal("expected error for invalid URL") + } +} + +func TestExecuteCmd_Timeout_stcov2(t *testing.T) { + execCommand = "sleep 10" + execFormat = "text" + execTimeout = 1 + _ = ExecuteCmd.RunE(ExecuteCmd, []string{}) +} + +func TestExecuteCmd_TimeoutZero_stcov2(t *testing.T) { + execCommand = "sleep 1" + execFormat = "text" + execTimeout = 0 + _ = ExecuteCmd.RunE(ExecuteCmd, []string{}) +} + +func TestResolveAnchor_UpwardDrift_stcov2(t *testing.T) { + lines := []string{"a", "b", "target", "d"} + idx, drift, err := ResolveAnchor(lines, Anchor{Line: 4, Hash: LineHash("target")}, DefaultDriftWindow) + if err != nil { + t.Fatal(err) + } + if idx != 2 || drift != -1 { + t.Fatalf("want idx=2 drift=-1, got idx=%d drift=%d", idx, drift) + } +} + +func TestBraceBlockEnd_NoOpenBrace_stcov2(t *testing.T) { + lines := []string{"func Foo() int", "\treturn 1", "end"} + end := braceBlockEnd(lines, 0) + if end != 1 { + t.Fatalf("want end=1, got %d", end) + } +} + +func TestDiscoverCmd_FileNotDirectory_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n"), 0644) + discoverFormat = "text" + discoverPattern = "**/*" + if err := DiscoverCmd.RunE(DiscoverCmd, []string{p}); err == nil { + t.Fatal("expected error when path is a file") + } +} + +func TestScoreRelevance_CappedAt100_stcov2(t *testing.T) { + score := scoreRelevance("app.main.index.go", 100) + if score != 100 { + t.Errorf("expected score capped at 100, got %.1f", score) + } +} + +func TestExtractDependencies_Truncated_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "imports.go") + var imports []string + for i := 0; i < 25; i++ { + imports = append(imports, fmt.Sprintf("\t_ \"pkg%d\"", i)) + } + os.WriteFile(p, []byte("package main\nimport (\n"+strings.Join(imports, "\n")+"\n)\n"), 0644) + deps := extractDependencies(p) + if len(deps) != 20 { + t.Errorf("expected deps truncated to 20, got %d", len(deps)) + } +} + +func TestDiffWithIntent_BeforeReadError_stcov2(t *testing.T) { + dir := t.TempDir() + beforeFile := filepath.Join(dir, "before.txt") + os.WriteFile(beforeFile, []byte("secret"), 0644) + os.Chmod(beforeFile, 0000) + defer os.Chmod(beforeFile, 0644) + + _, err := diffWithIntent(beforeFile, "/also/missing/after.txt", "test") + if err == nil { + t.Fatal("expected error for unreadable before file") + } +} + +func TestDiffWithIntent_AfterReadError_stcov2(t *testing.T) { + dir := t.TempDir() + afterFile := filepath.Join(dir, "after.txt") + os.WriteFile(afterFile, []byte("secret"), 0644) + os.Chmod(afterFile, 0000) + defer os.Chmod(afterFile, 0644) + + _, err := diffWithIntent("/nonexistent/before.txt", afterFile, "test") + if err == nil { + t.Fatal("expected error for unreadable after file") + } +} + +func TestCollectPythonDeps_ReadError_stcov2(t *testing.T) { + dir := t.TempDir() + req := filepath.Join(dir, "requirements.txt") + os.WriteFile(req, []byte("requests\n"), 0644) + os.Chmod(req, 0000) + defer os.Chmod(req, 0644) + + _, err := collectPythonDeps(dir) + if err == nil { + t.Fatal("expected error for unreadable requirements.txt") + } +} + +func TestCollectPythonDeps_PyprojectError_stcov2(t *testing.T) { + dir := t.TempDir() + pyproject := filepath.Join(dir, "pyproject.toml") + os.WriteFile(pyproject, []byte("[project]\n"), 0644) + os.Chmod(pyproject, 0000) + defer os.Chmod(pyproject, 0644) + + _, err := collectPythonDeps(dir) + if err == nil { + t.Fatal("expected error for unreadable pyproject.toml") + } +} + +func TestEditCmd_GetwdError_stcov2(t *testing.T) { + oldGetwd := editGetwd + defer func() { editGetwd = oldGetwd }() + editGetwd = func() (string, error) { return "", fmt.Errorf("no wd") } + if err := EditCmd.RunE(EditCmd, []string{"f.go"}); err == nil { + t.Fatal("expected error for getwd failure") + } +} + +func TestApplyEdit_MultipleModes_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n"), 0644) + _, err := applyEdit(p, editRequest{Anchor: "1:deadbeef", OldString: "x"}) + if err == nil { + t.Fatal("expected error for multiple modes") + } +} + +func TestEditCmd_RunE_JSON_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n\nfunc Hello() {}\n"), 0644) + + oldAnchor := editAnchor + oldNewText := editNewText + oldFormat := editFormat + oldGetwd := editGetwd + defer func() { editAnchor = oldAnchor; editNewText = oldNewText; editFormat = oldFormat; editGetwd = oldGetwd }() + + editGetwd = func() (string, error) { return dir, nil } + editAnchor = "3:" + LineHash("func Hello() {}") + editNewText = "func World() {}" + editFormat = "json" + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + err := EditCmd.RunE(EditCmd, []string{"f.go"}) + w.Close() + os.Stdout = oldStdout + if err != nil { + t.Fatalf("EditCmd failed: %v", err) + } + out, _ := io.ReadAll(r) + if !strings.Contains(string(out), "\"path\"") { + t.Errorf("expected JSON output, got %q", out) + } +} + +func TestApplyEdit_BadAnchor_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{Anchor: "bad"}) + if err == nil { + t.Fatal("expected error for invalid anchor") + } +} + +func TestApplyEdit_BadEndAnchor_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{ + Anchor: "2:" + LineHash("func Hello() {}"), + EndAnchor: "bad", + NewText: "x", + }) + if err == nil { + t.Fatal("expected error for invalid end anchor") + } +} + +func TestApplyEdit_UnresolvableEndAnchor_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{ + Anchor: "2:" + LineHash("func Hello() {}"), + EndAnchor: "2:deadbeef", + NewText: "x", + }) + if err == nil { + t.Fatal("expected error for unresolvable end anchor") + } +} + +func TestApplyEdit_SymbolInsertBeforeMissing_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{Symbol: "Hello", Insert: "before"}) + if err == nil { + t.Fatal("expected error for symbol insert before without new-text") + } +} + +func TestApplyEdit_SymbolInsertAfterMissing_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{Symbol: "Hello", Insert: "after"}) + if err == nil { + t.Fatal("expected error for symbol insert after without new-text") + } +} + +func TestApplyEdit_SymbolReplaceMissing_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{Symbol: "Hello"}) + if err == nil { + t.Fatal("expected error for symbol replace without new-text") + } +} + +func TestDiscoverCmd_InvalidAbsPath_stcov2(t *testing.T) { + discoverFormat = "text" + discoverPattern = "**/*" + if err := DiscoverCmd.RunE(DiscoverCmd, []string{"\x00invalid"}); err == nil { + t.Fatal("expected error for invalid abs path") + } +} + +func TestDiscoverFiles_EarlyStop_stcov2(t *testing.T) { + dir := t.TempDir() + for i := 0; i < 25; i++ { + os.WriteFile(filepath.Join(dir, fmt.Sprintf("f%d.go", i)), []byte("package main\n"), 0644) + } + results, err := discoverFiles(dir, "*.go", 1) + if err != nil { + t.Fatalf("discoverFiles failed: %v", err) + } + if len(results) == 0 { + t.Fatal("expected some results") + } +} + +func TestDiscoverFiles_WalkErrorSkip_stcov2(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "ok.go"), []byte("package main\n"), 0644) + badDir := filepath.Join(dir, "bad") + os.MkdirAll(badDir, 0755) + os.WriteFile(filepath.Join(badDir, "x.go"), []byte("package main\n"), 0644) + os.Chmod(badDir, 0000) + defer os.Chmod(badDir, 0755) + + _, err := discoverFiles(dir, "**/*.go", 10) + if err != nil { + t.Fatalf("discoverFiles should skip unreadable dirs: %v", err) + } +} + +func TestMapCmd_InvalidAbsPath_stcov2(t *testing.T) { + mapFormat = "text" + mapAction = "map" + if err := MapCmd.RunE(MapCmd, []string{"\x00invalid"}); err == nil { + t.Fatal("expected error for invalid abs path") + } +} + +func TestReadCmd_InvalidAbsPath_stcov2(t *testing.T) { + readFormat = "text" + if err := ReadCmd.RunE(ReadCmd, []string{"\x00invalid"}); err == nil { + t.Fatal("expected error for invalid abs path") + } +} + +func TestReadFile_NotFound_stcov2(t *testing.T) { + _, err := readFile("/nonexistent/file.go", "raw", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for missing file") + } +} + +func TestReadFile_ReadError_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\n"), 0644) + os.Chmod(p, 0000) + defer os.Chmod(p, 0644) + _, err := readFile(p, "raw", 1, 100, 1<<20) + if err == nil { + t.Fatal("expected error for unreadable file") + } +} + +func TestIbdCmd_MissingArgs_stcov2(t *testing.T) { + ibdBefore = "" + ibdAfter = "" + if err := IbdCmd.RunE(IbdCmd, []string{}); err == nil { + t.Fatal("expected error when before/after missing") + } +} + +func TestReadCmd_OffsetLessThanOne_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\nb\n"), 0644) + + oldOffset := readOffset + oldFormat := readFormat + defer func() { readOffset = oldOffset; readFormat = oldFormat }() + + readFormat = "text" + readOffset = 0 + if err := ReadCmd.RunE(ReadCmd, []string{p}); err != nil { + t.Fatalf("ReadCmd should clamp offset, got: %v", err) + } +} + +func TestMapArchitecture_OrphanTestFile_stcov2(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "util_test.go"), []byte("package main\n"), 0644) + result, err := mapArchitecture(dir, "map") + if err != nil { + t.Fatalf("mapArchitecture failed: %v", err) + } + for _, o := range result.Orphans { + if o == "util_test.go" { + t.Fatal("test file should not be listed as orphan") + } + } +} + +func TestMapArchitecture_EmptyModule_stcov2(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "root.go"), []byte("package main\n"), 0644) + os.MkdirAll(filepath.Join(dir, "empty"), 0755) + result, err := mapArchitecture(dir, "map") + if err != nil { + t.Fatalf("mapArchitecture failed: %v", err) + } + for _, m := range result.Modules { + if m.Path == "empty" && m.Files != 0 { + t.Fatal("empty module should have 0 files") + } + } +} + +func TestApplyEdit_WriteFileError_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + + old := editWriteFile + defer func() { editWriteFile = old }() + editWriteFile = func(path, content string, opts writeOpts) (*writeResult, error) { + return nil, fmt.Errorf("simulated write error") + } + + _, err := applyEdit(p, editRequest{ + Anchor: "2:" + LineHash("func Hello() {}"), + NewText: "func World() {}", + }) + if err == nil { + t.Fatal("expected error when write fails") + } +} + +func TestIbdCmd_DiffError_stcov2(t *testing.T) { + dir := t.TempDir() + beforeFile := filepath.Join(dir, "before.txt") + os.WriteFile(beforeFile, []byte("secret"), 0644) + os.Chmod(beforeFile, 0000) + defer os.Chmod(beforeFile, 0644) + + ibdBefore = beforeFile + ibdAfter = "/nonexistent/after.txt" + ibdIntent = "test" + ibdFormat = "text" + if err := IbdCmd.RunE(IbdCmd, []string{}); err == nil { + t.Fatal("expected error when diffWithIntent fails") + } +} + +func TestReadCmd_OffsetBeyondEnd_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("a\n"), 0644) + + oldOffset := readOffset + oldFormat := readFormat + oldLimit := readLimit + defer func() { readOffset = oldOffset; readFormat = oldFormat; readLimit = oldLimit }() + + readFormat = "text" + readLimit = 100 + readOffset = 10 + if err := ReadCmd.RunE(ReadCmd, []string{p}); err == nil { + t.Fatal("expected error for offset beyond end") + } +} + +func TestApplyAnchorEdit_InsertBeforeMissing_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{ + Anchor: "2:" + LineHash("func Hello() {}"), + Insert: "before", + }) + if err == nil { + t.Fatal("expected error for insert before without new-text") + } +} + +func TestApplyEdit_UnresolvableStartAnchor_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\nfunc Hello() {}\n"), 0644) + _, err := applyEdit(p, editRequest{ + Anchor: "2:deadbeef", + NewText: "x", + }) + if err == nil { + t.Fatal("expected error for unresolvable start anchor") + } +} + +func TestEditCmd_ApplyEditError_stcov2(t *testing.T) { + dir := t.TempDir() + p := filepath.Join(dir, "f.go") + os.WriteFile(p, []byte("package main\n"), 0644) + + oldAnchor := editAnchor + oldOldString := editOldString + oldGetwd := editGetwd + defer func() { editAnchor = oldAnchor; editOldString = oldOldString; editGetwd = oldGetwd }() + + editGetwd = func() (string, error) { return dir, nil } + editAnchor = "1:deadbeef" + editOldString = "x" + + if err := EditCmd.RunE(EditCmd, []string{"f.go"}); err == nil { + t.Fatal("expected error when applyEdit fails") + } +} + +func TestDiscoverCmd_LimitTruncation_stcov2(t *testing.T) { + dir := t.TempDir() + for i := 0; i < 25; i++ { + os.WriteFile(filepath.Join(dir, fmt.Sprintf("f%d.go", i)), []byte("package main\n"), 0644) + } + + discoverFormat = "text" + discoverPattern = "*.go" + discoverLimit = 1 + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + err := DiscoverCmd.RunE(DiscoverCmd, []string{dir}) + w.Close() + os.Stdout = oldStdout + if err != nil { + t.Fatalf("DiscoverCmd failed: %v", err) + } + out, _ := io.ReadAll(r) + if !strings.Contains(string(out), "f") { + t.Errorf("expected output, got %q", out) + } +} + +func TestMapArchitecture_HotPathsTruncated_stcov2(t *testing.T) { + dir := t.TempDir() + for dep := 0; dep < 25; dep++ { + os.WriteFile(filepath.Join(dir, fmt.Sprintf("dep%d.go", dep)), []byte(fmt.Sprintf("package main\nfunc Dep%d() {}\n", dep)), 0644) + for importer := 0; importer < 3; importer++ { + os.WriteFile(filepath.Join(dir, fmt.Sprintf("dep%d_importer%d.go", dep, importer)), []byte(fmt.Sprintf("package main\nimport _ \"dep%d\"\n", dep)), 0644) + } + } + result, err := mapArchitecture(dir, "map") + if err != nil { + t.Fatalf("mapArchitecture failed: %v", err) + } + if len(result.HotPaths) > 20 { + t.Errorf("expected hot paths capped at 20, got %d", len(result.HotPaths)) + } + if len(result.HotPaths) == 0 { + t.Fatal("expected at least one hot path") + } + if result.HotPaths[0].Imports != 3 { + t.Errorf("expected top hot path to have 3 imports, got %d", result.HotPaths[0].Imports) + } +} + +func TestMapArchitecture_ModulesSorted_stcov2(t *testing.T) { + dir := t.TempDir() + os.MkdirAll(filepath.Join(dir, "a"), 0755) + os.MkdirAll(filepath.Join(dir, "b"), 0755) + os.WriteFile(filepath.Join(dir, "a", "a.go"), []byte("package a\n"), 0644) + os.WriteFile(filepath.Join(dir, "b", "b1.go"), []byte("package b\n"), 0644) + os.WriteFile(filepath.Join(dir, "b", "b2.go"), []byte("package b\n"), 0644) + result, err := mapArchitecture(dir, "map") + if err != nil { + t.Fatalf("mapArchitecture failed: %v", err) + } + if len(result.Modules) < 2 { + t.Fatalf("expected modules, got %d", len(result.Modules)) + } + if result.Modules[0].Files < result.Modules[1].Files { + t.Errorf("modules not sorted by file count: %v", result.Modules) + } +}