7ef2743964
Events API consumers rely on being able to detect that events were skipped by the fact that the event ID has increased by more than 1. This is documented, and is absolutely necessary when trying to maintain a local model of Syncthing's state. With the introduction of LocalChangeDetected, which is not exposed to the Events API, this contract was broken. This commit introduces separate concepts of a "Global ID" and a "Subscription ID". The Global ID of an event is unique across all subscriptions. The Subscription ID is local to a particular subscription, and always increments by 1. They are both exposed over the Events API, but the Subscription ID uses the key "id" for backwards compatibility, and the "?since=xx" parameter refers to the Subscription ID (making the Global ID for information only). GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3351 LGTM: calmh
303 lines
6.4 KiB
Go
303 lines
6.4 KiB
Go
// Copyright (C) 2014 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package events_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/syncthing/syncthing/lib/events"
|
|
)
|
|
|
|
const timeout = 100 * time.Millisecond
|
|
|
|
func TestNewLogger(t *testing.T) {
|
|
l := events.NewLogger()
|
|
if l == nil {
|
|
t.Fatal("Unexpected nil Logger")
|
|
}
|
|
}
|
|
|
|
func TestSubscriber(t *testing.T) {
|
|
l := events.NewLogger()
|
|
s := l.Subscribe(0)
|
|
defer l.Unsubscribe(s)
|
|
if s == nil {
|
|
t.Fatal("Unexpected nil Subscription")
|
|
}
|
|
}
|
|
|
|
func TestTimeout(t *testing.T) {
|
|
l := events.NewLogger()
|
|
s := l.Subscribe(0)
|
|
defer l.Unsubscribe(s)
|
|
_, err := s.Poll(timeout)
|
|
if err != events.ErrTimeout {
|
|
t.Fatal("Unexpected non-Timeout error:", err)
|
|
}
|
|
}
|
|
|
|
func TestEventBeforeSubscribe(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
l.Log(events.DeviceConnected, "foo")
|
|
s := l.Subscribe(0)
|
|
defer l.Unsubscribe(s)
|
|
|
|
_, err := s.Poll(timeout)
|
|
if err != events.ErrTimeout {
|
|
t.Fatal("Unexpected non-Timeout error:", err)
|
|
}
|
|
}
|
|
|
|
func TestEventAfterSubscribe(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.AllEvents)
|
|
defer l.Unsubscribe(s)
|
|
l.Log(events.DeviceConnected, "foo")
|
|
|
|
ev, err := s.Poll(timeout)
|
|
|
|
if err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
if ev.Type != events.DeviceConnected {
|
|
t.Error("Incorrect event type", ev.Type)
|
|
}
|
|
switch v := ev.Data.(type) {
|
|
case string:
|
|
if v != "foo" {
|
|
t.Error("Incorrect Data string", v)
|
|
}
|
|
default:
|
|
t.Errorf("Incorrect Data type %#v", v)
|
|
}
|
|
}
|
|
|
|
func TestEventAfterSubscribeIgnoreMask(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.DeviceDisconnected)
|
|
defer l.Unsubscribe(s)
|
|
l.Log(events.DeviceConnected, "foo")
|
|
|
|
_, err := s.Poll(timeout)
|
|
if err != events.ErrTimeout {
|
|
t.Fatal("Unexpected non-Timeout error:", err)
|
|
}
|
|
}
|
|
|
|
func TestBufferOverflow(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.AllEvents)
|
|
defer l.Unsubscribe(s)
|
|
|
|
t0 := time.Now()
|
|
for i := 0; i < events.BufferSize*2; i++ {
|
|
l.Log(events.DeviceConnected, "foo")
|
|
}
|
|
if time.Since(t0) > timeout {
|
|
t.Fatalf("Logging took too long")
|
|
}
|
|
}
|
|
|
|
func TestUnsubscribe(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.AllEvents)
|
|
l.Log(events.DeviceConnected, "foo")
|
|
|
|
_, err := s.Poll(timeout)
|
|
if err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
l.Unsubscribe(s)
|
|
l.Log(events.DeviceConnected, "foo")
|
|
|
|
_, err = s.Poll(timeout)
|
|
if err != events.ErrClosed {
|
|
t.Fatal("Unexpected non-Closed error:", err)
|
|
}
|
|
}
|
|
|
|
func TestGlobalIDs(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.AllEvents)
|
|
defer l.Unsubscribe(s)
|
|
l.Log(events.DeviceConnected, "foo")
|
|
_ = l.Subscribe(events.AllEvents)
|
|
l.Log(events.DeviceConnected, "bar")
|
|
|
|
ev, err := s.Poll(timeout)
|
|
if err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
if ev.Data.(string) != "foo" {
|
|
t.Fatal("Incorrect event:", ev)
|
|
}
|
|
id := ev.GlobalID
|
|
|
|
ev, err = s.Poll(timeout)
|
|
if err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
if ev.Data.(string) != "bar" {
|
|
t.Fatal("Incorrect event:", ev)
|
|
}
|
|
if ev.GlobalID != id+1 {
|
|
t.Fatalf("ID not incremented (%d != %d)", ev.GlobalID, id+1)
|
|
}
|
|
}
|
|
|
|
func TestSubscriptionIDs(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.DeviceConnected)
|
|
defer l.Unsubscribe(s)
|
|
|
|
l.Log(events.DeviceDisconnected, "a")
|
|
l.Log(events.DeviceConnected, "b")
|
|
l.Log(events.DeviceConnected, "c")
|
|
l.Log(events.DeviceDisconnected, "d")
|
|
|
|
ev, err := s.Poll(timeout)
|
|
if err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
|
|
if ev.GlobalID != 2 {
|
|
t.Fatal("Incorrect GlobalID:", ev.GlobalID)
|
|
}
|
|
if ev.SubscriptionID != 1 {
|
|
t.Fatal("Incorrect SubscriptionID:", ev.SubscriptionID)
|
|
}
|
|
|
|
ev, err = s.Poll(timeout)
|
|
if err != nil {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
if ev.GlobalID != 3 {
|
|
t.Fatal("Incorrect GlobalID:", ev.GlobalID)
|
|
}
|
|
if ev.SubscriptionID != 2 {
|
|
t.Fatal("Incorrect SubscriptionID:", ev.SubscriptionID)
|
|
}
|
|
|
|
ev, err = s.Poll(timeout)
|
|
if err != events.ErrTimeout {
|
|
t.Fatal("Unexpected error:", err)
|
|
}
|
|
}
|
|
|
|
func TestBufferedSub(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.AllEvents)
|
|
defer l.Unsubscribe(s)
|
|
bs := events.NewBufferedSubscription(s, 10*events.BufferSize)
|
|
|
|
go func() {
|
|
for i := 0; i < 10*events.BufferSize; i++ {
|
|
l.Log(events.DeviceConnected, fmt.Sprintf("event-%d", i))
|
|
if i%30 == 0 {
|
|
// Give the buffer routine time to pick up the events
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
}
|
|
}()
|
|
|
|
recv := 0
|
|
for recv < 10*events.BufferSize {
|
|
evs := bs.Since(recv, nil)
|
|
for _, ev := range evs {
|
|
if ev.GlobalID != recv+1 {
|
|
t.Fatalf("Incorrect ID; %d != %d", ev.GlobalID, recv+1)
|
|
}
|
|
recv = ev.GlobalID
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkBufferedSub(b *testing.B) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.AllEvents)
|
|
defer l.Unsubscribe(s)
|
|
bufferSize := events.BufferSize
|
|
bs := events.NewBufferedSubscription(s, bufferSize)
|
|
|
|
// The coord channel paces the sender according to the receiver,
|
|
// ensuring that no events are dropped. The benchmark measures sending +
|
|
// receiving + synchronization overhead.
|
|
|
|
coord := make(chan struct{}, bufferSize)
|
|
for i := 0; i < bufferSize-1; i++ {
|
|
coord <- struct{}{}
|
|
}
|
|
|
|
// Receive the events
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
recv := 0
|
|
var evs []events.Event
|
|
for i := 0; i < b.N; {
|
|
evs = bs.Since(recv, evs[:0])
|
|
for _, ev := range evs {
|
|
if ev.GlobalID != recv+1 {
|
|
b.Fatal("skipped event", ev.GlobalID, recv)
|
|
}
|
|
recv = ev.GlobalID
|
|
coord <- struct{}{}
|
|
}
|
|
i += len(evs)
|
|
}
|
|
}()
|
|
|
|
// Send the events
|
|
eventData := map[string]string{
|
|
"foo": "bar",
|
|
"other": "data",
|
|
"and": "something else",
|
|
}
|
|
for i := 0; i < b.N; i++ {
|
|
l.Log(events.DeviceConnected, eventData)
|
|
<-coord
|
|
}
|
|
|
|
<-done
|
|
b.ReportAllocs()
|
|
}
|
|
|
|
func TestSinceUsesSubscriptionId(t *testing.T) {
|
|
l := events.NewLogger()
|
|
|
|
s := l.Subscribe(events.DeviceConnected)
|
|
defer l.Unsubscribe(s)
|
|
bs := events.NewBufferedSubscription(s, 10*events.BufferSize)
|
|
|
|
l.Log(events.DeviceConnected, "a") // SubscriptionID = 1
|
|
l.Log(events.DeviceDisconnected, "b")
|
|
l.Log(events.DeviceDisconnected, "c")
|
|
l.Log(events.DeviceConnected, "d") // SubscriptionID = 2
|
|
|
|
events := bs.Since(0, nil)
|
|
if len(events) != 2 {
|
|
t.Fatal("Incorrect number of events:", len(events))
|
|
}
|
|
|
|
events = bs.Since(1, nil)
|
|
if len(events) != 1 {
|
|
t.Fatal("Incorrect number of events:", len(events))
|
|
}
|
|
}
|