package thread import ( "context" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/barerepo/server/internal/gitx" ) // anchorRepo commits a file, changes it in a named way, and returns the original blob hash. func anchorRepo(t *testing.T, before, after string) (dir, oldBlob string) { t.Helper() if _, err := gitx.Version(context.Background()); err != nil { t.Skip("git is not installed") } dir = t.TempDir() run := func(args ...string) string { t.Helper() cmd := exec.Command(gitx.Bin, args...) cmd.Dir = dir cmd.Env = append(cmd.Environ(), "GIT_AUTHOR_NAME=m", "GIT_AUTHOR_EMAIL=m@x", "GIT_COMMITTER_NAME=m", "GIT_COMMITTER_EMAIL=m@x") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } return strings.TrimSpace(string(out)) } write := func(body string) { if err := os.WriteFile(filepath.Join(dir, "config.go"), []byte(body), 0o644); err != nil { t.Fatal(err) } } run("init", "-q", "-b", "master") write(before) run("add", "-A") run("commit", "-qm", "before") oldBlob = run("rev-parse", "HEAD:config.go") if after != before { write(after) run("add", "-A") run("commit", "-qm", "after") } return dir, oldBlob } // Chapter 43.3's three outcomes, plus 43.5's fourth for when the original is gone. func TestResolve(t *testing.T) { const before = "package main\n\nfunc Load() error {\n\treturn nil\n}\n" t.Run("unchanged blob is exact", func(t *testing.T) { dir, blob := anchorRepo(t, before, before) got := Resolve(context.Background(), dir, Anchor{Path: "config.go", Line: 4, Blob: blob}, "HEAD") if got.Placement != Exact || got.Line != 4 { t.Errorf("got %+v, want exact at 4", got) } if got.Excerpt != "\treturn nil" { t.Errorf("excerpt = %q", got.Excerpt) } }) t.Run("line moved down", func(t *testing.T) { // Two lines added above, so line 4 becomes line 6. after := "package main\n\nimport \"os\"\n\nfunc Load() error {\n\treturn nil\n}\n" dir, blob := anchorRepo(t, before, after) got := Resolve(context.Background(), dir, Anchor{Path: "config.go", Line: 4, Blob: blob}, "HEAD") if got.Placement != Moved || got.Line != 6 { t.Errorf("got %+v, want moved to 6", got) } // The excerpt is what the commenter saw, not what is there now. if got.Excerpt != "\treturn nil" { t.Errorf("excerpt = %q", got.Excerpt) } }) t.Run("line rewritten is outdated", func(t *testing.T) { after := "package main\n\nfunc Load() error {\n\treturn fmt.Errorf(\"no\")\n}\n" dir, blob := anchorRepo(t, before, after) got := Resolve(context.Background(), dir, Anchor{Path: "config.go", Line: 4, Blob: blob}, "HEAD") if got.Placement != Outdated { t.Errorf("got %+v, want outdated", got) } // Chapter 43.4: never hide an outdated comment, and show the code it referred to. if got.Excerpt != "\treturn nil" { t.Errorf("the original line was lost: %q", got.Excerpt) } }) t.Run("file deleted is outdated", func(t *testing.T) { dir, blob := anchorRepo(t, before, before) cmd := exec.Command(gitx.Bin, "rm", "-q", "config.go") cmd.Dir = dir cmd.Run() cmd = exec.Command(gitx.Bin, "-c", "user.email=m@x", "-c", "user.name=m", "commit", "-qm", "gone") cmd.Dir = dir cmd.Run() got := Resolve(context.Background(), dir, Anchor{Path: "config.go", Line: 4, Blob: blob}, "HEAD") if got.Placement != Outdated { t.Errorf("got %+v, want outdated", got) } if got.Excerpt != "\treturn nil" { t.Errorf("the original line was lost: %q", got.Excerpt) } }) t.Run("blob gone degrades to lost", func(t *testing.T) { dir, _ := anchorRepo(t, before, before) got := Resolve(context.Background(), dir, Anchor{Path: "config.go", Line: 4, Blob: "0000000000000000000000000000000000000000"}, "HEAD") if got.Placement != Lost { t.Errorf("got %+v, want lost", got) } }) t.Run("no blob stored degrades to lost", func(t *testing.T) { dir, _ := anchorRepo(t, before, before) got := Resolve(context.Background(), dir, Anchor{Path: "config.go", Line: 4}, "HEAD") if got.Placement != Lost { t.Errorf("got %+v, want lost", got) } }) } func TestParseAnchor(t *testing.T) { a, ok := ParseAnchor("config.go:43") if !ok || a.Path != "config.go" || a.Line != 43 { t.Errorf("got %+v %v", a, ok) } if a.String() != "config.go:43" { t.Errorf("String() = %q", a.String()) } for _, bad := range []string{"", "config.go", "config.go:0", "config.go:-1", "config.go:x", "../../etc/passwd:1", ":4", "-rf:1"} { if _, ok := ParseAnchor(bad); ok { t.Errorf("ParseAnchor(%q) was accepted", bad) } } }