| // Copyright 2020 Marc-Antoine Ruel. All rights reserved. | |
| // Use of this source code is governed under the Apache License, Version 2.0 | |
| // that can be found in the LICENSE file. | |
| // Tests and benchmarks HTTP snapshots, errors, methods, and memory limits. | |
| package webstack | |
| import ( | |
| "context" | |
| "net/http" | |
| "net/http/httptest" | |
| "sync" | |
| "testing" | |
| ) | |
| //nolint:paralleltest // Concurrent handlers change the goroutines being captured. | |
| func TestSnapshotHandler(t *testing.T) { | |
| data := []string{ | |
| "/debug", | |
| "/debug?augment=1", | |
| "/debug?maxmem=1", | |
| "/debug?maxmem=2097152", | |
| "/debug?similarity=exactflags", | |
| "/debug?similarity=exactlines", | |
| "/debug?similarity=anypointer", | |
| "/debug?similarity=anyvalue", | |
| } | |
| //nolint:paralleltest // Snapshot contents depend on active goroutines. | |
| for _, url := range data { | |
| t.Run(url, func(t *testing.T) { | |
| req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, url, nil) | |
| w := httptest.NewRecorder() | |
| SnapshotHandler(w, req) | |
| if w.Code != 200 { | |
| t.Fatalf("%s: %d\n%s", url, w.Code, w.Body.String()) | |
| } | |
| }) | |
| } | |
| } | |
| func TestSnapshotHandler_Err(t *testing.T) { | |
| t.Parallel() | |
| data := []string{ | |
| "/debug?augment=2", | |
| "/debug?maxmem=abc", | |
| "/debug?similarity=alike", | |
| } | |
| for _, url := range data { | |
| t.Run(url, func(t *testing.T) { | |
| t.Parallel() | |
| req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, url, nil) | |
| w := httptest.NewRecorder() | |
| SnapshotHandler(w, req) | |
| if w.Code != 400 { | |
| t.Fatalf("%s: %d\n%s", url, w.Code, w.Body.String()) | |
| } | |
| }) | |
| } | |
| } | |
| func TestSnapshotHandler_Method_POST(t *testing.T) { | |
| t.Parallel() | |
| req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/debug", nil) | |
| w := httptest.NewRecorder() | |
| SnapshotHandler(w, req) | |
| if w.Code != 405 { | |
| t.Fatalf("%d\n%s", w.Code, w.Body.String()) | |
| } | |
| } | |
| //nolint:paralleltest // This test intentionally creates thousands of goroutines. | |
| func TestSnapshotHandler_LargeMemory(t *testing.T) { | |
| // Try to create a stack frame over 1MiB in size when serialized to string. | |
| // This is tricky since this is dependent on many factors out of our control. | |
| // Do this by starting a lot of callbacks with a lot of arguments. | |
| wg := sync.WaitGroup{} | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| defer cancel() | |
| a := 0 | |
| alive := make(chan struct{}) | |
| // Assuming >400 bytes per goroutine, 2500 parallel goroutines is enough to | |
| // use more than 1MiB of call stack. We must not put it too high or it'll | |
| // crash on Travis. | |
| const parallel = 2500 | |
| for range parallel { | |
| wg.Go(func() { | |
| alive <- struct{}{} | |
| dummy(ctx, &a, &a, &a, &a, &a, &a, &a, &a, &a) | |
| }) | |
| } | |
| for range parallel { | |
| <-alive | |
| } | |
| // Normal. | |
| req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/debug", nil) | |
| w := httptest.NewRecorder() | |
| SnapshotHandler(w, req) | |
| if w.Code != 200 { | |
| t.Fatalf("%d\n%s", w.Code, w.Body.String()) | |
| } | |
| // Cut off. That's 1<<20 + 1 | |
| req = httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/debug?maxmem=1048577", nil) | |
| w = httptest.NewRecorder() | |
| SnapshotHandler(w, req) | |
| // It can result in a 500 because the cut off is arbitrary, making parsing to | |
| // fail. | |
| if w.Code != 200 && w.Code != 500 { | |
| t.Fatalf("%d\n%s", w.Code, w.Body.String()) | |
| } | |
| cancel() | |
| wg.Wait() | |
| } | |
| func BenchmarkSnapshotHandle(b *testing.B) { | |
| // TODO(maruel): We should hook runtime.Stack() to make it a deterministic | |
| // output with internaltest.StaticPanicwebOutput(). | |
| b.ReportAllocs() | |
| req := httptest.NewRequestWithContext(b.Context(), http.MethodGet, "/", nil) | |
| b.ResetTimer() | |
| for range b.N { | |
| w := httptest.NewRecorder() | |
| SnapshotHandler(w, req) | |
| if w.Code != 200 { | |
| b.Fatalf("%d\n%s", w.Code, w.Body.String()) | |
| } | |
| } | |
| } | |
| //nolint:revive,unparam // Arguments intentionally enlarge the captured stack frame. | |
| func dummy(ctx context.Context, a1, a2, a3, a4, a5, a6, a7, a8, a9 *int) { | |
| <-ctx.Done() | |
| } |