364 lines
9.0 KiB
Go
364 lines
9.0 KiB
Go
package gitcmd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestVersion(t *testing.T) {
|
|
ver, err := Version(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ver == "" {
|
|
t.Error("expected version string, got empty")
|
|
}
|
|
t.Logf("git version: %s", ver)
|
|
}
|
|
|
|
func TestIsSafeValue(t *testing.T) {
|
|
cases := []struct {
|
|
arg string
|
|
want bool
|
|
}{
|
|
{"", true},
|
|
{"HEAD", true},
|
|
{"main", true},
|
|
{"file.txt", true},
|
|
{"-rf", false},
|
|
{"--evil", false},
|
|
{"--exec=foo", false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := isSafeValue(tc.arg); got != tc.want {
|
|
t.Errorf("isSafeValue(%q) = %v, want %v", tc.arg, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedArg(t *testing.T) {
|
|
cases := []struct {
|
|
arg string
|
|
want bool
|
|
}{
|
|
{"log", true},
|
|
{"", true},
|
|
{"--pretty", true},
|
|
{"--format=%H%x1f%h", true},
|
|
{"--format=%(refname:short)", true},
|
|
{"foo(bar)", true},
|
|
{"HEAD", true},
|
|
{"foo;bar", false},
|
|
{"foo`bar", false},
|
|
{"$(evil)", false},
|
|
{"foo|bar", false},
|
|
{"foo&bar", false},
|
|
{"foo\nbar", false},
|
|
{"foo\rbar", false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := isTrustedArg(tc.arg); got != tc.want {
|
|
t.Errorf("isTrustedArg(%q) = %v, want %v", tc.arg, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommandRejectsDynamicOption(t *testing.T) {
|
|
_, _, err := NewCommand("show").
|
|
AddDynamicArguments("--evil-option").
|
|
RunStdString(context.Background())
|
|
|
|
if err == nil {
|
|
t.Fatal("expected error for dynamic argument starting with '-', got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "cannot start with '-'") {
|
|
t.Errorf("unexpected error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCommandDashesAndList(t *testing.T) {
|
|
cmd := NewCommand("ls-tree", "HEAD").AddDashesAndList("foo", "bar")
|
|
|
|
if len(cmd.args) != 5 {
|
|
t.Fatalf("expected 5 args, got %d: %v", len(cmd.args), cmd.args)
|
|
}
|
|
if cmd.args[2] != "--" {
|
|
t.Errorf("expected '--' at index 2, got %q", cmd.args[2])
|
|
}
|
|
}
|
|
|
|
func TestCommandAddDashesAndListKeepsLeadingDash(t *testing.T) {
|
|
cmd := NewCommand("ls-tree", "HEAD").AddDashesAndList("-rf", "bar")
|
|
want := []string{"ls-tree", "HEAD", "--", "-rf", "bar"}
|
|
if len(cmd.args) != len(want) {
|
|
t.Fatalf("args = %v, want %v", cmd.args, want)
|
|
}
|
|
for i := range want {
|
|
if cmd.args[i] != want[i] {
|
|
t.Errorf("args[%d] = %q, want %q", i, cmd.args[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommandAdd(t *testing.T) {
|
|
c := NewCommand("log").Add("--format=%H", "HEAD")
|
|
want := []string{"log", "--format=%H", "HEAD"}
|
|
if len(c.args) != len(want) {
|
|
t.Fatalf("args = %v, want %v", c.args, want)
|
|
}
|
|
for i := range want {
|
|
if c.args[i] != want[i] {
|
|
t.Errorf("args[%d] = %q, want %q", i, c.args[i], want[i])
|
|
}
|
|
}
|
|
if len(c.preErrs) != 0 {
|
|
t.Errorf("unexpected preErrs: %v", c.preErrs)
|
|
}
|
|
}
|
|
|
|
func TestCommandAddShortCircuitsOnPreErr(t *testing.T) {
|
|
c := NewCommand("log")
|
|
c.AddDynamicArguments("--bad")
|
|
c.Add("--format=%H")
|
|
|
|
if len(c.args) != 1 || c.args[0] != "log" {
|
|
t.Errorf("args = %v, want [log]", c.args)
|
|
}
|
|
if len(c.preErrs) == 0 {
|
|
t.Error("expected a preErr to be recorded")
|
|
}
|
|
}
|
|
|
|
func TestCommandAddOptionValues(t *testing.T) {
|
|
|
|
c := NewCommand("show").AddOptionValues("--git-dir", "/some/path")
|
|
want := []string{"show", "--git-dir", "/some/path"}
|
|
if len(c.args) != len(want) {
|
|
t.Fatalf("args = %v, want %v", c.args, want)
|
|
}
|
|
for i := range want {
|
|
if c.args[i] != want[i] {
|
|
t.Errorf("args[%d] = %q, want %q", i, c.args[i], want[i])
|
|
}
|
|
}
|
|
|
|
c2 := NewCommand("show").AddOptionValues("git-dir", "v")
|
|
if len(c2.args) != 1 || len(c2.preErrs) == 0 {
|
|
t.Errorf("invalid option: args=%v preErrs=%v", c2.args, c2.preErrs)
|
|
}
|
|
|
|
c3 := NewCommand("show").AddOptionValues("--git-dir", "-x")
|
|
if len(c3.args) != 2 || len(c3.preErrs) == 0 {
|
|
t.Errorf("leading-dash value: args=%v preErrs=%v", c3.args, c3.preErrs)
|
|
}
|
|
|
|
if c3.args[1] != "--git-dir" {
|
|
t.Errorf("expected --git-dir appended, got args=%v", c3.args)
|
|
}
|
|
}
|
|
|
|
func TestCommandPreErrsAccumulate(t *testing.T) {
|
|
c := NewCommand("show", "foo|bar")
|
|
c.AddDynamicArguments("--evil")
|
|
|
|
_, _, err := c.RunStdBytes(context.Background())
|
|
if err == nil {
|
|
t.Fatal("expected joined preErr, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "foo|bar") {
|
|
t.Errorf("err should mention foo|bar: %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "--evil") {
|
|
t.Errorf("err should mention --evil: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCommandWithDir(t *testing.T) {
|
|
f := newFixture(t)
|
|
out, _, err := NewCommand("rev-parse", "--is-bare-repository").
|
|
WithDir(f.repo.Path).
|
|
RunStdString(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := strings.TrimSpace(out); got != "true" {
|
|
t.Errorf("is-bare-repository = %q, want true", got)
|
|
}
|
|
}
|
|
|
|
func TestCommandWithEnv(t *testing.T) {
|
|
out, _, err := NewCommand("var", "GIT_COMMITTER_IDENT").
|
|
WithEnv("GIT_COMMITTER_NAME=TestUser", "GIT_COMMITTER_EMAIL=test@example.com").
|
|
RunStdString(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, "TestUser <test@example.com>") {
|
|
t.Errorf("GIT_COMMITTER_IDENT = %q, want it to contain TestUser <test@example.com>", out)
|
|
}
|
|
}
|
|
|
|
func TestCommandWithStdin(t *testing.T) {
|
|
content := "hello world\n"
|
|
out, _, err := NewCommand("hash-object", "--stdin").
|
|
WithStdin(strings.NewReader(content)).
|
|
RunStdString(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := strings.TrimSpace(out)
|
|
|
|
h := sha1.New()
|
|
fmt.Fprintf(h, "blob %d\x00", len(content))
|
|
h.Write([]byte(content))
|
|
want := fmt.Sprintf("%x", h.Sum(nil))
|
|
if len(got) != 40 {
|
|
t.Errorf("hash-object output len = %d, want 40: %q", len(got), got)
|
|
}
|
|
if got != want {
|
|
t.Errorf("hash-object = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestCommandWithTimeout(t *testing.T) {
|
|
|
|
out, _, err := NewCommand("version").WithTimeout(30 * time.Second).RunStdString(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("generous timeout: %v", err)
|
|
}
|
|
if !strings.Contains(out, "git version") {
|
|
t.Errorf("version output = %q", out)
|
|
}
|
|
|
|
_, _, err = NewCommand("version").WithTimeout(1 * time.Nanosecond).RunStdBytes(context.Background())
|
|
if err == nil {
|
|
t.Error("expected error from expired timeout, got nil")
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
_, _, err = NewCommand("version").RunStdBytes(ctx)
|
|
if err == nil {
|
|
t.Error("expected error from cancelled context, got nil")
|
|
}
|
|
}
|
|
|
|
func TestCommandCmdString(t *testing.T) {
|
|
|
|
s := NewCommand("log", "--format=%H").CmdString()
|
|
if !strings.Contains(s, "log") || !strings.Contains(s, "--format=%H") {
|
|
t.Errorf("CmdString = %q", s)
|
|
}
|
|
|
|
s2 := NewCommand("ls-tree").AddDashesAndList("my file.txt").CmdString()
|
|
if !strings.Contains(s2, `"my file.txt"`) {
|
|
t.Errorf("expected quoted 'my file.txt' in %q", s2)
|
|
}
|
|
}
|
|
|
|
func TestCommandRunStdStringAndFailing(t *testing.T) {
|
|
|
|
out, stderr, err := NewCommand("version").RunStdString(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out, "git version") {
|
|
t.Errorf("stdout = %q", out)
|
|
}
|
|
if stderr != "" {
|
|
t.Errorf("stderr = %q, want empty", stderr)
|
|
}
|
|
|
|
_, stderr2, err := NewCommand("--bogus-flag").RunStdString(context.Background())
|
|
if err == nil {
|
|
t.Fatal("expected error from bogus flag")
|
|
}
|
|
if stderr2 == "" {
|
|
t.Error("expected non-empty stderr from bogus flag")
|
|
}
|
|
}
|
|
|
|
func TestCommandRun(t *testing.T) {
|
|
if err := NewCommand("version").Run(context.Background()); err != nil {
|
|
t.Fatalf("Run: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCommandRunStdBytes(t *testing.T) {
|
|
out, _, err := NewCommand("version").RunStdBytes(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(out) == 0 {
|
|
t.Error("expected non-empty stdout")
|
|
}
|
|
}
|
|
|
|
func TestCommandRunPiped(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
if err := NewCommand("version").RunPiped(context.Background(), &buf, io.Discard); err != nil {
|
|
t.Fatalf("RunPiped: %v", err)
|
|
}
|
|
if !strings.Contains(buf.String(), "git version") {
|
|
t.Errorf("piped stdout = %q", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestCommandAddRecordsPreErr(t *testing.T) {
|
|
c := NewCommand("log").Add("foo;bar")
|
|
if len(c.args) != 1 || c.args[0] != "log" {
|
|
t.Errorf("args = %v, want [log]", c.args)
|
|
}
|
|
if len(c.preErrs) != 1 {
|
|
t.Errorf("preErrs = %v, want exactly 1", c.preErrs)
|
|
}
|
|
}
|
|
|
|
func TestCommandRunPipedPreErr(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
err := NewCommand("show").AddDynamicArguments("--bad").
|
|
RunPiped(context.Background(), &buf, io.Discard)
|
|
if err == nil {
|
|
t.Fatal("expected preErr from RunPiped, got nil")
|
|
}
|
|
if buf.Len() != 0 {
|
|
t.Errorf("piped buffer = %q, want empty (command must not run)", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestExecutablePresent(t *testing.T) {
|
|
exe, err := Executable()
|
|
if err != nil {
|
|
t.Fatalf("Executable: %v", err)
|
|
}
|
|
if exe == "" {
|
|
t.Error("Executable returned empty path")
|
|
}
|
|
}
|
|
|
|
func TestSetExecutableHonored(t *testing.T) {
|
|
real, err := exec.LookPath("git")
|
|
if err != nil {
|
|
t.Skip("git not found")
|
|
}
|
|
SetExecutable(real)
|
|
exe, err := Executable()
|
|
if err != nil {
|
|
t.Fatalf("Executable: %v", err)
|
|
}
|
|
if exe != real {
|
|
t.Errorf("Executable = %q, want %q", exe, real)
|
|
}
|
|
|
|
if _, _, err := NewCommand("version").RunStdString(context.Background()); err != nil {
|
|
t.Errorf("version after SetExecutable: %v", err)
|
|
}
|
|
}
|