github.com/koderover/helm@v2.17.0+incompatible/internal/third_party/dep/fs/fs_test.go (about)

     1  /*
     2  Copyright (c) for portions of fs_test.go are held by The Go Authors, 2016 and are provided under
     3  the BSD license.
     4  
     5  Redistribution and use in source and binary forms, with or without
     6  modification, are permitted provided that the following conditions are
     7  met:
     8  
     9     * Redistributions of source code must retain the above copyright
    10  notice, this list of conditions and the following disclaimer.
    11     * Redistributions in binary form must reproduce the above
    12  copyright notice, this list of conditions and the following disclaimer
    13  in the documentation and/or other materials provided with the
    14  distribution.
    15     * Neither the name of Google Inc. nor the names of its
    16  contributors may be used to endorse or promote products derived from
    17  this software without specific prior written permission.
    18  
    19  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    20  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    21  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    22  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    23  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    24  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    25  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    26  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    27  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    28  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    29  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    30  */
    31  
    32  package fs
    33  
    34  import (
    35  	"io/ioutil"
    36  	"os"
    37  	"os/exec"
    38  	"os/user"
    39  	"path/filepath"
    40  	"runtime"
    41  	"sync"
    42  	"testing"
    43  )
    44  
    45  var (
    46  	mu sync.Mutex
    47  )
    48  
    49  func TestRenameWithFallback(t *testing.T) {
    50  	dir, err := ioutil.TempDir("", "helm-tmp")
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	defer os.RemoveAll(dir)
    55  
    56  	if err = RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")); err == nil {
    57  		t.Fatal("expected an error for non existing file, but got nil")
    58  	}
    59  
    60  	srcpath := filepath.Join(dir, "src")
    61  
    62  	if srcf, err := os.Create(srcpath); err != nil {
    63  		t.Fatal(err)
    64  	} else {
    65  		srcf.Close()
    66  	}
    67  
    68  	if err = RenameWithFallback(srcpath, filepath.Join(dir, "dst")); err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	srcpath = filepath.Join(dir, "a")
    73  	if err = os.MkdirAll(srcpath, 0777); err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	dstpath := filepath.Join(dir, "b")
    78  	if err = os.MkdirAll(dstpath, 0777); err != nil {
    79  		t.Fatal(err)
    80  	}
    81  
    82  	if err = RenameWithFallback(srcpath, dstpath); err == nil {
    83  		t.Fatal("expected an error if dst is an existing directory, but got nil")
    84  	}
    85  }
    86  
    87  func TestCopyDir(t *testing.T) {
    88  	dir, err := ioutil.TempDir("", "helm-tmp")
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  	defer os.RemoveAll(dir)
    93  
    94  	srcdir := filepath.Join(dir, "src")
    95  	if err := os.MkdirAll(srcdir, 0755); err != nil {
    96  		t.Fatal(err)
    97  	}
    98  
    99  	files := []struct {
   100  		path     string
   101  		contents string
   102  		fi       os.FileInfo
   103  	}{
   104  		{path: "myfile", contents: "hello world"},
   105  		{path: filepath.Join("subdir", "file"), contents: "subdir file"},
   106  	}
   107  
   108  	// Create structure indicated in 'files'
   109  	for i, file := range files {
   110  		fn := filepath.Join(srcdir, file.path)
   111  		dn := filepath.Dir(fn)
   112  		if err = os.MkdirAll(dn, 0755); err != nil {
   113  			t.Fatal(err)
   114  		}
   115  
   116  		fh, err := os.Create(fn)
   117  		if err != nil {
   118  			t.Fatal(err)
   119  		}
   120  
   121  		if _, err = fh.Write([]byte(file.contents)); err != nil {
   122  			t.Fatal(err)
   123  		}
   124  		fh.Close()
   125  
   126  		files[i].fi, err = os.Stat(fn)
   127  		if err != nil {
   128  			t.Fatal(err)
   129  		}
   130  	}
   131  
   132  	destdir := filepath.Join(dir, "dest")
   133  	if err := CopyDir(srcdir, destdir); err != nil {
   134  		t.Fatal(err)
   135  	}
   136  
   137  	// Compare copy against structure indicated in 'files'
   138  	for _, file := range files {
   139  		fn := filepath.Join(srcdir, file.path)
   140  		dn := filepath.Dir(fn)
   141  		dirOK, err := IsDir(dn)
   142  		if err != nil {
   143  			t.Fatal(err)
   144  		}
   145  		if !dirOK {
   146  			t.Fatalf("expected %s to be a directory", dn)
   147  		}
   148  
   149  		got, err := ioutil.ReadFile(fn)
   150  		if err != nil {
   151  			t.Fatal(err)
   152  		}
   153  
   154  		if file.contents != string(got) {
   155  			t.Fatalf("expected: %s, got: %s", file.contents, string(got))
   156  		}
   157  
   158  		gotinfo, err := os.Stat(fn)
   159  		if err != nil {
   160  			t.Fatal(err)
   161  		}
   162  
   163  		if file.fi.Mode() != gotinfo.Mode() {
   164  			t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v",
   165  				file.path, file.fi.Mode(), fn, gotinfo.Mode())
   166  		}
   167  	}
   168  }
   169  
   170  func TestCopyDirFail_SrcInaccessible(t *testing.T) {
   171  	if runtime.GOOS == "windows" {
   172  		// XXX: setting permissions works differently in
   173  		// Microsoft Windows. Skipping this this until a
   174  		// compatible implementation is provided.
   175  		t.Skip("skipping on windows")
   176  	}
   177  
   178  	var currentUser, err = user.Current()
   179  
   180  	if err != nil {
   181  		t.Fatalf("Failed to get name of current user: %s", err)
   182  	}
   183  
   184  	if currentUser.Name == "root" {
   185  		// Skipping if root, because all files are accessible
   186  		t.Skip("Skipping for root user")
   187  	}
   188  
   189  	var srcdir, dstdir string
   190  
   191  	cleanup := setupInaccessibleDir(t, func(dir string) error {
   192  		srcdir = filepath.Join(dir, "src")
   193  		return os.MkdirAll(srcdir, 0755)
   194  	})
   195  	defer cleanup()
   196  
   197  	dir, err := ioutil.TempDir("", "helm-tmp")
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  	defer os.RemoveAll(dir)
   202  
   203  	dstdir = filepath.Join(dir, "dst")
   204  	if err = CopyDir(srcdir, dstdir); err == nil {
   205  		t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
   206  	}
   207  }
   208  
   209  func TestCopyDirFail_DstInaccessible(t *testing.T) {
   210  	if runtime.GOOS == "windows" {
   211  		// XXX: setting permissions works differently in
   212  		// Microsoft Windows. Skipping this this until a
   213  		// compatible implementation is provided.
   214  		t.Skip("skipping on windows")
   215  	}
   216  
   217  	var currentUser, err = user.Current()
   218  
   219  	if err != nil {
   220  		t.Fatalf("Failed to get name of current user: %s", err)
   221  	}
   222  
   223  	if currentUser.Name == "root" {
   224  		// Skipping if root, because all files are accessible
   225  		t.Skip("Skipping for root user")
   226  	}
   227  
   228  	var srcdir, dstdir string
   229  
   230  	dir, err := ioutil.TempDir("", "helm-tmp")
   231  	if err != nil {
   232  		t.Fatal(err)
   233  	}
   234  	defer os.RemoveAll(dir)
   235  
   236  	srcdir = filepath.Join(dir, "src")
   237  	if err = os.MkdirAll(srcdir, 0755); err != nil {
   238  		t.Fatal(err)
   239  	}
   240  
   241  	cleanup := setupInaccessibleDir(t, func(dir string) error {
   242  		dstdir = filepath.Join(dir, "dst")
   243  		return nil
   244  	})
   245  	defer cleanup()
   246  
   247  	if err := CopyDir(srcdir, dstdir); err == nil {
   248  		t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
   249  	}
   250  }
   251  
   252  func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
   253  	var srcdir, dstdir string
   254  
   255  	dir, err := ioutil.TempDir("", "helm-tmp")
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	defer os.RemoveAll(dir)
   260  
   261  	srcdir = filepath.Join(dir, "src")
   262  	if _, err = os.Create(srcdir); err != nil {
   263  		t.Fatal(err)
   264  	}
   265  
   266  	dstdir = filepath.Join(dir, "dst")
   267  
   268  	if err = CopyDir(srcdir, dstdir); err == nil {
   269  		t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
   270  	}
   271  
   272  	if err != errSrcNotDir {
   273  		t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err)
   274  	}
   275  
   276  }
   277  
   278  func TestCopyDirFail_DstExists(t *testing.T) {
   279  	var srcdir, dstdir string
   280  
   281  	dir, err := ioutil.TempDir("", "helm-tmp")
   282  	if err != nil {
   283  		t.Fatal(err)
   284  	}
   285  	defer os.RemoveAll(dir)
   286  
   287  	srcdir = filepath.Join(dir, "src")
   288  	if err = os.MkdirAll(srcdir, 0755); err != nil {
   289  		t.Fatal(err)
   290  	}
   291  
   292  	dstdir = filepath.Join(dir, "dst")
   293  	if err = os.MkdirAll(dstdir, 0755); err != nil {
   294  		t.Fatal(err)
   295  	}
   296  
   297  	if err = CopyDir(srcdir, dstdir); err == nil {
   298  		t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
   299  	}
   300  
   301  	if err != errDstExist {
   302  		t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err)
   303  	}
   304  }
   305  
   306  func TestCopyDirFailOpen(t *testing.T) {
   307  	if runtime.GOOS == "windows" {
   308  		// XXX: setting permissions works differently in
   309  		// Microsoft Windows. os.Chmod(..., 0222) below is not
   310  		// enough for the file to be readonly, and os.Chmod(...,
   311  		// 0000) returns an invalid argument error. Skipping
   312  		// this this until a compatible implementation is
   313  		// provided.
   314  		t.Skip("skipping on windows")
   315  	}
   316  
   317  	var currentUser, err = user.Current()
   318  
   319  	if err != nil {
   320  		t.Fatalf("Failed to get name of current user: %s", err)
   321  	}
   322  
   323  	if currentUser.Name == "root" {
   324  		// Skipping if root, because all files are accessible
   325  		t.Skip("Skipping for root user")
   326  	}
   327  
   328  	var srcdir, dstdir string
   329  
   330  	dir, err := ioutil.TempDir("", "helm-tmp")
   331  	if err != nil {
   332  		t.Fatal(err)
   333  	}
   334  	defer os.RemoveAll(dir)
   335  
   336  	srcdir = filepath.Join(dir, "src")
   337  	if err = os.MkdirAll(srcdir, 0755); err != nil {
   338  		t.Fatal(err)
   339  	}
   340  
   341  	srcfn := filepath.Join(srcdir, "file")
   342  	srcf, err := os.Create(srcfn)
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  	srcf.Close()
   347  
   348  	// setup source file so that it cannot be read
   349  	if err = os.Chmod(srcfn, 0222); err != nil {
   350  		t.Fatal(err)
   351  	}
   352  
   353  	dstdir = filepath.Join(dir, "dst")
   354  
   355  	if err = CopyDir(srcdir, dstdir); err == nil {
   356  		t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
   357  	}
   358  }
   359  
   360  func TestCopyFile(t *testing.T) {
   361  	dir, err := ioutil.TempDir("", "helm-tmp")
   362  	if err != nil {
   363  		t.Fatal(err)
   364  	}
   365  	defer os.RemoveAll(dir)
   366  
   367  	srcf, err := os.Create(filepath.Join(dir, "srcfile"))
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  
   372  	want := "hello world"
   373  	if _, err := srcf.Write([]byte(want)); err != nil {
   374  		t.Fatal(err)
   375  	}
   376  	srcf.Close()
   377  
   378  	destf := filepath.Join(dir, "destf")
   379  	if err := copyFile(srcf.Name(), destf); err != nil {
   380  		t.Fatal(err)
   381  	}
   382  
   383  	got, err := ioutil.ReadFile(destf)
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  
   388  	if want != string(got) {
   389  		t.Fatalf("expected: %s, got: %s", want, string(got))
   390  	}
   391  
   392  	wantinfo, err := os.Stat(srcf.Name())
   393  	if err != nil {
   394  		t.Fatal(err)
   395  	}
   396  
   397  	gotinfo, err := os.Stat(destf)
   398  	if err != nil {
   399  		t.Fatal(err)
   400  	}
   401  
   402  	if wantinfo.Mode() != gotinfo.Mode() {
   403  		t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), wantinfo.Mode(), destf, gotinfo.Mode())
   404  	}
   405  }
   406  
   407  func cleanUpDir(dir string) {
   408  	// NOTE(mattn): It seems that sometimes git.exe is not dead
   409  	// when cleanUpDir() is called. But we do not know any way to wait for it.
   410  	if runtime.GOOS == "windows" {
   411  		mu.Lock()
   412  		exec.Command(`taskkill`, `/F`, `/IM`, `git.exe`).Run()
   413  		mu.Unlock()
   414  	}
   415  	if dir != "" {
   416  		os.RemoveAll(dir)
   417  	}
   418  }
   419  
   420  func TestCopyFileSymlink(t *testing.T) {
   421  	var tempdir, err = ioutil.TempDir("", "gotest")
   422  
   423  	if err != nil {
   424  		t.Fatalf("failed to create directory: %s", err)
   425  	}
   426  
   427  	defer cleanUpDir(tempdir)
   428  
   429  	testcases := map[string]string{
   430  		filepath.Join("./testdata/symlinks/file-symlink"):         filepath.Join(tempdir, "dst-file"),
   431  		filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(tempdir, "windows-dst-file"),
   432  		filepath.Join("./testdata/symlinks/invalid-symlink"):      filepath.Join(tempdir, "invalid-symlink"),
   433  	}
   434  
   435  	for symlink, dst := range testcases {
   436  		t.Run(symlink, func(t *testing.T) {
   437  			var err error
   438  			if err = copyFile(symlink, dst); err != nil {
   439  				t.Fatalf("failed to copy symlink: %s", err)
   440  			}
   441  
   442  			var want, got string
   443  
   444  			if runtime.GOOS == "windows" {
   445  				// Creating symlinks on Windows require an additional permission
   446  				// regular users aren't granted usually. So we copy the file
   447  				// content as a fall back instead of creating a real symlink.
   448  				srcb, err := ioutil.ReadFile(symlink)
   449  				if err != nil {
   450  					t.Fatalf("%+v", err)
   451  				}
   452  				dstb, err := ioutil.ReadFile(dst)
   453  				if err != nil {
   454  					t.Fatalf("%+v", err)
   455  				}
   456  
   457  				want = string(srcb)
   458  				got = string(dstb)
   459  			} else {
   460  				want, err = os.Readlink(symlink)
   461  				if err != nil {
   462  					t.Fatalf("%+v", err)
   463  				}
   464  
   465  				got, err = os.Readlink(dst)
   466  				if err != nil {
   467  					t.Fatalf("could not resolve symlink: %s", err)
   468  				}
   469  			}
   470  
   471  			if want != got {
   472  				t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
   473  			}
   474  		})
   475  	}
   476  }
   477  
   478  func TestCopyFileFail(t *testing.T) {
   479  	if runtime.GOOS == "windows" {
   480  		// XXX: setting permissions works differently in
   481  		// Microsoft Windows. Skipping this this until a
   482  		// compatible implementation is provided.
   483  		t.Skip("skipping on windows")
   484  	}
   485  
   486  	var currentUser, err = user.Current()
   487  
   488  	if err != nil {
   489  		t.Fatalf("Failed to get name of current user: %s", err)
   490  	}
   491  
   492  	if currentUser.Name == "root" {
   493  		// Skipping if root, because all files are accessible
   494  		t.Skip("Skipping for root user")
   495  	}
   496  
   497  	dir, err := ioutil.TempDir("", "helm-tmp")
   498  	if err != nil {
   499  		t.Fatal(err)
   500  	}
   501  	defer os.RemoveAll(dir)
   502  
   503  	srcf, err := os.Create(filepath.Join(dir, "srcfile"))
   504  	if err != nil {
   505  		t.Fatal(err)
   506  	}
   507  	srcf.Close()
   508  
   509  	var dstdir string
   510  
   511  	cleanup := setupInaccessibleDir(t, func(dir string) error {
   512  		dstdir = filepath.Join(dir, "dir")
   513  		return os.Mkdir(dstdir, 0777)
   514  	})
   515  	defer cleanup()
   516  
   517  	fn := filepath.Join(dstdir, "file")
   518  	if err := copyFile(srcf.Name(), fn); err == nil {
   519  		t.Fatalf("expected error for %s, got none", fn)
   520  	}
   521  }
   522  
   523  // setupInaccessibleDir creates a temporary location with a single
   524  // directory in it, in such a way that that directory is not accessible
   525  // after this function returns.
   526  //
   527  // op is called with the directory as argument, so that it can create
   528  // files or other test artifacts.
   529  //
   530  // If setupInaccessibleDir fails in its preparation, or op fails, t.Fatal
   531  // will be invoked.
   532  //
   533  // This function returns a cleanup function that removes all the temporary
   534  // files this function creates. It is the caller's responsibility to call
   535  // this function before the test is done running, whether there's an error or not.
   536  func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
   537  	dir, err := ioutil.TempDir("", "helm-tmp")
   538  	if err != nil {
   539  		t.Fatal(err)
   540  		return nil // keep compiler happy
   541  	}
   542  
   543  	subdir := filepath.Join(dir, "dir")
   544  
   545  	cleanup := func() {
   546  		if err := os.Chmod(subdir, 0777); err != nil {
   547  			t.Error(err)
   548  		}
   549  		if err := os.RemoveAll(dir); err != nil {
   550  			t.Error(err)
   551  		}
   552  	}
   553  
   554  	if err := os.Mkdir(subdir, 0777); err != nil {
   555  		cleanup()
   556  		t.Fatal(err)
   557  		return nil
   558  	}
   559  
   560  	if err := op(subdir); err != nil {
   561  		cleanup()
   562  		t.Fatal(err)
   563  		return nil
   564  	}
   565  
   566  	if err := os.Chmod(subdir, 0666); err != nil {
   567  		cleanup()
   568  		t.Fatal(err)
   569  		return nil
   570  	}
   571  
   572  	return cleanup
   573  }
   574  
   575  func TestIsDir(t *testing.T) {
   576  
   577  	var currentUser, err = user.Current()
   578  
   579  	if err != nil {
   580  		t.Fatalf("Failed to get name of current user: %s", err)
   581  	}
   582  
   583  	if currentUser.Name == "root" {
   584  		// Skipping if root, because all files are accessible
   585  		t.Skip("Skipping for root user")
   586  	}
   587  
   588  	wd, err := os.Getwd()
   589  	if err != nil {
   590  		t.Fatal(err)
   591  	}
   592  
   593  	var dn string
   594  
   595  	cleanup := setupInaccessibleDir(t, func(dir string) error {
   596  		dn = filepath.Join(dir, "dir")
   597  		return os.Mkdir(dn, 0777)
   598  	})
   599  	defer cleanup()
   600  
   601  	tests := map[string]struct {
   602  		exists bool
   603  		err    bool
   604  	}{
   605  		wd:                            {true, false},
   606  		filepath.Join(wd, "testdata"): {true, false},
   607  		filepath.Join(wd, "main.go"):  {false, true},
   608  		filepath.Join(wd, "this_file_does_not_exist.thing"): {false, true},
   609  		dn: {false, true},
   610  	}
   611  
   612  	if runtime.GOOS == "windows" {
   613  		// This test doesn't work on Microsoft Windows because
   614  		// of the differences in how file permissions are
   615  		// implemented. For this to work, the directory where
   616  		// the directory exists should be inaccessible.
   617  		delete(tests, dn)
   618  	}
   619  
   620  	for f, want := range tests {
   621  		got, err := IsDir(f)
   622  		if err != nil && !want.err {
   623  			t.Fatalf("expected no error, got %v", err)
   624  		}
   625  
   626  		if got != want.exists {
   627  			t.Fatalf("expected %t for %s, got %t", want.exists, f, got)
   628  		}
   629  	}
   630  }
   631  
   632  func TestIsSymlink(t *testing.T) {
   633  
   634  	var currentUser, err = user.Current()
   635  
   636  	if err != nil {
   637  		t.Fatalf("Failed to get name of current user: %s", err)
   638  	}
   639  
   640  	if currentUser.Name == "root" {
   641  		// Skipping if root, because all files are accessible
   642  		t.Skip("Skipping for root user")
   643  	}
   644  
   645  	dir, err := ioutil.TempDir("", "helm-tmp")
   646  	if err != nil {
   647  		t.Fatal(err)
   648  	}
   649  	defer os.RemoveAll(dir)
   650  
   651  	dirPath := filepath.Join(dir, "directory")
   652  	if err = os.MkdirAll(dirPath, 0777); err != nil {
   653  		t.Fatal(err)
   654  	}
   655  
   656  	filePath := filepath.Join(dir, "file")
   657  	f, err := os.Create(filePath)
   658  	if err != nil {
   659  		t.Fatal(err)
   660  	}
   661  	f.Close()
   662  
   663  	dirSymlink := filepath.Join(dir, "dirSymlink")
   664  	fileSymlink := filepath.Join(dir, "fileSymlink")
   665  
   666  	if err = os.Symlink(dirPath, dirSymlink); err != nil {
   667  		t.Fatal(err)
   668  	}
   669  	if err = os.Symlink(filePath, fileSymlink); err != nil {
   670  		t.Fatal(err)
   671  	}
   672  
   673  	var (
   674  		inaccessibleFile    string
   675  		inaccessibleSymlink string
   676  	)
   677  
   678  	cleanup := setupInaccessibleDir(t, func(dir string) error {
   679  		inaccessibleFile = filepath.Join(dir, "file")
   680  		if fh, err := os.Create(inaccessibleFile); err != nil {
   681  			return err
   682  		} else if err = fh.Close(); err != nil {
   683  			return err
   684  		}
   685  
   686  		inaccessibleSymlink = filepath.Join(dir, "symlink")
   687  		return os.Symlink(inaccessibleFile, inaccessibleSymlink)
   688  	})
   689  	defer cleanup()
   690  
   691  	tests := map[string]struct{ expected, err bool }{
   692  		dirPath:             {false, false},
   693  		filePath:            {false, false},
   694  		dirSymlink:          {true, false},
   695  		fileSymlink:         {true, false},
   696  		inaccessibleFile:    {false, true},
   697  		inaccessibleSymlink: {false, true},
   698  	}
   699  
   700  	if runtime.GOOS == "windows" {
   701  		// XXX: setting permissions works differently in Windows. Skipping
   702  		// these cases until a compatible implementation is provided.
   703  		delete(tests, inaccessibleFile)
   704  		delete(tests, inaccessibleSymlink)
   705  	}
   706  
   707  	for path, want := range tests {
   708  		got, err := IsSymlink(path)
   709  		if err != nil {
   710  			if !want.err {
   711  				t.Errorf("expected no error, got %v", err)
   712  			}
   713  		}
   714  
   715  		if got != want.expected {
   716  			t.Errorf("expected %t for %s, got %t", want.expected, path, got)
   717  		}
   718  	}
   719  }