// 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,!js 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) { t.Parallel() 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 { if err.Error() == "Unix syslog delivery error" { t.Skip("skipping: syslogd not running") } 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 { if err.Error() == "Unix syslog delivery error" { t.Skip("skipping: syslogd not running") } 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 { if err.Error() == "Unix syslog delivery error" { t.Skip("skipping: syslogd not running") } 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) { t.Parallel() 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.Errorf("syslog.Dial() failed: %v", err) return } 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") } }