From 354da79bb2edaa1af7d909d2774e7d67eb4e198c Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 23 Jan 2018 18:17:51 +0100 Subject: Add vendor --- vendor/github.com/fluffle/goirc/state/channel.go | 350 +++++++++++++ .../github.com/fluffle/goirc/state/channel_test.go | 176 +++++++ .../github.com/fluffle/goirc/state/mock_tracker.go | 201 ++++++++ vendor/github.com/fluffle/goirc/state/nick.go | 200 ++++++++ vendor/github.com/fluffle/goirc/state/nick_test.go | 88 ++++ vendor/github.com/fluffle/goirc/state/tracker.go | 369 ++++++++++++++ .../github.com/fluffle/goirc/state/tracker_test.go | 564 +++++++++++++++++++++ 7 files changed, 1948 insertions(+) create mode 100644 vendor/github.com/fluffle/goirc/state/channel.go create mode 100644 vendor/github.com/fluffle/goirc/state/channel_test.go create mode 100644 vendor/github.com/fluffle/goirc/state/mock_tracker.go create mode 100644 vendor/github.com/fluffle/goirc/state/nick.go create mode 100644 vendor/github.com/fluffle/goirc/state/nick_test.go create mode 100644 vendor/github.com/fluffle/goirc/state/tracker.go create mode 100644 vendor/github.com/fluffle/goirc/state/tracker_test.go (limited to 'vendor/github.com/fluffle/goirc/state') diff --git a/vendor/github.com/fluffle/goirc/state/channel.go b/vendor/github.com/fluffle/goirc/state/channel.go new file mode 100644 index 0000000..35379d2 --- /dev/null +++ b/vendor/github.com/fluffle/goirc/state/channel.go @@ -0,0 +1,350 @@ +package state + +import ( + "github.com/fluffle/goirc/logging" + + "reflect" + "strconv" +) + +// A Channel is returned from the state tracker and contains +// a copy of the channel state at a particular time. +type Channel struct { + Name, Topic string + Modes *ChanMode + Nicks map[string]*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 +// (the ones we care about, at least). +// http://www.unrealircd.com/files/docs/unreal32docs.html#userchannelmodes +type ChanMode struct { + // MODE +p, +s, +t, +n, +m + Private, Secret, ProtectedTopic, NoExternalMsg, Moderated bool + + // MODE +i, +O, +z + InviteOnly, OperOnly, SSLOnly bool + + // MODE +r, +Z + Registered, AllSSL bool + + // MODE +k + Key string + + // MODE +l + Limit int +} + +// A struct representing the modes a Nick can have on a Channel +type ChanPrivs struct { + // MODE +q, +a, +o, +h, +v + Owner, Admin, Op, HalfOp, Voice bool +} + +// Map ChanMode fields to IRC mode characters +var StringToChanMode = map[string]string{} +var ChanModeToString = map[string]string{ + "Private": "p", + "Secret": "s", + "ProtectedTopic": "t", + "NoExternalMsg": "n", + "Moderated": "m", + "InviteOnly": "i", + "OperOnly": "O", + "SSLOnly": "z", + "Registered": "r", + "AllSSL": "Z", + "Key": "k", + "Limit": "l", +} + +// Map *irc.ChanPrivs fields to IRC mode characters +var StringToChanPriv = map[string]string{} +var ChanPrivToString = map[string]string{ + "Owner": "q", + "Admin": "a", + "Op": "o", + "HalfOp": "h", + "Voice": "v", +} + +// Map *irc.ChanPrivs fields to the symbols used to represent these modes +// in NAMES and WHOIS responses +var ModeCharToChanPriv = map[byte]string{} +var ChanPrivToModeChar = map[string]byte{ + "Owner": '~', + "Admin": '&', + "Op": '@', + "HalfOp": '%', + "Voice": '+', +} + +// Init function to fill in reverse mappings for *toString constants. +func init() { + for k, v := range ChanModeToString { + StringToChanMode[v] = k + } + for k, v := range ChanPrivToString { + StringToChanPriv[v] = k + } + for k, v := range ChanPrivToModeChar { + ModeCharToChanPriv[v] = k + } +} + +/******************************************************************************\ + * Channel methods for state management +\******************************************************************************/ + +func newChannel(name string) *channel { + return &channel{ + name: name, + modes: new(ChanMode), + nicks: make(map[*nick]*ChanPrivs), + lookup: make(map[string]*nick), + } +} + +// Returns a copy of the internal tracker channel state at this time. +// Relies on tracker-level locking for concurrent access. +func (ch *channel) Channel() *Channel { + 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) isOn(nk *nick) (*ChanPrivs, bool) { + cp, ok := ch.nicks[nk] + return cp.Copy(), ok +} + +// Associates a Nick with a Channel +func (ch *channel) addNick(nk *nick, cp *ChanPrivs) { + if _, ok := ch.nicks[nk]; !ok { + ch.nicks[nk] = cp + ch.lookup[nk.nick] = nk + } else { + logging.Warn("Channel.addNick(): %s already on %s.", nk.nick, ch.name) + } +} + +// Disassociates a Nick from a Channel. +func (ch *channel) delNick(nk *nick) { + if _, ok := ch.nicks[nk]; ok { + delete(ch.nicks, nk) + delete(ch.lookup, nk.nick) + } else { + logging.Warn("Channel.delNick(): %s not on %s.", nk.nick, ch.name) + } +} + +// Parses mode strings for a channel. +func (ch *channel) parseModes(modes string, modeargs ...string) { + var modeop bool // true => add mode, false => remove mode + var modestr string + for i := 0; i < len(modes); i++ { + switch m := modes[i]; m { + case '+': + modeop = true + modestr = string(m) + case '-': + modeop = false + modestr = string(m) + case 'i': + ch.modes.InviteOnly = modeop + case 'm': + ch.modes.Moderated = modeop + case 'n': + ch.modes.NoExternalMsg = modeop + case 'p': + ch.modes.Private = modeop + case 'r': + ch.modes.Registered = modeop + case 's': + ch.modes.Secret = modeop + case 't': + ch.modes.ProtectedTopic = modeop + case 'z': + ch.modes.SSLOnly = modeop + case 'Z': + ch.modes.AllSSL = modeop + case 'O': + ch.modes.OperOnly = modeop + case 'k': + if modeop && len(modeargs) != 0 { + ch.modes.Key, modeargs = modeargs[0], modeargs[1:] + } else if !modeop { + ch.modes.Key = "" + } else { + logging.Warn("Channel.ParseModes(): not enough arguments to "+ + "process MODE %s %s%c", ch.name, modestr, m) + } + case 'l': + if modeop && len(modeargs) != 0 { + ch.modes.Limit, _ = strconv.Atoi(modeargs[0]) + modeargs = modeargs[1:] + } else if !modeop { + ch.modes.Limit = 0 + } else { + logging.Warn("Channel.ParseModes(): not enough arguments to "+ + "process MODE %s %s%c", ch.name, modestr, m) + } + case 'q', 'a', 'o', 'h', 'v': + if len(modeargs) != 0 { + if nk, ok := ch.lookup[modeargs[0]]; ok { + cp := ch.nicks[nk] + switch m { + case 'q': + cp.Owner = modeop + case 'a': + cp.Admin = modeop + case 'o': + cp.Op = modeop + case 'h': + cp.HalfOp = modeop + case 'v': + cp.Voice = modeop + } + modeargs = modeargs[1:] + } else { + logging.Warn("Channel.ParseModes(): untracked nick %s "+ + "received MODE on channel %s", modeargs[0], ch.name) + } + } else { + logging.Warn("Channel.ParseModes(): not enough arguments to "+ + "process MODE %s %s%c", ch.name, modestr, m) + } + default: + logging.Info("Channel.ParseModes(): unknown mode char %c", m) + } + } +} + +// Returns true if the Nick is associated with the Channel +func (ch *Channel) IsOn(nk string) (*ChanPrivs, bool) { + cp, ok := ch.Nicks[nk] + return cp, ok +} + +// Test Channel equality. +func (ch *Channel) Equals(other *Channel) bool { + return reflect.DeepEqual(ch, other) +} + +// Duplicates a ChanMode struct. +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: +// Channel: e.g. #moo +// Topic: e.g. Discussing the merits of cows! +// Mode: e.g. +nsti +// Nicks: +// : e.g. CowMaster: +o +// ... +func (ch *Channel) String() string { + str := "Channel: " + ch.Name + "\n\t" + str += "Topic: " + ch.Topic + "\n\t" + str += "Modes: " + ch.Modes.String() + "\n\t" + str += "Nicks: \n" + for nk, cp := range ch.Nicks { + str += "\t\t" + nk + ": " + cp.String() + "\n" + } + return str +} + +func (ch *channel) String() string { + return ch.Channel().String() +} + +// Returns a string representing the channel modes. Looks like: +// +npk key +func (cm *ChanMode) String() string { + str := "+" + a := make([]string, 0) + v := reflect.Indirect(reflect.ValueOf(cm)) + t := v.Type() + for i := 0; i < v.NumField(); i++ { + switch f := v.Field(i); f.Kind() { + case reflect.Bool: + if f.Bool() { + str += ChanModeToString[t.Field(i).Name] + } + case reflect.String: + if f.String() != "" { + str += ChanModeToString[t.Field(i).Name] + a = append(a, f.String()) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if f.Int() != 0 { + str += ChanModeToString[t.Field(i).Name] + a = append(a, strconv.FormatInt(f.Int(), 10)) + } + } + } + for _, s := range a { + if s != "" { + str += " " + s + } + } + if str == "+" { + str = "No modes set" + } + return str +} + +// Returns a string representing the channel privileges. Looks like: +// +o +func (cp *ChanPrivs) String() string { + str := "+" + v := reflect.Indirect(reflect.ValueOf(cp)) + t := v.Type() + for i := 0; i < v.NumField(); i++ { + switch f := v.Field(i); f.Kind() { + // only bools here at the mo too! + case reflect.Bool: + if f.Bool() { + str += ChanPrivToString[t.Field(i).Name] + } + } + } + if str == "+" { + str = "No modes set" + } + return str +} diff --git a/vendor/github.com/fluffle/goirc/state/channel_test.go b/vendor/github.com/fluffle/goirc/state/channel_test.go new file mode 100644 index 0000000..d5c804d --- /dev/null +++ b/vendor/github.com/fluffle/goirc/state/channel_test.go @@ -0,0 +1,176 @@ +package state + +import "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) { + ch := newChannel("#test1") + + if ch.name != "#test1" { + t.Errorf("Channel not created correctly by NewChannel()") + } + if len(ch.nicks) != 0 || len(ch.lookup) != 0 { + t.Errorf("Channel maps contain data after NewChannel()") + } + compareChannel(t, ch) +} + +func TestAddNick(t *testing.T) { + ch := newChannel("#test1") + nk := newNick("test1") + cp := new(ChanPrivs) + + ch.addNick(nk, cp) + + if len(ch.nicks) != 1 || len(ch.lookup) != 1 { + t.Errorf("Nick lists not updated correctly for add.") + } + if c, ok := ch.nicks[nk]; !ok || c != cp { + t.Errorf("Nick test1 not properly stored in nicks map.") + } + if n, ok := ch.lookup["test1"]; !ok || n != nk { + t.Errorf("Nick test1 not properly stored in lookup map.") + } + compareChannel(t, ch) +} + +func TestDelNick(t *testing.T) { + ch := newChannel("#test1") + nk := newNick("test1") + cp := new(ChanPrivs) + + ch.addNick(nk, cp) + ch.delNick(nk) + if len(ch.nicks) != 0 || len(ch.lookup) != 0 { + t.Errorf("Nick lists not updated correctly for del.") + } + if c, ok := ch.nicks[nk]; ok || c != nil { + t.Errorf("Nick test1 not properly removed from nicks map.") + } + if n, ok := ch.lookup["#test1"]; ok || n != nil { + t.Errorf("Nick test1 not properly removed from lookup map.") + } + compareChannel(t, ch) +} + +func TestChannelParseModes(t *testing.T) { + ch := newChannel("#test1") + md := ch.modes + + // Channel modes can adjust channel privs too, so we need a Nick + nk := newNick("test1") + cp := new(ChanPrivs) + ch.addNick(nk, cp) + + // Test bools first. + compareChannel(t, ch) + if md.Private || md.Secret || md.ProtectedTopic || md.NoExternalMsg || + md.Moderated || md.InviteOnly || md.OperOnly || md.SSLOnly { + t.Errorf("Modes for new channel set to true.") + } + + // Flip some bits! + md.Private = true + md.NoExternalMsg = true + md.InviteOnly = true + + // Flip some MOAR bits. + ch.parseModes("+s-p+tm-i") + + compareChannel(t, ch) + if md.Private || !md.Secret || !md.ProtectedTopic || !md.NoExternalMsg || + !md.Moderated || md.InviteOnly || md.OperOnly || md.SSLOnly { + t.Errorf("Modes not flipped correctly by ParseModes.") + } + + // Test numeric parsing (currently only channel limits) + if md.Limit != 0 { + t.Errorf("Limit for new channel not zero.") + } + + // enable limit correctly + ch.parseModes("+l", "256") + compareChannel(t, ch) + if md.Limit != 256 { + t.Errorf("Limit for channel not set correctly") + } + + // enable limit incorrectly + ch.parseModes("+l") + compareChannel(t, ch) + if md.Limit != 256 { + t.Errorf("Bad limit value caused limit to be unset.") + } + + // disable limit correctly + ch.parseModes("-l") + compareChannel(t, ch) + if md.Limit != 0 { + t.Errorf("Limit for channel not unset correctly") + } + + // Test string parsing (currently only channel key) + if md.Key != "" { + t.Errorf("Key set for new channel.") + } + + // enable key correctly + ch.parseModes("+k", "foobar") + compareChannel(t, ch) + if md.Key != "foobar" { + t.Errorf("Key for channel not set correctly") + } + + // enable key incorrectly + ch.parseModes("+k") + compareChannel(t, ch) + if md.Key != "foobar" { + t.Errorf("Bad key value caused key to be unset.") + } + + // disable key correctly + ch.parseModes("-k") + compareChannel(t, ch) + if md.Key != "" { + t.Errorf("Key for channel not unset correctly") + } + + // Test chan privs parsing. + cp.Op = true + cp.HalfOp = true + ch.parseModes("+aq-o", "test1", "test1", "test1") + + compareChannel(t, ch) + if !cp.Owner || !cp.Admin || cp.Op || !cp.HalfOp || cp.Voice { + t.Errorf("Channel privileges not flipped correctly by ParseModes.") + } + + // Test a random mix of modes, just to be sure + md.Limit = 256 + ch.parseModes("+zpt-qsl+kv-h", "test1", "foobar", "test1") + + compareChannel(t, ch) + if !md.Private || md.Secret || !md.ProtectedTopic || !md.NoExternalMsg || + !md.Moderated || md.InviteOnly || md.OperOnly || !md.SSLOnly { + t.Errorf("Modes not flipped correctly by ParseModes (2).") + } + if md.Limit != 0 || md.Key != "foobar" { + t.Errorf("Key and limit not changed correctly by ParseModes (2).") + } + if cp.Owner || !cp.Admin || cp.Op || !cp.HalfOp || !cp.Voice { + // NOTE: HalfOp not actually unset above thanks to deliberate error. + t.Errorf("Channel privileges not flipped correctly by ParseModes (2).") + } +} diff --git a/vendor/github.com/fluffle/goirc/state/mock_tracker.go b/vendor/github.com/fluffle/goirc/state/mock_tracker.go new file mode 100644 index 0000000..aa594c4 --- /dev/null +++ b/vendor/github.com/fluffle/goirc/state/mock_tracker.go @@ -0,0 +1,201 @@ +// Automatically generated by MockGen. DO NOT EDIT! +// Source: tracker.go + +package state + +import ( + gomock "github.com/golang/mock/gomock" +) + +// Mock of Tracker interface +type MockTracker struct { + ctrl *gomock.Controller + recorder *_MockTrackerRecorder +} + +// Recorder for MockTracker (not exported) +type _MockTrackerRecorder struct { + mock *MockTracker +} + +func NewMockTracker(ctrl *gomock.Controller) *MockTracker { + mock := &MockTracker{ctrl: ctrl} + mock.recorder = &_MockTrackerRecorder{mock} + return mock +} + +func (_m *MockTracker) EXPECT() *_MockTrackerRecorder { + return _m.recorder +} + +func (_m *MockTracker) NewNick(nick string) *Nick { + ret := _m.ctrl.Call(_m, "NewNick", nick) + ret0, _ := ret[0].(*Nick) + return ret0 +} + +func (_mr *_MockTrackerRecorder) NewNick(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "NewNick", arg0) +} + +func (_m *MockTracker) GetNick(nick string) *Nick { + ret := _m.ctrl.Call(_m, "GetNick", nick) + ret0, _ := ret[0].(*Nick) + return ret0 +} + +func (_mr *_MockTrackerRecorder) GetNick(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetNick", arg0) +} + +func (_m *MockTracker) ReNick(old string, neu string) *Nick { + ret := _m.ctrl.Call(_m, "ReNick", old, neu) + ret0, _ := ret[0].(*Nick) + return ret0 +} + +func (_mr *_MockTrackerRecorder) ReNick(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "ReNick", arg0, arg1) +} + +func (_m *MockTracker) DelNick(nick string) *Nick { + ret := _m.ctrl.Call(_m, "DelNick", nick) + ret0, _ := ret[0].(*Nick) + return ret0 +} + +func (_mr *_MockTrackerRecorder) DelNick(arg0 interface{}) *gomock.Call { + 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 { + ret := _m.ctrl.Call(_m, "NewChannel", channel) + ret0, _ := ret[0].(*Channel) + return ret0 +} + +func (_mr *_MockTrackerRecorder) NewChannel(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "NewChannel", arg0) +} + +func (_m *MockTracker) GetChannel(channel string) *Channel { + ret := _m.ctrl.Call(_m, "GetChannel", channel) + ret0, _ := ret[0].(*Channel) + return ret0 +} + +func (_mr *_MockTrackerRecorder) GetChannel(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetChannel", arg0) +} + +func (_m *MockTracker) DelChannel(channel string) *Channel { + ret := _m.ctrl.Call(_m, "DelChannel", channel) + ret0, _ := ret[0].(*Channel) + return ret0 +} + +func (_mr *_MockTrackerRecorder) DelChannel(arg0 interface{}) *gomock.Call { + 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 { + ret := _m.ctrl.Call(_m, "Me") + ret0, _ := ret[0].(*Nick) + return ret0 +} + +func (_mr *_MockTrackerRecorder) Me() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Me") +} + +func (_m *MockTracker) IsOn(channel string, nick string) (*ChanPrivs, bool) { + ret := _m.ctrl.Call(_m, "IsOn", channel, nick) + ret0, _ := ret[0].(*ChanPrivs) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +func (_mr *_MockTrackerRecorder) IsOn(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "IsOn", arg0, arg1) +} + +func (_m *MockTracker) Associate(channel string, nick string) *ChanPrivs { + ret := _m.ctrl.Call(_m, "Associate", channel, nick) + ret0, _ := ret[0].(*ChanPrivs) + return ret0 +} + +func (_mr *_MockTrackerRecorder) Associate(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Associate", arg0, arg1) +} + +func (_m *MockTracker) Dissociate(channel string, nick string) { + _m.ctrl.Call(_m, "Dissociate", channel, nick) +} + +func (_mr *_MockTrackerRecorder) Dissociate(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Dissociate", arg0, arg1) +} + +func (_m *MockTracker) Wipe() { + _m.ctrl.Call(_m, "Wipe") +} + +func (_mr *_MockTrackerRecorder) Wipe() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Wipe") +} + +func (_m *MockTracker) String() string { + ret := _m.ctrl.Call(_m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +func (_mr *_MockTrackerRecorder) String() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "String") +} diff --git a/vendor/github.com/fluffle/goirc/state/nick.go b/vendor/github.com/fluffle/goirc/state/nick.go new file mode 100644 index 0000000..7704b0b --- /dev/null +++ b/vendor/github.com/fluffle/goirc/state/nick.go @@ -0,0 +1,200 @@ +package state + +import ( + "github.com/fluffle/goirc/logging" + + "reflect" +) + +// A Nick is returned from the state tracker and contains +// a copy of the nick state at a particular time. +type Nick struct { + Nick, Ident, Host, Name string + Modes *NickMode + Channels map[string]*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) +// (again, only the ones we care about) +// +// This is only really useful for me, as we can't see other people's modes +// without IRC operator privileges (and even then only on some IRCd's). +type NickMode struct { + // MODE +B, +i, +o, +w, +x, +z + Bot, Invisible, Oper, WallOps, HiddenHost, SSL bool +} + +// Map *irc.NickMode fields to IRC mode characters and vice versa +var StringToNickMode = map[string]string{} +var NickModeToString = map[string]string{ + "Bot": "B", + "Invisible": "i", + "Oper": "o", + "WallOps": "w", + "HiddenHost": "x", + "SSL": "z", +} + +func init() { + for k, v := range NickModeToString { + StringToNickMode[v] = k + } +} + +/******************************************************************************\ + * nick methods for state management +\******************************************************************************/ + +func newNick(n string) *nick { + return &nick{ + nick: n, + modes: new(NickMode), + chans: make(map[*channel]*ChanPrivs), + lookup: make(map[string]*channel), + } +} + +// Returns a copy of the internal tracker nick state at this time. +// Relies on tracker-level locking for concurrent access. +func (nk *nick) Nick() *Nick { + 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) isOn(ch *channel) (*ChanPrivs, bool) { + cp, ok := nk.chans[ch] + return cp.Copy(), ok +} + +// Associates a Channel with a Nick. +func (nk *nick) addChannel(ch *channel, cp *ChanPrivs) { + if _, ok := nk.chans[ch]; !ok { + nk.chans[ch] = cp + nk.lookup[ch.name] = ch + } else { + logging.Warn("Nick.addChannel(): %s already on %s.", nk.nick, ch.name) + } +} + +// Disassociates a Channel from a Nick. +func (nk *nick) delChannel(ch *channel) { + if _, ok := nk.chans[ch]; ok { + delete(nk.chans, ch) + delete(nk.lookup, ch.name) + } else { + logging.Warn("Nick.delChannel(): %s not on %s.", nk.nick, ch.name) + } +} + +// Parse mode strings for a Nick. +func (nk *nick) parseModes(modes string) { + var modeop bool // true => add mode, false => remove mode + for i := 0; i < len(modes); i++ { + switch m := modes[i]; m { + case '+': + modeop = true + case '-': + modeop = false + case 'B': + nk.modes.Bot = modeop + case 'i': + nk.modes.Invisible = modeop + case 'o': + nk.modes.Oper = modeop + case 'w': + nk.modes.WallOps = modeop + case 'x': + nk.modes.HiddenHost = modeop + case 'z': + nk.modes.SSL = modeop + default: + logging.Info("Nick.ParseModes(): unknown mode char %c", m) + } + } +} + +// Returns true if the Nick is associated with the Channel. +func (nk *Nick) IsOn(ch string) (*ChanPrivs, bool) { + cp, ok := nk.Channels[ch] + return cp, ok +} + +// Tests Nick equality. +func (nk *Nick) Equals(other *Nick) bool { + return reflect.DeepEqual(nk, other) +} + +// Duplicates a NickMode struct. +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: +// Nick: e.g. CowMaster +// Hostmask: e.g. moo@cows.org +// Real Name: e.g. Steve "CowMaster" Bush +// Modes: e.g. +z +// Channels: +// : e.g. #moo: +o +// ... +func (nk *Nick) String() string { + str := "Nick: " + nk.Nick + "\n\t" + str += "Hostmask: " + nk.Ident + "@" + nk.Host + "\n\t" + str += "Real Name: " + nk.Name + "\n\t" + str += "Modes: " + nk.Modes.String() + "\n\t" + str += "Channels: \n" + for ch, cp := range nk.Channels { + str += "\t\t" + ch + ": " + cp.String() + "\n" + } + return str +} + +func (nk *nick) String() string { + return nk.Nick().String() +} + +// Returns a string representing the nick modes. Looks like: +// +iwx +func (nm *NickMode) String() string { + str := "+" + v := reflect.Indirect(reflect.ValueOf(nm)) + t := v.Type() + for i := 0; i < v.NumField(); i++ { + switch f := v.Field(i); f.Kind() { + // only bools here at the mo! + case reflect.Bool: + if f.Bool() { + str += NickModeToString[t.Field(i).Name] + } + } + } + if str == "+" { + str = "No modes set" + } + return str +} diff --git a/vendor/github.com/fluffle/goirc/state/nick_test.go b/vendor/github.com/fluffle/goirc/state/nick_test.go new file mode 100644 index 0000000..1344400 --- /dev/null +++ b/vendor/github.com/fluffle/goirc/state/nick_test.go @@ -0,0 +1,88 @@ +package state + +import "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) { + nk := newNick("test1") + + if nk.nick != "test1" { + t.Errorf("Nick not created correctly by NewNick()") + } + if len(nk.chans) != 0 || len(nk.lookup) != 0 { + t.Errorf("Nick maps contain data after NewNick()") + } + compareNick(t, nk) +} + +func TestAddChannel(t *testing.T) { + nk := newNick("test1") + ch := newChannel("#test1") + cp := new(ChanPrivs) + + nk.addChannel(ch, cp) + + if len(nk.chans) != 1 || len(nk.lookup) != 1 { + t.Errorf("Channel lists not updated correctly for add.") + } + if c, ok := nk.chans[ch]; !ok || c != cp { + t.Errorf("Channel #test1 not properly stored in chans map.") + } + if c, ok := nk.lookup["#test1"]; !ok || c != ch { + t.Errorf("Channel #test1 not properly stored in lookup map.") + } + compareNick(t, nk) +} + +func TestDelChannel(t *testing.T) { + nk := newNick("test1") + ch := newChannel("#test1") + cp := new(ChanPrivs) + + nk.addChannel(ch, cp) + nk.delChannel(ch) + if len(nk.chans) != 0 || len(nk.lookup) != 0 { + t.Errorf("Channel lists not updated correctly for del.") + } + if c, ok := nk.chans[ch]; ok || c != nil { + t.Errorf("Channel #test1 not properly removed from chans map.") + } + if c, ok := nk.lookup["#test1"]; ok || c != nil { + t.Errorf("Channel #test1 not properly removed from lookup map.") + } + compareNick(t, nk) +} + +func TestNickParseModes(t *testing.T) { + nk := newNick("test1") + md := nk.modes + + // Modes should all be false for a new nick + if md.Invisible || md.Oper || md.WallOps || md.HiddenHost || md.SSL { + t.Errorf("Modes for new nick set to true.") + } + + // Set a couple of modes, for testing. + md.Invisible = true + md.HiddenHost = true + + // Parse a mode line that flips one true to false and two false to true + nk.parseModes("+z-x+w") + + compareNick(t, nk) + if !md.Invisible || md.Oper || !md.WallOps || md.HiddenHost || !md.SSL { + t.Errorf("Modes not flipped correctly by ParseModes.") + } +} diff --git a/vendor/github.com/fluffle/goirc/state/tracker.go b/vendor/github.com/fluffle/goirc/state/tracker.go new file mode 100644 index 0000000..209c7cf --- /dev/null +++ b/vendor/github.com/fluffle/goirc/state/tracker.go @@ -0,0 +1,369 @@ +package state + +import ( + "github.com/fluffle/goirc/logging" + + "sync" +) + +// The state manager interface +type Tracker interface { + // Nick methods + NewNick(nick string) *Nick + GetNick(nick string) *Nick + ReNick(old, neu string) *Nick + DelNick(nick string) *Nick + NickInfo(nick, ident, host, name string) *Nick + NickModes(nick, modestr string) *Nick + // Channel methods + NewChannel(channel string) *Channel + GetChannel(channel string) *Channel + DelChannel(channel string) *Channel + Topic(channel, topic string) *Channel + ChannelModes(channel, modestr string, modeargs ...string) *Channel + // Information about ME! + Me() *Nick + // And the tracking operations + IsOn(channel, nick string) (*ChanPrivs, bool) + Associate(channel, nick string) *ChanPrivs + Dissociate(channel, nick string) + Wipe() + // The state tracker can output a debugging string + String() string +} + +// ... and a struct to implement it ... +type stateTracker struct { + // Map of channels we're on + chans map[string]*channel + // Map of nicks we know about + nicks map[string]*nick + + // We need to keep state on who we are :-) + me *nick + + // And we need to protect against data races *cough*. + mu sync.Mutex +} + +var _ Tracker = (*stateTracker)(nil) + +// ... and a constructor to make it ... +func NewTracker(mynick string) *stateTracker { + st := &stateTracker{ + chans: make(map[string]*channel), + nicks: make(map[string]*nick), + } + st.me = newNick(mynick) + st.nicks[mynick] = st.me + return st +} + +// ... and a method to wipe the state clean. +func (st *stateTracker) Wipe() { + st.mu.Lock() + defer st.mu.Unlock() + // Deleting all the channels implicitly deletes every nick but me. + for _, ch := range st.chans { + st.delChannel(ch) + } +} + +/******************************************************************************\ + * tracker methods to create/look up nicks/channels +\******************************************************************************/ + +// Creates a new nick, initialises it, and stores it so it +// can be properly tracked for state management purposes. +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 { + logging.Warn("Tracker.NewNick(): %s already tracked.", n) + return nil + } + st.nicks[n] = newNick(n) + return st.nicks[n].Nick() +} + +// Returns a nick for the nick n, if we're tracking it. +func (st *stateTracker) GetNick(n string) *Nick { + st.mu.Lock() + defer st.mu.Unlock() + if nk, ok := st.nicks[n]; ok { + return nk.Nick() + } + return nil +} + +// Signals to the tracker that a nick should be tracked +// under a "neu" nick rather than the old one. +func (st *stateTracker) ReNick(old, neu string) *Nick { + st.mu.Lock() + defer st.mu.Unlock() + 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) + st.nicks[neu] = nk + for ch, _ := range nk.chans { + // We also need to update the lookup maps of all the channels + // the nick is on, to keep things in sync. + delete(ch.lookup, old) + ch.lookup[neu] = nk + } + return nk.Nick() +} + +// Removes a nick from being tracked. +func (st *stateTracker) DelNick(n string) *Nick { + st.mu.Lock() + defer st.mu.Unlock() + if nk, ok := st.nicks[n]; ok { + if nk == st.me { + logging.Warn("Tracker.DelNick(): won't delete myself.") + return nil + } + st.delNick(nk) + return nk.Nick() + } + logging.Warn("Tracker.DelNick(): %s not tracked.", n) + return nil +} + +func (st *stateTracker) delNick(nk *nick) { + // st.mu lock held by DelNick, DelChannel or Wipe + if nk == st.me { + // Shouldn't get here => internal state tracking code is fubar. + logging.Error("Tracker.DelNick(): TRYING TO DELETE ME :-(") + return + } + delete(st.nicks, nk.nick) + for ch, _ := range nk.chans { + nk.delChannel(ch) + ch.delNick(nk) + if len(ch.nicks) == 0 { + // Deleting a nick from tracking shouldn't empty any channels as + // *we* should be on the channel with them to be tracking them. + logging.Error("Tracker.delNick(): deleting nick %s emptied "+ + "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 +// can be properly tracked for state management purposes. +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 { + logging.Warn("Tracker.NewChannel(): %s already tracked.", c) + return nil + } + st.chans[c] = newChannel(c) + return st.chans[c].Channel() +} + +// Returns a Channel for the channel c, if we're tracking it. +func (st *stateTracker) GetChannel(c string) *Channel { + st.mu.Lock() + defer st.mu.Unlock() + if ch, ok := st.chans[c]; ok { + return ch.Channel() + } + return nil +} + +// Removes a Channel from being tracked. +func (st *stateTracker) DelChannel(c string) *Channel { + st.mu.Lock() + defer st.mu.Unlock() + if ch, ok := st.chans[c]; ok { + st.delChannel(ch) + return ch.Channel() + } + logging.Warn("Tracker.DelChannel(): %s not tracked.", c) + return nil +} + +func (st *stateTracker) delChannel(ch *channel) { + // st.mu lock held by DelChannel or Wipe + delete(st.chans, ch.name) + for nk, _ := range ch.nicks { + ch.delNick(nk) + nk.delChannel(ch) + if len(nk.chans) == 0 && nk != st.me { + // We're no longer in any channels with this nick. + st.delNick(nk) + } + } +} + +// 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. +// NOTE: Nick() requires the mutex to be held. +func (st *stateTracker) Me() *Nick { + st.mu.Lock() + defer st.mu.Unlock() + return st.me.Nick() +} + +// Returns true if both the channel c and the nick n are tracked +// and the nick is associated with the channel. +func (st *stateTracker) IsOn(c, n string) (*ChanPrivs, bool) { + st.mu.Lock() + defer st.mu.Unlock() + nk, nok := st.nicks[n] + ch, cok := st.chans[c] + if nok && cok { + return nk.isOn(ch) + } + return nil, false +} + +// Associates an already known nick with an already known channel. +func (st *stateTracker) Associate(c, n string) *ChanPrivs { + st.mu.Lock() + defer st.mu.Unlock() + nk, nok := st.nicks[n] + ch, cok := st.chans[c] + + if !cok { + // As we can implicitly delete both nicks and channels from being + // tracked by dissociating one from the other, we should verify that + // we're not being passed an old Nick or Channel. + logging.Error("Tracker.Associate(): channel %s not found in "+ + "internal state.", c) + return nil + } else if !nok { + logging.Error("Tracker.Associate(): nick %s not found in "+ + "internal state.", n) + return nil + } else if _, ok := nk.isOn(ch); ok { + logging.Warn("Tracker.Associate(): %s already on %s.", + nk, ch) + return nil + } + cp := new(ChanPrivs) + ch.addNick(nk, cp) + nk.addChannel(ch, cp) + return cp.Copy() +} + +// Dissociates an already known nick from an already known channel. +// Does some tidying up to stop tracking nicks we're no longer on +// any common channels with, and channels we're no longer on. +func (st *stateTracker) Dissociate(c, n string) { + st.mu.Lock() + defer st.mu.Unlock() + nk, nok := st.nicks[n] + ch, cok := st.chans[c] + + if !cok { + // As we can implicitly delete both nicks and channels from being + // tracked by dissociating one from the other, we should verify that + // we're not being passed an old Nick or Channel. + logging.Error("Tracker.Dissociate(): channel %s not found in "+ + "internal state.", c) + } else if !nok { + logging.Error("Tracker.Dissociate(): nick %s not found in "+ + "internal state.", n) + } else if _, ok := nk.isOn(ch); !ok { + logging.Warn("Tracker.Dissociate(): %s not on %s.", + nk.nick, ch.name) + } else if nk == st.me { + // I'm leaving the channel for some reason, so it won't be tracked. + st.delChannel(ch) + } else { + // Remove the nick from the channel and the channel from the nick. + ch.delNick(nk) + nk.delChannel(ch) + if len(nk.chans) == 0 { + // We're no longer in any channels with this nick. + st.delNick(nk) + } + } +} + +func (st *stateTracker) String() string { + st.mu.Lock() + defer st.mu.Unlock() + str := "GoIRC Channels\n" + str += "--------------\n\n" + for _, ch := range st.chans { + str += ch.String() + "\n" + } + str += "GoIRC NickNames\n" + str += "---------------\n\n" + for _, n := range st.nicks { + if n != st.me { + str += n.String() + "\n" + } + } + return str +} diff --git a/vendor/github.com/fluffle/goirc/state/tracker_test.go b/vendor/github.com/fluffle/goirc/state/tracker_test.go new file mode 100644 index 0000000..1cfe8fc --- /dev/null +++ b/vendor/github.com/fluffle/goirc/state/tracker_test.go @@ -0,0 +1,564 @@ +package state + +import ( + "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) { + st := NewTracker("mynick") + + if len(st.nicks) != 1 { + t.Errorf("Nick list of new tracker is not 1 (me!).") + } + if len(st.chans) != 0 { + t.Errorf("Channel list of new tracker is not empty.") + } + if nk, ok := st.nicks["mynick"]; !ok || nk.nick != "mynick" || nk != st.me { + t.Errorf("My nick not stored correctly in tracker.") + } +} + +func TestSTNewNick(t *testing.T) { + st := NewTracker("mynick") + test1 := st.NewNick("test1") + + if test1 == nil || test1.Nick != "test1" { + t.Errorf("Nick object created incorrectly by NewNick.") + } + if n, ok := st.nicks["test1"]; !ok || !test1.Equals(n.Nick()) || len(st.nicks) != 2 { + t.Errorf("Nick object stored incorrectly by NewNick.") + } + + if fail := st.NewNick("test1"); fail != nil { + 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) { + st := NewTracker("mynick") + test1 := st.NewNick("test1") + + if n := st.GetNick("test1"); !test1.Equals(n) { + t.Errorf("Incorrect nick returned by GetNick.") + } + if n := st.GetNick("test2"); n != nil { + t.Errorf("Nick unexpectedly returned by GetNick.") + } + if len(st.nicks) != 2 { + t.Errorf("Nick list changed size during GetNick.") + } +} + +func TestSTReNick(t *testing.T) { + st := NewTracker("mynick") + test1 := st.NewNick("test1") + + // This channel is here to ensure that its lookup map gets updated + st.NewChannel("#chan1") + st.Associate("#chan1", "test1") + + // 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 { + t.Errorf("Nick test1 still exists after ReNick.") + } + if n, ok := st.nicks["test2"]; !ok || n != n1 { + t.Errorf("Nick test2 doesn't exist after ReNick.") + } + if _, ok := c1.lookup["test1"]; ok { + t.Errorf("Channel #chan1 still knows about test1 after ReNick.") + } + if n, ok := c1.lookup["test2"]; !ok || n != n1 { + t.Errorf("Channel #chan1 doesn't know about test2 after ReNick.") + } + if test1.Nick != "test1" { + t.Errorf("Nick test1 changed unexpectedly.") + } + if !test2.Equals(n1.Nick()) { + t.Errorf("Nick test2 did not change.") + } + if len(st.nicks) != 2 { + t.Errorf("Nick list changed size during ReNick.") + } + if len(c1.lookup) != 1 { + t.Errorf("Channel lookup list changed size during ReNick.") + } + + st.NewNick("test1") + n2 := st.nicks["test1"] + fail := st.ReNick("test1", "test2") + + if n, ok := st.nicks["test2"]; !ok || n != n1 { + t.Errorf("Nick test2 overwritten/deleted by ReNick.") + } + if n, ok := st.nicks["test1"]; !ok || n != n2 { + t.Errorf("Nick test1 overwritten/deleted by ReNick.") + } + if fail != nil { + t.Errorf("ReNick returned Nick on failure.") + } + if len(st.nicks) != 3 { + t.Errorf("Nick list changed size during ReNick.") + } +} + +func TestSTDelNick(t *testing.T) { + st := NewTracker("mynick") + + add := st.NewNick("test1") + del := st.DelNick("test1") + + if _, ok := st.nicks["test1"]; ok { + t.Errorf("Nick test1 still exists after DelNick.") + } + if len(st.nicks) != 1 { + 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 + // known nick first to catch any possible accidental removals. + st.NewNick("test1") + fail := st.DelNick("test2") + if fail != nil || len(st.nicks) != 2 { + t.Errorf("Deleting unknown nick had unexpected side-effects.") + } + + // Deleting my nick shouldn't work + fail = st.DelNick("mynick") + if fail != nil || len(st.nicks) != 2 { + t.Errorf("Deleting myself had unexpected side-effects.") + } + + // Test that deletion correctly dissociates nick from channels. + // NOTE: the two error states in delNick (as opposed to DelNick) + // are not tested for here, as they will only arise from programming + // errors in other methods. + + // Create a new channel for testing purposes. + st.NewChannel("#test1") + + // Associate both "my" nick and test1 with the channel + st.Associate("#test1", "mynick") + 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) + if len(c1.nicks) != 2 || len(st.nicks) != 2 || + len(st.me.chans) != 1 || len(n1.chans) != 1 || len(st.chans) != 1 { + t.Errorf("Bad initial state for test DelNick() channel dissociation.") + } + + // Actual deletion tested above... + st.DelNick("test1") + + if len(c1.nicks) != 1 || len(st.nicks) != 1 || + len(st.me.chans) != 1 || len(n1.chans) != 0 || len(st.chans) != 1 { + t.Errorf("Deleting nick didn't dissociate correctly from channels.") + } + + if _, ok := c1.nicks[n1]; ok { + t.Errorf("Nick not removed from channel's nick map.") + } + if _, ok := c1.lookup["test1"]; ok { + 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) { + st := NewTracker("mynick") + + if len(st.chans) != 0 { + t.Errorf("Channel list of new tracker is non-zero length.") + } + + test1 := st.NewChannel("#test1") + + if test1 == nil || test1.Name != "#test1" { + t.Errorf("Channel object created incorrectly by NewChannel.") + } + if c, ok := st.chans["#test1"]; !ok || !test1.Equals(c.Channel()) || len(st.chans) != 1 { + t.Errorf("Channel object stored incorrectly by NewChannel.") + } + + if fail := st.NewChannel("#test1"); fail != nil { + 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) { + st := NewTracker("mynick") + + test1 := st.NewChannel("#test1") + + if c := st.GetChannel("#test1"); !test1.Equals(c) { + t.Errorf("Incorrect Channel returned by GetChannel.") + } + if c := st.GetChannel("#test2"); c != nil { + t.Errorf("Channel unexpectedly returned by GetChannel.") + } + if len(st.chans) != 1 { + t.Errorf("Channel list changed size during GetChannel.") + } +} + +func TestSTDelChannel(t *testing.T) { + st := NewTracker("mynick") + + add := st.NewChannel("#test1") + del := st.DelChannel("#test1") + + if _, ok := st.chans["#test1"]; ok { + t.Errorf("Channel test1 still exists after DelChannel.") + } + if len(st.chans) != 0 { + t.Errorf("Channel list still contains chans after DelChannel.") + } + if !add.Equals(del) { + t.Errorf("DelChannel returned different channel.") + } + + // Deleting unknown channel shouldn't work, but let's make sure we have a + // known channel first to catch any possible accidental removals. + st.NewChannel("#test1") + fail := st.DelChannel("#test2") + if fail != nil || len(st.chans) != 1 { + t.Errorf("DelChannel had unexpected side-effects.") + } + + // Test that deletion correctly dissociates channel from tracked nicks. + // In order to test this thoroughly we need two channels (so that delNick() + // is not called internally in delChannel() when len(nick1.chans) == 0. + st.NewChannel("#test2") + st.NewNick("test1") + + // Associate both "my" nick and test1 with the channels + st.Associate("#test1", "mynick") + st.Associate("#test1", "test1") + st.Associate("#test2", "mynick") + 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) + if len(c1.nicks) != 2 || len(c2.nicks) != 2 || len(st.nicks) != 2 || + len(st.me.chans) != 2 || len(n1.chans) != 2 || len(st.chans) != 2 { + t.Errorf("Bad initial state for test DelChannel() nick dissociation.") + } + + st.DelChannel("#test1") + + // Test intermediate state. We're still on #test2 with test1, so test1 + // shouldn't be deleted from state tracking itself just yet. + if len(c1.nicks) != 0 || len(c2.nicks) != 2 || len(st.nicks) != 2 || + len(st.me.chans) != 1 || len(n1.chans) != 1 || len(st.chans) != 1 { + t.Errorf("Deleting channel didn't dissociate correctly from nicks.") + } + if _, ok := n1.chans[c1]; ok { + t.Errorf("Channel not removed from nick's chans map.") + } + if _, ok := n1.lookup["#test1"]; ok { + t.Errorf("Channel not removed from nick's lookup map.") + } + + st.DelChannel("#test2") + + // 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. + if len(c1.nicks) != 0 || len(c2.nicks) != 0 || len(st.nicks) != 1 || + len(st.me.chans) != 0 || len(n1.chans) != 0 || len(st.chans) != 0 { + t.Errorf("Deleting last channel didn't dissociate correctly from nicks.") + } + if _, ok := st.nicks["test1"]; ok { + t.Errorf("Nick not deleted correctly when on no channels.") + } + if _, ok := st.nicks["mynick"]; !ok { + t.Errorf("My nick deleted incorrectly when on no channels.") + } +} + +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) { + st := NewTracker("mynick") + + st.NewNick("test1") + st.NewChannel("#test1") + + if priv, ok := st.IsOn("#test1", "test1"); ok || priv != nil { + t.Errorf("test1 is not on #test1 (yet)") + } + st.Associate("#test1", "test1") + if priv, ok := st.IsOn("#test1", "test1"); !ok || priv == nil { + t.Errorf("test1 is on #test1 (now)") + } +} + +func TestSTAssociate(t *testing.T) { + st := NewTracker("mynick") + + st.NewNick("test1") + st.NewChannel("#test1") + + // We need to check out the manipulation of the internals. + 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.") + } + + // Test error cases + if st.Associate("", "test1") != nil { + t.Errorf("Associating unknown channel did not return nil.") + } + if st.Associate("#test1", "") != nil { + t.Errorf("Associating unknown nick did not return nil.") + } + if st.Associate("#test1", "test1") != nil { + t.Errorf("Associating already-associated things did not return nil.") + } +} + +func TestSTDissociate(t *testing.T) { + st := NewTracker("mynick") + + st.NewNick("test1") + st.NewChannel("#test1") + st.NewChannel("#test2") + + // Associate both "my" nick and test1 with the channels + st.Associate("#test1", "mynick") + st.Associate("#test1", "test1") + st.Associate("#test2", "mynick") + 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. + if len(c1.nicks) != 2 || len(c2.nicks) != 2 || len(st.nicks) != 2 || + len(st.me.chans) != 2 || len(n1.chans) != 2 || len(st.chans) != 2 { + t.Errorf("Initial state for dissociation tests looks odd.") + } + + // First, test the case of me leaving #test2 + st.Dissociate("#test2", "mynick") + + // This should have resulted in the complete deletion of the channel. + if len(c1.nicks) != 2 || len(c2.nicks) != 0 || len(st.nicks) != 2 || + len(st.me.chans) != 1 || len(n1.chans) != 1 || len(st.chans) != 1 { + 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. + st.NewChannel("#test2") + st.Associate("#test2", "mynick") + st.Associate("#test2", "test1") + + // c2 is out of date with the complete deletion of the channel + c2 = st.chans["#test2"] + + // Check state once moar. + if len(c1.nicks) != 2 || len(c2.nicks) != 2 || len(st.nicks) != 2 || + len(st.me.chans) != 2 || len(n1.chans) != 2 || len(st.chans) != 2 { + t.Errorf("Reassociating to channel has produced unexpected state.") + } + + // Now, lets dissociate test1 from #test1 then #test2. + // This first one should only result in a change in associations. + st.Dissociate("#test1", "test1") + + if len(c1.nicks) != 1 || len(c2.nicks) != 2 || len(st.nicks) != 2 || + len(st.me.chans) != 2 || len(n1.chans) != 1 || len(st.chans) != 2 { + t.Errorf("Dissociating a nick from one channel went wrong.") + } + + // This second one should also delete test1 + // as it's no longer on any common channels with us + st.Dissociate("#test2", "test1") + + if len(c1.nicks) != 1 || len(c2.nicks) != 1 || len(st.nicks) != 1 || + 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.") + } + if st.GetNick("test1") != nil { + t.Errorf("Able to get nick after dissociating from all channels.") + } +} + +func TestSTWipe(t *testing.T) { + st := NewTracker("mynick") + + st.NewNick("test1") + st.NewNick("test2") + st.NewNick("test3") + st.NewChannel("#test1") + st.NewChannel("#test2") + st.NewChannel("#test3") + + // Some associations + st.Associate("#test1", "mynick") + st.Associate("#test2", "mynick") + st.Associate("#test3", "mynick") + + st.Associate("#test1", "test1") + st.Associate("#test2", "test2") + st.Associate("#test3", "test3") + + st.Associate("#test1", "test2") + st.Associate("#test2", "test3") + + 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. + if len(st.nicks) != 4 || len(st.chans) != 3 || len(st.me.chans) != 3 { + t.Errorf("Tracker nick/channel lists wrong length before wipe.") + } + if len(chan1.nicks) != 4 || len(chan2.nicks) != 3 || len(chan3.nicks) != 2 { + t.Errorf("Channel nick lists wrong length before wipe.") + } + if len(nick1.chans) != 1 || len(nick2.chans) != 2 || len(nick3.chans) != 3 { + t.Errorf("Nick chan lists wrong length before wipe.") + } + + // Nuke *all* the state! + st.Wipe() + + // Check the state we have at this point is what we would expect. + if len(st.nicks) != 1 || len(st.chans) != 0 || len(st.me.chans) != 0 { + t.Errorf("Tracker nick/channel lists wrong length after wipe.") + } + if len(chan1.nicks) != 0 || len(chan2.nicks) != 0 || len(chan3.nicks) != 0 { + t.Errorf("Channel nick lists wrong length after wipe.") + } + if len(nick1.chans) != 0 || len(nick2.chans) != 0 || len(nick3.chans) != 0 { + t.Errorf("Nick chan lists wrong length after wipe.") + } +} -- cgit v1.2.3