mirror of https://github.com/fluffle/goirc
Overhaul the state tracker to return copies of data that should be correct at the time of the response. Subsequent changes to tracked IRC state will not be reflected in the copies. For people fixing up their code because this merge broke everything, you probably want to be paying particular attention to the use of conn.Me() instead of conn.cfg.Me. Sorry if this causes you hassle, it's for the best. Lastly, if kballard is watching: sorry dude, you were mostly right ;-)
This commit is contained in:
commit
0cac69d2ee
|
@ -90,7 +90,7 @@ type Config struct {
|
||||||
|
|
||||||
func NewConfig(nick string, args ...string) *Config {
|
func NewConfig(nick string, args ...string) *Config {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Me: state.NewNick(nick),
|
Me: &state.Nick{Nick: nick},
|
||||||
PingFreq: 3 * time.Minute,
|
PingFreq: 3 * time.Minute,
|
||||||
NewNick: func(s string) string { return s + "_" },
|
NewNick: func(s string) string { return s + "_" },
|
||||||
Recover: (*Conn).LogPanic, // in dispatch.go
|
Recover: (*Conn).LogPanic, // in dispatch.go
|
||||||
|
@ -122,7 +122,7 @@ func Client(cfg *Config) *Conn {
|
||||||
cfg = NewConfig("__idiot__")
|
cfg = NewConfig("__idiot__")
|
||||||
}
|
}
|
||||||
if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
|
if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
|
||||||
cfg.Me = state.NewNick("__idiot__")
|
cfg.Me = &state.Nick{Nick: "__idiot__"}
|
||||||
cfg.Me.Ident = "goirc"
|
cfg.Me.Ident = "goirc"
|
||||||
cfg.Me.Name = "Powered by GoIRC"
|
cfg.Me.Name = "Powered by GoIRC"
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,11 @@ func (conn *Conn) Config() *Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) Me() *state.Nick {
|
func (conn *Conn) Me() *state.Nick {
|
||||||
|
conn.mu.RLock()
|
||||||
|
defer conn.mu.RUnlock()
|
||||||
|
if conn.st != nil {
|
||||||
|
conn.cfg.Me = conn.st.Me()
|
||||||
|
}
|
||||||
return conn.cfg.Me
|
return conn.cfg.Me
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +182,8 @@ func (conn *Conn) StateTracker() state.Tracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) EnableStateTracking() {
|
func (conn *Conn) EnableStateTracking() {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
if conn.st == nil {
|
if conn.st == nil {
|
||||||
n := conn.cfg.Me
|
n := conn.cfg.Me
|
||||||
conn.st = state.NewTracker(n.Nick)
|
conn.st = state.NewTracker(n.Nick)
|
||||||
|
@ -188,7 +195,10 @@ func (conn *Conn) EnableStateTracking() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) DisableStateTracking() {
|
func (conn *Conn) DisableStateTracking() {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
if conn.st != nil {
|
if conn.st != nil {
|
||||||
|
conn.cfg.Me = conn.st.Me()
|
||||||
conn.delSTHandlers()
|
conn.delSTHandlers()
|
||||||
conn.st.Wipe()
|
conn.st.Wipe()
|
||||||
conn.st = nil
|
conn.st = nil
|
||||||
|
|
|
@ -128,20 +128,23 @@ func TestClientAndStateTracking(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're expecting the untracked me to be replaced by a tracked one
|
// We're expecting the untracked me to be replaced by a tracked one
|
||||||
|
if c.st == nil {
|
||||||
|
t.Errorf("State tracker not enabled correctly.")
|
||||||
|
}
|
||||||
if me := c.cfg.Me; me.Nick != "test" || me.Ident != "test" ||
|
if me := c.cfg.Me; me.Nick != "test" || me.Ident != "test" ||
|
||||||
me.Name != "Testing IRC" || me.Host != "" {
|
me.Name != "Testing IRC" || me.Host != "" {
|
||||||
t.Errorf("Enabling state tracking did not replace Me correctly.")
|
t.Errorf("Enabling state tracking did not replace Me correctly.")
|
||||||
}
|
}
|
||||||
if c.st == nil || c.cfg.Me != c.st.Me() {
|
|
||||||
t.Errorf("State tracker not enabled correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, shim in the mock state tracker and test disabling state tracking
|
// Now, shim in the mock state tracker and test disabling state tracking
|
||||||
me := c.cfg.Me
|
me := c.cfg.Me
|
||||||
c.st = st
|
c.st = st
|
||||||
st.EXPECT().Wipe()
|
gomock.InOrder(
|
||||||
|
st.EXPECT().Me().Return(me),
|
||||||
|
st.EXPECT().Wipe(),
|
||||||
|
)
|
||||||
c.DisableStateTracking()
|
c.DisableStateTracking()
|
||||||
if c.st != nil || c.cfg.Me != me {
|
if c.st != nil || !c.cfg.Me.Equals(me) {
|
||||||
t.Errorf("State tracker not disabled correctly.")
|
t.Errorf("State tracker not disabled correctly.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,9 +49,14 @@ func (conn *Conn) h_001(line *Line) {
|
||||||
if idx := strings.LastIndex(t, " "); idx != -1 {
|
if idx := strings.LastIndex(t, " "); idx != -1 {
|
||||||
t = t[idx+1:]
|
t = t[idx+1:]
|
||||||
if idx = strings.Index(t, "@"); idx != -1 {
|
if idx = strings.Index(t, "@"); idx != -1 {
|
||||||
|
if conn.st != nil {
|
||||||
|
me := conn.Me()
|
||||||
|
conn.st.NickInfo(me.Nick, me.Ident, t[idx+1:], me.Name)
|
||||||
|
} else {
|
||||||
conn.cfg.Me.Host = t[idx+1:]
|
conn.cfg.Me.Host = t[idx+1:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: do we need 005 protocol support message parsing here?
|
// XXX: do we need 005 protocol support message parsing here?
|
||||||
|
@ -65,14 +70,15 @@ func (conn *Conn) h_001(line *Line) {
|
||||||
// Handler to deal with "433 :Nickname already in use"
|
// Handler to deal with "433 :Nickname already in use"
|
||||||
func (conn *Conn) h_433(line *Line) {
|
func (conn *Conn) h_433(line *Line) {
|
||||||
// Args[1] is the new nick we were attempting to acquire
|
// Args[1] is the new nick we were attempting to acquire
|
||||||
|
me := conn.Me()
|
||||||
neu := conn.cfg.NewNick(line.Args[1])
|
neu := conn.cfg.NewNick(line.Args[1])
|
||||||
conn.Nick(neu)
|
conn.Nick(neu)
|
||||||
// if this is happening before we're properly connected (i.e. the nick
|
// if this is happening before we're properly connected (i.e. the nick
|
||||||
// we sent in the initial NICK command is in use) we will not receive
|
// we sent in the initial NICK command is in use) we will not receive
|
||||||
// a NICK message to confirm our change of nick, so ReNick here...
|
// a NICK message to confirm our change of nick, so ReNick here...
|
||||||
if line.Args[1] == conn.cfg.Me.Nick {
|
if line.Args[1] == me.Nick {
|
||||||
if conn.st != nil {
|
if conn.st != nil {
|
||||||
conn.st.ReNick(conn.cfg.Me.Nick, neu)
|
conn.cfg.Me = conn.st.ReNick(me.Nick, neu)
|
||||||
} else {
|
} else {
|
||||||
conn.cfg.Me.Nick = neu
|
conn.cfg.Me.Nick = neu
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,11 @@ func Test001(t *testing.T) {
|
||||||
hcon = true
|
hcon = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Test state tracking first.
|
||||||
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
|
s.st.EXPECT().NickInfo("test", "test", "somehost.com", "Testing IRC"),
|
||||||
|
)
|
||||||
// Call handler with a valid 001 line
|
// Call handler with a valid 001 line
|
||||||
c.h_001(l)
|
c.h_001(l)
|
||||||
<-time.After(time.Millisecond)
|
<-time.After(time.Millisecond)
|
||||||
|
@ -57,10 +62,14 @@ func Test001(t *testing.T) {
|
||||||
t.Errorf("001 handler did not dispatch connected event.")
|
t.Errorf("001 handler did not dispatch connected event.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now without state tracking.
|
||||||
|
c.st = nil
|
||||||
|
c.h_001(l)
|
||||||
// Check host parsed correctly
|
// Check host parsed correctly
|
||||||
if c.cfg.Me.Host != "somehost.com" {
|
if c.cfg.Me.Host != "somehost.com" {
|
||||||
t.Errorf("Host parsing failed, host is '%s'.", c.cfg.Me.Host)
|
t.Errorf("Host parsing failed, host is '%s'.", c.cfg.Me.Host)
|
||||||
}
|
}
|
||||||
|
c.st = s.st
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the handler for 433 / ERR_NICKNAMEINUSE
|
// Test the handler for 433 / ERR_NICKNAMEINUSE
|
||||||
|
@ -69,29 +78,21 @@ func Test433(t *testing.T) {
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Call handler with a 433 line, not triggering c.cfg.Me.Renick()
|
// Call handler with a 433 line, not triggering c.cfg.Me.Renick()
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me)
|
||||||
c.h_433(ParseLine(":irc.server.org 433 test new :Nickname is already in use."))
|
c.h_433(ParseLine(":irc.server.org 433 test new :Nickname is already in use."))
|
||||||
s.nc.Expect("NICK new_")
|
s.nc.Expect("NICK new_")
|
||||||
|
|
||||||
// In this case, we're expecting the server to send a NICK line
|
|
||||||
if c.cfg.Me.Nick != "test" {
|
|
||||||
t.Errorf("ReNick() called unexpectedly, Nick == '%s'.", c.cfg.Me.Nick)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a line that will trigger a renick. This happens when our wanted
|
// Send a line that will trigger a renick. This happens when our wanted
|
||||||
// nick is unavailable during initial negotiation, so we must choose a
|
// nick is unavailable during initial negotiation, so we must choose a
|
||||||
// different one before the connection can proceed. No NICK line will be
|
// different one before the connection can proceed. No NICK line will be
|
||||||
// sent by the server to confirm nick change in this case.
|
// sent by the server to confirm nick change in this case.
|
||||||
s.st.EXPECT().ReNick("test", "test_")
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
|
s.st.EXPECT().ReNick("test", "test_").Return(c.cfg.Me),
|
||||||
|
)
|
||||||
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
|
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
|
||||||
s.nc.Expect("NICK test_")
|
s.nc.Expect("NICK test_")
|
||||||
|
|
||||||
// Counter-intuitively, c.cfg.Me.Nick will not change in this case. This
|
|
||||||
// is an artifact of the test set-up, with a mocked out state tracker that
|
|
||||||
// doesn't actually change any state. Normally, this would be fine :-)
|
|
||||||
if c.cfg.Me.Nick != "test" {
|
|
||||||
t.Errorf("My nick changed from '%s'.", c.cfg.Me.Nick)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the code path that *doesn't* involve state tracking.
|
// Test the code path that *doesn't* involve state tracking.
|
||||||
c.st = nil
|
c.st = nil
|
||||||
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
|
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
|
||||||
|
@ -164,13 +165,14 @@ func TestJOIN(t *testing.T) {
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// The state tracker should be creating a new channel in this first test
|
// The state tracker should be creating a new channel in this first test
|
||||||
chan1 := state.NewChannel("#test1")
|
chan1 := &state.Channel{Name: "#test1"}
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(nil),
|
s.st.EXPECT().GetChannel("#test1").Return(nil),
|
||||||
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
|
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
s.st.EXPECT().NewChannel("#test1").Return(chan1),
|
s.st.EXPECT().NewChannel("#test1").Return(chan1),
|
||||||
s.st.EXPECT().Associate(chan1, c.cfg.Me),
|
s.st.EXPECT().Associate("#test1", "test"),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use #test1 to test expected behaviour
|
// Use #test1 to test expected behaviour
|
||||||
|
@ -182,13 +184,14 @@ func TestJOIN(t *testing.T) {
|
||||||
s.nc.Expect("WHO #test1")
|
s.nc.Expect("WHO #test1")
|
||||||
|
|
||||||
// In this second test, we should be creating a new nick
|
// In this second test, we should be creating a new nick
|
||||||
nick1 := state.NewNick("user1")
|
nick1 := &state.Nick{Nick: "user1"}
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
||||||
s.st.EXPECT().GetNick("user1").Return(nil),
|
s.st.EXPECT().GetNick("user1").Return(nil),
|
||||||
s.st.EXPECT().NewNick("user1").Return(nick1),
|
s.st.EXPECT().NewNick("user1").Return(nick1),
|
||||||
s.st.EXPECT().Associate(chan1, nick1),
|
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "").Return(nick1),
|
||||||
|
s.st.EXPECT().Associate("#test1", "user1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
// OK, now #test1 exists, JOIN another user we don't know about
|
// OK, now #test1 exists, JOIN another user we don't know about
|
||||||
|
@ -198,11 +201,11 @@ func TestJOIN(t *testing.T) {
|
||||||
s.nc.Expect("WHO user1")
|
s.nc.Expect("WHO user1")
|
||||||
|
|
||||||
// In this third test, we'll be pretending we know about the nick already.
|
// In this third test, we'll be pretending we know about the nick already.
|
||||||
nick2 := state.NewNick("user2")
|
nick2 := &state.Nick{Nick: "user2"}
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
||||||
s.st.EXPECT().GetNick("user2").Return(nick2),
|
s.st.EXPECT().GetNick("user2").Return(nick2),
|
||||||
s.st.EXPECT().Associate(chan1, nick2),
|
s.st.EXPECT().Associate("#test1", "user2"),
|
||||||
)
|
)
|
||||||
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test1"))
|
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test1"))
|
||||||
|
|
||||||
|
@ -211,9 +214,11 @@ func TestJOIN(t *testing.T) {
|
||||||
// unknown channel, unknown nick
|
// unknown channel, unknown nick
|
||||||
s.st.EXPECT().GetChannel("#test2").Return(nil),
|
s.st.EXPECT().GetChannel("#test2").Return(nil),
|
||||||
s.st.EXPECT().GetNick("blah").Return(nil),
|
s.st.EXPECT().GetNick("blah").Return(nil),
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
// unknown channel, known nick that isn't Me.
|
// unknown channel, known nick that isn't Me.
|
||||||
s.st.EXPECT().GetChannel("#test2").Return(nil),
|
s.st.EXPECT().GetChannel("#test2").Return(nil),
|
||||||
s.st.EXPECT().GetNick("user2").Return(nick2),
|
s.st.EXPECT().GetNick("user2").Return(nick2),
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
)
|
)
|
||||||
c.h_JOIN(ParseLine(":blah!moo@cows.com JOIN :#test2"))
|
c.h_JOIN(ParseLine(":blah!moo@cows.com JOIN :#test2"))
|
||||||
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test2"))
|
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test2"))
|
||||||
|
@ -224,16 +229,8 @@ func TestPART(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// We need some valid and associated nicks / channels to PART with.
|
|
||||||
chan1 := state.NewChannel("#test1")
|
|
||||||
nick1 := state.NewNick("user1")
|
|
||||||
|
|
||||||
// PART should dissociate a nick from a channel.
|
// PART should dissociate a nick from a channel.
|
||||||
gomock.InOrder(
|
s.st.EXPECT().Dissociate("#test1", "user1")
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
|
||||||
s.st.EXPECT().GetNick("user1").Return(nick1),
|
|
||||||
s.st.EXPECT().Dissociate(chan1, nick1),
|
|
||||||
)
|
|
||||||
c.h_PART(ParseLine(":user1!ident1@host1.com PART #test1 :Bye!"))
|
c.h_PART(ParseLine(":user1!ident1@host1.com PART #test1 :Bye!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,16 +240,8 @@ func TestKICK(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// We need some valid and associated nicks / channels to KICK.
|
|
||||||
chan1 := state.NewChannel("#test1")
|
|
||||||
nick1 := state.NewNick("user1")
|
|
||||||
|
|
||||||
// KICK should dissociate a nick from a channel.
|
// KICK should dissociate a nick from a channel.
|
||||||
gomock.InOrder(
|
s.st.EXPECT().Dissociate("#test1", "user1")
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
|
||||||
s.st.EXPECT().GetNick("user1").Return(nick1),
|
|
||||||
s.st.EXPECT().Dissociate(chan1, nick1),
|
|
||||||
)
|
|
||||||
c.h_KICK(ParseLine(":test!test@somehost.com KICK #test1 user1 :Bye!"))
|
c.h_KICK(ParseLine(":test!test@somehost.com KICK #test1 user1 :Bye!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,34 +260,28 @@ func TestMODE(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
chan1 := state.NewChannel("#test1")
|
// Channel modes
|
||||||
nick1 := state.NewNick("user1")
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
|
||||||
// Send a channel mode line. Inconveniently, Channel and Nick objects
|
s.st.EXPECT().ChannelModes("#test1", "+sk", "somekey"),
|
||||||
// aren't mockable with gomock as they're not interface types (and I
|
)
|
||||||
// don't want them to be, writing accessors for struct fields sucks).
|
|
||||||
// This makes testing whether ParseModes is called correctly harder.
|
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
|
||||||
c.h_MODE(ParseLine(":user1!ident1@host1.com MODE #test1 +sk somekey"))
|
c.h_MODE(ParseLine(":user1!ident1@host1.com MODE #test1 +sk somekey"))
|
||||||
if !chan1.Modes.Secret || chan1.Modes.Key != "somekey" {
|
|
||||||
t.Errorf("Channel.ParseModes() not called correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a nick mode line, returning Me
|
// Nick modes for Me.
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
s.st.EXPECT().GetChannel("test").Return(nil),
|
s.st.EXPECT().GetChannel("test").Return(nil),
|
||||||
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
|
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
|
s.st.EXPECT().NickModes("test", "+i"),
|
||||||
)
|
)
|
||||||
c.h_MODE(ParseLine(":test!test@somehost.com MODE test +i"))
|
c.h_MODE(ParseLine(":test!test@somehost.com MODE test +i"))
|
||||||
if !c.cfg.Me.Modes.Invisible {
|
|
||||||
t.Errorf("Nick.ParseModes() not called correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths
|
// Check error paths
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
// send a valid user mode that's not us
|
// send a valid user mode that's not us
|
||||||
s.st.EXPECT().GetChannel("user1").Return(nil),
|
s.st.EXPECT().GetChannel("user1").Return(nil),
|
||||||
s.st.EXPECT().GetNick("user1").Return(nick1),
|
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
// Send a random mode for an unknown channel
|
// Send a random mode for an unknown channel
|
||||||
s.st.EXPECT().GetChannel("#test2").Return(nil),
|
s.st.EXPECT().GetChannel("#test2").Return(nil),
|
||||||
s.st.EXPECT().GetNick("#test2").Return(nil),
|
s.st.EXPECT().GetNick("#test2").Return(nil),
|
||||||
|
@ -312,22 +295,13 @@ func TestTOPIC(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
chan1 := state.NewChannel("#test1")
|
// Ensure TOPIC reply calls Topic
|
||||||
|
gomock.InOrder(
|
||||||
// Assert that it has no topic originally
|
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
|
||||||
if chan1.Topic != "" {
|
s.st.EXPECT().Topic("#test1", "something something"),
|
||||||
t.Errorf("Test channel already has a topic.")
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Send a TOPIC line
|
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
|
||||||
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test1 :something something"))
|
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test1 :something something"))
|
||||||
|
|
||||||
// Make sure the channel's topic has been changed
|
|
||||||
if chan1.Topic != "something something" {
|
|
||||||
t.Errorf("Topic of test channel not set correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths -- send a topic for an unknown channel
|
// Check error paths -- send a topic for an unknown channel
|
||||||
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
||||||
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test2 :dark side"))
|
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test2 :dark side"))
|
||||||
|
@ -338,20 +312,14 @@ func Test311(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Create user1, who we know little about
|
// Ensure 311 reply calls NickInfo
|
||||||
nick1 := state.NewNick("user1")
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
|
||||||
// Send a 311 reply
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
|
||||||
|
)
|
||||||
c.h_311(ParseLine(":irc.server.org 311 test user1 ident1 host1.com * :name"))
|
c.h_311(ParseLine(":irc.server.org 311 test user1 ident1 host1.com * :name"))
|
||||||
|
|
||||||
// Verify we now know more about user1
|
|
||||||
if nick1.Ident != "ident1" ||
|
|
||||||
nick1.Host != "host1.com" ||
|
|
||||||
nick1.Name != "name" {
|
|
||||||
t.Errorf("WHOIS info of user1 not set correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths -- send a 311 for an unknown nick
|
// Check error paths -- send a 311 for an unknown nick
|
||||||
s.st.EXPECT().GetNick("user2").Return(nil)
|
s.st.EXPECT().GetNick("user2").Return(nil)
|
||||||
c.h_311(ParseLine(":irc.server.org 311 test user2 ident2 host2.com * :dongs"))
|
c.h_311(ParseLine(":irc.server.org 311 test user2 ident2 host2.com * :dongs"))
|
||||||
|
@ -362,15 +330,12 @@ func Test324(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Create #test1, whose modes we don't know
|
// Ensure 324 reply calls ChannelModes
|
||||||
chan1 := state.NewChannel("#test1")
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
|
||||||
// Send a 324 reply
|
s.st.EXPECT().ChannelModes("#test1", "+sk", "somekey"),
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
)
|
||||||
c.h_324(ParseLine(":irc.server.org 324 test #test1 +sk somekey"))
|
c.h_324(ParseLine(":irc.server.org 324 test #test1 +sk somekey"))
|
||||||
if !chan1.Modes.Secret || chan1.Modes.Key != "somekey" {
|
|
||||||
t.Errorf("Channel.ParseModes() not called correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths -- send 324 for an unknown channel
|
// Check error paths -- send 324 for an unknown channel
|
||||||
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
||||||
|
@ -382,23 +347,13 @@ func Test332(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Create #test1, whose topic we don't know
|
// Ensure 332 reply calls Topic
|
||||||
chan1 := state.NewChannel("#test1")
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
|
||||||
// Assert that it has no topic originally
|
s.st.EXPECT().Topic("#test1", "something something"),
|
||||||
if chan1.Topic != "" {
|
)
|
||||||
t.Errorf("Test channel already has a topic.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a 332 reply
|
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
|
||||||
c.h_332(ParseLine(":irc.server.org 332 test #test1 :something something"))
|
c.h_332(ParseLine(":irc.server.org 332 test #test1 :something something"))
|
||||||
|
|
||||||
// Make sure the channel's topic has been changed
|
|
||||||
if chan1.Topic != "something something" {
|
|
||||||
t.Errorf("Topic of test channel not set correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths -- send 332 for an unknown channel
|
// Check error paths -- send 332 for an unknown channel
|
||||||
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
||||||
c.h_332(ParseLine(":irc.server.org 332 test #test2 :dark side"))
|
c.h_332(ParseLine(":irc.server.org 332 test #test2 :dark side"))
|
||||||
|
@ -409,30 +364,24 @@ func Test352(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Create user1, who we know little about
|
// Ensure 352 reply calls NickInfo and NickModes
|
||||||
nick1 := state.NewNick("user1")
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
|
||||||
// Send a 352 reply
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
|
||||||
|
)
|
||||||
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 G :0 name"))
|
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 G :0 name"))
|
||||||
|
|
||||||
// Verify we now know more about user1
|
|
||||||
if nick1.Ident != "ident1" ||
|
|
||||||
nick1.Host != "host1.com" ||
|
|
||||||
nick1.Name != "name" ||
|
|
||||||
nick1.Modes.Invisible ||
|
|
||||||
nick1.Modes.Oper {
|
|
||||||
t.Errorf("WHO info of user1 not set correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that modes are set correctly from WHOREPLY
|
// Check that modes are set correctly from WHOREPLY
|
||||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
gomock.InOrder(
|
||||||
|
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
|
||||||
|
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||||
|
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
|
||||||
|
s.st.EXPECT().NickModes("user1", "+o"),
|
||||||
|
s.st.EXPECT().NickModes("user1", "+i"),
|
||||||
|
)
|
||||||
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 H* :0 name"))
|
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 H* :0 name"))
|
||||||
|
|
||||||
if !nick1.Modes.Invisible || !nick1.Modes.Oper {
|
|
||||||
t.Errorf("WHO modes of user1 not set correctly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths -- send a 352 for an unknown nick
|
// Check error paths -- send a 352 for an unknown nick
|
||||||
s.st.EXPECT().GetNick("user2").Return(nil)
|
s.st.EXPECT().GetNick("user2").Return(nil)
|
||||||
c.h_352(ParseLine(":irc.server.org 352 test #test2 ident2 host2.com irc.server.org user2 G :0 fooo"))
|
c.h_352(ParseLine(":irc.server.org 352 test #test2 ident2 host2.com irc.server.org user2 G :0 fooo"))
|
||||||
|
@ -443,55 +392,42 @@ func Test353(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Create #test1, whose user list we're mostly unfamiliar with
|
|
||||||
chan1 := state.NewChannel("#test1")
|
|
||||||
|
|
||||||
// Create maps for testing -- this is what the mock ST calls will return
|
|
||||||
nicks := make(map[string]*state.Nick)
|
|
||||||
privs := make(map[string]*state.ChanPrivs)
|
|
||||||
|
|
||||||
nicks["test"] = c.cfg.Me
|
|
||||||
privs["test"] = new(state.ChanPrivs)
|
|
||||||
|
|
||||||
for _, n := range []string{"user1", "user2", "voice", "halfop",
|
|
||||||
"op", "admin", "owner"} {
|
|
||||||
nicks[n] = state.NewNick(n)
|
|
||||||
privs[n] = new(state.ChanPrivs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 353 handler is called twice, so GetChannel will be called twice
|
// 353 handler is called twice, so GetChannel will be called twice
|
||||||
s.st.EXPECT().GetChannel("#test1").Return(chan1).Times(2)
|
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}).Times(2)
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
// "test" is Me, i am known, and already on the channel
|
// "test" is Me, i am known, and already on the channel
|
||||||
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
|
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
|
||||||
s.st.EXPECT().IsOn("#test1", "test").Return(privs["test"], true),
|
s.st.EXPECT().IsOn("#test1", "test").Return(&state.ChanPrivs{}, true),
|
||||||
// user1 is known, but not on the channel, so should be associated
|
// user1 is known, but not on the channel, so should be associated
|
||||||
s.st.EXPECT().GetNick("user1").Return(nicks["user1"]),
|
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
|
||||||
s.st.EXPECT().IsOn("#test1", "user1").Return(nil, false),
|
s.st.EXPECT().IsOn("#test1", "user1").Return(nil, false),
|
||||||
s.st.EXPECT().Associate(chan1, nicks["user1"]).Return(privs["user1"]),
|
s.st.EXPECT().Associate("#test1", "user1").Return(&state.ChanPrivs{}),
|
||||||
|
s.st.EXPECT().ChannelModes("#test1", "+o", "user1"),
|
||||||
)
|
)
|
||||||
for _, n := range []string{"user2", "voice", "halfop", "op", "admin", "owner"} {
|
for n, m := range map[string]string{
|
||||||
gomock.InOrder(
|
"user2": "",
|
||||||
|
"voice": "+v",
|
||||||
|
"halfop": "+h",
|
||||||
|
"op": "+o",
|
||||||
|
"admin": "+a",
|
||||||
|
"owner": "+q",
|
||||||
|
} {
|
||||||
|
calls := []*gomock.Call{
|
||||||
s.st.EXPECT().GetNick(n).Return(nil),
|
s.st.EXPECT().GetNick(n).Return(nil),
|
||||||
s.st.EXPECT().NewNick(n).Return(nicks[n]),
|
s.st.EXPECT().NewNick(n).Return(&state.Nick{Nick: n}),
|
||||||
s.st.EXPECT().IsOn("#test1", n).Return(nil, false),
|
s.st.EXPECT().IsOn("#test1", n).Return(nil, false),
|
||||||
s.st.EXPECT().Associate(chan1, nicks[n]).Return(privs[n]),
|
s.st.EXPECT().Associate("#test1", n).Return(&state.ChanPrivs{}),
|
||||||
)
|
}
|
||||||
|
if m != "" {
|
||||||
|
calls = append(calls, s.st.EXPECT().ChannelModes("#test1", m, n))
|
||||||
|
}
|
||||||
|
gomock.InOrder(calls...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a couple of names replies (complete with trailing space)
|
// Send a couple of names replies (complete with trailing space)
|
||||||
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :test @user1 user2 +voice "))
|
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :test @user1 user2 +voice "))
|
||||||
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :%halfop @op &admin ~owner "))
|
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :%halfop @op &admin ~owner "))
|
||||||
|
|
||||||
if p := privs["user2"]; p.Voice || p.HalfOp || p.Op || p.Admin || p.Owner {
|
|
||||||
t.Errorf("353 handler incorrectly set modes on nick.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !privs["user1"].Op || !privs["voice"].Voice || !privs["halfop"].HalfOp ||
|
|
||||||
!privs["op"].Op || !privs["admin"].Admin || !privs["owner"].Owner {
|
|
||||||
t.Errorf("353 handler failed to set correct modes for nicks.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths -- send 353 for an unknown channel
|
// Check error paths -- send 353 for an unknown channel
|
||||||
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
||||||
c.h_353(ParseLine(":irc.server.org 353 test = #test2 :test ~user3"))
|
c.h_353(ParseLine(":irc.server.org 353 test = #test2 :test ~user3"))
|
||||||
|
@ -502,21 +438,13 @@ func Test671(t *testing.T) {
|
||||||
c, s := setUp(t)
|
c, s := setUp(t)
|
||||||
defer s.tearDown()
|
defer s.tearDown()
|
||||||
|
|
||||||
// Create user1, who should not be secure
|
// Ensure 671 reply calls NickModes
|
||||||
nick1 := state.NewNick("user1")
|
gomock.InOrder(
|
||||||
if nick1.Modes.SSL {
|
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
|
||||||
t.Errorf("Test nick user1 is already using SSL?")
|
s.st.EXPECT().NickModes("user1", "+z"),
|
||||||
}
|
)
|
||||||
|
|
||||||
// Send a 671 reply
|
|
||||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
|
||||||
c.h_671(ParseLine(":irc.server.org 671 test user1 :some ignored text"))
|
c.h_671(ParseLine(":irc.server.org 671 test user1 :some ignored text"))
|
||||||
|
|
||||||
// Ensure user1 is now known to be on an SSL connection
|
|
||||||
if !nick1.Modes.SSL {
|
|
||||||
t.Errorf("Test nick user1 not using SSL?")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check error paths -- send a 671 for an unknown nick
|
// Check error paths -- send a 671 for an unknown nick
|
||||||
s.st.EXPECT().GetNick("user2").Return(nil)
|
s.st.EXPECT().GetNick("user2").Return(nil)
|
||||||
c.h_671(ParseLine(":irc.server.org 671 test user2 :some ignored text"))
|
c.h_671(ParseLine(":irc.server.org 671 test user2 :some ignored text"))
|
||||||
|
|
|
@ -51,44 +51,41 @@ func (conn *Conn) h_JOIN(line *Line) {
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
// first we've seen of this channel, so should be us joining it
|
// first we've seen of this channel, so should be us joining it
|
||||||
// NOTE this will also take care of nk == nil && ch == nil
|
// NOTE this will also take care of nk == nil && ch == nil
|
||||||
if nk != conn.cfg.Me {
|
if !conn.Me().Equals(nk) {
|
||||||
logging.Warn("irc.JOIN(): JOIN to unknown channel %s received "+
|
logging.Warn("irc.JOIN(): JOIN to unknown channel %s received "+
|
||||||
"from (non-me) nick %s", line.Args[0], line.Nick)
|
"from (non-me) nick %s", line.Args[0], line.Nick)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ch = conn.st.NewChannel(line.Args[0])
|
conn.st.NewChannel(line.Args[0])
|
||||||
// since we don't know much about this channel, ask server for info
|
// since we don't know much about this channel, ask server for info
|
||||||
// we get the channel users automatically in 353 and the channel
|
// we get the channel users automatically in 353 and the channel
|
||||||
// topic in 332 on join, so we just need to get the modes
|
// topic in 332 on join, so we just need to get the modes
|
||||||
conn.Mode(ch.Name)
|
conn.Mode(line.Args[0])
|
||||||
// sending a WHO for the channel is MUCH more efficient than
|
// sending a WHO for the channel is MUCH more efficient than
|
||||||
// triggering a WHOIS on every nick from the 353 handler
|
// triggering a WHOIS on every nick from the 353 handler
|
||||||
conn.Who(ch.Name)
|
conn.Who(line.Args[0])
|
||||||
}
|
}
|
||||||
if nk == nil {
|
if nk == nil {
|
||||||
// this is the first we've seen of this nick
|
// this is the first we've seen of this nick
|
||||||
nk = conn.st.NewNick(line.Nick)
|
conn.st.NewNick(line.Nick)
|
||||||
nk.Ident = line.Ident
|
conn.st.NickInfo(line.Nick, line.Ident, line.Host, "")
|
||||||
nk.Host = line.Host
|
|
||||||
// since we don't know much about this nick, ask server for info
|
// since we don't know much about this nick, ask server for info
|
||||||
conn.Who(nk.Nick)
|
conn.Who(line.Nick)
|
||||||
}
|
}
|
||||||
// this takes care of both nick and channel linking \o/
|
// this takes care of both nick and channel linking \o/
|
||||||
conn.st.Associate(ch, nk)
|
conn.st.Associate(line.Args[0], line.Nick)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle PARTs from channels to maintain state
|
// Handle PARTs from channels to maintain state
|
||||||
func (conn *Conn) h_PART(line *Line) {
|
func (conn *Conn) h_PART(line *Line) {
|
||||||
conn.st.Dissociate(conn.st.GetChannel(line.Args[0]),
|
conn.st.Dissociate(line.Args[0], line.Nick)
|
||||||
conn.st.GetNick(line.Nick))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle KICKs from channels to maintain state
|
// Handle KICKs from channels to maintain state
|
||||||
func (conn *Conn) h_KICK(line *Line) {
|
func (conn *Conn) h_KICK(line *Line) {
|
||||||
// XXX: this won't handle autorejoining channels on KICK
|
// XXX: this won't handle autorejoining channels on KICK
|
||||||
// it's trivial to do this in a seperate handler...
|
// it's trivial to do this in a seperate handler...
|
||||||
conn.st.Dissociate(conn.st.GetChannel(line.Args[0]),
|
conn.st.Dissociate(line.Args[0], line.Args[1])
|
||||||
conn.st.GetNick(line.Args[1]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle other people's QUITs
|
// Handle other people's QUITs
|
||||||
|
@ -100,15 +97,15 @@ func (conn *Conn) h_QUIT(line *Line) {
|
||||||
func (conn *Conn) h_MODE(line *Line) {
|
func (conn *Conn) h_MODE(line *Line) {
|
||||||
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
||||||
// channel modes first
|
// channel modes first
|
||||||
ch.ParseModes(line.Args[1], line.Args[2:]...)
|
conn.st.ChannelModes(line.Args[0], line.Args[1], line.Args[2:]...)
|
||||||
} else if nk := conn.st.GetNick(line.Args[0]); nk != nil {
|
} else if nk := conn.st.GetNick(line.Args[0]); nk != nil {
|
||||||
// nick mode change, should be us
|
// nick mode change, should be us
|
||||||
if nk != conn.cfg.Me {
|
if !conn.Me().Equals(nk) {
|
||||||
logging.Warn("irc.MODE(): recieved MODE %s for (non-me) nick %s",
|
logging.Warn("irc.MODE(): recieved MODE %s for (non-me) nick %s",
|
||||||
line.Args[1], line.Args[0])
|
line.Args[1], line.Args[0])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nk.ParseModes(line.Args[1])
|
conn.st.NickModes(line.Args[0], line.Args[1])
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("irc.MODE(): not sure what to do with MODE %s",
|
logging.Warn("irc.MODE(): not sure what to do with MODE %s",
|
||||||
strings.Join(line.Args, " "))
|
strings.Join(line.Args, " "))
|
||||||
|
@ -118,7 +115,7 @@ func (conn *Conn) h_MODE(line *Line) {
|
||||||
// Handle TOPIC changes for channels
|
// Handle TOPIC changes for channels
|
||||||
func (conn *Conn) h_TOPIC(line *Line) {
|
func (conn *Conn) h_TOPIC(line *Line) {
|
||||||
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
||||||
ch.Topic = line.Args[1]
|
conn.st.Topic(line.Args[0], line.Args[1])
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("irc.TOPIC(): topic change on unknown channel %s",
|
logging.Warn("irc.TOPIC(): topic change on unknown channel %s",
|
||||||
line.Args[0])
|
line.Args[0])
|
||||||
|
@ -127,10 +124,8 @@ func (conn *Conn) h_TOPIC(line *Line) {
|
||||||
|
|
||||||
// Handle 311 whois reply
|
// Handle 311 whois reply
|
||||||
func (conn *Conn) h_311(line *Line) {
|
func (conn *Conn) h_311(line *Line) {
|
||||||
if nk := conn.st.GetNick(line.Args[1]); nk != nil && nk != conn.Me() {
|
if nk := conn.st.GetNick(line.Args[1]); nk != nil && !conn.Me().Equals(nk) {
|
||||||
nk.Ident = line.Args[2]
|
conn.st.NickInfo(line.Args[1], line.Args[2], line.Args[3], line.Args[5])
|
||||||
nk.Host = line.Args[3]
|
|
||||||
nk.Name = line.Args[5]
|
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("irc.311(): received WHOIS info for unknown nick %s",
|
logging.Warn("irc.311(): received WHOIS info for unknown nick %s",
|
||||||
line.Args[1])
|
line.Args[1])
|
||||||
|
@ -140,7 +135,7 @@ func (conn *Conn) h_311(line *Line) {
|
||||||
// Handle 324 mode reply
|
// Handle 324 mode reply
|
||||||
func (conn *Conn) h_324(line *Line) {
|
func (conn *Conn) h_324(line *Line) {
|
||||||
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
||||||
ch.ParseModes(line.Args[2], line.Args[3:]...)
|
conn.st.ChannelModes(line.Args[1], line.Args[2], line.Args[3:]...)
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("irc.324(): received MODE settings for unknown channel %s",
|
logging.Warn("irc.324(): received MODE settings for unknown channel %s",
|
||||||
line.Args[1])
|
line.Args[1])
|
||||||
|
@ -150,7 +145,7 @@ func (conn *Conn) h_324(line *Line) {
|
||||||
// Handle 332 topic reply on join to channel
|
// Handle 332 topic reply on join to channel
|
||||||
func (conn *Conn) h_332(line *Line) {
|
func (conn *Conn) h_332(line *Line) {
|
||||||
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
||||||
ch.Topic = line.Args[2]
|
conn.st.Topic(line.Args[1], line.Args[2])
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("irc.332(): received TOPIC value for unknown channel %s",
|
logging.Warn("irc.332(): received TOPIC value for unknown channel %s",
|
||||||
line.Args[1])
|
line.Args[1])
|
||||||
|
@ -165,24 +160,22 @@ func (conn *Conn) h_352(line *Line) {
|
||||||
line.Args[5])
|
line.Args[5])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if nk == conn.Me() {
|
if conn.Me().Equals(nk) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nk.Ident = line.Args[2]
|
|
||||||
nk.Host = line.Args[3]
|
|
||||||
// XXX: do we care about the actual server the nick is on?
|
// XXX: do we care about the actual server the nick is on?
|
||||||
// or the hop count to this server?
|
// or the hop count to this server?
|
||||||
// last arg contains "<hop count> <real name>"
|
// last arg contains "<hop count> <real name>"
|
||||||
a := strings.SplitN(line.Args[len(line.Args)-1], " ", 2)
|
a := strings.SplitN(line.Args[len(line.Args)-1], " ", 2)
|
||||||
nk.Name = a[1]
|
conn.st.NickInfo(nk.Nick, line.Args[2], line.Args[3], a[1])
|
||||||
if idx := strings.Index(line.Args[6], "*"); idx != -1 {
|
if idx := strings.Index(line.Args[6], "*"); idx != -1 {
|
||||||
nk.Modes.Oper = true
|
conn.st.NickModes(nk.Nick, "+o")
|
||||||
}
|
}
|
||||||
if idx := strings.Index(line.Args[6], "B"); idx != -1 {
|
if idx := strings.Index(line.Args[6], "B"); idx != -1 {
|
||||||
nk.Modes.Bot = true
|
conn.st.NickModes(nk.Nick, "+B")
|
||||||
}
|
}
|
||||||
if idx := strings.Index(line.Args[6], "H"); idx != -1 {
|
if idx := strings.Index(line.Args[6], "H"); idx != -1 {
|
||||||
nk.Modes.Invisible = true
|
conn.st.NickModes(nk.Nick, "+i")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,27 +193,25 @@ func (conn *Conn) h_353(line *Line) {
|
||||||
nick = nick[1:]
|
nick = nick[1:]
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
nk := conn.st.GetNick(nick)
|
if conn.st.GetNick(nick) == nil {
|
||||||
if nk == nil {
|
|
||||||
// we don't know this nick yet!
|
// we don't know this nick yet!
|
||||||
nk = conn.st.NewNick(nick)
|
conn.st.NewNick(nick)
|
||||||
}
|
}
|
||||||
cp, ok := conn.st.IsOn(ch.Name, nick)
|
if _, ok := conn.st.IsOn(ch.Name, nick); !ok {
|
||||||
if !ok {
|
|
||||||
// This nick isn't associated with this channel yet!
|
// This nick isn't associated with this channel yet!
|
||||||
cp = conn.st.Associate(ch, nk)
|
conn.st.Associate(ch.Name, nick)
|
||||||
}
|
}
|
||||||
switch c {
|
switch c {
|
||||||
case '~':
|
case '~':
|
||||||
cp.Owner = true
|
conn.st.ChannelModes(ch.Name, "+q", nick)
|
||||||
case '&':
|
case '&':
|
||||||
cp.Admin = true
|
conn.st.ChannelModes(ch.Name, "+a", nick)
|
||||||
case '@':
|
case '@':
|
||||||
cp.Op = true
|
conn.st.ChannelModes(ch.Name, "+o", nick)
|
||||||
case '%':
|
case '%':
|
||||||
cp.HalfOp = true
|
conn.st.ChannelModes(ch.Name, "+h", nick)
|
||||||
case '+':
|
case '+':
|
||||||
cp.Voice = true
|
conn.st.ChannelModes(ch.Name, "+v", nick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +224,7 @@ func (conn *Conn) h_353(line *Line) {
|
||||||
// Handle 671 whois reply (nick connected via SSL)
|
// Handle 671 whois reply (nick connected via SSL)
|
||||||
func (conn *Conn) h_671(line *Line) {
|
func (conn *Conn) h_671(line *Line) {
|
||||||
if nk := conn.st.GetNick(line.Args[1]); nk != nil {
|
if nk := conn.st.GetNick(line.Args[1]); nk != nil {
|
||||||
nk.Modes.SSL = true
|
conn.st.NickModes(nk.Nick, "+z")
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("irc.671(): received WHOIS SSL info for unknown nick %s",
|
logging.Warn("irc.671(): received WHOIS SSL info for unknown nick %s",
|
||||||
line.Args[1])
|
line.Args[1])
|
||||||
|
|
163
state/channel.go
163
state/channel.go
|
@ -3,18 +3,24 @@ package state
|
||||||
import (
|
import (
|
||||||
"github.com/fluffle/goirc/logging"
|
"github.com/fluffle/goirc/logging"
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A struct representing an IRC channel
|
// A Channel is returned from the state tracker and contains
|
||||||
|
// a copy of the channel state at a particular time.
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
Name, Topic string
|
Name, Topic string
|
||||||
Modes *ChanMode
|
Modes *ChanMode
|
||||||
lookup map[string]*Nick
|
Nicks map[string]*ChanPrivs
|
||||||
nicks map[*Nick]*ChanPrivs
|
}
|
||||||
|
|
||||||
|
// Internal bookkeeping struct for channels.
|
||||||
|
type channel struct {
|
||||||
|
name, topic string
|
||||||
|
modes *ChanMode
|
||||||
|
lookup map[string]*nick
|
||||||
|
nicks map[*nick]*ChanPrivs
|
||||||
}
|
}
|
||||||
|
|
||||||
// A struct representing the modes of an IRC Channel
|
// A struct representing the modes of an IRC Channel
|
||||||
|
@ -98,48 +104,57 @@ func init() {
|
||||||
* Channel methods for state management
|
* Channel methods for state management
|
||||||
\******************************************************************************/
|
\******************************************************************************/
|
||||||
|
|
||||||
func NewChannel(name string) *Channel {
|
func newChannel(name string) *channel {
|
||||||
return &Channel{
|
return &channel{
|
||||||
Name: name,
|
name: name,
|
||||||
Modes: new(ChanMode),
|
modes: new(ChanMode),
|
||||||
nicks: make(map[*Nick]*ChanPrivs),
|
nicks: make(map[*nick]*ChanPrivs),
|
||||||
lookup: make(map[string]*Nick),
|
lookup: make(map[string]*nick),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the Nick is associated with the Channel
|
// Returns a copy of the internal tracker channel state at this time.
|
||||||
func (ch *Channel) IsOn(nk *Nick) (*ChanPrivs, bool) {
|
// Relies on tracker-level locking for concurrent access.
|
||||||
cp, ok := ch.nicks[nk]
|
func (ch *channel) Channel() *Channel {
|
||||||
return cp, ok
|
c := &Channel{
|
||||||
|
Name: ch.name,
|
||||||
|
Topic: ch.topic,
|
||||||
|
Modes: ch.modes.Copy(),
|
||||||
|
Nicks: make(map[string]*ChanPrivs),
|
||||||
|
}
|
||||||
|
for n, cp := range ch.nicks {
|
||||||
|
c.Nicks[n.nick] = cp.Copy()
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *Channel) IsOnStr(n string) (*Nick, bool) {
|
func (ch *channel) isOn(nk *nick) (*ChanPrivs, bool) {
|
||||||
nk, ok := ch.lookup[n]
|
cp, ok := ch.nicks[nk]
|
||||||
return nk, ok
|
return cp.Copy(), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Associates a Nick with a Channel
|
// Associates a Nick with a Channel
|
||||||
func (ch *Channel) addNick(nk *Nick, cp *ChanPrivs) {
|
func (ch *channel) addNick(nk *nick, cp *ChanPrivs) {
|
||||||
if _, ok := ch.nicks[nk]; !ok {
|
if _, ok := ch.nicks[nk]; !ok {
|
||||||
ch.nicks[nk] = cp
|
ch.nicks[nk] = cp
|
||||||
ch.lookup[nk.Nick] = nk
|
ch.lookup[nk.nick] = nk
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Channel.addNick(): %s already on %s.", nk.Nick, ch.Name)
|
logging.Warn("Channel.addNick(): %s already on %s.", nk.nick, ch.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disassociates a Nick from a Channel.
|
// Disassociates a Nick from a Channel.
|
||||||
func (ch *Channel) delNick(nk *Nick) {
|
func (ch *channel) delNick(nk *nick) {
|
||||||
if _, ok := ch.nicks[nk]; ok {
|
if _, ok := ch.nicks[nk]; ok {
|
||||||
delete(ch.nicks, nk)
|
delete(ch.nicks, nk)
|
||||||
delete(ch.lookup, nk.Nick)
|
delete(ch.lookup, nk.nick)
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Channel.delNick(): %s not on %s.", nk.Nick, ch.Name)
|
logging.Warn("Channel.delNick(): %s not on %s.", nk.nick, ch.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses mode strings for a channel.
|
// Parses mode strings for a channel.
|
||||||
func (ch *Channel) ParseModes(modes string, modeargs ...string) {
|
func (ch *channel) parseModes(modes string, modeargs ...string) {
|
||||||
var modeop bool // true => add mode, false => remove mode
|
var modeop bool // true => add mode, false => remove mode
|
||||||
var modestr string
|
var modestr string
|
||||||
for i := 0; i < len(modes); i++ {
|
for i := 0; i < len(modes); i++ {
|
||||||
|
@ -151,43 +166,43 @@ func (ch *Channel) ParseModes(modes string, modeargs ...string) {
|
||||||
modeop = false
|
modeop = false
|
||||||
modestr = string(m)
|
modestr = string(m)
|
||||||
case 'i':
|
case 'i':
|
||||||
ch.Modes.InviteOnly = modeop
|
ch.modes.InviteOnly = modeop
|
||||||
case 'm':
|
case 'm':
|
||||||
ch.Modes.Moderated = modeop
|
ch.modes.Moderated = modeop
|
||||||
case 'n':
|
case 'n':
|
||||||
ch.Modes.NoExternalMsg = modeop
|
ch.modes.NoExternalMsg = modeop
|
||||||
case 'p':
|
case 'p':
|
||||||
ch.Modes.Private = modeop
|
ch.modes.Private = modeop
|
||||||
case 'r':
|
case 'r':
|
||||||
ch.Modes.Registered = modeop
|
ch.modes.Registered = modeop
|
||||||
case 's':
|
case 's':
|
||||||
ch.Modes.Secret = modeop
|
ch.modes.Secret = modeop
|
||||||
case 't':
|
case 't':
|
||||||
ch.Modes.ProtectedTopic = modeop
|
ch.modes.ProtectedTopic = modeop
|
||||||
case 'z':
|
case 'z':
|
||||||
ch.Modes.SSLOnly = modeop
|
ch.modes.SSLOnly = modeop
|
||||||
case 'Z':
|
case 'Z':
|
||||||
ch.Modes.AllSSL = modeop
|
ch.modes.AllSSL = modeop
|
||||||
case 'O':
|
case 'O':
|
||||||
ch.Modes.OperOnly = modeop
|
ch.modes.OperOnly = modeop
|
||||||
case 'k':
|
case 'k':
|
||||||
if modeop && len(modeargs) != 0 {
|
if modeop && len(modeargs) != 0 {
|
||||||
ch.Modes.Key, modeargs = modeargs[0], modeargs[1:]
|
ch.modes.Key, modeargs = modeargs[0], modeargs[1:]
|
||||||
} else if !modeop {
|
} else if !modeop {
|
||||||
ch.Modes.Key = ""
|
ch.modes.Key = ""
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
||||||
"process MODE %s %s%c", ch.Name, modestr, m)
|
"process MODE %s %s%c", ch.name, modestr, m)
|
||||||
}
|
}
|
||||||
case 'l':
|
case 'l':
|
||||||
if modeop && len(modeargs) != 0 {
|
if modeop && len(modeargs) != 0 {
|
||||||
ch.Modes.Limit, _ = strconv.Atoi(modeargs[0])
|
ch.modes.Limit, _ = strconv.Atoi(modeargs[0])
|
||||||
modeargs = modeargs[1:]
|
modeargs = modeargs[1:]
|
||||||
} else if !modeop {
|
} else if !modeop {
|
||||||
ch.Modes.Limit = 0
|
ch.modes.Limit = 0
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
||||||
"process MODE %s %s%c", ch.Name, modestr, m)
|
"process MODE %s %s%c", ch.name, modestr, m)
|
||||||
}
|
}
|
||||||
case 'q', 'a', 'o', 'h', 'v':
|
case 'q', 'a', 'o', 'h', 'v':
|
||||||
if len(modeargs) != 0 {
|
if len(modeargs) != 0 {
|
||||||
|
@ -208,11 +223,11 @@ func (ch *Channel) ParseModes(modes string, modeargs ...string) {
|
||||||
modeargs = modeargs[1:]
|
modeargs = modeargs[1:]
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Channel.ParseModes(): untracked nick %s "+
|
logging.Warn("Channel.ParseModes(): untracked nick %s "+
|
||||||
"received MODE on channel %s", modeargs[0], ch.Name)
|
"received MODE on channel %s", modeargs[0], ch.name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
logging.Warn("Channel.ParseModes(): not enough arguments to "+
|
||||||
"process MODE %s %s%c", ch.Name, modestr, m)
|
"process MODE %s %s%c", ch.name, modestr, m)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
logging.Info("Channel.ParseModes(): unknown mode char %c", m)
|
logging.Info("Channel.ParseModes(): unknown mode char %c", m)
|
||||||
|
@ -220,29 +235,39 @@ func (ch *Channel) ParseModes(modes string, modeargs ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type byNick []*Nick
|
// Returns true if the Nick is associated with the Channel
|
||||||
|
func (ch *Channel) IsOn(nk string) (*ChanPrivs, bool) {
|
||||||
func (b byNick) Len() int { return len(b) }
|
cp, ok := ch.Nicks[nk]
|
||||||
func (b byNick) Less(i, j int) bool { return b[i].Nick < b[j].Nick }
|
return cp, ok
|
||||||
func (b byNick) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
||||||
|
|
||||||
// Nicks returns a list of *Nick that are on the channel, sorted by nick.
|
|
||||||
func (ch *Channel) Nicks() []*Nick {
|
|
||||||
nicks := make([]*Nick, 0, len(ch.lookup))
|
|
||||||
for _, nick := range ch.lookup {
|
|
||||||
nicks = append(nicks, nick)
|
|
||||||
}
|
|
||||||
sort.Sort(byNick(nicks))
|
|
||||||
return nicks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NicksStr returns a list of nick strings that are on the channel, sorted by nick.
|
// Test Channel equality.
|
||||||
func (ch *Channel) NicksStr() []string {
|
func (ch *Channel) Equals(other *Channel) bool {
|
||||||
var nicks []string
|
return reflect.DeepEqual(ch, other)
|
||||||
for _, nick := range ch.Nicks() {
|
}
|
||||||
nicks = append(nicks, nick.Nick)
|
|
||||||
}
|
// Duplicates a ChanMode struct.
|
||||||
return nicks
|
func (cm *ChanMode) Copy() *ChanMode {
|
||||||
|
if cm == nil { return nil }
|
||||||
|
c := *cm
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test ChanMode equality.
|
||||||
|
func (cm *ChanMode) Equals(other *ChanMode) bool {
|
||||||
|
return reflect.DeepEqual(cm, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicates a ChanPrivs struct.
|
||||||
|
func (cp *ChanPrivs) Copy() *ChanPrivs {
|
||||||
|
if cp == nil { return nil }
|
||||||
|
c := *cp
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test ChanPrivs equality.
|
||||||
|
func (cp *ChanPrivs) Equals(other *ChanPrivs) bool {
|
||||||
|
return reflect.DeepEqual(cp, other)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a string representing the channel. Looks like:
|
// Returns a string representing the channel. Looks like:
|
||||||
|
@ -257,12 +282,16 @@ func (ch *Channel) String() string {
|
||||||
str += "Topic: " + ch.Topic + "\n\t"
|
str += "Topic: " + ch.Topic + "\n\t"
|
||||||
str += "Modes: " + ch.Modes.String() + "\n\t"
|
str += "Modes: " + ch.Modes.String() + "\n\t"
|
||||||
str += "Nicks: \n"
|
str += "Nicks: \n"
|
||||||
for nk, cp := range ch.nicks {
|
for nk, cp := range ch.Nicks {
|
||||||
str += "\t\t" + nk.Nick + ": " + cp.String() + "\n"
|
str += "\t\t" + nk + ": " + cp.String() + "\n"
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch *channel) String() string {
|
||||||
|
return ch.Channel().String()
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a string representing the channel modes. Looks like:
|
// Returns a string representing the channel modes. Looks like:
|
||||||
// +npk key
|
// +npk key
|
||||||
func (cm *ChanMode) String() string {
|
func (cm *ChanMode) String() string {
|
||||||
|
@ -284,7 +313,7 @@ func (cm *ChanMode) String() string {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
if f.Int() != 0 {
|
if f.Int() != 0 {
|
||||||
str += ChanModeToString[t.Field(i).Name]
|
str += ChanModeToString[t.Field(i).Name]
|
||||||
a = append(a, fmt.Sprintf("%d", f.Int()))
|
a = append(a, strconv.FormatInt(f.Int(), 10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"testing"
|
|
||||||
)
|
func compareChannel(t *testing.T, ch *channel) {
|
||||||
|
c := ch.Channel()
|
||||||
|
if c.Name != ch.name || c.Topic != ch.topic ||
|
||||||
|
!c.Modes.Equals(ch.modes) || len(c.Nicks) != len(ch.nicks) {
|
||||||
|
t.Errorf("Channel not duped correctly from internal state.")
|
||||||
|
}
|
||||||
|
for nk, cp := range ch.nicks {
|
||||||
|
if other, ok := c.Nicks[nk.nick]; !ok || !cp.Equals(other) {
|
||||||
|
t.Errorf("Nick not duped correctly from internal state.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewChannel(t *testing.T) {
|
func TestNewChannel(t *testing.T) {
|
||||||
ch := NewChannel("#test1")
|
ch := newChannel("#test1")
|
||||||
|
|
||||||
if ch.Name != "#test1" {
|
if ch.name != "#test1" {
|
||||||
t.Errorf("Channel not created correctly by NewChannel()")
|
t.Errorf("Channel not created correctly by NewChannel()")
|
||||||
}
|
}
|
||||||
if len(ch.nicks) != 0 || len(ch.lookup) != 0 {
|
if len(ch.nicks) != 0 || len(ch.lookup) != 0 {
|
||||||
t.Errorf("Channel maps contain data after NewChannel()")
|
t.Errorf("Channel maps contain data after NewChannel()")
|
||||||
}
|
}
|
||||||
|
compareChannel(t, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddNick(t *testing.T) {
|
func TestAddNick(t *testing.T) {
|
||||||
ch := NewChannel("#test1")
|
ch := newChannel("#test1")
|
||||||
nk := NewNick("test1")
|
nk := newNick("test1")
|
||||||
cp := new(ChanPrivs)
|
cp := new(ChanPrivs)
|
||||||
|
|
||||||
ch.addNick(nk, cp)
|
ch.addNick(nk, cp)
|
||||||
|
@ -31,11 +43,12 @@ func TestAddNick(t *testing.T) {
|
||||||
if n, ok := ch.lookup["test1"]; !ok || n != nk {
|
if n, ok := ch.lookup["test1"]; !ok || n != nk {
|
||||||
t.Errorf("Nick test1 not properly stored in lookup map.")
|
t.Errorf("Nick test1 not properly stored in lookup map.")
|
||||||
}
|
}
|
||||||
|
compareChannel(t, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelNick(t *testing.T) {
|
func TestDelNick(t *testing.T) {
|
||||||
ch := NewChannel("#test1")
|
ch := newChannel("#test1")
|
||||||
nk := NewNick("test1")
|
nk := newNick("test1")
|
||||||
cp := new(ChanPrivs)
|
cp := new(ChanPrivs)
|
||||||
|
|
||||||
ch.addNick(nk, cp)
|
ch.addNick(nk, cp)
|
||||||
|
@ -49,18 +62,20 @@ func TestDelNick(t *testing.T) {
|
||||||
if n, ok := ch.lookup["#test1"]; ok || n != nil {
|
if n, ok := ch.lookup["#test1"]; ok || n != nil {
|
||||||
t.Errorf("Nick test1 not properly removed from lookup map.")
|
t.Errorf("Nick test1 not properly removed from lookup map.")
|
||||||
}
|
}
|
||||||
|
compareChannel(t, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChannelParseModes(t *testing.T) {
|
func TestChannelParseModes(t *testing.T) {
|
||||||
ch := NewChannel("#test1")
|
ch := newChannel("#test1")
|
||||||
md := ch.Modes
|
md := ch.modes
|
||||||
|
|
||||||
// Channel modes can adjust channel privs too, so we need a Nick
|
// Channel modes can adjust channel privs too, so we need a Nick
|
||||||
nk := NewNick("test1")
|
nk := newNick("test1")
|
||||||
cp := new(ChanPrivs)
|
cp := new(ChanPrivs)
|
||||||
ch.addNick(nk, cp)
|
ch.addNick(nk, cp)
|
||||||
|
|
||||||
// Test bools first.
|
// Test bools first.
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Private || md.Secret || md.ProtectedTopic || md.NoExternalMsg ||
|
if md.Private || md.Secret || md.ProtectedTopic || md.NoExternalMsg ||
|
||||||
md.Moderated || md.InviteOnly || md.OperOnly || md.SSLOnly {
|
md.Moderated || md.InviteOnly || md.OperOnly || md.SSLOnly {
|
||||||
t.Errorf("Modes for new channel set to true.")
|
t.Errorf("Modes for new channel set to true.")
|
||||||
|
@ -72,8 +87,9 @@ func TestChannelParseModes(t *testing.T) {
|
||||||
md.InviteOnly = true
|
md.InviteOnly = true
|
||||||
|
|
||||||
// Flip some MOAR bits.
|
// Flip some MOAR bits.
|
||||||
ch.ParseModes("+s-p+tm-i")
|
ch.parseModes("+s-p+tm-i")
|
||||||
|
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Private || !md.Secret || !md.ProtectedTopic || !md.NoExternalMsg ||
|
if md.Private || !md.Secret || !md.ProtectedTopic || !md.NoExternalMsg ||
|
||||||
!md.Moderated || md.InviteOnly || md.OperOnly || md.SSLOnly {
|
!md.Moderated || md.InviteOnly || md.OperOnly || md.SSLOnly {
|
||||||
t.Errorf("Modes not flipped correctly by ParseModes.")
|
t.Errorf("Modes not flipped correctly by ParseModes.")
|
||||||
|
@ -85,19 +101,22 @@ func TestChannelParseModes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable limit correctly
|
// enable limit correctly
|
||||||
ch.ParseModes("+l", "256")
|
ch.parseModes("+l", "256")
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Limit != 256 {
|
if md.Limit != 256 {
|
||||||
t.Errorf("Limit for channel not set correctly")
|
t.Errorf("Limit for channel not set correctly")
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable limit incorrectly
|
// enable limit incorrectly
|
||||||
ch.ParseModes("+l")
|
ch.parseModes("+l")
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Limit != 256 {
|
if md.Limit != 256 {
|
||||||
t.Errorf("Bad limit value caused limit to be unset.")
|
t.Errorf("Bad limit value caused limit to be unset.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable limit correctly
|
// disable limit correctly
|
||||||
ch.ParseModes("-l")
|
ch.parseModes("-l")
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Limit != 0 {
|
if md.Limit != 0 {
|
||||||
t.Errorf("Limit for channel not unset correctly")
|
t.Errorf("Limit for channel not unset correctly")
|
||||||
}
|
}
|
||||||
|
@ -108,19 +127,22 @@ func TestChannelParseModes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable key correctly
|
// enable key correctly
|
||||||
ch.ParseModes("+k", "foobar")
|
ch.parseModes("+k", "foobar")
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Key != "foobar" {
|
if md.Key != "foobar" {
|
||||||
t.Errorf("Key for channel not set correctly")
|
t.Errorf("Key for channel not set correctly")
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable key incorrectly
|
// enable key incorrectly
|
||||||
ch.ParseModes("+k")
|
ch.parseModes("+k")
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Key != "foobar" {
|
if md.Key != "foobar" {
|
||||||
t.Errorf("Bad key value caused key to be unset.")
|
t.Errorf("Bad key value caused key to be unset.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable key correctly
|
// disable key correctly
|
||||||
ch.ParseModes("-k")
|
ch.parseModes("-k")
|
||||||
|
compareChannel(t, ch)
|
||||||
if md.Key != "" {
|
if md.Key != "" {
|
||||||
t.Errorf("Key for channel not unset correctly")
|
t.Errorf("Key for channel not unset correctly")
|
||||||
}
|
}
|
||||||
|
@ -128,16 +150,18 @@ func TestChannelParseModes(t *testing.T) {
|
||||||
// Test chan privs parsing.
|
// Test chan privs parsing.
|
||||||
cp.Op = true
|
cp.Op = true
|
||||||
cp.HalfOp = true
|
cp.HalfOp = true
|
||||||
ch.ParseModes("+aq-o", "test1", "test1", "test1")
|
ch.parseModes("+aq-o", "test1", "test1", "test1")
|
||||||
|
|
||||||
|
compareChannel(t, ch)
|
||||||
if !cp.Owner || !cp.Admin || cp.Op || !cp.HalfOp || cp.Voice {
|
if !cp.Owner || !cp.Admin || cp.Op || !cp.HalfOp || cp.Voice {
|
||||||
t.Errorf("Channel privileges not flipped correctly by ParseModes.")
|
t.Errorf("Channel privileges not flipped correctly by ParseModes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test a random mix of modes, just to be sure
|
// Test a random mix of modes, just to be sure
|
||||||
md.Limit = 256
|
md.Limit = 256
|
||||||
ch.ParseModes("+zpt-qsl+kv-h", "test1", "foobar", "test1")
|
ch.parseModes("+zpt-qsl+kv-h", "test1", "foobar", "test1")
|
||||||
|
|
||||||
|
compareChannel(t, ch)
|
||||||
if !md.Private || md.Secret || !md.ProtectedTopic || !md.NoExternalMsg ||
|
if !md.Private || md.Secret || !md.ProtectedTopic || !md.NoExternalMsg ||
|
||||||
!md.Moderated || md.InviteOnly || md.OperOnly || !md.SSLOnly {
|
!md.Moderated || md.InviteOnly || md.OperOnly || !md.SSLOnly {
|
||||||
t.Errorf("Modes not flipped correctly by ParseModes (2).")
|
t.Errorf("Modes not flipped correctly by ParseModes (2).")
|
||||||
|
|
|
@ -48,22 +48,46 @@ func (_mr *_MockTrackerRecorder) GetNick(arg0 interface{}) *gomock.Call {
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetNick", arg0)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetNick", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockTracker) ReNick(old string, neu string) {
|
func (_m *MockTracker) ReNick(old string, neu string) *Nick {
|
||||||
_m.ctrl.Call(_m, "ReNick", old, neu)
|
ret := _m.ctrl.Call(_m, "ReNick", old, neu)
|
||||||
|
ret0, _ := ret[0].(*Nick)
|
||||||
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockTrackerRecorder) ReNick(arg0, arg1 interface{}) *gomock.Call {
|
func (_mr *_MockTrackerRecorder) ReNick(arg0, arg1 interface{}) *gomock.Call {
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "ReNick", arg0, arg1)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "ReNick", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockTracker) DelNick(nick string) {
|
func (_m *MockTracker) DelNick(nick string) *Nick {
|
||||||
_m.ctrl.Call(_m, "DelNick", nick)
|
ret := _m.ctrl.Call(_m, "DelNick", nick)
|
||||||
|
ret0, _ := ret[0].(*Nick)
|
||||||
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockTrackerRecorder) DelNick(arg0 interface{}) *gomock.Call {
|
func (_mr *_MockTrackerRecorder) DelNick(arg0 interface{}) *gomock.Call {
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "DelNick", arg0)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "DelNick", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_m *MockTracker) NickInfo(nick string, ident string, host string, name string) *Nick {
|
||||||
|
ret := _m.ctrl.Call(_m, "NickInfo", nick, ident, host, name)
|
||||||
|
ret0, _ := ret[0].(*Nick)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_mr *_MockTrackerRecorder) NickInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "NickInfo", arg0, arg1, arg2, arg3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockTracker) NickModes(nick string, modestr string) *Nick {
|
||||||
|
ret := _m.ctrl.Call(_m, "NickModes", nick, modestr)
|
||||||
|
ret0, _ := ret[0].(*Nick)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_mr *_MockTrackerRecorder) NickModes(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "NickModes", arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
func (_m *MockTracker) NewChannel(channel string) *Channel {
|
func (_m *MockTracker) NewChannel(channel string) *Channel {
|
||||||
ret := _m.ctrl.Call(_m, "NewChannel", channel)
|
ret := _m.ctrl.Call(_m, "NewChannel", channel)
|
||||||
ret0, _ := ret[0].(*Channel)
|
ret0, _ := ret[0].(*Channel)
|
||||||
|
@ -84,14 +108,41 @@ func (_mr *_MockTrackerRecorder) GetChannel(arg0 interface{}) *gomock.Call {
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetChannel", arg0)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetChannel", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockTracker) DelChannel(channel string) {
|
func (_m *MockTracker) DelChannel(channel string) *Channel {
|
||||||
_m.ctrl.Call(_m, "DelChannel", channel)
|
ret := _m.ctrl.Call(_m, "DelChannel", channel)
|
||||||
|
ret0, _ := ret[0].(*Channel)
|
||||||
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockTrackerRecorder) DelChannel(arg0 interface{}) *gomock.Call {
|
func (_mr *_MockTrackerRecorder) DelChannel(arg0 interface{}) *gomock.Call {
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "DelChannel", arg0)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "DelChannel", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_m *MockTracker) Topic(channel string, topic string) *Channel {
|
||||||
|
ret := _m.ctrl.Call(_m, "Topic", channel, topic)
|
||||||
|
ret0, _ := ret[0].(*Channel)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_mr *_MockTrackerRecorder) Topic(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "Topic", arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockTracker) ChannelModes(channel string, modestr string, modeargs ...string) *Channel {
|
||||||
|
_s := []interface{}{channel, modestr}
|
||||||
|
for _, _x := range modeargs {
|
||||||
|
_s = append(_s, _x)
|
||||||
|
}
|
||||||
|
ret := _m.ctrl.Call(_m, "ChannelModes", _s...)
|
||||||
|
ret0, _ := ret[0].(*Channel)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_mr *_MockTrackerRecorder) ChannelModes(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||||
|
_s := append([]interface{}{arg0, arg1}, arg2...)
|
||||||
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "ChannelModes", _s...)
|
||||||
|
}
|
||||||
|
|
||||||
func (_m *MockTracker) Me() *Nick {
|
func (_m *MockTracker) Me() *Nick {
|
||||||
ret := _m.ctrl.Call(_m, "Me")
|
ret := _m.ctrl.Call(_m, "Me")
|
||||||
ret0, _ := ret[0].(*Nick)
|
ret0, _ := ret[0].(*Nick)
|
||||||
|
@ -113,7 +164,7 @@ func (_mr *_MockTrackerRecorder) IsOn(arg0, arg1 interface{}) *gomock.Call {
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "IsOn", arg0, arg1)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "IsOn", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockTracker) Associate(channel *Channel, nick *Nick) *ChanPrivs {
|
func (_m *MockTracker) Associate(channel string, nick string) *ChanPrivs {
|
||||||
ret := _m.ctrl.Call(_m, "Associate", channel, nick)
|
ret := _m.ctrl.Call(_m, "Associate", channel, nick)
|
||||||
ret0, _ := ret[0].(*ChanPrivs)
|
ret0, _ := ret[0].(*ChanPrivs)
|
||||||
return ret0
|
return ret0
|
||||||
|
@ -123,7 +174,7 @@ func (_mr *_MockTrackerRecorder) Associate(arg0, arg1 interface{}) *gomock.Call
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Associate", arg0, arg1)
|
return _mr.mock.ctrl.RecordCall(_mr.mock, "Associate", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockTracker) Dissociate(channel *Channel, nick *Nick) {
|
func (_m *MockTracker) Dissociate(channel string, nick string) {
|
||||||
_m.ctrl.Call(_m, "Dissociate", channel, nick)
|
_m.ctrl.Call(_m, "Dissociate", channel, nick)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
128
state/nick.go
128
state/nick.go
|
@ -4,15 +4,22 @@ import (
|
||||||
"github.com/fluffle/goirc/logging"
|
"github.com/fluffle/goirc/logging"
|
||||||
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A struct representing an IRC nick
|
// A Nick is returned from the state tracker and contains
|
||||||
|
// a copy of the nick state at a particular time.
|
||||||
type Nick struct {
|
type Nick struct {
|
||||||
Nick, Ident, Host, Name string
|
Nick, Ident, Host, Name string
|
||||||
Modes *NickMode
|
Modes *NickMode
|
||||||
lookup map[string]*Channel
|
Channels map[string]*ChanPrivs
|
||||||
chans map[*Channel]*ChanPrivs
|
}
|
||||||
|
|
||||||
|
// Internal bookkeeping struct for nicks.
|
||||||
|
type nick struct {
|
||||||
|
nick, ident, host, name string
|
||||||
|
modes *NickMode
|
||||||
|
lookup map[string]*channel
|
||||||
|
chans map[*channel]*ChanPrivs
|
||||||
}
|
}
|
||||||
|
|
||||||
// A struct representing the modes of an IRC Nick (User Modes)
|
// A struct representing the modes of an IRC Nick (User Modes)
|
||||||
|
@ -43,51 +50,62 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************\
|
/******************************************************************************\
|
||||||
* Nick methods for state management
|
* nick methods for state management
|
||||||
\******************************************************************************/
|
\******************************************************************************/
|
||||||
|
|
||||||
func NewNick(n string) *Nick {
|
func newNick(n string) *nick {
|
||||||
return &Nick{
|
return &nick{
|
||||||
Nick: n,
|
nick: n,
|
||||||
Modes: new(NickMode),
|
modes: new(NickMode),
|
||||||
chans: make(map[*Channel]*ChanPrivs),
|
chans: make(map[*channel]*ChanPrivs),
|
||||||
lookup: make(map[string]*Channel),
|
lookup: make(map[string]*channel),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the Nick is associated with the Channel.
|
// Returns a copy of the internal tracker nick state at this time.
|
||||||
func (nk *Nick) IsOn(ch *Channel) (*ChanPrivs, bool) {
|
// Relies on tracker-level locking for concurrent access.
|
||||||
cp, ok := nk.chans[ch]
|
func (nk *nick) Nick() *Nick {
|
||||||
return cp, ok
|
n := &Nick{
|
||||||
|
Nick: nk.nick,
|
||||||
|
Ident: nk.ident,
|
||||||
|
Host: nk.host,
|
||||||
|
Name: nk.name,
|
||||||
|
Modes: nk.modes.Copy(),
|
||||||
|
Channels: make(map[string]*ChanPrivs),
|
||||||
|
}
|
||||||
|
for c, cp := range nk.chans {
|
||||||
|
n.Channels[c.name] = cp.Copy()
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nk *Nick) IsOnStr(c string) (*Channel, bool) {
|
func (nk *nick) isOn(ch *channel) (*ChanPrivs, bool) {
|
||||||
ch, ok := nk.lookup[c]
|
cp, ok := nk.chans[ch]
|
||||||
return ch, ok
|
return cp.Copy(), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Associates a Channel with a Nick.
|
// Associates a Channel with a Nick.
|
||||||
func (nk *Nick) addChannel(ch *Channel, cp *ChanPrivs) {
|
func (nk *nick) addChannel(ch *channel, cp *ChanPrivs) {
|
||||||
if _, ok := nk.chans[ch]; !ok {
|
if _, ok := nk.chans[ch]; !ok {
|
||||||
nk.chans[ch] = cp
|
nk.chans[ch] = cp
|
||||||
nk.lookup[ch.Name] = ch
|
nk.lookup[ch.name] = ch
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Nick.addChannel(): %s already on %s.", nk.Nick, ch.Name)
|
logging.Warn("Nick.addChannel(): %s already on %s.", nk.nick, ch.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disassociates a Channel from a Nick.
|
// Disassociates a Channel from a Nick.
|
||||||
func (nk *Nick) delChannel(ch *Channel) {
|
func (nk *nick) delChannel(ch *channel) {
|
||||||
if _, ok := nk.chans[ch]; ok {
|
if _, ok := nk.chans[ch]; ok {
|
||||||
delete(nk.chans, ch)
|
delete(nk.chans, ch)
|
||||||
delete(nk.lookup, ch.Name)
|
delete(nk.lookup, ch.name)
|
||||||
} else {
|
} else {
|
||||||
logging.Warn("Nick.delChannel(): %s not on %s.", nk.Nick, ch.Name)
|
logging.Warn("Nick.delChannel(): %s not on %s.", nk.nick, ch.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse mode strings for a Nick.
|
// Parse mode strings for a Nick.
|
||||||
func (nk *Nick) ParseModes(modes string) {
|
func (nk *nick) parseModes(modes string) {
|
||||||
var modeop bool // true => add mode, false => remove mode
|
var modeop bool // true => add mode, false => remove mode
|
||||||
for i := 0; i < len(modes); i++ {
|
for i := 0; i < len(modes); i++ {
|
||||||
switch m := modes[i]; m {
|
switch m := modes[i]; m {
|
||||||
|
@ -96,46 +114,44 @@ func (nk *Nick) ParseModes(modes string) {
|
||||||
case '-':
|
case '-':
|
||||||
modeop = false
|
modeop = false
|
||||||
case 'B':
|
case 'B':
|
||||||
nk.Modes.Bot = modeop
|
nk.modes.Bot = modeop
|
||||||
case 'i':
|
case 'i':
|
||||||
nk.Modes.Invisible = modeop
|
nk.modes.Invisible = modeop
|
||||||
case 'o':
|
case 'o':
|
||||||
nk.Modes.Oper = modeop
|
nk.modes.Oper = modeop
|
||||||
case 'w':
|
case 'w':
|
||||||
nk.Modes.WallOps = modeop
|
nk.modes.WallOps = modeop
|
||||||
case 'x':
|
case 'x':
|
||||||
nk.Modes.HiddenHost = modeop
|
nk.modes.HiddenHost = modeop
|
||||||
case 'z':
|
case 'z':
|
||||||
nk.Modes.SSL = modeop
|
nk.modes.SSL = modeop
|
||||||
default:
|
default:
|
||||||
logging.Info("Nick.ParseModes(): unknown mode char %c", m)
|
logging.Info("Nick.ParseModes(): unknown mode char %c", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type byName []*Channel
|
// Returns true if the Nick is associated with the Channel.
|
||||||
|
func (nk *Nick) IsOn(ch string) (*ChanPrivs, bool) {
|
||||||
func (b byName) Len() int { return len(b) }
|
cp, ok := nk.Channels[ch]
|
||||||
func (b byName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
return cp, ok
|
||||||
func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
||||||
|
|
||||||
// Channels returns a list of *Channel the nick is on, sorted by name.
|
|
||||||
func (nk *Nick) Channels() []*Channel {
|
|
||||||
channels := make([]*Channel, 0, len(nk.lookup))
|
|
||||||
for _, channel := range nk.lookup {
|
|
||||||
channels = append(channels, channel)
|
|
||||||
}
|
|
||||||
sort.Sort(byName(channels))
|
|
||||||
return channels
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelsStr returns a list of channel strings the nick is on, sorted by name.
|
// Tests Nick equality.
|
||||||
func (nk *Nick) ChannelsStr() []string {
|
func (nk *Nick) Equals(other *Nick) bool {
|
||||||
var names []string
|
return reflect.DeepEqual(nk, other)
|
||||||
for _, channel := range nk.Channels() {
|
}
|
||||||
names = append(names, channel.Name)
|
|
||||||
}
|
// Duplicates a NickMode struct.
|
||||||
return names
|
func (nm *NickMode) Copy() *NickMode {
|
||||||
|
if nm == nil { return nil }
|
||||||
|
n := *nm
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests NickMode equality.
|
||||||
|
func (nm *NickMode) Equals(other *NickMode) bool {
|
||||||
|
return reflect.DeepEqual(nm, other)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a string representing the nick. Looks like:
|
// Returns a string representing the nick. Looks like:
|
||||||
|
@ -152,12 +168,16 @@ func (nk *Nick) String() string {
|
||||||
str += "Real Name: " + nk.Name + "\n\t"
|
str += "Real Name: " + nk.Name + "\n\t"
|
||||||
str += "Modes: " + nk.Modes.String() + "\n\t"
|
str += "Modes: " + nk.Modes.String() + "\n\t"
|
||||||
str += "Channels: \n"
|
str += "Channels: \n"
|
||||||
for ch, cp := range nk.chans {
|
for ch, cp := range nk.Channels {
|
||||||
str += "\t\t" + ch.Name + ": " + cp.String() + "\n"
|
str += "\t\t" + ch + ": " + cp.String() + "\n"
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nk *nick) String() string {
|
||||||
|
return nk.Nick().String()
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a string representing the nick modes. Looks like:
|
// Returns a string representing the nick modes. Looks like:
|
||||||
// +iwx
|
// +iwx
|
||||||
func (nm *NickMode) String() string {
|
func (nm *NickMode) String() string {
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"testing"
|
|
||||||
)
|
func compareNick(t *testing.T, nk *nick) {
|
||||||
|
n := nk.Nick()
|
||||||
|
if n.Nick != nk.nick || n.Ident != nk.ident || n.Host != nk.host || n.Name != nk.name ||
|
||||||
|
!n.Modes.Equals(nk.modes) || len(n.Channels) != len(nk.chans) {
|
||||||
|
t.Errorf("Nick not duped correctly from internal state.")
|
||||||
|
}
|
||||||
|
for ch, cp := range nk.chans {
|
||||||
|
if other, ok := n.Channels[ch.name]; !ok || !cp.Equals(other) {
|
||||||
|
t.Errorf("Channel not duped correctly from internal state.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewNick(t *testing.T) {
|
func TestNewNick(t *testing.T) {
|
||||||
nk := NewNick("test1")
|
nk := newNick("test1")
|
||||||
|
|
||||||
if nk.Nick != "test1" {
|
if nk.nick != "test1" {
|
||||||
t.Errorf("Nick not created correctly by NewNick()")
|
t.Errorf("Nick not created correctly by NewNick()")
|
||||||
}
|
}
|
||||||
if len(nk.chans) != 0 || len(nk.lookup) != 0 {
|
if len(nk.chans) != 0 || len(nk.lookup) != 0 {
|
||||||
t.Errorf("Nick maps contain data after NewNick()")
|
t.Errorf("Nick maps contain data after NewNick()")
|
||||||
}
|
}
|
||||||
|
compareNick(t, nk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddChannel(t *testing.T) {
|
func TestAddChannel(t *testing.T) {
|
||||||
nk := NewNick("test1")
|
nk := newNick("test1")
|
||||||
ch := NewChannel("#test1")
|
ch := newChannel("#test1")
|
||||||
cp := new(ChanPrivs)
|
cp := new(ChanPrivs)
|
||||||
|
|
||||||
nk.addChannel(ch, cp)
|
nk.addChannel(ch, cp)
|
||||||
|
@ -31,11 +43,12 @@ func TestAddChannel(t *testing.T) {
|
||||||
if c, ok := nk.lookup["#test1"]; !ok || c != ch {
|
if c, ok := nk.lookup["#test1"]; !ok || c != ch {
|
||||||
t.Errorf("Channel #test1 not properly stored in lookup map.")
|
t.Errorf("Channel #test1 not properly stored in lookup map.")
|
||||||
}
|
}
|
||||||
|
compareNick(t, nk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelChannel(t *testing.T) {
|
func TestDelChannel(t *testing.T) {
|
||||||
nk := NewNick("test1")
|
nk := newNick("test1")
|
||||||
ch := NewChannel("#test1")
|
ch := newChannel("#test1")
|
||||||
cp := new(ChanPrivs)
|
cp := new(ChanPrivs)
|
||||||
|
|
||||||
nk.addChannel(ch, cp)
|
nk.addChannel(ch, cp)
|
||||||
|
@ -49,11 +62,12 @@ func TestDelChannel(t *testing.T) {
|
||||||
if c, ok := nk.lookup["#test1"]; ok || c != nil {
|
if c, ok := nk.lookup["#test1"]; ok || c != nil {
|
||||||
t.Errorf("Channel #test1 not properly removed from lookup map.")
|
t.Errorf("Channel #test1 not properly removed from lookup map.")
|
||||||
}
|
}
|
||||||
|
compareNick(t, nk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNickParseModes(t *testing.T) {
|
func TestNickParseModes(t *testing.T) {
|
||||||
nk := NewNick("test1")
|
nk := newNick("test1")
|
||||||
md := nk.Modes
|
md := nk.modes
|
||||||
|
|
||||||
// Modes should all be false for a new nick
|
// Modes should all be false for a new nick
|
||||||
if md.Invisible || md.Oper || md.WallOps || md.HiddenHost || md.SSL {
|
if md.Invisible || md.Oper || md.WallOps || md.HiddenHost || md.SSL {
|
||||||
|
@ -65,8 +79,9 @@ func TestNickParseModes(t *testing.T) {
|
||||||
md.HiddenHost = true
|
md.HiddenHost = true
|
||||||
|
|
||||||
// Parse a mode line that flips one true to false and two false to true
|
// Parse a mode line that flips one true to false and two false to true
|
||||||
nk.ParseModes("+z-x+w")
|
nk.parseModes("+z-x+w")
|
||||||
|
|
||||||
|
compareNick(t, nk)
|
||||||
if !md.Invisible || md.Oper || !md.WallOps || md.HiddenHost || !md.SSL {
|
if !md.Invisible || md.Oper || !md.WallOps || md.HiddenHost || !md.SSL {
|
||||||
t.Errorf("Modes not flipped correctly by ParseModes.")
|
t.Errorf("Modes not flipped correctly by ParseModes.")
|
||||||
}
|
}
|
||||||
|
|
242
state/tracker.go
242
state/tracker.go
|
@ -2,6 +2,8 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/fluffle/goirc/logging"
|
"github.com/fluffle/goirc/logging"
|
||||||
|
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The state manager interface
|
// The state manager interface
|
||||||
|
@ -9,18 +11,22 @@ type Tracker interface {
|
||||||
// Nick methods
|
// Nick methods
|
||||||
NewNick(nick string) *Nick
|
NewNick(nick string) *Nick
|
||||||
GetNick(nick string) *Nick
|
GetNick(nick string) *Nick
|
||||||
ReNick(old, neu string)
|
ReNick(old, neu string) *Nick
|
||||||
DelNick(nick string)
|
DelNick(nick string) *Nick
|
||||||
|
NickInfo(nick, ident, host, name string) *Nick
|
||||||
|
NickModes(nick, modestr string) *Nick
|
||||||
// Channel methods
|
// Channel methods
|
||||||
NewChannel(channel string) *Channel
|
NewChannel(channel string) *Channel
|
||||||
GetChannel(channel string) *Channel
|
GetChannel(channel string) *Channel
|
||||||
DelChannel(channel string)
|
DelChannel(channel string) *Channel
|
||||||
|
Topic(channel, topic string) *Channel
|
||||||
|
ChannelModes(channel, modestr string, modeargs ...string) *Channel
|
||||||
// Information about ME!
|
// Information about ME!
|
||||||
Me() *Nick
|
Me() *Nick
|
||||||
// And the tracking operations
|
// And the tracking operations
|
||||||
IsOn(channel, nick string) (*ChanPrivs, bool)
|
IsOn(channel, nick string) (*ChanPrivs, bool)
|
||||||
Associate(channel *Channel, nick *Nick) *ChanPrivs
|
Associate(channel, nick string) *ChanPrivs
|
||||||
Dissociate(channel *Channel, nick *Nick)
|
Dissociate(channel, nick string)
|
||||||
Wipe()
|
Wipe()
|
||||||
// The state tracker can output a debugging string
|
// The state tracker can output a debugging string
|
||||||
String() string
|
String() string
|
||||||
|
@ -29,26 +35,34 @@ type Tracker interface {
|
||||||
// ... and a struct to implement it ...
|
// ... and a struct to implement it ...
|
||||||
type stateTracker struct {
|
type stateTracker struct {
|
||||||
// Map of channels we're on
|
// Map of channels we're on
|
||||||
chans map[string]*Channel
|
chans map[string]*channel
|
||||||
// Map of nicks we know about
|
// Map of nicks we know about
|
||||||
nicks map[string]*Nick
|
nicks map[string]*nick
|
||||||
|
|
||||||
// We need to keep state on who we are :-)
|
// We need to keep state on who we are :-)
|
||||||
me *Nick
|
me *nick
|
||||||
|
|
||||||
|
// And we need to protect against data races *cough*.
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Tracker = (*stateTracker)(nil)
|
||||||
|
|
||||||
// ... and a constructor to make it ...
|
// ... and a constructor to make it ...
|
||||||
func NewTracker(mynick string) *stateTracker {
|
func NewTracker(mynick string) *stateTracker {
|
||||||
st := &stateTracker{
|
st := &stateTracker{
|
||||||
chans: make(map[string]*Channel),
|
chans: make(map[string]*channel),
|
||||||
nicks: make(map[string]*Nick),
|
nicks: make(map[string]*nick),
|
||||||
}
|
}
|
||||||
st.me = st.NewNick(mynick)
|
st.me = newNick(mynick)
|
||||||
|
st.nicks[mynick] = st.me
|
||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... and a method to wipe the state clean.
|
// ... and a method to wipe the state clean.
|
||||||
func (st *stateTracker) Wipe() {
|
func (st *stateTracker) Wipe() {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
// Deleting all the channels implicitly deletes every nick but me.
|
// Deleting all the channels implicitly deletes every nick but me.
|
||||||
for _, ch := range st.chans {
|
for _, ch := range st.chans {
|
||||||
st.delChannel(ch)
|
st.delChannel(ch)
|
||||||
|
@ -59,31 +73,49 @@ func (st *stateTracker) Wipe() {
|
||||||
* tracker methods to create/look up nicks/channels
|
* tracker methods to create/look up nicks/channels
|
||||||
\******************************************************************************/
|
\******************************************************************************/
|
||||||
|
|
||||||
// Creates a new Nick, initialises it, and stores it so it
|
// Creates a new nick, initialises it, and stores it so it
|
||||||
// can be properly tracked for state management purposes.
|
// can be properly tracked for state management purposes.
|
||||||
func (st *stateTracker) NewNick(n string) *Nick {
|
func (st *stateTracker) NewNick(n string) *Nick {
|
||||||
|
if n == "" {
|
||||||
|
logging.Warn("Tracker.NewNick(): Not tracking empty nick.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
if _, ok := st.nicks[n]; ok {
|
if _, ok := st.nicks[n]; ok {
|
||||||
logging.Warn("Tracker.NewNick(): %s already tracked.", n)
|
logging.Warn("Tracker.NewNick(): %s already tracked.", n)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
st.nicks[n] = NewNick(n)
|
st.nicks[n] = newNick(n)
|
||||||
return st.nicks[n]
|
return st.nicks[n].Nick()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a Nick for the nick n, if we're tracking it.
|
// Returns a nick for the nick n, if we're tracking it.
|
||||||
func (st *stateTracker) GetNick(n string) *Nick {
|
func (st *stateTracker) GetNick(n string) *Nick {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
if nk, ok := st.nicks[n]; ok {
|
if nk, ok := st.nicks[n]; ok {
|
||||||
return nk
|
return nk.Nick()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signals to the tracker that a Nick should be tracked
|
// Signals to the tracker that a nick should be tracked
|
||||||
// under a "neu" nick rather than the old one.
|
// under a "neu" nick rather than the old one.
|
||||||
func (st *stateTracker) ReNick(old, neu string) {
|
func (st *stateTracker) ReNick(old, neu string) *Nick {
|
||||||
if nk, ok := st.nicks[old]; ok {
|
st.mu.Lock()
|
||||||
if _, ok := st.nicks[neu]; !ok {
|
defer st.mu.Unlock()
|
||||||
nk.Nick = neu
|
nk, ok := st.nicks[old]
|
||||||
|
if !ok {
|
||||||
|
logging.Warn("Tracker.ReNick(): %s not tracked.", old)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, ok := st.nicks[neu]; ok {
|
||||||
|
logging.Warn("Tracker.ReNick(): %s already exists.", neu)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nk.nick = neu
|
||||||
delete(st.nicks, old)
|
delete(st.nicks, old)
|
||||||
st.nicks[neu] = nk
|
st.nicks[neu] = nk
|
||||||
for ch, _ := range nk.chans {
|
for ch, _ := range nk.chans {
|
||||||
|
@ -92,34 +124,33 @@ func (st *stateTracker) ReNick(old, neu string) {
|
||||||
delete(ch.lookup, old)
|
delete(ch.lookup, old)
|
||||||
ch.lookup[neu] = nk
|
ch.lookup[neu] = nk
|
||||||
}
|
}
|
||||||
} else {
|
return nk.Nick()
|
||||||
logging.Warn("Tracker.ReNick(): %s already exists.", neu)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logging.Warn("Tracker.ReNick(): %s not tracked.", old)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes a Nick from being tracked.
|
// Removes a nick from being tracked.
|
||||||
func (st *stateTracker) DelNick(n string) {
|
func (st *stateTracker) DelNick(n string) *Nick {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
if nk, ok := st.nicks[n]; ok {
|
if nk, ok := st.nicks[n]; ok {
|
||||||
if nk != st.me {
|
if nk == st.me {
|
||||||
st.delNick(nk)
|
|
||||||
} else {
|
|
||||||
logging.Warn("Tracker.DelNick(): won't delete myself.")
|
logging.Warn("Tracker.DelNick(): won't delete myself.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
st.delNick(nk)
|
||||||
|
return nk.Nick()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logging.Warn("Tracker.DelNick(): %s not tracked.", n)
|
logging.Warn("Tracker.DelNick(): %s not tracked.", n)
|
||||||
}
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stateTracker) delNick(nk *Nick) {
|
func (st *stateTracker) delNick(nk *nick) {
|
||||||
|
// st.mu lock held by DelNick, DelChannel or Wipe
|
||||||
if nk == st.me {
|
if nk == st.me {
|
||||||
// Shouldn't get here => internal state tracking code is fubar.
|
// Shouldn't get here => internal state tracking code is fubar.
|
||||||
logging.Error("Tracker.DelNick(): TRYING TO DELETE ME :-(")
|
logging.Error("Tracker.DelNick(): TRYING TO DELETE ME :-(")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(st.nicks, nk.Nick)
|
delete(st.nicks, nk.nick)
|
||||||
for ch, _ := range nk.chans {
|
for ch, _ := range nk.chans {
|
||||||
nk.delChannel(ch)
|
nk.delChannel(ch)
|
||||||
ch.delNick(nk)
|
ch.delNick(nk)
|
||||||
|
@ -127,41 +158,79 @@ func (st *stateTracker) delNick(nk *Nick) {
|
||||||
// Deleting a nick from tracking shouldn't empty any channels as
|
// Deleting a nick from tracking shouldn't empty any channels as
|
||||||
// *we* should be on the channel with them to be tracking them.
|
// *we* should be on the channel with them to be tracking them.
|
||||||
logging.Error("Tracker.delNick(): deleting nick %s emptied "+
|
logging.Error("Tracker.delNick(): deleting nick %s emptied "+
|
||||||
"channel %s, this shouldn't happen!", nk.Nick, ch.Name)
|
"channel %s, this shouldn't happen!", nk.nick, ch.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets ident, host and "real" name for the nick.
|
||||||
|
func (st *stateTracker) NickInfo(n, ident, host, name string) *Nick {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
|
nk, ok := st.nicks[n]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
nk.ident = ident
|
||||||
|
nk.host = host
|
||||||
|
nk.name = name
|
||||||
|
return nk.Nick()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets user modes for the nick.
|
||||||
|
func (st *stateTracker) NickModes(n, modes string) *Nick {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
|
nk, ok := st.nicks[n]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
nk.parseModes(modes)
|
||||||
|
return nk.Nick()
|
||||||
|
}
|
||||||
|
|
||||||
// Creates a new Channel, initialises it, and stores it so it
|
// Creates a new Channel, initialises it, and stores it so it
|
||||||
// can be properly tracked for state management purposes.
|
// can be properly tracked for state management purposes.
|
||||||
func (st *stateTracker) NewChannel(c string) *Channel {
|
func (st *stateTracker) NewChannel(c string) *Channel {
|
||||||
|
if c == "" {
|
||||||
|
logging.Warn("Tracker.NewChannel(): Not tracking empty channel.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
if _, ok := st.chans[c]; ok {
|
if _, ok := st.chans[c]; ok {
|
||||||
logging.Warn("Tracker.NewChannel(): %s already tracked.", c)
|
logging.Warn("Tracker.NewChannel(): %s already tracked.", c)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
st.chans[c] = NewChannel(c)
|
st.chans[c] = newChannel(c)
|
||||||
return st.chans[c]
|
return st.chans[c].Channel()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a Channel for the channel c, if we're tracking it.
|
// Returns a Channel for the channel c, if we're tracking it.
|
||||||
func (st *stateTracker) GetChannel(c string) *Channel {
|
func (st *stateTracker) GetChannel(c string) *Channel {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
if ch, ok := st.chans[c]; ok {
|
if ch, ok := st.chans[c]; ok {
|
||||||
return ch
|
return ch.Channel()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes a Channel from being tracked.
|
// Removes a Channel from being tracked.
|
||||||
func (st *stateTracker) DelChannel(c string) {
|
func (st *stateTracker) DelChannel(c string) *Channel {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
if ch, ok := st.chans[c]; ok {
|
if ch, ok := st.chans[c]; ok {
|
||||||
st.delChannel(ch)
|
st.delChannel(ch)
|
||||||
} else {
|
return ch.Channel()
|
||||||
logging.Warn("Tracker.DelChannel(): %s not tracked.", c)
|
|
||||||
}
|
}
|
||||||
|
logging.Warn("Tracker.DelChannel(): %s not tracked.", c)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stateTracker) delChannel(ch *Channel) {
|
func (st *stateTracker) delChannel(ch *channel) {
|
||||||
delete(st.chans, ch.Name)
|
// st.mu lock held by DelChannel or Wipe
|
||||||
|
delete(st.chans, ch.name)
|
||||||
for nk, _ := range ch.nicks {
|
for nk, _ := range ch.nicks {
|
||||||
ch.delNick(nk)
|
ch.delNick(nk)
|
||||||
nk.delChannel(ch)
|
nk.delChannel(ch)
|
||||||
|
@ -172,67 +241,98 @@ func (st *stateTracker) delChannel(ch *Channel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets the topic of a channel.
|
||||||
|
func (st *stateTracker) Topic(c, topic string) *Channel {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
|
ch, ok := st.chans[c]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ch.topic = topic
|
||||||
|
return ch.Channel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets modes for a channel, including privileges like +o.
|
||||||
|
func (st *stateTracker) ChannelModes(c, modes string, args ...string) *Channel {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
|
ch, ok := st.chans[c]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ch.parseModes(modes, args...)
|
||||||
|
return ch.Channel()
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the Nick the state tracker thinks is Me.
|
// Returns the Nick the state tracker thinks is Me.
|
||||||
func (st *stateTracker) Me() *Nick {
|
func (st *stateTracker) Me() *Nick {
|
||||||
return st.me
|
return st.me.Nick()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if both the channel c and the nick n are tracked
|
// Returns true if both the channel c and the nick n are tracked
|
||||||
// and the nick is associated with the channel.
|
// and the nick is associated with the channel.
|
||||||
func (st *stateTracker) IsOn(c, n string) (*ChanPrivs, bool) {
|
func (st *stateTracker) IsOn(c, n string) (*ChanPrivs, bool) {
|
||||||
nk := st.GetNick(n)
|
st.mu.Lock()
|
||||||
ch := st.GetChannel(c)
|
defer st.mu.Unlock()
|
||||||
if nk != nil && ch != nil {
|
nk, nok := st.nicks[n]
|
||||||
return nk.IsOn(ch)
|
ch, cok := st.chans[c]
|
||||||
|
if nok && cok {
|
||||||
|
return nk.isOn(ch)
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Associates an already known nick with an already known channel.
|
// Associates an already known nick with an already known channel.
|
||||||
func (st *stateTracker) Associate(ch *Channel, nk *Nick) *ChanPrivs {
|
func (st *stateTracker) Associate(c, n string) *ChanPrivs {
|
||||||
if ch == nil || nk == nil {
|
st.mu.Lock()
|
||||||
logging.Error("Tracker.Associate(): passed nil values :-(")
|
defer st.mu.Unlock()
|
||||||
return nil
|
nk, nok := st.nicks[n]
|
||||||
} else if _ch, ok := st.chans[ch.Name]; !ok || ch != _ch {
|
ch, cok := st.chans[c]
|
||||||
|
|
||||||
|
if !cok {
|
||||||
// As we can implicitly delete both nicks and channels from being
|
// As we can implicitly delete both nicks and channels from being
|
||||||
// tracked by dissociating one from the other, we should verify that
|
// tracked by dissociating one from the other, we should verify that
|
||||||
// we're not being passed an old Nick or Channel.
|
// we're not being passed an old Nick or Channel.
|
||||||
logging.Error("Tracker.Associate(): channel %s not found in "+
|
logging.Error("Tracker.Associate(): channel %s not found in "+
|
||||||
"(or differs from) internal state.", ch.Name)
|
"internal state.", c)
|
||||||
return nil
|
return nil
|
||||||
} else if _nk, ok := st.nicks[nk.Nick]; !ok || nk != _nk {
|
} else if !nok {
|
||||||
logging.Error("Tracker.Associate(): nick %s not found in "+
|
logging.Error("Tracker.Associate(): nick %s not found in "+
|
||||||
"(or differs from) internal state.", nk.Nick)
|
"internal state.", n)
|
||||||
return nil
|
return nil
|
||||||
} else if _, ok := nk.IsOn(ch); ok {
|
} else if _, ok := nk.isOn(ch); ok {
|
||||||
logging.Warn("Tracker.Associate(): %s already on %s.",
|
logging.Warn("Tracker.Associate(): %s already on %s.",
|
||||||
nk.Nick, ch.Name)
|
nk, ch)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cp := new(ChanPrivs)
|
cp := new(ChanPrivs)
|
||||||
ch.addNick(nk, cp)
|
ch.addNick(nk, cp)
|
||||||
nk.addChannel(ch, cp)
|
nk.addChannel(ch, cp)
|
||||||
return cp
|
return cp.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dissociates an already known nick from an already known channel.
|
// Dissociates an already known nick from an already known channel.
|
||||||
// Does some tidying up to stop tracking nicks we're no longer on
|
// Does some tidying up to stop tracking nicks we're no longer on
|
||||||
// any common channels with, and channels we're no longer on.
|
// any common channels with, and channels we're no longer on.
|
||||||
func (st *stateTracker) Dissociate(ch *Channel, nk *Nick) {
|
func (st *stateTracker) Dissociate(c, n string) {
|
||||||
if ch == nil || nk == nil {
|
st.mu.Lock()
|
||||||
logging.Error("Tracker.Dissociate(): passed nil values :-(")
|
defer st.mu.Unlock()
|
||||||
} else if _ch, ok := st.chans[ch.Name]; !ok || ch != _ch {
|
nk, nok := st.nicks[n]
|
||||||
|
ch, cok := st.chans[c]
|
||||||
|
|
||||||
|
if !cok {
|
||||||
// As we can implicitly delete both nicks and channels from being
|
// As we can implicitly delete both nicks and channels from being
|
||||||
// tracked by dissociating one from the other, we should verify that
|
// tracked by dissociating one from the other, we should verify that
|
||||||
// we're not being passed an old Nick or Channel.
|
// we're not being passed an old Nick or Channel.
|
||||||
logging.Error("Tracker.Dissociate(): channel %s not found in "+
|
logging.Error("Tracker.Dissociate(): channel %s not found in "+
|
||||||
"(or differs from) internal state.", ch.Name)
|
"internal state.", c)
|
||||||
} else if _nk, ok := st.nicks[nk.Nick]; !ok || nk != _nk {
|
} else if !nok {
|
||||||
logging.Error("Tracker.Dissociate(): nick %s not found in "+
|
logging.Error("Tracker.Dissociate(): nick %s not found in "+
|
||||||
"(or differs from) internal state.", nk.Nick)
|
"internal state.", n)
|
||||||
} else if _, ok := nk.IsOn(ch); !ok {
|
} else if _, ok := nk.isOn(ch); !ok {
|
||||||
logging.Warn("Tracker.Dissociate(): %s not on %s.",
|
logging.Warn("Tracker.Dissociate(): %s not on %s.",
|
||||||
nk.Nick, ch.Name)
|
nk.nick, ch.name)
|
||||||
} else if nk == st.me {
|
} else if nk == st.me {
|
||||||
// I'm leaving the channel for some reason, so it won't be tracked.
|
// I'm leaving the channel for some reason, so it won't be tracked.
|
||||||
st.delChannel(ch)
|
st.delChannel(ch)
|
||||||
|
@ -248,6 +348,8 @@ func (st *stateTracker) Dissociate(ch *Channel, nk *Nick) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stateTracker) String() string {
|
func (st *stateTracker) String() string {
|
||||||
|
st.mu.Lock()
|
||||||
|
defer st.mu.Unlock()
|
||||||
str := "GoIRC Channels\n"
|
str := "GoIRC Channels\n"
|
||||||
str += "--------------\n\n"
|
str += "--------------\n\n"
|
||||||
for _, ch := range st.chans {
|
for _, ch := range st.chans {
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
package state
|
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,
|
||||||
|
// uncaptialised members. Items retrieved from state tracker public interface
|
||||||
|
// methods are public and only have public, capitalised members. Comparisons of
|
||||||
|
// the two are done on the basis of nick or channel name.
|
||||||
|
|
||||||
func TestSTNewTracker(t *testing.T) {
|
func TestSTNewTracker(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
@ -11,7 +20,7 @@ func TestSTNewTracker(t *testing.T) {
|
||||||
if len(st.chans) != 0 {
|
if len(st.chans) != 0 {
|
||||||
t.Errorf("Channel list of new tracker is not empty.")
|
t.Errorf("Channel list of new tracker is not empty.")
|
||||||
}
|
}
|
||||||
if nk, ok := st.nicks["mynick"]; !ok || nk.Nick != "mynick" || nk != st.me {
|
if nk, ok := st.nicks["mynick"]; !ok || nk.nick != "mynick" || nk != st.me {
|
||||||
t.Errorf("My nick not stored correctly in tracker.")
|
t.Errorf("My nick not stored correctly in tracker.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,20 +32,23 @@ func TestSTNewNick(t *testing.T) {
|
||||||
if test1 == nil || test1.Nick != "test1" {
|
if test1 == nil || test1.Nick != "test1" {
|
||||||
t.Errorf("Nick object created incorrectly by NewNick.")
|
t.Errorf("Nick object created incorrectly by NewNick.")
|
||||||
}
|
}
|
||||||
if n, ok := st.nicks["test1"]; !ok || n != test1 || len(st.nicks) != 2 {
|
if n, ok := st.nicks["test1"]; !ok || !test1.Equals(n.Nick()) || len(st.nicks) != 2 {
|
||||||
t.Errorf("Nick object stored incorrectly by NewNick.")
|
t.Errorf("Nick object stored incorrectly by NewNick.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if fail := st.NewNick("test1"); fail != nil {
|
if fail := st.NewNick("test1"); fail != nil {
|
||||||
t.Errorf("Creating duplicate nick did not produce nil return.")
|
t.Errorf("Creating duplicate nick did not produce nil return.")
|
||||||
}
|
}
|
||||||
|
if fail := st.NewNick(""); fail != nil {
|
||||||
|
t.Errorf("Creating empty nick did not produce nil return.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSTGetNick(t *testing.T) {
|
func TestSTGetNick(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
test1 := st.NewNick("test1")
|
test1 := st.NewNick("test1")
|
||||||
|
|
||||||
if n := st.GetNick("test1"); n != test1 {
|
if n := st.GetNick("test1"); !test1.Equals(n) {
|
||||||
t.Errorf("Incorrect nick returned by GetNick.")
|
t.Errorf("Incorrect nick returned by GetNick.")
|
||||||
}
|
}
|
||||||
if n := st.GetNick("test2"); n != nil {
|
if n := st.GetNick("test2"); n != nil {
|
||||||
|
@ -52,39 +64,53 @@ func TestSTReNick(t *testing.T) {
|
||||||
test1 := st.NewNick("test1")
|
test1 := st.NewNick("test1")
|
||||||
|
|
||||||
// This channel is here to ensure that its lookup map gets updated
|
// This channel is here to ensure that its lookup map gets updated
|
||||||
chan1 := st.NewChannel("#chan1")
|
st.NewChannel("#chan1")
|
||||||
st.Associate(chan1, test1)
|
st.Associate("#chan1", "test1")
|
||||||
|
|
||||||
st.ReNick("test1", "test2")
|
// We need to check out the manipulation of the internals.
|
||||||
|
n1 := st.nicks["test1"]
|
||||||
|
c1 := st.chans["#chan1"]
|
||||||
|
|
||||||
|
test2 := st.ReNick("test1", "test2")
|
||||||
|
|
||||||
if _, ok := st.nicks["test1"]; ok {
|
if _, ok := st.nicks["test1"]; ok {
|
||||||
t.Errorf("Nick test1 still exists after ReNick.")
|
t.Errorf("Nick test1 still exists after ReNick.")
|
||||||
}
|
}
|
||||||
if n, ok := st.nicks["test2"]; !ok || n != test1 {
|
if n, ok := st.nicks["test2"]; !ok || n != n1 {
|
||||||
t.Errorf("Nick test2 doesn't exist after ReNick.")
|
t.Errorf("Nick test2 doesn't exist after ReNick.")
|
||||||
}
|
}
|
||||||
if _, ok := chan1.lookup["test1"]; ok {
|
if _, ok := c1.lookup["test1"]; ok {
|
||||||
t.Errorf("Channel #chan1 still knows about test1 after ReNick.")
|
t.Errorf("Channel #chan1 still knows about test1 after ReNick.")
|
||||||
}
|
}
|
||||||
if n, ok := chan1.lookup["test2"]; !ok || n != test1 {
|
if n, ok := c1.lookup["test2"]; !ok || n != n1 {
|
||||||
t.Errorf("Channel #chan1 doesn't know about test2 after ReNick.")
|
t.Errorf("Channel #chan1 doesn't know about test2 after ReNick.")
|
||||||
}
|
}
|
||||||
if test1.Nick != "test2" {
|
if test1.Nick != "test1" {
|
||||||
t.Errorf("Nick test1 not changed correctly.")
|
t.Errorf("Nick test1 changed unexpectedly.")
|
||||||
|
}
|
||||||
|
if !test2.Equals(n1.Nick()) {
|
||||||
|
t.Errorf("Nick test2 did not change.")
|
||||||
}
|
}
|
||||||
if len(st.nicks) != 2 {
|
if len(st.nicks) != 2 {
|
||||||
t.Errorf("Nick list changed size during ReNick.")
|
t.Errorf("Nick list changed size during ReNick.")
|
||||||
}
|
}
|
||||||
|
if len(c1.lookup) != 1 {
|
||||||
|
t.Errorf("Channel lookup list changed size during ReNick.")
|
||||||
|
}
|
||||||
|
|
||||||
test2 := st.NewNick("test1")
|
st.NewNick("test1")
|
||||||
st.ReNick("test1", "test2")
|
n2 := st.nicks["test1"]
|
||||||
|
fail := st.ReNick("test1", "test2")
|
||||||
|
|
||||||
if n, ok := st.nicks["test2"]; !ok || n != test1 {
|
if n, ok := st.nicks["test2"]; !ok || n != n1 {
|
||||||
t.Errorf("Nick test2 overwritten/deleted by ReNick.")
|
t.Errorf("Nick test2 overwritten/deleted by ReNick.")
|
||||||
}
|
}
|
||||||
if n, ok := st.nicks["test1"]; !ok || n != test2 {
|
if n, ok := st.nicks["test1"]; !ok || n != n2 {
|
||||||
t.Errorf("Nick test1 overwritten/deleted by ReNick.")
|
t.Errorf("Nick test1 overwritten/deleted by ReNick.")
|
||||||
}
|
}
|
||||||
|
if fail != nil {
|
||||||
|
t.Errorf("ReNick returned Nick on failure.")
|
||||||
|
}
|
||||||
if len(st.nicks) != 3 {
|
if len(st.nicks) != 3 {
|
||||||
t.Errorf("Nick list changed size during ReNick.")
|
t.Errorf("Nick list changed size during ReNick.")
|
||||||
}
|
}
|
||||||
|
@ -93,8 +119,8 @@ func TestSTReNick(t *testing.T) {
|
||||||
func TestSTDelNick(t *testing.T) {
|
func TestSTDelNick(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
|
||||||
st.NewNick("test1")
|
add := st.NewNick("test1")
|
||||||
st.DelNick("test1")
|
del := st.DelNick("test1")
|
||||||
|
|
||||||
if _, ok := st.nicks["test1"]; ok {
|
if _, ok := st.nicks["test1"]; ok {
|
||||||
t.Errorf("Nick test1 still exists after DelNick.")
|
t.Errorf("Nick test1 still exists after DelNick.")
|
||||||
|
@ -102,55 +128,106 @@ func TestSTDelNick(t *testing.T) {
|
||||||
if len(st.nicks) != 1 {
|
if len(st.nicks) != 1 {
|
||||||
t.Errorf("Nick list still contains nicks after DelNick.")
|
t.Errorf("Nick list still contains nicks after DelNick.")
|
||||||
}
|
}
|
||||||
|
if !add.Equals(del) {
|
||||||
|
t.Errorf("DelNick returned different nick.")
|
||||||
|
}
|
||||||
|
|
||||||
// Deleting unknown nick shouldn't work, but let's make sure we have a
|
// Deleting unknown nick shouldn't work, but let's make sure we have a
|
||||||
// known nick first to catch any possible accidental removals.
|
// known nick first to catch any possible accidental removals.
|
||||||
nick1 := st.NewNick("test1")
|
st.NewNick("test1")
|
||||||
st.DelNick("test2")
|
fail := st.DelNick("test2")
|
||||||
if len(st.nicks) != 2 {
|
if fail != nil || len(st.nicks) != 2 {
|
||||||
t.Errorf("Deleting unknown nick had unexpected side-effects.")
|
t.Errorf("Deleting unknown nick had unexpected side-effects.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deleting my nick shouldn't work
|
// Deleting my nick shouldn't work
|
||||||
st.DelNick("mynick")
|
fail = st.DelNick("mynick")
|
||||||
if len(st.nicks) != 2 {
|
if fail != nil || len(st.nicks) != 2 {
|
||||||
t.Errorf("Deleting myself had unexpected side-effects.")
|
t.Errorf("Deleting myself had unexpected side-effects.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that deletion correctly dissociates nick from channels.
|
// Test that deletion correctly dissociates nick from channels.
|
||||||
// NOTE: the two error states in delNick (as opposed to DelNick)
|
// NOTE: the two error states in delNick (as opposed to DelNick)
|
||||||
// are not tested for here, as they will only arise from programming
|
// are not tested for here, as they will only arise from programming
|
||||||
// errors in other methods. The mock logger should catch these.
|
// errors in other methods.
|
||||||
|
|
||||||
// Create a new channel for testing purposes.
|
// Create a new channel for testing purposes.
|
||||||
chan1 := st.NewChannel("#test1")
|
st.NewChannel("#test1")
|
||||||
|
|
||||||
// Associate both "my" nick and test1 with the channel
|
// Associate both "my" nick and test1 with the channel
|
||||||
st.Associate(chan1, st.me)
|
st.Associate("#test1", "mynick")
|
||||||
st.Associate(chan1, nick1)
|
st.Associate("#test1", "test1")
|
||||||
|
|
||||||
|
// We need to check out the manipulation of the internals.
|
||||||
|
n1 := st.nicks["test1"]
|
||||||
|
c1 := st.chans["#test1"]
|
||||||
|
|
||||||
// Test we have the expected starting state (at least vaguely)
|
// Test we have the expected starting state (at least vaguely)
|
||||||
if len(chan1.nicks) != 2 || len(st.nicks) != 2 ||
|
if len(c1.nicks) != 2 || len(st.nicks) != 2 ||
|
||||||
len(st.me.chans) != 1 || len(nick1.chans) != 1 || len(st.chans) != 1 {
|
len(st.me.chans) != 1 || len(n1.chans) != 1 || len(st.chans) != 1 {
|
||||||
t.Errorf("Bad initial state for test DelNick() channel dissociation.")
|
t.Errorf("Bad initial state for test DelNick() channel dissociation.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actual deletion tested above...
|
||||||
st.DelNick("test1")
|
st.DelNick("test1")
|
||||||
|
|
||||||
// Actual deletion tested above...
|
if len(c1.nicks) != 1 || len(st.nicks) != 1 ||
|
||||||
if len(chan1.nicks) != 1 || len(st.chans) != 1 ||
|
len(st.me.chans) != 1 || len(n1.chans) != 0 || len(st.chans) != 1 {
|
||||||
len(st.me.chans) != 1 || len(nick1.chans) != 0 || len(st.chans) != 1 {
|
|
||||||
t.Errorf("Deleting nick didn't dissociate correctly from channels.")
|
t.Errorf("Deleting nick didn't dissociate correctly from channels.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := chan1.nicks[nick1]; ok {
|
if _, ok := c1.nicks[n1]; ok {
|
||||||
t.Errorf("Nick not removed from channel's nick map.")
|
t.Errorf("Nick not removed from channel's nick map.")
|
||||||
}
|
}
|
||||||
if _, ok := chan1.lookup["test1"]; ok {
|
if _, ok := c1.lookup["test1"]; ok {
|
||||||
t.Errorf("Nick not removed from channel's lookup map.")
|
t.Errorf("Nick not removed from channel's lookup map.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSTNickInfo(t *testing.T) {
|
||||||
|
st := NewTracker("mynick")
|
||||||
|
test1 := st.NewNick("test1")
|
||||||
|
test2 := st.NickInfo("test1", "foo", "bar", "baz")
|
||||||
|
test3 := st.GetNick("test1")
|
||||||
|
|
||||||
|
if test1.Equals(test2) {
|
||||||
|
t.Errorf("NickInfo did not return modified nick.")
|
||||||
|
}
|
||||||
|
if !test3.Equals(test2) {
|
||||||
|
t.Errorf("Getting nick after NickInfo returned different nick.")
|
||||||
|
}
|
||||||
|
test1.Ident, test1.Host, test1.Name = "foo", "bar", "baz"
|
||||||
|
if !test1.Equals(test2) {
|
||||||
|
t.Errorf("NickInfo did not set nick info correctly.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fail := st.NickInfo("test2", "foo", "bar", "baz"); fail != nil {
|
||||||
|
t.Errorf("NickInfo for nonexistent nick did not return nil.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSTNickModes(t *testing.T) {
|
||||||
|
st := NewTracker("mynick")
|
||||||
|
test1 := st.NewNick("test1")
|
||||||
|
test2 := st.NickModes("test1", "+iB")
|
||||||
|
test3 := st.GetNick("test1")
|
||||||
|
|
||||||
|
if test1.Equals(test2) {
|
||||||
|
t.Errorf("NickModes did not return modified nick.")
|
||||||
|
}
|
||||||
|
if !test3.Equals(test2) {
|
||||||
|
t.Errorf("Getting nick after NickModes returned different nick.")
|
||||||
|
}
|
||||||
|
test1.Modes.Invisible, test1.Modes.Bot = true, true
|
||||||
|
if !test1.Equals(test2) {
|
||||||
|
t.Errorf("NickModes did not set nick modes correctly.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fail := st.NickModes("test2", "whatevs"); fail != nil {
|
||||||
|
t.Errorf("NickModes for nonexistent nick did not return nil.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSTNewChannel(t *testing.T) {
|
func TestSTNewChannel(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
|
||||||
|
@ -163,13 +240,16 @@ func TestSTNewChannel(t *testing.T) {
|
||||||
if test1 == nil || test1.Name != "#test1" {
|
if test1 == nil || test1.Name != "#test1" {
|
||||||
t.Errorf("Channel object created incorrectly by NewChannel.")
|
t.Errorf("Channel object created incorrectly by NewChannel.")
|
||||||
}
|
}
|
||||||
if c, ok := st.chans["#test1"]; !ok || c != test1 || len(st.chans) != 1 {
|
if c, ok := st.chans["#test1"]; !ok || !test1.Equals(c.Channel()) || len(st.chans) != 1 {
|
||||||
t.Errorf("Channel object stored incorrectly by NewChannel.")
|
t.Errorf("Channel object stored incorrectly by NewChannel.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if fail := st.NewChannel("#test1"); fail != nil {
|
if fail := st.NewChannel("#test1"); fail != nil {
|
||||||
t.Errorf("Creating duplicate chan did not produce nil return.")
|
t.Errorf("Creating duplicate chan did not produce nil return.")
|
||||||
}
|
}
|
||||||
|
if fail := st.NewChannel(""); fail != nil {
|
||||||
|
t.Errorf("Creating empty chan did not produce nil return.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSTGetChannel(t *testing.T) {
|
func TestSTGetChannel(t *testing.T) {
|
||||||
|
@ -177,7 +257,7 @@ func TestSTGetChannel(t *testing.T) {
|
||||||
|
|
||||||
test1 := st.NewChannel("#test1")
|
test1 := st.NewChannel("#test1")
|
||||||
|
|
||||||
if c := st.GetChannel("#test1"); c != test1 {
|
if c := st.GetChannel("#test1"); !test1.Equals(c) {
|
||||||
t.Errorf("Incorrect Channel returned by GetChannel.")
|
t.Errorf("Incorrect Channel returned by GetChannel.")
|
||||||
}
|
}
|
||||||
if c := st.GetChannel("#test2"); c != nil {
|
if c := st.GetChannel("#test2"); c != nil {
|
||||||
|
@ -191,8 +271,8 @@ func TestSTGetChannel(t *testing.T) {
|
||||||
func TestSTDelChannel(t *testing.T) {
|
func TestSTDelChannel(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
|
||||||
st.NewChannel("#test1")
|
add := st.NewChannel("#test1")
|
||||||
st.DelChannel("#test1")
|
del := st.DelChannel("#test1")
|
||||||
|
|
||||||
if _, ok := st.chans["#test1"]; ok {
|
if _, ok := st.chans["#test1"]; ok {
|
||||||
t.Errorf("Channel test1 still exists after DelChannel.")
|
t.Errorf("Channel test1 still exists after DelChannel.")
|
||||||
|
@ -200,30 +280,38 @@ func TestSTDelChannel(t *testing.T) {
|
||||||
if len(st.chans) != 0 {
|
if len(st.chans) != 0 {
|
||||||
t.Errorf("Channel list still contains chans after DelChannel.")
|
t.Errorf("Channel list still contains chans after DelChannel.")
|
||||||
}
|
}
|
||||||
|
if !add.Equals(del) {
|
||||||
|
t.Errorf("DelChannel returned different channel.")
|
||||||
|
}
|
||||||
|
|
||||||
// Deleting unknown nick shouldn't work, but let's make sure we have a
|
// Deleting unknown channel shouldn't work, but let's make sure we have a
|
||||||
// known nick first to catch any possible accidental removals.
|
// known channel first to catch any possible accidental removals.
|
||||||
chan1 := st.NewChannel("#test1")
|
st.NewChannel("#test1")
|
||||||
st.DelChannel("#test2")
|
fail := st.DelChannel("#test2")
|
||||||
if len(st.chans) != 1 {
|
if fail != nil || len(st.chans) != 1 {
|
||||||
t.Errorf("DelChannel had unexpected side-effects.")
|
t.Errorf("DelChannel had unexpected side-effects.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that deletion correctly dissociates channel from tracked nicks.
|
// Test that deletion correctly dissociates channel from tracked nicks.
|
||||||
// In order to test this thoroughly we need two channels (so that delNick()
|
// In order to test this thoroughly we need two channels (so that delNick()
|
||||||
// is not called internally in delChannel() when len(nick1.chans) == 0.
|
// is not called internally in delChannel() when len(nick1.chans) == 0.
|
||||||
chan2 := st.NewChannel("#test2")
|
st.NewChannel("#test2")
|
||||||
nick1 := st.NewNick("test1")
|
st.NewNick("test1")
|
||||||
|
|
||||||
// Associate both "my" nick and test1 with the channels
|
// Associate both "my" nick and test1 with the channels
|
||||||
st.Associate(chan1, st.me)
|
st.Associate("#test1", "mynick")
|
||||||
st.Associate(chan1, nick1)
|
st.Associate("#test1", "test1")
|
||||||
st.Associate(chan2, st.me)
|
st.Associate("#test2", "mynick")
|
||||||
st.Associate(chan2, nick1)
|
st.Associate("#test2", "test1")
|
||||||
|
|
||||||
|
// We need to check out the manipulation of the internals.
|
||||||
|
n1 := st.nicks["test1"]
|
||||||
|
c1 := st.chans["#test1"]
|
||||||
|
c2 := st.chans["#test2"]
|
||||||
|
|
||||||
// Test we have the expected starting state (at least vaguely)
|
// Test we have the expected starting state (at least vaguely)
|
||||||
if len(chan1.nicks) != 2 || len(chan2.nicks) != 2 || len(st.nicks) != 2 ||
|
if len(c1.nicks) != 2 || len(c2.nicks) != 2 || len(st.nicks) != 2 ||
|
||||||
len(st.me.chans) != 2 || len(nick1.chans) != 2 || len(st.chans) != 2 {
|
len(st.me.chans) != 2 || len(n1.chans) != 2 || len(st.chans) != 2 {
|
||||||
t.Errorf("Bad initial state for test DelChannel() nick dissociation.")
|
t.Errorf("Bad initial state for test DelChannel() nick dissociation.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,14 +319,14 @@ func TestSTDelChannel(t *testing.T) {
|
||||||
|
|
||||||
// Test intermediate state. We're still on #test2 with test1, so test1
|
// Test intermediate state. We're still on #test2 with test1, so test1
|
||||||
// shouldn't be deleted from state tracking itself just yet.
|
// shouldn't be deleted from state tracking itself just yet.
|
||||||
if len(chan1.nicks) != 0 || len(chan2.nicks) != 2 || len(st.nicks) != 2 ||
|
if len(c1.nicks) != 0 || len(c2.nicks) != 2 || len(st.nicks) != 2 ||
|
||||||
len(st.me.chans) != 1 || len(nick1.chans) != 1 || len(st.chans) != 1 {
|
len(st.me.chans) != 1 || len(n1.chans) != 1 || len(st.chans) != 1 {
|
||||||
t.Errorf("Deleting channel didn't dissociate correctly from nicks.")
|
t.Errorf("Deleting channel didn't dissociate correctly from nicks.")
|
||||||
}
|
}
|
||||||
if _, ok := nick1.chans[chan1]; ok {
|
if _, ok := n1.chans[c1]; ok {
|
||||||
t.Errorf("Channel not removed from nick's chans map.")
|
t.Errorf("Channel not removed from nick's chans map.")
|
||||||
}
|
}
|
||||||
if _, ok := nick1.lookup["#test1"]; ok {
|
if _, ok := n1.lookup["#test1"]; ok {
|
||||||
t.Errorf("Channel not removed from nick's lookup map.")
|
t.Errorf("Channel not removed from nick's lookup map.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,8 +334,8 @@ func TestSTDelChannel(t *testing.T) {
|
||||||
|
|
||||||
// Test final state. Deleting #test2 means that we're no longer on any
|
// Test final state. Deleting #test2 means that we're no longer on any
|
||||||
// common channels with test1, and thus it should be removed from tracking.
|
// common channels with test1, and thus it should be removed from tracking.
|
||||||
if len(chan1.nicks) != 0 || len(chan2.nicks) != 0 || len(st.nicks) != 1 ||
|
if len(c1.nicks) != 0 || len(c2.nicks) != 0 || len(st.nicks) != 1 ||
|
||||||
len(st.me.chans) != 0 || len(nick1.chans) != 0 || len(st.chans) != 0 {
|
len(st.me.chans) != 0 || len(n1.chans) != 0 || len(st.chans) != 0 {
|
||||||
t.Errorf("Deleting last channel didn't dissociate correctly from nicks.")
|
t.Errorf("Deleting last channel didn't dissociate correctly from nicks.")
|
||||||
}
|
}
|
||||||
if _, ok := st.nicks["test1"]; ok {
|
if _, ok := st.nicks["test1"]; ok {
|
||||||
|
@ -258,17 +346,61 @@ func TestSTDelChannel(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSTTopic(t *testing.T) {
|
||||||
|
st := NewTracker("mynick")
|
||||||
|
test1 := st.NewChannel("#test1")
|
||||||
|
test2 := st.Topic("#test1", "foo bar")
|
||||||
|
test3 := st.GetChannel("#test1")
|
||||||
|
|
||||||
|
if test1.Equals(test2) {
|
||||||
|
t.Errorf("Topic did not return modified channel.")
|
||||||
|
}
|
||||||
|
if !test3.Equals(test2) {
|
||||||
|
t.Errorf("Getting channel after Topic returned different channel.")
|
||||||
|
}
|
||||||
|
test1.Topic = "foo bar"
|
||||||
|
if !test1.Equals(test2) {
|
||||||
|
t.Errorf("Topic did not set channel topic correctly.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fail := st.Topic("#test2", "foo baz"); fail != nil {
|
||||||
|
t.Errorf("Topic for nonexistent channel did not return nil.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSTChannelModes(t *testing.T) {
|
||||||
|
st := NewTracker("mynick")
|
||||||
|
test1 := st.NewChannel("#test1")
|
||||||
|
test2 := st.ChannelModes("#test1", "+sk", "foo")
|
||||||
|
test3 := st.GetChannel("#test1")
|
||||||
|
|
||||||
|
if test1.Equals(test2) {
|
||||||
|
t.Errorf("ChannelModes did not return modified channel.")
|
||||||
|
}
|
||||||
|
if !test3.Equals(test2) {
|
||||||
|
t.Errorf("Getting channel after ChannelModes returned different channel.")
|
||||||
|
}
|
||||||
|
test1.Modes.Secret, test1.Modes.Key = true, "foo"
|
||||||
|
if !test1.Equals(test2) {
|
||||||
|
t.Errorf("ChannelModes did not set channel modes correctly.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fail := st.ChannelModes("test2", "whatevs"); fail != nil {
|
||||||
|
t.Errorf("ChannelModes for nonexistent channel did not return nil.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSTIsOn(t *testing.T) {
|
func TestSTIsOn(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
|
||||||
nick1 := st.NewNick("test1")
|
st.NewNick("test1")
|
||||||
chan1 := st.NewChannel("#test1")
|
st.NewChannel("#test1")
|
||||||
|
|
||||||
if priv, ok := st.IsOn("#test1", "test1"); ok || priv != nil {
|
if priv, ok := st.IsOn("#test1", "test1"); ok || priv != nil {
|
||||||
t.Errorf("test1 is not on #test1 (yet)")
|
t.Errorf("test1 is not on #test1 (yet)")
|
||||||
}
|
}
|
||||||
cp := st.Associate(chan1, nick1)
|
st.Associate("#test1", "test1")
|
||||||
if priv, ok := st.IsOn("#test1", "test1"); !ok || priv != cp {
|
if priv, ok := st.IsOn("#test1", "test1"); !ok || priv == nil {
|
||||||
t.Errorf("test1 is on #test1 (now)")
|
t.Errorf("test1 is on #test1 (now)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,117 +408,135 @@ func TestSTIsOn(t *testing.T) {
|
||||||
func TestSTAssociate(t *testing.T) {
|
func TestSTAssociate(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
|
||||||
nick1 := st.NewNick("test1")
|
st.NewNick("test1")
|
||||||
chan1 := st.NewChannel("#test1")
|
st.NewChannel("#test1")
|
||||||
|
|
||||||
cp := st.Associate(chan1, nick1)
|
// We need to check out the manipulation of the internals.
|
||||||
if priv, ok := nick1.chans[chan1]; !ok || cp != priv {
|
n1 := st.nicks["test1"]
|
||||||
|
c1 := st.chans["#test1"]
|
||||||
|
|
||||||
|
st.Associate("#test1", "test1")
|
||||||
|
npriv, nok := n1.chans[c1]
|
||||||
|
cpriv, cok := c1.nicks[n1]
|
||||||
|
if !nok || !cok || npriv != cpriv {
|
||||||
t.Errorf("#test1 was not associated with test1.")
|
t.Errorf("#test1 was not associated with test1.")
|
||||||
}
|
}
|
||||||
if priv, ok := chan1.nicks[nick1]; !ok || cp != priv {
|
|
||||||
t.Errorf("test1 was not associated with #test1.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test error cases
|
// Test error cases
|
||||||
if st.Associate(nil, nick1) != nil {
|
if st.Associate("", "test1") != nil {
|
||||||
t.Errorf("Associating nil *Channel did not return nil.")
|
t.Errorf("Associating unknown channel did not return nil.")
|
||||||
}
|
}
|
||||||
if st.Associate(chan1, nil) != nil {
|
if st.Associate("#test1", "") != nil {
|
||||||
t.Errorf("Associating nil *Nick did not return nil.")
|
t.Errorf("Associating unknown nick did not return nil.")
|
||||||
}
|
}
|
||||||
if st.Associate(chan1, nick1) != nil {
|
if st.Associate("#test1", "test1") != nil {
|
||||||
t.Errorf("Associating already-associated things did not return nil.")
|
t.Errorf("Associating already-associated things did not return nil.")
|
||||||
}
|
}
|
||||||
if st.Associate(chan1, NewNick("test2")) != nil {
|
|
||||||
t.Errorf("Associating unknown *Nick did not return nil.")
|
|
||||||
}
|
|
||||||
if st.Associate(NewChannel("#test2"), nick1) != nil {
|
|
||||||
t.Errorf("Associating unknown *Channel did not return nil.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSTDissociate(t *testing.T) {
|
func TestSTDissociate(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
|
||||||
nick1 := st.NewNick("test1")
|
st.NewNick("test1")
|
||||||
chan1 := st.NewChannel("#test1")
|
st.NewChannel("#test1")
|
||||||
chan2 := st.NewChannel("#test2")
|
st.NewChannel("#test2")
|
||||||
|
|
||||||
// Associate both "my" nick and test1 with the channels
|
// Associate both "my" nick and test1 with the channels
|
||||||
st.Associate(chan1, st.me)
|
st.Associate("#test1", "mynick")
|
||||||
st.Associate(chan1, nick1)
|
st.Associate("#test1", "test1")
|
||||||
st.Associate(chan2, st.me)
|
st.Associate("#test2", "mynick")
|
||||||
st.Associate(chan2, nick1)
|
st.Associate("#test2", "test1")
|
||||||
|
|
||||||
|
// We need to check out the manipulation of the internals.
|
||||||
|
n1 := st.nicks["test1"]
|
||||||
|
c1 := st.chans["#test1"]
|
||||||
|
c2 := st.chans["#test2"]
|
||||||
|
|
||||||
// Check the initial state looks mostly like we expect it to.
|
// Check the initial state looks mostly like we expect it to.
|
||||||
if len(chan1.nicks) != 2 || len(chan2.nicks) != 2 || len(st.nicks) != 2 ||
|
if len(c1.nicks) != 2 || len(c2.nicks) != 2 || len(st.nicks) != 2 ||
|
||||||
len(st.me.chans) != 2 || len(nick1.chans) != 2 || len(st.chans) != 2 {
|
len(st.me.chans) != 2 || len(n1.chans) != 2 || len(st.chans) != 2 {
|
||||||
t.Errorf("Initial state for dissociation tests looks odd.")
|
t.Errorf("Initial state for dissociation tests looks odd.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, test the case of me leaving #test2
|
// First, test the case of me leaving #test2
|
||||||
st.Dissociate(chan2, st.me)
|
st.Dissociate("#test2", "mynick")
|
||||||
|
|
||||||
// This should have resulted in the complete deletion of the channel.
|
// This should have resulted in the complete deletion of the channel.
|
||||||
if len(chan1.nicks) != 2 || len(chan2.nicks) != 0 || len(st.nicks) != 2 ||
|
if len(c1.nicks) != 2 || len(c2.nicks) != 0 || len(st.nicks) != 2 ||
|
||||||
len(st.me.chans) != 1 || len(nick1.chans) != 1 || len(st.chans) != 1 {
|
len(st.me.chans) != 1 || len(n1.chans) != 1 || len(st.chans) != 1 {
|
||||||
t.Errorf("Dissociating myself from channel didn't delete it correctly.")
|
t.Errorf("Dissociating myself from channel didn't delete it correctly.")
|
||||||
}
|
}
|
||||||
|
if st.GetChannel("#test2") != nil {
|
||||||
|
t.Errorf("Able to get channel after dissociating myself.")
|
||||||
|
}
|
||||||
|
|
||||||
// Reassociating myself and test1 to #test2 shouldn't cause any errors.
|
// Reassociating myself and test1 to #test2 shouldn't cause any errors.
|
||||||
chan2 = st.NewChannel("#test2")
|
st.NewChannel("#test2")
|
||||||
st.Associate(chan2, st.me)
|
st.Associate("#test2", "mynick")
|
||||||
st.Associate(chan2, nick1)
|
st.Associate("#test2", "test1")
|
||||||
|
|
||||||
|
// c2 is out of date with the complete deletion of the channel
|
||||||
|
c2 = st.chans["#test2"]
|
||||||
|
|
||||||
// Check state once moar.
|
// Check state once moar.
|
||||||
if len(chan1.nicks) != 2 || len(chan2.nicks) != 2 || len(st.nicks) != 2 ||
|
if len(c1.nicks) != 2 || len(c2.nicks) != 2 || len(st.nicks) != 2 ||
|
||||||
len(st.me.chans) != 2 || len(nick1.chans) != 2 || len(st.chans) != 2 {
|
len(st.me.chans) != 2 || len(n1.chans) != 2 || len(st.chans) != 2 {
|
||||||
t.Errorf("Reassociating to channel has produced unexpected state.")
|
t.Errorf("Reassociating to channel has produced unexpected state.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, lets dissociate test1 from #test1 then #test2.
|
// Now, lets dissociate test1 from #test1 then #test2.
|
||||||
// This first one should only result in a change in associations.
|
// This first one should only result in a change in associations.
|
||||||
st.Dissociate(chan1, nick1)
|
st.Dissociate("#test1", "test1")
|
||||||
|
|
||||||
if len(chan1.nicks) != 1 || len(chan2.nicks) != 2 || len(st.nicks) != 2 ||
|
if len(c1.nicks) != 1 || len(c2.nicks) != 2 || len(st.nicks) != 2 ||
|
||||||
len(st.me.chans) != 2 || len(nick1.chans) != 1 || len(st.chans) != 2 {
|
len(st.me.chans) != 2 || len(n1.chans) != 1 || len(st.chans) != 2 {
|
||||||
t.Errorf("Dissociating a nick from one channel went wrong.")
|
t.Errorf("Dissociating a nick from one channel went wrong.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This second one should also delete test1
|
// This second one should also delete test1
|
||||||
// as it's no longer on any common channels with us
|
// as it's no longer on any common channels with us
|
||||||
st.Dissociate(chan2, nick1)
|
st.Dissociate("#test2", "test1")
|
||||||
|
|
||||||
if len(chan1.nicks) != 1 || len(chan2.nicks) != 1 || len(st.nicks) != 1 ||
|
if len(c1.nicks) != 1 || len(c2.nicks) != 1 || len(st.nicks) != 1 ||
|
||||||
len(st.me.chans) != 2 || len(nick1.chans) != 0 || len(st.chans) != 2 {
|
len(st.me.chans) != 2 || len(n1.chans) != 0 || len(st.chans) != 2 {
|
||||||
t.Errorf("Dissociating a nick from it's last channel went wrong.")
|
t.Errorf("Dissociating a nick from it's last channel went wrong.")
|
||||||
}
|
}
|
||||||
|
if st.GetNick("test1") != nil {
|
||||||
|
t.Errorf("Able to get nick after dissociating from all channels.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSTWipe(t *testing.T) {
|
func TestSTWipe(t *testing.T) {
|
||||||
st := NewTracker("mynick")
|
st := NewTracker("mynick")
|
||||||
|
|
||||||
nick1 := st.NewNick("test1")
|
st.NewNick("test1")
|
||||||
nick2 := st.NewNick("test2")
|
st.NewNick("test2")
|
||||||
nick3 := st.NewNick("test3")
|
st.NewNick("test3")
|
||||||
|
st.NewChannel("#test1")
|
||||||
chan1 := st.NewChannel("#test1")
|
st.NewChannel("#test2")
|
||||||
chan2 := st.NewChannel("#test2")
|
st.NewChannel("#test3")
|
||||||
chan3 := st.NewChannel("#test3")
|
|
||||||
|
|
||||||
// Some associations
|
// Some associations
|
||||||
st.Associate(chan1, st.me)
|
st.Associate("#test1", "mynick")
|
||||||
st.Associate(chan2, st.me)
|
st.Associate("#test2", "mynick")
|
||||||
st.Associate(chan3, st.me)
|
st.Associate("#test3", "mynick")
|
||||||
|
|
||||||
st.Associate(chan1, nick1)
|
st.Associate("#test1", "test1")
|
||||||
st.Associate(chan2, nick2)
|
st.Associate("#test2", "test2")
|
||||||
st.Associate(chan3, nick3)
|
st.Associate("#test3", "test3")
|
||||||
|
|
||||||
st.Associate(chan1, nick2)
|
st.Associate("#test1", "test2")
|
||||||
st.Associate(chan2, nick3)
|
st.Associate("#test2", "test3")
|
||||||
|
|
||||||
st.Associate(chan1, nick3)
|
st.Associate("#test1", "test3")
|
||||||
|
|
||||||
|
// We need to check out the manipulation of the internals.
|
||||||
|
nick1 := st.nicks["test1"]
|
||||||
|
nick2 := st.nicks["test2"]
|
||||||
|
nick3 := st.nicks["test3"]
|
||||||
|
chan1 := st.chans["#test1"]
|
||||||
|
chan2 := st.chans["#test2"]
|
||||||
|
chan3 := st.chans["#test3"]
|
||||||
|
|
||||||
// Check the state we have at this point is what we would expect.
|
// Check the state we have at this point is what we would expect.
|
||||||
if len(st.nicks) != 4 || len(st.chans) != 3 || len(st.me.chans) != 3 {
|
if len(st.nicks) != 4 || len(st.chans) != 3 || len(st.me.chans) != 3 {
|
||||||
|
@ -413,3 +563,56 @@ func TestSTWipe(t *testing.T) {
|
||||||
t.Errorf("Nick chan lists wrong length after wipe.")
|
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.Name, "mynick")
|
||||||
|
wg.Done()
|
||||||
|
}(string(i))
|
||||||
|
go func(s string) {
|
||||||
|
n := st.GetNick("nick-" + s)
|
||||||
|
c := st.GetChannel("#chan-" + s)
|
||||||
|
st.Associate(c.Name, n.Nick)
|
||||||
|
wg.Done()
|
||||||
|
}(string(i))
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
wg = sync.WaitGroup{}
|
||||||
|
race := func(ns, cs string) {
|
||||||
|
wg.Add(5)
|
||||||
|
go func() {
|
||||||
|
st.Associate("#chan-"+cs, "nick-"+ns)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
st.GetNick("nick-"+ns)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
st.GetChannel("#chan-"+cs)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
st.Dissociate("#chan-"+cs, "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()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue