mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Control-D-Inc/ctrld"
|
|
)
|
|
|
|
// streamRecorder collects a streamed response under a lock, because the
|
|
// handler writes from its own goroutine while the test reads.
|
|
type streamRecorder struct {
|
|
mu sync.Mutex
|
|
header http.Header
|
|
body strings.Builder
|
|
}
|
|
|
|
func (r *streamRecorder) Header() http.Header { return r.header }
|
|
|
|
func (r *streamRecorder) Write(p []byte) (int, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return r.body.Write(p)
|
|
}
|
|
|
|
func (r *streamRecorder) WriteHeader(int) {}
|
|
|
|
func (r *streamRecorder) Flush() {}
|
|
|
|
func (r *streamRecorder) String() string {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return r.body.String()
|
|
}
|
|
|
|
// waitForText polls the recorder until want arrives or the deadline passes.
|
|
func waitForText(rec *streamRecorder, want string, timeout time.Duration) bool {
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
if strings.Contains(rec.String(), want) {
|
|
return true
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Test_logTailFollowsTheRotatedLogPath keeps one tail request open while the
|
|
// log_path file rotates. The lines after the rotation must reach the client.
|
|
func Test_logTailFollowsTheRotatedLogPath(t *testing.T) {
|
|
origUID := cdUID
|
|
cdUID = ""
|
|
t.Cleanup(func() { cdUID = origUID })
|
|
|
|
path := filepath.Join(t.TempDir(), "ctrld.log")
|
|
rf, err := newRotatingFile(path, logBudget{maxSize: 64, backups: 1}, nil)
|
|
if err != nil {
|
|
t.Fatalf("newRotatingFile: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = rf.close() })
|
|
if _, err := rf.Write([]byte("before rotation\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
p := &prog{
|
|
cfg: &ctrld.Config{Service: ctrld.ServiceConfig{LogPath: path}},
|
|
cs: &controlServer{mux: http.NewServeMux()},
|
|
}
|
|
p.registerControlServerHandler()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
req := httptest.NewRequest(http.MethodGet, tailLogsPath+"?lines=1", nil).WithContext(ctx)
|
|
rec := &streamRecorder{header: http.Header{}}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
p.cs.mux.ServeHTTP(rec, req)
|
|
}()
|
|
t.Cleanup(func() {
|
|
cancel()
|
|
<-done
|
|
})
|
|
|
|
if !waitForText(rec, "before rotation", 2*time.Second) {
|
|
t.Fatalf("the first line did not arrive: %q", rec.String())
|
|
}
|
|
for i := 0; i < 4; i++ {
|
|
if _, err := rf.Write([]byte("filler line that crosses the budget\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, err := rf.Write([]byte("after rotation sentinel\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !waitForText(rec, "after rotation sentinel", 3*time.Second) {
|
|
t.Fatalf("the sentinel did not arrive after the rotation: %q", rec.String())
|
|
}
|
|
}
|