go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/system/exec2/exec2_test.go (about)

     1  // Copyright 2019 The LUCI Authors.
     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 exec2
    16  
    17  import (
    18  	"context"
    19  	"os/exec"
    20  	"path/filepath"
    21  	"runtime"
    22  	"testing"
    23  	"time"
    24  
    25  	"go.chromium.org/luci/common/system/environ"
    26  
    27  	. "github.com/smartystreets/goconvey/convey"
    28  	. "go.chromium.org/luci/common/testing/assertions"
    29  )
    30  
    31  func build(src, tmpdir string) (string, error) {
    32  	binary := filepath.Join(tmpdir, "exe.exe")
    33  	cmd := exec.Command("go", "build", "-o", binary, src)
    34  	if err := cmd.Run(); err != nil {
    35  		return "", err
    36  	}
    37  	return binary, nil
    38  }
    39  
    40  func TestExec(t *testing.T) {
    41  	t.Parallel()
    42  
    43  	Convey("TestExec", t, func() {
    44  		ctx := context.Background()
    45  
    46  		tmpdir := t.TempDir()
    47  
    48  		errCh := make(chan error, 1)
    49  
    50  		Convey("exit", func() {
    51  			testBinary, err := build(filepath.Join("testdata", "exit.go"), tmpdir)
    52  			So(err, ShouldBeNil)
    53  
    54  			Convey("exit 0", func() {
    55  				cmd := CommandContext(ctx, testBinary)
    56  				So(cmd.Start(), ShouldBeNil)
    57  
    58  				So(cmd.Wait(), ShouldBeNil)
    59  
    60  				So(cmd.ProcessState.ExitCode(), ShouldEqual, 0)
    61  			})
    62  
    63  			Convey("exit 42", func() {
    64  				cmd := CommandContext(ctx, testBinary, "42")
    65  				So(cmd.Start(), ShouldBeNil)
    66  
    67  				So(cmd.Wait(), ShouldBeError, "exit status 42")
    68  
    69  				So(cmd.ProcessState.ExitCode(), ShouldEqual, 42)
    70  			})
    71  		})
    72  
    73  		Convey("timeout", func() {
    74  			testBinary, err := build(filepath.Join("testdata", "timeout.go"), tmpdir)
    75  			So(err, ShouldBeNil)
    76  
    77  			cmd := CommandContext(ctx, testBinary)
    78  			rc, err := cmd.StdoutPipe()
    79  			So(err, ShouldBeNil)
    80  
    81  			So(cmd.Start(), ShouldBeNil)
    82  
    83  			expected := []byte("I'm alive!")
    84  			buf := make([]byte, len(expected))
    85  			n, err := rc.Read(buf)
    86  			So(err, ShouldBeNil)
    87  			So(n, ShouldEqual, len(expected))
    88  			So(buf, ShouldResemble, expected)
    89  
    90  			So(rc.Close(), ShouldBeNil)
    91  
    92  			go func() {
    93  				errCh <- cmd.Wait()
    94  			}()
    95  
    96  			select {
    97  			case err := <-errCh:
    98  				Print(err)
    99  				So("should not reach here", ShouldBeNil)
   100  			case <-time.After(time.Millisecond):
   101  			}
   102  
   103  			So(cmd.Terminate(), ShouldBeNil)
   104  
   105  			select {
   106  			case err := <-errCh:
   107  				if runtime.GOOS == "windows" {
   108  					So(err, ShouldErrLike, "exit status")
   109  				} else {
   110  					So(err, ShouldErrLike, "signal: terminated")
   111  				}
   112  				// The value of the exit code depends on GOOS and version of Go runtime.
   113  				So(cmd.ProcessState.ExitCode(), ShouldNotEqual, 0)
   114  			case <-time.After(time.Minute):
   115  				Print(err)
   116  				So("should not reach here", ShouldBeNil)
   117  			}
   118  		})
   119  
   120  		Convey("context timeout", func() {
   121  			testBinary, err := build(filepath.Join("testdata", "timeout.go"), tmpdir)
   122  			So(err, ShouldBeNil)
   123  
   124  			if runtime.GOOS == "windows" {
   125  				// TODO(tikuta): support context timeout on windows
   126  				return
   127  			}
   128  
   129  			ctx, cancel := context.WithTimeout(ctx, time.Millisecond)
   130  			defer cancel()
   131  
   132  			cmd := CommandContext(ctx, testBinary)
   133  
   134  			So(cmd.Start(), ShouldBeNil)
   135  
   136  			So(cmd.Wait(), ShouldBeError, "signal: killed")
   137  
   138  			So(cmd.ProcessState.ExitCode(), ShouldEqual, -1)
   139  		})
   140  
   141  	})
   142  }
   143  
   144  func TestSetEnv(t *testing.T) {
   145  	t.Parallel()
   146  
   147  	Convey("TestSetEnv", t, func() {
   148  		ctx := context.Background()
   149  
   150  		tmpdir := t.TempDir()
   151  
   152  		testBinary, err := build(filepath.Join("testdata", "env.go"), tmpdir)
   153  		So(err, ShouldBeNil)
   154  
   155  		cmd := CommandContext(ctx, testBinary)
   156  		env := environ.System()
   157  		env.Set("envvar", "envvar")
   158  		cmd.Env = env.Sorted()
   159  
   160  		So(cmd.Start(), ShouldBeNil)
   161  
   162  		So(cmd.Wait(), ShouldBeNil)
   163  
   164  		So(cmd.ProcessState.ExitCode(), ShouldEqual, 0)
   165  	})
   166  }