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