github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/os/exec/lp_windows_test.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Use an external test to avoid os/exec -> internal/testenv -> os/exec 6 // circular dependency. 7 8 package exec_test 9 10 import ( 11 "fmt" 12 "internal/testenv" 13 "io" 14 "io/ioutil" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "strconv" 19 "strings" 20 "testing" 21 ) 22 23 func installExe(t *testing.T, dest, src string) { 24 fsrc, err := os.Open(src) 25 if err != nil { 26 t.Fatal("os.Open failed: ", err) 27 } 28 defer fsrc.Close() 29 fdest, err := os.Create(dest) 30 if err != nil { 31 t.Fatal("os.Create failed: ", err) 32 } 33 defer fdest.Close() 34 _, err = io.Copy(fdest, fsrc) 35 if err != nil { 36 t.Fatal("io.Copy failed: ", err) 37 } 38 } 39 40 func installBat(t *testing.T, dest string) { 41 f, err := os.Create(dest) 42 if err != nil { 43 t.Fatalf("failed to create batch file: %v", err) 44 } 45 defer f.Close() 46 fmt.Fprintf(f, "@echo %s\n", dest) 47 } 48 49 func installProg(t *testing.T, dest, srcExe string) { 50 err := os.MkdirAll(filepath.Dir(dest), 0700) 51 if err != nil { 52 t.Fatal("os.MkdirAll failed: ", err) 53 } 54 if strings.ToLower(filepath.Ext(dest)) == ".bat" { 55 installBat(t, dest) 56 return 57 } 58 installExe(t, dest, srcExe) 59 } 60 61 type lookPathTest struct { 62 rootDir string 63 PATH string 64 PATHEXT string 65 files []string 66 searchFor string 67 fails bool // test is expected to fail 68 } 69 70 func (test lookPathTest) runProg(t *testing.T, env []string, args ...string) (string, error) { 71 cmd := exec.Command(args[0], args[1:]...) 72 cmd.Env = env 73 cmd.Dir = test.rootDir 74 args[0] = filepath.Base(args[0]) 75 cmdText := fmt.Sprintf("%q command", strings.Join(args, " ")) 76 out, err := cmd.CombinedOutput() 77 if (err != nil) != test.fails { 78 if test.fails { 79 t.Fatalf("test=%+v: %s succeeded, but expected to fail", test, cmdText) 80 } 81 t.Fatalf("test=%+v: %s failed, but expected to succeed: %v - %v", test, cmdText, err, string(out)) 82 } 83 if err != nil { 84 return "", fmt.Errorf("test=%+v: %s failed: %v - %v", test, cmdText, err, string(out)) 85 } 86 // normalise program output 87 p := string(out) 88 // trim terminating \r and \n that batch file outputs 89 for len(p) > 0 && (p[len(p)-1] == '\n' || p[len(p)-1] == '\r') { 90 p = p[:len(p)-1] 91 } 92 if !filepath.IsAbs(p) { 93 return p, nil 94 } 95 if p[:len(test.rootDir)] != test.rootDir { 96 t.Fatalf("test=%+v: %s output is wrong: %q must have %q prefix", test, cmdText, p, test.rootDir) 97 } 98 return p[len(test.rootDir)+1:], nil 99 } 100 101 func updateEnv(env []string, name, value string) []string { 102 for i, e := range env { 103 if strings.HasPrefix(strings.ToUpper(e), name+"=") { 104 env[i] = name + "=" + value 105 return env 106 } 107 } 108 return append(env, name+"="+value) 109 } 110 111 func createEnv(dir, PATH, PATHEXT string) []string { 112 env := os.Environ() 113 env = updateEnv(env, "PATHEXT", PATHEXT) 114 // Add dir in front of every directory in the PATH. 115 dirs := filepath.SplitList(PATH) 116 for i := range dirs { 117 dirs[i] = filepath.Join(dir, dirs[i]) 118 } 119 path := strings.Join(dirs, ";") 120 env = updateEnv(env, "PATH", os.Getenv("SystemRoot")+"/System32;"+path) 121 return env 122 } 123 124 // createFiles copies srcPath file into multiply files. 125 // It uses dir as prefix for all destination files. 126 func createFiles(t *testing.T, dir string, files []string, srcPath string) { 127 for _, f := range files { 128 installProg(t, filepath.Join(dir, f), srcPath) 129 } 130 } 131 132 func (test lookPathTest) run(t *testing.T, tmpdir, printpathExe string) { 133 test.rootDir = tmpdir 134 createFiles(t, test.rootDir, test.files, printpathExe) 135 env := createEnv(test.rootDir, test.PATH, test.PATHEXT) 136 // Run "cmd.exe /c test.searchFor" with new environment and 137 // work directory set. All candidates are copies of printpath.exe. 138 // These will output their program paths when run. 139 should, errCmd := test.runProg(t, env, "cmd", "/c", test.searchFor) 140 // Run the lookpath program with new environment and work directory set. 141 env = append(env, "GO_WANT_HELPER_PROCESS=1") 142 have, errLP := test.runProg(t, env, os.Args[0], "-test.run=TestHelperProcess", "--", "lookpath", test.searchFor) 143 // Compare results. 144 if errCmd == nil && errLP == nil { 145 // both succeeded 146 if should != have { 147 t.Fatalf("test=%+v failed: expected to find %q, but found %q", test, should, have) 148 } 149 return 150 } 151 if errCmd != nil && errLP != nil { 152 // both failed -> continue 153 return 154 } 155 if errCmd != nil { 156 t.Fatal(errCmd) 157 } 158 if errLP != nil { 159 t.Fatal(errLP) 160 } 161 } 162 163 var lookPathTests = []lookPathTest{ 164 { 165 PATHEXT: `.COM;.EXE;.BAT`, 166 PATH: `p1;p2`, 167 files: []string{`p1\a.exe`, `p2\a.exe`, `p2\a`}, 168 searchFor: `a`, 169 }, 170 { 171 PATHEXT: `.COM;.EXE;.BAT`, 172 PATH: `p1.dir;p2.dir`, 173 files: []string{`p1.dir\a`, `p2.dir\a.exe`}, 174 searchFor: `a`, 175 }, 176 { 177 PATHEXT: `.COM;.EXE;.BAT`, 178 PATH: `p1;p2`, 179 files: []string{`p1\a.exe`, `p2\a.exe`}, 180 searchFor: `a.exe`, 181 }, 182 { 183 PATHEXT: `.COM;.EXE;.BAT`, 184 PATH: `p1;p2`, 185 files: []string{`p1\a.exe`, `p2\b.exe`}, 186 searchFor: `b`, 187 }, 188 { 189 PATHEXT: `.COM;.EXE;.BAT`, 190 PATH: `p1;p2`, 191 files: []string{`p1\b`, `p2\a`}, 192 searchFor: `a`, 193 fails: true, // TODO(brainman): do not know why this fails 194 }, 195 // If the command name specifies a path, the shell searches 196 // the specified path for an executable file matching 197 // the command name. If a match is found, the external 198 // command (the executable file) executes. 199 { 200 PATHEXT: `.COM;.EXE;.BAT`, 201 PATH: `p1;p2`, 202 files: []string{`p1\a.exe`, `p2\a.exe`}, 203 searchFor: `p2\a`, 204 }, 205 // If the command name specifies a path, the shell searches 206 // the specified path for an executable file matching the command 207 // name. ... If no match is found, the shell reports an error 208 // and command processing completes. 209 { 210 PATHEXT: `.COM;.EXE;.BAT`, 211 PATH: `p1;p2`, 212 files: []string{`p1\b.exe`, `p2\a.exe`}, 213 searchFor: `p2\b`, 214 fails: true, 215 }, 216 // If the command name does not specify a path, the shell 217 // searches the current directory for an executable file 218 // matching the command name. If a match is found, the external 219 // command (the executable file) executes. 220 { 221 PATHEXT: `.COM;.EXE;.BAT`, 222 PATH: `p1;p2`, 223 files: []string{`a`, `p1\a.exe`, `p2\a.exe`}, 224 searchFor: `a`, 225 }, 226 // The shell now searches each directory specified by the 227 // PATH environment variable, in the order listed, for an 228 // executable file matching the command name. If a match 229 // is found, the external command (the executable file) executes. 230 { 231 PATHEXT: `.COM;.EXE;.BAT`, 232 PATH: `p1;p2`, 233 files: []string{`p1\a.exe`, `p2\a.exe`}, 234 searchFor: `a`, 235 }, 236 // The shell now searches each directory specified by the 237 // PATH environment variable, in the order listed, for an 238 // executable file matching the command name. If no match 239 // is found, the shell reports an error and command processing 240 // completes. 241 { 242 PATHEXT: `.COM;.EXE;.BAT`, 243 PATH: `p1;p2`, 244 files: []string{`p1\a.exe`, `p2\a.exe`}, 245 searchFor: `b`, 246 fails: true, 247 }, 248 // If the command name includes a file extension, the shell 249 // searches each directory for the exact file name specified 250 // by the command name. 251 { 252 PATHEXT: `.COM;.EXE;.BAT`, 253 PATH: `p1;p2`, 254 files: []string{`p1\a.exe`, `p2\a.exe`}, 255 searchFor: `a.exe`, 256 }, 257 { 258 PATHEXT: `.COM;.EXE;.BAT`, 259 PATH: `p1;p2`, 260 files: []string{`p1\a.exe`, `p2\a.exe`}, 261 searchFor: `a.com`, 262 fails: true, // includes extension and not exact file name match 263 }, 264 { 265 PATHEXT: `.COM;.EXE;.BAT`, 266 PATH: `p1`, 267 files: []string{`p1\a.exe.exe`}, 268 searchFor: `a.exe`, 269 }, 270 { 271 PATHEXT: `.COM;.BAT`, 272 PATH: `p1;p2`, 273 files: []string{`p1\a.exe`, `p2\a.exe`}, 274 searchFor: `a.exe`, 275 }, 276 // If the command name does not include a file extension, the shell 277 // adds the extensions listed in the PATHEXT environment variable, 278 // one by one, and searches the directory for that file name. Note 279 // that the shell tries all possible file extensions in a specific 280 // directory before moving on to search the next directory 281 // (if there is one). 282 { 283 PATHEXT: `.COM;.EXE`, 284 PATH: `p1;p2`, 285 files: []string{`p1\a.bat`, `p2\a.exe`}, 286 searchFor: `a`, 287 }, 288 { 289 PATHEXT: `.COM;.EXE;.BAT`, 290 PATH: `p1;p2`, 291 files: []string{`p1\a.bat`, `p2\a.exe`}, 292 searchFor: `a`, 293 }, 294 { 295 PATHEXT: `.COM;.EXE;.BAT`, 296 PATH: `p1;p2`, 297 files: []string{`p1\a.bat`, `p1\a.exe`, `p2\a.bat`, `p2\a.exe`}, 298 searchFor: `a`, 299 }, 300 { 301 PATHEXT: `.COM`, 302 PATH: `p1;p2`, 303 files: []string{`p1\a.bat`, `p2\a.exe`}, 304 searchFor: `a`, 305 fails: true, // tried all extensions in PATHEXT, but none matches 306 }, 307 } 308 309 func TestLookPath(t *testing.T) { 310 tmp, err := ioutil.TempDir("", "TestLookPath") 311 if err != nil { 312 t.Fatal("TempDir failed: ", err) 313 } 314 defer os.RemoveAll(tmp) 315 316 printpathExe := buildPrintPathExe(t, tmp) 317 318 // Run all tests. 319 for i, test := range lookPathTests { 320 dir := filepath.Join(tmp, "d"+strconv.Itoa(i)) 321 err := os.Mkdir(dir, 0700) 322 if err != nil { 323 t.Fatal("Mkdir failed: ", err) 324 } 325 test.run(t, dir, printpathExe) 326 } 327 } 328 329 type commandTest struct { 330 PATH string 331 files []string 332 dir string 333 arg0 string 334 want string 335 fails bool // test is expected to fail 336 } 337 338 func (test commandTest) isSuccess(rootDir, output string, err error) error { 339 if err != nil { 340 return fmt.Errorf("test=%+v: exec: %v %v", test, err, output) 341 } 342 path := output 343 if path[:len(rootDir)] != rootDir { 344 return fmt.Errorf("test=%+v: %q must have %q prefix", test, path, rootDir) 345 } 346 path = path[len(rootDir)+1:] 347 if path != test.want { 348 return fmt.Errorf("test=%+v: want %q, got %q", test, test.want, path) 349 } 350 return nil 351 } 352 353 func (test commandTest) runOne(rootDir string, env []string, dir, arg0 string) error { 354 cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess", "--", "exec", dir, arg0) 355 cmd.Dir = rootDir 356 cmd.Env = env 357 output, err := cmd.CombinedOutput() 358 err = test.isSuccess(rootDir, string(output), err) 359 if (err != nil) != test.fails { 360 if test.fails { 361 return fmt.Errorf("test=%+v: succeeded, but expected to fail", test) 362 } 363 return err 364 } 365 return nil 366 } 367 368 func (test commandTest) run(t *testing.T, rootDir, printpathExe string) { 369 createFiles(t, rootDir, test.files, printpathExe) 370 PATHEXT := `.COM;.EXE;.BAT` 371 env := createEnv(rootDir, test.PATH, PATHEXT) 372 env = append(env, "GO_WANT_HELPER_PROCESS=1") 373 err := test.runOne(rootDir, env, test.dir, test.arg0) 374 if err != nil { 375 t.Error(err) 376 } 377 } 378 379 var commandTests = []commandTest{ 380 // testing commands with no slash, like `a.exe` 381 { 382 // should find a.exe in current directory 383 files: []string{`a.exe`}, 384 arg0: `a.exe`, 385 want: `a.exe`, 386 }, 387 { 388 // like above, but add PATH in attempt to break the test 389 PATH: `p2;p`, 390 files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, 391 arg0: `a.exe`, 392 want: `a.exe`, 393 }, 394 { 395 // like above, but use "a" instead of "a.exe" for command 396 PATH: `p2;p`, 397 files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, 398 arg0: `a`, 399 want: `a.exe`, 400 }, 401 // testing commands with slash, like `.\a.exe` 402 { 403 // should find p\a.exe 404 files: []string{`p\a.exe`}, 405 arg0: `p\a.exe`, 406 want: `p\a.exe`, 407 }, 408 { 409 // like above, but adding `.` in front of executable should still be OK 410 files: []string{`p\a.exe`}, 411 arg0: `.\p\a.exe`, 412 want: `p\a.exe`, 413 }, 414 { 415 // like above, but with PATH added in attempt to break it 416 PATH: `p2`, 417 files: []string{`p\a.exe`, `p2\a.exe`}, 418 arg0: `p\a.exe`, 419 want: `p\a.exe`, 420 }, 421 { 422 // like above, but make sure .exe is tried even for commands with slash 423 PATH: `p2`, 424 files: []string{`p\a.exe`, `p2\a.exe`}, 425 arg0: `p\a`, 426 want: `p\a.exe`, 427 }, 428 // tests commands, like `a.exe`, with c.Dir set 429 { 430 // should not find a.exe in p, because LookPath(`a.exe`) will fail 431 files: []string{`p\a.exe`}, 432 dir: `p`, 433 arg0: `a.exe`, 434 want: `p\a.exe`, 435 fails: true, 436 }, 437 { 438 // LookPath(`a.exe`) will find `.\a.exe`, but prefixing that with 439 // dir `p\a.exe` will refer to a non-existent file 440 files: []string{`a.exe`, `p\not_important_file`}, 441 dir: `p`, 442 arg0: `a.exe`, 443 want: `a.exe`, 444 fails: true, 445 }, 446 { 447 // like above, but making test succeed by installing file 448 // in referred destination (so LookPath(`a.exe`) will still 449 // find `.\a.exe`, but we successfully execute `p\a.exe`) 450 files: []string{`a.exe`, `p\a.exe`}, 451 dir: `p`, 452 arg0: `a.exe`, 453 want: `p\a.exe`, 454 }, 455 { 456 // like above, but add PATH in attempt to break the test 457 PATH: `p2;p`, 458 files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, 459 dir: `p`, 460 arg0: `a.exe`, 461 want: `p\a.exe`, 462 }, 463 { 464 // like above, but use "a" instead of "a.exe" for command 465 PATH: `p2;p`, 466 files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, 467 dir: `p`, 468 arg0: `a`, 469 want: `p\a.exe`, 470 }, 471 { 472 // finds `a.exe` in the PATH regardless of dir set 473 // because LookPath returns full path in that case 474 PATH: `p2;p`, 475 files: []string{`p\a.exe`, `p2\a.exe`}, 476 dir: `p`, 477 arg0: `a.exe`, 478 want: `p2\a.exe`, 479 }, 480 // tests commands, like `.\a.exe`, with c.Dir set 481 { 482 // should use dir when command is path, like ".\a.exe" 483 files: []string{`p\a.exe`}, 484 dir: `p`, 485 arg0: `.\a.exe`, 486 want: `p\a.exe`, 487 }, 488 { 489 // like above, but with PATH added in attempt to break it 490 PATH: `p2`, 491 files: []string{`p\a.exe`, `p2\a.exe`}, 492 dir: `p`, 493 arg0: `.\a.exe`, 494 want: `p\a.exe`, 495 }, 496 { 497 // like above, but make sure .exe is tried even for commands with slash 498 PATH: `p2`, 499 files: []string{`p\a.exe`, `p2\a.exe`}, 500 dir: `p`, 501 arg0: `.\a`, 502 want: `p\a.exe`, 503 }, 504 } 505 506 func TestCommand(t *testing.T) { 507 tmp, err := ioutil.TempDir("", "TestCommand") 508 if err != nil { 509 t.Fatal("TempDir failed: ", err) 510 } 511 defer os.RemoveAll(tmp) 512 513 printpathExe := buildPrintPathExe(t, tmp) 514 515 // Run all tests. 516 for i, test := range commandTests { 517 dir := filepath.Join(tmp, "d"+strconv.Itoa(i)) 518 err := os.Mkdir(dir, 0700) 519 if err != nil { 520 t.Fatal("Mkdir failed: ", err) 521 } 522 test.run(t, dir, printpathExe) 523 } 524 } 525 526 // buildPrintPathExe creates a Go program that prints its own path. 527 // dir is a temp directory where executable will be created. 528 // The function returns full path to the created program. 529 func buildPrintPathExe(t *testing.T, dir string) string { 530 const name = "printpath" 531 srcname := name + ".go" 532 err := ioutil.WriteFile(filepath.Join(dir, srcname), []byte(printpathSrc), 0644) 533 if err != nil { 534 t.Fatalf("failed to create source: %v", err) 535 } 536 if err != nil { 537 t.Fatalf("failed to execute template: %v", err) 538 } 539 outname := name + ".exe" 540 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", outname, srcname) 541 cmd.Dir = dir 542 out, err := cmd.CombinedOutput() 543 if err != nil { 544 t.Fatalf("failed to build executable: %v - %v", err, string(out)) 545 } 546 return filepath.Join(dir, outname) 547 } 548 549 const printpathSrc = ` 550 package main 551 552 import ( 553 "os" 554 "syscall" 555 "unicode/utf16" 556 "unsafe" 557 ) 558 559 func getMyName() (string, error) { 560 var sysproc = syscall.MustLoadDLL("kernel32.dll").MustFindProc("GetModuleFileNameW") 561 b := make([]uint16, syscall.MAX_PATH) 562 r, _, err := sysproc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b))) 563 n := uint32(r) 564 if n == 0 { 565 return "", err 566 } 567 return string(utf16.Decode(b[0:n])), nil 568 } 569 570 func main() { 571 path, err := getMyName() 572 if err != nil { 573 os.Stderr.Write([]byte("getMyName failed: " + err.Error() + "\n")) 574 os.Exit(1) 575 } 576 os.Stdout.Write([]byte(path)) 577 } 578 `