github.com/letsencrypt/boulder@v0.20251208.0/log/mock.go (about)

     1  package log
     2  
     3  import (
     4  	"fmt"
     5  	"log/syslog"
     6  	"regexp"
     7  	"strings"
     8  )
     9  
    10  // UseMock sets a mock logger as the default logger, and returns it.
    11  func UseMock() *Mock {
    12  	m := NewMock()
    13  	_ = Set(m)
    14  	return m
    15  }
    16  
    17  // NewMock creates a mock logger.
    18  func NewMock() *Mock {
    19  	return &Mock{impl{newMockWriter()}}
    20  }
    21  
    22  // Mock is a logger that stores all log messages in memory to be examined by a
    23  // test.
    24  type Mock struct {
    25  	impl
    26  }
    27  
    28  // WaitingMock is a logger that stores all messages in memory to be examined by a test with methods
    29  type WaitingMock struct {
    30  	impl
    31  }
    32  
    33  // Mock implements the writer interface. It
    34  // stores all logged messages in a buffer for inspection by test
    35  // functions (via GetAll()) instead of sending them to syslog.
    36  type mockWriter struct {
    37  	logged    []string
    38  	msgChan   chan<- string
    39  	getChan   <-chan []string
    40  	clearChan chan<- struct{}
    41  	closeChan chan<- struct{}
    42  }
    43  
    44  var levelName = map[syslog.Priority]string{
    45  	syslog.LOG_ERR:     "ERR",
    46  	syslog.LOG_WARNING: "WARNING",
    47  	syslog.LOG_INFO:    "INFO",
    48  	syslog.LOG_DEBUG:   "DEBUG",
    49  }
    50  
    51  func (w *mockWriter) logAtLevel(p syslog.Priority, msg string, a ...any) {
    52  	w.msgChan <- fmt.Sprintf("%s: %s", levelName[p&7], fmt.Sprintf(msg, a...))
    53  }
    54  
    55  // newMockWriter returns a new mockWriter
    56  func newMockWriter() *mockWriter {
    57  	msgChan := make(chan string)
    58  	getChan := make(chan []string)
    59  	clearChan := make(chan struct{})
    60  	closeChan := make(chan struct{})
    61  	w := &mockWriter{
    62  		logged:    []string{},
    63  		msgChan:   msgChan,
    64  		getChan:   getChan,
    65  		clearChan: clearChan,
    66  		closeChan: closeChan,
    67  	}
    68  	go func() {
    69  		for {
    70  			select {
    71  			case logMsg := <-msgChan:
    72  				w.logged = append(w.logged, logMsg)
    73  			case getChan <- w.logged:
    74  			case <-clearChan:
    75  				w.logged = []string{}
    76  			case <-closeChan:
    77  				close(getChan)
    78  				return
    79  			}
    80  		}
    81  	}()
    82  	return w
    83  }
    84  
    85  // GetAll returns all messages logged since instantiation or the last call to
    86  // Clear().
    87  //
    88  // The caller must not modify the returned slice or its elements.
    89  func (m *Mock) GetAll() []string {
    90  	w := m.w.(*mockWriter)
    91  	return <-w.getChan
    92  }
    93  
    94  // GetAllMatching returns all messages logged since instantiation or the last
    95  // Clear() whose text matches the given regexp. The regexp is
    96  // accepted as a string and compiled on the fly, because convenience
    97  // is more important than performance.
    98  //
    99  // The caller must not modify the elements of the returned slice.
   100  func (m *Mock) GetAllMatching(reString string) []string {
   101  	var matches []string
   102  	w := m.w.(*mockWriter)
   103  	re := regexp.MustCompile(reString)
   104  	for _, logMsg := range <-w.getChan {
   105  		if re.MatchString(logMsg) {
   106  			matches = append(matches, logMsg)
   107  		}
   108  	}
   109  	return matches
   110  }
   111  
   112  func (m *Mock) ExpectMatch(reString string) error {
   113  	results := m.GetAllMatching(reString)
   114  	if len(results) == 0 {
   115  		return fmt.Errorf("expected log line %q, got %q", reString, strings.Join(m.GetAll(), "\n"))
   116  	}
   117  	return nil
   118  }
   119  
   120  // Clear resets the log buffer.
   121  func (m *Mock) Clear() {
   122  	w := m.w.(*mockWriter)
   123  	w.clearChan <- struct{}{}
   124  }