github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/main_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  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/juju/cmd"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/featureflag"
    15  	"github.com/juju/utils/set"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/cmd/envcmd"
    19  	cmdtesting "github.com/juju/juju/cmd/testing"
    20  	"github.com/juju/juju/feature"
    21  	"github.com/juju/juju/juju/osenv"
    22  	_ "github.com/juju/juju/provider/dummy"
    23  	"github.com/juju/juju/testing"
    24  	"github.com/juju/juju/version"
    25  )
    26  
    27  type MainSuite struct {
    28  	testing.FakeJujuHomeSuite
    29  }
    30  
    31  var _ = gc.Suite(&MainSuite{})
    32  
    33  func deployHelpText() string {
    34  	return cmdtesting.HelpText(envcmd.Wrap(&DeployCommand{}), "juju deploy")
    35  }
    36  
    37  func setHelpText() string {
    38  	return cmdtesting.HelpText(envcmd.Wrap(&SetCommand{}), "juju set")
    39  }
    40  
    41  func syncToolsHelpText() string {
    42  	return cmdtesting.HelpText(envcmd.Wrap(&SyncToolsCommand{}), "juju sync-tools")
    43  }
    44  
    45  func (s *MainSuite) TestRunMain(c *gc.C) {
    46  	// The test array structure needs to be inline here as some of the
    47  	// expected values below use deployHelpText().  This constructs the deploy
    48  	// command and runs gets the help for it.  When the deploy command is
    49  	// setting the flags (which is needed for the help text) it is accessing
    50  	// osenv.JujuHome(), which panics if SetJujuHome has not been called.
    51  	// The FakeHome from testing does this.
    52  	for i, t := range []struct {
    53  		summary string
    54  		args    []string
    55  		code    int
    56  		out     string
    57  	}{{
    58  		summary: "no params shows help",
    59  		args:    []string{},
    60  		code:    0,
    61  		out:     strings.TrimLeft(helpBasics, "\n"),
    62  	}, {
    63  		summary: "juju help is the same as juju",
    64  		args:    []string{"help"},
    65  		code:    0,
    66  		out:     strings.TrimLeft(helpBasics, "\n"),
    67  	}, {
    68  		summary: "juju --help works too",
    69  		args:    []string{"--help"},
    70  		code:    0,
    71  		out:     strings.TrimLeft(helpBasics, "\n"),
    72  	}, {
    73  		summary: "juju help basics is the same as juju",
    74  		args:    []string{"help", "basics"},
    75  		code:    0,
    76  		out:     strings.TrimLeft(helpBasics, "\n"),
    77  	}, {
    78  		summary: "juju help foo doesn't exist",
    79  		args:    []string{"help", "foo"},
    80  		code:    1,
    81  		out:     "ERROR unknown command or topic for foo\n",
    82  	}, {
    83  		summary: "juju help deploy shows the default help without global options",
    84  		args:    []string{"help", "deploy"},
    85  		code:    0,
    86  		out:     deployHelpText(),
    87  	}, {
    88  		summary: "juju --help deploy shows the same help as 'help deploy'",
    89  		args:    []string{"--help", "deploy"},
    90  		code:    0,
    91  		out:     deployHelpText(),
    92  	}, {
    93  		summary: "juju deploy --help shows the same help as 'help deploy'",
    94  		args:    []string{"deploy", "--help"},
    95  		code:    0,
    96  		out:     deployHelpText(),
    97  	}, {
    98  		summary: "juju help set shows the default help without global options",
    99  		args:    []string{"help", "set"},
   100  		code:    0,
   101  		out:     setHelpText(),
   102  	}, {
   103  		summary: "juju --help set shows the same help as 'help set'",
   104  		args:    []string{"--help", "set"},
   105  		code:    0,
   106  		out:     setHelpText(),
   107  	}, {
   108  		summary: "juju set --help shows the same help as 'help set'",
   109  		args:    []string{"set", "--help"},
   110  		code:    0,
   111  		out:     setHelpText(),
   112  	}, {
   113  		summary: "unknown command",
   114  		args:    []string{"discombobulate"},
   115  		code:    1,
   116  		out:     "ERROR unrecognized command: juju discombobulate\n",
   117  	}, {
   118  		summary: "unknown option before command",
   119  		args:    []string{"--cheese", "bootstrap"},
   120  		code:    2,
   121  		out:     "error: flag provided but not defined: --cheese\n",
   122  	}, {
   123  		summary: "unknown option after command",
   124  		args:    []string{"bootstrap", "--cheese"},
   125  		code:    2,
   126  		out:     "error: flag provided but not defined: --cheese\n",
   127  	}, {
   128  		summary: "known option, but specified before command",
   129  		args:    []string{"--environment", "blah", "bootstrap"},
   130  		code:    2,
   131  		out:     "error: flag provided but not defined: --environment\n",
   132  	}, {
   133  		summary: "juju sync-tools registered properly",
   134  		args:    []string{"sync-tools", "--help"},
   135  		code:    0,
   136  		out:     syncToolsHelpText(),
   137  	}, {
   138  		summary: "check version command registered properly",
   139  		args:    []string{"version"},
   140  		code:    0,
   141  		out:     version.Current.String() + "\n",
   142  	}, {
   143  		summary: "check block command registered properly",
   144  		args:    []string{"block"},
   145  		code:    0,
   146  		out:     "error: must specify one of [destroy-environment | remove-object | all-changes] to block\n",
   147  	}, {
   148  		summary: "check unblock command registered properly",
   149  		args:    []string{"unblock"},
   150  		code:    0,
   151  		out:     "error: must specify one of [destroy-environment | remove-object | all-changes] to unblock\n",
   152  	},
   153  	} {
   154  		c.Logf("test %d: %s", i, t.summary)
   155  		out := badrun(c, t.code, t.args...)
   156  		c.Assert(out, gc.Equals, t.out)
   157  	}
   158  }
   159  
   160  func (s *MainSuite) TestActualRunJujuArgOrder(c *gc.C) {
   161  	logpath := filepath.Join(c.MkDir(), "log")
   162  	tests := [][]string{
   163  		{"--log-file", logpath, "--debug", "env"}, // global flags before
   164  		{"env", "--log-file", logpath, "--debug"}, // after
   165  		{"--log-file", logpath, "env", "--debug"}, // mixed
   166  	}
   167  	for i, test := range tests {
   168  		c.Logf("test %d: %v", i, test)
   169  		badrun(c, 0, test...)
   170  		content, err := ioutil.ReadFile(logpath)
   171  		c.Assert(err, jc.ErrorIsNil)
   172  		c.Assert(string(content), gc.Matches, "(.|\n)*running juju(.|\n)*command finished(.|\n)*")
   173  		err = os.Remove(logpath)
   174  		c.Assert(err, jc.ErrorIsNil)
   175  	}
   176  }
   177  
   178  var commandNames = []string{
   179  	"action",
   180  	"add-machine",
   181  	"add-relation",
   182  	"add-unit",
   183  	"api-endpoints",
   184  	"api-info",
   185  	"authorised-keys", // alias for authorized-keys
   186  	"authorized-keys",
   187  	"backups",
   188  	"block",
   189  	"bootstrap",
   190  	"cached-images",
   191  	"charms",
   192  	"debug-hooks",
   193  	"debug-log",
   194  	"deploy",
   195  	"destroy-environment",
   196  	"destroy-machine",
   197  	"destroy-relation",
   198  	"destroy-service",
   199  	"destroy-unit",
   200  	"ensure-availability",
   201  	"env", // alias for switch
   202  	"environment",
   203  	"expose",
   204  	"generate-config", // alias for init
   205  	"get",
   206  	"get-constraints",
   207  	"get-env", // alias for get-environment
   208  	"get-environment",
   209  	"help",
   210  	"help-tool",
   211  	"init",
   212  	"machine",
   213  	"publish",
   214  	"remove-machine",  // alias for destroy-machine
   215  	"remove-relation", // alias for destroy-relation
   216  	"remove-service",  // alias for destroy-service
   217  	"remove-unit",     // alias for destroy-unit
   218  	"resolved",
   219  	"retry-provisioning",
   220  	"run",
   221  	"scp",
   222  	"set",
   223  	"set-constraints",
   224  	"set-env", // alias for set-environment
   225  	"set-environment",
   226  	"ssh",
   227  	"stat", // alias for status
   228  	"status",
   229  	"storage",
   230  	"switch",
   231  	"sync-tools",
   232  	"terminate-machine", // alias for destroy-machine
   233  	"unblock",
   234  	"unexpose",
   235  	"unset",
   236  	"unset-env", // alias for unset-environment
   237  	"unset-environment",
   238  	"upgrade-charm",
   239  	"upgrade-juju",
   240  	"user",
   241  	"version",
   242  }
   243  
   244  func (s *MainSuite) TestHelpCommands(c *gc.C) {
   245  	defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir()))
   246  
   247  	// Check that we have correctly registered all the commands
   248  	// by checking the help output.
   249  	// First check default commands, and then check commands that are
   250  	// activated by feature flags.
   251  
   252  	// remove "action" for the first test because the feature is not
   253  	// enabled.
   254  	devFeatures := []string{feature.Actions, feature.Storage}
   255  
   256  	// remove features behind dev_flag for the first test
   257  	// since they are not enabled.
   258  	cmdSet := set.NewStrings(commandNames...)
   259  	for _, feature := range devFeatures {
   260  		cmdSet.Remove(feature)
   261  	}
   262  
   263  	// 1. Default Commands. Disable all features.
   264  	setFeatureFlags("")
   265  	c.Assert(getHelpCommandNames(c), jc.SameContents, cmdSet.Values())
   266  
   267  	// 2. Enable development features, and test again.
   268  	setFeatureFlags(strings.Join(devFeatures, ","))
   269  	c.Assert(getHelpCommandNames(c), jc.SameContents, commandNames)
   270  }
   271  
   272  func getHelpCommandNames(c *gc.C) []string {
   273  	out := badrun(c, 0, "help", "commands")
   274  	lines := strings.Split(out, "\n")
   275  	var names []string
   276  	for _, line := range lines {
   277  		f := strings.Fields(line)
   278  		if len(f) == 0 {
   279  			continue
   280  		}
   281  		names = append(names, f[0])
   282  	}
   283  	return names
   284  }
   285  
   286  func setFeatureFlags(flags string) {
   287  	if err := os.Setenv(osenv.JujuFeatureFlagEnvKey, flags); err != nil {
   288  		panic(err)
   289  	}
   290  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
   291  }
   292  
   293  var topicNames = []string{
   294  	"azure-provider",
   295  	"basics",
   296  	"commands",
   297  	"constraints",
   298  	"ec2-provider",
   299  	"global-options",
   300  	"glossary",
   301  	"hpcloud-provider",
   302  	"local-provider",
   303  	"logging",
   304  	"maas-provider",
   305  	"openstack-provider",
   306  	"placement",
   307  	"plugins",
   308  	"topics",
   309  }
   310  
   311  func (s *MainSuite) TestHelpTopics(c *gc.C) {
   312  	// Check that we have correctly registered all the topics
   313  	// by checking the help output.
   314  	defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir()))
   315  	out := badrun(c, 0, "help", "topics")
   316  	lines := strings.Split(out, "\n")
   317  	var names []string
   318  	for _, line := range lines {
   319  		f := strings.Fields(line)
   320  		if len(f) == 0 {
   321  			continue
   322  		}
   323  		names = append(names, f[0])
   324  	}
   325  	// The names should be output in alphabetical order, so don't sort.
   326  	c.Assert(names, gc.DeepEquals, topicNames)
   327  }
   328  
   329  var globalFlags = []string{
   330  	"--debug .*",
   331  	"--description .*",
   332  	"-h, --help .*",
   333  	"--log-file .*",
   334  	"--logging-config .*",
   335  	"-q, --quiet .*",
   336  	"--show-log .*",
   337  	"-v, --verbose .*",
   338  }
   339  
   340  func (s *MainSuite) TestHelpGlobalOptions(c *gc.C) {
   341  	// Check that we have correctly registered all the topics
   342  	// by checking the help output.
   343  	defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir()))
   344  	out := badrun(c, 0, "help", "global-options")
   345  	c.Assert(out, gc.Matches, `Global Options
   346  
   347  These options may be used with any command, and may appear in front of any
   348  command\.(.|\n)*`)
   349  	lines := strings.Split(out, "\n")
   350  	var flags []string
   351  	for _, line := range lines {
   352  		f := strings.Fields(line)
   353  		if len(f) == 0 || line[0] != '-' {
   354  			continue
   355  		}
   356  		flags = append(flags, line)
   357  	}
   358  	c.Assert(len(flags), gc.Equals, len(globalFlags))
   359  	for i, line := range flags {
   360  		c.Assert(line, gc.Matches, globalFlags[i])
   361  	}
   362  }
   363  
   364  type commands []cmd.Command
   365  
   366  func (r *commands) Register(c cmd.Command) {
   367  	*r = append(*r, c)
   368  }
   369  
   370  func (r *commands) RegisterSuperAlias(name, super, forName string, check cmd.DeprecationCheck) {
   371  	// Do nothing.
   372  }
   373  
   374  func (s *MainSuite) TestEnvironCommands(c *gc.C) {
   375  	var commands commands
   376  	registerCommands(&commands, testing.Context(c))
   377  	// There should not be any EnvironCommands registered.
   378  	// EnvironCommands must be wrapped using envcmd.Wrap.
   379  	for _, cmd := range commands {
   380  		c.Logf("%v", cmd.Info().Name)
   381  		c.Check(cmd, gc.Not(gc.FitsTypeOf), envcmd.EnvironCommand(&BootstrapCommand{}))
   382  	}
   383  }
   384  
   385  func (s *MainSuite) TestAllCommandsPurposeDocCapitalization(c *gc.C) {
   386  	// Verify each command that:
   387  	// - the Purpose field is not empty and begins with a lowercase
   388  	// letter, and,
   389  	// - if set, the Doc field either begins with the name of the
   390  	// command or and uppercase letter.
   391  	//
   392  	// The first makes Purpose a required documentation. Also, makes
   393  	// both "help commands"'s output and "help <cmd>"'s header more
   394  	// uniform. The second makes the Doc content either start like a
   395  	// sentence, or start godoc-like by using the command's name in
   396  	// lowercase.
   397  	var commands commands
   398  	registerCommands(&commands, testing.Context(c))
   399  	for _, cmd := range commands {
   400  		info := cmd.Info()
   401  		c.Logf("%v", info.Name)
   402  		purpose := strings.TrimSpace(info.Purpose)
   403  		doc := strings.TrimSpace(info.Doc)
   404  		comment := func(message string) interface{} {
   405  			return gc.Commentf("command %q %s", info.Name, message)
   406  		}
   407  
   408  		c.Check(purpose, gc.Not(gc.Equals), "", comment("has empty Purpose"))
   409  		if purpose != "" {
   410  			prefix := string(purpose[0])
   411  			c.Check(prefix, gc.Equals, strings.ToLower(prefix),
   412  				comment("expected lowercase first-letter Purpose"),
   413  			)
   414  		}
   415  		if doc != "" && !strings.HasPrefix(doc, info.Name) {
   416  			prefix := string(doc[0])
   417  			c.Check(prefix, gc.Equals, strings.ToUpper(prefix),
   418  				comment("expected uppercase first-letter Doc"),
   419  			)
   420  		}
   421  	}
   422  }
   423  
   424  func (s *MainSuite) TestTwoDotOhDeprecation(c *gc.C) {
   425  	check := twoDotOhDeprecation("the replacement")
   426  
   427  	// first check pre-2.0
   428  	s.PatchValue(&version.Current.Number, version.MustParse("1.26.4"))
   429  	deprecated, replacement := check.Deprecated()
   430  	c.Check(deprecated, jc.IsFalse)
   431  	c.Check(replacement, gc.Equals, "")
   432  	c.Check(check.Obsolete(), jc.IsFalse)
   433  
   434  	s.PatchValue(&version.Current.Number, version.MustParse("2.0-alpha1"))
   435  	deprecated, replacement = check.Deprecated()
   436  	c.Check(deprecated, jc.IsTrue)
   437  	c.Check(replacement, gc.Equals, "the replacement")
   438  	c.Check(check.Obsolete(), jc.IsFalse)
   439  
   440  	s.PatchValue(&version.Current.Number, version.MustParse("3.0-alpha1"))
   441  	deprecated, replacement = check.Deprecated()
   442  	c.Check(deprecated, jc.IsTrue)
   443  	c.Check(replacement, gc.Equals, "the replacement")
   444  	c.Check(check.Obsolete(), jc.IsTrue)
   445  }