1package sentry 2 3import ( 4 "context" 5 "fmt" 6 "sync" 7 "testing" 8 9 "github.com/google/go-cmp/cmp" 10 "github.com/google/go-cmp/cmp/cmpopts" 11) 12 13func setupHubTest() (*Hub, *Client, *Scope) { 14 client, _ := NewClient(ClientOptions{Dsn: "http://whatever@really.com/1337"}) 15 scope := NewScope() 16 hub := NewHub(client, scope) 17 return hub, client, scope 18} 19 20func TestNewHubPushesLayerOnTopOfStack(t *testing.T) { 21 hub, _, _ := setupHubTest() 22 assertEqual(t, len(*hub.stack), 1) 23} 24 25func TestNewHubLayerStoresClientAndScope(t *testing.T) { 26 hub, client, scope := setupHubTest() 27 assertEqual(t, &layer{client: client, scope: scope}, (*hub.stack)[0]) 28} 29 30func TestCloneHubInheritsClientAndScope(t *testing.T) { 31 hub, client, scope := setupHubTest() 32 clone := hub.Clone() 33 34 if hub == clone { 35 t.Error("Cloned hub should be a new instance") 36 } 37 38 if clone.Client() != client { 39 t.Error("Client should be inherited") 40 } 41 42 if clone.Scope() == scope { 43 t.Error("Scope should be cloned, not reused") 44 } 45 46 assertEqual(t, clone.Scope(), scope) 47} 48 49func TestPushScopeAddsScopeOnTopOfStack(t *testing.T) { 50 hub, _, _ := setupHubTest() 51 hub.PushScope() 52 assertEqual(t, len(*hub.stack), 2) 53} 54 55func TestPushScopeInheritsScopeData(t *testing.T) { 56 hub, _, scope := setupHubTest() 57 scope.SetExtra("foo", "bar") 58 hub.PushScope() 59 scope.SetExtra("baz", "qux") 60 61 if (*hub.stack)[0].scope == (*hub.stack)[1].scope { 62 t.Error("Scope shouldnt point to the same struct") 63 } 64 assertEqual(t, map[string]interface{}{"foo": "bar", "baz": "qux"}, (*hub.stack)[0].scope.extra) 65 assertEqual(t, map[string]interface{}{"foo": "bar"}, (*hub.stack)[1].scope.extra) 66} 67 68func TestPushScopeInheritsClient(t *testing.T) { 69 hub, _, _ := setupHubTest() 70 hub.PushScope() 71 72 if (*hub.stack)[0].client != (*hub.stack)[1].client { 73 t.Error("Client should be inherited") 74 } 75} 76 77func TestPopScopeRemovesLayerFromTheStack(t *testing.T) { 78 hub, _, _ := setupHubTest() 79 hub.PushScope() 80 hub.PushScope() 81 hub.PopScope() 82 83 assertEqual(t, len(*hub.stack), 2) 84} 85 86func TestPopScopeCannotLeaveStackEmpty(t *testing.T) { 87 hub, _, _ := setupHubTest() 88 assertEqual(t, len(*hub.stack), 1) 89 hub.PopScope() 90 assertEqual(t, len(*hub.stack), 1) 91} 92 93func TestBindClient(t *testing.T) { 94 hub, client, _ := setupHubTest() 95 hub.PushScope() 96 newClient, _ := NewClient(ClientOptions{Dsn: "http://whatever@really.com/1337"}) 97 hub.BindClient(newClient) 98 99 if (*hub.stack)[0].client == (*hub.stack)[1].client { 100 t.Error("Two stack layers should have different clients bound") 101 } 102 if (*hub.stack)[0].client != client { 103 t.Error("Stack's parent layer should have old client bound") 104 } 105 if (*hub.stack)[1].client != newClient { 106 t.Error("Stack's top layer should have new client bound") 107 } 108} 109 110func TestWithScopeCreatesIsolatedScope(t *testing.T) { 111 hub, _, _ := setupHubTest() 112 113 hub.WithScope(func(scope *Scope) { 114 assertEqual(t, len(*hub.stack), 2) 115 }) 116 117 assertEqual(t, len(*hub.stack), 1) 118} 119 120func TestWithScopeBindClient(t *testing.T) { 121 hub, client, _ := setupHubTest() 122 123 hub.WithScope(func(scope *Scope) { 124 newClient, _ := NewClient(ClientOptions{Dsn: "http://whatever@really.com/1337"}) 125 hub.BindClient(newClient) 126 if hub.stackTop().client != newClient { 127 t.Error("should use newly bound client") 128 } 129 }) 130 131 if hub.stackTop().client != client { 132 t.Error("should use old client") 133 } 134} 135 136func TestWithScopeDirectChanges(t *testing.T) { 137 hub, _, _ := setupHubTest() 138 hub.Scope().SetExtra("extra", "foo") 139 140 hub.WithScope(func(scope *Scope) { 141 scope.SetExtra("extra", "bar") 142 assertEqual(t, map[string]interface{}{"extra": "bar"}, hub.stackTop().scope.extra) 143 }) 144 145 assertEqual(t, map[string]interface{}{"extra": "foo"}, hub.stackTop().scope.extra) 146} 147 148func TestWithScopeChangesThroughConfigureScope(t *testing.T) { 149 hub, _, _ := setupHubTest() 150 hub.Scope().SetExtra("extra", "foo") 151 152 hub.WithScope(func(scope *Scope) { 153 hub.ConfigureScope(func(scope *Scope) { 154 scope.SetExtra("extra", "bar") 155 }) 156 assertEqual(t, map[string]interface{}{"extra": "bar"}, hub.stackTop().scope.extra) 157 }) 158 159 assertEqual(t, map[string]interface{}{"extra": "foo"}, hub.stackTop().scope.extra) 160} 161 162func TestConfigureScope(t *testing.T) { 163 hub, _, _ := setupHubTest() 164 hub.Scope().SetExtra("extra", "foo") 165 166 hub.ConfigureScope(func(scope *Scope) { 167 scope.SetExtra("extra", "bar") 168 assertEqual(t, map[string]interface{}{"extra": "bar"}, hub.stackTop().scope.extra) 169 }) 170 171 assertEqual(t, map[string]interface{}{"extra": "bar"}, hub.stackTop().scope.extra) 172} 173 174func TestLastEventID(t *testing.T) { 175 uuid := EventID(uuid()) 176 hub := &Hub{lastEventID: uuid} 177 assertEqual(t, uuid, hub.LastEventID()) 178} 179 180func TestLastEventIDUpdatesAfterCaptures(t *testing.T) { 181 hub, _, _ := setupHubTest() 182 183 messageID := hub.CaptureMessage("wat") 184 assertEqual(t, *messageID, hub.LastEventID()) 185 186 errorID := hub.CaptureException(fmt.Errorf("wat")) 187 assertEqual(t, *errorID, hub.LastEventID()) 188 189 eventID := hub.CaptureEvent(&Event{Message: "wat"}) 190 assertEqual(t, *eventID, hub.LastEventID()) 191} 192 193func TestAddBreadcrumbRespectMaxBreadcrumbsOption(t *testing.T) { 194 hub, client, scope := setupHubTest() 195 client.options.MaxBreadcrumbs = 2 196 197 breadcrumb := &Breadcrumb{Message: "Breadcrumb"} 198 199 hub.AddBreadcrumb(breadcrumb, nil) 200 hub.AddBreadcrumb(breadcrumb, nil) 201 hub.AddBreadcrumb(breadcrumb, nil) 202 203 assertEqual(t, len(scope.breadcrumbs), 2) 204} 205 206func TestAddBreadcrumbSkipAllBreadcrumbsIfMaxBreadcrumbsIsLessThanZero(t *testing.T) { 207 hub, client, scope := setupHubTest() 208 client.options.MaxBreadcrumbs = -1 209 210 breadcrumb := &Breadcrumb{Message: "Breadcrumb"} 211 212 hub.AddBreadcrumb(breadcrumb, nil) 213 hub.AddBreadcrumb(breadcrumb, nil) 214 hub.AddBreadcrumb(breadcrumb, nil) 215 216 assertEqual(t, len(scope.breadcrumbs), 0) 217} 218 219func TestAddBreadcrumbShouldNeverExceedMaxBreadcrumbsConst(t *testing.T) { 220 hub, client, scope := setupHubTest() 221 client.options.MaxBreadcrumbs = 1000 222 223 breadcrumb := &Breadcrumb{Message: "Breadcrumb"} 224 225 for i := 0; i < 111; i++ { 226 hub.AddBreadcrumb(breadcrumb, nil) 227 } 228 229 assertEqual(t, len(scope.breadcrumbs), 100) 230} 231 232func TestAddBreadcrumbShouldWorkWithoutClient(t *testing.T) { 233 scope := NewScope() 234 hub := NewHub(nil, scope) 235 236 breadcrumb := &Breadcrumb{Message: "Breadcrumb"} 237 for i := 0; i < 111; i++ { 238 hub.AddBreadcrumb(breadcrumb, nil) 239 } 240 241 assertEqual(t, len(scope.breadcrumbs), 100) 242} 243 244func TestAddBreadcrumbCallsBeforeBreadcrumbCallback(t *testing.T) { 245 hub, client, scope := setupHubTest() 246 client.options.BeforeBreadcrumb = func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb { 247 breadcrumb.Message += "_wat" 248 return breadcrumb 249 } 250 251 hub.AddBreadcrumb(&Breadcrumb{Message: "Breadcrumb"}, nil) 252 253 assertEqual(t, len(scope.breadcrumbs), 1) 254 assertEqual(t, "Breadcrumb_wat", scope.breadcrumbs[0].Message) 255} 256 257func TestBeforeBreadcrumbCallbackCanDropABreadcrumb(t *testing.T) { 258 hub, client, scope := setupHubTest() 259 client.options.BeforeBreadcrumb = func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb { 260 return nil 261 } 262 263 hub.AddBreadcrumb(&Breadcrumb{Message: "Breadcrumb"}, nil) 264 hub.AddBreadcrumb(&Breadcrumb{Message: "Breadcrumb"}, nil) 265 266 assertEqual(t, len(scope.breadcrumbs), 0) 267} 268 269func TestBeforeBreadcrumbGetAccessToEventHint(t *testing.T) { 270 hub, client, scope := setupHubTest() 271 client.options.BeforeBreadcrumb = func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb { 272 if val, ok := (*hint)["foo"]; ok { 273 if val, ok := val.(string); ok { 274 breadcrumb.Message += val 275 } 276 } 277 278 return breadcrumb 279 } 280 281 hub.AddBreadcrumb(&Breadcrumb{Message: "Breadcrumb"}, &BreadcrumbHint{"foo": "_oh"}) 282 283 assertEqual(t, len(scope.breadcrumbs), 1) 284 assertEqual(t, "Breadcrumb_oh", scope.breadcrumbs[0].Message) 285} 286 287func TestHasHubOnContextReturnsTrueIfHubIsThere(t *testing.T) { 288 hub, _, _ := setupHubTest() 289 ctx := context.Background() 290 ctx = SetHubOnContext(ctx, hub) 291 assertEqual(t, true, HasHubOnContext(ctx)) 292} 293 294func TestHasHubOnContextReturnsFalseIfHubIsNotThere(t *testing.T) { 295 ctx := context.Background() 296 assertEqual(t, false, HasHubOnContext(ctx)) 297} 298 299func TestGetHubFromContext(t *testing.T) { 300 hub, _, _ := setupHubTest() 301 ctx := context.Background() 302 ctx = SetHubOnContext(ctx, hub) 303 hubFromContext := GetHubFromContext(ctx) 304 assertEqual(t, hub, hubFromContext) 305} 306 307func TestGetHubFromContextReturnsNilIfHubIsNotThere(t *testing.T) { 308 ctx := context.Background() 309 hub := GetHubFromContext(ctx) 310 if hub != nil { 311 t.Error("hub shouldnt be available on empty context") 312 } 313} 314 315func TestSetHubOnContextReturnsNewContext(t *testing.T) { 316 hub, _, _ := setupHubTest() 317 ctx := context.Background() 318 ctxWithHub := SetHubOnContext(ctx, hub) 319 if ctx == ctxWithHub { 320 t.Error("contexts should be different") 321 } 322} 323 324func TestConcurrentHubClone(t *testing.T) { 325 const goroutineCount = 3 326 327 hub, client, _ := setupHubTest() 328 transport := &TransportMock{} 329 client.Transport = transport 330 331 var wg sync.WaitGroup 332 wg.Add(goroutineCount) 333 for i := 1; i <= goroutineCount; i++ { 334 // Mutate hub in the main goroutine. 335 hub.PushScope() 336 hub.PopScope() 337 hub.BindClient(client) 338 // Clone scope in a new Goroutine as documented in 339 // https://docs.sentry.io/platforms/go/goroutines/. 340 go func(i int) { 341 defer wg.Done() 342 localHub := hub.Clone() 343 localHub.ConfigureScope(func(scope *Scope) { 344 scope.SetTag("secretTag", fmt.Sprintf("go#%d", i)) 345 }) 346 localHub.CaptureMessage(fmt.Sprintf("Hello from goroutine! #%d", i)) 347 }(i) 348 } 349 wg.Wait() 350 351 type TestEvent struct { 352 Message string 353 Tags map[string]string 354 } 355 356 want := []TestEvent{ 357 { 358 Message: "Hello from goroutine! #1", 359 Tags: map[string]string{"secretTag": "go#1"}, 360 }, 361 { 362 Message: "Hello from goroutine! #2", 363 Tags: map[string]string{"secretTag": "go#2"}, 364 }, 365 { 366 Message: "Hello from goroutine! #3", 367 Tags: map[string]string{"secretTag": "go#3"}, 368 }, 369 } 370 371 var got []TestEvent 372 for _, event := range transport.Events() { 373 got = append(got, TestEvent{ 374 Message: event.Message, 375 Tags: event.Tags, 376 }) 377 } 378 379 if diff := cmp.Diff(want, got, cmpopts.SortSlices(func(x, y TestEvent) bool { 380 return x.Message < y.Message 381 })); diff != "" { 382 t.Errorf("Events mismatch (-want +got):\n%s", diff) 383 } 384} 385