diff --git a/client/driver/spawn/spawn.go b/client/driver/spawn/spawn.go index 32a27e11c..5c7795f38 100644 --- a/client/driver/spawn/spawn.go +++ b/client/driver/spawn/spawn.go @@ -73,10 +73,10 @@ func (s *Spawner) Spawn(cb func(pid int) error) error { } exitFile, err := os.OpenFile(s.StateFile, os.O_CREATE|os.O_WRONLY, 0666) - defer exitFile.Close() if err != nil { return fmt.Errorf("Error opening file to store exit status: %v", err) } + defer exitFile.Close() config, err := s.spawnConfig() if err != nil { @@ -87,17 +87,17 @@ func (s *Spawner) Spawn(cb func(pid int) error) error { // Capture stdout spawnStdout, err := spawn.StdoutPipe() - defer spawnStdout.Close() if err != nil { return fmt.Errorf("Failed to capture spawn-daemon stdout: %v", err) } + defer spawnStdout.Close() // Capture stdin. spawnStdin, err := spawn.StdinPipe() - defer spawnStdin.Close() if err != nil { return fmt.Errorf("Failed to capture spawn-daemon stdin: %v", err) } + defer spawnStdin.Close() if err := spawn.Start(); err != nil { return fmt.Errorf("Failed to call spawn-daemon on nomad executable: %v", err) @@ -264,10 +264,10 @@ func (s *Spawner) pollWait() *structs.WaitResult { // returns an error if the file can't be read. func (s *Spawner) readExitCode() *structs.WaitResult { f, err := os.Open(s.StateFile) - defer f.Close() if err != nil { return structs.NewWaitResult(-1, 0, fmt.Errorf("Failed to open %v to read exit code: %v", s.StateFile, err)) } + defer f.Close() stat, err := f.Stat() if err != nil { @@ -289,7 +289,7 @@ func (s *Spawner) readExitCode() *structs.WaitResult { // Valid checks that the state of the Spawner is valid and that a subsequent // Wait could be called. This is useful to call when reopening a Spawner -// throught client restarts. If Valid a nil error is returned. +// through client restarts. If Valid a nil error is returned. func (s *Spawner) Valid() error { // If the spawner is still alive, then the task is running and we can wait // on it. diff --git a/client/driver/spawn/spawn_test.go b/client/driver/spawn/spawn_test.go index eb013db0d..62323bf83 100644 --- a/client/driver/spawn/spawn_test.go +++ b/client/driver/spawn/spawn_test.go @@ -5,64 +5,81 @@ import ( "io/ioutil" "os" "os/exec" - "runtime" "strings" "testing" "time" ) -func TestSpawn_NoCmd(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") +func TestMain(m *testing.M) { + switch os.Getenv("TEST_MAIN") { + case "app": + appMain() + default: + os.Exit(m.Run()) } - defer os.Remove(f.Name()) +} - spawn := NewSpawner(f.Name()) +func appMain() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "no command provided") + os.Exit(1) + } + switch cmd := os.Args[1]; cmd { + case "echo": + fmt.Println(strings.Join(os.Args[2:], " ")) + case "sleep": + if len(os.Args) != 3 { + fmt.Fprintln(os.Stderr, "expected 3 args") + os.Exit(1) + } + dur, err := time.ParseDuration(os.Args[2]) + if err != nil { + fmt.Fprintf(os.Stderr, "could not parse sleep time: %v", err) + os.Exit(1) + } + time.Sleep(dur) + default: + fmt.Fprintln(os.Stderr, "unknown command:", cmd) + os.Exit(1) + } +} + +func TestSpawn_NoCmd(t *testing.T) { + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) + + spawn := NewSpawner(tempFile) if err := spawn.Spawn(nil); err == nil { t.Fatalf("Spawn() with no user command should fail") } } func TestSpawn_InvalidCmd(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("foo")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(exec.Command("foo")) // non-existent command if err := spawn.Spawn(nil); err == nil { - t.Fatalf("Spawn() with no invalid command should fail") + t.Fatalf("Spawn() with an invalid command should fail") } } func TestSpawn_SetsLogs(t *testing.T) { - // TODO: Figure out why this test fails. If the spawn-daemon directly writes - // to the opened stdout file it works but not the user command. Maybe a - // flush issue? - if runtime.GOOS == "windows" { - t.Skip("Test fails on windows; unknown reason. Skipping") - } + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) - - spawn := NewSpawner(f.Name()) + spawn := NewSpawner(tempFile) exp := "foo" - spawn.SetCommand(exec.Command("echo", exp)) + spawn.SetCommand(testCommand("echo", exp)) // Create file for stdout. - stdout, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(stdout.Name()) - spawn.SetLogs(&Logs{Stdout: stdout.Name()}) + stdout := tempFileName(t) + defer os.Remove(stdout) + spawn.SetLogs(&Logs{Stdout: stdout}) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed: %v", err) @@ -72,7 +89,7 @@ func TestSpawn_SetsLogs(t *testing.T) { t.Fatalf("Wait() returned %v, %v; want 0, nil", res.ExitCode, res.Err) } - stdout2, err := os.Open(stdout.Name()) + stdout2, err := os.Open(stdout) if err != nil { t.Fatalf("Open() failed: %v", err) } @@ -89,14 +106,12 @@ func TestSpawn_SetsLogs(t *testing.T) { } func TestSpawn_Callback(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("sleep", "1")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("sleep", "1s")) called := false cbErr := fmt.Errorf("ERROR CB") @@ -115,14 +130,12 @@ func TestSpawn_Callback(t *testing.T) { } func TestSpawn_ParentWaitExited(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("echo", "foo")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("echo", "foo")) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed %v", err) } @@ -135,14 +148,12 @@ func TestSpawn_ParentWaitExited(t *testing.T) { } func TestSpawn_ParentWait(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("sleep", "2")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("sleep", "2s")) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed %v", err) } @@ -153,14 +164,12 @@ func TestSpawn_ParentWait(t *testing.T) { } func TestSpawn_NonParentWaitExited(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("echo", "foo")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("echo", "foo")) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed %v", err) } @@ -175,14 +184,12 @@ func TestSpawn_NonParentWaitExited(t *testing.T) { } func TestSpawn_NonParentWait(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("sleep", "2")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("sleep", "2s")) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed %v", err) } @@ -204,11 +211,9 @@ func TestSpawn_NonParentWait(t *testing.T) { } func TestSpawn_DeadSpawnDaemon_Parent(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) var spawnPid int cb := func(pid int) error { @@ -216,8 +221,8 @@ func TestSpawn_DeadSpawnDaemon_Parent(t *testing.T) { return nil } - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("sleep", "5")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("sleep", "5s")) if err := spawn.Spawn(cb); err != nil { t.Fatalf("Spawn() errored: %v", err) } @@ -241,11 +246,9 @@ func TestSpawn_DeadSpawnDaemon_Parent(t *testing.T) { } func TestSpawn_DeadSpawnDaemon_NonParent(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) var spawnPid int cb := func(pid int) error { @@ -253,8 +256,8 @@ func TestSpawn_DeadSpawnDaemon_NonParent(t *testing.T) { return nil } - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("sleep", "2")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("sleep", "2s")) if err := spawn.Spawn(cb); err != nil { t.Fatalf("Spawn() errored: %v", err) } @@ -280,14 +283,12 @@ func TestSpawn_DeadSpawnDaemon_NonParent(t *testing.T) { } func TestSpawn_Valid_TaskRunning(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("sleep", "2")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("sleep", "2s")) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed %v", err) } @@ -302,14 +303,12 @@ func TestSpawn_Valid_TaskRunning(t *testing.T) { } func TestSpawn_Valid_TaskExit_ExitCode(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } - defer os.Remove(f.Name()) + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("echo", "foo")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("echo", "foo")) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed %v", err) } @@ -324,13 +323,12 @@ func TestSpawn_Valid_TaskExit_ExitCode(t *testing.T) { } func TestSpawn_Valid_TaskExit_NoExitCode(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile() failed") - } + t.Parallel() + tempFile := tempFileName(t) + defer os.Remove(tempFile) - spawn := NewSpawner(f.Name()) - spawn.SetCommand(exec.Command("echo", "foo")) + spawn := NewSpawner(tempFile) + spawn.SetCommand(testCommand("echo", "foo")) if err := spawn.Spawn(nil); err != nil { t.Fatalf("Spawn() failed %v", err) } @@ -340,9 +338,24 @@ func TestSpawn_Valid_TaskExit_NoExitCode(t *testing.T) { } // Delete the file so that it can't find the exit code. - os.Remove(f.Name()) + os.Remove(tempFile) if err := spawn.Valid(); err == nil { t.Fatalf("Valid() should have failed") } } + +func tempFileName(t *testing.T) string { + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("TempFile() failed") + } + defer f.Close() + return f.Name() +} + +func testCommand(args ...string) *exec.Cmd { + cmd := exec.Command(os.Args[0], args...) + cmd.Env = append(os.Environ(), "TEST_MAIN=app") + return cmd +}