test(cabana,flare,dev,summer): json, reported errors and dev serve
- Docker: admin:token:create and list --json, -q keeps the token - flare generate and test-push failures print once and are Reported - summer dev starts serve with the forwarded globals, reports rebuilds, failures and restarts, and stops serve with SIGTERM returning nil - delegates carry two examples and say the global options pass through
This commit is contained in:
@@ -472,3 +472,151 @@ func waitUntil(t *testing.T, name string, ok func() bool) {
|
||||
}
|
||||
t.Fatalf("timed out waiting for %s", name)
|
||||
}
|
||||
|
||||
// fakeServe writes an executable that records its arguments in
|
||||
// $DEV_ARGS_FILE and, on SIGTERM, writes "term" to $DEV_TERM_FILE and exits
|
||||
// 0, like serve's graceful stop.
|
||||
func fakeServe(t *testing.T, path string) (argsFile, termFile string) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
argsFile, termFile = filepath.Join(dir, "args"), filepath.Join(dir, "term")
|
||||
t.Setenv("DEV_ARGS_FILE", argsFile)
|
||||
t.Setenv("DEV_TERM_FILE", termFile)
|
||||
script := "#!/bin/sh\n" +
|
||||
"printf '%s\\n' \"$*\" > \"$DEV_ARGS_FILE\"\n" +
|
||||
"trap 'echo term > \"$DEV_TERM_FILE\"; exit 0' TERM\n" +
|
||||
"while :; do sleep 0.05; done\n"
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return argsFile, termFile
|
||||
}
|
||||
|
||||
func readTrimmed(path string) string {
|
||||
b, _ := os.ReadFile(path)
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
// TestStartBinServe: summer dev starts bin/<binary> with "serve" first and
|
||||
// then the forwarded global options, through the real startBin.
|
||||
func TestStartBinServe(t *testing.T) {
|
||||
bin := filepath.Join(t.TempDir(), "bin", "hello")
|
||||
argsFile, _ := fakeServe(t, bin)
|
||||
var out safeBuffer
|
||||
cmd, err := startBin(t.Context(), bin, []string{"serve", "-vv", "--env=staging"}, &out, &out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waitUntil(t, "args written", func() bool { return readTrimmed(argsFile) != "" })
|
||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
||||
_ = cmd.Wait()
|
||||
if got := readTrimmed(argsFile); got != "serve -vv --env=staging" {
|
||||
t.Fatalf("child args = %q", got)
|
||||
}
|
||||
|
||||
// Through watch: the default Start runs serve with the globals summer
|
||||
// dev was given.
|
||||
dir := t.TempDir()
|
||||
writeWatchApp(t, dir)
|
||||
argsFile, _ = fakeServe(t, filepath.Join(dir, "bin", "hello"))
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
done := make(chan error, 1)
|
||||
opts := Options{Debounce: 40 * time.Millisecond, Build: func(context.Context, string, io.Writer) error { return nil }}
|
||||
go func() { done <- watch(ctx, dir, testOutput(&out), []string{"-q", "--no-ansi"}, opts) }()
|
||||
waitUntil(t, "child started", func() bool { return readTrimmed(argsFile) != "" })
|
||||
if got := readTrimmed(argsFile); got != "serve -q --no-ansi" {
|
||||
t.Fatalf("watch started the child with %q", got)
|
||||
}
|
||||
if !strings.Contains(out.String(), "running bin/hello serve -q --no-ansi") {
|
||||
t.Errorf("watch line lacks the child command:\n%s", out.String())
|
||||
}
|
||||
cancel()
|
||||
<-done
|
||||
}
|
||||
|
||||
// TestDevReport: the dev report counts successful and failed rebuilds and
|
||||
// restarts; a failed build keeps the running child.
|
||||
func TestDevReport(t *testing.T) {
|
||||
r := devReport{rebuilds: 2, failed: 1, restarts: 1, started: time.Now().Add(-90 * time.Second)}
|
||||
d := r.details()
|
||||
if d[0].Value != "3 (1 failed)" || !d[0].Warn || d[1].Value != "1" || d[2].Value != "1m30s" {
|
||||
t.Fatalf("details = %+v", d)
|
||||
}
|
||||
if clean := (devReport{rebuilds: 1, started: time.Now()}).details(); clean[0].Value != "1 (0 failed)" || clean[0].Warn {
|
||||
t.Fatalf("clean details = %+v", clean)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
writeWatchApp(t, dir)
|
||||
var out safeBuffer
|
||||
var builds, starts atomic.Int32
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
opts := Options{
|
||||
Debounce: 40 * time.Millisecond,
|
||||
Build: func(context.Context, string, io.Writer) error {
|
||||
if builds.Add(1) == 2 {
|
||||
return fmt.Errorf("syntax error")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Start: func(context.Context, string, []string, io.Writer, io.Writer) (*exec.Cmd, error) {
|
||||
starts.Add(1)
|
||||
cmd := exec.Command("sleep", "30")
|
||||
return cmd, cmd.Start()
|
||||
},
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- watch(ctx, dir, testOutput(&out), nil, opts) }()
|
||||
waitUntil(t, "initial start", func() bool { return starts.Load() == 1 })
|
||||
write(t, filepath.Join(dir, "plugin.go"), "package p\n// broken\n")
|
||||
waitUntil(t, "failed build", func() bool { return builds.Load() == 2 })
|
||||
waitUntil(t, "build failure line", func() bool { return strings.Contains(out.String(), "Build failed: syntax error") })
|
||||
write(t, filepath.Join(dir, "plugin.go"), "package p\n// fixed\n")
|
||||
waitUntil(t, "restart", func() bool { return starts.Load() == 2 })
|
||||
cancel()
|
||||
if err := <-done; err != nil {
|
||||
t.Fatalf("watch returned %v", err)
|
||||
}
|
||||
got := out.String()
|
||||
for _, want := range []string{"Dev report", "Rebuilds 3 (1 failed)", "Restarts 1", "Uptime"} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("dev report lacks %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDevGracefulNil: when summer dev is stopped it sends SIGTERM to the
|
||||
// running serve, which stops gracefully, prints the dev report and returns
|
||||
// nil (exit 0).
|
||||
func TestDevGracefulNil(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeWatchApp(t, dir)
|
||||
argsFile, termFile := fakeServe(t, filepath.Join(dir, "bin", "hello"))
|
||||
var out safeBuffer
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
done := make(chan error, 1)
|
||||
opts := Options{Debounce: 40 * time.Millisecond, Build: func(context.Context, string, io.Writer) error { return nil }}
|
||||
go func() { done <- watch(ctx, dir, testOutput(&out), nil, opts) }()
|
||||
waitUntil(t, "child started", func() bool { return readTrimmed(argsFile) == "serve" })
|
||||
cancel()
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatalf("watch returned %v after stop, want nil", err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("watch did not stop")
|
||||
}
|
||||
if readTrimmed(termFile) != "term" {
|
||||
t.Fatal("serve did not get SIGTERM")
|
||||
}
|
||||
if !strings.Contains(out.String(), "Dev report") || !strings.Contains(out.String(), "Restarts 0") {
|
||||
t.Fatalf("no dev report:\n%s", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user