Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 1 | package modules |
| 2 | |
| 3 | import ( |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 4 | "flag" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 5 | "io" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 6 | "os" |
| 7 | "os/exec" |
| 8 | "strings" |
| 9 | "sync" |
| 10 | "time" |
| 11 | |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 12 | vexec "veyron.io/veyron/veyron/lib/exec" |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 13 | "veyron.io/veyron/veyron2/vlog" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 14 | ) |
| 15 | |
| 16 | // execHandle implements both the command and Handle interfaces. |
| 17 | type execHandle struct { |
| 18 | mu sync.Mutex |
| 19 | cmd *exec.Cmd |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 20 | name string |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 21 | entryPoint string |
| 22 | handle *vexec.ParentHandle |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 23 | sh *Shell |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 24 | stderr *os.File |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 25 | stdout io.ReadCloser |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 26 | stdin io.WriteCloser |
| 27 | } |
| 28 | |
| 29 | func testFlags() []string { |
| 30 | var fl []string |
| 31 | // pass logging flags to any subprocesses |
| 32 | for fname, fval := range vlog.Log.ExplicitlySetFlags() { |
| 33 | fl = append(fl, "--"+fname+"="+fval) |
| 34 | } |
| 35 | timeout := flag.Lookup("test.timeout") |
| 36 | if timeout == nil { |
| 37 | // not a go test binary |
| 38 | return fl |
| 39 | } |
| 40 | // must be a go test binary |
| 41 | fl = append(fl, "-test.run=TestHelperProcess") |
| 42 | val := timeout.Value.(flag.Getter).Get().(time.Duration) |
| 43 | if val.String() != timeout.DefValue { |
| 44 | // use supplied command value for subprocesses |
| 45 | fl = append(fl, "--test.timeout="+timeout.Value.String()) |
| 46 | } else { |
| 47 | // translate default value into 1m for subproccesses |
| 48 | fl = append(fl, "--test.timeout=1m") |
| 49 | } |
| 50 | return fl |
| 51 | } |
| 52 | |
Cosmos Nicolaou | 9c9918d | 2014-09-23 08:45:56 -0700 | [diff] [blame] | 53 | // IsTestHelperProces returns true if it is called in via |
| 54 | // -run=TestHelperProcess which normally only ever happens for subprocesses |
| 55 | // run from tests. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 56 | func IsTestHelperProcess() bool { |
| 57 | runFlag := flag.Lookup("test.run") |
| 58 | if runFlag == nil { |
| 59 | return false |
| 60 | } |
| 61 | return runFlag.Value.String() == "TestHelperProcess" |
| 62 | } |
| 63 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 64 | func newExecHandle(name string) command { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame^] | 65 | return &execHandle{name: name, entryPoint: shellEntryPoint + "=" + name} |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 66 | } |
| 67 | |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 68 | func (eh *execHandle) Stdout() io.Reader { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 69 | eh.mu.Lock() |
| 70 | defer eh.mu.Unlock() |
| 71 | return eh.stdout |
| 72 | } |
| 73 | |
| 74 | func (eh *execHandle) Stderr() io.Reader { |
| 75 | eh.mu.Lock() |
| 76 | defer eh.mu.Unlock() |
| 77 | return eh.stderr |
| 78 | } |
| 79 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 80 | func (eh *execHandle) Stdin() io.Writer { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 81 | eh.mu.Lock() |
| 82 | defer eh.mu.Unlock() |
| 83 | return eh.stdin |
| 84 | } |
| 85 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 86 | func (eh *execHandle) CloseStdin() { |
| 87 | eh.mu.Lock() |
| 88 | eh.stdin.Close() |
| 89 | eh.mu.Unlock() |
| 90 | } |
| 91 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 92 | func (eh *execHandle) envelope(sh *Shell, env []string, args ...string) ([]string, []string) { |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 93 | newargs := []string{os.Args[0]} |
| 94 | newargs = append(newargs, testFlags()...) |
| 95 | newargs = append(newargs, args...) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame^] | 96 | // Be careful to remove any existing shellEntryPoint env vars. This |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 97 | // can happen when subprocesses run other subprocesses etc. |
| 98 | cleaned := make([]string, 0, len(env)+1) |
| 99 | for _, e := range env { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame^] | 100 | if strings.HasPrefix(e, shellEntryPoint+"=") { |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 101 | continue |
| 102 | } |
| 103 | cleaned = append(cleaned, e) |
| 104 | } |
| 105 | return newargs, append(cleaned, eh.entryPoint) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 106 | } |
| 107 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 108 | func (eh *execHandle) start(sh *Shell, env []string, args ...string) (Handle, error) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 109 | eh.mu.Lock() |
| 110 | defer eh.mu.Unlock() |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 111 | eh.sh = sh |
Cosmos Nicolaou | 244d344 | 2014-10-26 14:19:42 -0700 | [diff] [blame] | 112 | newargs, newenv := eh.envelope(sh, env, args[1:]...) |
| 113 | cmd := exec.Command(os.Args[0], newargs[1:]...) |
| 114 | cmd.Env = newenv |
| 115 | stderr, err := newLogfile(strings.TrimLeft(eh.name, "-\n\t ")) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 116 | if err != nil { |
| 117 | return nil, err |
| 118 | } |
| 119 | cmd.Stderr = stderr |
| 120 | stdout, err := cmd.StdoutPipe() |
| 121 | if err != nil { |
| 122 | return nil, err |
| 123 | } |
| 124 | stdin, err := cmd.StdinPipe() |
| 125 | if err != nil { |
| 126 | return nil, err |
| 127 | } |
| 128 | |
Jiri Simsa | 3789339 | 2014-11-07 10:55:45 -0800 | [diff] [blame] | 129 | handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: sh.config}) |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 130 | eh.stdout = stdout |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 131 | eh.stderr = stderr |
| 132 | eh.stdin = stdin |
| 133 | eh.handle = handle |
| 134 | eh.cmd = cmd |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 135 | vlog.VI(1).Infof("Start: %q args: %v", eh.name, cmd.Args) |
| 136 | vlog.VI(2).Infof("Start: %q env: %v", eh.name, cmd.Env) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 137 | if err := handle.Start(); err != nil { |
| 138 | return nil, err |
| 139 | } |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 140 | vlog.VI(1).Infof("Started: %q, pid %d", eh.name, cmd.Process.Pid) |
| 141 | err = handle.WaitForReady(sh.startTimeout) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 142 | return eh, err |
| 143 | } |
| 144 | |
Cosmos Nicolaou | cc58172 | 2014-10-07 12:45:39 -0700 | [diff] [blame] | 145 | func (eh *execHandle) Pid() int { |
| 146 | return eh.cmd.Process.Pid |
| 147 | } |
| 148 | |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 149 | func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 150 | eh.mu.Lock() |
| 151 | defer eh.mu.Unlock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 152 | vlog.VI(1).Infof("Shutdown: %q", eh.name) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 153 | eh.stdin.Close() |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 154 | logFile := eh.stderr.Name() |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 155 | defer eh.sh.Forget(eh) |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 156 | |
| 157 | defer func() { |
| 158 | os.Remove(logFile) |
| 159 | }() |
| 160 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 161 | // TODO(cnicolaou): make this configurable |
| 162 | timeout := 10 * time.Second |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 163 | if stdout == nil && stderr == nil { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 164 | return eh.handle.Wait(timeout) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 165 | } |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 166 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 167 | if stdout != nil { |
| 168 | // Read from stdin before waiting for the child process to ensure |
| 169 | // that we get to read all of its output. |
| 170 | readTo(eh.stdout, stdout) |
| 171 | } |
| 172 | |
| 173 | procErr := eh.handle.Wait(timeout) |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 174 | |
| 175 | // Stderr is buffered to a file, so we can safely read it after we |
| 176 | // wait for the process. |
| 177 | eh.stderr.Close() |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 178 | if stderr != nil { |
| 179 | stderrFile, err := os.Open(logFile) |
| 180 | if err != nil { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 181 | vlog.VI(1).Infof("failed to open %q: %s\n", logFile, err) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 182 | return procErr |
| 183 | } |
| 184 | readTo(stderrFile, stderr) |
| 185 | stderrFile.Close() |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 186 | } |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 187 | return procErr |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 188 | } |