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 }