github.com/MaximeAubanel/moby@v1.13.1/integration-cli/docker_cli_help_test.go (about)

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