mirror of https://github.com/nealey/vail.git
More unit test junk
This commit is contained in:
parent
db9ca5dc83
commit
d6e6a268a3
|
@ -5,15 +5,20 @@ import (
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Book maps names to repeaters
|
||||||
|
//
|
||||||
|
// It ensures that names map 1-1 to repeaters.
|
||||||
type Book struct {
|
type Book struct {
|
||||||
entries map[string]*Repeater
|
entries map[string]*Repeater
|
||||||
events chan bookEvent
|
events chan bookEvent
|
||||||
|
makeRepeater func() *Repeater
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBook() Book {
|
func NewBook() Book {
|
||||||
return Book{
|
return Book{
|
||||||
entries: make(map[string]*Repeater),
|
entries: make(map[string]*Repeater),
|
||||||
events: make(chan bookEvent, 5),
|
events: make(chan bookEvent, 5),
|
||||||
|
makeRepeater: NewRepeater,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +37,7 @@ type bookEvent struct {
|
||||||
m Message
|
m Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Join adds a writer to a named repeater
|
||||||
func (b Book) Join(name string, w io.Writer) {
|
func (b Book) Join(name string, w io.Writer) {
|
||||||
b.events <- bookEvent{
|
b.events <- bookEvent{
|
||||||
eventType: joinEvent,
|
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) {
|
func (b Book) Part(name string, w io.Writer) {
|
||||||
b.events <- bookEvent{
|
b.events <- bookEvent{
|
||||||
eventType: partEvent,
|
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) {
|
func (b Book) Send(name string, m Message) {
|
||||||
b.events <- bookEvent{
|
b.events <- bookEvent{
|
||||||
eventType: sendEvent,
|
eventType: sendEvent,
|
||||||
|
@ -56,6 +64,7 @@ func (b Book) Send(name string, m Message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run is the endless run loop
|
||||||
func (b Book) Run() {
|
func (b Book) Run() {
|
||||||
for {
|
for {
|
||||||
b.loop()
|
b.loop()
|
||||||
|
@ -69,7 +78,7 @@ func (b Book) loop() {
|
||||||
switch event.eventType {
|
switch event.eventType {
|
||||||
case joinEvent:
|
case joinEvent:
|
||||||
if !ok {
|
if !ok {
|
||||||
repeater = NewRepeater()
|
repeater = b.makeRepeater()
|
||||||
b.entries[event.name] = repeater
|
b.entries[event.name] = repeater
|
||||||
}
|
}
|
||||||
repeater.Join(event.w)
|
repeater.Join(event.w)
|
||||||
|
|
|
@ -1,69 +1,57 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBook(t *testing.T) {
|
func TestBook(t *testing.T) {
|
||||||
b := NewBook()
|
b := Book{
|
||||||
m := TestMessage{Message{1, 2, []uint8{3, 4}}}
|
entries: make(map[string]*Repeater),
|
||||||
|
events: make(chan bookEvent, 5),
|
||||||
|
makeRepeater: NewTestingRepeater,
|
||||||
|
}
|
||||||
|
|
||||||
buf1 := bytes.NewBufferString("buf1")
|
c1 := NewTestingClient(t)
|
||||||
buf1Expect := bytes.NewBufferString("buf1")
|
b.Join("moo", c1)
|
||||||
b.Join("moo", buf1)
|
|
||||||
m.Clients = 1
|
|
||||||
b.loop()
|
b.loop()
|
||||||
if len(b.entries) != 1 {
|
if len(b.entries) != 1 {
|
||||||
t.Error("Wrong number of entries")
|
t.Error("Wrong number of entries")
|
||||||
}
|
}
|
||||||
|
c1.Expect(1)
|
||||||
|
|
||||||
// Send to an empty channel
|
// Send to an empty channel
|
||||||
b.Send("merf", m.Message)
|
m := Message{0, 0, []uint8{22, 33}}
|
||||||
|
b.Send("merf", m)
|
||||||
b.loop()
|
b.loop()
|
||||||
if buf1.String() != buf1Expect.String() {
|
if c1.Len() > 0 {
|
||||||
t.Error("Sending to empty channel sent to non-empty channel")
|
t.Error("Sending to empty channel sent to non-empty channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to a non-empty channel!
|
// Send to a non-empty channel!
|
||||||
b.Send("moo", m.Message)
|
b.Send("moo", m)
|
||||||
b.loop()
|
b.loop()
|
||||||
buf1Expect.Write(m.bytes())
|
c1.Expect(1, 22, 33)
|
||||||
if buf1.String() != buf1Expect.String() {
|
|
||||||
t.Error("Sending didn't work")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join another client
|
// Join another client
|
||||||
buf2 := bytes.NewBufferString("buf2")
|
c2 := NewTestingClient(t)
|
||||||
buf2Expect := bytes.NewBufferString("buf2")
|
b.Join("moo", c2)
|
||||||
b.Join("moo", buf2)
|
|
||||||
m.Clients = 2
|
|
||||||
b.loop()
|
b.loop()
|
||||||
|
c1.Expect(2)
|
||||||
|
c2.Expect(2)
|
||||||
|
|
||||||
// Send to both
|
// Send to both
|
||||||
b.Send("moo", m.Message)
|
m.Duration = append(m.Duration, 44)
|
||||||
|
b.Send("moo", m)
|
||||||
b.loop()
|
b.loop()
|
||||||
buf1Expect.Write(m.bytes())
|
c1.Expect(2, 22, 33, 44)
|
||||||
buf2Expect.Write(m.bytes())
|
c2.Expect(2, 22, 33, 44)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Part a client
|
// Part a client
|
||||||
b.Part("moo", buf1)
|
b.Part("moo", c1)
|
||||||
b.loop()
|
b.loop()
|
||||||
m.Clients = 1
|
c2.Expect(1)
|
||||||
|
|
||||||
b.Send("moo", m.Message)
|
b.Send("moo", m)
|
||||||
b.loop()
|
b.loop()
|
||||||
buf2Expect.Write(m.bytes())
|
c2.Expect(1, 22, 33, 44)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,21 @@ import (
|
||||||
|
|
||||||
var book Book
|
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 {
|
type Client struct {
|
||||||
repeaterName string
|
repeaterName string
|
||||||
}
|
}
|
||||||
|
@ -22,9 +37,6 @@ func (c Client) Handle(ws *websocket.Conn) {
|
||||||
book.Join(c.repeaterName, ws)
|
book.Join(c.repeaterName, ws)
|
||||||
defer book.Part(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 {
|
for {
|
||||||
buf := make([]byte, ws.MaxPayloadBytes)
|
buf := make([]byte, ws.MaxPayloadBytes)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Message struct {
|
||||||
Duration []uint8
|
Duration []uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessage(ts time.Time, durations []time.Duration) Message {
|
func NewMessage(ts time.Time, durations ...time.Duration) Message {
|
||||||
msg := Message{
|
msg := Message{
|
||||||
Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(),
|
Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(),
|
||||||
Duration: make([]uint8, len(durations)),
|
Duration: make([]uint8, len(durations)),
|
||||||
|
|
|
@ -40,11 +40,9 @@ func TestMessageStruct(t *testing.T) {
|
||||||
0,
|
0,
|
||||||
m.Timestamp*time.Millisecond.Nanoseconds(),
|
m.Timestamp*time.Millisecond.Nanoseconds(),
|
||||||
),
|
),
|
||||||
[]time.Duration{
|
|
||||||
time.Duration(m.Duration[0])*time.Millisecond,
|
time.Duration(m.Duration[0])*time.Millisecond,
|
||||||
time.Duration(m.Duration[1])*time.Millisecond,
|
time.Duration(m.Duration[1])*time.Millisecond,
|
||||||
time.Duration(m.Duration[2])*time.Millisecond,
|
time.Duration(m.Duration[2])*time.Millisecond,
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if !m.Equal(m3) {
|
if !m.Equal(m3) {
|
||||||
t.Error("NewMessage didn't work", m, m3)
|
t.Error("NewMessage didn't work", m, m3)
|
||||||
|
|
|
@ -3,23 +3,30 @@ package main
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Repeater is just a list of Writers.
|
// A Repeater is just a list of Writers.
|
||||||
type Repeater struct {
|
type Repeater struct {
|
||||||
|
clock Clock
|
||||||
writers []io.Writer
|
writers []io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRepeater returns a newly-created repeater
|
||||||
func NewRepeater() *Repeater {
|
func NewRepeater() *Repeater {
|
||||||
return &Repeater{
|
return &Repeater{
|
||||||
|
clock: WallClock{},
|
||||||
writers: make([]io.Writer, 0, 20),
|
writers: make([]io.Writer, 0, 20),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Join joins a writer to this repeater
|
||||||
func (r *Repeater) Join(w io.Writer) {
|
func (r *Repeater) Join(w io.Writer) {
|
||||||
r.writers = append(r.writers, w)
|
r.writers = append(r.writers, w)
|
||||||
|
r.SendMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Part removes a writer from this repeater
|
||||||
func (r *Repeater) Part(w io.Writer) {
|
func (r *Repeater) Part(w io.Writer) {
|
||||||
for i, s := range r.writers {
|
for i, s := range r.writers {
|
||||||
if s == w {
|
if s == w {
|
||||||
|
@ -28,8 +35,10 @@ func (r *Repeater) Part(w io.Writer) {
|
||||||
r.writers = r.writers[:nsubs-1]
|
r.writers = r.writers[:nsubs-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
r.SendMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send send a message to all connected clients
|
||||||
func (r *Repeater) Send(m Message) {
|
func (r *Repeater) Send(m Message) {
|
||||||
m.Clients = uint16(r.Listeners())
|
m.Clients = uint16(r.Listeners())
|
||||||
buf, err := m.MarshalBinary()
|
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 {
|
func (r *Repeater) Listeners() int {
|
||||||
return len(r.writers)
|
return len(r.writers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,57 +2,75 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestMessage struct {
|
type FakeClock struct{}
|
||||||
Message
|
|
||||||
|
func (f FakeClock) Now() time.Time {
|
||||||
|
return time.UnixMilli(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m TestMessage) bytes() []byte {
|
type TestingClient struct {
|
||||||
b, _ := m.MarshalBinary()
|
bytes.Buffer
|
||||||
return b
|
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) {
|
func TestRepeater(t *testing.T) {
|
||||||
r := NewRepeater()
|
r := NewTestingRepeater()
|
||||||
m := TestMessage{Message{1, 3, []uint8{3, 4}}}
|
|
||||||
|
|
||||||
buf1 := bytes.NewBufferString("buf1")
|
c1 := NewTestingClient(t)
|
||||||
buf1Expect := bytes.NewBufferString("buf1")
|
r.Join(c1)
|
||||||
r.Join(buf1)
|
c1.Expect(1)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf2 := bytes.NewBufferString("buf2")
|
r.SendMessage(15 * time.Millisecond)
|
||||||
buf2Expect := bytes.NewBufferString("buf2")
|
c1.Expect(1, 15)
|
||||||
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.Part(buf1)
|
c2 := NewTestingClient(t)
|
||||||
r.Send(m.Message)
|
r.Join(c2)
|
||||||
m.Clients = 1
|
c1.Expect(2)
|
||||||
buf2Expect.Write(m.bytes())
|
c2.Expect(2)
|
||||||
if buf1.String() != buf1Expect.String() {
|
|
||||||
t.Error("Client 1 still getting data after part", buf1)
|
r.SendMessage(58 * time.Millisecond)
|
||||||
}
|
c1.Expect(2, 58)
|
||||||
if buf2.String() != buf2Expect.String() {
|
c2.Expect(2, 58)
|
||||||
t.Error("Client 2 not getting data after part", buf2)
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue