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