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 }