golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/gopathwalk/walk_test.go (about)

     1  // Copyright 2018 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 gopathwalk
     6  
     7  import (
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  )
    17  
    18  func TestSymlinkTraversal(t *testing.T) {
    19  	t.Parallel()
    20  
    21  	gopath := t.TempDir()
    22  
    23  	if err := mapToDir(gopath, map[string]string{
    24  		"a/b/c":          "LINK:../../a/d",
    25  		"a/b/pkg/pkg.go": "package pkg",
    26  		"a/d/e":          "LINK:../../a/b",
    27  		"a/d/pkg/pkg.go": "package pkg",
    28  		"a/f/loop":       "LINK:../f",
    29  		"a/f/pkg/pkg.go": "package pkg",
    30  		"a/g/pkg/pkg.go": "LINK:../../f/pkg/pkg.go",
    31  		"a/self":         "LINK:.",
    32  	}); err != nil {
    33  		switch runtime.GOOS {
    34  		case "windows", "plan9":
    35  			t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS)
    36  		}
    37  		t.Fatal(err)
    38  	}
    39  
    40  	pkgc := make(chan []string, 1)
    41  	pkgc <- nil
    42  	add := func(root Root, dir string) {
    43  		rel, err := filepath.Rel(filepath.Join(root.Path, "src"), dir)
    44  		if err != nil {
    45  			t.Error(err)
    46  		}
    47  		pkgc <- append(<-pkgc, filepath.ToSlash(rel))
    48  	}
    49  
    50  	Walk([]Root{{Path: gopath, Type: RootGOPATH}}, add, Options{Logf: t.Logf})
    51  
    52  	pkgs := <-pkgc
    53  	sort.Strings(pkgs)
    54  	t.Logf("Found packages:\n\t%s", strings.Join(pkgs, "\n\t"))
    55  
    56  	got := make(map[string]bool, len(pkgs))
    57  	for _, pkg := range pkgs {
    58  		got[pkg] = true
    59  	}
    60  	tests := []struct {
    61  		path string
    62  		want bool
    63  		why  string
    64  	}{
    65  		{
    66  			path: "a/b/pkg",
    67  			want: true,
    68  			why:  "found via regular directories",
    69  		},
    70  		{
    71  			path: "a/b/c/pkg",
    72  			want: true,
    73  			why:  "found via non-cyclic dir link",
    74  		},
    75  		{
    76  			path: "a/b/c/e/pkg",
    77  			want: true,
    78  			why:  "found via two non-cyclic dir links",
    79  		},
    80  		{
    81  			path: "a/d/e/c/pkg",
    82  			want: true,
    83  			why:  "found via two non-cyclic dir links",
    84  		},
    85  		{
    86  			path: "a/f/loop/pkg",
    87  			want: true,
    88  			why:  "found via a single parent-dir link",
    89  		},
    90  		{
    91  			path: "a/f/loop/loop/pkg",
    92  			want: false,
    93  			why:  "would follow loop symlink twice",
    94  		},
    95  		{
    96  			path: "a/self/b/pkg",
    97  			want: true,
    98  			why:  "follows self-link once",
    99  		},
   100  		{
   101  			path: "a/self/self/b/pkg",
   102  			want: false,
   103  			why:  "would follow self-link twice",
   104  		},
   105  	}
   106  	for _, tc := range tests {
   107  		if got[tc.path] != tc.want {
   108  			if tc.want {
   109  				t.Errorf("MISSING: %s (%s)", tc.path, tc.why)
   110  			} else {
   111  				t.Errorf("UNEXPECTED: %s (%s)", tc.path, tc.why)
   112  			}
   113  		}
   114  	}
   115  }
   116  
   117  // TestSkip tests that various goimports rules are followed in non-modules mode.
   118  func TestSkip(t *testing.T) {
   119  	t.Parallel()
   120  
   121  	dir := t.TempDir()
   122  
   123  	if err := mapToDir(dir, map[string]string{
   124  		"ignoreme/f.go":     "package ignoreme",     // ignored by .goimportsignore
   125  		"node_modules/f.go": "package nodemodules;", // ignored by hardcoded node_modules filter
   126  		"v/f.go":            "package v;",           // ignored by hardcoded vgo cache rule
   127  		"mod/f.go":          "package mod;",         // ignored by hardcoded vgo cache rule
   128  		"shouldfind/f.go":   "package shouldfind;",  // not ignored
   129  
   130  		".goimportsignore": "ignoreme\n",
   131  	}); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	var found []string
   136  	var mu sync.Mutex
   137  	walkDir(Root{filepath.Join(dir, "src"), RootGOPATH},
   138  		func(root Root, dir string) {
   139  			mu.Lock()
   140  			defer mu.Unlock()
   141  			found = append(found, dir[len(root.Path)+1:])
   142  		}, func(root Root, dir string) bool {
   143  			return false
   144  		}, Options{
   145  			ModulesEnabled: false,
   146  			Logf:           t.Logf,
   147  		})
   148  	if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) {
   149  		t.Errorf("expected to find only %v, got %v", want, found)
   150  	}
   151  }
   152  
   153  // TestSkipFunction tests that scan successfully skips directories from user callback.
   154  func TestSkipFunction(t *testing.T) {
   155  	t.Parallel()
   156  
   157  	dir := t.TempDir()
   158  
   159  	if err := mapToDir(dir, map[string]string{
   160  		"ignoreme/f.go":           "package ignoreme",    // ignored by skip
   161  		"ignoreme/subignore/f.go": "package subignore",   // also ignored by skip
   162  		"shouldfind/f.go":         "package shouldfind;", // not ignored
   163  	}); err != nil {
   164  		t.Fatal(err)
   165  	}
   166  
   167  	var found []string
   168  	var mu sync.Mutex
   169  	walkDir(Root{filepath.Join(dir, "src"), RootGOPATH},
   170  		func(root Root, dir string) {
   171  			mu.Lock()
   172  			defer mu.Unlock()
   173  			found = append(found, dir[len(root.Path)+1:])
   174  		}, func(root Root, dir string) bool {
   175  			return strings.HasSuffix(dir, "ignoreme")
   176  		},
   177  		Options{
   178  			ModulesEnabled: false,
   179  			Logf:           t.Logf,
   180  		})
   181  	if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) {
   182  		t.Errorf("expected to find only %v, got %v", want, found)
   183  	}
   184  }
   185  
   186  // TestWalkSymlinkConcurrentDeletion is a regression test for the panic reported
   187  // in https://go.dev/issue/58054#issuecomment-1791513726.
   188  func TestWalkSymlinkConcurrentDeletion(t *testing.T) {
   189  	t.Parallel()
   190  
   191  	src := t.TempDir()
   192  
   193  	m := map[string]string{
   194  		"dir/readme.txt": "dir is not a go package",
   195  		"dirlink":        "LINK:dir",
   196  	}
   197  	if err := mapToDir(src, m); err != nil {
   198  		switch runtime.GOOS {
   199  		case "windows", "plan9":
   200  			t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS)
   201  		}
   202  		t.Fatal(err)
   203  	}
   204  
   205  	done := make(chan struct{})
   206  	go func() {
   207  		if err := os.RemoveAll(src); err != nil {
   208  			t.Log(err)
   209  		}
   210  		close(done)
   211  	}()
   212  	defer func() {
   213  		<-done
   214  	}()
   215  
   216  	add := func(root Root, dir string) {
   217  		t.Errorf("unexpected call to add(%q, %q)", root.Path, dir)
   218  	}
   219  	Walk([]Root{{Path: src, Type: RootGOPATH}}, add, Options{Logf: t.Logf})
   220  }
   221  
   222  func mapToDir(destDir string, files map[string]string) error {
   223  	var symlinkPaths []string
   224  	for path, contents := range files {
   225  		file := filepath.Join(destDir, "src", path)
   226  		if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
   227  			return err
   228  		}
   229  		var err error
   230  		if strings.HasPrefix(contents, "LINK:") {
   231  			// To work around https://go.dev/issue/39183, wait to create symlinks
   232  			// until we have created all non-symlink paths.
   233  			symlinkPaths = append(symlinkPaths, path)
   234  		} else {
   235  			err = os.WriteFile(file, []byte(contents), 0644)
   236  		}
   237  		if err != nil {
   238  			return err
   239  		}
   240  	}
   241  
   242  	for _, path := range symlinkPaths {
   243  		file := filepath.Join(destDir, "src", path)
   244  		target := filepath.FromSlash(strings.TrimPrefix(files[path], "LINK:"))
   245  		err := os.Symlink(target, file)
   246  		if err != nil {
   247  			return err
   248  		}
   249  	}
   250  
   251  	return nil
   252  }