turbo-editors/turbo-rustpublic Fork 0
v1.0.2
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-rust.git
git clone ssh://git@rickub.com/turbo-editors/turbo-rust.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

install_test.go · 348 lines · 11.0 KBGo Blame HistoryRaw
📦 Turbo Rust 713ea5c k33g 8h ago1package main
2
3import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "runtime"
8 "strings"
9 "syscall"
10 "testing"
11)
12
13// runInstaller runs scripts/install.sh with the given arguments and returns
14// everything it printed, failing the test if it did not exit cleanly.
15func runInstaller(t *testing.T, args ...string) string {
16 t.Helper()
17
18 output, err := exec.Command("bash", append([]string{"scripts/install.sh"}, args...)...).CombinedOutput()
19 if err != nil {
20 t.Fatalf("scripts/install.sh %v failed: %v\n%s", args, err, output)
21 }
22 return string(output)
23}
24
25// skipUnlessShellIsAvailable skips a test where the installer cannot run.
26func skipUnlessShellIsAvailable(t *testing.T) {
27 t.Helper()
28
29 if testing.Short() {
30 t.Skip("-short: the installer compiles the whole editor")
31 }
32 if runtime.GOOS == "windows" {
33 t.Skip("the installer is a shell script")
34 }
35 if _, err := exec.LookPath("bash"); err != nil {
36 t.Skip("bash is not available")
37 }
38}
39
40func TestTheInstallerBuildsAWorkingBinary(t *testing.T) {
41 skipUnlessShellIsAvailable(t)
42 prefix := t.TempDir()
43
44 output := runInstaller(t, "--prefix", prefix)
45
46 binary := filepath.Join(prefix, "turbo-rust")
47 info, err := os.Stat(binary)
48 if err != nil {
49 t.Fatalf("nothing was installed at %s: %v\n%s", binary, err, output)
50 }
51 if info.Mode().Perm()&0o111 == 0 {
52 t.Errorf("the installed file has permissions %o, want it executable", info.Mode().Perm())
53 }
54
55 version, err := exec.Command(binary, "-version").Output()
56 if err != nil {
57 t.Fatalf("the installed binary does not run: %v", err)
58 }
59 if !strings.Contains(string(version), "Turbo Rust") {
60 t.Errorf("-version printed %q", version)
61 }
62}
63
64func TestTheInstallerSaysWhereItPutThings(t *testing.T) {
65 skipUnlessShellIsAvailable(t)
66 prefix := t.TempDir()
67
68 output := runInstaller(t, "--prefix", prefix)
69
70 for _, want := range []string{"Turbo Rust", prefix, "PATH", "rust-analyzer"} {
71 if !strings.Contains(output, want) {
72 t.Errorf("the installer never mentions %q:\n%s", want, output)
73 }
74 }
75}
76
77func TestTheInstallerWarnsWhenThePrefixIsNotOnPath(t *testing.T) {
78 skipUnlessShellIsAvailable(t)
79 prefix := t.TempDir() // a fresh temporary directory is never on PATH
80
81 output := runInstaller(t, "--prefix", prefix)
82
83 if !strings.Contains(output, "not on your PATH") {
84 t.Errorf("the installer did not warn about the PATH:\n%s", output)
85 }
86 if !strings.Contains(output, "export PATH=") {
87 t.Errorf("the installer warned without saying how to fix it:\n%s", output)
88 }
89}
90
91func TestTheInstallerRemovesWhatItInstalled(t *testing.T) {
92 skipUnlessShellIsAvailable(t)
93 prefix := t.TempDir()
94 runInstaller(t, "--prefix", prefix)
95
96 runInstaller(t, "--prefix", prefix, "--uninstall")
97
98 if _, err := os.Stat(filepath.Join(prefix, "turbo-rust")); !os.IsNotExist(err) {
99 t.Error("the binary is still there after --uninstall")
100 }
101}
102
103func TestUninstallingNothingIsNotAFailure(t *testing.T) {
104 skipUnlessShellIsAvailable(t)
105
106 output := runInstaller(t, "--prefix", t.TempDir(), "--uninstall")
107
108 if !strings.Contains(output, "nothing installed") {
109 t.Errorf("the installer did not say there was nothing to remove:\n%s", output)
110 }
111}
112
113func TestTheInstallerExplainsItself(t *testing.T) {
114 skipUnlessShellIsAvailable(t)
115
116 output := runInstaller(t, "--help")
117
118 for _, want := range []string{"--prefix", "--with-analyzer", "--uninstall"} {
119 if !strings.Contains(output, want) {
120 t.Errorf("--help does not document %q:\n%s", want, output)
121 }
122 }
123}
124
125func TestTheInstallerRefusesAnUnknownOption(t *testing.T) {
126 skipUnlessShellIsAvailable(t)
127
128 output, err := exec.Command("bash", "scripts/install.sh", "--nonsense").CombinedOutput()
129
130 if err == nil {
131 t.Fatal("the installer accepted an option it does not have")
132 }
133 if !strings.Contains(string(output), "unknown option") {
134 t.Errorf("the installer did not say what was wrong:\n%s", output)
135 }
136}
137
138func TestTheInstallerNeedsADirectoryAfterPrefix(t *testing.T) {
139 skipUnlessShellIsAvailable(t)
140
141 output, err := exec.Command("bash", "scripts/install.sh", "--prefix").CombinedOutput()
142
143 if err == nil {
144 t.Fatal("--prefix was accepted with nothing after it")
145 }
146 if !strings.Contains(string(output), "needs a directory") {
147 t.Errorf("the installer did not say what was wrong:\n%s", output)
148 }
149}
150
151func TestTheInstallerRunsFromAnyDirectory(t *testing.T) {
152 skipUnlessShellIsAvailable(t)
153 repo, err := filepath.Abs(".")
154 if err != nil {
155 t.Fatalf("Abs() error = %v", err)
156 }
157 prefix := t.TempDir()
158
159 // It is invoked by absolute path from somewhere else entirely, as it would
160 // be from a shell alias or another script.
161 command := exec.Command("bash", filepath.Join(repo, "scripts", "install.sh"), "--prefix", prefix)
162 command.Dir = t.TempDir()
163 if output, err := command.CombinedOutput(); err != nil {
164 t.Fatalf("the installer failed when run from elsewhere: %v\n%s", err, output)
165 }
166
167 if _, err := os.Stat(filepath.Join(prefix, "turbo-rust")); err != nil {
168 t.Errorf("nothing was installed: %v", err)
169 }
170}
171
172func TestAFailedBuildLeavesTheInstalledBinaryAlone(t *testing.T) {
173 skipUnlessShellIsAvailable(t)
174 prefix := t.TempDir()
175 runInstaller(t, "--prefix", prefix)
176
177 binary := filepath.Join(prefix, "turbo-rust")
178 before, err := os.Stat(binary)
179 if err != nil {
180 t.Fatalf("the first install produced nothing: %v", err)
181 }
182
183 // A stray file in package main is exactly what a user's own scratch file
184 // does to this repository, and it must not cost them their installation.
185 stray := filepath.Join("scripts", "..", "zz_broken_on_purpose.go")
186 if err := os.WriteFile(stray, []byte("package main\n\nfunc main() {}\n"), 0o644); err != nil {
187 t.Fatalf("writing the stray file: %v", err)
188 }
189 t.Cleanup(func() { os.Remove(stray) })
190
191 output, err := exec.Command("bash", "scripts/install.sh", "--prefix", prefix).CombinedOutput()
192
193 if err == nil {
194 t.Fatal("the installer reported success on a build that cannot succeed")
195 }
196 if !strings.Contains(string(output), "nothing was installed") {
197 t.Errorf("the installer did not say the installation was untouched:\n%s", output)
198 }
199 after, err := os.Stat(binary)
200 if err != nil {
201 t.Fatalf("the failed build removed the installed binary: %v", err)
202 }
203 if !after.ModTime().Equal(before.ModTime()) {
204 t.Error("the failed build replaced the installed binary")
205 }
206}
207
208func TestReinstallingReplacesTheFileRatherThanOverwritingIt(t *testing.T) {
209 // macOS caches a binary's code signature against its inode. Writing new
210 // bytes into the same inode — which is what cp does — leaves the cached
211 // signature describing something else, and the kernel then refuses to
212 // execute it: builds fine, installs fine, "does not run". Replacing the
213 // directory entry with a fresh inode is what avoids that, and it makes the
214 // install atomic besides.
215 skipUnlessShellIsAvailable(t)
216 prefix := t.TempDir()
217 binary := filepath.Join(prefix, "turbo-rust")
218
219 runInstaller(t, "--prefix", prefix)
220 first := inodeOf(t, binary)
221
222 runInstaller(t, "--prefix", prefix)
223 second := inodeOf(t, binary)
224
225 if first == second {
226 t.Errorf("the reinstall wrote into the same inode (%d); it must replace the file", first)
227 }
228}
229
230func TestReinstallingLeavesAWorkingBinary(t *testing.T) {
231 skipUnlessShellIsAvailable(t)
232 prefix := t.TempDir()
233 binary := filepath.Join(prefix, "turbo-rust")
234
235 runInstaller(t, "--prefix", prefix)
236 runInstaller(t, "--prefix", prefix)
237
238 if _, err := exec.Command(binary, "-version").Output(); err != nil {
239 t.Fatalf("the reinstalled binary does not run: %v", err)
240 }
241}
242
243func TestABinaryThatWillNotRunIsReportedWithItsOwnError(t *testing.T) {
244 // "the installed binary does not run" on its own tells whoever hit it
245 // nothing they can act on. Whatever the system said has to come through.
246 skipUnlessShellIsAvailable(t)
247
248 if !strings.Contains(readInstaller(t), "$verify") {
249 t.Error("the installer discards what the binary said when it will not run")
250 }
251}
252
253// readInstaller returns the installer's source.
254func readInstaller(t *testing.T) string {
255 t.Helper()
256
257 data, err := os.ReadFile("scripts/install.sh")
258 if err != nil {
259 t.Fatalf("reading the installer: %v", err)
260 }
261 return string(data)
262}
263
264// inodeOf returns a file's inode number.
265func inodeOf(t *testing.T, path string) uint64 {
266 t.Helper()
267
268 info, err := os.Stat(path)
269 if err != nil {
270 t.Fatalf("stat %s: %v", path, err)
271 }
272 stat, ok := info.Sys().(*syscall.Stat_t)
273 if !ok {
274 t.Skip("inode numbers are not available on this platform")
275 }
276 return uint64(stat.Ino)
277}
278
279func TestTheInstalledBinaryReportsTheCommitItWasBuiltFrom(t *testing.T) {
280 // The point of stamping: an installed editor must name the commit it came
281 // from, not a constant somebody forgot to bump before releasing.
282 skipUnlessShellIsAvailable(t)
283 prefix := t.TempDir()
284
285 runInstaller(t, "--prefix", prefix)
286
287 reported, err := exec.Command(filepath.Join(prefix, "turbo-rust"), "-version").Output()
288 if err != nil {
289 t.Fatalf("the installed binary does not run: %v", err)
290 }
291
292 commit, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
293 if err != nil {
294 t.Skip("not a git checkout, so there is no commit to stamp")
295 }
296 if want := strings.TrimSpace(string(commit)); !strings.Contains(string(reported), want) {
297 t.Errorf("-version printed %q, which never mentions the commit %s", reported, want)
298 }
299}
300
301func TestTheInstalledBinaryDoesNotReportAnUnknownVersion(t *testing.T) {
302 // "unknown" is what the binary says when *no* source could name it, and
303 // seeing it here would mean the installer's ldflags never reached the
304 // linker. "devel" is a different thing: it is what a correct build of a
305 // checkout with no tags reports, so a checkout that has never been tagged
306 // must not fail this.
307 //
308 // What proves the stamp arrived either way is the commit, which only the
309 // linker can have supplied.
310 skipUnlessShellIsAvailable(t)
311
312 // Outside a git checkout the installer has nothing to stamp *with*, and
313 // "unknown" is then the correct answer rather than a failure — so the
314 // premise is checked before anything is asserted on.
315 commit, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
316 if err != nil {
317 t.Skip("not a git checkout, so there is nothing for the installer to stamp")
318 }
319 prefix := t.TempDir()
320
321 runInstaller(t, "--prefix", prefix)
322
323 reported, err := exec.Command(filepath.Join(prefix, "turbo-rust"), "-version").Output()
324 if err != nil {
325 t.Fatalf("the installed binary does not run: %v", err)
326 }
327 if strings.Contains(string(reported), "unknown") {
328 t.Errorf("-version printed %q, so nothing reached the linker at all", reported)
329 }
330 if want := strings.TrimSpace(string(commit)); !strings.Contains(string(reported), want) {
331 t.Errorf("-version printed %q, want it to carry the commit %q", reported, want)
332 }
333}
334
335func TestTheInstallerStampsThroughTheLinker(t *testing.T) {
336 // A build outside a git checkout has nothing to describe, and must still
337 // build rather than passing a half-built -X flag to the linker.
338 script := readInstaller(t)
339
340 for _, want := range []string{"turbo-core/version", "-ldflags", "describe --tags --dirty"} {
341 if !strings.Contains(script, want) {
342 t.Errorf("the installer never mentions %q", want)
343 }
344 }
345 if !strings.Contains(script, `ldflags=""`) {
346 t.Error("the installer has no path for a checkout git cannot describe")
347 }
348}