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