github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/commands/plugin_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"runtime"
    12  	"text/template"
    13  	"time"
    14  
    15  	"github.com/juju/cmd"
    16  	gitjujutesting "github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/jujuclient"
    21  	"github.com/juju/juju/testing"
    22  )
    23  
    24  type PluginSuite struct {
    25  	testing.FakeJujuXDGDataHomeSuite
    26  	oldPath string
    27  }
    28  
    29  var _ = gc.Suite(&PluginSuite{})
    30  
    31  func (suite *PluginSuite) SetUpTest(c *gc.C) {
    32  	//TODO(bogdanteleaga): Fix bash tests
    33  	if runtime.GOOS == "windows" {
    34  		c.Skip("bug 1403084: tests use bash scrips, will be rewritten for windows")
    35  	}
    36  	suite.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    37  	suite.oldPath = os.Getenv("PATH")
    38  	os.Setenv("PATH", "/bin:"+gitjujutesting.HomePath())
    39  }
    40  
    41  func (suite *PluginSuite) TearDownTest(c *gc.C) {
    42  	os.Setenv("PATH", suite.oldPath)
    43  	suite.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    44  }
    45  
    46  func (*PluginSuite) TestFindPlugins(c *gc.C) {
    47  	plugins := findPlugins()
    48  	c.Assert(plugins, gc.DeepEquals, []string{})
    49  }
    50  
    51  func (suite *PluginSuite) TestFindPluginsOrder(c *gc.C) {
    52  	suite.makeWorkingPlugin("foo", 0744)
    53  	suite.makeWorkingPlugin("bar", 0654)
    54  	suite.makeWorkingPlugin("baz", 0645)
    55  	plugins := findPlugins()
    56  	c.Assert(plugins, gc.DeepEquals, []string{"juju-bar", "juju-baz", "juju-foo"})
    57  }
    58  
    59  func (suite *PluginSuite) TestFindPluginsBadNames(c *gc.C) {
    60  	suite.makePlugin("juju-1foo", "", 0755)
    61  	suite.makePlugin("juju--foo", "", 0755)
    62  	suite.makePlugin("ajuju-foo", "", 0755)
    63  	plugins := findPlugins()
    64  	c.Assert(plugins, gc.DeepEquals, []string{})
    65  }
    66  
    67  func (suite *PluginSuite) TestFindPluginsIgnoreNotExec(c *gc.C) {
    68  	suite.makeWorkingPlugin("foo", 0644)
    69  	suite.makeWorkingPlugin("bar", 0666)
    70  	plugins := findPlugins()
    71  	c.Assert(plugins, gc.DeepEquals, []string{})
    72  }
    73  
    74  func (suite *PluginSuite) TestRunPluginExising(c *gc.C) {
    75  	suite.makeWorkingPlugin("foo", 0755)
    76  	ctx := testing.Context(c)
    77  	err := RunPlugin(ctx, "foo", []string{"some params"})
    78  	c.Assert(err, jc.ErrorIsNil)
    79  	c.Assert(testing.Stdout(ctx), gc.Equals, "foo some params\n")
    80  	c.Assert(testing.Stderr(ctx), gc.Equals, "")
    81  }
    82  
    83  func (suite *PluginSuite) TestRunPluginWithFailing(c *gc.C) {
    84  	suite.makeFailingPlugin("foo", 2)
    85  	ctx := testing.Context(c)
    86  	err := RunPlugin(ctx, "foo", []string{"some params"})
    87  	c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 2")
    88  	c.Assert(err, jc.Satisfies, cmd.IsRcPassthroughError)
    89  	c.Assert(testing.Stdout(ctx), gc.Equals, "failing\n")
    90  	c.Assert(testing.Stderr(ctx), gc.Equals, "")
    91  }
    92  
    93  func (suite *PluginSuite) TestGatherDescriptionsInParallel(c *gc.C) {
    94  	// Make plugins that will deadlock if we don't start them in parallel.
    95  	// Each plugin depends on another one being started before they will
    96  	// complete. They make a full loop, so no sequential ordering will ever
    97  	// succeed.
    98  	suite.makeFullPlugin(PluginParams{Name: "foo", Creates: "foo", DependsOn: "bar"})
    99  	suite.makeFullPlugin(PluginParams{Name: "bar", Creates: "bar", DependsOn: "baz"})
   100  	suite.makeFullPlugin(PluginParams{Name: "baz", Creates: "baz", DependsOn: "error"})
   101  	suite.makeFullPlugin(PluginParams{Name: "error", ExitStatus: 1, Creates: "error", DependsOn: "foo"})
   102  
   103  	// If the code was wrong, GetPluginDescriptions would deadlock,
   104  	// so timeout after a short while
   105  	resultChan := make(chan []PluginDescription)
   106  	go func() {
   107  		resultChan <- GetPluginDescriptions()
   108  	}()
   109  	// 10 seconds is arbitrary but should always be generously long. Test
   110  	// actually only takes about 15ms in practice. But 10s allows for system hiccups, etc.
   111  	waitTime := 10 * time.Second
   112  	var results []PluginDescription
   113  	select {
   114  	case results = <-resultChan:
   115  		break
   116  	case <-time.After(waitTime):
   117  		c.Fatalf("took longer than %fs to complete.", waitTime.Seconds())
   118  	}
   119  
   120  	c.Assert(results, gc.HasLen, 4)
   121  	c.Assert(results[0].name, gc.Equals, "bar")
   122  	c.Assert(results[0].description, gc.Equals, "bar description")
   123  	c.Assert(results[1].name, gc.Equals, "baz")
   124  	c.Assert(results[1].description, gc.Equals, "baz description")
   125  	c.Assert(results[2].name, gc.Equals, "error")
   126  	c.Assert(results[2].description, gc.Equals, "error occurred running 'juju-error --description'")
   127  	c.Assert(results[3].name, gc.Equals, "foo")
   128  	c.Assert(results[3].description, gc.Equals, "foo description")
   129  }
   130  
   131  func (suite *PluginSuite) TestHelpPluginName(c *gc.C) {
   132  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   133  	output := badrun(c, 0, "help", "foo")
   134  	expectedHelp := `foo longer help
   135  
   136  something useful
   137  `
   138  	c.Assert(output, gc.Matches, expectedHelp)
   139  }
   140  
   141  func (suite *PluginSuite) TestHelpPluginNameNotAPlugin(c *gc.C) {
   142  	output := badrun(c, 0, "help", "foo")
   143  	expectedHelp := "ERROR unknown command or topic for foo\n"
   144  	c.Assert(output, gc.Matches, expectedHelp)
   145  }
   146  
   147  func (suite *PluginSuite) TestHelpAsArg(c *gc.C) {
   148  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   149  	output := badrun(c, 0, "foo", "--help")
   150  	expectedHelp := `foo longer help
   151  
   152  something useful
   153  `
   154  	c.Assert(output, gc.Matches, expectedHelp)
   155  }
   156  
   157  func (suite *PluginSuite) TestDebugAsArg(c *gc.C) {
   158  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   159  	output := badrun(c, 0, "foo", "--debug")
   160  	expectedDebug := "some debug\n"
   161  	c.Assert(output, gc.Matches, expectedDebug)
   162  }
   163  
   164  func (suite *PluginSuite) TestJujuEnvVars(c *gc.C) {
   165  	// Plugins are run as model commands, and so require a current
   166  	// account and model.
   167  	store := jujuclient.NewFileClientStore()
   168  	err := store.AddController("myctrl", jujuclient.ControllerDetails{
   169  		ControllerUUID: testing.ControllerTag.Id(),
   170  		CACert:         "fake",
   171  	})
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	err = store.SetCurrentController("myctrl")
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	err = store.UpdateAccount("myctrl", jujuclient.AccountDetails{
   176  		User:     "admin@local",
   177  		Password: "hunter2",
   178  	})
   179  	c.Assert(err, jc.ErrorIsNil)
   180  
   181  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   182  	output := badrun(c, 0, "foo", "-m", "mymodel", "-p", "pluginarg")
   183  	expectedDebug := "foo -m mymodel -p pluginarg\nmodel is:  mymodel\n"
   184  	c.Assert(output, gc.Matches, expectedDebug)
   185  }
   186  
   187  func (suite *PluginSuite) makePlugin(fullName, script string, perm os.FileMode) {
   188  	filename := gitjujutesting.HomePath(fullName)
   189  	content := fmt.Sprintf("#!/bin/bash --norc\n%s", script)
   190  	ioutil.WriteFile(filename, []byte(content), perm)
   191  }
   192  
   193  func (suite *PluginSuite) makeWorkingPlugin(name string, perm os.FileMode) {
   194  	script := fmt.Sprintf("echo %s $*", name)
   195  	suite.makePlugin(JujuPluginPrefix+name, script, perm)
   196  }
   197  
   198  func (suite *PluginSuite) makeFailingPlugin(name string, exitStatus int) {
   199  	script := fmt.Sprintf("echo failing\nexit %d", exitStatus)
   200  	suite.makePlugin(JujuPluginPrefix+name, script, 0755)
   201  }
   202  
   203  type PluginParams struct {
   204  	Name       string
   205  	ExitStatus int
   206  	Creates    string
   207  	DependsOn  string
   208  }
   209  
   210  const pluginTemplate = `#!/bin/bash --norc
   211  
   212  if [ "$1" = "--description" ]; then
   213    if [ -n "{{.Creates}}" ]; then
   214      touch "{{.Creates}}"
   215    fi
   216    if [ -n "{{.DependsOn}}" ]; then
   217      # Sleep 10ms while waiting to allow other stuff to do work
   218      while [ ! -e "{{.DependsOn}}" ]; do sleep 0.010; done
   219    fi
   220    echo "{{.Name}} description"
   221    exit {{.ExitStatus}}
   222  fi
   223  
   224  if [ "$1" = "--help" ]; then
   225    echo "{{.Name}} longer help"
   226    echo ""
   227    echo "something useful"
   228    exit {{.ExitStatus}}
   229  fi
   230  
   231  if [ "$1" = "--debug" ]; then
   232    echo "some debug"
   233    exit {{.ExitStatus}}
   234  fi
   235  
   236  echo {{.Name}} $*
   237  echo "model is: " $JUJU_MODEL
   238  exit {{.ExitStatus}}
   239  `
   240  
   241  func (suite *PluginSuite) makeFullPlugin(params PluginParams) {
   242  	// Create a new template and parse the plugin into it.
   243  	t := template.Must(template.New("plugin").Parse(pluginTemplate))
   244  	content := &bytes.Buffer{}
   245  	filename := gitjujutesting.HomePath("juju-" + params.Name)
   246  	// Create the files in the temp dirs, so we don't pollute the working space
   247  	if params.Creates != "" {
   248  		params.Creates = gitjujutesting.HomePath(params.Creates)
   249  	}
   250  	if params.DependsOn != "" {
   251  		params.DependsOn = gitjujutesting.HomePath(params.DependsOn)
   252  	}
   253  	t.Execute(content, params)
   254  	ioutil.WriteFile(filename, content.Bytes(), 0755)
   255  }