package testutil import ( "fmt" "os" "runtime" "strings" "testing" ) const ( envFlag = "GH_ANNOTATION" rootPath = "/linkerd2/" ) type level int const ( err level = iota warn ) func (l level) String() string { switch l { case err: return "error" case warn: return "warning" } panic(fmt.Sprintf("invalid level: %d", l)) } func echoAnnotation(t *testing.T, l level, args ...interface{}) { if _, ok := os.LookupEnv(envFlag); ok { _, fileName, fileLine, ok := runtime.Caller(3) if !ok { panic("Couldn't recover runtime info") } fileName = fileName[strings.LastIndex(fileName, rootPath)+len(rootPath):] // In case of coming from `t.Run(testName, ...)`, only take the first part // of the name; the following parts might not be as generic parts := strings.Split(t.Name(), "/") testName := parts[0] for _, arg := range args { msg := fmt.Sprintf("%s - %s", testName, arg) fmt.Printf("::%s file=%s,line=%d::%s\n", l, fileName, fileLine, msg) } } } func echoAnnotationErr(t *testing.T, args ...interface{}) { echoAnnotation(t, err, args...) } func echoAnnotationWarn(t *testing.T, args ...interface{}) { echoAnnotation(t, warn, args...) } // Error is a wrapper around t.Error() // args are passed to t.Error(args) and each arg will be sent to stdout formatted // as a GitHub annotation when the envFlag environment variable is set func Error(t *testing.T, args ...interface{}) { t.Helper() echoAnnotationErr(t, args...) t.Error(args...) } // AnnotatedError is similar to Error() but it also admits a msg string that // will be used as the GitHub annotation func AnnotatedError(t *testing.T, msg string, args ...interface{}) { t.Helper() echoAnnotationErr(t, msg) t.Error(args...) } // Errorf is a wrapper around t.Errorf() // format and args are passed to t.Errorf(format, args) and the formatted // message will be sent to stdout as a GitHub annotation when the envFlag // environment variable is set func Errorf(t *testing.T, format string, args ...interface{}) { t.Helper() echoAnnotationErr(t, fmt.Sprintf(format, args...)) t.Errorf(format, args...) } // AnnotatedErrorf is similar to Errorf() but it also admits a msg string that // will be used as the GitHub annotation func AnnotatedErrorf(t *testing.T, msg, format string, args ...interface{}) { t.Helper() echoAnnotationErr(t, msg) t.Errorf(format, args...) } // Fatal is a wrapper around t.Fatal() // args are passed to t.Fatal(args) and each arg will be sent to stdout formatted // as a GitHub annotation when the envFlag environment variable is set func Fatal(t *testing.T, args ...interface{}) { t.Helper() echoAnnotationErr(t, args) t.Fatal(args...) } // AnnotatedFatal is similar to Fatal() but it also admits a msg string that // will be used as the GitHub annotation func AnnotatedFatal(t *testing.T, msg string, args ...interface{}) { t.Helper() echoAnnotationErr(t, msg) t.Fatal(args...) } // Fatalf is a wrapper around t.Errorf() // format and args are passed to t.Fatalf(format, args) and the formatted // message will be sent to stdout as a GitHub annotation when the envFlag // environment variable is set func Fatalf(t *testing.T, format string, args ...interface{}) { t.Helper() echoAnnotationErr(t, fmt.Sprintf(format, args...)) t.Fatalf(format, args...) } // AnnotatedFatalf is similar to Fatalf() but it also admits a msg string that // will be used as the GitHub annotation func AnnotatedFatalf(t *testing.T, msg, format string, args ...interface{}) { t.Helper() echoAnnotationErr(t, msg) t.Fatalf(format, args...) } // AnnotatedWarn is a wrapper around t.Log() but it also admits a msg string that // will be used as the GitHub warning annotation func AnnotatedWarn(t *testing.T, msg string, args ...interface{}) { t.Helper() echoAnnotationWarn(t, msg) t.Log(args...) }