github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/subprocess_test.go (about)

     1  // Copyright 2012 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nin
    16  
    17  import (
    18  	"os"
    19  	"os/signal"
    20  	"runtime"
    21  	"testing"
    22  )
    23  
    24  func testCommand() string {
    25  	if runtime.GOOS == "windows" {
    26  		return "cmd /c dir \\"
    27  	}
    28  	return "ls /"
    29  }
    30  
    31  func newSubprocessSetTest(t *testing.T) *subprocessSet {
    32  	s := newSubprocessSet()
    33  	t.Cleanup(s.Clear)
    34  	return s
    35  }
    36  
    37  // Run a command that fails and emits to stderr.
    38  func TestSubprocessTest_BadCommandStderr(t *testing.T) {
    39  	subprocs := newSubprocessSetTest(t)
    40  	cmd := "bash -c foo"
    41  	if runtime.GOOS == "windows" {
    42  		cmd = "cmd /c ninja_no_such_command"
    43  	}
    44  	subproc := subprocs.Add(cmd, false)
    45  	if nil == subproc {
    46  		t.Fatal("expected different")
    47  	}
    48  
    49  	for !subproc.Done() {
    50  		// Pretend we discovered that stderr was ready for writing.
    51  		subprocs.DoWork()
    52  	}
    53  
    54  	// ExitFailure
    55  	want := 127
    56  	if runtime.GOOS == "windows" {
    57  		want = 1
    58  	}
    59  	if got := subproc.Finish(); got != want {
    60  		t.Fatal(got)
    61  	}
    62  	if got := subproc.GetOutput(); got == "" {
    63  		t.Fatal("expected error output")
    64  	}
    65  }
    66  
    67  // Run a command that does not exist
    68  func TestSubprocessTest_NoSuchCommand(t *testing.T) {
    69  	subprocs := newSubprocessSetTest(t)
    70  	subproc := subprocs.Add("ninja_no_such_command", false)
    71  	if nil == subproc {
    72  		t.Fatal("expected different")
    73  	}
    74  
    75  	for !subproc.Done() {
    76  		// Pretend we discovered that stderr was ready for writing.
    77  		subprocs.DoWork()
    78  	}
    79  
    80  	// ExitFailure
    81  	// 127 on posix, -1 on Windows.
    82  	want := 127 // Generated by /bin/sh.
    83  	if runtime.GOOS == "windows" {
    84  		want = -1
    85  	}
    86  	if got := subproc.Finish(); got != want {
    87  		t.Fatal(got)
    88  	}
    89  	/*
    90  		if got := subproc.GetOutput(); got != "" {
    91  			t.Fatalf("%q", got)
    92  		}
    93  		if runtime.GOOS == "windows" {
    94  			if "CreateProcess failed: The system cannot find the file specified.\n" != subproc.GetOutput() {
    95  				t.Fatal()
    96  			}
    97  		}
    98  	*/
    99  }
   100  
   101  func TestSubprocessTest_InterruptChild(t *testing.T) {
   102  	if runtime.GOOS == "windows" {
   103  		t.Skip("can't run on Windows")
   104  	}
   105  	subprocs := newSubprocessSetTest(t)
   106  	subproc := subprocs.Add("kill -INT $$", false)
   107  	if nil == subproc {
   108  		t.Fatal("expected different")
   109  	}
   110  
   111  	for !subproc.Done() {
   112  		subprocs.DoWork()
   113  	}
   114  
   115  	// ExitInterrupted
   116  	if got := subproc.Finish(); got != -1 {
   117  		t.Fatal(got)
   118  	}
   119  }
   120  
   121  func TestSubprocessTest_InterruptParent(t *testing.T) {
   122  	if runtime.GOOS == "windows" {
   123  		t.Skip("can't run on Windows")
   124  	}
   125  	t.Skip("TODO")
   126  	subprocs := newSubprocessSetTest(t)
   127  	c := make(chan os.Signal, 1)
   128  	go func() {
   129  		<-c
   130  		subprocs.Clear()
   131  	}()
   132  	signal.Notify(c, os.Interrupt)
   133  	defer signal.Reset(os.Interrupt)
   134  	subproc := subprocs.Add("kill -INT $PPID ; sleep 1", false)
   135  	if nil == subproc {
   136  		t.Fatal("expected different")
   137  	}
   138  
   139  	for !subproc.Done() {
   140  		if subprocs.DoWork() {
   141  			return
   142  		}
   143  	}
   144  
   145  	t.Fatal("We should have been interrupted")
   146  }
   147  
   148  func TestSubprocessTest_InterruptChildWithSigTerm(t *testing.T) {
   149  	if runtime.GOOS == "windows" {
   150  		t.Skip("can't run on Windows")
   151  	}
   152  	subprocs := newSubprocessSetTest(t)
   153  	subproc := subprocs.Add("kill -TERM $$", false)
   154  	if nil == subproc {
   155  		t.Fatal("expected different")
   156  	}
   157  
   158  	for !subproc.Done() {
   159  		subprocs.DoWork()
   160  	}
   161  
   162  	// TODO(maruel): ExitInterrupted
   163  	if got := subproc.Finish(); got != -1 {
   164  		t.Fatal(got)
   165  	}
   166  }
   167  
   168  func TestSubprocessTest_InterruptParentWithSigTerm(t *testing.T) {
   169  	if runtime.GOOS == "windows" {
   170  		t.Skip("can't run on Windows")
   171  	}
   172  	t.Skip("TODO")
   173  	subprocs := newSubprocessSetTest(t)
   174  	subproc := subprocs.Add("kill -TERM $PPID ; sleep 1", false)
   175  	if nil == subproc {
   176  		t.Fatal("expected different")
   177  	}
   178  
   179  	for !subproc.Done() {
   180  		if subprocs.DoWork() {
   181  			return
   182  		}
   183  	}
   184  
   185  	t.Fatal("We should have been interrupted")
   186  }
   187  
   188  func TestSubprocessTest_InterruptChildWithSigHup(t *testing.T) {
   189  	if runtime.GOOS == "windows" {
   190  		t.Skip("can't run on Windows")
   191  	}
   192  	subprocs := newSubprocessSetTest(t)
   193  	subproc := subprocs.Add("kill -HUP $$", false)
   194  	if nil == subproc {
   195  		t.Fatal("expected different")
   196  	}
   197  
   198  	for !subproc.Done() {
   199  		subprocs.DoWork()
   200  	}
   201  
   202  	// TODO(maruel): ExitInterrupted
   203  	if got := subproc.Finish(); got != -1 {
   204  		t.Fatal(got)
   205  	}
   206  }
   207  
   208  func TestSubprocessTest_InterruptParentWithSigHup(t *testing.T) {
   209  	t.Skip("TODO")
   210  	if runtime.GOOS == "windows" {
   211  		t.Skip("can't run on Windows")
   212  	}
   213  	subprocs := newSubprocessSetTest(t)
   214  	subproc := subprocs.Add("kill -HUP $PPID ; sleep 1", false)
   215  	if nil == subproc {
   216  		t.Fatal("expected different")
   217  	}
   218  
   219  	for !subproc.Done() {
   220  		if subprocs.DoWork() {
   221  			return
   222  		}
   223  	}
   224  
   225  	t.Fatal("We should have been interrupted")
   226  }
   227  
   228  func TestSubprocessTest_Console(t *testing.T) {
   229  	if runtime.GOOS == "windows" {
   230  		t.Skip("can't run on Windows")
   231  	}
   232  	t.Skip("TODO")
   233  	/*
   234  		// Skip test if we don't have the console ourselves.
   235  		// TODO(maruel): Sub-run with a fake pty?
   236  		if !isatty(0) || !isatty(1) || !isatty(2) {
   237  			t.Skip("need a real console to run this test")
   238  		}
   239  	*/
   240  	subprocs := newSubprocessSetTest(t)
   241  	// useConsole = true
   242  	subproc := subprocs.Add("test -t 0 -a -t 1 -a -t 2", true)
   243  	if nil == subproc {
   244  		t.Fatal("expected different")
   245  	}
   246  
   247  	for !subproc.Done() {
   248  		subprocs.DoWork()
   249  	}
   250  
   251  	if got := subproc.Finish(); got != ExitSuccess {
   252  		t.Fatal(got)
   253  	}
   254  }
   255  
   256  func TestSubprocessTest_SetWithSingle(t *testing.T) {
   257  	subprocs := newSubprocessSetTest(t)
   258  	subproc := subprocs.Add(testCommand(), false)
   259  	if subproc == nil {
   260  		t.Fatal("expected different")
   261  	}
   262  
   263  	for !subproc.Done() {
   264  		subprocs.DoWork()
   265  	}
   266  	if subproc.Finish() != ExitSuccess {
   267  		t.Fatal("expected equal")
   268  	}
   269  	if subproc.GetOutput() == "" {
   270  		t.Fatal("expected different")
   271  	}
   272  
   273  	if got := subprocs.Finished(); got != 1 {
   274  		t.Fatal(got)
   275  	}
   276  }
   277  
   278  func TestSubprocessTest_SetWithMulti(t *testing.T) {
   279  	processes := [3]*subprocess{}
   280  	commands := []string{testCommand()}
   281  	if runtime.GOOS == "windows" {
   282  		commands = append(commands, "cmd /c echo hi", "cmd /c time /t")
   283  	} else {
   284  		commands = append(commands, "id -u", "pwd")
   285  	}
   286  
   287  	subprocs := newSubprocessSetTest(t)
   288  	for i := 0; i < 3; i++ {
   289  		processes[i] = subprocs.Add(commands[i], false)
   290  		if processes[i] == nil {
   291  			t.Fatal("expected different")
   292  		}
   293  	}
   294  
   295  	if subprocs.Running() != 3 {
   296  		t.Fatal("expected equal")
   297  	}
   298  	/* The expectations with the C++ code is different.
   299  	for i := 0; i < 3; i++ {
   300  		if processes[i].Done() {
   301  			t.Fatal("expected false")
   302  		}
   303  		if got := processes[i].GetOutput(); got != "" {
   304  			t.Fatalf("%q", got)
   305  		}
   306  	}
   307  	*/
   308  
   309  	for !processes[0].Done() || !processes[1].Done() || !processes[2].Done() {
   310  		if subprocs.Running() <= 0 {
   311  			t.Fatal("expected greater")
   312  		}
   313  		subprocs.DoWork()
   314  	}
   315  
   316  	if subprocs.Running() != 0 {
   317  		t.Fatal("expected equal")
   318  	}
   319  	if subprocs.Finished() != 3 {
   320  		t.Fatal("expected equal")
   321  	}
   322  
   323  	for i := 0; i < 3; i++ {
   324  		if processes[i].Finish() != ExitSuccess {
   325  			t.Fatal("expected equal")
   326  		}
   327  		if processes[i].GetOutput() == "" {
   328  			t.Fatal("expected different")
   329  		}
   330  	}
   331  }
   332  
   333  func TestSubprocessTest_SetWithLots(t *testing.T) {
   334  	if runtime.GOOS == "windows" {
   335  		t.Skip("skipped on windows")
   336  	}
   337  
   338  	// Arbitrary big number; needs to be over 1024 to confirm we're no longer
   339  	// hostage to pselect.
   340  	const numProcs = 1025
   341  
   342  	subprocessTestFixUlimit(t, numProcs)
   343  	cmd := "/bin/echo"
   344  
   345  	subprocs := newSubprocessSetTest(t)
   346  	var procs []*subprocess
   347  	for i := 0; i < numProcs; i++ {
   348  		subproc := subprocs.Add(cmd, false)
   349  		if nil == subproc {
   350  			t.Fatal("expected different")
   351  		}
   352  		procs = append(procs, subproc)
   353  	}
   354  	for subprocs.Running() != 0 {
   355  		subprocs.DoWork()
   356  	}
   357  	for i := 0; i < len(procs); i++ {
   358  		if got := procs[i].Finish(); got != ExitSuccess {
   359  			t.Fatal(got)
   360  		}
   361  		if procs[i].GetOutput() == "" {
   362  			t.Fatal("expected different")
   363  		}
   364  	}
   365  	if numProcs != subprocs.Finished() {
   366  		t.Fatal("expected equal")
   367  	}
   368  }
   369  
   370  // TODO: this test could work on Windows, just not sure how to simply
   371  // read stdin.
   372  // Verify that a command that attempts to read stdin correctly thinks
   373  // that stdin is closed.
   374  func TestSubprocessTest_ReadStdin(t *testing.T) {
   375  	if runtime.GOOS == "windows" {
   376  		t.Skip("Has to be ported")
   377  	}
   378  	subprocs := newSubprocessSetTest(t)
   379  	subproc := subprocs.Add("cat -", false)
   380  	for !subproc.Done() {
   381  		subprocs.DoWork()
   382  	}
   383  	if subproc.Finish() != ExitSuccess {
   384  		t.Fatal("expected equal")
   385  	}
   386  	if subprocs.Finished() != 1 {
   387  		t.Fatal("expected equal")
   388  	}
   389  }