github.com/jd-ly/tools@v0.5.7/internal/lsp/cache/view_test.go (about) 1 // Copyright 2020 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 package cache 5 6 import ( 7 "context" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 "testing" 13 14 "golang.org/x/mod/modfile" 15 "github.com/jd-ly/tools/internal/lsp/fake" 16 "github.com/jd-ly/tools/internal/lsp/protocol" 17 "github.com/jd-ly/tools/internal/lsp/source" 18 "github.com/jd-ly/tools/internal/span" 19 ) 20 21 func TestCaseInsensitiveFilesystem(t *testing.T) { 22 base, err := ioutil.TempDir("", t.Name()) 23 if err != nil { 24 t.Fatal(err) 25 } 26 27 inner := filepath.Join(base, "a/B/c/DEFgh") 28 if err := os.MkdirAll(inner, 0777); err != nil { 29 t.Fatal(err) 30 } 31 file := filepath.Join(inner, "f.go") 32 if err := ioutil.WriteFile(file, []byte("hi"), 0777); err != nil { 33 t.Fatal(err) 34 } 35 if _, err := os.Stat(filepath.Join(inner, "F.go")); err != nil { 36 t.Skip("filesystem is case-sensitive") 37 } 38 39 tests := []struct { 40 path string 41 err bool 42 }{ 43 {file, false}, 44 {filepath.Join(inner, "F.go"), true}, 45 {filepath.Join(base, "a/b/c/defgh/f.go"), true}, 46 } 47 for _, tt := range tests { 48 err := checkPathCase(tt.path) 49 if err != nil != tt.err { 50 t.Errorf("checkPathCase(%q) = %v, wanted error: %v", tt.path, err, tt.err) 51 } 52 } 53 } 54 55 func TestFindWorkspaceRoot(t *testing.T) { 56 workspace := ` 57 -- a/go.mod -- 58 module a 59 -- a/x/x.go 60 package x 61 -- b/go.mod -- 62 module b 63 -- b/c/go.mod -- 64 module bc 65 -- d/gopls.mod -- 66 module d-goplsworkspace 67 -- d/e/go.mod -- 68 module de 69 -- f/g/go.mod -- 70 module fg 71 ` 72 dir, err := fake.Tempdir(workspace) 73 if err != nil { 74 t.Fatal(err) 75 } 76 defer os.RemoveAll(dir) 77 78 tests := []struct { 79 folder, want string 80 experimental bool 81 }{ 82 {"", "", false}, // no module at root, and more than one nested module 83 {"a", "a", false}, 84 {"a/x", "a", false}, 85 {"b/c", "b/c", false}, 86 {"d", "d/e", false}, 87 {"d", "d", true}, 88 {"d/e", "d/e", false}, 89 {"d/e", "d", true}, 90 {"f", "f/g", false}, 91 {"f", "f", true}, 92 } 93 94 for _, test := range tests { 95 ctx := context.Background() 96 rel := fake.RelativeTo(dir) 97 folderURI := span.URIFromPath(rel.AbsPath(test.folder)) 98 got, err := findWorkspaceRoot(ctx, folderURI, osFileSource{}, test.experimental) 99 if err != nil { 100 t.Fatal(err) 101 } 102 if gotf, wantf := filepath.Clean(got.Filename()), rel.AbsPath(test.want); gotf != wantf { 103 t.Errorf("findWorkspaceRoot(%q, %t) = %q, want %q", test.folder, test.experimental, gotf, wantf) 104 } 105 } 106 } 107 108 // This tests the logic used to extract positions from parse and other Go 109 // command errors. 110 func TestExtractPositionFromError(t *testing.T) { 111 workspace := ` 112 -- a/go.mod -- 113 modul a.com 114 -- b/go.mod -- 115 module b.com 116 117 go 1.12.hello 118 -- c/go.mod -- 119 module c.com 120 121 require a.com master 122 ` 123 dir, err := fake.Tempdir(workspace) 124 if err != nil { 125 t.Fatal(err) 126 } 127 defer os.RemoveAll(dir) 128 129 tests := []struct { 130 filename string 131 wantRng protocol.Range 132 }{ 133 { 134 filename: "a/go.mod", 135 wantRng: protocol.Range{}, 136 }, 137 { 138 filename: "b/go.mod", 139 wantRng: protocol.Range{ 140 Start: protocol.Position{Line: 2}, 141 End: protocol.Position{Line: 2}, 142 }, 143 }, 144 { 145 filename: "c/go.mod", 146 wantRng: protocol.Range{ 147 Start: protocol.Position{Line: 2}, 148 End: protocol.Position{Line: 2}, 149 }, 150 }, 151 } 152 for _, test := range tests { 153 ctx := context.Background() 154 rel := fake.RelativeTo(dir) 155 uri := span.URIFromPath(rel.AbsPath(test.filename)) 156 if source.DetectLanguage("", uri.Filename()) != source.Mod { 157 t.Fatalf("expected only go.mod files") 158 } 159 // Try directly parsing the given, invalid go.mod file. Then, extract a 160 // position from the error message. 161 src := osFileSource{} 162 modFH, err := src.GetFile(ctx, uri) 163 if err != nil { 164 t.Fatal(err) 165 } 166 content, err := modFH.Read() 167 if err != nil { 168 t.Fatal(err) 169 } 170 _, parseErr := modfile.Parse(uri.Filename(), content, nil) 171 if parseErr == nil { 172 t.Fatalf("%s: expected an unparseable go.mod file", uri.Filename()) 173 } 174 srcErr, err := extractErrorWithPosition(ctx, parseErr.Error(), src) 175 if err != nil { 176 t.Fatal(err) 177 } 178 if srcErr.URI != uri { 179 t.Errorf("unexpected URI: got %s, wanted %s", srcErr.URI, uri) 180 } 181 if protocol.CompareRange(test.wantRng, srcErr.Range) != 0 { 182 t.Errorf("unexpected range: got %s, wanted %s", srcErr.Range, test.wantRng) 183 } 184 if !strings.HasSuffix(parseErr.Error(), srcErr.Message) { 185 t.Errorf("unexpected message: got %s, wanted %s", srcErr.Message, parseErr) 186 } 187 } 188 } 189 190 func TestInVendor(t *testing.T) { 191 for _, tt := range []struct { 192 path string 193 inVendor bool 194 }{ 195 { 196 path: "foo/vendor/x.go", 197 inVendor: false, 198 }, 199 { 200 path: "foo/vendor/x/x.go", 201 inVendor: true, 202 }, 203 { 204 path: "foo/x.go", 205 inVendor: false, 206 }, 207 } { 208 if got := inVendor(span.URIFromPath(tt.path)); got != tt.inVendor { 209 t.Errorf("expected %s inVendor %v, got %v", tt.path, tt.inVendor, got) 210 } 211 } 212 }