返回 DeepSeek-Reasonix
model_settings_detached_test.go
根目录 / internal / serve / model_settings_detached_test.go
1 package serve
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "path/filepath"
8 "testing"
9
10 "reasonix/internal/boot"
11 "reasonix/internal/config"
12 "reasonix/internal/control"
13 )
14
15 func TestDetachedModelSettingsRefreshTargetsItsOwnerAndPreservesFailure(t *testing.T) {
16 for _, fail := range []bool{false, true} {
17 t.Run(fmt.Sprint(fail), func(t *testing.T) {
18 foreground := control.New(control.Options{ModelRef: "foreground/m"})
19 defer foreground.Close()
20 path := filepath.Join(t.TempDir(), "background.jsonl")
21 old := control.New(control.Options{
22 SessionPath: path, ModelRef: "p/m", ModelSettingsRevision: "old",
23 ModelSettingsCurrent: func() (string, error) { return "new", nil },
24 })
25 defer old.Close()
26 s := New(foreground, NewBroadcaster(), config.ServeConfig{AuthMode: "none"})
27 d := &detachedSession{ctrl: old, path: path, buildOptions: boot.Options{StatsSource: "serve"}}
28 s.detached = map[string]*detachedSession{path: d}
29 builds := 0
30 s.rebuildControllerWithOptions = func(_ context.Context, current *control.Controller, ref string, opts boot.Options) (*control.Controller, error) {
31 builds++
32 if current != old || opts.SessionDir != old.SessionDir() || ref != "p/m" {
33 t.Fatal("rebuild used another runtime's identity")
34 }
35 if fail {
36 return nil, fmt.Errorf("injected background build failure")
37 }
38 return control.New(control.Options{
39 SessionPath: path, ModelRef: ref, Sink: opts.Sink, ModelSettingsRevision: "new",
40 ModelSettingsCurrent: func() (string, error) { return "new", nil },
41 }), nil
42 }
43 release, err := s.beforeInboxDispatch(old)
44 if release != nil {
45 release()
46 t.Fatal("the outgoing runtime was admitted after a settings change")
47 }
48 if s.ctl() != foreground || builds != 1 {
49 t.Fatal("background application changed the foreground owner")
50 }
51 if fail {
52 if err == nil || d.ctrl != old {
53 t.Fatal("failed background build replaced its owner")
54 }
55 return
56 }
57 if !errors.Is(err, control.ErrInboxRuntimeUnpublished) || d.ctrl == old {
58 t.Fatalf("replacement was not published: %v", err)
59 }
60 next := d.ctrl.(*control.Controller)
61 defer next.Close()
62 release, err = s.beforeInboxDispatch(next)
63 if err != nil || release == nil {
64 t.Fatalf("replacement was not admitted: %v", err)
65 }
66 release()
67 if builds != 1 {
68 t.Fatal("unchanged background settings rebuilt again")
69 }
70 })
71 }
72 }
73
74 func TestDetachedModelSettingsKeepsQueuedOwnerUntilAdmission(t *testing.T) {
75 ctrl := control.New(control.Options{SessionPath: filepath.Join(t.TempDir(), "queued.jsonl")})
76 defer ctrl.Close()
77 ctrl.SetBeforeInboxDispatch(func(*control.Controller) (func(), error) {
78 return nil, control.ErrInboxRuntimeUnpublished
79 })
80 if _, err := ctrl.EnqueueInbox(control.InboxRequest{Submit: "next background run"}); err != nil {
81 t.Fatal(err)
82 }
83 d := &detachedSession{path: ctrl.SessionPath(), ctrl: ctrl}
84 s := &Server{detached: map[string]*detachedSession{d.path: d}}
85 if !s.detachedHasPendingWork(d) || d.retiring {
86 t.Fatal("close-on-idle discarded the pending admission owner")
87 }
88 if err := ctrl.SetInboxPausedPassive(true); err != nil {
89 t.Fatal(err)
90 }
91 if s.detachedHasPendingWork(d) || !d.retiring {
92 t.Fatal("paused durable work prevented owner retirement")
93 }
94 }
95
95 lines GO