github.com/stevenmatthewt/agent@v3.5.4+incompatible/bootstrap/shell/shell_test.go (about) 1 package shell_test 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "testing" 14 "time" 15 16 "github.com/buildkite/agent/bootstrap/shell" 17 "github.com/buildkite/bintest" 18 ) 19 20 func TestRunAndCaptureWithTTY(t *testing.T) { 21 sshKeygen, err := bintest.CompileProxy("ssh-keygen") 22 if err != nil { 23 t.Fatal(err) 24 } 25 defer sshKeygen.Close() 26 27 sh := newShellForTest(t) 28 sh.PTY = true 29 30 go func() { 31 call := <-sshKeygen.Ch 32 fmt.Fprintln(call.Stdout, "Llama party! 🎉") 33 call.Exit(0) 34 }() 35 36 actual, err := sh.RunAndCapture(sshKeygen.Path, "-f", "my_hosts", "-F", "llamas.com") 37 if err != nil { 38 t.Error(err) 39 } 40 41 if expected := "Llama party! 🎉"; string(actual) != expected { 42 t.Fatalf("Expected %q, got %q", expected, actual) 43 } 44 } 45 46 func TestRun(t *testing.T) { 47 sshKeygen, err := bintest.CompileProxy("ssh-keygen") 48 if err != nil { 49 t.Fatal(err) 50 } 51 defer sshKeygen.Close() 52 53 out := &bytes.Buffer{} 54 55 sh := newShellForTest(t) 56 sh.PTY = false 57 sh.Writer = out 58 sh.Logger = &shell.WriterLogger{Writer: out, Ansi: false} 59 60 go func() { 61 call := <-sshKeygen.Ch 62 fmt.Fprintln(call.Stdout, "Llama party! 🎉") 63 call.Exit(0) 64 }() 65 66 if err = sh.Run(sshKeygen.Path, "-f", "my_hosts", "-F", "llamas.com"); err != nil { 67 t.Fatal(err) 68 } 69 70 actual := out.String() 71 72 promptPrefix := "$" 73 if runtime.GOOS == "windows" { 74 promptPrefix = ">" 75 } 76 77 if expected := promptPrefix + " " + sshKeygen.Path + " -f my_hosts -F llamas.com\nLlama party! 🎉\n"; actual != expected { 78 t.Fatalf("Expected %q, got %q", expected, actual) 79 } 80 } 81 82 func TestDefaultWorkingDirFromSystem(t *testing.T) { 83 sh, err := shell.New() 84 if err != nil { 85 t.Fatal(err) 86 } 87 88 currentWd, _ := os.Getwd() 89 if actual := sh.Getwd(); actual != currentWd { 90 t.Fatalf("Expected working dir %q, got %q", currentWd, actual) 91 } 92 } 93 94 func TestWorkingDir(t *testing.T) { 95 tempDir, err := ioutil.TempDir("", "shelltest") 96 if err != nil { 97 t.Fatal(err) 98 } 99 defer os.RemoveAll(tempDir) 100 101 // macos has a symlinked temp dir 102 if runtime.GOOS == "darwin" { 103 tempDir, _ = filepath.EvalSymlinks(tempDir) 104 } 105 106 dirs := []string{tempDir, "my", "test", "dirs"} 107 108 if err := os.MkdirAll(filepath.Join(dirs...), 0700); err != nil { 109 t.Fatal(err) 110 } 111 112 currentWd, _ := os.Getwd() 113 114 sh, err := shell.New() 115 sh.Logger = shell.DiscardLogger 116 117 if err != nil { 118 t.Fatal(err) 119 } 120 121 for idx := range dirs { 122 dir := filepath.Join(dirs[0 : idx+1]...) 123 124 if err := sh.Chdir(dir); err != nil { 125 t.Fatal(err) 126 } 127 128 if actual := sh.Getwd(); actual != dir { 129 t.Fatalf("Expected working dir %q, got %q", dir, actual) 130 } 131 132 var out string 133 134 // there is no pwd for windows, and getting it requires using a shell builtin 135 if runtime.GOOS == "windows" { 136 out, err = sh.RunAndCapture("cmd", "/c", "echo", "%cd%") 137 if err != nil { 138 t.Fatal(err) 139 } 140 } else { 141 out, err = sh.RunAndCapture("pwd") 142 if err != nil { 143 t.Fatal(err) 144 } 145 } 146 147 if actual := out; actual != dir { 148 t.Fatalf("Expected working dir (from pwd command) %q, got %q", dir, actual) 149 } 150 } 151 152 afterWd, _ := os.Getwd() 153 if afterWd != currentWd { 154 t.Fatalf("Expected working dir to be the same as before shell commands ran") 155 } 156 } 157 158 func TestLockFileRetriesAndTimesOut(t *testing.T) { 159 dir, err := ioutil.TempDir("", "shelltest") 160 if err != nil { 161 t.Fatal(err) 162 } 163 defer os.RemoveAll(dir) 164 165 sh := newShellForTest(t) 166 sh.Logger = shell.DiscardLogger 167 168 lockPath := filepath.Join(dir, "my.lock") 169 170 // acquire a lock in another process 171 cmd, err := acquireLockInOtherProcess(lockPath) 172 if err != nil { 173 t.Fatal(err) 174 } 175 176 defer cmd.Process.Kill() 177 178 // acquire lock 179 _, err = sh.LockFile(lockPath, time.Second*2) 180 if err != context.DeadlineExceeded { 181 t.Fatalf("Expected DeadlineExceeded error, got %v", err) 182 } 183 } 184 185 func acquireLockInOtherProcess(lockfile string) (*exec.Cmd, error) { 186 cmd := exec.Command(os.Args[0], "-test.run=TestAcquiringLockHelperProcess", "--", lockfile) 187 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 188 189 err := cmd.Start() 190 if err != nil { 191 return cmd, err 192 } 193 194 // wait for the above process to get a lock 195 for { 196 if _, err = os.Stat(lockfile); os.IsNotExist(err) { 197 time.Sleep(time.Millisecond * 10) 198 continue 199 } 200 break 201 } 202 203 return cmd, nil 204 } 205 206 // TestAcquiringLockHelperProcess isn't a real test. It's used as a helper process 207 func TestAcquiringLockHelperProcess(t *testing.T) { 208 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 209 return 210 } 211 212 fileName := os.Args[len(os.Args)-1] 213 sh := newShellForTest(t) 214 215 log.Printf("Locking %s", fileName) 216 if _, err := sh.LockFile(fileName, time.Second*10); err != nil { 217 os.Exit(1) 218 } 219 220 log.Printf("Acquired lock %s", fileName) 221 c := make(chan struct{}) 222 <-c 223 } 224 225 func newShellForTest(t *testing.T) *shell.Shell { 226 sh, err := shell.New() 227 if err != nil { 228 t.Fatal(err) 229 } 230 sh.Logger = shell.DiscardLogger 231 return sh 232 }