From 1af679b34e4a405ac2c66137b5324c0103e92b7a Mon Sep 17 00:00:00 2001 From: steampunkcoder Date: Fri, 22 Jun 2018 08:24:56 -0700 Subject: [PATCH 1/2] Backwards-compatible enhancements to add optional context and verbose logger Add pipe.Sleep(), pipe.Reduce(), pipe.CountLines() Add Makefile, .travis.yml Minor updates for lint and UT fix --- .travis.yml | 18 ++++ Makefile | 10 ++ README.md | 10 ++ mocklogger_test.go | 26 +++++ pipe.go | 258 +++++++++++++++++++++++++++++++++++++++++++-- pipe_test.go | 106 ++++++++++++++++++- 6 files changed, 420 insertions(+), 8 deletions(-) create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 mocklogger_test.go diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..42e8d1b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: go +go: +- "1.2.x" +- "1.3.x" +- "1.4.x" +- "1.5.x" +- "1.6.x" +- "1.7.x" +- "1.8.x" +- "1.9.x" +- "1.10.x" +- stable +- master +before_install: +- go get gopkg.in/check.v1 +scripts: +- make test + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c3c526e --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +test: + go test -v -cover + +vet: + go vet + +lint: + golint -set_exit_status + gofmt -s -d *.go + diff --git a/README.md b/README.md index d0ff487..11eff8e 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,13 @@ Installation and usage ---------------------- See [gopkg.in/pipe.v2](https://gopkg.in/pipe.v2) for documentation and usage details. + +[![Build status][travis-img]][travis-url] [![License][license-img]][license-url] [![GoDoc][godoc-img]][godoc-url] + +[travis-img]: https://img.shields.io/travis/steampunkcoder/pipe.svg?style=flat-square +[travis-url]: https://travis-ci.org/steampunkcoder/pipe +[license-img]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square +[license-url]: LICENSE +[godoc-img]: https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square +[godoc-url]: https://godoc.org/github.com/steampunkcoder/pipe + diff --git a/mocklogger_test.go b/mocklogger_test.go new file mode 100644 index 0000000..3c06126 --- /dev/null +++ b/mocklogger_test.go @@ -0,0 +1,26 @@ +package pipe_test + +import ( + "fmt" + "strings" +) + +// MockLogger mocks interface pipe.StdLogger +type MockLogger []string + +// Printf implements pipe.StdLogger interface for MockLogger +func (mock *MockLogger) Printf(format string, v ...interface{}) { + mock.appendToLog(fmt.Sprintf(format, v...)) +} + +// Println implements pipe.StdLogger interface for MockLogger +func (mock *MockLogger) Println(v ...interface{}) { + mock.appendToLog(fmt.Sprintln(v...)) +} + +func (mock *MockLogger) appendToLog(loggedStr string) { + if !strings.HasSuffix(loggedStr, "\n") { + loggedStr = loggedStr + "\n" + } + *mock = append(*mock, loggedStr) +} diff --git a/pipe.go b/pipe.go index f4aa8b9..4792fb9 100644 --- a/pipe.go +++ b/pipe.go @@ -35,6 +35,7 @@ package pipe import ( "bufio" "bytes" + "context" "errors" "fmt" "io" @@ -42,6 +43,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "sync" "sync/atomic" @@ -55,6 +57,22 @@ import ( // operations must be run from a Task. type Pipe func(s *State) error +// StdLogger defines std log interface that is inexplicably not defined +// by the standard golang log pkg. StdLogger allows pipe pkg to use +// any logging pkg that conforms to StdLogger (eg: log, logrus). +// pipe pkg only uses a small subset of the std golang log api, +// so only that small subset is defined in StdLogger. +type StdLogger interface { + + // Printf prints to logger. Arguments handled like fmt.Printf. + // Newline appended if last character not already a newline. + Printf(format string, v ...interface{}) + + // Println prints to logger. Arguments handled like fmt.Println. + // Newline appended if last character not already a newline. + Println(v ...interface{}) +} + // A Task may be registered by a Pipe into a State to run any // activity concurrently with other tasks. // Tasks registered within the execution of a Script only run after @@ -102,11 +120,17 @@ type State struct { // If set to zero, the pipe will not be aborted. Timeout time.Duration + // Ctx is the context used during execution. + // If nil, then context.Background() is used. + Ctx context.Context + killedMutex sync.Mutex killedNoted bool killed chan bool pendingTasks []*pendingTask + + logger StdLogger } // NewState returns a new state for running pipes with. @@ -166,12 +190,17 @@ func (pt *pendingTask) done(err error) { } var ( + // ErrTimeout is error returned when task times out ErrTimeout = errors.New("timeout") - ErrKilled = errors.New("explicitly killed") + + // ErrKilled is error returned when task explicitly killed + ErrKilled = errors.New("explicitly killed") ) +// Errors contains a list of all errors returned by tasks type Errors []error +// Error implements error interface for Errors func (e Errors) Error() string { var errors []string for _, err := range e { @@ -189,17 +218,22 @@ func (s *State) AddTask(t Task) error { return nil } - // RunTasks runs all pending tasks registered via AddTask. // This is called by the pipe running functions and generally // there's no reason to call it directly. func (s *State) RunTasks() error { + if s.Ctx == nil { + s.Ctx = context.Background() + } + done := make(chan error, len(s.pendingTasks)) for _, f := range s.pendingTasks { go func(pt *pendingTask) { pt.wait() var err error if pt.cancel == 0 { + pt.s.Ctx = s.Ctx + pt.s.logger = s.logger err = pt.t.Run(&pt.s) } pt.done(err) @@ -221,7 +255,12 @@ func (s *State) RunTasks() error { pt.t.Kill() } } - if errs == nil || errs[len(errs)-1] != ErrTimeout && errs[len(errs)-1] != ErrKilled { + var lastErr error + if errs != nil { + lastErr = errs[len(errs)-1] + } + if errs == nil || lastErr != ErrTimeout && lastErr != ErrKilled && + lastErr != context.Canceled && lastErr != context.DeadlineExceeded { errs = append(errs, err) if discardErr(err) { badErr = true @@ -231,10 +270,13 @@ func (s *State) RunTasks() error { } } - for _ = range s.pendingTasks { + for range s.pendingTasks { var err error select { case err = <-done: + case <-s.Ctx.Done(): + fail(s.Ctx.Err()) + err = <-done case <-timeout: fail(ErrTimeout) err = <-done @@ -338,7 +380,24 @@ func firstErr(err1, err2 error) error { // // See functions Output, CombinedOutput, and DividedOutput. func Run(p Pipe) error { + return RunContext(context.Background(), nil, p) +} + +// RunContext runs the p pipe discarding its output. +// RunContext is like Run but includes optional context +// and/or optional logger. +// +// The pipe is killed if the context is done before pipe is finished. +// +// If logger is specified, then verbose logging of each task is +// performed as each task in the pipe is executed. +// +// See functions OutputContext, CombinedOutputContext, and DividedOutputContext. +func RunContext(ctx context.Context, logger StdLogger, p Pipe) error { s := NewState(nil, nil) + s.logger = logger + s.Ctx = ctx + s.Timeout = 0 err := p(s) if err == nil { err = s.RunTasks() @@ -354,6 +413,7 @@ func Run(p Pipe) error { func RunTimeout(p Pipe, timeout time.Duration) error { s := NewState(nil, nil) s.Timeout = timeout + s.Ctx = nil err := p(s) if err == nil { err = s.RunTasks() @@ -365,8 +425,25 @@ func RunTimeout(p Pipe, timeout time.Duration) error { // // See functions Run, CombinedOutput, and DividedOutput. func Output(p Pipe) ([]byte, error) { + return OutputContext(context.Background(), nil, p) +} + +// OutputContext runs the p pipe and returns its stdout output. +// OutputContext is like Output but includes optional context +// and/or optional logger. +// +// The pipe is killed if the context is done before pipe is finished. +// +// If logger is specified, then verbose logging of each task is +// performed as each task in the pipe is executed. +// +// See functions RunContext, CombinedOutputContext, and DividedOutputContext. +func OutputContext(ctx context.Context, logger StdLogger, p Pipe) ([]byte, error) { outb := &OutputBuffer{} s := NewState(outb, nil) + s.logger = logger + s.Ctx = ctx + s.Timeout = 0 err := p(s) if err == nil { err = s.RunTasks() @@ -395,8 +472,26 @@ func OutputTimeout(p Pipe, timeout time.Duration) ([]byte, error) { // // See functions Run, Output, and DividedOutput. func CombinedOutput(p Pipe) ([]byte, error) { + return CombinedOutputContext(context.Background(), nil, p) +} + +// CombinedOutputContext runs the p pipe and returns its stdout and stderr +// outputs merged together. +// CombinedOutputContext is like CombinedOutput but includes optional context +// and/or optional logger. +// +// The pipe is killed if the context is done before pipe is finished. +// +// If logger is specified, then verbose logging of each task is +// performed as each task in the pipe is executed. +// +// See functions RunContext, OutputContext, and DividedOutputContext. +func CombinedOutputContext(ctx context.Context, logger StdLogger, p Pipe) ([]byte, error) { outb := &OutputBuffer{} s := NewState(outb, outb) + s.logger = logger + s.Ctx = ctx + s.Timeout = 0 err := p(s) if err == nil { err = s.RunTasks() @@ -425,9 +520,26 @@ func CombinedOutputTimeout(p Pipe, timeout time.Duration) ([]byte, error) { // // See functions Run, Output, and CombinedOutput. func DividedOutput(p Pipe) (stdout []byte, stderr []byte, err error) { + return DividedOutputContext(context.Background(), nil, p) +} + +// DividedOutputContext runs the p pipe and returns its stdout and stderr outputs. +// DividedOutputContext is like DividedOutput but includes optional context +// and/or optional logger. +// +// The pipe is killed if the context is done before pipe is finished. +// +// If logger is specified, then verbose logging of each task is +// performed as each task in the pipe is executed. +// +// See functions RunContext, OutputContext, and CombinedOutputContext. +func DividedOutputContext(ctx context.Context, logger StdLogger, p Pipe) (stdout []byte, stderr []byte, err error) { outb := &OutputBuffer{} errb := &OutputBuffer{} s := NewState(outb, errb) + s.logger = logger + s.Ctx = ctx + s.Timeout = 0 err = p(s) if err == nil { err = s.RunTasks() @@ -505,7 +617,15 @@ func (f *execTask) Run(s *State) error { f.m.Unlock() return nil } - cmd := exec.Command(f.name, f.args...) + if s.logger != nil { + printArgs := make([]interface{}, len(f.args)+1) + printArgs[0] = interface{}(f.name) + for idx, stringArg := range f.args { + printArgs[idx+1] = stringArg + } + s.logger.Println(printArgs...) + } + cmd := exec.CommandContext(s.Ctx, f.name, f.args...) cmd.Dir = s.Dir cmd.Env = s.Env cmd.Stdin = s.Stdin @@ -733,6 +853,10 @@ func TaskFunc(f func(s *State) error) Pipe { // string to the pipe's stdout. func Print(args ...interface{}) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.Print %s\n", + strings.Split(fmt.Sprint(args...), "\n")[0]) + } _, err := s.Stdout.Write([]byte(fmt.Sprint(args...))) return err }) @@ -742,6 +866,10 @@ func Print(args ...interface{}) Pipe { // string to the pipe's stdout. func Println(args ...interface{}) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.Println %s\n", + strings.Split(fmt.Sprintln(args...), "\n")[0]) + } _, err := s.Stdout.Write([]byte(fmt.Sprintln(args...))) return err }) @@ -751,6 +879,10 @@ func Println(args ...interface{}) Pipe { // the resulting string to the pipe's stdout. func Printf(format string, args ...interface{}) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.Printf %s\n", + strings.Split(fmt.Sprintf(format, args...), "\n")[0]) + } _, err := s.Stdout.Write([]byte(fmt.Sprintf(format, args...))) return err }) @@ -759,6 +891,9 @@ func Printf(format string, args ...interface{}) Pipe { // Read reads data from r and writes it to the pipe's stdout. func Read(r io.Reader) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.Read %s\n", r) + } _, err := io.Copy(s.Stdout, r) return err }) @@ -766,7 +901,15 @@ func Read(r io.Reader) Pipe { // Write writes to w the data read from the pipe's stdin. func Write(w io.Writer) Pipe { + return verboseWrite("pipe.Write", w) +} + +// verboseWrite writes to w the data read from the pipe's stdin. +func verboseWrite(verboseStr string, w io.Writer) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("%s %s\n", verboseStr, w) + } _, err := io.Copy(w, s.Stdin) return err }) @@ -774,13 +917,16 @@ func Write(w io.Writer) Pipe { // Discard reads data from the pipe's stdin and discards it. func Discard() Pipe { - return Write(ioutil.Discard) + return verboseWrite("pipe.Discard", ioutil.Discard) } // Tee reads data from the pipe's stdin and writes it both to // the pipe's stdout and to w. func Tee(w io.Writer) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.Tee %s\n", w) + } _, err := io.Copy(w, io.TeeReader(s.Stdin, s.Stdout)) return err }) @@ -790,6 +936,9 @@ func Tee(w io.Writer) Pipe { // pipe's stdout. func ReadFile(path string) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.ReadFile %s\n", path) + } file, err := os.Open(s.Path(path)) if err != nil { return err @@ -804,6 +953,9 @@ func ReadFile(path string) Pipe { // pipe's stdin. If the file doesn't exist, it is created with perm. func WriteFile(path string, perm os.FileMode) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.WriteFile %s %s\n", path, perm) + } file, err := os.OpenFile(s.Path(path), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err @@ -818,6 +970,9 @@ func WriteFile(path string, perm os.FileMode) Pipe { // with perm. func AppendFile(path string, perm os.FileMode) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.AppendFile %s %s\n", path, perm) + } file, err := os.OpenFile(s.Path(path), os.O_WRONLY|os.O_CREATE|os.O_APPEND, perm) if err != nil { return err @@ -832,6 +987,9 @@ func AppendFile(path string, perm os.FileMode) Pipe { // exist, it is created with perm. func TeeWriteFile(path string, perm os.FileMode) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.TeeWriteFile %s %s\n", path, perm) + } file, err := os.OpenFile(s.Path(path), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err @@ -846,6 +1004,9 @@ func TeeWriteFile(path string, perm os.FileMode) Pipe { // exist, it is created with perm. func TeeAppendFile(path string, perm os.FileMode) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.TeeAppendFile %s %s\n", path, perm) + } file, err := os.OpenFile(s.Path(path), os.O_WRONLY|os.O_CREATE|os.O_APPEND, perm) if err != nil { return err @@ -858,7 +1019,16 @@ func TeeAppendFile(path string, perm os.FileMode) Pipe { // Replace filters lines read from the pipe's stdin and writes // the returned values to stdout. func Replace(f func(line []byte) []byte) Pipe { + return verboseReplace("pipe.Replace", f) +} + +// verboseReplace filters lines read from the pipe's stdin and writes +// the returned values to stdout. +func verboseReplace(verboseStr string, f func(line []byte) []byte) Pipe { return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("%s %s\n", verboseStr, f) + } r := bufio.NewReader(s.Stdin) for { line, err := r.ReadBytes('\n') @@ -886,7 +1056,7 @@ func Replace(f func(line []byte) []byte) Pipe { // for which f is true are written to the pipe's stdout. // The line provided to f has '\n' and '\r' trimmed. func Filter(f func(line []byte) bool) Pipe { - return Replace(func(line []byte) []byte { + return verboseReplace("pipe.Filter", func(line []byte) []byte { if f(bytes.TrimRight(line, "\r\n")) { return line } @@ -899,6 +1069,80 @@ func RenameFile(fromPath, toPath string) Pipe { // Register it as a task function so that within scripts // it holds until all the preceding flushing is done. return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.RenameFile %s %s\n", fromPath, toPath) + } return os.Rename(s.Path(fromPath), s.Path(toPath)) }) } + +// Sleep pauses for duration d. +func Sleep(d time.Duration) Pipe { + return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("pipe.Sleep %s\n", d) + } + time.Sleep(d) + return nil + }) +} + +// verboseReduce applies function cumulatively to stdin lines, +// writing the single reduced line to stdout. +// The starting cumulative value is startCumulative and can be nil; +// this is also the value if there are no stdin lines. +// The first time function is called, x will be startCumulative. +// After the first time, x will be the accumulated value. +// The y value will always be the iterated stdin line. +// Note no trailing newline is written to stdout, +// so function should include trailing newline in result if desired. +func verboseReduce(verboseStr string, startCumulative []byte, f func(x, y []byte) []byte) Pipe { + return TaskFunc(func(s *State) error { + if s.logger != nil { + s.logger.Printf("%s %s\n", verboseStr, f) + } + accumulated := startCumulative + r := bufio.NewReader(s.Stdin) + for { + line, err := r.ReadBytes('\n') + if err == io.EOF { + if len(line) > 0 { + accumulated = f(accumulated, line) + } + break + } else if err != nil { + return err + } else if line != nil { + accumulated = f(accumulated, line) + } + } + _, err := s.Stdout.Write(accumulated) + if err != nil { + return err + } + return nil + }) +} + +// Reduce applies function cumulatively to stdin lines, +// writing the single reduced line to stdout. +// The starting cumulative value is startCumulative and can be nil; +// this is also the value if there are no stdin lines. +// The first time function is called, x will be startCumulative. +// After the first time, x will be the accumulated value. +// The y value will always be the iterated stdin line. +// Note no trailing newline is written to stdout, +// so function should include trailing newline in result if desired. +func Reduce(startCumulative []byte, f func(x, y []byte) []byte) Pipe { + return verboseReduce("pipe.Reduce", startCumulative, f) +} + +// CountLines writes the count of stdin lines to stdout. +// Note there is no trailing newline written to stdout. +func CountLines() Pipe { + return verboseReduce("pipe.CountLines", []byte("0"), func(x, y []byte) []byte { + count, _ := strconv.Atoi(strings.TrimSpace(string(x))) + count++ + return []byte(strconv.Itoa(count)) + }) +} diff --git a/pipe_test.go b/pipe_test.go index 70b235c..67c5857 100644 --- a/pipe_test.go +++ b/pipe_test.go @@ -2,6 +2,7 @@ package pipe_test import ( "bytes" + "context" "fmt" "io/ioutil" "os" @@ -174,7 +175,7 @@ func (S) TestLineTermination(c *C) { pipe.Exec("true"), ) output, err := pipe.Output(p) - c.Assert(err, ErrorMatches, `command "true": write \|1: broken pipe`) + c.Assert(err, ErrorMatches, `io: read/write on closed pipe`) c.Assert(string(output), Equals, "") } @@ -845,3 +846,106 @@ func (S) TestRenameFileRelative(c *C) { _, err = os.Stat(to) c.Assert(err, IsNil) } + +func (S) TestCanceledContext(c *C) { + cancelableCtx, ctxCancelFn := context.WithCancel(context.Background()) + defer ctxCancelFn() + go func() { + select { + case <-time.After(1 * time.Second): + ctxCancelFn() + } + }() + out, err := pipe.CombinedOutputContext(cancelableCtx, nil, pipe.Line( + pipe.Print("hello world\n"), + pipe.Exec("cat"), + pipe.Exec("sleep", "987654321"), + )) + ctxCancelFn() + c.Assert(string(out), Equals, "") + c.Assert(err, ErrorMatches, context.Canceled.Error()) +} + +func (S) TestTimedoutContext(c *C) { + cancelableCtx, ctxCancelFn := context.WithTimeout(context.Background(), 1*time.Second) + defer ctxCancelFn() + out, err := pipe.CombinedOutputContext(cancelableCtx, nil, pipe.Line( + pipe.Print("hello world\n"), + pipe.Exec("cat"), + pipe.Exec("sleep", "987654321"), + )) + ctxCancelFn() + c.Assert(string(out), Equals, "") + c.Assert(err, ErrorMatches, context.DeadlineExceeded.Error()) +} + +func (S) TestLogger(c *C) { + var mockLogger MockLogger + out, err := pipe.CombinedOutputContext(nil, &mockLogger, pipe.Script( + pipe.Print("print hello world\n"), + pipe.Exec("echo", "echo", "hello", "world"), + )) + c.Assert(err, IsNil) + c.Assert(mockLogger[0], Equals, "pipe.Print print hello world\n") + c.Assert(mockLogger[1], Equals, "echo echo hello world\n") + c.Assert(string(out), Equals, "print hello world\necho hello world\n") +} + +func (S) TestCountLines(c *C) { + p := pipe.Line( + pipe.System("echo out1; echo; echo err1 1>&2; echo out2; echo err2 1>&2; echo out3"), + pipe.CountLines(), + ) + output, err := pipe.Output(p) + c.Assert(err, IsNil) + c.Assert(string(output), Equals, "4") +} + +func (S) TestCountLinesNoNewLine(c *C) { + p := pipe.Line( + pipe.Print("out1\n\nout2\nout3"), + pipe.CountLines(), + ) + output, err := pipe.Output(p) + c.Assert(err, IsNil) + c.Assert(string(output), Equals, "4") +} + +func (S) TestCountLinesZeroLines(c *C) { + output, err := pipe.Output(pipe.CountLines()) + c.Assert(err, IsNil) + c.Assert(string(output), Equals, "0") +} + +func (S) TestReduce(c *C) { + p := pipe.Line( + pipe.System("echo out1; echo; echo err1 1>&2; echo out2; echo err2 1>&2; echo out3"), + pipe.Reduce(nil, func(x, y []byte) []byte { + return []byte(string(x) + strings.Replace(string(y), "\n", ";", -1)) + }), + ) + output, err := pipe.Output(p) + c.Assert(err, IsNil) + c.Assert(string(output), Equals, "out1;;out2;out3;") +} + +func (S) TestReduceNoNewLine(c *C) { + p := pipe.Line( + pipe.Print("out1\n\nout2\nout3"), + pipe.Reduce(nil, func(x, y []byte) []byte { + return []byte(string(x) + strings.Replace(string(y), "\n", ";", -1)) + }), + ) + output, err := pipe.Output(p) + c.Assert(err, IsNil) + c.Assert(string(output), Equals, "out1;;out2;out3") +} + +func (S) TestReduceZeroLines(c *C) { + output, err := pipe.Output( + pipe.Reduce(nil, func(x, y []byte) []byte { + return []byte(string(x) + strings.Replace(string(y), "\n", ";", -1)) + })) + c.Assert(err, IsNil) + c.Assert(string(output), Equals, "") +} From fed710f3152bf6640f5e87ec1d8be11da82a2ebe Mon Sep 17 00:00:00 2001 From: steampunkcoder Date: Fri, 22 Jun 2018 09:28:45 -0700 Subject: [PATCH 2/2] Fix travis builds by adding vendor/gopkg.in/pipe.v2 symlink and removing test builds against golang 1.[23456] b/c they don't support golang context pkg --- .travis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 42e8d1b..b8a5105 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,5 @@ language: go go: -- "1.2.x" -- "1.3.x" -- "1.4.x" -- "1.5.x" -- "1.6.x" - "1.7.x" - "1.8.x" - "1.9.x" @@ -13,6 +8,8 @@ go: - master before_install: - go get gopkg.in/check.v1 +- mkdir -p vendor/gopkg.in +- ln -s $(pwd) vendor/gopkg.in/pipe.v2 scripts: - make test