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