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
5package gopathwalk
6
7import (
8	"io/ioutil"
9	"log"
10	"os"
11	"path/filepath"
12	"reflect"
13	"runtime"
14	"strings"
15	"sync"
16	"testing"
17)
18
19func TestShouldTraverse(t *testing.T) {
20	switch runtime.GOOS {
21	case "windows", "plan9":
22		t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS)
23	}
24
25	dir, err := ioutil.TempDir("", "goimports-")
26	if err != nil {
27		t.Fatal(err)
28	}
29	defer os.RemoveAll(dir)
30
31	// Note: mapToDir prepends "src" to each element, since
32	// mapToDir was made for creating GOPATHs.
33	if err := mapToDir(dir, map[string]string{
34		"foo/foo2/file.txt":        "",
35		"foo/foo2/link-to-src":     "LINK:../../",
36		"foo/foo2/link-to-src-foo": "LINK:../../foo",
37		"foo/foo2/link-to-dot":     "LINK:.",
38		"bar/bar2/file.txt":        "",
39		"bar/bar2/link-to-src-foo": "LINK:../../foo",
40
41		"a/b/c": "LINK:../../a/d",
42		"a/d/e": "LINK:../../a/b",
43	}); err != nil {
44		t.Fatal(err)
45	}
46	tests := []struct {
47		dir  string
48		file string
49		want bool
50	}{
51		{
52			dir:  "src/foo/foo2",
53			file: "link-to-src-foo",
54			want: false, // loop
55		},
56		{
57			dir:  "src/foo/foo2",
58			file: "link-to-src",
59			want: false, // loop
60		},
61		{
62			dir:  "src/foo/foo2",
63			file: "link-to-dot",
64			want: false, // loop
65		},
66		{
67			dir:  "src/bar/bar2",
68			file: "link-to-src-foo",
69			want: true, // not a loop
70		},
71		{
72			dir:  "src/a/b/c",
73			file: "e",
74			want: false, // loop: "e" is the same as "b".
75		},
76	}
77	for i, tt := range tests {
78		fi, err := os.Stat(filepath.Join(dir, tt.dir, tt.file))
79		if err != nil {
80			t.Errorf("%d. Stat = %v", i, err)
81			continue
82		}
83		var w walker
84		got := w.shouldTraverse(filepath.Join(dir, tt.dir), fi)
85		if got != tt.want {
86			t.Errorf("%d. shouldTraverse(%q, %q) = %v; want %v", i, tt.dir, tt.file, got, tt.want)
87		}
88	}
89}
90
91// TestSkip tests that various goimports rules are followed in non-modules mode.
92func TestSkip(t *testing.T) {
93	dir, err := ioutil.TempDir("", "goimports-")
94	if err != nil {
95		t.Fatal(err)
96	}
97	defer os.RemoveAll(dir)
98
99	if err := mapToDir(dir, map[string]string{
100		"ignoreme/f.go":     "package ignoreme",     // ignored by .goimportsignore
101		"node_modules/f.go": "package nodemodules;", // ignored by hardcoded node_modules filter
102		"v/f.go":            "package v;",           // ignored by hardcoded vgo cache rule
103		"mod/f.go":          "package mod;",         // ignored by hardcoded vgo cache rule
104		"shouldfind/f.go":   "package shouldfind;",  // not ignored
105
106		".goimportsignore": "ignoreme\n",
107	}); err != nil {
108		t.Fatal(err)
109	}
110
111	var found []string
112	var mu sync.Mutex
113	walkDir(Root{filepath.Join(dir, "src"), RootGOPATH},
114		func(root Root, dir string) {
115			mu.Lock()
116			defer mu.Unlock()
117			found = append(found, dir[len(root.Path)+1:])
118		}, func(root Root, dir string) bool {
119			return false
120		}, Options{ModulesEnabled: false, Logf: log.Printf})
121	if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) {
122		t.Errorf("expected to find only %v, got %v", want, found)
123	}
124}
125
126// TestSkipFunction tests that scan successfully skips directories from user callback.
127func TestSkipFunction(t *testing.T) {
128	dir, err := ioutil.TempDir("", "goimports-")
129	if err != nil {
130		t.Fatal(err)
131	}
132	defer os.RemoveAll(dir)
133
134	if err := mapToDir(dir, map[string]string{
135		"ignoreme/f.go":           "package ignoreme",    // ignored by skip
136		"ignoreme/subignore/f.go": "package subignore",   // also ignored by skip
137		"shouldfind/f.go":         "package shouldfind;", // not ignored
138	}); err != nil {
139		t.Fatal(err)
140	}
141
142	var found []string
143	var mu sync.Mutex
144	walkDir(Root{filepath.Join(dir, "src"), RootGOPATH},
145		func(root Root, dir string) {
146			mu.Lock()
147			defer mu.Unlock()
148			found = append(found, dir[len(root.Path)+1:])
149		}, func(root Root, dir string) bool {
150			return strings.HasSuffix(dir, "ignoreme")
151		},
152		Options{ModulesEnabled: false})
153	if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) {
154		t.Errorf("expected to find only %v, got %v", want, found)
155	}
156}
157
158func mapToDir(destDir string, files map[string]string) error {
159	for path, contents := range files {
160		file := filepath.Join(destDir, "src", path)
161		if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
162			return err
163		}
164		var err error
165		if strings.HasPrefix(contents, "LINK:") {
166			err = os.Symlink(strings.TrimPrefix(contents, "LINK:"), file)
167		} else {
168			err = ioutil.WriteFile(file, []byte(contents), 0644)
169		}
170		if err != nil {
171			return err
172		}
173	}
174	return nil
175}
176