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 }