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