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 }