turbo-editors/turbo-corepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

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

🛟 Updated. 28d5985 · on main · k33g · 4h ago
run_test.go · 259 lines · 6.9 KBGo Blame HistoryRaw
  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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package tools

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

// runToEnd starts a command and waits for it to finish.
func runToEnd(t *testing.T, command, dir string) *Run {
	t.Helper()

	run, err := Start(command, dir, nil)
	if err != nil {
		t.Fatalf("Start(%q) error = %v", command, err)
	}
	waitUntilDone(t, run)
	return run
}

// waitUntilDone waits for a command to end, failing the test if it never does.
func waitUntilDone(t *testing.T, run *Run) {
	t.Helper()

	deadline := time.After(20 * time.Second)
	for {
		if finished, _ := run.Done(); finished {
			return
		}
		select {
		case <-deadline:
			t.Fatalf("%q never finished", run.Command())
		case <-time.After(5 * time.Millisecond):
		}
	}
}

func TestStartCollectsWhatACommandPrints(t *testing.T) {
	run := runToEnd(t, "echo one; echo two", t.TempDir())

	got := run.Lines()
	if len(got) != 2 || got[0] != "one" || got[1] != "two" {
		t.Errorf("Lines() = %v, want [one two]", got)
	}
}

func TestStandardErrorIsMergedInOrder(t *testing.T) {
	// A compiler's errors and its progress belong together, in the order it
	// wrote them.
	run := runToEnd(t, "echo out; echo err >&2; echo out2", t.TempDir())

	got := strings.Join(run.Lines(), "|")
	if got != "out|err|out2" {
		t.Errorf("Lines() = %q, want the two streams interleaved as written", got)
	}
}

func TestTheExitCodeIsReported(t *testing.T) {
	for command, want := range map[string]int{
		"true":     0,
		"exit 3":   3,
		"exit 1":   1,
		"false":    1,
		"nosuchcm": 127,
	} {
		t.Run(command, func(t *testing.T) {
			run := runToEnd(t, command, t.TempDir())

			finished, code := run.Done()
			if !finished {
				t.Fatal("Done() reported unfinished after waiting")
			}
			if code != want {
				t.Errorf("exit code = %d, want %d", code, want)
			}
		})
	}
}

func TestACommandIsNotDoneWhileItRuns(t *testing.T) {
	run, err := Start("sleep 5", t.TempDir(), nil)
	if err != nil {
		t.Fatalf("Start() error = %v", err)
	}
	t.Cleanup(run.Stop)

	if finished, _ := run.Done(); finished {
		t.Error("a command that has just started reports itself finished")
	}
}

func TestTheCommandRunsInTheDirectoryGiven(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "marker-is-here"), nil, 0o644); err != nil {
		t.Fatalf("creating the marker: %v", err)
	}

	run := runToEnd(t, "ls", dir)

	if got := strings.Join(run.Lines(), "\n"); !strings.Contains(got, "marker-is-here") {
		t.Errorf("the command ran somewhere else; ls gave %q", got)
	}
}

func TestACommandLineWithPipesAndAndsWorks(t *testing.T) {
	// The whole reason it goes through a shell: one entry can be a sequence.
	run := runToEnd(t, "echo b; echo a | sort && echo done", t.TempDir())

	if got := strings.Join(run.Lines(), "|"); got != "b|a|done" {
		t.Errorf("Lines() = %q", got)
	}
}

func TestOnLineIsCalledAsOutputArrivesAndAtTheEnd(t *testing.T) {
	calls := make(chan struct{}, 64)
	run, err := Start("echo one; echo two", t.TempDir(), func() {
		select {
		case calls <- struct{}{}:
		default:
		}
	})
	if err != nil {
		t.Fatalf("Start() error = %v", err)
	}
	waitUntilDone(t, run)

	// Two lines and one ending: at least three, and never zero, which is what
	// would leave a dialog blank for ever.
	if len(calls) < 3 {
		t.Errorf("onLine was called %d times, want one per line plus the end", len(calls))
	}
}

func TestStopEndsACommandThatWouldNotStopItself(t *testing.T) {
	run, err := Start("sleep 60", t.TempDir(), nil)
	if err != nil {
		t.Fatalf("Start() error = %v", err)
	}

	run.Stop()

	waitUntilDone(t, run)
	if _, code := run.Done(); code == 0 {
		t.Error("a killed command reported success")
	}
}

func TestStopEndsWhatTheCommandStartedToo(t *testing.T) {
	// Killing only the shell leaves a grandchild holding the output pipe, so
	// the reading goroutine blocks until *it* ends — which for `go test ./...`
	// means every test binary it spawned. Stopping has to take the group.
	// The echo is what proves the background child exists before we stop
	// anything: killing the shell before it has forked would make this pass
	// for the wrong reason.
	run, err := Start("(sleep 30) & echo forked; wait", t.TempDir(), nil)
	if err != nil {
		t.Fatalf("Start() error = %v", err)
	}
	waitUntilPrinted(t, run, "forked")

	start := time.Now()
	run.Stop()
	waitUntilDone(t, run)

	if elapsed := time.Since(start); elapsed > 5*time.Second {
		t.Errorf("Done() took %s after Stop(); something is still holding the pipe", elapsed)
	}
}

// waitUntilPrinted waits for a command to have printed a line, which is the
// only honest evidence that it has got as far as doing something.
func waitUntilPrinted(t *testing.T, run *Run, want string) {
	t.Helper()

	deadline := time.After(10 * time.Second)
	for {
		for _, line := range run.Lines() {
			if line == want {
				return
			}
		}
		select {
		case <-deadline:
			t.Fatalf("the command never printed %q; it printed %v", want, run.Lines())
		case <-time.After(5 * time.Millisecond):
		}
	}
}

func TestStoppingAFinishedCommandIsSafe(t *testing.T) {
	run := runToEnd(t, "true", t.TempDir())

	run.Stop()
	run.Stop()

	if _, code := run.Done(); code != 0 {
		t.Errorf("stopping a finished command changed its exit code to %d", code)
	}
}

func TestACommandThatPrintsNothingGivesNoLines(t *testing.T) {
	// go build ./... succeeding is silent, and the caller has to be able to
	// tell that from a command that has not started yet.
	run := runToEnd(t, "true", t.TempDir())

	if got := run.Lines(); len(got) != 0 {
		t.Errorf("Lines() = %v, want nothing", got)
	}
	if finished, code := run.Done(); !finished || code != 0 {
		t.Errorf("Done() = %v, %d; want finished and successful", finished, code)
	}
}

func TestLinesIsACopy(t *testing.T) {
	// A caller holds it while the command goes on writing.
	run := runToEnd(t, "echo one", t.TempDir())

	lines := run.Lines()
	lines[0] = "tampered"

	if got := run.Lines(); got[0] != "one" {
		t.Errorf("Lines() = %v; the caller's slice is the run's own", got)
	}
}

func TestOutputPastTheLimitDropsTheOldestAndSaysSo(t *testing.T) {
	// The tail of a failing build is the part that matters, so it is the head
	// that goes — and the caller is told, rather than quietly shown less.
	run := runToEnd(t, "seq 1 10500", t.TempDir())

	lines := run.Lines()
	if len(lines) != maxLines {
		t.Fatalf("Lines() kept %d lines, want the cap of %d", len(lines), maxLines)
	}
	if got := lines[len(lines)-1]; got != "10500" {
		t.Errorf("the last line is %q, want the newest output", got)
	}
	if run.Dropped() != 500 {
		t.Errorf("Dropped() = %d, want 500", run.Dropped())
	}
}

func TestStartFailsWhenTheShellIsNotThere(t *testing.T) {
	// Not a real risk on a Unix, but the error path exists and returning nil,
	// nil from it would be a nil dereference in the caller.
	if _, err := Start("true", "/nonexistent-directory-for-a-test", nil); err == nil {
		t.Error("Start() accepted a directory that does not exist")
	}
}

func TestCommandReportsWhatIsBeingRun(t *testing.T) {
	run := runToEnd(t, "echo x", t.TempDir())

	if got := run.Command(); got != "echo x" {
		t.Errorf("Command() = %q", got)
	}
}