github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/plugin_deadlock_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "os" 8 "strings" 9 "testing" 10 "text/template" 11 "time" 12 13 "github.com/stretchr/testify/require" 14 15 "github.com/mattermost/mattermost-server/v5/model" 16 ) 17 18 func TestPluginDeadlock(t *testing.T) { 19 t.Run("Single Plugin", func(t *testing.T) { 20 th := Setup(t).InitBasic() 21 defer th.TearDown() 22 23 pluginPostOnActivate := template.Must(template.New("pluginPostOnActivate").Parse(` 24 package main 25 26 import ( 27 "github.com/mattermost/mattermost-server/v5/plugin" 28 "github.com/mattermost/mattermost-server/v5/model" 29 ) 30 31 type MyPlugin struct { 32 plugin.MattermostPlugin 33 } 34 35 func (p *MyPlugin) OnActivate() error { 36 _, err := p.API.CreatePost(&model.Post{ 37 UserId: "{{.User.Id}}", 38 ChannelId: "{{.Channel.Id}}", 39 Message: "message", 40 }) 41 if err != nil { 42 panic(err.Error()) 43 } 44 45 return nil 46 } 47 48 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 49 if _, from_plugin := post.GetProps()["from_plugin"]; from_plugin { 50 return nil, "" 51 } 52 53 p.API.CreatePost(&model.Post{ 54 UserId: "{{.User.Id}}", 55 ChannelId: "{{.Channel.Id}}", 56 Message: "message", 57 Props: map[string]interface{}{ 58 "from_plugin": true, 59 }, 60 }) 61 62 return nil, "" 63 } 64 65 func main() { 66 plugin.ClientMain(&MyPlugin{}) 67 } 68 `, 69 )) 70 71 templateData := struct { 72 User *model.User 73 Channel *model.Channel 74 }{ 75 th.BasicUser, 76 th.BasicChannel, 77 } 78 79 plugins := []string{} 80 pluginTemplates := []*template.Template{ 81 pluginPostOnActivate, 82 } 83 for _, pluginTemplate := range pluginTemplates { 84 b := &strings.Builder{} 85 pluginTemplate.Execute(b, templateData) 86 87 plugins = append(plugins, b.String()) 88 } 89 90 done := make(chan bool) 91 go func() { 92 SetAppEnvironmentWithPlugins(t, plugins, th.App, th.App.NewPluginAPI) 93 close(done) 94 }() 95 96 select { 97 case <-done: 98 case <-time.After(30 * time.Second): 99 require.Fail(t, "plugin failed to activate: likely deadlocked") 100 go func() { 101 time.Sleep(5 * time.Second) 102 os.Exit(1) 103 }() 104 } 105 }) 106 107 t.Run("Multiple Plugins", func(t *testing.T) { 108 th := Setup(t).InitBasic() 109 defer th.TearDown() 110 111 pluginPostOnHasBeenPosted := template.Must(template.New("pluginPostOnHasBeenPosted").Parse(` 112 package main 113 114 import ( 115 "github.com/mattermost/mattermost-server/v5/plugin" 116 "github.com/mattermost/mattermost-server/v5/model" 117 ) 118 119 type MyPlugin struct { 120 plugin.MattermostPlugin 121 } 122 123 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 124 if _, from_plugin := post.GetProps()["from_plugin"]; from_plugin { 125 return nil, "" 126 } 127 128 p.API.CreatePost(&model.Post{ 129 UserId: "{{.User.Id}}", 130 ChannelId: "{{.Channel.Id}}", 131 Message: "message", 132 Props: map[string]interface{}{ 133 "from_plugin": true, 134 }, 135 }) 136 137 return nil, "" 138 } 139 140 func main() { 141 plugin.ClientMain(&MyPlugin{}) 142 } 143 `, 144 )) 145 146 pluginPostOnActivate := template.Must(template.New("pluginPostOnActivate").Parse(` 147 package main 148 149 import ( 150 "github.com/mattermost/mattermost-server/v5/plugin" 151 "github.com/mattermost/mattermost-server/v5/model" 152 ) 153 154 type MyPlugin struct { 155 plugin.MattermostPlugin 156 } 157 158 func (p *MyPlugin) OnActivate() error { 159 _, err := p.API.CreatePost(&model.Post{ 160 UserId: "{{.User.Id}}", 161 ChannelId: "{{.Channel.Id}}", 162 Message: "message", 163 }) 164 if err != nil { 165 panic(err.Error()) 166 } 167 168 return nil 169 } 170 171 func main() { 172 plugin.ClientMain(&MyPlugin{}) 173 } 174 `, 175 )) 176 177 templateData := struct { 178 User *model.User 179 Channel *model.Channel 180 }{ 181 th.BasicUser, 182 th.BasicChannel, 183 } 184 185 plugins := []string{} 186 pluginTemplates := []*template.Template{ 187 pluginPostOnHasBeenPosted, 188 pluginPostOnActivate, 189 } 190 for _, pluginTemplate := range pluginTemplates { 191 b := &strings.Builder{} 192 pluginTemplate.Execute(b, templateData) 193 194 plugins = append(plugins, b.String()) 195 } 196 197 done := make(chan bool) 198 go func() { 199 SetAppEnvironmentWithPlugins(t, plugins, th.App, th.App.NewPluginAPI) 200 close(done) 201 }() 202 203 select { 204 case <-done: 205 case <-time.After(30 * time.Second): 206 require.Fail(t, "plugin failed to activate: likely deadlocked") 207 go func() { 208 time.Sleep(5 * time.Second) 209 os.Exit(1) 210 }() 211 } 212 }) 213 214 t.Run("CreatePost on OnDeactivate Plugin", func(t *testing.T) { 215 th := Setup(t).InitBasic() 216 217 pluginPostOnActivate := template.Must(template.New("pluginPostOnActivate").Parse(` 218 package main 219 220 import ( 221 "github.com/mattermost/mattermost-server/v5/plugin" 222 "github.com/mattermost/mattermost-server/v5/model" 223 ) 224 225 type MyPlugin struct { 226 plugin.MattermostPlugin 227 } 228 229 func (p *MyPlugin) OnDeactivate() error { 230 _, err := p.API.CreatePost(&model.Post{ 231 UserId: "{{.User.Id}}", 232 ChannelId: "{{.Channel.Id}}", 233 Message: "OnDeactivate", 234 }) 235 if err != nil { 236 panic(err.Error()) 237 } 238 239 return nil 240 } 241 242 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 243 updatedPost := &model.Post{ 244 UserId: "{{.User.Id}}", 245 ChannelId: "{{.Channel.Id}}", 246 Message: "messageUpdated", 247 Props: map[string]interface{}{ 248 "from_plugin": true, 249 }, 250 } 251 252 return updatedPost, "" 253 } 254 255 func main() { 256 plugin.ClientMain(&MyPlugin{}) 257 } 258 `, 259 )) 260 261 templateData := struct { 262 User *model.User 263 Channel *model.Channel 264 }{ 265 th.BasicUser, 266 th.BasicChannel, 267 } 268 269 plugins := []string{} 270 pluginTemplates := []*template.Template{ 271 pluginPostOnActivate, 272 } 273 for _, pluginTemplate := range pluginTemplates { 274 b := &strings.Builder{} 275 pluginTemplate.Execute(b, templateData) 276 277 plugins = append(plugins, b.String()) 278 } 279 280 done := make(chan bool) 281 go func() { 282 posts, appErr := th.App.GetPosts(th.BasicChannel.Id, 0, 2) 283 require.Nil(t, appErr) 284 require.NotNil(t, posts) 285 286 messageWillBePostedCalled := false 287 for _, p := range posts.Posts { 288 if p.Message == "messageUpdated" { 289 messageWillBePostedCalled = true 290 } 291 } 292 require.False(t, messageWillBePostedCalled, "MessageWillBePosted should not have been called") 293 294 SetAppEnvironmentWithPlugins(t, plugins, th.App, th.App.NewPluginAPI) 295 th.TearDown() 296 297 posts, appErr = th.App.GetPosts(th.BasicChannel.Id, 0, 2) 298 require.Nil(t, appErr) 299 require.NotNil(t, posts) 300 301 messageWillBePostedCalled = false 302 for _, p := range posts.Posts { 303 if p.Message == "messageUpdated" { 304 messageWillBePostedCalled = true 305 } 306 } 307 require.True(t, messageWillBePostedCalled, "MessageWillBePosted was not called on deactivate") 308 close(done) 309 }() 310 311 select { 312 case <-done: 313 case <-time.After(30 * time.Second): 314 require.Fail(t, "plugin failed to activate: likely deadlocked") 315 go func() { 316 time.Sleep(5 * time.Second) 317 os.Exit(1) 318 }() 319 } 320 }) 321 }