Repository: gotestyourself/gotestsum
Pull request #110 (2020-05-15 19:54:29)

Remove logrus dependency
logrus is a full featured logging library, but gotestsum does not really need much of what it provides. All that is needed is a couple functions for printing a colored prefix and changing log levels.

Replace logrus with a small log package


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index ee50983..4a7ef42 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -9,10 +9,18 @@ linters-settings:
 
 issues:
   exclude-use-default: false
-  exclude:
-    # unparam
-    - 'result .* is always'
-    - 'always receives'
+  exclude-rules:
+    - linters: [golint]
+      text: 'should have comment .*or be unexported'
+    - linters: [stylecheck]
+      text: 'ST1000: at least one file in a package should have a package comment'
+    - linters: [errcheck]
+      path: log/
+      text: 'Error return value of `out.WriteString` is not checked'
+    - linters: [unparam]
+      text: 'result .* is always'
+    - linters: [unparam]
+      text: 'always receives'
 
 linters:
   disable-all: true
diff --git a/go.mod b/go.mod
index 4cfb8ee..ca284ba 100644
--- a/go.mod
+++ b/go.mod
@@ -6,16 +6,10 @@ require (
 	github.com/jonboulle/clockwork v0.1.0
 	github.com/mattn/go-colorable v0.0.9 // indirect
 	github.com/mattn/go-isatty v0.0.3
-	github.com/onsi/ginkgo v1.8.0 // indirect
-	github.com/onsi/gomega v1.4.3 // indirect
 	github.com/pkg/errors v0.8.1
-	github.com/sirupsen/logrus v1.0.5
 	github.com/spf13/pflag v1.0.3
-	github.com/stretchr/testify v1.3.0 // indirect
 	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
 	golang.org/x/sync v0.0.0-20190423024810-112230192c58
-	gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
-	gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
 	gotest.tools/v3 v3.0.2
 )
 
diff --git a/go.sum b/go.sum
index e12dde9..d94a752 100644
--- a/go.sum
+++ b/go.sum
@@ -1,64 +1,27 @@
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU=
 github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
-github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.0.5 h1:8c8b5uO0zS4X6RPl/sd1ENwSkIc0/H2PaHxE3udaE8I=
-github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
-gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
-gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
diff --git a/handler.go b/handler.go
index f6f2559..645b5f9 100644
--- a/handler.go
+++ b/handler.go
@@ -1,12 +1,13 @@
 package main
 
 import (
+	"fmt"
 	"io"
 	"os"
 
 	"github.com/pkg/errors"
-	log "github.com/sirupsen/logrus"
 	"gotest.tools/gotestsum/internal/junitxml"
+	"gotest.tools/gotestsum/log"
 	"gotest.tools/gotestsum/testjson"
 )
 
@@ -39,7 +40,7 @@ func (h *eventHandler) Event(event testjson.TestEvent, execution *testjson.Execu
 func (h *eventHandler) Close() error {
 	if h.jsonFile != nil {
 		if err := h.jsonFile.Close(); err != nil {
-			log.WithError(err).Error("failed to close JSON file")
+			log.Errorf("Failed to close JSON file: %v", err)
 		}
 	}
 	return nil
@@ -72,11 +73,11 @@ func writeJUnitFile(opts *options, execution *testjson.Execution) error {
 	}
 	junitFile, err := os.Create(opts.junitFile)
 	if err != nil {
-		return errors.Wrap(err, "failed to open JUnit file")
+		return fmt.Errorf("failed to open JUnit file: %v", err)
 	}
 	defer func() {
 		if err := junitFile.Close(); err != nil {
-			log.WithError(err).Error("failed to close JUnit file")
+			log.Errorf("Failed to close JUnit file: %v", err)
 		}
 	}()
 
diff --git a/internal/junitxml/report.go b/internal/junitxml/report.go
index 817b81c..1b2f95a 100644
--- a/internal/junitxml/report.go
+++ b/internal/junitxml/report.go
@@ -11,8 +11,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
+	"gotest.tools/gotestsum/log"
 	"gotest.tools/gotestsum/testjson"
 )
 
@@ -73,7 +72,10 @@ type FormatFunc func(string) string
 
 // Write creates an XML document and writes it to out.
 func Write(out io.Writer, exec *testjson.Execution, cfg Config) error {
-	return errors.Wrap(write(out, generate(exec, cfg)), "failed to write JUnit XML")
+	if err := write(out, generate(exec, cfg)); err != nil {
+		return fmt.Errorf("failed to write JUnit XML: %v", err)
+	}
+	return nil
 }
 
 func generate(exec *testjson.Execution, cfg Config) JUnitTestSuites {
@@ -129,11 +131,11 @@ func goVersion() string {
 	if version, ok := os.LookupEnv("GOVERSION"); ok {
 		return version
 	}
-	logrus.Debugf("exec: go version")
+	log.Debugf("exec: go version")
 	cmd := exec.Command("go", "version")
 	out, err := cmd.Output()
 	if err != nil {
-		logrus.WithError(err).Warn("failed to lookup go version for junit xml")
+		log.Warnf("Failed to lookup go version for junit xml: %v", err)
 		return "unknown"
 	}
 	return strings.TrimPrefix(strings.TrimSpace(string(out)), "go version ")
diff --git a/log/log.go b/log/log.go
new file mode 100644
index 0000000..7f4824c
--- /dev/null
+++ b/log/log.go
@@ -0,0 +1,60 @@
+package log
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/fatih/color"
+)
+
+type Level uint8
+
+const (
+	ErrorLevel Level = iota
+	WarnLevel
+	DebugLevel
+)
+
+var (
+	level              = WarnLevel
+	out   stringWriter = os.Stderr
+)
+
+// TODO: replace with io.StringWriter once support for go1.11 is dropped.
+type stringWriter interface {
+	WriteString(s string) (n int, err error)
+}
+
+// SetLevel for the global logger.
+func SetLevel(l Level) {
+	level = l
+}
+
+// Warnf prints the message to stderr, with a yellow WARN prefix.
+func Warnf(format string, args ...interface{}) {
+	if level < WarnLevel {
+		return
+	}
+	out.WriteString(color.YellowString("WARN "))
+	out.WriteString(fmt.Sprintf(format, args...))
+	out.WriteString("\n")
+}
+
+// Debugf prints the message to stderr, with no prefix.
+func Debugf(format string, args ...interface{}) {
+	if level < DebugLevel {
+		return
+	}
+	out.WriteString(fmt.Sprintf(format, args...))
+	out.WriteString("\n")
+}
+
+// Errorf prints the message to stderr, with a red ERROR prefix.
+func Errorf(format string, args ...interface{}) {
+	if level < ErrorLevel {
+		return
+	}
+	out.WriteString(color.RedString("ERROR "))
+	out.WriteString(fmt.Sprintf(format, args...))
+	out.WriteString("\n")
+}
diff --git a/main.go b/main.go
index e4f055e..843d9e9 100644
--- a/main.go
+++ b/main.go
@@ -10,8 +10,8 @@ import (
 
 	"github.com/fatih/color"
 	"github.com/pkg/errors"
-	log "github.com/sirupsen/logrus"
 	"github.com/spf13/pflag"
+	"gotest.tools/gotestsum/log"
 	"gotest.tools/gotestsum/testjson"
 )
 
@@ -24,7 +24,7 @@ func main() {
 	case err == pflag.ErrHelp:
 		os.Exit(0)
 	case err != nil:
-		log.Error(err.Error())
+		log.Errorf(err.Error())
 		flags.Usage()
 		os.Exit(1)
 	}
@@ -43,7 +43,7 @@ func main() {
 		// the same status code
 		os.Exit(ExitCodeWithDefault(err))
 	default:
-		fmt.Fprintln(os.Stderr, name+": Error: "+err.Error())
+		log.Errorf(err.Error())
 		os.Exit(3)
 	}
 }
@@ -125,7 +125,6 @@ func setupLogging(opts *options) {
 	color.NoColor = opts.noColor
 }
 
-// TODO: add flag --max-failures
 func run(opts *options) error {
 	ctx := context.Background()
 	goTestProc, err := startGoTest(ctx, goTestCmdArgs(opts))


Test patch:
diff --git a/testjson/dotformat.go b/testjson/dotformat.go
index a5c559a..f38c32d 100644
--- a/testjson/dotformat.go
+++ b/testjson/dotformat.go
@@ -8,9 +8,9 @@ import (
 	"strings"
 	"time"
 
-	"github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh/terminal"
 	"gotest.tools/gotestsum/internal/dotwriter"
+	"gotest.tools/gotestsum/log"
 )
 
 func dotsFormatV1(event TestEvent, exec *Execution) (string, error) {
@@ -70,7 +70,7 @@ func (l *dotLine) checkWidth(prefix, terminal int) {
 func newDotFormatter(out io.Writer) EventFormatter {
 	w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
 	if err != nil || w == 0 {
-		logrus.Warnf("Failed to detect terminal width for dots format, error: %v", err)
+		log.Warnf("Failed to detect terminal width for dots format, error: %v", err)
 		return &formatAdapter{format: dotsFormatV1, out: out}
 	}
 	return &dotFormatter{
diff --git a/testjson/execution.go b/testjson/execution.go
index 095290f..acb21c4 100644
--- a/testjson/execution.go
+++ b/testjson/execution.go
@@ -12,8 +12,8 @@ import (
 
 	"github.com/jonboulle/clockwork"
 	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
 	"golang.org/x/sync/errgroup"
+	"gotest.tools/gotestsum/log"
 )
 
 // Action of TestEvent
@@ -473,7 +473,7 @@ func isGoModuleOutput(scannerText string) bool {
 func parseEvent(raw []byte) (TestEvent, error) {
 	// TODO: this seems to be a bug in the `go test -json` output
 	if bytes.HasPrefix(raw, []byte("FAIL")) {
-		logrus.Warn(string(raw))
+		log.Warnf(string(raw))
 		return TestEvent{}, errBadEvent
 	}