github.com/jmigpin/editor@v1.6.0/util/osutil/cmd_test.go (about)

     1  package osutil
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io"
     7  	"io/ioutil"
     8  	"testing"
     9  	"time"
    10  )
    11  
    12  //godebug:annotatepackage
    13  
    14  //----------
    15  
    16  func TestCmdRead1(t *testing.T) {
    17  	// wait for stdin indefinitely (correct, cmd.cmd.wait waits)
    18  
    19  	ctx := context.Background()
    20  	cmd := NewCmd(ctx, "sh", "-c", "sleep 1")
    21  	midT := 2 * time.Second
    22  	h := NewHanger(3 * time.Second)
    23  	cmd.Stdin = h // hangs
    24  	if err := cmd.Start(); err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	now := time.Now()
    28  	go func() {
    29  		<-ctx.Done()
    30  		t.Logf("context: %v\n", time.Since(now))
    31  	}()
    32  	if err := cmd.Wait(); err != nil {
    33  		t.Fatal(err)
    34  	}
    35  	dur := time.Since(now)
    36  	t.Logf("wait done: %v\n", dur)
    37  	if dur < midT {
    38  		t.Fatalf("cmd did end, did not wait for stdin")
    39  	}
    40  }
    41  
    42  func TestCmdRead2(t *testing.T) {
    43  	// don't wait for stdin
    44  
    45  	ctx := context.Background()
    46  	cmd := NewCmd(ctx, "sh", "-c", "sleep 1")
    47  	midT := 2 * time.Second
    48  	h := NewHanger(3 * time.Second)
    49  	//cmd.Stdin = h // hangs
    50  	//ipwc, _ := cmd.StdinPipe() // doesn't hang
    51  	//go func() {
    52  	//	io.Copy(ipwc, h)
    53  	//}()
    54  	cmd.SetupStdio(h, nil, nil) // doesn't hang
    55  	if err := cmd.Start(); err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	now := time.Now()
    59  	go func() {
    60  		<-ctx.Done()
    61  		t.Logf("context: %v\n", time.Since(now))
    62  	}()
    63  	if err := cmd.Wait(); err != nil {
    64  		t.Fatal(err)
    65  	}
    66  	dur := time.Since(now)
    67  	t.Logf("wait done: %v\n", dur)
    68  	if dur > midT {
    69  		t.Fatalf("cmd waited for stdin")
    70  	}
    71  }
    72  
    73  func TestCmdRead2Ctx(t *testing.T) {
    74  	// ctx cancel should be able to stop the hang on stdin
    75  
    76  	ctx, _ := context.WithTimeout(context.Background(), 1*time.Second)
    77  	cmd := NewCmd(ctx, "sh", "-c", "sleep 1")
    78  	midT := 2 * time.Second
    79  	h := NewHanger(3 * time.Second)
    80  	//cmd.Stdin = h              // hangs
    81  	//ipwc, _ := cmd.StdinPipe() // doesn't hang
    82  	//go func() {
    83  	//	io.Copy(ipwc, h)
    84  	//}()
    85  	cmd.SetupStdio(h, nil, nil) // doesn't hang
    86  	if err := cmd.Start(); err != nil {
    87  		t.Fatal(err)
    88  	}
    89  	now := time.Now()
    90  	go func() {
    91  		<-ctx.Done()
    92  		t.Logf("context: %v\n", time.Since(now))
    93  	}()
    94  	if err := cmd.Wait(); err == nil {
    95  		t.Fatal("expecting error")
    96  	}
    97  	dur := time.Since(now)
    98  	t.Logf("wait done: %v\n", dur)
    99  	if dur > midT {
   100  		t.Fatalf("cmd did not end at ctx cancel")
   101  	}
   102  }
   103  
   104  //----------
   105  
   106  func TestCmdWrite1(t *testing.T) {
   107  	// wait for stdout indefinitely (correct, cmd.cmd.wait waits)
   108  
   109  	ctx := context.Background()
   110  	cmd := NewCmd(ctx, "sh", "-c", "sleep 1; echo aaa")
   111  	midT := 2 * time.Second
   112  	h := NewHanger(3 * time.Second)
   113  	cmd.Stdout = h // hangs
   114  	if err := cmd.Start(); err != nil {
   115  		t.Fatal(err)
   116  	}
   117  	now := time.Now()
   118  	go func() {
   119  		<-ctx.Done()
   120  		t.Logf("context: %v\n", time.Since(now))
   121  	}()
   122  	if err := cmd.Wait(); err != nil {
   123  		t.Fatal(err)
   124  	}
   125  	dur := time.Since(now)
   126  	t.Logf("wait done: %v\n", dur)
   127  	if dur < midT {
   128  		t.Fatalf("cmd did end, did not wait for stdout")
   129  	}
   130  	s := string(h.buf.Bytes())
   131  	if s != "aaa\n" {
   132  		t.Fatalf("bad output: %v", s)
   133  	}
   134  }
   135  
   136  func TestCmdWrite2(t *testing.T) {
   137  	// wait for stdout indefinitely (correct, cmd.cmd.wait waits)
   138  
   139  	ctx := context.Background()
   140  	cmd := NewCmd(ctx, "sh", "-c", "sleep 1; echo aaa")
   141  	midT := 2 * time.Second
   142  	h := NewHanger(3 * time.Second)
   143  	//cmd.Stdout = h // hangs
   144  	cmd.SetupStdio(nil, h, nil) // hangs
   145  	if err := cmd.Start(); err != nil {
   146  		t.Fatal(err)
   147  	}
   148  	now := time.Now()
   149  	go func() {
   150  		<-ctx.Done()
   151  		t.Logf("context: %v\n", time.Since(now))
   152  	}()
   153  	if err := cmd.Wait(); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	dur := time.Since(now)
   157  	t.Logf("wait done: %v\n", dur)
   158  	if dur < midT {
   159  		t.Fatalf("cmd did end, did not wait for stdout")
   160  	}
   161  	s := string(h.buf.Bytes())
   162  	if s != "aaa\n" {
   163  		t.Fatalf("bad output: %v", s)
   164  	}
   165  }
   166  
   167  func TestCmdWrite2Ctx(t *testing.T) {
   168  	// ctx cancel should be able to stop the hang on stdout (correct, cmd.cmd cancels and sends kill sig)
   169  
   170  	ctx, _ := context.WithTimeout(context.Background(), 1*time.Second)
   171  	midT := 2 * time.Second
   172  	cmd := NewCmd(ctx, "sh", "-c", "sleep 3; echo aaa")
   173  	h := NewHanger(4 * time.Second)
   174  	//cmd.Stdout = h // doesn't hang
   175  	cmd.SetupStdio(nil, h, nil) // doesn't hang
   176  	if err := cmd.Start(); err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	now := time.Now()
   180  	go func() {
   181  		<-ctx.Done()
   182  		t.Logf("context: %v\n", time.Since(now))
   183  	}()
   184  	if err := cmd.Wait(); err == nil {
   185  		t.Fatal("expecting error")
   186  	} else {
   187  		t.Logf("err: %v", err)
   188  	}
   189  	dur := time.Since(now)
   190  	t.Logf("wait done: %v\n", dur)
   191  	if dur > midT {
   192  		t.Fatalf("cmd did not end at ctx cancel")
   193  	}
   194  	s := string(h.buf.Bytes())
   195  	if s != "" {
   196  		t.Fatalf("bad output: %v", s)
   197  	}
   198  }
   199  
   200  //----------
   201  
   202  func TestCmdKill(t *testing.T) {
   203  	// killing a process will still wait for the input to complete (correct, cmd.cmd.wait waits)
   204  	// the hanger used in this test is sleeping, but in normal conditions, its write attempt would fail when the kill happens
   205  
   206  	ctx := context.Background()
   207  	killT := 1 * time.Second
   208  	cmd := NewCmd(ctx, "sh", "-c", "sleep 2")
   209  	midT := 2 * time.Second
   210  	h := NewHanger(4 * time.Second)
   211  	cmd.Stdin = h // hangs
   212  	//cmd.SetupStdInOutErr(h, nil, nil) // hangs
   213  	cmd.Start()
   214  	now := time.Now()
   215  	go func() {
   216  		time.Sleep(killT)
   217  		cmd.Process.Kill()
   218  		t.Logf("kill: %v\n", time.Since(now))
   219  	}()
   220  	if err := cmd.Wait(); err == nil {
   221  		t.Fatal(err)
   222  	}
   223  	dur := time.Since(now)
   224  	t.Logf("wait done: %v\n", dur)
   225  	//if dur > 2*time.Second {
   226  	//t.Fatalf("cmd did not end at kill")
   227  	//}
   228  	if dur < midT {
   229  		t.Fatalf("cmd did end at kill")
   230  	}
   231  }
   232  
   233  //----------
   234  
   235  func TestCmdPipe(t *testing.T) {
   236  	type strErr struct {
   237  		s   string
   238  		err error
   239  	}
   240  	for i := 0; i < 100; i++ {
   241  		ctx := context.Background()
   242  		cmd := NewCmd(ctx, "echo", "aaa")
   243  		pr, pw := io.Pipe()
   244  		c := make(chan strErr)
   245  		cmd.SetupStdio(nil, pw, nil)
   246  		go func() {
   247  			b, err := ioutil.ReadAll(pr)
   248  			c <- strErr{string(b), err}
   249  		}()
   250  		if err := cmd.Start(); err != nil {
   251  			t.Errorf("%d. Start: %v", i, err)
   252  			continue
   253  		}
   254  		if err := cmd.Wait(); err != nil {
   255  			t.Errorf("%d. Wait: %v", i, err)
   256  			continue
   257  		}
   258  		pw.Close() // must close after wait (handle pipe externally to cmd), or it will hang
   259  		se := <-c
   260  		if se.err != nil {
   261  			t.Errorf("%d. echo: %v", i, se.err)
   262  		}
   263  		if se.s != "aaa\n" {
   264  			t.Errorf("%d. echo: want %q, got %q", i, "aaa\n", se.s)
   265  		}
   266  	}
   267  }
   268  
   269  //----------
   270  
   271  func TestStress1(t *testing.T) {
   272  	// direct cmd.stoutpipe test for understanding
   273  
   274  	type strErr struct {
   275  		s   string
   276  		err error
   277  	}
   278  	for i := 0; i < 1000; i++ {
   279  		ctx := context.Background()
   280  		cmd := NewCmd(ctx, "echo", "aaa")
   281  		p, err := cmd.StdoutPipe()
   282  		if err != nil {
   283  			t.Errorf("%d. StdoutPipe: %v", i, err)
   284  			continue
   285  		}
   286  		c := make(chan strErr)
   287  		go func() {
   288  			b, err := ioutil.ReadAll(p)
   289  			c <- strErr{string(b), err}
   290  		}()
   291  		if err := cmd.Start(); err != nil {
   292  			t.Errorf("%d. Start: %v", i, err)
   293  			continue
   294  		}
   295  		se := <-c // must read before wait (using stdoutpipe directly)
   296  		if err := cmd.Wait(); err != nil {
   297  			t.Errorf("%d. Wait: %v", i, err)
   298  			continue
   299  		}
   300  		//se := <-c // fails to get all output since wait will not have a copy loop
   301  		if se.err != nil {
   302  			t.Errorf("%d. echo: %v", i, se.err)
   303  		}
   304  		if se.s != "aaa\n" {
   305  			t.Errorf("%d. echo: want %q, got %q", i, "aaa\n", se.s)
   306  		}
   307  	}
   308  }
   309  
   310  //----------
   311  
   312  type Hanger struct {
   313  	t   time.Duration
   314  	buf bytes.Buffer
   315  }
   316  
   317  func NewHanger(t time.Duration) *Hanger {
   318  	return &Hanger{t: t}
   319  }
   320  func (h *Hanger) Read(b []byte) (int, error) {
   321  	time.Sleep(time.Duration(h.t))
   322  	//return 0, io.EOF
   323  	return h.buf.Read(b)
   324  }
   325  func (h *Hanger) Write(b []byte) (int, error) {
   326  	time.Sleep(time.Duration(h.t))
   327  	//return 0, io.EOF
   328  	return h.buf.Write(b)
   329  }