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