github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/integration-cli/docker_cli_help_test.go (about)

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