104 lines
2.6 KiB
Go
104 lines
2.6 KiB
Go
package collect
|
||
|
||
import (
|
||
"context"
|
||
"os"
|
||
"path/filepath"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
"git.opencomputing.cn/yumoqing/host-metrics-collector/internal/checkpoint"
|
||
"git.opencomputing.cn/yumoqing/host-metrics-collector/internal/model"
|
||
)
|
||
|
||
func writeTempLog(t *testing.T, content string) string {
|
||
t.Helper()
|
||
path := filepath.Join(t.TempDir(), "app.log")
|
||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||
t.Fatalf("write temp log: %v", err)
|
||
}
|
||
return path
|
||
}
|
||
|
||
func TestLogTailReadOnceEmitsCompleteLines(t *testing.T) {
|
||
path := writeTempLog(t, "line one\nline two\n")
|
||
|
||
var mu sync.Mutex
|
||
var got []model.LogRecord
|
||
tail := NewLogTail(path, checkpoint.NewMemoryStore(), time.Second, func(r model.LogRecord) {
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
got = append(got, r)
|
||
})
|
||
|
||
offset, partial := tail.readOnce(0, nil)
|
||
if offset != int64(len("line one\nline two\n")) {
|
||
t.Fatalf("offset = %d", offset)
|
||
}
|
||
if len(partial) != 0 {
|
||
t.Fatalf("partial = %q, want empty", partial)
|
||
}
|
||
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
if len(got) != 2 || got[0].Message != "line one" || got[1].Message != "line two" {
|
||
t.Fatalf("records = %+v", got)
|
||
}
|
||
if got[0].Level != "INFO" {
|
||
t.Fatalf("level = %q, want INFO", got[0].Level)
|
||
}
|
||
}
|
||
|
||
func TestLogTailReadOnceKeepsPartialLine(t *testing.T) {
|
||
path := writeTempLog(t, "partial line")
|
||
|
||
tail := NewLogTail(path, checkpoint.NewMemoryStore(), time.Second, nil)
|
||
offset, partial := tail.readOnce(0, nil)
|
||
if offset != 0 {
|
||
t.Fatalf("offset = %d, want 0 (no newline yet)", offset)
|
||
}
|
||
if string(partial) != "partial line" {
|
||
t.Fatalf("partial = %q", partial)
|
||
}
|
||
}
|
||
|
||
func TestLogTailReadOnceHandlesTruncation(t *testing.T) {
|
||
path := writeTempLog(t, "old content longer\n")
|
||
tail := NewLogTail(path, checkpoint.NewMemoryStore(), time.Second, nil)
|
||
|
||
// 模拟文件被轮转/截断:offset 超出文件大小,应从头读取。
|
||
if err := os.WriteFile(path, []byte("new\n"), 0o644); err != nil {
|
||
t.Fatalf("truncate: %v", err)
|
||
}
|
||
offset, partial := tail.readOnce(100, []byte("tail"))
|
||
if offset != int64(len("new\n")) {
|
||
t.Fatalf("offset = %d, want %d", offset, len("new\n"))
|
||
}
|
||
if len(partial) != 0 {
|
||
t.Fatalf("partial = %q", partial)
|
||
}
|
||
}
|
||
|
||
func TestLogTailRunStopsOnContextCancel(t *testing.T) {
|
||
path := writeTempLog(t, "hello\n")
|
||
tail := NewLogTail(path, checkpoint.NewMemoryStore(), 10*time.Millisecond, nil)
|
||
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
done := make(chan struct{})
|
||
go func() {
|
||
tail.Run(ctx)
|
||
close(done)
|
||
}()
|
||
|
||
// 等它至少消费一轮,再取消。
|
||
time.Sleep(50 * time.Millisecond)
|
||
cancel()
|
||
|
||
select {
|
||
case <-done:
|
||
case <-time.After(2 * time.Second):
|
||
t.Fatalf("LogTail.Run did not stop after cancel")
|
||
}
|
||
}
|