blake.io/pqx@v0.2.2-0.20231231055241-83f2254c0a07/internal/logplex/logplex_test.go (about)

     1  package logplex
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"runtime"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  
    12  	"kr.dev/diff"
    13  )
    14  
    15  func testSplitter(line []byte) (key, msg []byte) {
    16  	key, msg, hasSep := bytes.Cut(line, []byte("::"))
    17  	if hasSep {
    18  		return key, msg
    19  	}
    20  	return nil, line
    21  }
    22  
    23  func TestLogplex(t *testing.T) {
    24  	var (
    25  		d0 strings.Builder
    26  		d1 strings.Builder
    27  		d2 strings.Builder
    28  		d3 strings.Builder
    29  	)
    30  
    31  	lp := &Logplex{
    32  		Sink:  &d0,
    33  		Split: testSplitter,
    34  	}
    35  
    36  	write := func(s string) {
    37  		t.Helper()
    38  		n, err := lp.Write([]byte(s))
    39  		if err != nil {
    40  			t.Error(err)
    41  		}
    42  		if n != len(s) {
    43  			t.Errorf("wrote %d bytes, want %d", n, len(s))
    44  		}
    45  	}
    46  
    47  	write("nothing\n") //nolint
    48  	diff.Test(t, t.Errorf, d0.String(), "nothing\n")
    49  	d0.Reset()
    50  
    51  	lp.Watch("d1", &d1)
    52  	lp.Watch("d2", &d2)
    53  
    54  	// unknowns sent to Sink
    55  	write("zero\n")
    56  	write("d0::zero\n") // unknown prefix; sent to Drain
    57  
    58  	// d1
    59  	write("d1::one\n")
    60  	write("d1::a")
    61  	write("b\n")
    62  
    63  	// d2
    64  	write("d2::t")
    65  	write("wo\n")
    66  
    67  	// d3
    68  	write("d3::three\n") // write d3 before Watch
    69  
    70  	lp.Watch("d3", &d3) // late watch
    71  	write("d3:")        // split seperator
    72  	write(":3\n\tcontinuation\n")
    73  
    74  	// detach d1 so it goes to Sink
    75  	lp.Unwatch("d1")
    76  	write("d1::detached\n")
    77  
    78  	diff.Test(t, t.Errorf, d0.String(), "zero\nd0::zero\nd3::three\nd1::detached\n") // captures d3 logs until Watch("d3", ...)
    79  	diff.Test(t, t.Errorf, d1.String(), "one\nab\n")
    80  	diff.Test(t, t.Errorf, d2.String(), "two\n")
    81  	diff.Test(t, t.Errorf, d3.String(), "3\n\tcontinuation\n")
    82  }
    83  
    84  // run with -race
    85  func TestConcurrency(t *testing.T) {
    86  	var (
    87  		d0 strings.Builder
    88  		d1 strings.Builder
    89  		d2 strings.Builder
    90  	)
    91  
    92  	lp := &Logplex{
    93  		Sink: &d0,
    94  	}
    95  	lp.Watch("d1", &d1)
    96  	lp.Watch("d2", &d2)
    97  
    98  	const seq = "abcdefghijklmnopqrstuvwxyz"
    99  	var g sync.WaitGroup
   100  	for i := 0; i < runtime.GOMAXPROCS(0); i++ {
   101  		i := i
   102  		g.Add(1)
   103  		go func() {
   104  			defer g.Done()
   105  			for _, c := range seq {
   106  				fmt.Fprintf(lp, "d%d::%c\n", i, c)
   107  			}
   108  		}()
   109  	}
   110  
   111  	g.Wait()
   112  }
   113  
   114  func TestWriteAllocs(t *testing.T) {
   115  	lp := &Logplex{
   116  		Sink:  io.Discard,
   117  		Split: testSplitter,
   118  	}
   119  	lp.Watch("a", io.Discard)
   120  
   121  	line := []byte("a::b\n")
   122  	got := testing.AllocsPerRun(100, func() {
   123  		lp.Write(line) //nolint
   124  	})
   125  
   126  	if got > 0 {
   127  		t.Errorf("got %f allocs, want 0", got)
   128  	}
   129  }
   130  
   131  func BenchmarkWrite(b *testing.B) {
   132  	b.ReportAllocs()
   133  
   134  	lp := &Logplex{
   135  		Sink:  io.Discard,
   136  		Split: testSplitter,
   137  	}
   138  	lp.Watch("a", io.Discard)
   139  
   140  	line := []byte("a::b\n")
   141  	for i := 0; i < b.N; i++ {
   142  		lp.Write(line) //nolint
   143  	}
   144  }