]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/logger_test.go
log/slog: catch panics during formatting
[gostls13.git] / src / log / slog / logger_test.go
1 // Copyright 2022 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package slog
6
7 import (
8         "bytes"
9         "context"
10         "internal/race"
11         "internal/testenv"
12         "io"
13         "log"
14         loginternal "log/internal"
15         "os"
16         "path/filepath"
17         "regexp"
18         "runtime"
19         "slices"
20         "strings"
21         "sync"
22         "testing"
23         "time"
24 )
25
26 const timeRE = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}(Z|[+-]\d{2}:\d{2})`
27
28 func TestLogTextHandler(t *testing.T) {
29         ctx := context.Background()
30         var buf bytes.Buffer
31
32         l := New(NewTextHandler(&buf, nil))
33
34         check := func(want string) {
35                 t.Helper()
36                 if want != "" {
37                         want = "time=" + timeRE + " " + want
38                 }
39                 checkLogOutput(t, buf.String(), want)
40                 buf.Reset()
41         }
42
43         l.Info("msg", "a", 1, "b", 2)
44         check(`level=INFO msg=msg a=1 b=2`)
45
46         // By default, debug messages are not printed.
47         l.Debug("bg", Int("a", 1), "b", 2)
48         check("")
49
50         l.Warn("w", Duration("dur", 3*time.Second))
51         check(`level=WARN msg=w dur=3s`)
52
53         l.Error("bad", "a", 1)
54         check(`level=ERROR msg=bad a=1`)
55
56         l.Log(ctx, LevelWarn+1, "w", Int("a", 1), String("b", "two"))
57         check(`level=WARN\+1 msg=w a=1 b=two`)
58
59         l.LogAttrs(ctx, LevelInfo+1, "a b c", Int("a", 1), String("b", "two"))
60         check(`level=INFO\+1 msg="a b c" a=1 b=two`)
61
62         l.Info("info", "a", []Attr{Int("i", 1)})
63         check(`level=INFO msg=info a.i=1`)
64
65         l.Info("info", "a", GroupValue(Int("i", 1)))
66         check(`level=INFO msg=info a.i=1`)
67 }
68
69 func TestConnections(t *testing.T) {
70         var logbuf, slogbuf bytes.Buffer
71
72         // Revert any changes to the default logger. This is important because other
73         // tests might change the default logger using SetDefault. Also ensure we
74         // restore the default logger at the end of the test.
75         currentLogger := Default()
76         SetDefault(New(newDefaultHandler(loginternal.DefaultOutput)))
77         t.Cleanup(func() {
78                 SetDefault(currentLogger)
79         })
80
81         // The default slog.Logger's handler uses the log package's default output.
82         log.SetOutput(&logbuf)
83         log.SetFlags(log.Lshortfile &^ log.LstdFlags)
84         Info("msg", "a", 1)
85         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg a=1`)
86         logbuf.Reset()
87         Info("msg", "p", nil)
88         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg p=<nil>`)
89         logbuf.Reset()
90         var r *regexp.Regexp
91         Info("msg", "r", r)
92         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg r=<nil>`)
93         logbuf.Reset()
94         Warn("msg", "b", 2)
95         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: WARN msg b=2`)
96         logbuf.Reset()
97         Error("msg", "err", io.EOF, "c", 3)
98         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: ERROR msg err=EOF c=3`)
99
100         // Levels below Info are not printed.
101         logbuf.Reset()
102         Debug("msg", "c", 3)
103         checkLogOutput(t, logbuf.String(), "")
104
105         t.Run("wrap default handler", func(t *testing.T) {
106                 // It should be possible to wrap the default handler and get the right output.
107                 // This works because the default handler uses the pc in the Record
108                 // to get the source line, rather than a call depth.
109                 logger := New(wrappingHandler{Default().Handler()})
110                 logger.Info("msg", "d", 4)
111                 checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg d=4`)
112         })
113
114         // Once slog.SetDefault is called, the direction is reversed: the default
115         // log.Logger's output goes through the handler.
116         SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{AddSource: true})))
117         log.Print("msg2")
118         checkLogOutput(t, slogbuf.String(), "time="+timeRE+` level=INFO source=.*logger_test.go:\d{3}"? msg=msg2`)
119
120         // The default log.Logger always outputs at Info level.
121         slogbuf.Reset()
122         SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{Level: LevelWarn})))
123         log.Print("should not appear")
124         if got := slogbuf.String(); got != "" {
125                 t.Errorf("got %q, want empty", got)
126         }
127
128         // Setting log's output again breaks the connection.
129         logbuf.Reset()
130         slogbuf.Reset()
131         log.SetOutput(&logbuf)
132         log.SetFlags(log.Lshortfile &^ log.LstdFlags)
133         log.Print("msg3")
134         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: msg3`)
135         if got := slogbuf.String(); got != "" {
136                 t.Errorf("got %q, want empty", got)
137         }
138 }
139
140 type wrappingHandler struct {
141         h Handler
142 }
143
144 func (h wrappingHandler) Enabled(ctx context.Context, level Level) bool {
145         return h.h.Enabled(ctx, level)
146 }
147 func (h wrappingHandler) WithGroup(name string) Handler              { return h.h.WithGroup(name) }
148 func (h wrappingHandler) WithAttrs(as []Attr) Handler                { return h.h.WithAttrs(as) }
149 func (h wrappingHandler) Handle(ctx context.Context, r Record) error { return h.h.Handle(ctx, r) }
150
151 func TestAttrs(t *testing.T) {
152         check := func(got []Attr, want ...Attr) {
153                 t.Helper()
154                 if !attrsEqual(got, want) {
155                         t.Errorf("got %v, want %v", got, want)
156                 }
157         }
158
159         l1 := New(&captureHandler{}).With("a", 1)
160         l2 := New(l1.Handler()).With("b", 2)
161         l2.Info("m", "c", 3)
162         h := l2.Handler().(*captureHandler)
163         check(h.attrs, Int("a", 1), Int("b", 2))
164         check(attrsSlice(h.r), Int("c", 3))
165 }
166
167 func TestCallDepth(t *testing.T) {
168         ctx := context.Background()
169         h := &captureHandler{}
170         var startLine int
171
172         check := func(count int) {
173                 t.Helper()
174                 const wantFunc = "log/slog.TestCallDepth"
175                 const wantFile = "logger_test.go"
176                 wantLine := startLine + count*2
177                 got := h.r.source()
178                 gotFile := filepath.Base(got.File)
179                 if got.Function != wantFunc || gotFile != wantFile || got.Line != wantLine {
180                         t.Errorf("got (%s, %s, %d), want (%s, %s, %d)",
181                                 got.Function, gotFile, got.Line, wantFunc, wantFile, wantLine)
182                 }
183         }
184
185         logger := New(h)
186         SetDefault(logger)
187
188         // Calls to check must be one line apart.
189         // Determine line where calls start.
190         f, _ := runtime.CallersFrames([]uintptr{callerPC(2)}).Next()
191         startLine = f.Line + 4
192         // Do not change the number of lines between here and the call to check(0).
193
194         logger.Log(ctx, LevelInfo, "")
195         check(0)
196         logger.LogAttrs(ctx, LevelInfo, "")
197         check(1)
198         logger.Debug("")
199         check(2)
200         logger.Info("")
201         check(3)
202         logger.Warn("")
203         check(4)
204         logger.Error("")
205         check(5)
206         Debug("")
207         check(6)
208         Info("")
209         check(7)
210         Warn("")
211         check(8)
212         Error("")
213         check(9)
214         Log(ctx, LevelInfo, "")
215         check(10)
216         LogAttrs(ctx, LevelInfo, "")
217         check(11)
218 }
219
220 func TestAlloc(t *testing.T) {
221         ctx := context.Background()
222         dl := New(discardHandler{})
223         defer SetDefault(Default()) // restore
224         SetDefault(dl)
225
226         t.Run("Info", func(t *testing.T) {
227                 wantAllocs(t, 0, func() { Info("hello") })
228         })
229         t.Run("Error", func(t *testing.T) {
230                 wantAllocs(t, 0, func() { Error("hello") })
231         })
232         t.Run("logger.Info", func(t *testing.T) {
233                 wantAllocs(t, 0, func() { dl.Info("hello") })
234         })
235         t.Run("logger.Log", func(t *testing.T) {
236                 wantAllocs(t, 0, func() { dl.Log(ctx, LevelDebug, "hello") })
237         })
238         t.Run("2 pairs", func(t *testing.T) {
239                 s := "abc"
240                 i := 2000
241                 wantAllocs(t, 2, func() {
242                         dl.Info("hello",
243                                 "n", i,
244                                 "s", s,
245                         )
246                 })
247         })
248         t.Run("2 pairs disabled inline", func(t *testing.T) {
249                 l := New(discardHandler{disabled: true})
250                 s := "abc"
251                 i := 2000
252                 wantAllocs(t, 2, func() {
253                         l.Log(ctx, LevelInfo, "hello",
254                                 "n", i,
255                                 "s", s,
256                         )
257                 })
258         })
259         t.Run("2 pairs disabled", func(t *testing.T) {
260                 l := New(discardHandler{disabled: true})
261                 s := "abc"
262                 i := 2000
263                 wantAllocs(t, 0, func() {
264                         if l.Enabled(ctx, LevelInfo) {
265                                 l.Log(ctx, LevelInfo, "hello",
266                                         "n", i,
267                                         "s", s,
268                                 )
269                         }
270                 })
271         })
272         t.Run("9 kvs", func(t *testing.T) {
273                 s := "abc"
274                 i := 2000
275                 d := time.Second
276                 wantAllocs(t, 11, func() {
277                         dl.Info("hello",
278                                 "n", i, "s", s, "d", d,
279                                 "n", i, "s", s, "d", d,
280                                 "n", i, "s", s, "d", d)
281                 })
282         })
283         t.Run("pairs", func(t *testing.T) {
284                 wantAllocs(t, 0, func() { dl.Info("", "error", io.EOF) })
285         })
286         t.Run("attrs1", func(t *testing.T) {
287                 wantAllocs(t, 0, func() { dl.LogAttrs(ctx, LevelInfo, "", Int("a", 1)) })
288                 wantAllocs(t, 0, func() { dl.LogAttrs(ctx, LevelInfo, "", Any("error", io.EOF)) })
289         })
290         t.Run("attrs3", func(t *testing.T) {
291                 wantAllocs(t, 0, func() {
292                         dl.LogAttrs(ctx, LevelInfo, "hello", Int("a", 1), String("b", "two"), Duration("c", time.Second))
293                 })
294         })
295         t.Run("attrs3 disabled", func(t *testing.T) {
296                 logger := New(discardHandler{disabled: true})
297                 wantAllocs(t, 0, func() {
298                         logger.LogAttrs(ctx, LevelInfo, "hello", Int("a", 1), String("b", "two"), Duration("c", time.Second))
299                 })
300         })
301         t.Run("attrs6", func(t *testing.T) {
302                 wantAllocs(t, 1, func() {
303                         dl.LogAttrs(ctx, LevelInfo, "hello",
304                                 Int("a", 1), String("b", "two"), Duration("c", time.Second),
305                                 Int("d", 1), String("e", "two"), Duration("f", time.Second))
306                 })
307         })
308         t.Run("attrs9", func(t *testing.T) {
309                 wantAllocs(t, 1, func() {
310                         dl.LogAttrs(ctx, LevelInfo, "hello",
311                                 Int("a", 1), String("b", "two"), Duration("c", time.Second),
312                                 Int("d", 1), String("e", "two"), Duration("f", time.Second),
313                                 Int("d", 1), String("e", "two"), Duration("f", time.Second))
314                 })
315         })
316 }
317
318 func TestSetAttrs(t *testing.T) {
319         for _, test := range []struct {
320                 args []any
321                 want []Attr
322         }{
323                 {nil, nil},
324                 {[]any{"a", 1}, []Attr{Int("a", 1)}},
325                 {[]any{"a", 1, "b", "two"}, []Attr{Int("a", 1), String("b", "two")}},
326                 {[]any{"a"}, []Attr{String(badKey, "a")}},
327                 {[]any{"a", 1, "b"}, []Attr{Int("a", 1), String(badKey, "b")}},
328                 {[]any{"a", 1, 2, 3}, []Attr{Int("a", 1), Int(badKey, 2), Int(badKey, 3)}},
329         } {
330                 r := NewRecord(time.Time{}, 0, "", 0)
331                 r.Add(test.args...)
332                 got := attrsSlice(r)
333                 if !attrsEqual(got, test.want) {
334                         t.Errorf("%v:\ngot  %v\nwant %v", test.args, got, test.want)
335                 }
336         }
337 }
338
339 func TestSetDefault(t *testing.T) {
340         // Verify that setting the default to itself does not result in deadlock.
341         ctx, cancel := context.WithTimeout(context.Background(), time.Second)
342         defer cancel()
343         defer func(w io.Writer) { log.SetOutput(w) }(log.Writer())
344         log.SetOutput(io.Discard)
345         go func() {
346                 Info("A")
347                 SetDefault(Default())
348                 Info("B")
349                 cancel()
350         }()
351         <-ctx.Done()
352         if err := ctx.Err(); err != context.Canceled {
353                 t.Errorf("wanted canceled, got %v", err)
354         }
355 }
356
357 func TestLoggerError(t *testing.T) {
358         var buf bytes.Buffer
359
360         removeTime := func(_ []string, a Attr) Attr {
361                 if a.Key == TimeKey {
362                         return Attr{}
363                 }
364                 return a
365         }
366         l := New(NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeTime}))
367         l.Error("msg", "err", io.EOF, "a", 1)
368         checkLogOutput(t, buf.String(), `level=ERROR msg=msg err=EOF a=1`)
369         buf.Reset()
370         // use local var 'args' to defeat vet check
371         args := []any{"err", io.EOF, "a"}
372         l.Error("msg", args...)
373         checkLogOutput(t, buf.String(), `level=ERROR msg=msg err=EOF !BADKEY=a`)
374 }
375
376 func TestNewLogLogger(t *testing.T) {
377         var buf bytes.Buffer
378         h := NewTextHandler(&buf, nil)
379         ll := NewLogLogger(h, LevelWarn)
380         ll.Print("hello")
381         checkLogOutput(t, buf.String(), "time="+timeRE+` level=WARN msg=hello`)
382 }
383
384 func TestLoggerNoOps(t *testing.T) {
385         l := Default()
386         if l.With() != l {
387                 t.Error("wanted receiver, didn't get it")
388         }
389         if With() != l {
390                 t.Error("wanted receiver, didn't get it")
391         }
392         if l.WithGroup("") != l {
393                 t.Error("wanted receiver, didn't get it")
394         }
395 }
396
397 func TestContext(t *testing.T) {
398         // Verify that the context argument to log output methods is passed to the handler.
399         // Also check the level.
400         h := &captureHandler{}
401         l := New(h)
402         defer SetDefault(Default()) // restore
403         SetDefault(l)
404
405         for _, test := range []struct {
406                 f         func(context.Context, string, ...any)
407                 wantLevel Level
408         }{
409                 {l.DebugContext, LevelDebug},
410                 {l.InfoContext, LevelInfo},
411                 {l.WarnContext, LevelWarn},
412                 {l.ErrorContext, LevelError},
413                 {DebugContext, LevelDebug},
414                 {InfoContext, LevelInfo},
415                 {WarnContext, LevelWarn},
416                 {ErrorContext, LevelError},
417         } {
418                 h.clear()
419                 ctx := context.WithValue(context.Background(), "L", test.wantLevel)
420
421                 test.f(ctx, "msg")
422                 if gv := h.ctx.Value("L"); gv != test.wantLevel || h.r.Level != test.wantLevel {
423                         t.Errorf("got context value %v, level %s; want %s for both", gv, h.r.Level, test.wantLevel)
424                 }
425         }
426 }
427
428 func checkLogOutput(t *testing.T, got, wantRegexp string) {
429         t.Helper()
430         got = clean(got)
431         wantRegexp = "^" + wantRegexp + "$"
432         matched, err := regexp.MatchString(wantRegexp, got)
433         if err != nil {
434                 t.Fatal(err)
435         }
436         if !matched {
437                 t.Errorf("\ngot  %s\nwant %s", got, wantRegexp)
438         }
439 }
440
441 // clean prepares log output for comparison.
442 func clean(s string) string {
443         if len(s) > 0 && s[len(s)-1] == '\n' {
444                 s = s[:len(s)-1]
445         }
446         return strings.ReplaceAll(s, "\n", "~")
447 }
448
449 type captureHandler struct {
450         mu     sync.Mutex
451         ctx    context.Context
452         r      Record
453         attrs  []Attr
454         groups []string
455 }
456
457 func (h *captureHandler) Handle(ctx context.Context, r Record) error {
458         h.mu.Lock()
459         defer h.mu.Unlock()
460         h.ctx = ctx
461         h.r = r
462         return nil
463 }
464
465 func (*captureHandler) Enabled(context.Context, Level) bool { return true }
466
467 func (c *captureHandler) WithAttrs(as []Attr) Handler {
468         c.mu.Lock()
469         defer c.mu.Unlock()
470         var c2 captureHandler
471         c2.r = c.r
472         c2.groups = c.groups
473         c2.attrs = concat(c.attrs, as)
474         return &c2
475 }
476
477 func (c *captureHandler) WithGroup(name string) Handler {
478         c.mu.Lock()
479         defer c.mu.Unlock()
480         var c2 captureHandler
481         c2.r = c.r
482         c2.attrs = c.attrs
483         c2.groups = append(slices.Clip(c.groups), name)
484         return &c2
485 }
486
487 func (c *captureHandler) clear() {
488         c.mu.Lock()
489         defer c.mu.Unlock()
490         c.ctx = nil
491         c.r = Record{}
492 }
493
494 type discardHandler struct {
495         disabled bool
496         attrs    []Attr
497 }
498
499 func (d discardHandler) Enabled(context.Context, Level) bool { return !d.disabled }
500 func (discardHandler) Handle(context.Context, Record) error  { return nil }
501 func (d discardHandler) WithAttrs(as []Attr) Handler {
502         d.attrs = concat(d.attrs, as)
503         return d
504 }
505 func (h discardHandler) WithGroup(name string) Handler {
506         return h
507 }
508
509 // concat returns a new slice with the elements of s1 followed
510 // by those of s2. The slice has no additional capacity.
511 func concat[T any](s1, s2 []T) []T {
512         s := make([]T, len(s1)+len(s2))
513         copy(s, s1)
514         copy(s[len(s1):], s2)
515         return s
516 }
517
518 // This is a simple benchmark. See the benchmarks subdirectory for more extensive ones.
519 func BenchmarkNopLog(b *testing.B) {
520         ctx := context.Background()
521         l := New(&captureHandler{})
522         b.Run("no attrs", func(b *testing.B) {
523                 b.ReportAllocs()
524                 for i := 0; i < b.N; i++ {
525                         l.LogAttrs(ctx, LevelInfo, "msg")
526                 }
527         })
528         b.Run("attrs", func(b *testing.B) {
529                 b.ReportAllocs()
530                 for i := 0; i < b.N; i++ {
531                         l.LogAttrs(ctx, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
532                 }
533         })
534         b.Run("attrs-parallel", func(b *testing.B) {
535                 b.ReportAllocs()
536                 b.RunParallel(func(pb *testing.PB) {
537                         for pb.Next() {
538                                 l.LogAttrs(ctx, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
539                         }
540                 })
541         })
542         b.Run("keys-values", func(b *testing.B) {
543                 b.ReportAllocs()
544                 for i := 0; i < b.N; i++ {
545                         l.Log(ctx, LevelInfo, "msg", "a", 1, "b", "two", "c", true)
546                 }
547         })
548         b.Run("WithContext", func(b *testing.B) {
549                 b.ReportAllocs()
550                 for i := 0; i < b.N; i++ {
551                         l.LogAttrs(ctx, LevelInfo, "msg2", Int("a", 1), String("b", "two"), Bool("c", true))
552                 }
553         })
554         b.Run("WithContext-parallel", func(b *testing.B) {
555                 b.ReportAllocs()
556                 b.RunParallel(func(pb *testing.PB) {
557                         for pb.Next() {
558                                 l.LogAttrs(ctx, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
559                         }
560                 })
561         })
562 }
563
564 // callerPC returns the program counter at the given stack depth.
565 func callerPC(depth int) uintptr {
566         var pcs [1]uintptr
567         runtime.Callers(depth, pcs[:])
568         return pcs[0]
569 }
570
571 func wantAllocs(t *testing.T, want int, f func()) {
572         if race.Enabled {
573                 t.Skip("skipping test in race mode")
574         }
575         testenv.SkipIfOptimizationOff(t)
576         t.Helper()
577         got := int(testing.AllocsPerRun(5, f))
578         if got != want {
579                 t.Errorf("got %d allocs, want %d", got, want)
580         }
581 }
582
583 // panicTextAndJsonMarshaler is a type that panics in MarshalText and MarshalJSON.
584 type panicTextAndJsonMarshaler struct {
585         msg any
586 }
587
588 func (p panicTextAndJsonMarshaler) MarshalText() ([]byte, error) {
589         panic(p.msg)
590 }
591
592 func (p panicTextAndJsonMarshaler) MarshalJSON() ([]byte, error) {
593         panic(p.msg)
594 }
595
596 func TestPanics(t *testing.T) {
597         // Revert any changes to the default logger. This is important because other
598         // tests might change the default logger using SetDefault. Also ensure we
599         // restore the default logger at the end of the test.
600         currentLogger := Default()
601         t.Cleanup(func() {
602                 SetDefault(currentLogger)
603                 log.SetOutput(os.Stderr)
604                 log.SetFlags(log.LstdFlags)
605         })
606
607         var logBuf bytes.Buffer
608         log.SetOutput(&logBuf)
609         log.SetFlags(log.Lshortfile &^ log.LstdFlags)
610
611         SetDefault(New(newDefaultHandler(loginternal.DefaultOutput)))
612         for _, pt := range []struct {
613                 in  any
614                 out string
615         }{
616                 {(*panicTextAndJsonMarshaler)(nil), `logger_test.go:\d+: INFO msg p=<nil>`},
617                 {panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `logger_test.go:\d+: INFO msg p="!PANIC: unexpected EOF"`},
618                 {panicTextAndJsonMarshaler{"panicking"}, `logger_test.go:\d+: INFO msg p="!PANIC: panicking"`},
619                 {panicTextAndJsonMarshaler{42}, `logger_test.go:\d+: INFO msg p="!PANIC: 42"`},
620         } {
621                 Info("msg", "p", pt.in)
622                 checkLogOutput(t, logBuf.String(), pt.out)
623                 logBuf.Reset()
624         }
625
626         SetDefault(New(NewJSONHandler(&logBuf, nil)))
627         for _, pt := range []struct {
628                 in  any
629                 out string
630         }{
631                 {(*panicTextAndJsonMarshaler)(nil), `{"time":".*?","level":"INFO","msg":"msg","p":null}`},
632                 {panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: unexpected EOF"}`},
633                 {panicTextAndJsonMarshaler{"panicking"}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: panicking"}`},
634                 {panicTextAndJsonMarshaler{42}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: 42"}`},
635         } {
636                 Info("msg", "p", pt.in)
637                 checkLogOutput(t, logBuf.String(), pt.out)
638                 logBuf.Reset()
639         }
640 }