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