package coordinate import ( "math" "testing" "time" ) func TestPerformance_Line(t *testing.T) { const spacing = 10 * time.Millisecond const nodes, cycles = 10, 1000 config := DefaultConfig() clients, err := GenerateClients(nodes, config) if err != nil { t.Fatal(err) } truth := GenerateLine(nodes, spacing) Simulate(clients, truth, cycles) stats := Evaluate(clients, truth) if stats.ErrorAvg > 0.0018 || stats.ErrorMax > 0.0092 { t.Fatalf("performance stats are out of spec: %v", stats) } } func TestPerformance_Grid(t *testing.T) { const spacing = 10 * time.Millisecond const nodes, cycles = 25, 1000 config := DefaultConfig() clients, err := GenerateClients(nodes, config) if err != nil { t.Fatal(err) } truth := GenerateGrid(nodes, spacing) Simulate(clients, truth, cycles) stats := Evaluate(clients, truth) if stats.ErrorAvg > 0.0015 || stats.ErrorMax > 0.022 { t.Fatalf("performance stats are out of spec: %v", stats) } } func TestPerformance_Split(t *testing.T) { const lan, wan = 1 * time.Millisecond, 10 * time.Millisecond const nodes, cycles = 25, 1000 config := DefaultConfig() clients, err := GenerateClients(nodes, config) if err != nil { t.Fatal(err) } truth := GenerateSplit(nodes, lan, wan) Simulate(clients, truth, cycles) stats := Evaluate(clients, truth) if stats.ErrorAvg > 0.000060 || stats.ErrorMax > 0.00048 { t.Fatalf("performance stats are out of spec: %v", stats) } } func TestPerformance_Height(t *testing.T) { const radius = 100 * time.Millisecond const nodes, cycles = 25, 1000 // Constrain us to two dimensions so that we can just exactly represent // the circle. config := DefaultConfig() config.Dimensionality = 2 clients, err := GenerateClients(nodes, config) if err != nil { t.Fatal(err) } // Generate truth where the first coordinate is in the "middle" because // it's equidistant from all the nodes, but it will have an extra radius // added to the distance, so it should come out above all the others. truth := GenerateCircle(nodes, radius) Simulate(clients, truth, cycles) // Make sure the height looks reasonable with the regular nodes all in a // plane, and the center node up above. for i, _ := range clients { coord := clients[i].GetCoordinate() if i == 0 { if coord.Height < 0.97*radius.Seconds() { t.Fatalf("height is out of spec: %9.6f", coord.Height) } } else { if coord.Height > 0.03*radius.Seconds() { t.Fatalf("height is out of spec: %9.6f", coord.Height) } } } stats := Evaluate(clients, truth) if stats.ErrorAvg > 0.0025 || stats.ErrorMax > 0.064 { t.Fatalf("performance stats are out of spec: %v", stats) } } func TestPerformance_Drift(t *testing.T) { const dist = 500 * time.Millisecond const nodes = 4 config := DefaultConfig() config.Dimensionality = 2 clients, err := GenerateClients(nodes, config) if err != nil { t.Fatal(err) } // Do some icky surgery on the clients to put them into a square, up in // the first quadrant. clients[0].coord.Vec = []float64{0.0, 0.0} clients[1].coord.Vec = []float64{0.0, dist.Seconds()} clients[2].coord.Vec = []float64{dist.Seconds(), dist.Seconds()} clients[3].coord.Vec = []float64{dist.Seconds(), dist.Seconds()} // Make a corresponding truth matrix. The nodes are laid out like this // so the distances are all equal, except for the diagonal: // // (1) <- dist -> (2) // // | <- dist | // | | // | dist -> | // // (0) <- dist -> (3) // truth := make([][]time.Duration, nodes) for i := range truth { truth[i] = make([]time.Duration, nodes) } for i := 0; i < nodes; i++ { for j := i + 1; j < nodes; j++ { rtt := dist if (i%2 == 0) && (j%2 == 0) { rtt = time.Duration(math.Sqrt2 * float64(rtt)) } truth[i][j], truth[j][i] = rtt, rtt } } calcCenterError := func() float64 { min, max := clients[0].GetCoordinate(), clients[0].GetCoordinate() for i := 1; i < nodes; i++ { coord := clients[i].GetCoordinate() for j, v := range coord.Vec { min.Vec[j] = math.Min(min.Vec[j], v) max.Vec[j] = math.Max(max.Vec[j], v) } } mid := make([]float64, config.Dimensionality) for i, _ := range mid { mid[i] = min.Vec[i] + (max.Vec[i]-min.Vec[i])/2 } return magnitude(mid) } // Let the simulation run for a while to stabilize, then snap a baseline // for the center error. Simulate(clients, truth, 1000) baseline := calcCenterError() // Now run for a bunch more cycles and see if gravity pulls the center // in the right direction. Simulate(clients, truth, 10000) if error := calcCenterError(); error > 0.8*baseline { t.Fatalf("drift performance out of spec: %9.6f -> %9.6f", baseline, error) } } func TestPerformance_Random(t *testing.T) { const mean, deviation = 100 * time.Millisecond, 10 * time.Millisecond const nodes, cycles = 25, 1000 config := DefaultConfig() clients, err := GenerateClients(nodes, config) if err != nil { t.Fatal(err) } truth := GenerateRandom(nodes, mean, deviation) Simulate(clients, truth, cycles) stats := Evaluate(clients, truth) if stats.ErrorAvg > 0.075 || stats.ErrorMax > 0.33 { t.Fatalf("performance stats are out of spec: %v", stats) } }