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 }