github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"bytes"
     8  	"flag"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  	stdtesting "testing"
    16  
    17  	jc "github.com/juju/testing/checkers"
    18  	"launchpad.net/gnuflag"
    19  	gc "launchpad.net/gocheck"
    20  
    21  	"github.com/juju/juju/cmd"
    22  	"github.com/juju/juju/cmd/envcmd"
    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  func TestPackage(t *stdtesting.T) {
    30  	testing.MgoTestPackage(t)
    31  }
    32  
    33  type MainSuite struct {
    34  	testing.FakeJujuHomeSuite
    35  }
    36  
    37  var _ = gc.Suite(&MainSuite{})
    38  
    39  var (
    40  	flagRunMain = flag.Bool("run-main", false, "Run the application's main function for recursive testing")
    41  )
    42  
    43  // Reentrancy point for testing (something as close as possible to) the juju
    44  // tool itself.
    45  func TestRunMain(t *stdtesting.T) {
    46  	if *flagRunMain {
    47  		Main(flag.Args())
    48  	}
    49  }
    50  
    51  func badrun(c *gc.C, exit int, args ...string) string {
    52  	localArgs := append([]string{"-test.run", "TestRunMain", "-run-main", "--", "juju"}, args...)
    53  	ps := exec.Command(os.Args[0], localArgs...)
    54  	ps.Env = append(os.Environ(), osenv.JujuHomeEnvKey+"="+osenv.JujuHome())
    55  	output, err := ps.CombinedOutput()
    56  	c.Logf("command output: %q", output)
    57  	if exit != 0 {
    58  		c.Assert(err, gc.ErrorMatches, fmt.Sprintf("exit status %d", exit))
    59  	}
    60  	return string(output)
    61  }
    62  
    63  func helpText(command cmd.Command, name string) string {
    64  	buff := &bytes.Buffer{}
    65  	info := command.Info()
    66  	info.Name = name
    67  	f := gnuflag.NewFlagSet(info.Name, gnuflag.ContinueOnError)
    68  	command.SetFlags(f)
    69  	buff.Write(info.Help(f))
    70  	return buff.String()
    71  }
    72  
    73  func deployHelpText() string {
    74  	return helpText(envcmd.Wrap(&DeployCommand{}), "juju deploy")
    75  }
    76  
    77  func syncToolsHelpText() string {
    78  	return helpText(envcmd.Wrap(&SyncToolsCommand{}), "juju sync-tools")
    79  }
    80  
    81  func (s *MainSuite) TestRunMain(c *gc.C) {
    82  	// The test array structure needs to be inline here as some of the
    83  	// expected values below use deployHelpText().  This constructs the deploy
    84  	// command and runs gets the help for it.  When the deploy command is
    85  	// setting the flags (which is needed for the help text) it is accessing
    86  	// osenv.JujuHome(), which panics if SetJujuHome has not been called.
    87  	// The FakeHome from testing does this.
    88  	for i, t := range []struct {
    89  		summary string
    90  		args    []string
    91  		code    int
    92  		out     string
    93  	}{{
    94  		summary: "no params shows help",
    95  		args:    []string{},
    96  		code:    0,
    97  		out:     strings.TrimLeft(helpBasics, "\n"),
    98  	}, {
    99  		summary: "juju help is the same as juju",
   100  		args:    []string{"help"},
   101  		code:    0,
   102  		out:     strings.TrimLeft(helpBasics, "\n"),
   103  	}, {
   104  		summary: "juju --help works too",
   105  		args:    []string{"--help"},
   106  		code:    0,
   107  		out:     strings.TrimLeft(helpBasics, "\n"),
   108  	}, {
   109  		summary: "juju help basics is the same as juju",
   110  		args:    []string{"help", "basics"},
   111  		code:    0,
   112  		out:     strings.TrimLeft(helpBasics, "\n"),
   113  	}, {
   114  		summary: "juju help foo doesn't exist",
   115  		args:    []string{"help", "foo"},
   116  		code:    1,
   117  		out:     "ERROR unknown command or topic for foo\n",
   118  	}, {
   119  		summary: "juju help deploy shows the default help without global options",
   120  		args:    []string{"help", "deploy"},
   121  		code:    0,
   122  		out:     deployHelpText(),
   123  	}, {
   124  		summary: "juju --help deploy shows the same help as 'help deploy'",
   125  		args:    []string{"--help", "deploy"},
   126  		code:    0,
   127  		out:     deployHelpText(),
   128  	}, {
   129  		summary: "juju deploy --help shows the same help as 'help deploy'",
   130  		args:    []string{"deploy", "--help"},
   131  		code:    0,
   132  		out:     deployHelpText(),
   133  	}, {
   134  		summary: "unknown command",
   135  		args:    []string{"discombobulate"},
   136  		code:    1,
   137  		out:     "ERROR unrecognized command: juju discombobulate\n",
   138  	}, {
   139  		summary: "unknown option before command",
   140  		args:    []string{"--cheese", "bootstrap"},
   141  		code:    2,
   142  		out:     "error: flag provided but not defined: --cheese\n",
   143  	}, {
   144  		summary: "unknown option after command",
   145  		args:    []string{"bootstrap", "--cheese"},
   146  		code:    2,
   147  		out:     "error: flag provided but not defined: --cheese\n",
   148  	}, {
   149  		summary: "known option, but specified before command",
   150  		args:    []string{"--environment", "blah", "bootstrap"},
   151  		code:    2,
   152  		out:     "error: flag provided but not defined: --environment\n",
   153  	}, {
   154  		summary: "juju sync-tools registered properly",
   155  		args:    []string{"sync-tools", "--help"},
   156  		code:    0,
   157  		out:     syncToolsHelpText(),
   158  	}, {
   159  		summary: "check version command registered properly",
   160  		args:    []string{"version"},
   161  		code:    0,
   162  		out:     version.Current.String() + "\n",
   163  	},
   164  	} {
   165  		c.Logf("test %d: %s", i, t.summary)
   166  		out := badrun(c, t.code, t.args...)
   167  		c.Assert(out, gc.Equals, t.out)
   168  	}
   169  }
   170  
   171  func (s *MainSuite) TestActualRunJujuArgOrder(c *gc.C) {
   172  	logpath := filepath.Join(c.MkDir(), "log")
   173  	tests := [][]string{
   174  		{"--log-file", logpath, "--debug", "env"}, // global flags before
   175  		{"env", "--log-file", logpath, "--debug"}, // after
   176  		{"--log-file", logpath, "env", "--debug"}, // mixed
   177  	}
   178  	for i, test := range tests {
   179  		c.Logf("test %d: %v", i, test)
   180  		badrun(c, 0, test...)
   181  		content, err := ioutil.ReadFile(logpath)
   182  		c.Assert(err, gc.IsNil)
   183  		c.Assert(string(content), gc.Matches, "(.|\n)*running juju(.|\n)*command finished(.|\n)*")
   184  		err = os.Remove(logpath)
   185  		c.Assert(err, gc.IsNil)
   186  	}
   187  }
   188  
   189  var commandNames = []string{
   190  	"add-machine",
   191  	"add-relation",
   192  	"add-unit",
   193  	"api-endpoints",
   194  	"authorised-keys", // alias for authorized-keys
   195  	"authorized-keys",
   196  	"bootstrap",
   197  	"debug-hooks",
   198  	"debug-log",
   199  	"deploy",
   200  	"destroy-environment",
   201  	"destroy-machine",
   202  	"destroy-relation",
   203  	"destroy-service",
   204  	"destroy-unit",
   205  	"ensure-availability",
   206  	"env", // alias for switch
   207  	"expose",
   208  	"generate-config", // alias for init
   209  	"get",
   210  	"get-constraints",
   211  	"get-env", // alias for get-environment
   212  	"get-environment",
   213  	"help",
   214  	"help-tool",
   215  	"init",
   216  	"publish",
   217  	"remove-machine",  // alias for destroy-machine
   218  	"remove-relation", // alias for destroy-relation
   219  	"remove-service",  // alias for destroy-service
   220  	"remove-unit",     // alias for destroy-unit
   221  	"resolved",
   222  	"retry-provisioning",
   223  	"run",
   224  	"scp",
   225  	"set",
   226  	"set-constraints",
   227  	"set-env", // alias for set-environment
   228  	"set-environment",
   229  	"ssh",
   230  	"stat", // alias for status
   231  	"status",
   232  	"switch",
   233  	"sync-tools",
   234  	"terminate-machine", // alias for destroy-machine
   235  	"unexpose",
   236  	"unset",
   237  	"unset-env", // alias for unset-environment
   238  	"unset-environment",
   239  	"upgrade-charm",
   240  	"upgrade-juju",
   241  	"user",
   242  	"version",
   243  }
   244  
   245  func (s *MainSuite) TestHelpCommands(c *gc.C) {
   246  	// Check that we have correctly registered all the commands
   247  	// by checking the help output.
   248  	defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir()))
   249  	out := badrun(c, 0, "help", "commands")
   250  	lines := strings.Split(out, "\n")
   251  	var names []string
   252  	for _, line := range lines {
   253  		f := strings.Fields(line)
   254  		if len(f) == 0 {
   255  			continue
   256  		}
   257  		names = append(names, f[0])
   258  	}
   259  	// The names should be output in alphabetical order, so don't sort.
   260  	c.Assert(names, jc.DeepEquals, commandNames)
   261  }
   262  
   263  var topicNames = []string{
   264  	"azure-provider",
   265  	"basics",
   266  	"commands",
   267  	"constraints",
   268  	"ec2-provider",
   269  	"global-options",
   270  	"glossary",
   271  	"hpcloud-provider",
   272  	"local-provider",
   273  	"logging",
   274  	"openstack-provider",
   275  	"plugins",
   276  	"topics",
   277  }
   278  
   279  func (s *MainSuite) TestHelpTopics(c *gc.C) {
   280  	// Check that we have correctly registered all the topics
   281  	// by checking the help output.
   282  	defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir()))
   283  	out := badrun(c, 0, "help", "topics")
   284  	lines := strings.Split(out, "\n")
   285  	var names []string
   286  	for _, line := range lines {
   287  		f := strings.Fields(line)
   288  		if len(f) == 0 {
   289  			continue
   290  		}
   291  		names = append(names, f[0])
   292  	}
   293  	// The names should be output in alphabetical order, so don't sort.
   294  	c.Assert(names, gc.DeepEquals, topicNames)
   295  }
   296  
   297  var globalFlags = []string{
   298  	"--debug .*",
   299  	"--description .*",
   300  	"-h, --help .*",
   301  	"--log-file .*",
   302  	"--logging-config .*",
   303  	"-q, --quiet .*",
   304  	"--show-log .*",
   305  	"-v, --verbose .*",
   306  }
   307  
   308  func (s *MainSuite) TestHelpGlobalOptions(c *gc.C) {
   309  	// Check that we have correctly registered all the topics
   310  	// by checking the help output.
   311  	defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir()))
   312  	out := badrun(c, 0, "help", "global-options")
   313  	c.Assert(out, gc.Matches, `Global Options
   314  
   315  These options may be used with any command, and may appear in front of any
   316  command\.(.|\n)*`)
   317  	lines := strings.Split(out, "\n")
   318  	var flags []string
   319  	for _, line := range lines {
   320  		f := strings.Fields(line)
   321  		if len(f) == 0 || line[0] != '-' {
   322  			continue
   323  		}
   324  		flags = append(flags, line)
   325  	}
   326  	c.Assert(len(flags), gc.Equals, len(globalFlags))
   327  	for i, line := range flags {
   328  		c.Assert(line, gc.Matches, globalFlags[i])
   329  	}
   330  }
   331  
   332  type commands []cmd.Command
   333  
   334  func (r *commands) Register(c cmd.Command) {
   335  	*r = append(*r, c)
   336  }
   337  
   338  func (s *MainSuite) TestEnvironCommands(c *gc.C) {
   339  	var commands commands
   340  	registerCommands(&commands, testing.Context(c))
   341  	// There should not be any EnvironCommands registered.
   342  	// EnvironCommands must be wrapped using envcmd.Wrap.
   343  	for _, cmd := range commands {
   344  		c.Logf("%v", cmd.Info().Name)
   345  		c.Check(cmd, gc.Not(gc.FitsTypeOf), envcmd.EnvironCommand(&BootstrapCommand{}))
   346  	}
   347  }