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  }