1// Copyright The OpenTelemetry 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
15package otel
16
17import (
18	"bytes"
19	"errors"
20	"fmt"
21	"log"
22	"testing"
23	"time"
24
25	"github.com/stretchr/testify/suite"
26)
27
28type errLogger []string
29
30func (l *errLogger) Write(p []byte) (int, error) {
31	msg := bytes.TrimRight(p, "\n")
32	(*l) = append(*l, string(msg))
33	return len(msg), nil
34}
35
36func (l *errLogger) Reset() {
37	*l = errLogger([]string{})
38}
39
40func (l *errLogger) Got() []string {
41	return []string(*l)
42}
43
44type HandlerTestSuite struct {
45	suite.Suite
46
47	origHandler *loggingErrorHandler
48	errLogger   *errLogger
49}
50
51func (s *HandlerTestSuite) SetupSuite() {
52	s.errLogger = new(errLogger)
53	s.origHandler = globalErrorHandler
54	globalErrorHandler = &loggingErrorHandler{
55		l: log.New(s.errLogger, "", 0),
56	}
57}
58
59func (s *HandlerTestSuite) TearDownSuite() {
60	globalErrorHandler = s.origHandler
61}
62
63func (s *HandlerTestSuite) SetupTest() {
64	s.errLogger.Reset()
65}
66
67func (s *HandlerTestSuite) TestGlobalHandler() {
68	errs := []string{"one", "two"}
69	GetErrorHandler().Handle(errors.New(errs[0]))
70	Handle(errors.New(errs[1]))
71	s.Assert().Equal(errs, s.errLogger.Got())
72}
73
74func (s *HandlerTestSuite) TestNoDropsOnDelegate() {
75	// max time to wait for goroutine to Handle an error.
76	pause := 10 * time.Millisecond
77
78	var sent int
79	err := errors.New("")
80	stop := make(chan struct{})
81	beat := make(chan struct{})
82	done := make(chan struct{})
83
84	// Wait for a error to be submitted from the following goroutine.
85	wait := func(d time.Duration) error {
86		timer := time.NewTimer(d)
87		select {
88		case <-timer.C:
89			// We are about to fail, stop the spawned goroutine.
90			stop <- struct{}{}
91			return fmt.Errorf("no errors sent in %v", d)
92		case <-beat:
93			// Allow the timer to be reclaimed by GC.
94			timer.Stop()
95			return nil
96		}
97	}
98
99	go func() {
100		// Slow down to speed up: do not overload the processor.
101		ticker := time.NewTicker(100 * time.Microsecond)
102		for {
103			select {
104			case <-stop:
105				ticker.Stop()
106				done <- struct{}{}
107				return
108			case <-ticker.C:
109				sent++
110				Handle(err)
111			}
112
113			select {
114			case beat <- struct{}{}:
115			default:
116			}
117		}
118	}()
119
120	// Wait for the spice to flow
121	s.Require().NoError(wait(pause), "starting error stream")
122
123	// Change to another Handler. We are testing this is loss-less.
124	newErrLogger := new(errLogger)
125	secondary := &loggingErrorHandler{
126		l: log.New(newErrLogger, "", 0),
127	}
128	SetErrorHandler(secondary)
129	s.Require().NoError(wait(pause), "switched to new Handler")
130
131	// Testing done, stop sending errors.
132	stop <- struct{}{}
133	// Ensure we do not lose any straglers.
134	<-done
135
136	got := append(s.errLogger.Got(), newErrLogger.Got()...)
137	s.Assert().Greater(len(got), 1, "at least 2 errors should have been sent")
138	s.Assert().Len(got, sent)
139}
140
141func TestHandlerTestSuite(t *testing.T) {
142	suite.Run(t, new(HandlerTestSuite))
143}
144