mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
Add package stub.
This commit is contained in:
parent
172fa49056
commit
4c0919b130
|
@ -151,5 +151,6 @@ The adjective for elvish is also "elvish", not "elvishy" and definitely not "elv
|
|||
|parse|[![parse](https://gocover.io/_badge/github.com/elves/elvish/parse/)](https://gocover.io/github.com/elves/elvish/parse/)|
|
||||
|run|[![run](https://gocover.io/_badge/github.com/elves/elvish/run/)](https://gocover.io/github.com/elves/elvish/run/)|
|
||||
|store|[![store](https://gocover.io/_badge/github.com/elves/elvish/store/)](https://gocover.io/github.com/elves/elvish/store/)|
|
||||
|stub|[![stub](https://gocover.io/_badge/github.com/elves/elvish/stub/)](https://gocover.io/github.com/elves/elvish/stub/)|
|
||||
|sys|[![sys](https://gocover.io/_badge/github.com/elves/elvish/sys/)](https://gocover.io/github.com/elves/elvish/sys/)|
|
||||
|util|[![util](https://gocover.io/_badge/github.com/elves/elvish/util/)](https://gocover.io/github.com/elves/elvish/util/)|
|
||||
|
|
127
stub/stub.go
Normal file
127
stub/stub.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Package stub is used to start and manage an elvish-stub process.
|
||||
package stub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
var stubname = "elvish-stub"
|
||||
|
||||
type Stub struct {
|
||||
process *os.Process
|
||||
// write is the other end of stdin of the stub.
|
||||
write *os.File
|
||||
// read is the other end of stdout of the stub.
|
||||
read *os.File
|
||||
sigch chan os.Signal
|
||||
statech chan struct{}
|
||||
}
|
||||
|
||||
var stubEnv = []string{"A=BCDEFGHIJKLMNOPQRSTUVWXYZ"}
|
||||
|
||||
// NewStub spawns a new stub. The specified stderr is used for the subprocess.
|
||||
func NewStub(stderr *os.File) (*Stub, error) {
|
||||
// Find stub.
|
||||
stubpath, err := searchStub()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search: %v", err)
|
||||
}
|
||||
|
||||
// Make pipes.
|
||||
stdin, write, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pipe: %v", err)
|
||||
}
|
||||
read, stdout, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pipe: %v", err)
|
||||
}
|
||||
|
||||
// Spawn stub.
|
||||
process, err := os.StartProcess(stubpath, []string{stubpath},
|
||||
&os.ProcAttr{Env: stubEnv, Files: []*os.File{stdin, stdout, stderr}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("spawn: %v", err)
|
||||
}
|
||||
|
||||
// Wait for startup message.
|
||||
_, err = fmt.Fscanf(read, "ok\n")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read startup msg: %v", err)
|
||||
}
|
||||
|
||||
// Spawn signal relayer and waiter.
|
||||
sigch := make(chan os.Signal)
|
||||
statech := make(chan struct{})
|
||||
go relaySignals(read, sigch)
|
||||
go wait(process, statech)
|
||||
|
||||
return &Stub{process, write, read, sigch, statech}, nil
|
||||
}
|
||||
|
||||
func searchStub() (string, error) {
|
||||
// os.Args[0] contains an absolute path. Find elvish-stub in the same
|
||||
// directory where elvish was started.
|
||||
if len(os.Args) > 0 && path.IsAbs(os.Args[0]) {
|
||||
stubpath := path.Join(path.Dir(os.Args[0]), stubname)
|
||||
if util.IsExecutable(stubpath) {
|
||||
return stubpath, nil
|
||||
}
|
||||
}
|
||||
return util.Search(strings.Split(os.Getenv("PATH"), ":"), stubname)
|
||||
}
|
||||
|
||||
func (stub *Stub) Process() *os.Process {
|
||||
return stub.process
|
||||
}
|
||||
|
||||
// Terminate terminates the stub.
|
||||
func (stub *Stub) Terminate() {
|
||||
stub.write.Close()
|
||||
}
|
||||
|
||||
// SetTitle sets the title of the stub.
|
||||
func (stub *Stub) SetTitle(s string) {
|
||||
stub.write.WriteString(s + "\n")
|
||||
}
|
||||
|
||||
// Signals returns a channel into which signals sent to the stub are relayed.
|
||||
func (stub *Stub) Signals() <-chan os.Signal {
|
||||
return stub.sigch
|
||||
}
|
||||
|
||||
// State returns a channel that is closed when the stub exits.
|
||||
func (stub *Stub) State() <-chan struct{} {
|
||||
return stub.statech
|
||||
}
|
||||
|
||||
// relaySignals relays output of the stub to sigch, assuming that outputs
|
||||
// represent signal numbers.
|
||||
func relaySignals(reader io.Reader, sigch chan<- os.Signal) {
|
||||
for {
|
||||
var signum int
|
||||
_, err := fmt.Fscanf(reader, "%d", &signum)
|
||||
if err != nil {
|
||||
// XXX Swallow error.
|
||||
return
|
||||
}
|
||||
sigch <- syscall.Signal(signum)
|
||||
}
|
||||
}
|
||||
|
||||
func wait(proc *os.Process, ch chan<- struct{}) {
|
||||
for {
|
||||
state, err := proc.Wait()
|
||||
if err != nil || state.Exited() {
|
||||
break
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}
|
35
stub/stub_test.go
Normal file
35
stub/stub_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package stub
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStub(t *testing.T) {
|
||||
stub, err := NewStub(os.Stderr)
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
proc := stub.Process()
|
||||
|
||||
// Signals should be relayed.
|
||||
proc.Signal(syscall.SIGINT)
|
||||
select {
|
||||
case sig := <-stub.Signals():
|
||||
if sig != syscall.SIGINT {
|
||||
t.Errorf("got %v, want SIGINT", sig)
|
||||
}
|
||||
case <-time.After(time.Millisecond * 10):
|
||||
t.Errorf("signal not relayed after 10ms")
|
||||
}
|
||||
|
||||
// Calling Terminate should really terminate the process.
|
||||
stub.Terminate()
|
||||
select {
|
||||
case <-stub.State():
|
||||
case <-time.After(time.Millisecond * 10):
|
||||
t.Errorf("stub didn't exit within 10ms")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user