1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
package app
import (
"os"
"path/filepath"
"testing"
"codeberg.org/turbo-editors/turbo-core/profile"
)
// touch creates an empty file, making its directory first.
func touch(t *testing.T, path string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("creating %s: %v", filepath.Dir(path), err)
}
if err := os.WriteFile(path, nil, 0o644); err != nil {
t.Fatalf("writing %s: %v", path, err)
}
}
func TestProjectRootWalksUpToTheMarker(t *testing.T) {
root := t.TempDir()
touch(t, filepath.Join(root, "go.mod"))
deep := filepath.Join(root, "internal", "app", "app.go")
touch(t, deep)
got := ProjectRoot(profile.Profile{RootMarkers: []string{"go.mod"}}, []string{deep})
if got != root {
t.Errorf("ProjectRoot() = %q, want %q", got, root)
}
}
func TestProjectRootTakesTheFirstMarkerItMeetsGoingUp(t *testing.T) {
// A nested module is its own project; walking past it to the outer one
// would hand the language server a workspace the file is not in.
outer := t.TempDir()
touch(t, filepath.Join(outer, "Cargo.toml"))
inner := filepath.Join(outer, "crates", "parser")
touch(t, filepath.Join(inner, "Cargo.toml"))
file := filepath.Join(inner, "src", "lib.rs")
touch(t, file)
got := ProjectRoot(profile.Profile{RootMarkers: []string{"Cargo.toml"}}, []string{file})
if got != inner {
t.Errorf("ProjectRoot() = %q, want the nearest root %q", got, inner)
}
}
func TestProjectRootAcceptsAnyOfTheMarkers(t *testing.T) {
root := t.TempDir()
touch(t, filepath.Join(root, "Cargo.toml"))
file := filepath.Join(root, "src", "main.rs")
touch(t, file)
got := ProjectRoot(profile.Profile{RootMarkers: []string{"rust-toolchain.toml", "Cargo.toml"}}, []string{file})
if got != root {
t.Errorf("ProjectRoot() = %q, want %q", got, root)
}
}
func TestProjectRootFallsBackToTheFilesOwnDirectory(t *testing.T) {
// A file outside any project still has to give the server somewhere; the
// filesystem root would be absurd.
dir := t.TempDir()
file := filepath.Join(dir, "scratch.go")
touch(t, file)
got := ProjectRoot(profile.Profile{RootMarkers: []string{"no-such-marker-file"}}, []string{file})
if got != dir {
t.Errorf("ProjectRoot() = %q, want %q", got, dir)
}
}
func TestProjectRootWithNoFilesUsesTheWorkingDirectory(t *testing.T) {
dir := t.TempDir()
touch(t, filepath.Join(dir, "go.mod"))
t.Chdir(dir)
got := ProjectRoot(profile.Profile{RootMarkers: []string{"go.mod"}}, nil)
// The temporary directory may be reached through a symlink, so the
// comparison is on what the two resolve to.
if resolve(t, got) != resolve(t, dir) {
t.Errorf("ProjectRoot() = %q, want %q", got, dir)
}
}
func TestAProfileWithNoMarkersDoesNotWalk(t *testing.T) {
// An editor for a language with no project file at all: the directory the
// file is in is the best answer available.
dir := t.TempDir()
file := filepath.Join(dir, "notes.txt")
touch(t, file)
got := ProjectRoot(profile.Profile{}, []string{file})
if got != dir {
t.Errorf("ProjectRoot() = %q, want %q", got, dir)
}
}
// resolve follows symlinks, which is what /tmp is on some systems.
func resolve(t *testing.T, path string) string {
t.Helper()
resolved, err := filepath.EvalSymlinks(path)
if err != nil {
return path
}
return resolved
}
|