
This commit fixes a case where a roaming geofence will not fire a "faraway" event when it's supposed to. The fix required rewriting the nearby/faraway detection logic. It is now much more accurate and takes overall less memory, but it's also a little slower per operation because each object proximity is checked twice per update. Once to compare the old object's surrounding, and once to evaulated the new object. The two lists are then used to generate accurate "nearby" and "faraway" results.
295 lines
8.0 KiB
Go
295 lines
8.0 KiB
Go
package tests
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gomodule/redigo/redis"
|
|
"github.com/tidwall/pretty"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
func fence_roaming_webhook_test(mc *mockServer) error {
|
|
car1, car2, expected := roamingTestData()
|
|
finalErr := make(chan error)
|
|
|
|
// Create a connection for subscribing to geofence notifications
|
|
sc, err := redis.Dial("tcp", fmt.Sprintf(":%d", mc.port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer sc.Close()
|
|
|
|
actual := []string{}
|
|
// Create the test http server that will capture all messages
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if err := func() error {
|
|
// Read the request body
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the new message doesn't match whats expected an error
|
|
// should be returned
|
|
actual = append(actual, cleanMessage(body))
|
|
pos := len(actual) - 1
|
|
if len(expected) < pos+1 {
|
|
return fmt.Errorf("More messages than expected were received : '%s'", actual[pos])
|
|
}
|
|
if actual[pos] != expected[pos] {
|
|
return fmt.Errorf("Expected '%s' but got '%s'", expected[pos],
|
|
actual[pos])
|
|
}
|
|
if len(actual) == len(expected) {
|
|
finalErr <- nil
|
|
}
|
|
return nil
|
|
}(); err != nil {
|
|
finalErr <- err
|
|
}
|
|
fmt.Fprintln(w, "OK!")
|
|
}))
|
|
defer ts.Close()
|
|
|
|
_, err = sc.Do("SETHOOK", "carshook", ts.URL, "NEARBY", "cars", "FENCE", "ROAM", "cars", "*", 1000)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the base connection for setting up points and geofences
|
|
bc, err := redis.Dial("tcp", fmt.Sprintf(":%d", mc.port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer bc.Close()
|
|
|
|
// Fire all car movement commands on the base client
|
|
for i := range car1 {
|
|
if _, err := bc.Do("SET", "cars", "car1", "POINT", car1[i][1],
|
|
car1[i][0]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := bc.Do("SET", "cars", "car2", "POINT", car2[i][1],
|
|
car2[i][0]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return <-finalErr
|
|
}
|
|
|
|
func goMultiFunc(mc *mockServer, fns ...func() error) error {
|
|
errs := make([]error, len(fns))
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(fns))
|
|
for i := 0; i < len(fns); i++ {
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
errs[i] = fns[i]()
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
var ferrs []error
|
|
for i := 0; i < len(errs); i++ {
|
|
if errs[i] != nil {
|
|
ferrs = append(ferrs, errs[i])
|
|
}
|
|
}
|
|
if len(ferrs) == 0 {
|
|
return nil
|
|
}
|
|
if len(ferrs) == 1 {
|
|
return ferrs[0]
|
|
}
|
|
return fmt.Errorf("%v", ferrs)
|
|
}
|
|
|
|
func fence_roaming_live_test(mc *mockServer) error {
|
|
car1, car2, expected := roamingTestData()
|
|
var liveReady sync.WaitGroup
|
|
liveReady.Add(1)
|
|
return goMultiFunc(mc,
|
|
func() error {
|
|
sc, err := redis.DialTimeout("tcp", fmt.Sprintf(":%d", mc.port),
|
|
0, time.Second*5, time.Second*5)
|
|
if err != nil {
|
|
liveReady.Done()
|
|
return err
|
|
}
|
|
defer sc.Close()
|
|
// Set up a live geofence stream
|
|
reply, err := redis.String(
|
|
sc.Do("NEARBY", "cars", "FENCE", "ROAM", "cars", "*", 1000),
|
|
)
|
|
if err != nil {
|
|
liveReady.Done()
|
|
return err
|
|
}
|
|
if reply != "OK" {
|
|
liveReady.Done()
|
|
return fmt.Errorf("expected 'OK', got '%v'", reply)
|
|
}
|
|
liveReady.Done()
|
|
for i := 0; i < len(expected); i++ {
|
|
reply, err := redis.String(sc.Receive())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reply = cleanMessage([]byte(reply))
|
|
if reply != expected[i] {
|
|
return fmt.Errorf("Expected '%s' but got '%s'",
|
|
expected[i], reply)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
func() error {
|
|
liveReady.Wait()
|
|
bc, err := redis.Dial("tcp", fmt.Sprintf(":%d", mc.port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer bc.Close()
|
|
|
|
// Fire all car movement commands on the base client
|
|
for i := range car1 {
|
|
|
|
if _, err := bc.Do("SET", "cars", "car1", "POINT", car1[i][1],
|
|
car1[i][0]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := bc.Do("SET", "cars", "car2", "POINT", car2[i][1],
|
|
car2[i][0]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
func fence_roaming_channel_test(mc *mockServer) error {
|
|
car1, car2, expected := roamingTestData()
|
|
finalErr := make(chan error)
|
|
|
|
go func() {
|
|
// Create a connection for subscribing to geofence notifications
|
|
sc, err := redis.Dial("tcp", fmt.Sprintf(":%d", mc.port))
|
|
if err != nil {
|
|
finalErr <- err
|
|
return
|
|
}
|
|
defer sc.Close()
|
|
|
|
if _, err := sc.Do("SETCHAN", "carschan", "NEARBY", "cars", "FENCE", "ROAM", "cars", "*", 1000); err != nil {
|
|
finalErr <- err
|
|
return
|
|
}
|
|
|
|
// Subscribe the subscription client to the * pattern
|
|
psc := redis.PubSubConn{Conn: sc}
|
|
if err := psc.PSubscribe("carschan"); err != nil {
|
|
finalErr <- err
|
|
return
|
|
}
|
|
|
|
actual := []string{}
|
|
for sc.Err() == nil {
|
|
if err := func() error {
|
|
var body []byte
|
|
switch v := psc.Receive().(type) {
|
|
case redis.Message:
|
|
body = v.Data
|
|
case error:
|
|
return err
|
|
}
|
|
if len(body) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// If the new message doesn't match whats expected an error
|
|
// should be returned
|
|
actual = append(actual, cleanMessage(body))
|
|
pos := len(actual) - 1
|
|
if len(expected) < pos+1 {
|
|
return fmt.Errorf("More messages than expected were received : '%s'", actual[pos])
|
|
}
|
|
if actual[pos] != expected[pos] {
|
|
return fmt.Errorf("Expected '%s' but got '%s'", expected[pos],
|
|
actual[pos])
|
|
}
|
|
if len(actual) == len(expected) {
|
|
finalErr <- nil
|
|
}
|
|
return nil
|
|
}(); err != nil {
|
|
finalErr <- err
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Create the base connection for setting up points and geofences
|
|
bc, err := redis.Dial("tcp", fmt.Sprintf(":%d", mc.port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer bc.Close()
|
|
|
|
// Fire all car movement commands on the base client
|
|
for i := range car1 {
|
|
if _, err := bc.Do("SET", "cars", "car1", "POINT", car1[i][1],
|
|
car1[i][0]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := bc.Do("SET", "cars", "car2", "POINT", car2[i][1],
|
|
car2[i][0]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return <-finalErr
|
|
}
|
|
|
|
func cleanMessage(body []byte) string {
|
|
// Remove fields that are non-deterministic or use case specific
|
|
msg, _ := sjson.Delete(string(body), "group")
|
|
msg, _ = sjson.Delete(msg, "time")
|
|
msg, _ = sjson.Delete(msg, "hook")
|
|
msg = string(pretty.Ugly([]byte(msg)))
|
|
return msg
|
|
}
|
|
|
|
func roamingTestData() (car1 [][]float64, car2 [][]float64, output []string) {
|
|
car1 = [][]float64{
|
|
{-111.93669319152832, 33.414750027566235},
|
|
{-111.93051338195801, 33.414750027566235},
|
|
{-111.92416191101074, 33.414750027566235},
|
|
{-111.91789627075195, 33.414750027566235},
|
|
{-111.9111156463623, 33.414750027566235},
|
|
{-111.90510749816895, 33.414750027566235},
|
|
{-111.89746856689453, 33.414750027566235},
|
|
}
|
|
car2 = [][]float64{
|
|
{-111.89746856689453, 33.414750027566235},
|
|
{-111.90519332885742, 33.414750027566235},
|
|
{-111.91154479980467, 33.414750027566235},
|
|
{-111.91781044006346, 33.414750027566235},
|
|
{-111.92416191101074, 33.414750027566235},
|
|
{-111.93059921264648, 33.414750027566235},
|
|
{-111.93660736083984, 33.414750027566235},
|
|
}
|
|
output = []string{
|
|
`{"command":"set","detect":"roam","key":"cars","id":"car1","object":{"type":"Point","coordinates":[-111.91789627075195,33.414750027566235]},"nearby":{"key":"cars","id":"car2","object":{"type":"Point","coordinates":[-111.91154479980467,33.414750027566235]},"meters":589.512}}`,
|
|
`{"command":"set","detect":"roam","key":"cars","id":"car2","object":{"type":"Point","coordinates":[-111.91781044006346,33.414750027566235]},"nearby":{"key":"cars","id":"car1","object":{"type":"Point","coordinates":[-111.91789627075195,33.414750027566235]},"meters":7.966}}`,
|
|
`{"command":"set","detect":"roam","key":"cars","id":"car1","object":{"type":"Point","coordinates":[-111.9111156463623,33.414750027566235]},"nearby":{"key":"cars","id":"car2","object":{"type":"Point","coordinates":[-111.91781044006346,33.414750027566235]},"meters":621.377}}`,
|
|
`{"command":"set","detect":"roam","key":"cars","id":"car2","object":{"type":"Point","coordinates":[-111.92416191101074,33.414750027566235]},"faraway":{"key":"cars","id":"car1","object":{"type":"Point","coordinates":[-111.9111156463623,33.414750027566235]},"meters":1210.89}}`,
|
|
}
|
|
return
|
|
}
|