| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- package xray
- import (
- "os"
- "path/filepath"
- "slices"
- "strings"
- "sync"
- "testing"
- )
- // TestLogWriterLastLineConcurrent exercises the LogWriter from multiple
- // goroutines: Xray drives Write while another goroutine (Process.GetResult)
- // reads the last line. Run under `go test -race` this fails on an unguarded
- // lastLine field and passes once the access is serialized.
- func TestLogWriterLastLineConcurrent(t *testing.T) {
- lw := NewLogWriter()
- const writers, readers, iterations = 4, 4, 500
- var wg sync.WaitGroup
- wg.Add(writers + readers)
- for range writers {
- go func() {
- defer wg.Done()
- for range iterations {
- _, _ = lw.Write([]byte("2024/01/01 00:00:00.000000 [Info] connection accepted\n"))
- }
- }()
- }
- for range readers {
- go func() {
- defer wg.Done()
- for range iterations {
- _ = lw.LastLine()
- }
- }()
- }
- wg.Wait()
- }
- func crashReportsIn(t *testing.T, dir string) []string {
- t.Helper()
- entries, err := os.ReadDir(dir)
- if err != nil {
- t.Fatal(err)
- }
- var contents []string
- for _, e := range entries {
- if strings.HasPrefix(e.Name(), crashReportPrefix) {
- b, err := os.ReadFile(filepath.Join(dir, e.Name()))
- if err != nil {
- t.Fatal(err)
- }
- contents = append(contents, string(b))
- }
- }
- return contents
- }
- // The Go runtime prints "panic: " with its own write(2), so the pipe hands it
- // over as a chunk of its own; the report must still hold the whole traceback.
- func TestLogWriterCrashReportHoldsWholePanic(t *testing.T) {
- dir := t.TempDir()
- t.Setenv("XUI_LOG_FOLDER", dir)
- chunks := []string{
- "panic: ",
- "runtime error: index out of range [3] with length 1\n",
- "\ngoroutine 1 [running]:\nmain.main()\n\t/src/main.go:5 +0x1d\n",
- "exit status 2",
- }
- lw := NewLogWriter()
- for _, c := range chunks {
- _, _ = lw.Write([]byte(c))
- }
- lw.Flush()
- got := crashReportsIn(t, dir)
- want := strings.Join(chunks, "")
- if len(got) != 1 || got[0] != want {
- t.Fatalf("crash reports = %q, want exactly [%q]", got, want)
- }
- if line := lw.LastLine(); line != "panic: runtime error: index out of range [3] with length 1" {
- t.Fatalf("LastLine = %q", line)
- }
- }
- func TestLogWriterOrdinaryLineMentioningPanicIsNotACrash(t *testing.T) {
- dir := t.TempDir()
- t.Setenv("XUI_LOG_FOLDER", dir)
- lw := NewLogWriter()
- _, _ = lw.Write([]byte("2026/07/12 15:16:00.000000 [Info] [42] proxy/vless: sniffed domain panic.example.com\n"))
- lw.Flush()
- if got := crashReportsIn(t, dir); len(got) != 0 {
- t.Fatalf("ordinary log line produced crash reports %q", got)
- }
- }
- func TestWriteCrashReportKeepsReportsFromTheSameSecond(t *testing.T) {
- dir := t.TempDir()
- t.Setenv("XUI_LOG_FOLDER", dir)
- for _, m := range []string{"panic: first", "panic: second"} {
- if err := writeCrashReport([]byte(m)); err != nil {
- t.Fatal(err)
- }
- }
- got := crashReportsIn(t, dir)
- slices.Sort(got)
- if !slices.Equal(got, []string{"panic: first", "panic: second"}) {
- t.Fatalf("crash reports = %q, want both", got)
- }
- }
|