github.com/huiliang/nomad@v0.2.1-0.20151124023127-7a8b664699ff/client/driver/spawn/spawn_test.go (about)

     1  package spawn
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"os/exec"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  )
    12  
    13  func TestMain(m *testing.M) {
    14  	switch os.Getenv("TEST_MAIN") {
    15  	case "app":
    16  		appMain()
    17  	default:
    18  		os.Exit(m.Run())
    19  	}
    20  }
    21  
    22  func appMain() {
    23  	if len(os.Args) < 2 {
    24  		fmt.Fprintln(os.Stderr, "no command provided")
    25  		os.Exit(1)
    26  	}
    27  	switch cmd := os.Args[1]; cmd {
    28  	case "echo":
    29  		fmt.Println(strings.Join(os.Args[2:], " "))
    30  	case "sleep":
    31  		if len(os.Args) != 3 {
    32  			fmt.Fprintln(os.Stderr, "expected 3 args")
    33  			os.Exit(1)
    34  		}
    35  		dur, err := time.ParseDuration(os.Args[2])
    36  		if err != nil {
    37  			fmt.Fprintf(os.Stderr, "could not parse sleep time: %v", err)
    38  			os.Exit(1)
    39  		}
    40  		time.Sleep(dur)
    41  	default:
    42  		fmt.Fprintln(os.Stderr, "unknown command:", cmd)
    43  		os.Exit(1)
    44  	}
    45  }
    46  
    47  func TestSpawn_NoCmd(t *testing.T) {
    48  	t.Parallel()
    49  	tempFile := tempFileName(t)
    50  	defer os.Remove(tempFile)
    51  
    52  	spawn := NewSpawner(tempFile)
    53  	if err := spawn.Spawn(nil); err == nil {
    54  		t.Fatalf("Spawn() with no user command should fail")
    55  	}
    56  }
    57  
    58  func TestSpawn_InvalidCmd(t *testing.T) {
    59  	t.Parallel()
    60  	tempFile := tempFileName(t)
    61  	defer os.Remove(tempFile)
    62  
    63  	spawn := NewSpawner(tempFile)
    64  	spawn.SetCommand(exec.Command("foo")) // non-existent command
    65  	if err := spawn.Spawn(nil); err == nil {
    66  		t.Fatalf("Spawn() with an invalid command should fail")
    67  	}
    68  }
    69  
    70  func TestSpawn_SetsLogs(t *testing.T) {
    71  	t.Parallel()
    72  	tempFile := tempFileName(t)
    73  	defer os.Remove(tempFile)
    74  
    75  	spawn := NewSpawner(tempFile)
    76  	exp := "foo"
    77  	spawn.SetCommand(testCommand("echo", exp))
    78  
    79  	// Create file for stdout.
    80  	stdout := tempFileName(t)
    81  	defer os.Remove(stdout)
    82  	spawn.SetLogs(&Logs{Stdout: stdout})
    83  
    84  	if err := spawn.Spawn(nil); err != nil {
    85  		t.Fatalf("Spawn() failed: %v", err)
    86  	}
    87  
    88  	if res := spawn.Wait(); res.ExitCode != 0 && res.Err != nil {
    89  		t.Fatalf("Wait() returned %v, %v; want 0, nil", res.ExitCode, res.Err)
    90  	}
    91  
    92  	stdout2, err := os.Open(stdout)
    93  	if err != nil {
    94  		t.Fatalf("Open() failed: %v", err)
    95  	}
    96  
    97  	data, err := ioutil.ReadAll(stdout2)
    98  	if err != nil {
    99  		t.Fatalf("ReadAll() failed: %v", err)
   100  	}
   101  
   102  	act := strings.TrimSpace(string(data))
   103  	if act != exp {
   104  		t.Fatalf("Unexpected data written to stdout; got %v; want %v", act, exp)
   105  	}
   106  }
   107  
   108  func TestSpawn_Callback(t *testing.T) {
   109  	t.Parallel()
   110  	tempFile := tempFileName(t)
   111  	defer os.Remove(tempFile)
   112  
   113  	spawn := NewSpawner(tempFile)
   114  	spawn.SetCommand(testCommand("sleep", "1s"))
   115  
   116  	called := false
   117  	cbErr := fmt.Errorf("ERROR CB")
   118  	cb := func(_ int) error {
   119  		called = true
   120  		return cbErr
   121  	}
   122  
   123  	if err := spawn.Spawn(cb); err == nil {
   124  		t.Fatalf("Spawn(%#v) should have errored; want %v", cb, cbErr)
   125  	}
   126  
   127  	if !called {
   128  		t.Fatalf("Spawn(%#v) didn't call callback", cb)
   129  	}
   130  }
   131  
   132  func TestSpawn_ParentWaitExited(t *testing.T) {
   133  	t.Parallel()
   134  	tempFile := tempFileName(t)
   135  	defer os.Remove(tempFile)
   136  
   137  	spawn := NewSpawner(tempFile)
   138  	spawn.SetCommand(testCommand("echo", "foo"))
   139  	if err := spawn.Spawn(nil); err != nil {
   140  		t.Fatalf("Spawn() failed %v", err)
   141  	}
   142  
   143  	time.Sleep(1 * time.Second)
   144  
   145  	if res := spawn.Wait(); res.ExitCode != 0 && res.Err != nil {
   146  		t.Fatalf("Wait() returned %v, %v; want 0, nil", res.ExitCode, res.Err)
   147  	}
   148  }
   149  
   150  func TestSpawn_ParentWait(t *testing.T) {
   151  	t.Parallel()
   152  	tempFile := tempFileName(t)
   153  	defer os.Remove(tempFile)
   154  
   155  	spawn := NewSpawner(tempFile)
   156  	spawn.SetCommand(testCommand("sleep", "2s"))
   157  	if err := spawn.Spawn(nil); err != nil {
   158  		t.Fatalf("Spawn() failed %v", err)
   159  	}
   160  
   161  	if res := spawn.Wait(); res.ExitCode != 0 && res.Err != nil {
   162  		t.Fatalf("Wait() returned %v, %v; want 0, nil", res.ExitCode, res.Err)
   163  	}
   164  }
   165  
   166  func TestSpawn_NonParentWaitExited(t *testing.T) {
   167  	t.Parallel()
   168  	tempFile := tempFileName(t)
   169  	defer os.Remove(tempFile)
   170  
   171  	spawn := NewSpawner(tempFile)
   172  	spawn.SetCommand(testCommand("echo", "foo"))
   173  	if err := spawn.Spawn(nil); err != nil {
   174  		t.Fatalf("Spawn() failed %v", err)
   175  	}
   176  
   177  	time.Sleep(1 * time.Second)
   178  
   179  	// Force the wait to assume non-parent.
   180  	spawn.SpawnPpid = 0
   181  	if res := spawn.Wait(); res.ExitCode != 0 && res.Err != nil {
   182  		t.Fatalf("Wait() returned %v, %v; want 0, nil", res.ExitCode, res.Err)
   183  	}
   184  }
   185  
   186  func TestSpawn_NonParentWait(t *testing.T) {
   187  	t.Parallel()
   188  	tempFile := tempFileName(t)
   189  	defer os.Remove(tempFile)
   190  
   191  	spawn := NewSpawner(tempFile)
   192  	spawn.SetCommand(testCommand("sleep", "2s"))
   193  	if err := spawn.Spawn(nil); err != nil {
   194  		t.Fatalf("Spawn() failed %v", err)
   195  	}
   196  
   197  	// Need to wait on the spawner, otherwise it becomes a zombie and the test
   198  	// only finishes after the init process cleans it. This speeds that up.
   199  	go func() {
   200  		time.Sleep(3 * time.Second)
   201  		if _, err := spawn.spawn.Wait(); err != nil {
   202  			t.FailNow()
   203  		}
   204  	}()
   205  
   206  	// Force the wait to assume non-parent.
   207  	spawn.SpawnPpid = 0
   208  	if res := spawn.Wait(); res.ExitCode != 0 && res.Err != nil {
   209  		t.Fatalf("Wait() returned %v, %v; want 0, nil", res.ExitCode, res.Err)
   210  	}
   211  }
   212  
   213  func TestSpawn_DeadSpawnDaemon_Parent(t *testing.T) {
   214  	t.Parallel()
   215  	tempFile := tempFileName(t)
   216  	defer os.Remove(tempFile)
   217  
   218  	var spawnPid int
   219  	cb := func(pid int) error {
   220  		spawnPid = pid
   221  		return nil
   222  	}
   223  
   224  	spawn := NewSpawner(tempFile)
   225  	spawn.SetCommand(testCommand("sleep", "5s"))
   226  	if err := spawn.Spawn(cb); err != nil {
   227  		t.Fatalf("Spawn() errored: %v", err)
   228  	}
   229  
   230  	proc, err := os.FindProcess(spawnPid)
   231  	if err != nil {
   232  		t.FailNow()
   233  	}
   234  
   235  	if err := proc.Kill(); err != nil {
   236  		t.FailNow()
   237  	}
   238  
   239  	if _, err := proc.Wait(); err != nil {
   240  		t.FailNow()
   241  	}
   242  
   243  	if res := spawn.Wait(); res.Err == nil {
   244  		t.Fatalf("Wait() should have failed: %v", res.Err)
   245  	}
   246  }
   247  
   248  func TestSpawn_DeadSpawnDaemon_NonParent(t *testing.T) {
   249  	t.Parallel()
   250  	tempFile := tempFileName(t)
   251  	defer os.Remove(tempFile)
   252  
   253  	var spawnPid int
   254  	cb := func(pid int) error {
   255  		spawnPid = pid
   256  		return nil
   257  	}
   258  
   259  	spawn := NewSpawner(tempFile)
   260  	spawn.SetCommand(testCommand("sleep", "2s"))
   261  	if err := spawn.Spawn(cb); err != nil {
   262  		t.Fatalf("Spawn() errored: %v", err)
   263  	}
   264  
   265  	proc, err := os.FindProcess(spawnPid)
   266  	if err != nil {
   267  		t.FailNow()
   268  	}
   269  
   270  	if err := proc.Kill(); err != nil {
   271  		t.FailNow()
   272  	}
   273  
   274  	if _, err := proc.Wait(); err != nil {
   275  		t.FailNow()
   276  	}
   277  
   278  	// Force the wait to assume non-parent.
   279  	spawn.SpawnPpid = 0
   280  	if res := spawn.Wait(); res.Err == nil {
   281  		t.Fatalf("Wait() should have failed: %v", res.Err)
   282  	}
   283  }
   284  
   285  func TestSpawn_Valid_TaskRunning(t *testing.T) {
   286  	t.Parallel()
   287  	tempFile := tempFileName(t)
   288  	defer os.Remove(tempFile)
   289  
   290  	spawn := NewSpawner(tempFile)
   291  	spawn.SetCommand(testCommand("sleep", "2s"))
   292  	if err := spawn.Spawn(nil); err != nil {
   293  		t.Fatalf("Spawn() failed %v", err)
   294  	}
   295  
   296  	if err := spawn.Valid(); err != nil {
   297  		t.Fatalf("Valid() failed: %v", err)
   298  	}
   299  
   300  	if res := spawn.Wait(); res.Err != nil {
   301  		t.Fatalf("Wait() failed: %v", res.Err)
   302  	}
   303  }
   304  
   305  func TestSpawn_Valid_TaskExit_ExitCode(t *testing.T) {
   306  	t.Parallel()
   307  	tempFile := tempFileName(t)
   308  	defer os.Remove(tempFile)
   309  
   310  	spawn := NewSpawner(tempFile)
   311  	spawn.SetCommand(testCommand("echo", "foo"))
   312  	if err := spawn.Spawn(nil); err != nil {
   313  		t.Fatalf("Spawn() failed %v", err)
   314  	}
   315  
   316  	if res := spawn.Wait(); res.Err != nil {
   317  		t.Fatalf("Wait() failed: %v", res.Err)
   318  	}
   319  
   320  	if err := spawn.Valid(); err != nil {
   321  		t.Fatalf("Valid() failed: %v", err)
   322  	}
   323  }
   324  
   325  func TestSpawn_Valid_TaskExit_NoExitCode(t *testing.T) {
   326  	t.Parallel()
   327  	tempFile := tempFileName(t)
   328  	defer os.Remove(tempFile)
   329  
   330  	spawn := NewSpawner(tempFile)
   331  	spawn.SetCommand(testCommand("echo", "foo"))
   332  	if err := spawn.Spawn(nil); err != nil {
   333  		t.Fatalf("Spawn() failed %v", err)
   334  	}
   335  
   336  	if res := spawn.Wait(); res.Err != nil {
   337  		t.Fatalf("Wait() failed: %v", res.Err)
   338  	}
   339  
   340  	// Delete the file so that it can't find the exit code.
   341  	os.Remove(tempFile)
   342  
   343  	if err := spawn.Valid(); err == nil {
   344  		t.Fatalf("Valid() should have failed")
   345  	}
   346  }
   347  
   348  func tempFileName(t *testing.T) string {
   349  	f, err := ioutil.TempFile("", "")
   350  	if err != nil {
   351  		t.Fatalf("TempFile() failed")
   352  	}
   353  	defer f.Close()
   354  	return f.Name()
   355  }
   356  
   357  func testCommand(args ...string) *exec.Cmd {
   358  	cmd := exec.Command(os.Args[0], args...)
   359  	cmd.Env = append(os.Environ(), "TEST_MAIN=app")
   360  	return cmd
   361  }