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") } }