-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a NamedHandler, as well as the ability to remove handlers by thei…
…r name. (#47) * Add handler removals * Add some dispatcher tests to confirm removals work fine * fix lint * Add warnings that methods are not thread safe * Fix lints * Add threadsafe mutexes to protect against undefined behaviour from concurrent modification * Improve tests, and fix the error that they uncovered * pre-allocate handlers when using getGroups * Add additional tests for handler mappings * remove invalid thread safety comments on group add/remove * remove redundant dummy redeclaration
- Loading branch information
1 parent
eab7bce
commit f87f4f7
Showing
7 changed files
with
459 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package ext_test | ||
|
||
import ( | ||
"sort" | ||
"testing" | ||
|
||
"github.com/PaulSonOfLars/gotgbot/v2" | ||
"github.com/PaulSonOfLars/gotgbot/v2/ext" | ||
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers" | ||
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/message" | ||
) | ||
|
||
func TestDispatcher(t *testing.T) { | ||
type testHandler struct { | ||
group int | ||
shouldRun bool | ||
returnVal error | ||
} | ||
|
||
for name, testParams := range map[string]struct { | ||
handlers []testHandler | ||
numMatches int | ||
}{ | ||
"one group two handlers": { | ||
handlers: []testHandler{ | ||
{ | ||
group: 0, | ||
shouldRun: true, | ||
returnVal: nil, | ||
}, { | ||
group: 0, | ||
shouldRun: false, // same group, so doesnt run | ||
returnVal: nil, | ||
}, | ||
}, | ||
numMatches: 1, | ||
}, | ||
"two handlers two groups": { | ||
handlers: []testHandler{ | ||
{ | ||
group: 0, | ||
shouldRun: true, | ||
returnVal: nil, | ||
}, { | ||
group: 1, | ||
shouldRun: true, // second group, so also runs | ||
returnVal: nil, | ||
}, | ||
}, | ||
numMatches: 2, | ||
}, | ||
"end groups": { | ||
handlers: []testHandler{ | ||
{ | ||
group: 0, | ||
shouldRun: true, | ||
returnVal: ext.EndGroups, | ||
}, { | ||
group: 1, | ||
shouldRun: false, // ended, so second group doesnt run | ||
returnVal: nil, | ||
}, | ||
}, | ||
numMatches: 1, | ||
}, | ||
"continue groups": { | ||
handlers: []testHandler{ | ||
{ | ||
group: 0, | ||
shouldRun: true, | ||
returnVal: ext.ContinueGroups, | ||
}, { | ||
group: 0, | ||
shouldRun: true, // continued, so second item in same group runs | ||
returnVal: nil, | ||
}, | ||
}, | ||
numMatches: 2, | ||
}, | ||
} { | ||
name, testParams := name, testParams | ||
|
||
t.Run(name, func(t *testing.T) { | ||
d := ext.NewDispatcher(nil) | ||
var events []int | ||
for idx, h := range testParams.handlers { | ||
idx, h := idx, h | ||
|
||
t.Logf("Loading handler %d in group %d", idx, h.group) | ||
d.AddHandlerToGroup(handlers.NewMessage(message.All, func(b *gotgbot.Bot, ctx *ext.Context) error { | ||
if !h.shouldRun { | ||
t.Errorf("handler %d in group %d should not have run", idx, h.group) | ||
t.FailNow() | ||
} | ||
|
||
t.Logf("handler %d in group %d has run, as expected", idx, h.group) | ||
events = append(events, idx) | ||
return h.returnVal | ||
}), h.group) | ||
} | ||
|
||
t.Log("Processing one update...") | ||
err := d.ProcessUpdate(nil, &gotgbot.Update{ | ||
Message: &gotgbot.Message{Text: "test text"}, | ||
}, nil) | ||
if err != nil { | ||
t.Errorf("Unexpected error while processing updates: %s", err.Error()) | ||
} | ||
|
||
// ensure events handled in order | ||
if !sort.IntsAreSorted(events) { | ||
t.Errorf("order of events is not sorted: %v", events) | ||
} | ||
if len(events) != testParams.numMatches { | ||
t.Errorf("got %d matches, expected %d ", len(events), testParams.numMatches) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestDispatcher_RemoveHandlerFromGroup(t *testing.T) { | ||
d := ext.NewDispatcher(nil) | ||
|
||
const removeMe = "remove_me" | ||
const group = 0 | ||
|
||
d.AddHandlerToGroup(handlers.NewNamedhandler(removeMe, handlers.NewMessage(message.All, nil)), group) | ||
|
||
if found := d.RemoveHandlerFromGroup(removeMe, group); !found { | ||
t.Errorf("RemoveHandlerFromGroup() = %v, want true", found) | ||
} | ||
} | ||
|
||
func TestDispatcher_RemoveOneHandlerFromGroup(t *testing.T) { | ||
d := ext.NewDispatcher(nil) | ||
|
||
const removeMe = "remove_me" | ||
const group = 0 | ||
|
||
// Load handler twice. | ||
d.AddHandlerToGroup(handlers.NewNamedhandler(removeMe, handlers.NewMessage(message.All, nil)), group) | ||
d.AddHandlerToGroup(handlers.NewNamedhandler(removeMe, handlers.NewMessage(message.All, nil)), group) | ||
|
||
// Remove handler twice. | ||
if found := d.RemoveHandlerFromGroup(removeMe, group); !found { | ||
t.Errorf("RemoveHandlerFromGroup() = %v, want true", found) | ||
} | ||
if found := d.RemoveHandlerFromGroup(removeMe, group); !found { | ||
t.Errorf("RemoveHandlerFromGroup() = %v, want true", found) | ||
} | ||
// fail! only 2 in there. | ||
if found := d.RemoveHandlerFromGroup(removeMe, group); found { | ||
t.Errorf("RemoveHandlerFromGroup() = %v, want false", found) | ||
} | ||
} | ||
|
||
func TestDispatcher_RemoveHandlerNonExistingHandlerFromGroup(t *testing.T) { | ||
d := ext.NewDispatcher(nil) | ||
|
||
const keepMe = "keep_me" | ||
const removeMe = "remove_me" | ||
const group = 0 | ||
|
||
d.AddHandlerToGroup(handlers.NewNamedhandler(keepMe, handlers.NewMessage(message.All, nil)), group) | ||
|
||
if found := d.RemoveHandlerFromGroup(removeMe, group); found { | ||
t.Errorf("RemoveHandlerFromGroup() = %v, want false", found) | ||
} | ||
} | ||
|
||
func TestDispatcher_RemoveHandlerHandlerFromNonExistingGroup(t *testing.T) { | ||
d := ext.NewDispatcher(nil) | ||
|
||
const removeMe = "remove_me" | ||
const group = 0 | ||
const wrongGroup = 1 | ||
d.AddHandlerToGroup(handlers.NewNamedhandler(removeMe, handlers.NewMessage(message.All, nil)), group) | ||
|
||
if found := d.RemoveHandlerFromGroup(removeMe, wrongGroup); found { | ||
t.Errorf("RemoveHandlerFromGroup() = %v, want false", found) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package ext | ||
|
||
import ( | ||
"sort" | ||
"sync" | ||
) | ||
|
||
type handlerMappings struct { | ||
// mutex is used to ensure everything threadsafe. | ||
mutex sync.RWMutex | ||
|
||
// handlerGroups represents the list of available handler groups, numerically sorted. | ||
handlerGroups []int | ||
// handlers represents all available handlers, split into groups (see handlerGroups). | ||
handlers map[int][]Handler | ||
} | ||
|
||
func (m *handlerMappings) add(h Handler, group int) { | ||
m.mutex.Lock() | ||
defer m.mutex.Unlock() | ||
|
||
if m.handlers == nil { | ||
m.handlers = map[int][]Handler{} | ||
} | ||
currHandlers, ok := m.handlers[group] | ||
if !ok { | ||
m.handlerGroups = append(m.handlerGroups, group) | ||
sort.Ints(m.handlerGroups) | ||
} | ||
m.handlers[group] = append(currHandlers, h) | ||
} | ||
|
||
func (m *handlerMappings) remove(name string, group int) bool { | ||
m.mutex.Lock() | ||
defer m.mutex.Unlock() | ||
|
||
currHandlers, ok := m.handlers[group] | ||
if !ok { | ||
// group does not exist; removal failed. | ||
return false | ||
} | ||
|
||
for idx, handler := range currHandlers { | ||
if handler.Name() != name { | ||
continue | ||
} | ||
|
||
// Only one item left, so just delete the group entirely. | ||
if len(currHandlers) == 1 { | ||
// get index of the current group to remove it from the list of handlergroups | ||
gIdx := getIndex(group, m.handlerGroups) | ||
if gIdx != -1 { | ||
m.handlerGroups = append(m.handlerGroups[:gIdx], m.handlerGroups[gIdx+1:]...) | ||
} | ||
delete(m.handlers, group) | ||
return true | ||
} | ||
|
||
// Make sure to copy the handler list to ensure we don't change the values of the underlying arrays, which | ||
// could cause slice access issues when used concurrently. | ||
newHandlers := make([]Handler, len(m.handlers[group])) | ||
copy(newHandlers, m.handlers[group]) | ||
|
||
m.handlers[group] = append(newHandlers[:idx], newHandlers[idx+1:]...) | ||
return true | ||
} | ||
// handler not found - removal failed. | ||
return false | ||
} | ||
|
||
func getIndex(find int, is []int) int { | ||
for i, v := range is { | ||
if v == find { | ||
return i | ||
} | ||
} | ||
return -1 | ||
} | ||
|
||
func (m *handlerMappings) removeGroup(group int) bool { | ||
m.mutex.Lock() | ||
defer m.mutex.Unlock() | ||
|
||
if _, ok := m.handlers[group]; !ok { | ||
// Group doesn't exist in map, so already removed. | ||
return false | ||
} | ||
|
||
for idx, handlerGroup := range m.handlerGroups { | ||
if handlerGroup != group { | ||
continue | ||
} | ||
|
||
m.handlerGroups = append(m.handlerGroups[:idx], m.handlerGroups[idx+1:]...) | ||
delete(m.handlers, group) | ||
// Group found, and deleted. Success! | ||
return true | ||
} | ||
// Group not found in list - so already removed. | ||
return false | ||
} | ||
|
||
func (m *handlerMappings) getGroups() [][]Handler { | ||
m.mutex.RLock() | ||
defer m.mutex.RUnlock() | ||
|
||
allHandlers := make([][]Handler, len(m.handlerGroups)) | ||
for idx, num := range m.handlerGroups { | ||
allHandlers[idx] = m.handlers[num] | ||
} | ||
return allHandlers | ||
} |
Oops, something went wrong.