...

Source file src/github.com/letsencrypt/boulder/log/mock.go

Documentation: github.com/letsencrypt/boulder/log

     1  package log
     2  
     3  import (
     4  	"fmt"
     5  	"log/syslog"
     6  	"regexp"
     7  	"strings"
     8  	"time"
     9  )
    10  
    11  // UseMock sets a mock logger as the default logger, and returns it.
    12  func UseMock() *Mock {
    13  	m := NewMock()
    14  	_ = Set(m)
    15  	return m
    16  }
    17  
    18  // NewMock creates a mock logger.
    19  func NewMock() *Mock {
    20  	return &Mock{impl{newMockWriter()}}
    21  }
    22  
    23  // NewWaitingMock creates a mock logger implementing the writer interface.
    24  // It stores all logged messages in a buffer for inspection by test
    25  // functions.
    26  func NewWaitingMock() *WaitingMock {
    27  	return &WaitingMock{impl{newWaitingMockWriter()}}
    28  }
    29  
    30  // Mock is a logger that stores all log messages in memory to be examined by a
    31  // test.
    32  type Mock struct {
    33  	impl
    34  }
    35  
    36  // WaitingMock is a logger that stores all messages in memory to be examined by a test with methods
    37  type WaitingMock struct {
    38  	impl
    39  }
    40  
    41  // Mock implements the writer interface. It
    42  // stores all logged messages in a buffer for inspection by test
    43  // functions (via GetAll()) instead of sending them to syslog.
    44  type mockWriter struct {
    45  	logged    []string
    46  	msgChan   chan<- string
    47  	getChan   <-chan []string
    48  	clearChan chan<- struct{}
    49  	closeChan chan<- struct{}
    50  }
    51  
    52  var levelName = map[syslog.Priority]string{
    53  	syslog.LOG_ERR:     "ERR",
    54  	syslog.LOG_WARNING: "WARNING",
    55  	syslog.LOG_INFO:    "INFO",
    56  	syslog.LOG_DEBUG:   "DEBUG",
    57  }
    58  
    59  func (w *mockWriter) logAtLevel(p syslog.Priority, msg string, a ...interface{}) {
    60  	w.msgChan <- fmt.Sprintf("%s: %s", levelName[p&7], fmt.Sprintf(msg, a...))
    61  }
    62  
    63  // newMockWriter returns a new mockWriter
    64  func newMockWriter() *mockWriter {
    65  	msgChan := make(chan string)
    66  	getChan := make(chan []string)
    67  	clearChan := make(chan struct{})
    68  	closeChan := make(chan struct{})
    69  	w := &mockWriter{
    70  		logged:    []string{},
    71  		msgChan:   msgChan,
    72  		getChan:   getChan,
    73  		clearChan: clearChan,
    74  		closeChan: closeChan,
    75  	}
    76  	go func() {
    77  		for {
    78  			select {
    79  			case logMsg := <-msgChan:
    80  				w.logged = append(w.logged, logMsg)
    81  			case getChan <- w.logged:
    82  			case <-clearChan:
    83  				w.logged = []string{}
    84  			case <-closeChan:
    85  				close(getChan)
    86  				return
    87  			}
    88  		}
    89  	}()
    90  	return w
    91  }
    92  
    93  // GetAll returns all messages logged since instantiation or the last call to
    94  // Clear().
    95  //
    96  // The caller must not modify the returned slice or its elements.
    97  func (m *Mock) GetAll() []string {
    98  	w := m.w.(*mockWriter)
    99  	return <-w.getChan
   100  }
   101  
   102  // GetAllMatching returns all messages logged since instantiation or the last
   103  // Clear() whose text matches the given regexp. The regexp is
   104  // accepted as a string and compiled on the fly, because convenience
   105  // is more important than performance.
   106  //
   107  // The caller must not modify the elements of the returned slice.
   108  func (m *Mock) GetAllMatching(reString string) []string {
   109  	var matches []string
   110  	w := m.w.(*mockWriter)
   111  	re := regexp.MustCompile(reString)
   112  	for _, logMsg := range <-w.getChan {
   113  		if re.MatchString(logMsg) {
   114  			matches = append(matches, logMsg)
   115  		}
   116  	}
   117  	return matches
   118  }
   119  
   120  func (m *Mock) ExpectMatch(reString string) error {
   121  	results := m.GetAllMatching(reString)
   122  	if len(results) == 0 {
   123  		return fmt.Errorf("expected log line %q, got %q", reString, strings.Join(m.GetAll(), "\n"))
   124  	}
   125  	return nil
   126  }
   127  
   128  // Clear resets the log buffer.
   129  func (m *Mock) Clear() {
   130  	w := m.w.(*mockWriter)
   131  	w.clearChan <- struct{}{}
   132  }
   133  
   134  type waitingMockWriter struct {
   135  	logChan chan string
   136  }
   137  
   138  // newWaitingMockWriter returns a new waitingMockWriter
   139  func newWaitingMockWriter() *waitingMockWriter {
   140  	logChan := make(chan string, 1000)
   141  	return &waitingMockWriter{
   142  		logChan,
   143  	}
   144  }
   145  
   146  func (m *waitingMockWriter) logAtLevel(p syslog.Priority, msg string, a ...interface{}) {
   147  	m.logChan <- fmt.Sprintf("%s: %s", levelName[p&7], fmt.Sprintf(msg, a...))
   148  }
   149  
   150  // WaitForMatch returns the first log line matching a regex. It accepts a
   151  // regexp string and timeout. If the timeout value is met before the
   152  // matching pattern is read from the channel, an error is returned.
   153  func (m *WaitingMock) WaitForMatch(reString string, timeout time.Duration) (string, error) {
   154  	w := m.w.(*waitingMockWriter)
   155  	deadline := time.After(timeout)
   156  	re := regexp.MustCompile(reString)
   157  	for {
   158  		select {
   159  		case logLine := <-w.logChan:
   160  			if re.MatchString(logLine) {
   161  				close(w.logChan)
   162  				return logLine, nil
   163  			}
   164  		case <-deadline:
   165  			return "", fmt.Errorf("timeout waiting for match: %q", reString)
   166  		}
   167  	}
   168  }
   169  

View as plain text