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