├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── bell.go ├── bell_test.go ├── example_test.go ├── go.mod └── go.sum /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | autotests: 11 | runs-on: ubuntu-latest 12 | container: golang:1.18 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Run autotests 18 | run: | 19 | go test -race -vet=off -cover -coverprofile=coverage.out -covermode=atomic -v -coverpkg=./... -count=1 ./... 20 | go tool cover -func=coverage.out 21 | 22 | - name: Upload coverage to Codecov 23 | uses: codecov/codecov-action@v3 24 | with: 25 | token: ${{ secrets.CI_CODECOV_TOKEN }} 26 | files: ./coverage.out 27 | fail_ci_if_error: true 28 | 29 | code-style: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v2 33 | 34 | - name: Run golangci-lint 35 | uses: golangci/golangci-lint-action@v3 36 | with: 37 | version: v1.46.2 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | gocritic: 3 | enabled-tags: 4 | - diagnostic 5 | - opinionated 6 | - performance 7 | - style 8 | disabled-checks: 9 | - exposedSyncMutex 10 | misspell: 11 | locale: US 12 | 13 | linters: 14 | disable-all: true 15 | enable: 16 | - asciicheck 17 | - bidichk 18 | - bodyclose 19 | - deadcode 20 | - depguard 21 | - dupl 22 | - durationcheck 23 | - errcheck 24 | - errname 25 | - exportloopref 26 | - exhaustive 27 | - funlen 28 | - goconst 29 | - gocritic 30 | - gocyclo 31 | - godox 32 | - gofmt 33 | - goprintffuncname 34 | - gosec 35 | - gosimple 36 | - govet 37 | - ifshort 38 | - importas 39 | - ineffassign 40 | - lll 41 | - makezero 42 | - misspell 43 | - nakedret 44 | - nestif 45 | - nilnil 46 | - nolintlint 47 | - prealloc 48 | - predeclared 49 | - promlinter 50 | - revive 51 | - staticcheck 52 | - structcheck 53 | - stylecheck 54 | - tenv 55 | - thelper 56 | - typecheck 57 | - unconvert 58 | - unparam 59 | - unused 60 | - varcheck 61 | - whitespace 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright Nut.Tech 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bell 2 | 3 | [![GoDoc](https://pkg.go.dev/badge/github.com/nuttech/bell?status.svg)](https://pkg.go.dev/github.com/nuttech/bell/v2?tab=doc) 4 | [![Release](https://img.shields.io/github/release/nuttech/bell.svg?style=flat)](https://github.com/nuttech/bell/releases) 5 | [![codecov](https://codecov.io/gh/NUTtech/bell/branch/master/graph/badge.svg?token=3TMnbQkEny)](https://codecov.io/gh/NUTtech/bell) 6 | [![Tests](https://github.com/NUTtech/bell/actions/workflows/tests.yml/badge.svg)](https://github.com/NUTtech/bell/actions/workflows/tests.yml) 7 | 8 | Bell is the simplest event system written in Go (Golang) which is based on the execution of handlers independent of the 9 | main channel. 10 | 11 | - Written in pure go. Has no third-party libraries. 12 | - Support for custom event data. 13 | - Internally, it launches each handler in a separate goroutine and passes messages to them through channels, the 14 | handlers are executed independently of the main thread. 15 | - Support for adding multiple handlers to an event. 16 | - Complete unit testing. 17 | 18 | Bull does not contain logic for automatic restarts in case of a crash and does not have a persistent execution state store. 19 | 20 | ## Installation 21 | 22 | To install Bell package, you need install [Go](https://golang.org) 23 | with [modules](https://github.com/golang/go/wiki/Modules) support and set Go workspace first. 24 | 25 | 1. Use the below Go command to install Bell: 26 | 27 | ```shell 28 | go get -u github.com/nuttech/bell/v2 29 | ``` 30 | 31 | 2. Import package in your code: 32 | 33 | ```go 34 | import "github.com/nuttech/bell/v2" 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### Adding event listener 40 | 41 | The handler function accepts the Message type as input 42 | 43 | ```go 44 | bell.Listen("event_name", func(message bell.Message) { 45 | // here you must write your handler code 46 | }) 47 | ``` 48 | 49 | `bell.Message` has `interface{}` type and can consist any data. 50 | 51 | You can add more handlers one event: 52 | 53 | ```go 54 | bell.Listen("event_name", func(message bell.Message) { 55 | // first handler 56 | }) 57 | bell.Listen("event_name", func(message bell.Message) { 58 | // second handler 59 | }) 60 | ``` 61 | 62 | You can add a handler with multiple copies for parallel event processing. 63 | 64 | ```go 65 | bell.ListenN("event_name", func(msg bell.Message) { fmt.Println(msg) }, 42) 66 | ``` 67 | 68 | ### Calling an event 69 | 70 | This code call event. Activating handlers, who subscribed on "event_name" event 71 | 72 | ```go 73 | bell.Ring("event_name", "some data") 74 | 75 | bell.Ring("event_name", 1) // int 76 | 77 | bell.Ring("event_name", false) // bool 78 | ``` 79 | 80 | If you passing struct type of data: 81 | 82 | ```go 83 | type userStruct struct { 84 | Name string 85 | } 86 | bell.Ring("event_name", userStruct{Name: "Jon"}) 87 | ``` 88 | 89 | Then parsing the data in the handler may look like this: 90 | 91 | ```go 92 | bell.Listen("event_name", func(message bell.Message) { 93 | user := message.(userStruct) 94 | 95 | fmt.Printf("%#v\n", userStruct{Name: "Jon"}) // main.userStruct{Name:"Jon"} 96 | }) 97 | ``` 98 | 99 | ### Getting events list 100 | 101 | To get a list of events to which handlers are subscribed, call the code: 102 | 103 | ```go 104 | bell.List() 105 | ``` 106 | 107 | ### Checking if exists listeners of event 108 | 109 | You can check the existence of subscribers to an event like this: 110 | 111 | ```go 112 | bell.Has("event_name") 113 | ``` 114 | 115 | ### Removing listeners of event (all events) 116 | 117 | You can delete all listeners or listeners of only one event. 118 | 119 | #### Removing all listeners on all events 120 | 121 | ```go 122 | _ = bell.Remove() 123 | ``` 124 | 125 | #### Removing listeners of only the event "event_name" 126 | 127 | ```go 128 | _ = bell.Remove("event_name") 129 | ``` 130 | 131 | ### Wait until all events finish their work 132 | 133 | ```go 134 | bell.Wait() 135 | ``` 136 | 137 | ### Change events queue size (apply only for new listeners) 138 | 139 | ```go 140 | bell.Queue(42) 141 | ``` 142 | 143 | ### Usage without global state 144 | 145 | You can also use the bell package without using global state. To do this, you need to create a state storage object 146 | and use it. 147 | 148 | ```go 149 | events := bell.New() 150 | events.Listen("event", func(message bell.Message) {}) 151 | _ = events.Ring("event", "Hello bell!") 152 | ``` 153 | 154 | ## Examples 155 | 156 | See full example in [example_test.go](example_test.go). 157 | -------------------------------------------------------------------------------- /bell.go: -------------------------------------------------------------------------------- 1 | // Package bell implements a simple event system (bell ringing and listening) 2 | // 3 | // Several listeners can be added for each ringing (handlerFunc). 4 | // Listeners are called in a separate goroutine through an established channel. 5 | // When the bell rings, a message is sequentially transmitted to each listener. 6 | // 7 | // If a channel is closed, the goroutine for that event is terminated. 8 | // 9 | // Example for usage: 10 | // Listen("event_name", func(message Message) { fmt.PrintLn(message) }) - add listener on bell by name "event_name" 11 | // Ring("event_name", "some_data") - Ring on bell (call event "event_name") 12 | package bell 13 | 14 | import ( 15 | "fmt" 16 | "sync" 17 | ) 18 | 19 | // Message The message that is passed to the event handler 20 | type Message interface{} 21 | 22 | // Events thread safe structure stores events, their handlers and functions for management 23 | type Events struct { 24 | mutex sync.RWMutex 25 | wg sync.WaitGroup 26 | channels map[string][]chan Message 27 | queueSize uint 28 | } 29 | 30 | // New constructor for Events 31 | func New() *Events { 32 | return &Events{channels: map[string][]chan Message{}} 33 | } 34 | 35 | // Queue set events queue size 36 | func (e *Events) Queue(size uint) *Events { 37 | e.mutex.Lock() 38 | defer e.mutex.Unlock() 39 | 40 | e.queueSize = size 41 | return e 42 | } 43 | 44 | // ListenN Subscribe on event where 45 | // event - the event name, 46 | // handlerFunc - handler function 47 | // copiesCount - count handlers copies run 48 | func (e *Events) ListenN(event string, handlerFunc func(message Message), copiesCount uint) { 49 | e.mutex.Lock() 50 | defer e.mutex.Unlock() 51 | 52 | channel := make(chan Message, e.queueSize) 53 | 54 | for i := uint(0); i < copiesCount; i++ { 55 | go func(c chan Message, wg *sync.WaitGroup) { 56 | for { 57 | message, ok := <-c 58 | if !ok { 59 | break 60 | } 61 | handlerFunc(message) 62 | wg.Done() 63 | } 64 | }(channel, &e.wg) 65 | } 66 | 67 | e.channels[event] = append(e.channels[event], channel) 68 | } 69 | 70 | // Listen Subscribe on event where 71 | // event - the event name, 72 | // handlerFunc - handler function 73 | func (e *Events) Listen(event string, handlerFunc func(message Message)) { 74 | e.ListenN(event, handlerFunc, 1) 75 | } 76 | 77 | // Ring Call event there 78 | // event - event name 79 | // message - data that will be passed to the event handler 80 | func (e *Events) Ring(event string, message Message) error { 81 | e.mutex.RLock() 82 | defer e.mutex.RUnlock() 83 | 84 | if _, ok := e.channels[event]; !ok { 85 | return fmt.Errorf("channel %s not found", event) 86 | } 87 | 88 | for _, c := range e.channels[event] { 89 | e.wg.Add(1) 90 | c <- message 91 | } 92 | return nil 93 | } 94 | 95 | // Has Checks if there are listeners for the passed event 96 | func (e *Events) Has(event string) bool { 97 | e.mutex.RLock() 98 | defer e.mutex.RUnlock() 99 | 100 | _, ok := e.channels[event] 101 | return ok 102 | } 103 | 104 | // List Returns a list of events that listeners are subscribed to 105 | func (e *Events) List() []string { 106 | e.mutex.RLock() 107 | defer e.mutex.RUnlock() 108 | 109 | list := make([]string, 0, len(e.channels)) 110 | for event := range e.channels { 111 | list = append(list, event) 112 | } 113 | return list 114 | } 115 | 116 | // Remove Removes listeners by event name 117 | // Removing listeners closes channels and stops the goroutine. 118 | // 119 | // If you call the function without the "names" parameter, all listeners of all events will be removed. 120 | func (e *Events) Remove(names ...string) { 121 | e.mutex.Lock() 122 | defer e.mutex.Unlock() 123 | 124 | if len(names) == 0 { 125 | keys := make([]string, 0, len(e.channels)) 126 | for k := range e.channels { 127 | keys = append(keys, k) 128 | } 129 | 130 | names = keys 131 | } 132 | 133 | for _, name := range names { 134 | for _, channel := range e.channels[name] { 135 | close(channel) 136 | } 137 | 138 | delete(e.channels, name) 139 | } 140 | } 141 | 142 | // Wait Blocks the thread until all running events are completed 143 | func (e *Events) Wait() { 144 | e.mutex.Lock() 145 | defer e.mutex.Unlock() 146 | 147 | e.wg.Wait() 148 | } 149 | 150 | // globalState store of global event handlers 151 | var globalState = New() 152 | 153 | // ListenN Subscribe on event where 154 | // event - the event name, 155 | // handlerFunc - handler function 156 | // copiesCount - count handlers copies run 157 | func ListenN(event string, handlerFunc func(message Message), copiesCount uint) { 158 | globalState.ListenN(event, handlerFunc, copiesCount) 159 | } 160 | 161 | // Listen Subscribe on event where 162 | // event - the event name, 163 | // handlerFunc - handler function 164 | func Listen(event string, handlerFunc func(message Message)) { 165 | globalState.Listen(event, handlerFunc) 166 | } 167 | 168 | // Ring Call event there 169 | // event - event name 170 | // message - data that will be passed to the event handler 171 | func Ring(event string, message Message) error { 172 | return globalState.Ring(event, message) 173 | } 174 | 175 | // Has Checks if there are listeners for the passed event 176 | func Has(event string) bool { 177 | return globalState.Has(event) 178 | } 179 | 180 | // List Returns a list of events that listeners are subscribed to 181 | func List() []string { 182 | return globalState.List() 183 | } 184 | 185 | // Remove Removes listeners by event name 186 | // Removing listeners closes channels and stops the goroutine. 187 | // 188 | // If you call the function without the "names" parameter, all listeners of all events will be removed. 189 | func Remove(names ...string) { 190 | globalState.Remove(names...) 191 | } 192 | 193 | // Wait Blocks the thread until all running events are completed 194 | func Wait() { 195 | globalState.Wait() 196 | } 197 | 198 | // Queue set events queue size 199 | func Queue(size uint) { 200 | globalState.Queue(size) 201 | } 202 | -------------------------------------------------------------------------------- /bell_test.go: -------------------------------------------------------------------------------- 1 | package bell 2 | 3 | import ( 4 | "sort" 5 | "sync/atomic" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // resetSystem Clearing the State Store of Event Listeners 11 | func resetSystem() { 12 | for k := range globalState.channels { 13 | for _, channel := range globalState.channels[k] { 14 | close(channel) 15 | } 16 | } 17 | globalState = &Events{channels: map[string][]chan Message{}} 18 | } 19 | 20 | func assertNoError(t *testing.T, err error) { 21 | t.Helper() 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | } 26 | 27 | func assertTrue(t *testing.T, v bool) { 28 | t.Helper() 29 | if v != true { 30 | t.Error("Value must be true") 31 | } 32 | } 33 | 34 | // TestListenN checking the function of adding multiple copies of event listeners 35 | func TestListenN(t *testing.T) { 36 | defer resetSystem() 37 | 38 | eventName := "event" 39 | var wasRunning int32 40 | ListenN(eventName, func(Message) { atomic.AddInt32(&wasRunning, 1) }, 3) 41 | 42 | assertNoError(t, Ring(eventName, nil)) 43 | Wait() 44 | 45 | assertTrue(t, wasRunning == 1) 46 | 47 | assertNoError(t, Ring(eventName, nil)) 48 | assertNoError(t, Ring(eventName, nil)) 49 | Wait() 50 | 51 | assertTrue(t, wasRunning == 3) 52 | } 53 | 54 | // TestListen Testing the function of adding event listeners 55 | func TestListen(t *testing.T) { 56 | defer resetSystem() 57 | 58 | expMessageEvent := "test_event" 59 | expMessageValue := "value" 60 | 61 | Listen(expMessageEvent, func(message Message) { 62 | assertTrue(t, expMessageValue == message) 63 | }) 64 | 65 | assertTrue(t, len(globalState.channels) == 1) 66 | assertTrue(t, len(globalState.channels[expMessageEvent]) == 1) 67 | 68 | assertNoError(t, Ring(expMessageEvent, expMessageValue)) 69 | } 70 | 71 | // TestRing_Fail Checking the correctness of error handling in case of an erroneous ringing 72 | func TestRing_Fail(t *testing.T) { 73 | defer resetSystem() 74 | 75 | err := Ring("undefined_event", func() {}) 76 | assertTrue(t, err.Error() == "channel undefined_event not found") 77 | } 78 | 79 | // TestRemove Checking if event handlers are removed from storage 80 | func TestRemove(t *testing.T) { 81 | defer resetSystem() 82 | 83 | globalState.channels["test"] = append(globalState.channels["test"], make(chan Message), make(chan Message)) 84 | globalState.channels["test2"] = append(globalState.channels["test2"], make(chan Message)) 85 | 86 | Remove("test") 87 | assertTrue(t, len(globalState.channels) == 1) 88 | 89 | globalState.channels["test3"] = append(globalState.channels["test3"], make(chan Message)) 90 | globalState.channels["test4"] = append(globalState.channels["test4"], make(chan Message)) 91 | Remove("test2") 92 | assertTrue(t, len(globalState.channels) == 2) 93 | 94 | globalState.channels["test3"] = append(globalState.channels["test3"], make(chan Message)) 95 | globalState.channels["test4"] = append(globalState.channels["test4"], make(chan Message)) 96 | Remove() 97 | assertTrue(t, len(globalState.channels) == 0) 98 | } 99 | 100 | // TestHas Checking the Correctness of Determining the Existence of Event Listeners 101 | func TestHas(t *testing.T) { 102 | defer resetSystem() 103 | 104 | assertTrue(t, !Has("test")) 105 | 106 | globalState.channels["test"] = append(globalState.channels["test"], make(chan Message)) 107 | assertTrue(t, Has("test")) 108 | } 109 | 110 | // TestList Checking the correct receipt of the list of events on which handlers are installed 111 | func TestList(t *testing.T) { 112 | defer resetSystem() 113 | 114 | assertTrue(t, len(List()) == 0) 115 | 116 | globalState.channels["test"] = append(globalState.channels["test"], make(chan Message), make(chan Message)) 117 | globalState.channels["test2"] = append(globalState.channels["test2"], make(chan Message)) 118 | 119 | actualList := List() 120 | sort.Strings(actualList) 121 | 122 | assertTrue(t, len(actualList) == 2) 123 | assertTrue(t, actualList[0] == "test") 124 | assertTrue(t, actualList[1] == "test2") 125 | } 126 | 127 | // TestWait Checking Wait function 128 | func TestWait(t *testing.T) { 129 | defer resetSystem() 130 | 131 | eventName := "test" 132 | var wasRunning int32 133 | 134 | Listen(eventName, func(Message) { 135 | time.Sleep(time.Millisecond) 136 | atomic.StoreInt32(&wasRunning, 1) 137 | }) 138 | assertNoError(t, Ring(eventName, nil)) 139 | 140 | Wait() 141 | 142 | assertTrue(t, wasRunning == 1) 143 | } 144 | 145 | // TestQueue checking function for set queue size 146 | func TestQueue(t *testing.T) { 147 | defer resetSystem() 148 | 149 | var size uint = 6 150 | Queue(size) 151 | assertTrue(t, size == globalState.queueSize) 152 | } 153 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package bell_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/nuttech/bell/v2" 7 | "sort" 8 | "time" 9 | ) 10 | 11 | type CustomStruct struct { 12 | name string 13 | param int32 14 | } 15 | 16 | func Example() { 17 | // Use via global state 18 | 19 | event := "event_name" 20 | event2 := "event_name_2" 21 | 22 | // add listener on event event_name 23 | bell.Listen(event, func(message bell.Message) { 24 | // we extend CustomStruct in message 25 | customStruct := message.(CustomStruct) 26 | fmt.Println(customStruct) 27 | }) 28 | // add listener on event event_name_2 29 | bell.Listen(event2, func(message bell.Message) {}) 30 | 31 | // get event list 32 | list := bell.List() 33 | 34 | // only for test 35 | sort.Strings(list) 36 | fmt.Println(list) 37 | 38 | // remove listeners on event_name_2 39 | bell.Remove(event2) 40 | 41 | // get event list again 42 | fmt.Println(bell.List()) 43 | 44 | // check if exists event_name_2 event in storage 45 | fmt.Println(bell.Has(event2)) 46 | 47 | // call event event_name 48 | _ = bell.Ring(event, CustomStruct{name: "testName", param: 12}) 49 | 50 | // wait until the event completes its work 51 | bell.Wait() 52 | 53 | // Output: 54 | // [event_name event_name_2] 55 | // [event_name] 56 | // false 57 | // {testName 12} 58 | } 59 | 60 | func ExampleEvents() { 61 | // Use events object (without global state) 62 | 63 | eventName := "event_name" 64 | 65 | // make a new events store 66 | events := bell.New() 67 | 68 | // add listener on event 69 | events.Listen(eventName, func(msg bell.Message) { fmt.Println(msg) }) 70 | 71 | // call event event_name 72 | _ = events.Ring(eventName, "Hello bell!") 73 | 74 | // wait until the event completes its work 75 | events.Wait() 76 | 77 | // Output: 78 | // Hello bell! 79 | } 80 | 81 | func Example_usingContext() { 82 | // Use bell with context 83 | 84 | // create a custom struct for pass a context 85 | type Custom struct { 86 | ctx context.Context 87 | value interface{} 88 | } 89 | 90 | // add listener 91 | bell.Listen("event", func(message bell.Message) { 92 | for iterationsCount := 1; true; iterationsCount++ { 93 | select { 94 | case <-message.(*Custom).ctx.Done(): 95 | return 96 | default: 97 | fmt.Printf("Iteration #%d\n", iterationsCount) 98 | time.Sleep(10 * time.Second) 99 | } 100 | } 101 | }) 102 | 103 | // create a global context for all calls 104 | globalCtx, cancelGlobalCtx := context.WithCancel(context.Background()) 105 | 106 | // create a children context for a call with timeout 107 | ringCtx, ringCancel := context.WithTimeout(globalCtx, time.Minute) 108 | defer ringCancel() 109 | 110 | _ = bell.Ring("event", &Custom{ringCtx, "value"}) 111 | 112 | // wait a second for the handler to perform one iteration 113 | time.Sleep(time.Second) 114 | 115 | // interrupt all handlers 116 | cancelGlobalCtx() 117 | 118 | // Output: 119 | // Iteration #1 120 | } 121 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nuttech/bell/v2 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NUTtech/bell/1301322add145f01cc46ac892aba3b9f39af003a/go.sum --------------------------------------------------------------------------------