blob: c7226e53f407808714ddebb6cc7091a92aeec30a [file] [log] [blame] [edit]
// 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.
package webstack
import (
"context"
"net/http/httptest"
"sync"
"testing"
)
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",
}
for _, url := range data {
url := url
t.Run(url, func(t *testing.T) {
req := httptest.NewRequest("GET", 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 {
url := url
t.Run(url, func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest("GET", 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.NewRequest("POST", "/debug", nil)
w := httptest.NewRecorder()
SnapshotHandler(w, req)
if w.Code != 405 {
t.Fatalf("%d\n%s", w.Code, w.Body.String())
}
}
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 i := 0; i < parallel; i++ {
wg.Add(1)
go func() {
defer wg.Done()
alive <- struct{}{}
dummy(ctx, &a, &a, &a, &a, &a, &a, &a, &a, &a)
}()
}
for i := 0; i < parallel; i++ {
<-alive
}
// Normal.
req := httptest.NewRequest("GET", "/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.NewRequest("GET", "/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.NewRequest("GET", "/", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
SnapshotHandler(w, req)
if w.Code != 200 {
b.Fatalf("%d\n%s", w.Code, w.Body.String())
}
}
}
func dummy(ctx context.Context, a1, a2, a3, a4, a5, a6, a7, a8, a9 *int) {
<-ctx.Done()
}