github.com/liujq9674git/golang-src-1.7@v0.0.0-20230517174348-17f6ec47f3f8/src/path/filepath/path_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 filepath_test
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"reflect"
    15  	"strings"
    16  	"syscall"
    17  	"testing"
    18  )
    19  
    20  func init() {
    21  	tmpdir, err := ioutil.TempDir("", "symtest")
    22  	if err != nil {
    23  		panic("failed to create temp directory: " + err.Error())
    24  	}
    25  	defer os.RemoveAll(tmpdir)
    26  
    27  	err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
    28  	if err == nil {
    29  		return
    30  	}
    31  
    32  	err = err.(*os.LinkError).Err
    33  	switch err {
    34  	case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
    35  		supportsSymlinks = false
    36  	}
    37  }
    38  
    39  func TestWinSplitListTestsAreValid(t *testing.T) {
    40  	comspec := os.Getenv("ComSpec")
    41  	if comspec == "" {
    42  		t.Fatal("%ComSpec% must be set")
    43  	}
    44  
    45  	for ti, tt := range winsplitlisttests {
    46  		testWinSplitListTestIsValid(t, ti, tt, comspec)
    47  	}
    48  }
    49  
    50  func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest,
    51  	comspec string) {
    52  
    53  	const (
    54  		cmdfile             = `printdir.cmd`
    55  		perm    os.FileMode = 0700
    56  	)
    57  
    58  	tmp, err := ioutil.TempDir("", "testWinSplitListTestIsValid")
    59  	if err != nil {
    60  		t.Fatalf("TempDir failed: %v", err)
    61  	}
    62  	defer os.RemoveAll(tmp)
    63  
    64  	for i, d := range tt.result {
    65  		if d == "" {
    66  			continue
    67  		}
    68  		if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" ||
    69  			cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) {
    70  			t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d)
    71  			return
    72  		}
    73  		dd := filepath.Join(tmp, d)
    74  		if _, err := os.Stat(dd); err == nil {
    75  			t.Errorf("%d,%d: %#q already exists", ti, i, d)
    76  			return
    77  		}
    78  		if err = os.MkdirAll(dd, perm); err != nil {
    79  			t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err)
    80  			return
    81  		}
    82  		fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n")
    83  		if err = ioutil.WriteFile(fn, data, perm); err != nil {
    84  			t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err)
    85  			return
    86  		}
    87  	}
    88  
    89  	for i, d := range tt.result {
    90  		if d == "" {
    91  			continue
    92  		}
    93  		exp := []byte(d + "\r\n")
    94  		cmd := &exec.Cmd{
    95  			Path: comspec,
    96  			Args: []string{`/c`, cmdfile},
    97  			Env:  []string{`Path=` + tt.list},
    98  			Dir:  tmp,
    99  		}
   100  		out, err := cmd.CombinedOutput()
   101  		switch {
   102  		case err != nil:
   103  			t.Errorf("%d,%d: execution error %v\n%q", ti, i, err, out)
   104  			return
   105  		case !reflect.DeepEqual(out, exp):
   106  			t.Errorf("%d,%d: expected %#q, got %#q", ti, i, exp, out)
   107  			return
   108  		default:
   109  			// unshadow cmdfile in next directory
   110  			err = os.Remove(filepath.Join(tmp, d, cmdfile))
   111  			if err != nil {
   112  				t.Fatalf("Remove test command failed: %v", err)
   113  			}
   114  		}
   115  	}
   116  }
   117  
   118  // TestEvalSymlinksCanonicalNames verify that EvalSymlinks
   119  // returns "canonical" path names on windows.
   120  func TestEvalSymlinksCanonicalNames(t *testing.T) {
   121  	tmp, err := ioutil.TempDir("", "evalsymlinkcanonical")
   122  	if err != nil {
   123  		t.Fatal("creating temp dir:", err)
   124  	}
   125  	defer os.RemoveAll(tmp)
   126  
   127  	// ioutil.TempDir might return "non-canonical" name.
   128  	cTmpName, err := filepath.EvalSymlinks(tmp)
   129  	if err != nil {
   130  		t.Errorf("EvalSymlinks(%q) error: %v", tmp, err)
   131  	}
   132  
   133  	dirs := []string{
   134  		"test",
   135  		"test/dir",
   136  		"testing_long_dir",
   137  		"TEST2",
   138  	}
   139  
   140  	for _, d := range dirs {
   141  		dir := filepath.Join(cTmpName, d)
   142  		err := os.Mkdir(dir, 0755)
   143  		if err != nil {
   144  			t.Fatal(err)
   145  		}
   146  		cname, err := filepath.EvalSymlinks(dir)
   147  		if err != nil {
   148  			t.Errorf("EvalSymlinks(%q) error: %v", dir, err)
   149  			continue
   150  		}
   151  		if dir != cname {
   152  			t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", dir, cname, dir)
   153  			continue
   154  		}
   155  		// test non-canonical names
   156  		test := strings.ToUpper(dir)
   157  		p, err := filepath.EvalSymlinks(test)
   158  		if err != nil {
   159  			t.Errorf("EvalSymlinks(%q) error: %v", test, err)
   160  			continue
   161  		}
   162  		if p != cname {
   163  			t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
   164  			continue
   165  		}
   166  		// another test
   167  		test = strings.ToLower(dir)
   168  		p, err = filepath.EvalSymlinks(test)
   169  		if err != nil {
   170  			t.Errorf("EvalSymlinks(%q) error: %v", test, err)
   171  			continue
   172  		}
   173  		if p != cname {
   174  			t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
   175  			continue
   176  		}
   177  	}
   178  }
   179  
   180  // checkVolume8dot3Setting runs "fsutil 8dot3name query c:" command
   181  // (where c: is vol parameter) to discover "8dot3 name creation state".
   182  // The state is combination of 2 flags. The global flag controls if it
   183  // is per volume or global setting:
   184  //   0 - Enable 8dot3 name creation on all volumes on the system
   185  //   1 - Disable 8dot3 name creation on all volumes on the system
   186  //   2 - Set 8dot3 name creation on a per volume basis
   187  //   3 - Disable 8dot3 name creation on all volumes except the system volume
   188  // If global flag is set to 2, then per-volume flag needs to be examined:
   189  //   0 - Enable 8dot3 name creation on this volume
   190  //   1 - Disable 8dot3 name creation on this volume
   191  // checkVolume8dot3Setting verifies that "8dot3 name creation" flags
   192  // are set to 2 and 0, if enabled parameter is true, or 2 and 1, if enabled
   193  // is false. Otherwise checkVolume8dot3Setting returns error.
   194  func checkVolume8dot3Setting(vol string, enabled bool) error {
   195  	// It appears, on some systems "fsutil 8dot3name query ..." command always
   196  	// exits with error. Ignore exit code, and look at fsutil output instead.
   197  	out, _ := exec.Command("fsutil", "8dot3name", "query", vol).CombinedOutput()
   198  	// Check that system has "Volume level setting" set.
   199  	expected := "The registry state of NtfsDisable8dot3NameCreation is 2, the default (Volume level setting)"
   200  	if !strings.Contains(string(out), expected) {
   201  		// Windows 10 version of fsutil has different output message.
   202  		expectedWindow10 := "The registry state is: 2 (Per volume setting - the default)"
   203  		if !strings.Contains(string(out), expectedWindow10) {
   204  			return fmt.Errorf("fsutil output should contain %q, but is %q", expected, string(out))
   205  		}
   206  	}
   207  	// Now check the volume setting.
   208  	expected = "Based on the above two settings, 8dot3 name creation is %s on %s"
   209  	if enabled {
   210  		expected = fmt.Sprintf(expected, "enabled", vol)
   211  	} else {
   212  		expected = fmt.Sprintf(expected, "disabled", vol)
   213  	}
   214  	if !strings.Contains(string(out), expected) {
   215  		return fmt.Errorf("unexpected fsutil output: %q", string(out))
   216  	}
   217  	return nil
   218  }
   219  
   220  func setVolume8dot3Setting(vol string, enabled bool) error {
   221  	cmd := []string{"fsutil", "8dot3name", "set", vol}
   222  	if enabled {
   223  		cmd = append(cmd, "0")
   224  	} else {
   225  		cmd = append(cmd, "1")
   226  	}
   227  	// It appears, on some systems "fsutil 8dot3name set ..." command always
   228  	// exits with error. Ignore exit code, and look at fsutil output instead.
   229  	out, _ := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
   230  	if string(out) != "\r\nSuccessfully set 8dot3name behavior.\r\n" {
   231  		// Windows 10 version of fsutil has different output message.
   232  		expectedWindow10 := "Successfully %s 8dot3name generation on %s\r\n"
   233  		if enabled {
   234  			expectedWindow10 = fmt.Sprintf(expectedWindow10, "enabled", vol)
   235  		} else {
   236  			expectedWindow10 = fmt.Sprintf(expectedWindow10, "disabled", vol)
   237  		}
   238  		if string(out) != expectedWindow10 {
   239  			return fmt.Errorf("%v command failed: %q", cmd, string(out))
   240  		}
   241  	}
   242  	return nil
   243  }
   244  
   245  var runFSModifyTests = flag.Bool("run_fs_modify_tests", false, "run tests which modify filesystem parameters")
   246  
   247  // This test assumes registry state of NtfsDisable8dot3NameCreation is 2,
   248  // the default (Volume level setting).
   249  func TestEvalSymlinksCanonicalNamesWith8dot3Disabled(t *testing.T) {
   250  	if !*runFSModifyTests {
   251  		t.Skip("skipping test that modifies file system setting; enable with -run_fs_modify_tests")
   252  	}
   253  	tempVol := filepath.VolumeName(os.TempDir())
   254  	if len(tempVol) != 2 {
   255  		t.Fatalf("unexpected temp volume name %q", tempVol)
   256  	}
   257  
   258  	err := checkVolume8dot3Setting(tempVol, true)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	err = setVolume8dot3Setting(tempVol, false)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	defer func() {
   267  		err := setVolume8dot3Setting(tempVol, true)
   268  		if err != nil {
   269  			t.Fatal(err)
   270  		}
   271  		err = checkVolume8dot3Setting(tempVol, true)
   272  		if err != nil {
   273  			t.Fatal(err)
   274  		}
   275  	}()
   276  	err = checkVolume8dot3Setting(tempVol, false)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	TestEvalSymlinksCanonicalNames(t)
   281  }
   282  
   283  func TestToNorm(t *testing.T) {
   284  	stubBase := func(path string) (string, error) {
   285  		vol := filepath.VolumeName(path)
   286  		path = path[len(vol):]
   287  
   288  		if strings.Contains(path, "/") {
   289  			return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
   290  		}
   291  
   292  		if path == "" || path == "." || path == `\` {
   293  			return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
   294  		}
   295  
   296  		i := strings.LastIndexByte(path, filepath.Separator)
   297  		if i == len(path)-1 { // trailing '\' is invalid
   298  			return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
   299  		}
   300  		if i == -1 {
   301  			return strings.ToUpper(path), nil
   302  		}
   303  
   304  		return strings.ToUpper(path[i+1:]), nil
   305  	}
   306  
   307  	// On this test, toNorm should be same as string.ToUpper(filepath.Clean(path)) except empty string.
   308  	tests := []struct {
   309  		arg  string
   310  		want string
   311  	}{
   312  		{"", ""},
   313  		{".", "."},
   314  		{"./foo/bar", `FOO\BAR`},
   315  		{"/", `\`},
   316  		{"/foo/bar", `\FOO\BAR`},
   317  		{"/foo/bar/baz/qux", `\FOO\BAR\BAZ\QUX`},
   318  		{"foo/bar", `FOO\BAR`},
   319  		{"C:/foo/bar", `C:\FOO\BAR`},
   320  		{"C:foo/bar", `C:FOO\BAR`},
   321  		{"c:/foo/bar", `C:\FOO\BAR`},
   322  		{"C:/foo/bar", `C:\FOO\BAR`},
   323  		{"C:/foo/bar/", `C:\FOO\BAR`},
   324  		{`C:\foo\bar`, `C:\FOO\BAR`},
   325  		{`C:\foo/bar\`, `C:\FOO\BAR`},
   326  		{"C:/ふー/バー", `C:\ふー\バー`},
   327  	}
   328  
   329  	for _, test := range tests {
   330  		got, err := filepath.ToNorm(test.arg, stubBase)
   331  		if err != nil {
   332  			t.Errorf("toNorm(%s) failed: %v\n", test.arg, err)
   333  		} else if got != test.want {
   334  			t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want)
   335  		}
   336  	}
   337  
   338  	testPath := `{{tmp}}\test\foo\bar`
   339  
   340  	testsDir := []struct {
   341  		wd   string
   342  		arg  string
   343  		want string
   344  	}{
   345  		// test absolute paths
   346  		{".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`},
   347  		{".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`},
   348  		{".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`},
   349  		{".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`},
   350  
   351  		// test relative paths begin with drive letter
   352  		{`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`},
   353  		{`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`},
   354  		{`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`},
   355  		{`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`},
   356  		{`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`},
   357  		{`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`},
   358  
   359  		// test relative paths begin with '\'
   360  		{".", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
   361  		{".", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
   362  		{".", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
   363  		{".", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`},
   364  
   365  		// test relative paths begin without '\'
   366  		{`{{tmp}}\test`, ".", `.`},
   367  		{`{{tmp}}\test`, "..", `..`},
   368  		{`{{tmp}}\test`, `foo\bar`, `foo\bar`},
   369  		{`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
   370  		{`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
   371  		{`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
   372  	}
   373  
   374  	cwd, err := os.Getwd()
   375  	if err != nil {
   376  		t.Fatal(err)
   377  	}
   378  
   379  	defer func() {
   380  		err := os.Chdir(cwd)
   381  		if err != nil {
   382  			t.Fatal(err)
   383  		}
   384  	}()
   385  
   386  	tmp, err := ioutil.TempDir("", "testToNorm")
   387  	if err != nil {
   388  		t.Fatal(err)
   389  	}
   390  	defer os.RemoveAll(tmp)
   391  
   392  	// ioutil.TempDir might return "non-canonical" name.
   393  	tmp, err = filepath.EvalSymlinks(tmp)
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  
   398  	err = os.MkdirAll(strings.Replace(testPath, "{{tmp}}", tmp, -1), 0777)
   399  	if err != nil {
   400  		t.Fatal(err)
   401  	}
   402  
   403  	tmpVol := filepath.VolumeName(tmp)
   404  	tmpNoVol := tmp[len(tmpVol):]
   405  
   406  	for _, test := range testsDir {
   407  		wd := strings.Replace(strings.Replace(strings.Replace(test.wd, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1)
   408  		arg := strings.Replace(strings.Replace(strings.Replace(test.arg, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1)
   409  		want := strings.Replace(strings.Replace(strings.Replace(test.want, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1)
   410  
   411  		if test.wd == "." {
   412  			err := os.Chdir(cwd)
   413  			if err != nil {
   414  				t.Error(err)
   415  
   416  				continue
   417  			}
   418  		} else {
   419  			err := os.Chdir(wd)
   420  			if err != nil {
   421  				t.Error(err)
   422  
   423  				continue
   424  			}
   425  		}
   426  
   427  		got, err := filepath.ToNorm(arg, filepath.NormBase)
   428  		if err != nil {
   429  			t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd)
   430  		} else if got != want {
   431  			t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd)
   432  		}
   433  	}
   434  }