1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddyevents
16
+
17
+import (
18
+ "context"
19
+ "io"
20
+ "testing"
21
+
22
+ "go.uber.org/zap"
23
+ "go.uber.org/zap/zapcore"
24
+
25
+ "github.com/caddyserver/caddy/v2"
26
+)
27
+
28
+// discardLogger stands in for a production logger: a real core at info level,
29
+// so debug output is filtered but everything else behaves normally.
30
+func discardLogger() *zap.Logger {
31
+ return zap.New(zapcore.NewCore(
32
+ zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
33
+ zapcore.AddSync(io.Discard),
34
+ zapcore.InfoLevel,
35
+ ))
36
+}
37
+
38
+func testApp(tb testing.TB) (*App, caddy.Context, context.CancelFunc) {
39
+ tb.Helper()
40
+ app := &App{
41
+ logger: discardLogger(),
42
+ subscriptions: make(map[string]map[caddy.ModuleID][]Handler),
43
+ }
44
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
45
+ return app, ctx, cancel
46
+}
47
+
48
+type countingHandler struct{ count int }
49
+
50
+func (h *countingHandler) Handle(context.Context, caddy.Event) error {
51
+ h.count++
52
+ return nil
53
+}
54
+
55
+// Emit takes a shortcut when nothing is subscribed; a handler bound to the
56
+// event by name must still be invoked.
57
+func TestEmitDispatchesToNamedSubscriber(t *testing.T) {
58
+ app, ctx, cancel := testApp(t)
59
+ defer cancel()
60
+
61
+ h := new(countingHandler)
62
+ if err := app.On("cert_obtained", h); err != nil {
63
+ t.Fatal(err)
64
+ }
65
+
66
+ app.Emit(ctx, "cert_obtained", nil)
67
+ if h.count != 1 {
68
+ t.Errorf("handler invoked %d times, want 1", h.count)
69
+ }
70
+
71
+ // an event nobody subscribed to must not reach it
72
+ app.Emit(ctx, "cert_failed", nil)
73
+ if h.count != 1 {
74
+ t.Errorf("handler invoked %d times after unrelated event, want 1", h.count)
75
+ }
76
+}
77
+
78
+// Subscribing without naming an event binds to every event, which is stored
79
+// under the empty event name; the shortcut has to account for that.
80
+func TestEmitDispatchesToCatchAllSubscriber(t *testing.T) {
81
+ app, ctx, cancel := testApp(t)
82
+ defer cancel()
83
+
84
+ h := new(countingHandler)
85
+ if err := app.Subscribe(&Subscription{Handlers: []Handler{h}}); err != nil {
86
+ t.Fatal(err)
87
+ }
88
+
89
+ app.Emit(ctx, "tls_get_certificate", nil)
90
+ if h.count != 1 {
91
+ t.Errorf("handler invoked %d times, want 1", h.count)
92
+ }
93
+}
94
+
95
+// Some events, such as tls_get_certificate, are emitted on every TLS
96
+// handshake, whether or not anything is subscribed to them.
97
+func BenchmarkEmitNoSubscribers(b *testing.B) {
98
+ app, ctx, cancel := testApp(b)
99
+ defer cancel()
100
+
101
+ data := map[string]any{"client_hello": "example.com"}
102
+
103
+ b.ReportAllocs()
104
+ b.ResetTimer()
105
+ for i := 0; i < b.N; i++ {
106
+ app.Emit(ctx, "tls_get_certificate", data)
107
+ }
108
+}
109
+
110
+func BenchmarkEmitNoSubscribersParallel(b *testing.B) {
111
+ app, ctx, cancel := testApp(b)
112
+ defer cancel()
113
+
114
+ data := map[string]any{"client_hello": "example.com"}
115
+
116
+ b.ReportAllocs()
117
+ b.ResetTimer()
118
+ b.RunParallel(func(pb *testing.PB) {
119
+ for pb.Next() {
120
+ app.Emit(ctx, "tls_get_certificate", data)
121
+ }
122
+ })
123
+}
124
+
125
+func BenchmarkEmitWithSubscriber(b *testing.B) {
126
+ app, ctx, cancel := testApp(b)
127
+ defer cancel()
128
+
129
+ if err := app.On("tls_get_certificate", new(countingHandler)); err != nil {
130
+ b.Fatal(err)
131
+ }
132
+ data := map[string]any{"client_hello": "example.com"}
133
+
134
+ b.ReportAllocs()
135
+ b.ResetTimer()
136
+ for i := 0; i < b.N; i++ {
137
+ app.Emit(ctx, "tls_get_certificate", data)
138
+ }
139
+}