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