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