diff --git a/client/commands.go b/client/commands.go index 20e3a66..164e04e 100644 --- a/client/commands.go +++ b/client/commands.go @@ -96,8 +96,14 @@ func (conn *Conn) User(ident, name string) { conn.Raw(USER + " " + ident + " 12 * :" + name) } -// Join() sends a JOIN command to the server -func (conn *Conn) Join(channel string) { conn.Raw(JOIN + " " + channel) } +// Join() sends a JOIN command to the server with an optional key +func (conn *Conn) Join(channel string, key ...string) { + k := "" + if len(key) > 0 { + k = " " + key[0] + } + conn.Raw(JOIN + " " + channel + k) +} // Part() sends a PART command to the server with an optional part message func (conn *Conn) Part(channel string, message ...string) { diff --git a/client/commands_test.go b/client/commands_test.go index dabaf61..a4d457e 100644 --- a/client/commands_test.go +++ b/client/commands_test.go @@ -97,6 +97,8 @@ func TestClientCommands(t *testing.T) { c.Join("#foo") s.nc.Expect("JOIN #foo") + c.Join("#foo bar") + s.nc.Expect("JOIN #foo bar") c.Part("#foo") s.nc.Expect("PART #foo") diff --git a/client/connection.go b/client/connection.go index 7f03c88..d94f736 100644 --- a/client/connection.go +++ b/client/connection.go @@ -23,8 +23,8 @@ type Conn struct { // Handlers intHandlers *hSet - fgHandlers *hSet - bgHandlers *hSet + fgHandlers *hSet + bgHandlers *hSet // State tracker for nicks and channels st state.Tracker @@ -83,6 +83,9 @@ type Config struct { // Split PRIVMSGs, NOTICEs and CTCPs longer than // SplitLen characters over multiple lines. SplitLen int + + // Timeout, The amount of time in seconds until a timeout is triggered. + Timeout time.Duration } func NewConfig(nick string, args ...string) *Config { @@ -92,6 +95,7 @@ func NewConfig(nick string, args ...string) *Config { NewNick: func(s string) string { return s + "_" }, Recover: (*Conn).LogPanic, // in dispatch.go SplitLen: 450, + Timeout: 60 * time.Second, } cfg.Me.Ident = "goirc" if len(args) > 0 && args[0] != "" { @@ -124,6 +128,7 @@ func Client(cfg *Config) *Conn { } dialer := new(net.Dialer) + dialer.Timeout = cfg.Timeout if cfg.LocalAddr != "" { if !hasPort(cfg.LocalAddr) { cfg.LocalAddr += ":0" diff --git a/state/tracker_test.go b/state/tracker_test.go index 165b5aa..5e27f29 100644 --- a/state/tracker_test.go +++ b/state/tracker_test.go @@ -1,6 +1,9 @@ package state -import "testing" +import ( + "sync" + "testing" +) // There is some awkwardness in these tests. Items retrieved directly from the // state trackers internal maps are private and only have private, @@ -560,3 +563,56 @@ func TestSTWipe(t *testing.T) { t.Errorf("Nick chan lists wrong length after wipe.") } } + +func TestSTRaces(t *testing.T) { + st := NewTracker("mynick") + wg := sync.WaitGroup{} + + for i := 'a'; i < 'g'; i++ { + wg.Add(2) + go func(s string) { + st.NewNick("nick-" + s) + c := st.NewChannel("#chan-" + s) + st.Associate(c, st.me) + wg.Done() + }(string(i)) + go func(s string) { + n := st.GetNick("nick-" + s) + c := st.GetChannel("#chan-" + s) + st.Associate(c, n) + wg.Done() + }(string(i)) + } + wg.Wait() + + wg = sync.WaitGroup{} + race := func(ns, cs string) { + wg.Add(5) + go func() { + st.Associate(st.GetChannel("#chan-"+cs), st.GetNick("nick-"+ns)) + wg.Done() + }() + go func() { + st.GetNick("nick-"+ns).Channels() + wg.Done() + }() + go func() { + st.GetChannel("#chan-"+cs).Nicks() + wg.Done() + }() + go func() { + st.Dissociate(st.GetChannel("#chan-"+cs), st.GetNick("nick-"+ns)) + wg.Done() + }() + go func() { + st.ReNick("nick-"+ns, "nick2-"+ns) + wg.Done() + }() + } + for n := 'a'; n < 'g'; n++ { + for c := 'a'; c < 'g'; c++ { + race(string(n), string(c)) + } + } + wg.Wait() +}