github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+incompatible/plugin/rpcplugin/rpcplugintest/supervisor.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package rpcplugintest
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/mock"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/mattermost/mattermost-server/model"
    20  	"github.com/mattermost/mattermost-server/plugin"
    21  	"github.com/mattermost/mattermost-server/plugin/plugintest"
    22  )
    23  
    24  type SupervisorProviderFunc = func(*model.BundleInfo) (plugin.Supervisor, error)
    25  
    26  func TestSupervisorProvider(t *testing.T, sp SupervisorProviderFunc) {
    27  	for name, f := range map[string]func(*testing.T, SupervisorProviderFunc){
    28  		"Supervisor":                           testSupervisor,
    29  		"Supervisor_InvalidExecutablePath":     testSupervisor_InvalidExecutablePath,
    30  		"Supervisor_NonExistentExecutablePath": testSupervisor_NonExistentExecutablePath,
    31  		"Supervisor_StartTimeout":              testSupervisor_StartTimeout,
    32  		"Supervisor_PluginCrash":               testSupervisor_PluginCrash,
    33  	} {
    34  		t.Run(name, func(t *testing.T) { f(t, sp) })
    35  	}
    36  }
    37  
    38  func testSupervisor(t *testing.T, sp SupervisorProviderFunc) {
    39  	dir, err := ioutil.TempDir("", "")
    40  	require.NoError(t, err)
    41  	defer os.RemoveAll(dir)
    42  
    43  	backend := filepath.Join(dir, "backend.exe")
    44  	CompileGo(t, `
    45  		package main
    46  
    47  		import (
    48  			"github.com/mattermost/mattermost-server/plugin/rpcplugin"
    49  		)
    50  
    51  		type MyPlugin struct {}
    52  
    53  		func main() {
    54  			rpcplugin.Main(&MyPlugin{})
    55  		}
    56  	`, backend)
    57  
    58  	ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
    59  
    60  	bundle := model.BundleInfoForPath(dir)
    61  	supervisor, err := sp(bundle)
    62  	require.NoError(t, err)
    63  	require.NoError(t, supervisor.Start(nil))
    64  	require.NoError(t, supervisor.Stop())
    65  }
    66  
    67  func testSupervisor_InvalidExecutablePath(t *testing.T, sp SupervisorProviderFunc) {
    68  	dir, err := ioutil.TempDir("", "")
    69  	require.NoError(t, err)
    70  	defer os.RemoveAll(dir)
    71  
    72  	ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "/foo/../../backend.exe"}}`), 0600)
    73  
    74  	bundle := model.BundleInfoForPath(dir)
    75  	supervisor, err := sp(bundle)
    76  	assert.Nil(t, supervisor)
    77  	assert.Error(t, err)
    78  }
    79  
    80  func testSupervisor_NonExistentExecutablePath(t *testing.T, sp SupervisorProviderFunc) {
    81  	dir, err := ioutil.TempDir("", "")
    82  	require.NoError(t, err)
    83  	defer os.RemoveAll(dir)
    84  
    85  	ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "thisfileshouldnotexist"}}`), 0600)
    86  
    87  	bundle := model.BundleInfoForPath(dir)
    88  	supervisor, err := sp(bundle)
    89  	require.NotNil(t, supervisor)
    90  	require.NoError(t, err)
    91  
    92  	require.Error(t, supervisor.Start(nil))
    93  }
    94  
    95  // If plugin development goes really wrong, let's make sure plugin activation won't block forever.
    96  func testSupervisor_StartTimeout(t *testing.T, sp SupervisorProviderFunc) {
    97  	dir, err := ioutil.TempDir("", "")
    98  	require.NoError(t, err)
    99  	defer os.RemoveAll(dir)
   100  
   101  	backend := filepath.Join(dir, "backend.exe")
   102  	CompileGo(t, `
   103  		package main
   104  
   105  		func main() {
   106  			for {
   107  			}
   108  		}
   109  	`, backend)
   110  
   111  	ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
   112  
   113  	bundle := model.BundleInfoForPath(dir)
   114  	supervisor, err := sp(bundle)
   115  	require.NoError(t, err)
   116  	require.Error(t, supervisor.Start(nil))
   117  }
   118  
   119  // Crashed plugins should be relaunched.
   120  func testSupervisor_PluginCrash(t *testing.T, sp SupervisorProviderFunc) {
   121  	dir, err := ioutil.TempDir("", "")
   122  	require.NoError(t, err)
   123  	defer os.RemoveAll(dir)
   124  
   125  	backend := filepath.Join(dir, "backend.exe")
   126  	CompileGo(t, `
   127  		package main
   128  
   129  		import (
   130  			"os"
   131  
   132  			"github.com/mattermost/mattermost-server/plugin"
   133  			"github.com/mattermost/mattermost-server/plugin/rpcplugin"
   134  		)
   135  
   136  		type Configuration struct {
   137  			ShouldExit bool
   138  		}
   139  
   140  		type MyPlugin struct {
   141  			config Configuration
   142  		}
   143  
   144  		func (p *MyPlugin) OnActivate(api plugin.API) error {
   145  			api.LoadPluginConfiguration(&p.config)
   146  			return nil
   147  		}
   148  
   149  		func (p *MyPlugin) OnDeactivate() error {
   150  			if p.config.ShouldExit {
   151  				os.Exit(1)
   152  			}
   153  			return nil
   154  		}
   155  
   156  		func main() {
   157  			rpcplugin.Main(&MyPlugin{})
   158  		}
   159  	`, backend)
   160  
   161  	ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
   162  
   163  	var api plugintest.API
   164  	shouldExit := true
   165  	api.On("LoadPluginConfiguration", mock.MatchedBy(func(x interface{}) bool { return true })).Return(func(dest interface{}) error {
   166  		err := json.Unmarshal([]byte(fmt.Sprintf(`{"ShouldExit": %v}`, shouldExit)), dest)
   167  		shouldExit = false
   168  		return err
   169  	})
   170  
   171  	bundle := model.BundleInfoForPath(dir)
   172  	supervisor, err := sp(bundle)
   173  	require.NoError(t, err)
   174  	require.NoError(t, supervisor.Start(&api))
   175  
   176  	failed := false
   177  	recovered := false
   178  	for i := 0; i < 30; i++ {
   179  		if supervisor.Hooks().OnDeactivate() == nil {
   180  			require.True(t, failed)
   181  			recovered = true
   182  			break
   183  		} else {
   184  			failed = true
   185  		}
   186  		time.Sleep(time.Millisecond * 100)
   187  	}
   188  	assert.True(t, recovered)
   189  	require.NoError(t, supervisor.Stop())
   190  }