160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
package ctxasm
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"nub/internal/llm"
|
|
)
|
|
|
|
func writeFile(t *testing.T, path, content string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAssemble_WalkUp_OrdersRepoRootBeforeCwd(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
sub := filepath.Join(repoRoot, "sub", "deeper")
|
|
if err := os.MkdirAll(sub, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeFile(t, filepath.Join(repoRoot, "AGENTS.md"), "root instructions")
|
|
writeFile(t, filepath.Join(sub, "AGENTS.md"), "deeper instructions")
|
|
|
|
blocks, meta, err := Assemble(Options{
|
|
RepoRoot: repoRoot, Cwd: sub,
|
|
Files: []string{"AGENTS.md"}, WalkUp: true, MaxTokens: 20000,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(meta.Files) != 2 {
|
|
t.Fatalf("loaded %d files, want 2", len(meta.Files))
|
|
}
|
|
if meta.Files[0].Path != "AGENTS.md" {
|
|
t.Errorf("first file = %q, want repo-root AGENTS.md first", meta.Files[0].Path)
|
|
}
|
|
if meta.Files[1].Path != filepath.Join("sub", "deeper", "AGENTS.md") {
|
|
t.Errorf("second file = %q, want the deeper one last", meta.Files[1].Path)
|
|
}
|
|
|
|
joined := joinBlockTexts(blocks)
|
|
rootIdx := strings.Index(joined, "root instructions")
|
|
deeperIdx := strings.Index(joined, "deeper instructions")
|
|
if rootIdx == -1 || deeperIdx == -1 || rootIdx > deeperIdx {
|
|
t.Errorf("expected root instructions before deeper instructions in assembled text")
|
|
}
|
|
}
|
|
|
|
func TestAssemble_LastBlockHasCacheMark(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
blocks, _, err := Assemble(Options{RepoRoot: repoRoot, Cwd: repoRoot, MaxTokens: 20000})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, b := range blocks {
|
|
want := i == len(blocks)-1
|
|
if b.CacheMark != want {
|
|
t.Errorf("block %d CacheMark = %v, want %v", i, b.CacheMark, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAssemble_BudgetExceeded_TruncatesAndWarns(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
big := strings.Repeat("word ", 2000)
|
|
writeFile(t, filepath.Join(repoRoot, "AGENTS.md"), big)
|
|
|
|
_, meta, err := Assemble(Options{
|
|
RepoRoot: repoRoot, Cwd: repoRoot,
|
|
Files: []string{"AGENTS.md"}, WalkUp: false, MaxTokens: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(meta.Warnings) == 0 {
|
|
t.Fatal("expected a budget warning")
|
|
}
|
|
if len(meta.Files) != 1 || !meta.Files[0].Truncated {
|
|
t.Errorf("expected AGENTS.md to be marked truncated, got: %+v", meta.Files)
|
|
}
|
|
}
|
|
|
|
func TestAssemble_MissingFilesAreSkippedSilently(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
_, meta, err := Assemble(Options{
|
|
RepoRoot: repoRoot, Cwd: repoRoot,
|
|
Files: []string{"DOES_NOT_EXIST.md"}, WalkUp: false, MaxTokens: 20000,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(meta.Files) != 0 {
|
|
t.Errorf("expected no files loaded, got %+v", meta.Files)
|
|
}
|
|
}
|
|
|
|
func TestAssemble_WarnsOnStaleRepoMap(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
runGit(t, repoRoot, "init")
|
|
writeFile(t, filepath.Join(repoRoot, "f.txt"), "x")
|
|
runGit(t, repoRoot, "add", "-A")
|
|
runGit(t, repoRoot, "-c", "user.email=t@t.com", "-c", "user.name=t", "commit", "-m", "init")
|
|
|
|
content, err := GenerateRepoMap(repoRoot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeFile(t, filepath.Join(repoRoot, "REPOMAP.md"), content)
|
|
|
|
// Neuer Commit nach Repomap-Generierung -> Repomap ist jetzt stale.
|
|
writeFile(t, filepath.Join(repoRoot, "f.txt"), "y")
|
|
runGit(t, repoRoot, "add", "-A")
|
|
runGit(t, repoRoot, "-c", "user.email=t@t.com", "-c", "user.name=t", "commit", "-m", "change")
|
|
|
|
_, meta, err := Assemble(Options{
|
|
RepoRoot: repoRoot, Cwd: repoRoot,
|
|
Files: []string{"REPOMAP.md"}, WalkUp: false, MaxTokens: 20000,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
found := false
|
|
for _, w := range meta.Warnings {
|
|
if strings.Contains(w, "REPOMAP.md") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected a staleness warning, got: %v", meta.Warnings)
|
|
}
|
|
}
|
|
|
|
func runGit(t *testing.T, dir string, args ...string) {
|
|
t.Helper()
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = dir
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("git %v: %v\n%s", args, err, out)
|
|
}
|
|
}
|
|
|
|
func joinBlockTexts(blocks []llm.Block) string {
|
|
var b strings.Builder
|
|
for _, blk := range blocks {
|
|
b.WriteString(blk.Text)
|
|
b.WriteString("\n")
|
|
}
|
|
return b.String()
|
|
}
|