| // Copyright 2009 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // +build !windows,!nacl,!plan9 | |
| package syslog | |
| import ( | |
| "bufio" | |
| "fmt" | |
| "io" | |
| "io/ioutil" | |
| "log" | |
| "net" | |
| "os" | |
| "runtime" | |
| "sync" | |
| "testing" | |
| "time" | |
| ) | |
| func runPktSyslog(c net.PacketConn, done chan<- string) { | |
| var buf [4096]byte | |
| var rcvd string | |
| ct := 0 | |
| for { | |
| var n int | |
| var err error | |
| c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) | |
| n, _, err = c.ReadFrom(buf[:]) | |
| rcvd += string(buf[:n]) | |
| if err != nil { | |
| if oe, ok := err.(*net.OpError); ok { | |
| if ct < 3 && oe.Temporary() { | |
| ct++ | |
| continue | |
| } | |
| } | |
| break | |
| } | |
| } | |
| c.Close() | |
| done <- rcvd | |
| } | |
| var crashy = false | |
| func testableNetwork(network string) bool { | |
| switch network { | |
| case "unix", "unixgram": | |
| switch runtime.GOOS { | |
| case "darwin": | |
| switch runtime.GOARCH { | |
| case "arm", "arm64": | |
| return false | |
| } | |
| case "android": | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| func runStreamSyslog(l net.Listener, done chan<- string, wg *sync.WaitGroup) { | |
| for { | |
| var c net.Conn | |
| var err error | |
| if c, err = l.Accept(); err != nil { | |
| return | |
| } | |
| wg.Add(1) | |
| go func(c net.Conn) { | |
| defer wg.Done() | |
| c.SetReadDeadline(time.Now().Add(5 * time.Second)) | |
| b := bufio.NewReader(c) | |
| for ct := 1; !crashy || ct&7 != 0; ct++ { | |
| s, err := b.ReadString('\n') | |
| if err != nil { | |
| break | |
| } | |
| done <- s | |
| } | |
| c.Close() | |
| }(c) | |
| } | |
| } | |
| func startServer(n, la string, done chan<- string) (addr string, sock io.Closer, wg *sync.WaitGroup) { | |
| if n == "udp" || n == "tcp" { | |
| la = "127.0.0.1:0" | |
| } else { | |
| // unix and unixgram: choose an address if none given | |
| if la == "" { | |
| // use ioutil.TempFile to get a name that is unique | |
| f, err := ioutil.TempFile("", "syslogtest") | |
| if err != nil { | |
| log.Fatal("TempFile: ", err) | |
| } | |
| f.Close() | |
| la = f.Name() | |
| } | |
| os.Remove(la) | |
| } | |
| wg = new(sync.WaitGroup) | |
| if n == "udp" || n == "unixgram" { | |
| l, e := net.ListenPacket(n, la) | |
| if e != nil { | |
| log.Fatalf("startServer failed: %v", e) | |
| } | |
| addr = l.LocalAddr().String() | |
| sock = l | |
| wg.Add(1) | |
| go func() { | |
| defer wg.Done() | |
| runPktSyslog(l, done) | |
| }() | |
| } else { | |
| l, e := net.Listen(n, la) | |
| if e != nil { | |
| log.Fatalf("startServer failed: %v", e) | |
| } | |
| addr = l.Addr().String() | |
| sock = l | |
| wg.Add(1) | |
| go func() { | |
| defer wg.Done() | |
| runStreamSyslog(l, done, wg) | |
| }() | |
| } | |
| return | |
| } | |
| func TestWithSimulated(t *testing.T) { | |
| msg := "Test 123" | |
| var transport []string | |
| for _, n := range []string{"unix", "unixgram", "udp", "tcp"} { | |
| if testableNetwork(n) { | |
| transport = append(transport, n) | |
| } | |
| } | |
| for _, tr := range transport { | |
| done := make(chan string) | |
| addr, sock, srvWG := startServer(tr, "", done) | |
| defer srvWG.Wait() | |
| defer sock.Close() | |
| if tr == "unix" || tr == "unixgram" { | |
| defer os.Remove(addr) | |
| } | |
| s, err := Dial(tr, addr, LOG_INFO|LOG_USER, "syslog_test") | |
| if err != nil { | |
| t.Fatalf("Dial() failed: %v", err) | |
| } | |
| err = s.Info(msg) | |
| if err != nil { | |
| t.Fatalf("log failed: %v", err) | |
| } | |
| check(t, msg, <-done) | |
| s.Close() | |
| } | |
| } | |
| func TestFlap(t *testing.T) { | |
| net := "unix" | |
| if !testableNetwork(net) { | |
| t.Skipf("skipping on %s/%s; 'unix' is not supported", runtime.GOOS, runtime.GOARCH) | |
| } | |
| done := make(chan string) | |
| addr, sock, srvWG := startServer(net, "", done) | |
| defer srvWG.Wait() | |
| defer os.Remove(addr) | |
| defer sock.Close() | |
| s, err := Dial(net, addr, LOG_INFO|LOG_USER, "syslog_test") | |
| if err != nil { | |
| t.Fatalf("Dial() failed: %v", err) | |
| } | |
| msg := "Moo 2" | |
| err = s.Info(msg) | |
| if err != nil { | |
| t.Fatalf("log failed: %v", err) | |
| } | |
| check(t, msg, <-done) | |
| // restart the server | |
| _, sock2, srvWG2 := startServer(net, addr, done) | |
| defer srvWG2.Wait() | |
| defer sock2.Close() | |
| // and try retransmitting | |
| msg = "Moo 3" | |
| err = s.Info(msg) | |
| if err != nil { | |
| t.Fatalf("log failed: %v", err) | |
| } | |
| check(t, msg, <-done) | |
| s.Close() | |
| } | |
| func TestNew(t *testing.T) { | |
| if LOG_LOCAL7 != 23<<3 { | |
| t.Fatalf("LOG_LOCAL7 has wrong value") | |
| } | |
| if testing.Short() { | |
| // Depends on syslog daemon running, and sometimes it's not. | |
| t.Skip("skipping syslog test during -short") | |
| } | |
| s, err := New(LOG_INFO|LOG_USER, "the_tag") | |
| if err != nil { | |
| t.Fatalf("New() failed: %s", err) | |
| } | |
| // Don't send any messages. | |
| s.Close() | |
| } | |
| func TestNewLogger(t *testing.T) { | |
| if testing.Short() { | |
| t.Skip("skipping syslog test during -short") | |
| } | |
| f, err := NewLogger(LOG_USER|LOG_INFO, 0) | |
| if f == nil { | |
| t.Error(err) | |
| } | |
| } | |
| func TestDial(t *testing.T) { | |
| if testing.Short() { | |
| t.Skip("skipping syslog test during -short") | |
| } | |
| f, err := Dial("", "", (LOG_LOCAL7|LOG_DEBUG)+1, "syslog_test") | |
| if f != nil { | |
| t.Fatalf("Should have trapped bad priority") | |
| } | |
| f, err = Dial("", "", -1, "syslog_test") | |
| if f != nil { | |
| t.Fatalf("Should have trapped bad priority") | |
| } | |
| l, err := Dial("", "", LOG_USER|LOG_ERR, "syslog_test") | |
| if err != nil { | |
| t.Fatalf("Dial() failed: %s", err) | |
| } | |
| l.Close() | |
| } | |
| func check(t *testing.T, in, out string) { | |
| tmpl := fmt.Sprintf("<%d>%%s %%s syslog_test[%%d]: %s\n", LOG_USER+LOG_INFO, in) | |
| if hostname, err := os.Hostname(); err != nil { | |
| t.Error("Error retrieving hostname") | |
| } else { | |
| var parsedHostname, timestamp string | |
| var pid int | |
| if n, err := fmt.Sscanf(out, tmpl, ×tamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname { | |
| t.Errorf("Got %q, does not match template %q (%d %s)", out, tmpl, n, err) | |
| } | |
| } | |
| } | |
| func TestWrite(t *testing.T) { | |
| tests := []struct { | |
| pri Priority | |
| pre string | |
| msg string | |
| exp string | |
| }{ | |
| {LOG_USER | LOG_ERR, "syslog_test", "", "%s %s syslog_test[%d]: \n"}, | |
| {LOG_USER | LOG_ERR, "syslog_test", "write test", "%s %s syslog_test[%d]: write test\n"}, | |
| // Write should not add \n if there already is one | |
| {LOG_USER | LOG_ERR, "syslog_test", "write test 2\n", "%s %s syslog_test[%d]: write test 2\n"}, | |
| } | |
| if hostname, err := os.Hostname(); err != nil { | |
| t.Fatalf("Error retrieving hostname") | |
| } else { | |
| for _, test := range tests { | |
| done := make(chan string) | |
| addr, sock, srvWG := startServer("udp", "", done) | |
| defer srvWG.Wait() | |
| defer sock.Close() | |
| l, err := Dial("udp", addr, test.pri, test.pre) | |
| if err != nil { | |
| t.Fatalf("syslog.Dial() failed: %v", err) | |
| } | |
| defer l.Close() | |
| _, err = io.WriteString(l, test.msg) | |
| if err != nil { | |
| t.Fatalf("WriteString() failed: %v", err) | |
| } | |
| rcvd := <-done | |
| test.exp = fmt.Sprintf("<%d>", test.pri) + test.exp | |
| var parsedHostname, timestamp string | |
| var pid int | |
| if n, err := fmt.Sscanf(rcvd, test.exp, ×tamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname { | |
| t.Errorf("s.Info() = '%q', didn't match '%q' (%d %s)", rcvd, test.exp, n, err) | |
| } | |
| } | |
| } | |
| } | |
| func TestConcurrentWrite(t *testing.T) { | |
| addr, sock, srvWG := startServer("udp", "", make(chan string, 1)) | |
| defer srvWG.Wait() | |
| defer sock.Close() | |
| w, err := Dial("udp", addr, LOG_USER|LOG_ERR, "how's it going?") | |
| if err != nil { | |
| t.Fatalf("syslog.Dial() failed: %v", err) | |
| } | |
| var wg sync.WaitGroup | |
| for i := 0; i < 10; i++ { | |
| wg.Add(1) | |
| go func() { | |
| defer wg.Done() | |
| err := w.Info("test") | |
| if err != nil { | |
| t.Errorf("Info() failed: %v", err) | |
| return | |
| } | |
| }() | |
| } | |
| wg.Wait() | |
| } | |
| func TestConcurrentReconnect(t *testing.T) { | |
| crashy = true | |
| defer func() { crashy = false }() | |
| const N = 10 | |
| const M = 100 | |
| net := "unix" | |
| if !testableNetwork(net) { | |
| net = "tcp" | |
| if !testableNetwork(net) { | |
| t.Skipf("skipping on %s/%s; neither 'unix' or 'tcp' is supported", runtime.GOOS, runtime.GOARCH) | |
| } | |
| } | |
| done := make(chan string, N*M) | |
| addr, sock, srvWG := startServer(net, "", done) | |
| if net == "unix" { | |
| defer os.Remove(addr) | |
| } | |
| // count all the messages arriving | |
| count := make(chan int) | |
| go func() { | |
| ct := 0 | |
| for range done { | |
| ct++ | |
| // we are looking for 500 out of 1000 events | |
| // here because lots of log messages are lost | |
| // in buffers (kernel and/or bufio) | |
| if ct > N*M/2 { | |
| break | |
| } | |
| } | |
| count <- ct | |
| }() | |
| var wg sync.WaitGroup | |
| wg.Add(N) | |
| for i := 0; i < N; i++ { | |
| go func() { | |
| defer wg.Done() | |
| w, err := Dial(net, addr, LOG_USER|LOG_ERR, "tag") | |
| if err != nil { | |
| t.Fatalf("syslog.Dial() failed: %v", err) | |
| } | |
| defer w.Close() | |
| for i := 0; i < M; i++ { | |
| err := w.Info("test") | |
| if err != nil { | |
| t.Errorf("Info() failed: %v", err) | |
| return | |
| } | |
| } | |
| }() | |
| } | |
| wg.Wait() | |
| sock.Close() | |
| srvWG.Wait() | |
| close(done) | |
| select { | |
| case <-count: | |
| case <-time.After(100 * time.Millisecond): | |
| t.Error("timeout in concurrent reconnect") | |
| } | |
| } |