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  }