package coordinate
|
|
import (
|
"fmt"
|
"math"
|
"reflect"
|
"strings"
|
"testing"
|
"time"
|
)
|
|
func TestClient_NewClient(t *testing.T) {
|
config := DefaultConfig()
|
|
config.Dimensionality = 0
|
client, err := NewClient(config)
|
if err == nil || !strings.Contains(err.Error(), "dimensionality") {
|
t.Fatal(err)
|
}
|
|
config.Dimensionality = 7
|
client, err = NewClient(config)
|
if err != nil {
|
t.Fatal(err)
|
}
|
|
origin := NewCoordinate(config)
|
if !reflect.DeepEqual(client.GetCoordinate(), origin) {
|
t.Fatalf("fresh client should be located at the origin")
|
}
|
}
|
|
func TestClient_Update(t *testing.T) {
|
config := DefaultConfig()
|
config.Dimensionality = 3
|
|
client, err := NewClient(config)
|
if err != nil {
|
t.Fatal(err)
|
}
|
|
// Make sure the Euclidean part of our coordinate is what we expect.
|
c := client.GetCoordinate()
|
verifyEqualVectors(t, c.Vec, []float64{0.0, 0.0, 0.0})
|
|
// Place a node right above the client and observe an RTT longer than the
|
// client expects, given its distance.
|
other := NewCoordinate(config)
|
other.Vec[2] = 0.001
|
rtt := time.Duration(2.0 * other.Vec[2] * secondsToNanoseconds)
|
c, err = client.Update("node", other, rtt)
|
if err != nil {
|
t.Fatalf("err: %v", err)
|
}
|
|
// The client should have scooted down to get away from it.
|
if !(c.Vec[2] < 0.0) {
|
t.Fatalf("client z coordinate %9.6f should be < 0.0", c.Vec[2])
|
}
|
|
// Set the coordinate to a known state.
|
c.Vec[2] = 99.0
|
client.SetCoordinate(c)
|
c = client.GetCoordinate()
|
verifyEqualFloats(t, c.Vec[2], 99.0)
|
}
|
|
func TestClient_InvalidInPingValues(t *testing.T) {
|
config := DefaultConfig()
|
config.Dimensionality = 3
|
|
client, err := NewClient(config)
|
if err != nil {
|
t.Fatal(err)
|
}
|
|
// Place another node
|
other := NewCoordinate(config)
|
other.Vec[2] = 0.001
|
dist := client.DistanceTo(other)
|
|
// Update with a series of invalid ping periods, should return an error and estimated rtt remains unchanged
|
pings := []int{1<<63 - 1, -35, 11}
|
|
for _, ping := range pings {
|
expectedErr := fmt.Errorf("round trip time not in valid range, duration %v is not a positive value less than %v", ping, 10*time.Second)
|
_, err = client.Update("node", other, time.Duration(ping*secondsToNanoseconds))
|
if err == nil {
|
t.Fatalf("Unexpected error, wanted %v but got %v", expectedErr, err)
|
}
|
|
dist_new := client.DistanceTo(other)
|
if dist_new != dist {
|
t.Fatalf("distance estimate %v not equal to %v", dist_new, dist)
|
}
|
}
|
|
}
|
|
func TestClient_DistanceTo(t *testing.T) {
|
config := DefaultConfig()
|
config.Dimensionality = 3
|
config.HeightMin = 0
|
|
client, err := NewClient(config)
|
if err != nil {
|
t.Fatal(err)
|
}
|
|
// Fiddle a raw coordinate to put it a specific number of seconds away.
|
other := NewCoordinate(config)
|
other.Vec[2] = 12.345
|
expected := time.Duration(other.Vec[2] * secondsToNanoseconds)
|
dist := client.DistanceTo(other)
|
if dist != expected {
|
t.Fatalf("distance doesn't match %9.6f != %9.6f", dist.Seconds(), expected.Seconds())
|
}
|
}
|
|
func TestClient_latencyFilter(t *testing.T) {
|
config := DefaultConfig()
|
config.LatencyFilterSize = 3
|
|
client, err := NewClient(config)
|
if err != nil {
|
t.Fatal(err)
|
}
|
|
// Make sure we get the median, and that things age properly.
|
verifyEqualFloats(t, client.latencyFilter("alice", 0.201), 0.201)
|
verifyEqualFloats(t, client.latencyFilter("alice", 0.200), 0.201)
|
verifyEqualFloats(t, client.latencyFilter("alice", 0.207), 0.201)
|
|
// This glitch will get median-ed out and never seen by Vivaldi.
|
verifyEqualFloats(t, client.latencyFilter("alice", 1.9), 0.207)
|
verifyEqualFloats(t, client.latencyFilter("alice", 0.203), 0.207)
|
verifyEqualFloats(t, client.latencyFilter("alice", 0.199), 0.203)
|
verifyEqualFloats(t, client.latencyFilter("alice", 0.211), 0.203)
|
|
// Make sure different nodes are not coupled.
|
verifyEqualFloats(t, client.latencyFilter("bob", 0.310), 0.310)
|
|
// Make sure we don't leak coordinates for nodes that leave.
|
client.ForgetNode("alice")
|
verifyEqualFloats(t, client.latencyFilter("alice", 0.888), 0.888)
|
}
|
|
func TestClient_NaN_Defense(t *testing.T) {
|
config := DefaultConfig()
|
config.Dimensionality = 3
|
|
client, err := NewClient(config)
|
if err != nil {
|
t.Fatal(err)
|
}
|
|
// Block a bad coordinate from coming in.
|
other := NewCoordinate(config)
|
other.Vec[0] = math.NaN()
|
if other.IsValid() {
|
t.Fatalf("bad: %#v", *other)
|
}
|
rtt := 250 * time.Millisecond
|
c, err := client.Update("node", other, rtt)
|
if err == nil || !strings.Contains(err.Error(), "coordinate is invalid") {
|
t.Fatalf("err: %v", err)
|
}
|
if c := client.GetCoordinate(); !c.IsValid() {
|
t.Fatalf("bad: %#v", *c)
|
}
|
|
// Block setting an invalid coordinate directly.
|
err = client.SetCoordinate(other)
|
if err == nil || !strings.Contains(err.Error(), "coordinate is invalid") {
|
t.Fatalf("err: %v", err)
|
}
|
if c := client.GetCoordinate(); !c.IsValid() {
|
t.Fatalf("bad: %#v", *c)
|
}
|
|
// Block an incompatible coordinate.
|
other.Vec = make([]float64, 2*len(other.Vec))
|
c, err = client.Update("node", other, rtt)
|
if err == nil || !strings.Contains(err.Error(), "dimensions aren't compatible") {
|
t.Fatalf("err: %v", err)
|
}
|
if c := client.GetCoordinate(); !c.IsValid() {
|
t.Fatalf("bad: %#v", *c)
|
}
|
|
// Block setting an incompatible coordinate directly.
|
err = client.SetCoordinate(other)
|
if err == nil || !strings.Contains(err.Error(), "dimensions aren't compatible") {
|
t.Fatalf("err: %v", err)
|
}
|
if c := client.GetCoordinate(); !c.IsValid() {
|
t.Fatalf("bad: %#v", *c)
|
}
|
|
// Poison the internal state and make sure we reset on an update.
|
client.coord.Vec[0] = math.NaN()
|
other = NewCoordinate(config)
|
c, err = client.Update("node", other, rtt)
|
if err != nil {
|
t.Fatalf("err: %v", err)
|
}
|
if !c.IsValid() {
|
t.Fatalf("bad: %#v", *c)
|
}
|
if got, want := client.Stats().Resets, 1; got != want {
|
t.Fatalf("got %d want %d", got, want)
|
}
|
}
|