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