More unit test junk

This commit is contained in:
Neale Pickett 2022-05-15 17:38:57 -06:00
parent db9ca5dc83
commit d6e6a268a3
7 changed files with 133 additions and 92 deletions

View File

@ -5,15 +5,20 @@ import (
"log"
)
// Book maps names to repeaters
//
// It ensures that names map 1-1 to repeaters.
type Book struct {
entries map[string]*Repeater
events chan bookEvent
entries map[string]*Repeater
events chan bookEvent
makeRepeater func() *Repeater
}
func NewBook() Book {
return Book{
entries: make(map[string]*Repeater),
events: make(chan bookEvent, 5),
entries: make(map[string]*Repeater),
events: make(chan bookEvent, 5),
makeRepeater: NewRepeater,
}
}
@ -32,6 +37,7 @@ type bookEvent struct {
m Message
}
// Join adds a writer to a named repeater
func (b Book) Join(name string, w io.Writer) {
b.events <- bookEvent{
eventType: joinEvent,
@ -40,6 +46,7 @@ func (b Book) Join(name string, w io.Writer) {
}
}
// Part removes a writer from a named repeater
func (b Book) Part(name string, w io.Writer) {
b.events <- bookEvent{
eventType: partEvent,
@ -48,6 +55,7 @@ func (b Book) Part(name string, w io.Writer) {
}
}
// Send transmits a message to the named repeater
func (b Book) Send(name string, m Message) {
b.events <- bookEvent{
eventType: sendEvent,
@ -56,6 +64,7 @@ func (b Book) Send(name string, m Message) {
}
}
// Run is the endless run loop
func (b Book) Run() {
for {
b.loop()
@ -69,7 +78,7 @@ func (b Book) loop() {
switch event.eventType {
case joinEvent:
if !ok {
repeater = NewRepeater()
repeater = b.makeRepeater()
b.entries[event.name] = repeater
}
repeater.Join(event.w)

View File

@ -1,69 +1,57 @@
package main
import (
"bytes"
"testing"
)
func TestBook(t *testing.T) {
b := NewBook()
m := TestMessage{Message{1, 2, []uint8{3, 4}}}
b := Book{
entries: make(map[string]*Repeater),
events: make(chan bookEvent, 5),
makeRepeater: NewTestingRepeater,
}
buf1 := bytes.NewBufferString("buf1")
buf1Expect := bytes.NewBufferString("buf1")
b.Join("moo", buf1)
m.Clients = 1
c1 := NewTestingClient(t)
b.Join("moo", c1)
b.loop()
if len(b.entries) != 1 {
t.Error("Wrong number of entries")
}
c1.Expect(1)
// Send to an empty channel
b.Send("merf", m.Message)
m := Message{0, 0, []uint8{22, 33}}
b.Send("merf", m)
b.loop()
if buf1.String() != buf1Expect.String() {
if c1.Len() > 0 {
t.Error("Sending to empty channel sent to non-empty channel")
}
// Send to a non-empty channel!
b.Send("moo", m.Message)
b.Send("moo", m)
b.loop()
buf1Expect.Write(m.bytes())
if buf1.String() != buf1Expect.String() {
t.Error("Sending didn't work")
}
c1.Expect(1, 22, 33)
// Join another client
buf2 := bytes.NewBufferString("buf2")
buf2Expect := bytes.NewBufferString("buf2")
b.Join("moo", buf2)
m.Clients = 2
c2 := NewTestingClient(t)
b.Join("moo", c2)
b.loop()
c1.Expect(2)
c2.Expect(2)
// Send to both
b.Send("moo", m.Message)
m.Duration = append(m.Duration, 44)
b.Send("moo", m)
b.loop()
buf1Expect.Write(m.bytes())
buf2Expect.Write(m.bytes())
if buf1.String() != buf1Expect.String() {
t.Error("Send to 2-member channel busted", buf1)
}
if buf2.String() != buf2Expect.String() {
t.Error("Send to 2-member channel busted", buf2)
}
c1.Expect(2, 22, 33, 44)
c2.Expect(2, 22, 33, 44)
// Part a client
b.Part("moo", buf1)
b.Part("moo", c1)
b.loop()
m.Clients = 1
c2.Expect(1)
b.Send("moo", m.Message)
b.Send("moo", m)
b.loop()
buf2Expect.Write(m.bytes())
if buf1.String() != buf1Expect.String() {
t.Error("Parted channel but still getting messages", buf1)
}
if buf2.String() != buf2Expect.String() {
t.Error("Someone else parting somehow messed up sends", buf2)
}
c2.Expect(1, 22, 33, 44)
}

View File

@ -12,6 +12,21 @@ import (
var book Book
// Clock defines an interface for getting the current time.
//
// We use this in testing to provide a fixed value for the current time, so we
// can still compare clocks.
type Clock interface {
Now() time.Time
}
// WallClock provides the actual time
type WallClock struct{}
func (WallClock) Now() time.Time {
return time.Now()
}
type Client struct {
repeaterName string
}
@ -22,9 +37,6 @@ func (c Client) Handle(ws *websocket.Conn) {
book.Join(c.repeaterName, ws)
defer book.Part(c.repeaterName, ws)
// Tell the client what time we think it is
fmt.Fprintf(ws, "[%d]", time.Now().UnixNano()/time.Millisecond.Nanoseconds())
for {
buf := make([]byte, ws.MaxPayloadBytes)

View File

@ -20,7 +20,7 @@ type Message struct {
Duration []uint8
}
func NewMessage(ts time.Time, durations []time.Duration) Message {
func NewMessage(ts time.Time, durations ...time.Duration) Message {
msg := Message{
Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(),
Duration: make([]uint8, len(durations)),

View File

@ -40,11 +40,9 @@ func TestMessageStruct(t *testing.T) {
0,
m.Timestamp*time.Millisecond.Nanoseconds(),
),
[]time.Duration{
time.Duration(m.Duration[0]) * time.Millisecond,
time.Duration(m.Duration[1]) * time.Millisecond,
time.Duration(m.Duration[2]) * time.Millisecond,
},
time.Duration(m.Duration[0])*time.Millisecond,
time.Duration(m.Duration[1])*time.Millisecond,
time.Duration(m.Duration[2])*time.Millisecond,
)
if !m.Equal(m3) {
t.Error("NewMessage didn't work", m, m3)

View File

@ -3,23 +3,30 @@ package main
import (
"io"
"log"
"time"
)
// A Repeater is just a list of Writers.
type Repeater struct {
clock Clock
writers []io.Writer
}
// NewRepeater returns a newly-created repeater
func NewRepeater() *Repeater {
return &Repeater{
clock: WallClock{},
writers: make([]io.Writer, 0, 20),
}
}
// Join joins a writer to this repeater
func (r *Repeater) Join(w io.Writer) {
r.writers = append(r.writers, w)
r.SendMessage()
}
// Part removes a writer from this repeater
func (r *Repeater) Part(w io.Writer) {
for i, s := range r.writers {
if s == w {
@ -28,8 +35,10 @@ func (r *Repeater) Part(w io.Writer) {
r.writers = r.writers[:nsubs-1]
}
}
r.SendMessage()
}
// Send send a message to all connected clients
func (r *Repeater) Send(m Message) {
m.Clients = uint16(r.Listeners())
buf, err := m.MarshalBinary()
@ -41,6 +50,13 @@ func (r *Repeater) Send(m Message) {
}
}
// SendMessage constructs and sends a message
func (r *Repeater) SendMessage(durations ...time.Duration) {
m := NewMessage(r.clock.Now(), durations...)
r.Send(m)
}
// Listeners returns the number of connected clients
func (r *Repeater) Listeners() int {
return len(r.writers)
}

View File

@ -2,57 +2,75 @@ package main
import (
"bytes"
"io"
"testing"
"time"
)
type TestMessage struct {
Message
type FakeClock struct{}
func (f FakeClock) Now() time.Time {
return time.UnixMilli(0)
}
func (m TestMessage) bytes() []byte {
b, _ := m.MarshalBinary()
return b
type TestingClient struct {
bytes.Buffer
expected bytes.Buffer
repeater *Repeater
t *testing.T
}
func NewTestingClient(t *testing.T) *TestingClient {
return &TestingClient{
Buffer: bytes.Buffer{},
expected: bytes.Buffer{},
t: t,
}
}
func (tc *TestingClient) Expect(clients uint16, payload ...uint8) {
m := Message{0, clients, payload}
buf, _ := m.MarshalBinary()
tc.expected.Write(buf)
if tc.String() != tc.expected.String() {
tc.t.Errorf("Client buffer mismatch. Wanted %#v, got %#v", tc.expected.String(), tc.String())
}
tc.Reset()
tc.expected.Reset()
}
func NewTestingRepeater() *Repeater {
return &Repeater{
clock: FakeClock{},
writers: make([]io.Writer, 0, 2),
}
}
func TestRepeater(t *testing.T) {
r := NewRepeater()
m := TestMessage{Message{1, 3, []uint8{3, 4}}}
r := NewTestingRepeater()
buf1 := bytes.NewBufferString("buf1")
buf1Expect := bytes.NewBufferString("buf1")
r.Join(buf1)
if r.Listeners() != 1 {
t.Error("Joining did nothing")
}
r.Send(m.Message)
m.Clients = 1
buf1Expect.Write(m.bytes())
if buf1.String() != buf1Expect.String() {
t.Error("Client 1 not repeating", buf1)
}
c1 := NewTestingClient(t)
r.Join(c1)
c1.Expect(1)
buf2 := bytes.NewBufferString("buf2")
buf2Expect := bytes.NewBufferString("buf2")
r.Join(buf2)
r.Send(m.Message)
m.Clients = 2
buf1Expect.Write(m.bytes())
buf2Expect.Write(m.bytes())
if buf1.String() != buf1Expect.String() {
t.Errorf("Client 1 not repeating %#v %#v", buf1, buf1Expect)
}
if buf2.String() != buf2Expect.String() {
t.Error("Client 2 not repeating", buf2)
}
r.SendMessage(15 * time.Millisecond)
c1.Expect(1, 15)
r.Part(buf1)
r.Send(m.Message)
m.Clients = 1
buf2Expect.Write(m.bytes())
if buf1.String() != buf1Expect.String() {
t.Error("Client 1 still getting data after part", buf1)
}
if buf2.String() != buf2Expect.String() {
t.Error("Client 2 not getting data after part", buf2)
c2 := NewTestingClient(t)
r.Join(c2)
c1.Expect(2)
c2.Expect(2)
r.SendMessage(58 * time.Millisecond)
c1.Expect(2, 58)
c2.Expect(2, 58)
r.Part(c1)
c2.Expect(1)
r.SendMessage(5 * time.Millisecond)
c2.Expect(1, 5)
if c1.Len() > 0 {
t.Error("Client 1 still getting data after part")
}
}