github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/integration-cli/docker_cli_help_test.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"strings"
     7  	"unicode"
     8  
     9  	"github.com/docker/docker/integration-cli/checker"
    10  	"github.com/docker/docker/pkg/homedir"
    11  	icmd "github.com/docker/docker/pkg/testutil/cmd"
    12  	"github.com/go-check/check"
    13  )
    14  
    15  func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
    16  	// FIXME(vdemeester) should be a unit test, probably using golden files ?
    17  	testRequires(c, DaemonIsLinux)
    18  
    19  	// Make sure main help text fits within 80 chars and that
    20  	// on non-windows system we use ~ when possible (to shorten things).
    21  	// Test for HOME set to its default value and set to "/" on linux
    22  	// Yes on windows setting up an array and looping (right now) isn't
    23  	// necessary because we just have one value, but we'll need the
    24  	// array/loop on linux so we might as well set it up so that we can
    25  	// test any number of home dirs later on and all we need to do is
    26  	// modify the array - the rest of the testing infrastructure should work
    27  	homes := []string{homedir.Get()}
    28  
    29  	// Non-Windows machines need to test for this special case of $HOME
    30  	if runtime.GOOS != "windows" {
    31  		homes = append(homes, "/")
    32  	}
    33  
    34  	homeKey := homedir.Key()
    35  	baseEnvs := appendBaseEnv(true)
    36  
    37  	// Remove HOME env var from list so we can add a new value later.
    38  	for i, env := range baseEnvs {
    39  		if strings.HasPrefix(env, homeKey+"=") {
    40  			baseEnvs = append(baseEnvs[:i], baseEnvs[i+1:]...)
    41  			break
    42  		}
    43  	}
    44  
    45  	for _, home := range homes {
    46  
    47  		// Dup baseEnvs and add our new HOME value
    48  		newEnvs := make([]string, len(baseEnvs)+1)
    49  		copy(newEnvs, baseEnvs)
    50  		newEnvs[len(newEnvs)-1] = homeKey + "=" + home
    51  
    52  		scanForHome := runtime.GOOS != "windows" && home != "/"
    53  
    54  		// Check main help text to make sure its not over 80 chars
    55  		result := icmd.RunCmd(icmd.Cmd{
    56  			Command: []string{dockerBinary, "help"},
    57  			Env:     newEnvs,
    58  		})
    59  		result.Assert(c, icmd.Success)
    60  		lines := strings.Split(result.Combined(), "\n")
    61  		for _, line := range lines {
    62  			// All lines should not end with a space
    63  			c.Assert(line, checker.Not(checker.HasSuffix), " ", check.Commentf("Line should not end with a space"))
    64  
    65  			if scanForHome && strings.Contains(line, `=`+home) {
    66  				c.Fatalf("Line should use '%q' instead of %q:\n%s", homedir.GetShortcutString(), home, line)
    67  			}
    68  			if runtime.GOOS != "windows" {
    69  				i := strings.Index(line, homedir.GetShortcutString())
    70  				if i >= 0 && i != len(line)-1 && line[i+1] != '/' {
    71  					c.Fatalf("Main help should not have used home shortcut:\n%s", line)
    72  				}
    73  			}
    74  		}
    75  
    76  		// Make sure each cmd's help text fits within 90 chars and that
    77  		// on non-windows system we use ~ when possible (to shorten things).
    78  		// Pull the list of commands from the "Commands:" section of docker help
    79  		// FIXME(vdemeester) Why re-run help ?
    80  		//helpCmd = exec.Command(dockerBinary, "help")
    81  		//helpCmd.Env = newEnvs
    82  		//out, _, err = runCommandWithOutput(helpCmd)
    83  		//c.Assert(err, checker.IsNil, check.Commentf(out))
    84  		i := strings.Index(result.Combined(), "Commands:")
    85  		c.Assert(i, checker.GreaterOrEqualThan, 0, check.Commentf("Missing 'Commands:' in:\n%s", result.Combined()))
    86  
    87  		cmds := []string{}
    88  		// Grab all chars starting at "Commands:"
    89  		helpOut := strings.Split(result.Combined()[i:], "\n")
    90  		// Skip first line, it is just "Commands:"
    91  		helpOut = helpOut[1:]
    92  
    93  		// Create the list of commands we want to test
    94  		cmdsToTest := []string{}
    95  		for _, cmd := range helpOut {
    96  			// Stop on blank line or non-idented line
    97  			if cmd == "" || !unicode.IsSpace(rune(cmd[0])) {
    98  				break
    99  			}
   100  
   101  			// Grab just the first word of each line
   102  			cmd = strings.Split(strings.TrimSpace(cmd), " ")[0]
   103  			cmds = append(cmds, cmd) // Saving count for later
   104  
   105  			cmdsToTest = append(cmdsToTest, cmd)
   106  		}
   107  
   108  		// Add some 'two word' commands - would be nice to automatically
   109  		// calculate this list - somehow
   110  		cmdsToTest = append(cmdsToTest, "volume create")
   111  		cmdsToTest = append(cmdsToTest, "volume inspect")
   112  		cmdsToTest = append(cmdsToTest, "volume ls")
   113  		cmdsToTest = append(cmdsToTest, "volume rm")
   114  		cmdsToTest = append(cmdsToTest, "network connect")
   115  		cmdsToTest = append(cmdsToTest, "network create")
   116  		cmdsToTest = append(cmdsToTest, "network disconnect")
   117  		cmdsToTest = append(cmdsToTest, "network inspect")
   118  		cmdsToTest = append(cmdsToTest, "network ls")
   119  		cmdsToTest = append(cmdsToTest, "network rm")
   120  
   121  		if testEnv.ExperimentalDaemon() {
   122  			cmdsToTest = append(cmdsToTest, "checkpoint create")
   123  			cmdsToTest = append(cmdsToTest, "checkpoint ls")
   124  			cmdsToTest = append(cmdsToTest, "checkpoint rm")
   125  		}
   126  
   127  		// Divide the list of commands into go routines and  run the func testcommand on the commands in parallel
   128  		// to save runtime of test
   129  
   130  		errChan := make(chan error)
   131  
   132  		for index := 0; index < len(cmdsToTest); index++ {
   133  			go func(index int) {
   134  				errChan <- testCommand(cmdsToTest[index], newEnvs, scanForHome, home)
   135  			}(index)
   136  		}
   137  
   138  		for index := 0; index < len(cmdsToTest); index++ {
   139  			err := <-errChan
   140  			if err != nil {
   141  				c.Fatal(err)
   142  			}
   143  		}
   144  	}
   145  }
   146  
   147  func (s *DockerSuite) TestHelpExitCodesHelpOutput(c *check.C) {
   148  	// Test to make sure the exit code and output (stdout vs stderr) of
   149  	// various good and bad cases are what we expect
   150  
   151  	// docker : stdout=all, stderr=empty, rc=0
   152  	out, _ := dockerCmd(c)
   153  	// Be really pick
   154  	c.Assert(out, checker.Not(checker.HasSuffix), "\n\n", check.Commentf("Should not have a blank line at the end of 'docker'\n"))
   155  
   156  	// docker help: stdout=all, stderr=empty, rc=0
   157  	out, _ = dockerCmd(c, "help")
   158  	// Be really pick
   159  	c.Assert(out, checker.Not(checker.HasSuffix), "\n\n", check.Commentf("Should not have a blank line at the end of 'docker help'\n"))
   160  
   161  	// docker --help: stdout=all, stderr=empty, rc=0
   162  	out, _ = dockerCmd(c, "--help")
   163  	// Be really pick
   164  	c.Assert(out, checker.Not(checker.HasSuffix), "\n\n", check.Commentf("Should not have a blank line at the end of 'docker --help'\n"))
   165  
   166  	// docker inspect busybox: stdout=all, stderr=empty, rc=0
   167  	// Just making sure stderr is empty on valid cmd
   168  	out, _ = dockerCmd(c, "inspect", "busybox")
   169  	// Be really pick
   170  	c.Assert(out, checker.Not(checker.HasSuffix), "\n\n", check.Commentf("Should not have a blank line at the end of 'docker inspect busyBox'\n"))
   171  
   172  	// docker rm: stdout=empty, stderr=all, rc!=0
   173  	// testing the min arg error msg
   174  	icmd.RunCommand(dockerBinary, "rm").Assert(c, icmd.Expected{
   175  		ExitCode: 1,
   176  		Error:    "exit status 1",
   177  		Out:      "",
   178  		// Should not contain full help text but should contain info about
   179  		// # of args and Usage line
   180  		Err: "requires at least 1 argument",
   181  	})
   182  
   183  	// docker rm NoSuchContainer: stdout=empty, stderr=all, rc=0
   184  	// testing to make sure no blank line on error
   185  	result := icmd.RunCommand(dockerBinary, "rm", "NoSuchContainer")
   186  	result.Assert(c, icmd.Expected{
   187  		ExitCode: 1,
   188  		Error:    "exit status 1",
   189  		Out:      "",
   190  	})
   191  	// Be really picky
   192  	c.Assert(len(result.Stderr()), checker.Not(checker.Equals), 0)
   193  	c.Assert(result.Stderr(), checker.Not(checker.HasSuffix), "\n\n", check.Commentf("Should not have a blank line at the end of 'docker rm'\n"))
   194  
   195  	// docker BadCmd: stdout=empty, stderr=all, rc=0
   196  	icmd.RunCommand(dockerBinary, "BadCmd").Assert(c, icmd.Expected{
   197  		ExitCode: 1,
   198  		Error:    "exit status 1",
   199  		Out:      "",
   200  		Err:      "docker: 'BadCmd' is not a docker command.\nSee 'docker --help'\n",
   201  	})
   202  }
   203  
   204  func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) error {
   205  
   206  	args := strings.Split(cmd+" --help", " ")
   207  
   208  	// Check the full usage text
   209  	result := icmd.RunCmd(icmd.Cmd{
   210  		Command: append([]string{dockerBinary}, args...),
   211  		Env:     newEnvs,
   212  	})
   213  	err := result.Error
   214  	out := result.Stdout()
   215  	stderr := result.Stderr()
   216  	if len(stderr) != 0 {
   217  		return fmt.Errorf("Error on %q help. non-empty stderr:%q\n", cmd, stderr)
   218  	}
   219  	if strings.HasSuffix(out, "\n\n") {
   220  		return fmt.Errorf("Should not have blank line on %q\n", cmd)
   221  	}
   222  	if !strings.Contains(out, "--help") {
   223  		return fmt.Errorf("All commands should mention '--help'. Command '%v' did not.\n", cmd)
   224  	}
   225  
   226  	if err != nil {
   227  		return fmt.Errorf(out)
   228  	}
   229  
   230  	// Check each line for lots of stuff
   231  	lines := strings.Split(out, "\n")
   232  	for _, line := range lines {
   233  		i := strings.Index(line, "~")
   234  		if i >= 0 && i != len(line)-1 && line[i+1] != '/' {
   235  			return fmt.Errorf("Help for %q should not have used ~:\n%s", cmd, line)
   236  		}
   237  
   238  		// If a line starts with 4 spaces then assume someone
   239  		// added a multi-line description for an option and we need
   240  		// to flag it
   241  		if strings.HasPrefix(line, "    ") &&
   242  			!strings.HasPrefix(strings.TrimLeft(line, " "), "--") {
   243  			return fmt.Errorf("Help for %q should not have a multi-line option", cmd)
   244  		}
   245  
   246  		// Options should NOT end with a period
   247  		if strings.HasPrefix(line, "  -") && strings.HasSuffix(line, ".") {
   248  			return fmt.Errorf("Help for %q should not end with a period: %s", cmd, line)
   249  		}
   250  
   251  		// Options should NOT end with a space
   252  		if strings.HasSuffix(line, " ") {
   253  			return fmt.Errorf("Help for %q should not end with a space: %s", cmd, line)
   254  		}
   255  
   256  	}
   257  
   258  	// For each command make sure we generate an error
   259  	// if we give a bad arg
   260  	args = strings.Split(cmd+" --badArg", " ")
   261  
   262  	out, _, err = dockerCmdWithError(args...)
   263  	if err == nil {
   264  		return fmt.Errorf(out)
   265  	}
   266  
   267  	// Be really picky
   268  	if strings.HasSuffix(stderr, "\n\n") {
   269  		return fmt.Errorf("Should not have a blank line at the end of 'docker rm'\n")
   270  	}
   271  
   272  	// Now make sure that each command will print a short-usage
   273  	// (not a full usage - meaning no opts section) if we
   274  	// are missing a required arg or pass in a bad arg
   275  
   276  	// These commands will never print a short-usage so don't test
   277  	noShortUsage := map[string]string{
   278  		"images":        "",
   279  		"login":         "",
   280  		"logout":        "",
   281  		"network":       "",
   282  		"stats":         "",
   283  		"volume create": "",
   284  	}
   285  
   286  	if _, ok := noShortUsage[cmd]; !ok {
   287  		// skipNoArgs are ones that we don't want to try w/o
   288  		// any args. Either because it'll hang the test or
   289  		// lead to incorrect test result (like false negative).
   290  		// Whatever the reason, skip trying to run w/o args and
   291  		// jump to trying with a bogus arg.
   292  		skipNoArgs := map[string]struct{}{
   293  			"daemon": {},
   294  			"events": {},
   295  			"load":   {},
   296  		}
   297  
   298  		var result *icmd.Result
   299  		if _, ok := skipNoArgs[cmd]; !ok {
   300  			result = dockerCmdWithResult(strings.Split(cmd, " ")...)
   301  		}
   302  
   303  		// If its ok w/o any args then try again with an arg
   304  		if result == nil || result.ExitCode == 0 {
   305  			result = dockerCmdWithResult(strings.Split(cmd+" badArg", " ")...)
   306  		}
   307  
   308  		if err := result.Compare(icmd.Expected{
   309  			Out:      icmd.None,
   310  			Err:      "\nUsage:",
   311  			ExitCode: 1,
   312  		}); err != nil {
   313  			return err
   314  		}
   315  
   316  		stderr := result.Stderr()
   317  		// Shouldn't have full usage
   318  		if strings.Contains(stderr, "--help=false") {
   319  			return fmt.Errorf("Should not have full usage on %q:%v", result.Cmd.Args, stderr)
   320  		}
   321  		if strings.HasSuffix(stderr, "\n\n") {
   322  			return fmt.Errorf("Should not have a blank line on %q\n%v", result.Cmd.Args, stderr)
   323  		}
   324  	}
   325  
   326  	return nil
   327  }