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