-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlifecycle_test.go
More file actions
155 lines (143 loc) · 4.39 KB
/
Copy pathlifecycle_test.go
File metadata and controls
155 lines (143 loc) · 4.39 KB
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
package di_test
import (
"context"
"errors"
"strings"
"testing"
"github.com/floatdrop/di"
)
func TestStoppedChildIsDetached(t *testing.T) {
stops := 0
root := di.New()
for range 100 {
c := root.Child("request")
c.Value(&DB{}).OnStop(func(context.Context, *DB) error { stops++; return nil })
c.Get[*DB]()
if err := c.Stop(context.Background()); err != nil {
t.Fatal(err)
}
}
if stops != 100 {
t.Fatalf("child stops = %d", stops)
}
if err := root.Stop(context.Background()); err != nil {
t.Fatal(err)
}
if stops != 100 {
t.Fatalf("stopped children were stopped again by the parent: %d", stops)
}
}
func TestLateBuiltServiceStarts(t *testing.T) {
var log []string
s := di.New()
s.Provide(func(*di.Scope) *DB { return &DB{} }).
OnStart(func(context.Context, *DB) error { log = append(log, "start db"); return nil }).
OnStop(func(context.Context, *DB) error { log = append(log, "stop db"); return nil })
s.Provide(func(s *di.Scope) *Repo { return &Repo{db: s.Get[*DB]()} }).
OnStart(func(context.Context, *Repo) error { log = append(log, "start repo"); return nil }).
OnStop(func(context.Context, *Repo) error { log = append(log, "stop repo"); return nil })
if err := s.Start(context.Background()); err != nil {
t.Fatal(err)
}
if len(log) != 0 {
t.Fatalf("nothing is eager, log=%v", log)
}
s.Get[*Repo]() // built after Start: dependencies start before dependents
s.Get[*Repo]() // cached: hooks must not run again
if err := s.Stop(context.Background()); err != nil {
t.Fatal(err)
}
want := "start db,start repo,stop repo,stop db"
if got := strings.Join(log, ","); got != want {
t.Fatalf("got %q, want %q", got, want)
}
}
func TestLateStartFailureIsAnError(t *testing.T) {
boom := errors.New("boom")
stopped := false
s := di.New()
s.Provide(func(*di.Scope) *DB { return &DB{} }).
OnStart(func(context.Context, *DB) error { return boom }).
OnStop(func(context.Context, *DB) error { stopped = true; return nil })
if err := s.Start(context.Background()); err != nil {
t.Fatal(err)
}
if _, err := s.Resolve[*DB](); !errors.Is(err, boom) {
t.Fatalf("got %v", err)
}
if _, err := s.Resolve[*DB](); !errors.Is(err, boom) {
t.Fatalf("failure must stick: %v", err)
}
if err := s.Stop(context.Background()); err != nil || stopped {
t.Fatalf("a service that failed to start must not be stopped (stopped=%v err=%v)", stopped, err)
}
}
func TestChildOfRunningAppStartsLateServices(t *testing.T) {
started := false
root := di.New()
if err := root.Start(context.Background()); err != nil {
t.Fatal(err)
}
req := root.Child("request")
req.Provide(func(*di.Scope) *DB { return &DB{} }).
OnStart(func(context.Context, *DB) error { started = true; return nil })
req.Get[*DB]()
if !started {
t.Fatal("OnStart did not run for a service built in a child of a running app")
}
}
func TestStartTwice(t *testing.T) {
s := di.New()
if err := s.Start(context.Background()); err != nil {
t.Fatal(err)
}
if err := s.Start(context.Background()); err == nil {
t.Fatal("second Start must fail")
}
}
func TestMustInsideConstructor(t *testing.T) {
boom := errors.New("dial failed")
open := func() (*DB, error) { return nil, boom }
s := di.New()
s.Provide(func(s *di.Scope) *DB { return s.Must(open()) })
s.Provide(func(s *di.Scope) *Repo { return &Repo{db: s.Get[*DB]()} })
_, err := s.Resolve[*Repo]()
if !errors.Is(err, boom) {
t.Fatalf("got %v", err)
}
if !strings.Contains(err.Error(), "building *github.com/floatdrop/di_test.DB") {
t.Fatalf("error should name the failing service: %v", err)
}
if v := s.Must(42, nil); v != 42 {
t.Fatal("Must must pass values through")
}
}
func TestMustAtTopLevelPanicsWithError(t *testing.T) {
boom := errors.New("boom")
defer func() {
if r := recover(); r != boom {
t.Fatalf("got %v", r)
}
}()
di.New().Must(0, boom)
}
type ctxKey struct{}
func TestContextInConstructor(t *testing.T) {
s := di.New()
if s.Context() != context.Background() {
t.Fatal("before Start, Context must be Background")
}
var seen any
s.Provide(func(s *di.Scope) *DB { seen = s.Context().Value(ctxKey{}); return &DB{} }).Eager()
ctx := context.WithValue(context.Background(), ctxKey{}, "from-start")
if err := s.Start(ctx); err != nil {
t.Fatal(err)
}
if seen != "from-start" {
t.Fatalf("constructor saw %v", seen)
}
child := s.Child("child")
if child.Context().Value(ctxKey{}) != "from-start" {
t.Fatal("child must inherit the start context")
}
}