返回 DeepSeek-Reasonix
shutdown_service_result_test.go
根目录 / desktop / shutdown_service_result_test.go
1 package main
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "os"
8 "testing"
9
10 "reasonix/internal/session"
11 )
12
13 type busyShutdownExecution struct{}
14
15 func (busyShutdownExecution) Snapshot() session.RuntimeSnapshot { return session.RuntimeSnapshot{} }
16 func (busyShutdownExecution) Cancel() bool { return false }
17
18 func TestShutdownServiceFailurePreservesEvidenceAndRetries(t *testing.T) {
19 isolateDesktopUserDirs(t)
20 app := NewApp()
21 tracker := lifecycleTrackerForTest(t, t.TempDir(), 4242, "busy-service")
22 if err := tracker.start(); err != nil {
23 t.Fatal(err)
24 }
25 app.lifecycle.tracker = tracker
26 service := app.desktopSessionService("")
27 runtime, err := service.Create(t.Context(), session.CreateOptions{SessionID: "busy-service"})
28 if err != nil {
29 t.Fatal(err)
30 }
31 binding, err := service.Bind(runtime)
32 if err != nil {
33 t.Fatal(err)
34 }
35 generation := runtime.BindExecution(busyShutdownExecution{})
36 if generation == 0 || !runtime.BeginExecution(generation, "test") {
37 t.Fatal("could not create busy runtime")
38 }
39 defer func() {
40 runtime.NoteExecution(generation, session.RuntimeIdle, "")
41 runtime.UnbindExecution(generation)
42 _ = binding.Release(context.Background())
43 _ = service.Shutdown(context.Background())
44 tracker.stopWriter()
45 }()
46 request := shutdownRequest{RequestID: "busy-service", Reason: shutdownReasonUserQuit}
47 failed, err := app.requestShutdown(context.Background(), request)
48 if !errors.Is(err, session.ErrRuntimeBusy) || failed.Completed || !failed.Retryable || failed.ErrorCode != "session_service_close_failed" {
49 t.Fatalf("busy shutdown = %+v, %v", failed, err)
50 }
51 state, err := readDesktopLifecycleState(tracker.path)
52 if err != nil || state.CleanupOutcome != "failed" {
53 t.Fatalf("failure evidence was lost: %+v %v", state, err)
54 }
55 if app.shutdownState().finished["session-services"] {
56 t.Fatal("failed service was checkpointed as completed")
57 }
58 runtime.NoteExecution(generation, session.RuntimeIdle, "")
59 completed, err := app.requestShutdown(context.Background(), request)
60 if err != nil || !completed.Completed || completed.Outcome != "success" {
61 t.Fatalf("retry = %+v, %v", completed, err)
62 }
63 if _, retained := service.Runtime(runtime.Ref()); retained {
64 t.Fatal("successful retry retained runtime")
65 }
66 if _, err := os.Stat(tracker.path); !os.IsNotExist(err) {
67 t.Fatalf("completed user exit retained failure marker: %v", err)
68 }
69 }
70
71 func TestReleasedBindingWarningDoesNotHideOtherShutdownErrors(t *testing.T) {
72 bound := fmt.Errorf("released: %w", session.ErrRuntimeBound)
73 if !onlyReleasedBindingErrors(errors.Join(bound, bound)) {
74 t.Fatal("released binding warnings should not make shutdown fail permanently")
75 }
76 for _, err := range []error{session.ErrRuntimeBusy, errors.Join(bound, session.ErrRuntimeBusy), errors.Join(bound, errors.New("disk failure"))} {
77 if onlyReleasedBindingErrors(err) {
78 t.Fatalf("real cleanup failure swallowed: %v", err)
79 }
80 }
81 }
82
82 lines GO