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