github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/plugin_test.go (about) 1 package plugin_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "path/filepath" 9 "runtime" 10 "strconv" 11 "testing" 12 13 "github.com/evergreen-ci/evergreen/agent/comm" 14 agentutil "github.com/evergreen-ci/evergreen/agent/testutil" 15 "github.com/evergreen-ci/evergreen/db" 16 "github.com/evergreen-ci/evergreen/model" 17 "github.com/evergreen-ci/evergreen/model/task" 18 modelutil "github.com/evergreen-ci/evergreen/model/testutil" 19 "github.com/evergreen-ci/evergreen/plugin" 20 "github.com/evergreen-ci/evergreen/plugin/builtin/expansions" 21 "github.com/evergreen-ci/evergreen/plugin/builtin/shell" 22 _ "github.com/evergreen-ci/evergreen/plugin/config" 23 "github.com/evergreen-ci/evergreen/plugin/plugintest" 24 "github.com/evergreen-ci/evergreen/service" 25 "github.com/evergreen-ci/evergreen/testutil" 26 "github.com/evergreen-ci/evergreen/util" 27 "github.com/gorilla/mux" 28 "github.com/mitchellh/mapstructure" 29 "github.com/mongodb/grip/slogger" 30 . "github.com/smartystreets/goconvey/convey" 31 "gopkg.in/yaml.v2" 32 ) 33 34 type MockPlugin struct{} 35 36 func init() { 37 db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(testutil.TestConfig())) 38 } 39 40 func MockPluginEcho(w http.ResponseWriter, request *http.Request) { 41 arg1 := mux.Vars(request)["param1"] 42 arg2, err := strconv.Atoi(mux.Vars(request)["param2"]) 43 if err != nil { 44 http.Error(w, "bad val for param2", http.StatusBadRequest) 45 return 46 } 47 48 newTask := plugin.GetTask(request) 49 if newTask != nil { 50 //task should have been populated for us, by the API server 51 plugin.WriteJSON(w, http.StatusOK, map[string]string{ 52 "echo": fmt.Sprintf("%v/%v/%v", arg1, arg2, newTask.Id), 53 }) 54 return 55 } 56 http.Error(w, "couldn't get task from context", http.StatusInternalServerError) 57 } 58 59 func (mp *MockPlugin) Configure(conf map[string]interface{}) error { 60 return nil 61 } 62 63 func (mp *MockPlugin) GetAPIHandler() http.Handler { 64 r := mux.NewRouter() 65 r.Path("/blah/{param1}/{param2}").Methods("GET").HandlerFunc(MockPluginEcho) 66 return r 67 } 68 69 func (mp *MockPlugin) GetUIHandler() http.Handler { 70 return nil 71 } 72 73 func (mp *MockPlugin) GetPanelConfig() (*plugin.PanelConfig, error) { 74 return nil, nil 75 } 76 77 func (mp *MockPlugin) Name() string { 78 return "mock" 79 } 80 81 func (mp *MockPlugin) NewCommand(commandName string) (plugin.Command, error) { 82 if commandName != "foo" { 83 return nil, &plugin.ErrUnknownCommand{commandName} 84 } 85 return &MockCommand{}, nil 86 } 87 88 type MockCommand struct { 89 Param1 string 90 Param2 int64 91 } 92 93 func (mc *MockCommand) Name() string { 94 return "mock" 95 } 96 97 func (mc *MockCommand) Plugin() string { 98 return "mock" 99 } 100 101 func (mc *MockCommand) ParseParams(params map[string]interface{}) error { 102 err := mapstructure.Decode(params, mc) 103 if err != nil { 104 return err 105 } 106 if mc.Param1 == "" { 107 return fmt.Errorf("Param1 must be a non-blank string.") 108 } 109 if mc.Param2 == 0 { 110 return fmt.Errorf("Param2 must be a non-zero integer.") 111 } 112 return nil 113 } 114 115 func (mc *MockCommand) Execute(logger plugin.Logger, 116 pluginCom plugin.PluginCommunicator, conf *model.TaskConfig, stop chan bool) error { 117 resp, err := pluginCom.TaskGetJSON(fmt.Sprintf("blah/%s/%d", mc.Param1, mc.Param2)) 118 if err != nil { 119 return err 120 } 121 122 if resp != nil { 123 defer resp.Body.Close() 124 } 125 126 if resp == nil { 127 return fmt.Errorf("Received nil HTTP response from api server") 128 } 129 130 jsonReply := map[string]string{} 131 err = util.ReadJSONInto(resp.Body, &jsonReply) 132 if err != nil { 133 return err 134 } 135 136 if resp.StatusCode != http.StatusOK { 137 return fmt.Errorf("Got bad status code from API response: %v, body: %v", resp.StatusCode, jsonReply) 138 } 139 140 expectedEchoReply := fmt.Sprintf("%v/%v/%v", mc.Param1, mc.Param2, conf.Task.Id) 141 if jsonReply["echo"] != expectedEchoReply { 142 return fmt.Errorf("Wrong echo reply! Wanted %v, got %v", expectedEchoReply, jsonReply["echo"]) 143 } 144 return nil 145 } 146 147 func TestRegistry(t *testing.T) { 148 Convey("With a SimpleRegistry", t, func() { 149 Convey("Registering a plugin twice should return err", func() { 150 registry := plugin.NewSimpleRegistry() 151 err := registry.Register(&MockPlugin{}) 152 testutil.HandleTestingErr(err, t, "Couldn't register plugin") 153 err = registry.Register(&shell.ShellPlugin{}) 154 testutil.HandleTestingErr(err, t, "Couldn't register plugin") 155 err = registry.Register(&expansions.ExpansionsPlugin{}) 156 testutil.HandleTestingErr(err, t, "Couldn't register plugin") 157 }) 158 Convey("with a project file containing references to a valid plugin", func() { 159 registry := plugin.NewSimpleRegistry() 160 So(registry.Register(&MockPlugin{}), ShouldBeNil) 161 So(registry.Register(&shell.ShellPlugin{}), ShouldBeNil) 162 So(registry.Register(&expansions.ExpansionsPlugin{}), ShouldBeNil) 163 164 data, err := ioutil.ReadFile(filepath.Join(testutil.GetDirectoryOfFile(), 165 "testdata", "mongodb-mongo-master.yml")) 166 testutil.HandleTestingErr(err, t, "failed to load test yaml file") 167 project := &model.Project{} 168 So(yaml.Unmarshal(data, project), ShouldBeNil) 169 Convey("all commands in project file should load parse successfully", func() { 170 for _, newTask := range project.Tasks { 171 for _, command := range newTask.Commands { 172 pluginCmds, err := registry.GetCommands(command, project.Functions) 173 testutil.HandleTestingErr(err, t, "Got error getting plugin commands: %v") 174 So(pluginCmds, ShouldNotBeNil) 175 So(err, ShouldBeNil) 176 } 177 } 178 }) 179 }) 180 }) 181 } 182 183 func TestPluginFunctions(t *testing.T) { 184 testConfig := testutil.TestConfig() 185 testutil.ConfigureIntegrationTest(t, testConfig, "TestPatchTask") 186 Convey("With a SimpleRegistry", t, func() { 187 Convey("with a project file containing functions", func() { 188 registry := plugin.NewSimpleRegistry() 189 err := registry.Register(&shell.ShellPlugin{}) 190 testutil.HandleTestingErr(err, t, "Couldn't register plugin") 191 err = registry.Register(&expansions.ExpansionsPlugin{}) 192 testutil.HandleTestingErr(err, t, "Couldn't register plugin") 193 194 testServer, err := service.CreateTestServer(testConfig, nil, plugin.APIPlugins) 195 testutil.HandleTestingErr(err, t, "Couldn't set up testing server") 196 defer testServer.Close() 197 pluginfile := filepath.Join(testutil.GetDirectoryOfFile(), 198 "testdata", "plugin_project_functions.yml") 199 modelData, err := modelutil.SetupAPITestData(testConfig, "test", "rhel55", pluginfile, modelutil.NoPatch) 200 testutil.HandleTestingErr(err, t, "failed to setup test data") 201 httpCom := plugintest.TestAgentCommunicator(modelData, testServer.URL) 202 taskConfig := modelData.TaskConfig 203 204 Convey("all commands in project file should parse successfully", func() { 205 for _, newTask := range taskConfig.Project.Tasks { 206 for _, command := range newTask.Commands { 207 pluginCmd, err := registry.GetCommands(command, taskConfig.Project.Functions) 208 testutil.HandleTestingErr(err, t, "Got error getting plugin command: %v") 209 So(pluginCmd, ShouldNotBeNil) 210 So(err, ShouldBeNil) 211 } 212 } 213 }) 214 215 Convey("all commands in test project should execute successfully", func() { 216 logger := agentutil.NewTestLogger(slogger.StdOutAppender()) 217 for _, newTask := range taskConfig.Project.Tasks { 218 So(len(newTask.Commands), ShouldNotEqual, 0) 219 for _, command := range newTask.Commands { 220 pluginCmds, err := registry.GetCommands(command, taskConfig.Project.Functions) 221 testutil.HandleTestingErr(err, t, "Couldn't get plugin command: %v") 222 So(pluginCmds, ShouldNotBeNil) 223 So(err, ShouldBeNil) 224 So(len(pluginCmds), ShouldEqual, 1) 225 cmd := pluginCmds[0] 226 pluginCom := &comm.TaskJSONCommunicator{cmd.Plugin(), httpCom} 227 err = cmd.Execute(logger, pluginCom, taskConfig, make(chan bool)) 228 So(err, ShouldBeNil) 229 } 230 } 231 }) 232 }) 233 }) 234 } 235 236 func TestPluginExecution(t *testing.T) { 237 Convey("With a SimpleRegistry and test project file", t, func() { 238 registry := plugin.NewSimpleRegistry() 239 240 plugins := []plugin.CommandPlugin{&MockPlugin{}, &expansions.ExpansionsPlugin{}, &shell.ShellPlugin{}} 241 apiPlugins := []plugin.APIPlugin{&MockPlugin{}, &expansions.ExpansionsPlugin{}} 242 for _, p := range plugins { 243 err := registry.Register(p) 244 testutil.HandleTestingErr(err, t, "failed to register plugin") 245 } 246 testConfig := testutil.TestConfig() 247 testServer, err := service.CreateTestServer(testConfig, nil, apiPlugins) 248 249 testutil.HandleTestingErr(err, t, "Couldn't set up testing server") 250 defer testServer.Close() 251 252 pluginFilePath := filepath.Join(testutil.GetDirectoryOfFile(), 253 "testdata", "plugin_project_functions.yml") 254 255 modelData, err := modelutil.SetupAPITestData(testConfig, "test", "rhel55", pluginFilePath, modelutil.NoPatch) 256 testutil.HandleTestingErr(err, t, "failed to setup test data") 257 258 taskConfig := modelData.TaskConfig 259 260 httpCom := plugintest.TestAgentCommunicator(modelData, testServer.URL) 261 262 logger := agentutil.NewTestLogger(slogger.StdOutAppender()) 263 264 Convey("all commands in test project should execute successfully", func() { 265 for _, newTask := range taskConfig.Project.Tasks { 266 So(len(newTask.Commands), ShouldNotEqual, 0) 267 for _, command := range newTask.Commands { 268 pluginCmds, err := registry.GetCommands(command, taskConfig.Project.Functions) 269 testutil.HandleTestingErr(err, t, "Couldn't get plugin command: %v") 270 So(pluginCmds, ShouldNotBeNil) 271 So(err, ShouldBeNil) 272 for _, c := range pluginCmds { 273 pluginCom := &comm.TaskJSONCommunicator{c.Plugin(), httpCom} 274 err = c.Execute(logger, pluginCom, taskConfig, make(chan bool)) 275 So(err, ShouldBeNil) 276 } 277 } 278 } 279 }) 280 }) 281 } 282 283 // helper for generating a string of a size 284 func strOfLen(size int) string { 285 b := bytes.Buffer{} 286 for i := 0; i < size; i++ { 287 b.WriteByte('a') 288 } 289 return b.String() 290 } 291 292 func TestAttachLargeResults(t *testing.T) { 293 if runtime.Compiler == "gccgo" { 294 // TODO: Remove skip when compiler is upgraded to include fix for bug https://github.com/golang/go/issues/12781 295 t.Skip("skipping test to avoid httptest server bug") 296 } 297 testutil.HandleTestingErr(db.ClearCollections(task.Collection), t, "problem clearning collections") 298 Convey("With a test task and server", t, func() { 299 testConfig := testutil.TestConfig() 300 testServer, err := service.CreateTestServer(testConfig, nil, nil) 301 302 testutil.HandleTestingErr(err, t, "Couldn't set up testing server") 303 defer testServer.Close() 304 305 modelData, err := modelutil.SetupAPITestData(testConfig, "test", "rhel55", filepath.Join(testutil.GetDirectoryOfFile(), 306 "testdata", "plugin_project_functions.yml"), modelutil.NoPatch) 307 testutil.HandleTestingErr(err, t, "failed to setup test data") 308 309 httpCom := plugintest.TestAgentCommunicator(modelData, testServer.URL) 310 311 pluginCom := &comm.TaskJSONCommunicator{"test", httpCom} 312 Convey("a test log < 16 MB should be accepted", func() { 313 id, err := pluginCom.TaskPostTestLog(&model.TestLog{ 314 Name: "woah", 315 Lines: []string{strOfLen(1024 * 1024 * 15)}, //15MB 316 }) 317 So(id, ShouldNotEqual, "") 318 So(err, ShouldBeNil) 319 }) 320 Convey("a test log > 16 MB should error", func() { 321 id, err := pluginCom.TaskPostTestLog(&model.TestLog{ 322 Name: "woah", 323 Lines: []string{strOfLen(1024 * 1024 * 17)}, //17MB 324 }) 325 So(id, ShouldEqual, "") 326 So(err, ShouldNotBeNil) 327 So(err.Error(), ShouldContainSubstring, "unexpected end") 328 }) 329 }) 330 } 331 332 func TestPluginSelfRegistration(t *testing.T) { 333 Convey("Assuming the plugin collection has run its init functions", t, func() { 334 So(len(plugin.CommandPlugins), ShouldBeGreaterThan, 0) 335 nameMap := map[string]uint{} 336 // count all occurrences of a plugin name 337 for _, plugin := range plugin.CommandPlugins { 338 nameMap[plugin.Name()] = nameMap[plugin.Name()] + 1 339 } 340 341 Convey("no plugin should be present in Published more than once", func() { 342 for _, count := range nameMap { 343 So(count, ShouldEqual, 1) 344 } 345 }) 346 347 Convey("some known default plugins should be present in the list", func() { 348 // These use strings instead of consts from the plugin 349 // packages, so we can avoid importing those packages 350 // and make sure the registration from plugin/config 351 // is actually happening 352 So(nameMap["attach"], ShouldEqual, 1) 353 So(nameMap["s3"], ShouldEqual, 1) 354 So(nameMap["s3Copy"], ShouldEqual, 1) 355 So(nameMap["archive"], ShouldEqual, 1) 356 So(nameMap["expansions"], ShouldEqual, 1) 357 So(nameMap["git"], ShouldEqual, 1) 358 So(nameMap["shell"], ShouldEqual, 1) 359 }) 360 }) 361 }