github.com/traefik/yaegi@v0.15.1/cmd/yaegi/yaegi_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  )
    15  
    16  const (
    17  	// CITimeoutMultiplier is the multiplier for all timeouts in the CI.
    18  	CITimeoutMultiplier = 3
    19  )
    20  
    21  // Sleep pauses the current goroutine for at least the duration d.
    22  func Sleep(d time.Duration) {
    23  	d = applyCIMultiplier(d)
    24  	time.Sleep(d)
    25  }
    26  
    27  func applyCIMultiplier(timeout time.Duration) time.Duration {
    28  	ci := os.Getenv("CI")
    29  	if ci == "" {
    30  		return timeout
    31  	}
    32  	b, err := strconv.ParseBool(ci)
    33  	if err != nil || !b {
    34  		return timeout
    35  	}
    36  	return time.Duration(float64(timeout) * CITimeoutMultiplier)
    37  }
    38  
    39  func TestYaegiCmdCancel(t *testing.T) {
    40  	tmp := t.TempDir()
    41  	yaegi := filepath.Join(tmp, "yaegi")
    42  
    43  	args := []string{"build"}
    44  	if raceDetectorSupported(runtime.GOOS, runtime.GOARCH) {
    45  		args = append(args, "-race")
    46  	}
    47  	args = append(args, "-o", yaegi, ".")
    48  
    49  	build := exec.Command("go", args...)
    50  
    51  	out, err := build.CombinedOutput()
    52  	if err != nil {
    53  		t.Fatalf("failed to build yaegi command: %v: %s", err, out)
    54  	}
    55  
    56  	// Test src must be terminated by a single newline.
    57  	tests := []string{
    58  		"for {}\n",
    59  		"select {}\n",
    60  	}
    61  	for _, src := range tests {
    62  		cmd := exec.Command(yaegi)
    63  		in, err := cmd.StdinPipe()
    64  		if err != nil {
    65  			t.Errorf("failed to get stdin pipe to yaegi command: %v", err)
    66  		}
    67  		var outBuf, errBuf bytes.Buffer
    68  		cmd.Stdout = &outBuf
    69  		cmd.Stderr = &errBuf
    70  
    71  		// https://golang.org/doc/articles/race_detector.html#Options
    72  		cmd.Env = []string{`GORACE="halt_on_error=1"`}
    73  
    74  		err = cmd.Start()
    75  		if err != nil {
    76  			t.Fatalf("failed to start yaegi command: %v", err)
    77  		}
    78  
    79  		_, err = in.Write([]byte(src))
    80  		if err != nil {
    81  			t.Errorf("failed pipe test source to yaegi command: %v", err)
    82  		}
    83  		Sleep(500 * time.Millisecond)
    84  		err = cmd.Process.Signal(os.Interrupt)
    85  		if err != nil {
    86  			t.Errorf("failed to send os.Interrupt to yaegi command: %v", err)
    87  		}
    88  
    89  		_, err = in.Write([]byte("1+1\n"))
    90  		if err != nil {
    91  			t.Errorf("failed to probe race: %v", err)
    92  		}
    93  		err = in.Close()
    94  		if err != nil {
    95  			t.Errorf("failed to close stdin pipe: %v", err)
    96  		}
    97  
    98  		err = cmd.Wait()
    99  		if err != nil {
   100  			if cmd.ProcessState.ExitCode() == 66 { // See race_detector.html article.
   101  				t.Errorf("race detected running yaegi command canceling %q: %v", src, err)
   102  				if testing.Verbose() {
   103  					t.Log(&errBuf)
   104  				}
   105  			} else {
   106  				t.Errorf("error running yaegi command for %q: %v", src, err)
   107  			}
   108  			continue
   109  		}
   110  
   111  		if strings.TrimSuffix(errBuf.String(), "\n") != context.Canceled.Error() {
   112  			t.Errorf("unexpected error: %q", &errBuf)
   113  		}
   114  	}
   115  }
   116  
   117  func raceDetectorSupported(goos, goarch string) bool {
   118  	if strings.Contains(os.Getenv("GOFLAGS"), "-buildmode=pie") {
   119  		// The Go race detector is not compatible with position independent code (pie).
   120  		// We read the conventional GOFLAGS env variable used for example on AlpineLinux
   121  		// to build packages, as there is no way to get this information from the runtime.
   122  		return false
   123  	}
   124  	switch goos {
   125  	case "linux":
   126  		return goarch == "amd64" || goarch == "ppc64le" || goarch == "arm64"
   127  	case "darwin":
   128  		return goarch == "amd64" || goarch == "arm64"
   129  	case "freebsd", "netbsd", "openbsd", "windows":
   130  		return goarch == "amd64"
   131  	default:
   132  		return false
   133  	}
   134  }