go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/errors/annotate_test.go (about)

     1  // Copyright 2016 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package errors
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"regexp"
    21  	"strings"
    22  	"testing"
    23  
    24  	"go.chromium.org/luci/common/logging"
    25  	"go.chromium.org/luci/common/logging/memlogger"
    26  
    27  	. "github.com/smartystreets/goconvey/convey"
    28  )
    29  
    30  var (
    31  	fixSkip        = regexp.MustCompile(`skipped \d+ frames`)
    32  	fixNum         = regexp.MustCompile(`^#\d+`)
    33  	fixTestingLine = regexp.MustCompile(`(testing/\w+.go|\.\/_testmain\.go):\d+`)
    34  	fixSelfLN      = regexp.MustCompile(`(annotate_test\.go):\d+`)
    35  
    36  	excludedPkgs = []string{
    37  		`runtime`,
    38  		`github.com/jtolds/gls`,
    39  		`github.com/smartystreets/goconvey/convey`,
    40  	}
    41  )
    42  
    43  type emptyWrapper string
    44  
    45  func (e emptyWrapper) Error() string {
    46  	return string(e)
    47  }
    48  
    49  func (e emptyWrapper) Unwrap() error {
    50  	return nil
    51  }
    52  
    53  func FixForTest(lines []string) []string {
    54  	for i, l := range lines {
    55  		switch {
    56  		case strings.HasPrefix(l, "goroutine"):
    57  			l = "GOROUTINE LINE"
    58  		case strings.HasPrefix(l, "... skipped"):
    59  			l = fixSkip.ReplaceAllLiteralString(l, "skipped SOME frames")
    60  		}
    61  		l = fixNum.ReplaceAllLiteralString(l, "#?")
    62  		if strings.HasPrefix(l, "#? testing/") || strings.HasPrefix(l, "#? ./_testmain.go") {
    63  			l = fixTestingLine.ReplaceAllString(l, "$1:XXX")
    64  		}
    65  		l = fixSelfLN.ReplaceAllString(l, "$1:XX")
    66  		lines[i] = l
    67  	}
    68  	return lines
    69  }
    70  
    71  func TestAnnotation(t *testing.T) {
    72  	t.Parallel()
    73  
    74  	Convey("Test annotation struct", t, func() {
    75  		e := Annotate(New("bad thing"), "%d some error: %q", 20, "stringy").
    76  			InternalReason("extra(%.3f)", 8.2).Err()
    77  		ae := e.(*annotatedError)
    78  
    79  		Convey("annotation can render itself for public usage", func() {
    80  			So(ae.Error(), ShouldEqual, `20 some error: "stringy": bad thing`)
    81  		})
    82  
    83  		Convey("annotation can render itself for internal usage", func() {
    84  			lines := RenderStack(e, excludedPkgs...)
    85  			FixForTest(lines)
    86  
    87  			So(lines, ShouldResemble, []string{
    88  				`original error: bad thing`,
    89  				``,
    90  				`GOROUTINE LINE`,
    91  				`#? go.chromium.org/luci/common/errors/annotate_test.go:XX - errors.TestAnnotation.func1()`,
    92  				`  reason: 20 some error: "stringy"`,
    93  				`  internal reason: extra(8.200)`,
    94  				``,
    95  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
    96  				`... skipped SOME frames in pkg "github.com/jtolds/gls"...`,
    97  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
    98  				``,
    99  				`#? go.chromium.org/luci/common/errors/annotate_test.go:XX - errors.TestAnnotation()`,
   100  				`#? testing/testing.go:XXX - testing.tRunner()`,
   101  				`... skipped SOME frames in pkg "runtime"...`,
   102  			})
   103  		})
   104  
   105  		Convey("can render whole stack", func() {
   106  			e = Annotate(e, "outer frame %s", "outer").Err()
   107  			lines := RenderStack(e, excludedPkgs...)
   108  			FixForTest(lines)
   109  
   110  			expectedLines := []string{
   111  				`original error: bad thing`,
   112  				``,
   113  				`GOROUTINE LINE`,
   114  				`#? go.chromium.org/luci/common/errors/annotate_test.go:XX - errors.TestAnnotation.func1()`,
   115  				`  annotation #0:`,
   116  				`    reason: outer frame outer`,
   117  				`  annotation #1:`,
   118  				`    reason: 20 some error: "stringy"`,
   119  				`    internal reason: extra(8.200)`,
   120  				``,
   121  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
   122  				`... skipped SOME frames in pkg "github.com/jtolds/gls"...`,
   123  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
   124  				``,
   125  				`#? go.chromium.org/luci/common/errors/annotate_test.go:XX - errors.TestAnnotation()`,
   126  				`#? testing/testing.go:XXX - testing.tRunner()`,
   127  				`... skipped SOME frames in pkg "runtime"...`,
   128  			}
   129  			So(lines, ShouldResemble, expectedLines)
   130  
   131  			Convey("via Log", func() {
   132  				ctx := memlogger.Use(context.Background())
   133  				Log(ctx, e, excludedPkgs...)
   134  				ml := logging.Get(ctx).(*memlogger.MemLogger)
   135  				msgs := ml.Messages()
   136  				So(msgs, ShouldHaveLength, 1)
   137  				lines := strings.Split(msgs[0].Msg, "\n")
   138  				FixForTest(lines)
   139  				So(lines, ShouldResemble, expectedLines)
   140  			})
   141  			Convey("via Log in chunks", func() {
   142  				maxLogEntrySize = 200
   143  				ctx := memlogger.Use(context.Background())
   144  				Log(ctx, e, excludedPkgs...)
   145  				ml := logging.Get(ctx).(*memlogger.MemLogger)
   146  				var lines []string
   147  				for i, m := range ml.Messages() {
   148  					So(len(m.Msg), ShouldBeLessThan, maxLogEntrySize)
   149  					mLines := strings.Split(m.Msg, "\n")
   150  					if i > 0 {
   151  						So(mLines[0], ShouldEqual, "(continuation of error log)")
   152  						mLines = mLines[1:]
   153  					}
   154  					lines = append(lines, mLines...)
   155  				}
   156  				FixForTest(lines)
   157  				So(lines, ShouldResemble, expectedLines)
   158  			})
   159  		})
   160  
   161  		Convey(`can render external errors with Unwrap and no inner error`, func() {
   162  			So(RenderStack(emptyWrapper("hi")), ShouldResemble, []string{"hi"})
   163  		})
   164  
   165  		Convey(`can render external errors with Unwrap`, func() {
   166  			So(RenderStack(fmt.Errorf("outer: %w", fmt.Errorf("inner"))), ShouldResemble, []string{"outer: inner"})
   167  		})
   168  
   169  		Convey(`can render external errors using Unwrap when Annotated`, func() {
   170  			e := Annotate(fmt.Errorf("outer: %w", fmt.Errorf("inner")), "annotate").Err()
   171  			lines := RenderStack(e, excludedPkgs...)
   172  			FixForTest(lines)
   173  
   174  			expectedLines := []string{
   175  				`original error: outer: inner`,
   176  				``,
   177  				`GOROUTINE LINE`,
   178  				`#? go.chromium.org/luci/common/errors/annotate_test.go:XX - errors.TestAnnotation.func1.6()`,
   179  				`  reason: annotate`,
   180  				``,
   181  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
   182  				`... skipped SOME frames in pkg "github.com/jtolds/gls"...`,
   183  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
   184  				``,
   185  				`#? go.chromium.org/luci/common/errors/annotate_test.go:XX - errors.TestAnnotation.func1()`,
   186  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
   187  				`... skipped SOME frames in pkg "github.com/jtolds/gls"...`,
   188  				`... skipped SOME frames in pkg "github.com/smartystreets/goconvey/convey"...`,
   189  				``,
   190  				`#? go.chromium.org/luci/common/errors/annotate_test.go:XX - errors.TestAnnotation()`,
   191  				`#? testing/testing.go:XXX - testing.tRunner()`,
   192  				`... skipped SOME frames in pkg "runtime"...`,
   193  			}
   194  			So(lines, ShouldResemble, expectedLines)
   195  		})
   196  	})
   197  }