
The big change is that the GeoJSON package has been completely rewritten to fix a few of geometry calculation bugs, increase performance, and to better follow the GeoJSON spec RFC 7946. GeoJSON updates - A LineString now requires at least two points. - All json members, even foreign, now persist with the object. - The bbox member persists too but is no longer used for geometry calculations. This is change in behavior. Previously Tile38 would treat the bbox as the object's physical rectangle. - Corrections to geometry intersects and within calculations. Faster spatial queries - The performance of Point-in-polygon and object intersect operations are greatly improved for complex polygons and line strings. It went from O(n) to roughly O(log n). - The same for all collection types with many children, including FeatureCollection, GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon. Codebase changes - The pkg directory has been renamed to internal - The GeoJSON internal package has been moved to a seperate repo at https://github.com/tidwall/geojson. It's now vendored. Please look out for higher memory usage for datasets using complex shapes. A complex shape is one that has 64 or more points. For these shapes it's expected that there will be increase of least 54 bytes per point.
1268 lines
36 KiB
Go
1268 lines
36 KiB
Go
// Copyright 2012-2018 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package nats
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Package scoped specific tests here..
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/gnatsd/server"
|
|
gnatsd "github.com/nats-io/gnatsd/test"
|
|
)
|
|
|
|
// Dumb wait program to sync on callbacks, etc... Will timeout
|
|
func Wait(ch chan bool) error {
|
|
return WaitTime(ch, 5*time.Second)
|
|
}
|
|
|
|
func WaitTime(ch chan bool, timeout time.Duration) error {
|
|
select {
|
|
case <-ch:
|
|
return nil
|
|
case <-time.After(timeout):
|
|
}
|
|
return errors.New("timeout")
|
|
}
|
|
|
|
func stackFatalf(t *testing.T, f string, args ...interface{}) {
|
|
lines := make([]string, 0, 32)
|
|
msg := fmt.Sprintf(f, args...)
|
|
lines = append(lines, msg)
|
|
|
|
// Generate the Stack of callers: Skip us and verify* frames.
|
|
for i := 1; true; i++ {
|
|
_, file, line, ok := runtime.Caller(i)
|
|
if !ok {
|
|
break
|
|
}
|
|
msg := fmt.Sprintf("%d - %s:%d", i, file, line)
|
|
lines = append(lines, msg)
|
|
}
|
|
t.Fatalf("%s", strings.Join(lines, "\n"))
|
|
}
|
|
|
|
func TestVersionMatchesTag(t *testing.T) {
|
|
tag := os.Getenv("TRAVIS_TAG")
|
|
if tag == "" {
|
|
t.SkipNow()
|
|
}
|
|
// We expect a tag of the form vX.Y.Z. If that's not the case,
|
|
// we need someone to have a look. So fail if first letter is not
|
|
// a `v`
|
|
if tag[0] != 'v' {
|
|
t.Fatalf("Expect tag to start with `v`, tag is: %s", tag)
|
|
}
|
|
// Strip the `v` from the tag for the version comparison.
|
|
if Version != tag[1:] {
|
|
t.Fatalf("Version (%s) does not match tag (%s)", Version, tag[1:])
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Reconnect tests
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const TEST_PORT = 8368
|
|
|
|
var reconnectOpts = Options{
|
|
Url: fmt.Sprintf("nats://localhost:%d", TEST_PORT),
|
|
AllowReconnect: true,
|
|
MaxReconnect: 10,
|
|
ReconnectWait: 100 * time.Millisecond,
|
|
Timeout: DefaultTimeout,
|
|
}
|
|
|
|
func RunServerOnPort(port int) *server.Server {
|
|
opts := gnatsd.DefaultTestOptions
|
|
opts.Port = port
|
|
return RunServerWithOptions(opts)
|
|
}
|
|
|
|
func RunServerWithOptions(opts server.Options) *server.Server {
|
|
return gnatsd.RunServer(&opts)
|
|
}
|
|
|
|
func TestReconnectServerStats(t *testing.T) {
|
|
ts := RunServerOnPort(TEST_PORT)
|
|
|
|
opts := reconnectOpts
|
|
nc, _ := opts.Connect()
|
|
defer nc.Close()
|
|
nc.Flush()
|
|
|
|
ts.Shutdown()
|
|
// server is stopped here...
|
|
|
|
ts = RunServerOnPort(TEST_PORT)
|
|
defer ts.Shutdown()
|
|
|
|
if err := nc.FlushTimeout(5 * time.Second); err != nil {
|
|
t.Fatalf("Error on Flush: %v", err)
|
|
}
|
|
|
|
// Make sure the server who is reconnected has the reconnects stats reset.
|
|
nc.mu.Lock()
|
|
_, cur := nc.currentServer()
|
|
nc.mu.Unlock()
|
|
|
|
if cur.reconnects != 0 {
|
|
t.Fatalf("Current Server's reconnects should be 0 vs %d\n", cur.reconnects)
|
|
}
|
|
}
|
|
|
|
func TestParseStateReconnectFunctionality(t *testing.T) {
|
|
ts := RunServerOnPort(TEST_PORT)
|
|
ch := make(chan bool)
|
|
|
|
opts := reconnectOpts
|
|
dch := make(chan bool)
|
|
opts.DisconnectedCB = func(_ *Conn) {
|
|
dch <- true
|
|
}
|
|
|
|
nc, errc := opts.Connect()
|
|
if errc != nil {
|
|
t.Fatalf("Failed to create a connection: %v\n", errc)
|
|
}
|
|
ec, errec := NewEncodedConn(nc, DEFAULT_ENCODER)
|
|
if errec != nil {
|
|
nc.Close()
|
|
t.Fatalf("Failed to create an encoded connection: %v\n", errec)
|
|
}
|
|
defer ec.Close()
|
|
|
|
testString := "bar"
|
|
ec.Subscribe("foo", func(s string) {
|
|
if s != testString {
|
|
t.Fatal("String doesn't match")
|
|
}
|
|
ch <- true
|
|
})
|
|
ec.Flush()
|
|
|
|
// Got a RACE condition with Travis build. The locking below does not
|
|
// really help because the parser running in the readLoop accesses
|
|
// nc.ps without the connection lock. Sleeping may help better since
|
|
// it would make the memory write in parse.go (when processing the
|
|
// pong) further away from the modification below.
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// Simulate partialState, this needs to be cleared
|
|
nc.mu.Lock()
|
|
nc.ps.state = OP_PON
|
|
nc.mu.Unlock()
|
|
|
|
ts.Shutdown()
|
|
// server is stopped here...
|
|
|
|
if err := Wait(dch); err != nil {
|
|
t.Fatal("Did not get the DisconnectedCB")
|
|
}
|
|
|
|
if err := ec.Publish("foo", testString); err != nil {
|
|
t.Fatalf("Failed to publish message: %v\n", err)
|
|
}
|
|
|
|
ts = RunServerOnPort(TEST_PORT)
|
|
defer ts.Shutdown()
|
|
|
|
if err := ec.FlushTimeout(5 * time.Second); err != nil {
|
|
t.Fatalf("Error on Flush: %v", err)
|
|
}
|
|
|
|
if err := Wait(ch); err != nil {
|
|
t.Fatal("Did not receive our message")
|
|
}
|
|
|
|
expectedReconnectCount := uint64(1)
|
|
reconnectedCount := ec.Conn.Stats().Reconnects
|
|
|
|
if reconnectedCount != expectedReconnectCount {
|
|
t.Fatalf("Reconnect count incorrect: %d vs %d\n",
|
|
reconnectedCount, expectedReconnectCount)
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// ServerPool tests
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
var testServers = []string{
|
|
"nats://localhost:1222",
|
|
"nats://localhost:1223",
|
|
"nats://localhost:1224",
|
|
"nats://localhost:1225",
|
|
"nats://localhost:1226",
|
|
"nats://localhost:1227",
|
|
"nats://localhost:1228",
|
|
}
|
|
|
|
func TestSimplifiedURLs(t *testing.T) {
|
|
opts := GetDefaultOptions()
|
|
opts.NoRandomize = true
|
|
opts.Servers = []string{
|
|
"nats://host1:1234",
|
|
"nats://host2:",
|
|
"nats://host3",
|
|
"host4:1234",
|
|
"host5:",
|
|
"host6",
|
|
"nats://[1:2:3:4]:1234",
|
|
"nats://[5:6:7:8]:",
|
|
"nats://[9:10:11:12]",
|
|
"[13:14:15:16]:",
|
|
"[17:18:19:20]:1234",
|
|
}
|
|
|
|
// We expect the result in the server pool to be:
|
|
expected := []string{
|
|
"nats://host1:1234",
|
|
"nats://host2:4222",
|
|
"nats://host3:4222",
|
|
"nats://host4:1234",
|
|
"nats://host5:4222",
|
|
"nats://host6:4222",
|
|
"nats://[1:2:3:4]:1234",
|
|
"nats://[5:6:7:8]:4222",
|
|
"nats://[9:10:11:12]:4222",
|
|
"nats://[13:14:15:16]:4222",
|
|
"nats://[17:18:19:20]:1234",
|
|
}
|
|
|
|
nc := &Conn{Opts: opts}
|
|
if err := nc.setupServerPool(); err != nil {
|
|
t.Fatalf("Problem setting up Server Pool: %v\n", err)
|
|
}
|
|
// Check server pool directly
|
|
for i, u := range nc.srvPool {
|
|
if u.url.String() != expected[i] {
|
|
t.Fatalf("Expected url %q, got %q", expected[i], u.url.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestServersRandomize(t *testing.T) {
|
|
opts := GetDefaultOptions()
|
|
opts.Servers = testServers
|
|
nc := &Conn{Opts: opts}
|
|
if err := nc.setupServerPool(); err != nil {
|
|
t.Fatalf("Problem setting up Server Pool: %v\n", err)
|
|
}
|
|
// Build []string from srvPool
|
|
clientServers := []string{}
|
|
for _, s := range nc.srvPool {
|
|
clientServers = append(clientServers, s.url.String())
|
|
}
|
|
// In theory this could happen..
|
|
if reflect.DeepEqual(testServers, clientServers) {
|
|
t.Fatalf("ServerPool list not randomized\n")
|
|
}
|
|
|
|
// Now test that we do not randomize if proper flag is set.
|
|
opts = GetDefaultOptions()
|
|
opts.Servers = testServers
|
|
opts.NoRandomize = true
|
|
nc = &Conn{Opts: opts}
|
|
if err := nc.setupServerPool(); err != nil {
|
|
t.Fatalf("Problem setting up Server Pool: %v\n", err)
|
|
}
|
|
// Build []string from srvPool
|
|
clientServers = []string{}
|
|
for _, s := range nc.srvPool {
|
|
clientServers = append(clientServers, s.url.String())
|
|
}
|
|
if !reflect.DeepEqual(testServers, clientServers) {
|
|
t.Fatalf("ServerPool list should not be randomized\n")
|
|
}
|
|
|
|
// Although the original intent was that if Opts.Url is
|
|
// set, Opts.Servers is not (and vice versa), the behavior
|
|
// is that Opts.Url is always first, even when randomization
|
|
// is enabled. So make sure that this is still the case.
|
|
opts = GetDefaultOptions()
|
|
opts.Url = DefaultURL
|
|
opts.Servers = testServers
|
|
nc = &Conn{Opts: opts}
|
|
if err := nc.setupServerPool(); err != nil {
|
|
t.Fatalf("Problem setting up Server Pool: %v\n", err)
|
|
}
|
|
// Build []string from srvPool
|
|
clientServers = []string{}
|
|
for _, s := range nc.srvPool {
|
|
clientServers = append(clientServers, s.url.String())
|
|
}
|
|
// In theory this could happen..
|
|
if reflect.DeepEqual(testServers, clientServers) {
|
|
t.Fatalf("ServerPool list not randomized\n")
|
|
}
|
|
if clientServers[0] != DefaultURL {
|
|
t.Fatalf("Options.Url should be first in the array, got %v", clientServers[0])
|
|
}
|
|
}
|
|
|
|
func TestSelectNextServer(t *testing.T) {
|
|
opts := GetDefaultOptions()
|
|
opts.Servers = testServers
|
|
opts.NoRandomize = true
|
|
nc := &Conn{Opts: opts}
|
|
if err := nc.setupServerPool(); err != nil {
|
|
t.Fatalf("Problem setting up Server Pool: %v\n", err)
|
|
}
|
|
if nc.url != nc.srvPool[0].url {
|
|
t.Fatalf("Wrong default selection: %v\n", nc.url)
|
|
}
|
|
|
|
sel, err := nc.selectNextServer()
|
|
if err != nil {
|
|
t.Fatalf("Got an err: %v\n", err)
|
|
}
|
|
// Check that we are now looking at #2, and current is now last.
|
|
if len(nc.srvPool) != len(testServers) {
|
|
t.Fatalf("List is incorrect size: %d vs %d\n", len(nc.srvPool), len(testServers))
|
|
}
|
|
if nc.url.String() != testServers[1] {
|
|
t.Fatalf("Selection incorrect: %v vs %v\n", nc.url, testServers[1])
|
|
}
|
|
if nc.srvPool[len(nc.srvPool)-1].url.String() != testServers[0] {
|
|
t.Fatalf("Did not push old to last position\n")
|
|
}
|
|
if sel != nc.srvPool[0] {
|
|
t.Fatalf("Did not return correct server: %v vs %v\n", sel.url, nc.srvPool[0].url)
|
|
}
|
|
|
|
// Test that we do not keep servers where we have tried to reconnect past our limit.
|
|
nc.srvPool[0].reconnects = int(opts.MaxReconnect)
|
|
if _, err := nc.selectNextServer(); err != nil {
|
|
t.Fatalf("Got an err: %v\n", err)
|
|
}
|
|
// Check that we are now looking at #3, and current is not in the list.
|
|
if len(nc.srvPool) != len(testServers)-1 {
|
|
t.Fatalf("List is incorrect size: %d vs %d\n", len(nc.srvPool), len(testServers)-1)
|
|
}
|
|
if nc.url.String() != testServers[2] {
|
|
t.Fatalf("Selection incorrect: %v vs %v\n", nc.url, testServers[2])
|
|
}
|
|
if nc.srvPool[len(nc.srvPool)-1].url.String() == testServers[1] {
|
|
t.Fatalf("Did not throw away the last server correctly\n")
|
|
}
|
|
}
|
|
|
|
// This will test that comma separated url strings work properly for
|
|
// the Connect() command.
|
|
func TestUrlArgument(t *testing.T) {
|
|
check := func(url string, expected []string) {
|
|
if !reflect.DeepEqual(processUrlString(url), expected) {
|
|
t.Fatalf("Got wrong response processing URL: %q, RES: %#v\n", url, processUrlString(url))
|
|
}
|
|
}
|
|
// This is normal case
|
|
oneExpected := []string{"nats://localhost:1222"}
|
|
|
|
check("nats://localhost:1222", oneExpected)
|
|
check("nats://localhost:1222 ", oneExpected)
|
|
check(" nats://localhost:1222", oneExpected)
|
|
check(" nats://localhost:1222 ", oneExpected)
|
|
|
|
var multiExpected = []string{
|
|
"nats://localhost:1222",
|
|
"nats://localhost:1223",
|
|
"nats://localhost:1224",
|
|
}
|
|
|
|
check("nats://localhost:1222,nats://localhost:1223,nats://localhost:1224", multiExpected)
|
|
check("nats://localhost:1222, nats://localhost:1223, nats://localhost:1224", multiExpected)
|
|
check(" nats://localhost:1222, nats://localhost:1223, nats://localhost:1224 ", multiExpected)
|
|
check("nats://localhost:1222, nats://localhost:1223 ,nats://localhost:1224", multiExpected)
|
|
}
|
|
|
|
func TestParserPing(t *testing.T) {
|
|
c := &Conn{}
|
|
fake := &bytes.Buffer{}
|
|
c.bw = bufio.NewWriterSize(fake, c.Opts.ReconnectBufSize)
|
|
|
|
c.ps = &parseState{}
|
|
|
|
if c.ps.state != OP_START {
|
|
t.Fatalf("Expected OP_START vs %d\n", c.ps.state)
|
|
}
|
|
ping := []byte("PING\r\n")
|
|
err := c.parse(ping[:1])
|
|
if err != nil || c.ps.state != OP_P {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(ping[1:2])
|
|
if err != nil || c.ps.state != OP_PI {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(ping[2:3])
|
|
if err != nil || c.ps.state != OP_PIN {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(ping[3:4])
|
|
if err != nil || c.ps.state != OP_PING {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(ping[4:5])
|
|
if err != nil || c.ps.state != OP_PING {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(ping[5:6])
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(ping)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
// Should tolerate spaces
|
|
ping = []byte("PING \r")
|
|
err = c.parse(ping)
|
|
if err != nil || c.ps.state != OP_PING {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
c.ps.state = OP_START
|
|
ping = []byte("PING \r \n")
|
|
err = c.parse(ping)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
}
|
|
|
|
func TestParserErr(t *testing.T) {
|
|
c := &Conn{}
|
|
c.status = CLOSED
|
|
fake := &bytes.Buffer{}
|
|
c.bw = bufio.NewWriterSize(fake, c.Opts.ReconnectBufSize)
|
|
|
|
c.ps = &parseState{}
|
|
|
|
// This test focuses on the parser only, not how the error is
|
|
// actually processed by the upper layer.
|
|
|
|
if c.ps.state != OP_START {
|
|
t.Fatalf("Expected OP_START vs %d\n", c.ps.state)
|
|
}
|
|
|
|
expectedError := "'Any kind of error'"
|
|
errProto := []byte("-ERR " + expectedError + "\r\n")
|
|
err := c.parse(errProto[:1])
|
|
if err != nil || c.ps.state != OP_MINUS {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[1:2])
|
|
if err != nil || c.ps.state != OP_MINUS_E {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[2:3])
|
|
if err != nil || c.ps.state != OP_MINUS_ER {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[3:4])
|
|
if err != nil || c.ps.state != OP_MINUS_ERR {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[4:5])
|
|
if err != nil || c.ps.state != OP_MINUS_ERR_SPC {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[5:6])
|
|
if err != nil || c.ps.state != OP_MINUS_ERR_SPC {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
|
|
// Check with split arg buffer
|
|
err = c.parse(errProto[6:7])
|
|
if err != nil || c.ps.state != MINUS_ERR_ARG {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[7:10])
|
|
if err != nil || c.ps.state != MINUS_ERR_ARG {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[10 : len(errProto)-2])
|
|
if err != nil || c.ps.state != MINUS_ERR_ARG {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
if c.ps.argBuf == nil {
|
|
t.Fatal("ArgBuf should not be nil")
|
|
}
|
|
s := string(c.ps.argBuf)
|
|
if s != expectedError {
|
|
t.Fatalf("Expected %v, got %v", expectedError, s)
|
|
}
|
|
err = c.parse(errProto[len(errProto)-2:])
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
|
|
// Check without split arg buffer
|
|
errProto = []byte("-ERR 'Any error'\r\n")
|
|
err = c.parse(errProto)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
}
|
|
|
|
func TestParserOK(t *testing.T) {
|
|
c := &Conn{}
|
|
c.ps = &parseState{}
|
|
|
|
if c.ps.state != OP_START {
|
|
t.Fatalf("Expected OP_START vs %d\n", c.ps.state)
|
|
}
|
|
errProto := []byte("+OKay\r\n")
|
|
err := c.parse(errProto[:1])
|
|
if err != nil || c.ps.state != OP_PLUS {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[1:2])
|
|
if err != nil || c.ps.state != OP_PLUS_O {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[2:3])
|
|
if err != nil || c.ps.state != OP_PLUS_OK {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(errProto[3:])
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
}
|
|
|
|
func TestParserShouldFail(t *testing.T) {
|
|
c := &Conn{}
|
|
c.ps = &parseState{}
|
|
|
|
if err := c.parse([]byte(" PING")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("POO")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("Px")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("PIx")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("PINx")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
// Stop here because 'PING' protos are tolerant for anything between PING and \n
|
|
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("POx")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("PONx")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
// Stop here because 'PONG' protos are tolerant for anything between PONG and \n
|
|
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("ZOO")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("Mx\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSx\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSGx\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSG foo\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSG \r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSG foo 1\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSG foo bar 1\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSG foo bar 1 baz\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("MSG foo 1 bar baz\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("+x\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("+Ox\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("-x\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("-Ex\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("-ERx\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
c.ps.state = OP_START
|
|
if err := c.parse([]byte("-ERRx\r\n")); err == nil {
|
|
t.Fatal("Should have received a parse error")
|
|
}
|
|
}
|
|
|
|
func TestParserSplitMsg(t *testing.T) {
|
|
nc := &Conn{}
|
|
nc.ps = &parseState{}
|
|
|
|
buf := []byte("MSG a\r\n")
|
|
err := nc.parse(buf)
|
|
if err == nil {
|
|
t.Fatal("Expected an error")
|
|
}
|
|
nc.ps = &parseState{}
|
|
|
|
buf = []byte("MSG a b c\r\n")
|
|
err = nc.parse(buf)
|
|
if err == nil {
|
|
t.Fatal("Expected an error")
|
|
}
|
|
nc.ps = &parseState{}
|
|
|
|
expectedCount := uint64(1)
|
|
expectedSize := uint64(3)
|
|
|
|
buf = []byte("MSG a")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if nc.ps.argBuf == nil {
|
|
t.Fatal("Arg buffer should have been created")
|
|
}
|
|
|
|
buf = []byte(" 1 3\r\nf")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if nc.ps.ma.size != 3 {
|
|
t.Fatalf("Wrong msg size: %d instead of 3", nc.ps.ma.size)
|
|
}
|
|
if nc.ps.ma.sid != 1 {
|
|
t.Fatalf("Wrong sid: %d instead of 1", nc.ps.ma.sid)
|
|
}
|
|
if string(nc.ps.ma.subject) != "a" {
|
|
t.Fatalf("Wrong subject: '%s' instead of 'a'", string(nc.ps.ma.subject))
|
|
}
|
|
if nc.ps.msgBuf == nil {
|
|
t.Fatal("Msg buffer should have been created")
|
|
}
|
|
|
|
buf = []byte("oo\r\n")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if (nc.Statistics.InMsgs != expectedCount) || (nc.Statistics.InBytes != expectedSize) {
|
|
t.Fatalf("Wrong stats: %d - %d instead of %d - %d", nc.Statistics.InMsgs, nc.Statistics.InBytes, expectedCount, expectedSize)
|
|
}
|
|
if (nc.ps.argBuf != nil) || (nc.ps.msgBuf != nil) {
|
|
t.Fatal("Buffers should be nil now")
|
|
}
|
|
|
|
buf = []byte("MSG a 1 3\r\nfo")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if nc.ps.ma.size != 3 {
|
|
t.Fatalf("Wrong msg size: %d instead of 3", nc.ps.ma.size)
|
|
}
|
|
if nc.ps.ma.sid != 1 {
|
|
t.Fatalf("Wrong sid: %d instead of 1", nc.ps.ma.sid)
|
|
}
|
|
if string(nc.ps.ma.subject) != "a" {
|
|
t.Fatalf("Wrong subject: '%s' instead of 'a'", string(nc.ps.ma.subject))
|
|
}
|
|
if nc.ps.argBuf == nil {
|
|
t.Fatal("Arg buffer should have been created")
|
|
}
|
|
if nc.ps.msgBuf == nil {
|
|
t.Fatal("Msg buffer should have been created")
|
|
}
|
|
|
|
expectedCount++
|
|
expectedSize += 3
|
|
|
|
buf = []byte("o\r\n")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if (nc.Statistics.InMsgs != expectedCount) || (nc.Statistics.InBytes != expectedSize) {
|
|
t.Fatalf("Wrong stats: %d - %d instead of %d - %d", nc.Statistics.InMsgs, nc.Statistics.InBytes, expectedCount, expectedSize)
|
|
}
|
|
if (nc.ps.argBuf != nil) || (nc.ps.msgBuf != nil) {
|
|
t.Fatal("Buffers should be nil now")
|
|
}
|
|
|
|
buf = []byte("MSG a 1 6\r\nfo")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if nc.ps.ma.size != 6 {
|
|
t.Fatalf("Wrong msg size: %d instead of 3", nc.ps.ma.size)
|
|
}
|
|
if nc.ps.ma.sid != 1 {
|
|
t.Fatalf("Wrong sid: %d instead of 1", nc.ps.ma.sid)
|
|
}
|
|
if string(nc.ps.ma.subject) != "a" {
|
|
t.Fatalf("Wrong subject: '%s' instead of 'a'", string(nc.ps.ma.subject))
|
|
}
|
|
if nc.ps.argBuf == nil {
|
|
t.Fatal("Arg buffer should have been created")
|
|
}
|
|
if nc.ps.msgBuf == nil {
|
|
t.Fatal("Msg buffer should have been created")
|
|
}
|
|
|
|
buf = []byte("ob")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
|
|
expectedCount++
|
|
expectedSize += 6
|
|
|
|
buf = []byte("ar\r\n")
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if (nc.Statistics.InMsgs != expectedCount) || (nc.Statistics.InBytes != expectedSize) {
|
|
t.Fatalf("Wrong stats: %d - %d instead of %d - %d", nc.Statistics.InMsgs, nc.Statistics.InBytes, expectedCount, expectedSize)
|
|
}
|
|
if (nc.ps.argBuf != nil) || (nc.ps.msgBuf != nil) {
|
|
t.Fatal("Buffers should be nil now")
|
|
}
|
|
|
|
// Let's have a msg that is bigger than the parser's scratch size.
|
|
// Since we prepopulate the msg with 'foo', adding 3 to the size.
|
|
msgSize := cap(nc.ps.scratch) + 100 + 3
|
|
buf = []byte(fmt.Sprintf("MSG a 1 b %d\r\nfoo", msgSize))
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if nc.ps.ma.size != msgSize {
|
|
t.Fatalf("Wrong msg size: %d instead of %d", nc.ps.ma.size, msgSize)
|
|
}
|
|
if nc.ps.ma.sid != 1 {
|
|
t.Fatalf("Wrong sid: %d instead of 1", nc.ps.ma.sid)
|
|
}
|
|
if string(nc.ps.ma.subject) != "a" {
|
|
t.Fatalf("Wrong subject: '%s' instead of 'a'", string(nc.ps.ma.subject))
|
|
}
|
|
if string(nc.ps.ma.reply) != "b" {
|
|
t.Fatalf("Wrong reply: '%s' instead of 'b'", string(nc.ps.ma.reply))
|
|
}
|
|
if nc.ps.argBuf == nil {
|
|
t.Fatal("Arg buffer should have been created")
|
|
}
|
|
if nc.ps.msgBuf == nil {
|
|
t.Fatal("Msg buffer should have been created")
|
|
}
|
|
|
|
expectedCount++
|
|
expectedSize += uint64(msgSize)
|
|
|
|
bufSize := msgSize - 3
|
|
|
|
buf = make([]byte, bufSize)
|
|
for i := 0; i < bufSize; i++ {
|
|
buf[i] = byte('a' + (i % 26))
|
|
}
|
|
|
|
err = nc.parse(buf)
|
|
if err != nil {
|
|
t.Fatalf("Parser error: %v", err)
|
|
}
|
|
if nc.ps.state != MSG_PAYLOAD {
|
|
t.Fatalf("Wrong state: %v instead of %v", nc.ps.state, MSG_PAYLOAD)
|
|
}
|
|
if nc.ps.ma.size != msgSize {
|
|
t.Fatalf("Wrong (ma) msg size: %d instead of %d", nc.ps.ma.size, msgSize)
|
|
}
|
|
if len(nc.ps.msgBuf) != msgSize {
|
|
t.Fatalf("Wrong msg size: %d instead of %d", len(nc.ps.msgBuf), msgSize)
|
|
}
|
|
// Check content:
|
|
if string(nc.ps.msgBuf[0:3]) != "foo" {
|
|
t.Fatalf("Wrong msg content: %s", string(nc.ps.msgBuf))
|
|
}
|
|
for k := 3; k < nc.ps.ma.size; k++ {
|
|
if nc.ps.msgBuf[k] != byte('a'+((k-3)%26)) {
|
|
t.Fatalf("Wrong msg content: %s", string(nc.ps.msgBuf))
|
|
}
|
|
}
|
|
|
|
buf = []byte("\r\n")
|
|
if err := nc.parse(buf); err != nil {
|
|
t.Fatalf("Unexpected error during parsing: %v", err)
|
|
}
|
|
if (nc.Statistics.InMsgs != expectedCount) || (nc.Statistics.InBytes != expectedSize) {
|
|
t.Fatalf("Wrong stats: %d - %d instead of %d - %d", nc.Statistics.InMsgs, nc.Statistics.InBytes, expectedCount, expectedSize)
|
|
}
|
|
if (nc.ps.argBuf != nil) || (nc.ps.msgBuf != nil) {
|
|
t.Fatal("Buffers should be nil now")
|
|
}
|
|
if nc.ps.state != OP_START {
|
|
t.Fatalf("Wrong state: %v", nc.ps.state)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeError(t *testing.T) {
|
|
received := "Typical Error"
|
|
expected := strings.ToLower(received)
|
|
if s := normalizeErr("-ERR '" + received + "'"); s != expected {
|
|
t.Fatalf("Expected '%s', got '%s'", expected, s)
|
|
}
|
|
|
|
received = "Trim Surrounding Spaces"
|
|
expected = strings.ToLower(received)
|
|
if s := normalizeErr("-ERR '" + received + "' "); s != expected {
|
|
t.Fatalf("Expected '%s', got '%s'", expected, s)
|
|
}
|
|
|
|
received = "Trim Surrounding Spaces Without Quotes"
|
|
expected = strings.ToLower(received)
|
|
if s := normalizeErr("-ERR " + received + " "); s != expected {
|
|
t.Fatalf("Expected '%s', got '%s'", expected, s)
|
|
}
|
|
|
|
received = "Error Without Quotes"
|
|
expected = strings.ToLower(received)
|
|
if s := normalizeErr("-ERR " + received); s != expected {
|
|
t.Fatalf("Expected '%s', got '%s'", expected, s)
|
|
}
|
|
|
|
received = "Error With Quote Only On Left"
|
|
expected = strings.ToLower(received)
|
|
if s := normalizeErr("-ERR '" + received); s != expected {
|
|
t.Fatalf("Expected '%s', got '%s'", expected, s)
|
|
}
|
|
|
|
received = "Error With Quote Only On Right"
|
|
expected = strings.ToLower(received)
|
|
if s := normalizeErr("-ERR " + received + "'"); s != expected {
|
|
t.Fatalf("Expected '%s', got '%s'", expected, s)
|
|
}
|
|
}
|
|
|
|
func TestAsyncINFO(t *testing.T) {
|
|
opts := GetDefaultOptions()
|
|
c := &Conn{Opts: opts}
|
|
|
|
c.ps = &parseState{}
|
|
|
|
if c.ps.state != OP_START {
|
|
t.Fatalf("Expected OP_START vs %d\n", c.ps.state)
|
|
}
|
|
|
|
info := []byte("INFO {}\r\n")
|
|
if c.ps.state != OP_START {
|
|
t.Fatalf("Expected OP_START vs %d\n", c.ps.state)
|
|
}
|
|
err := c.parse(info[:1])
|
|
if err != nil || c.ps.state != OP_I {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(info[1:2])
|
|
if err != nil || c.ps.state != OP_IN {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(info[2:3])
|
|
if err != nil || c.ps.state != OP_INF {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(info[3:4])
|
|
if err != nil || c.ps.state != OP_INFO {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(info[4:5])
|
|
if err != nil || c.ps.state != OP_INFO_SPC {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
err = c.parse(info[5:])
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
|
|
// All at once
|
|
err = c.parse(info)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
|
|
// Server pool needs to be setup
|
|
c.setupServerPool()
|
|
|
|
// Partials requiring argBuf
|
|
expectedServer := serverInfo{
|
|
Id: "test",
|
|
Host: "localhost",
|
|
Port: 4222,
|
|
Version: "1.2.3",
|
|
AuthRequired: true,
|
|
TLSRequired: true,
|
|
MaxPayload: 2 * 1024 * 1024,
|
|
ConnectURLs: []string{"localhost:5222", "localhost:6222"},
|
|
}
|
|
// Set NoRandomize so that the check with expectedServer info
|
|
// matches.
|
|
c.Opts.NoRandomize = true
|
|
|
|
b, _ := json.Marshal(expectedServer)
|
|
info = []byte(fmt.Sprintf("INFO %s\r\n", b))
|
|
if c.ps.state != OP_START {
|
|
t.Fatalf("Expected OP_START vs %d\n", c.ps.state)
|
|
}
|
|
err = c.parse(info[:9])
|
|
if err != nil || c.ps.state != INFO_ARG || c.ps.argBuf == nil {
|
|
t.Fatalf("Unexpected: %d err: %v argBuf: %v\n", c.ps.state, err, c.ps.argBuf)
|
|
}
|
|
err = c.parse(info[9:11])
|
|
if err != nil || c.ps.state != INFO_ARG || c.ps.argBuf == nil {
|
|
t.Fatalf("Unexpected: %d err: %v argBuf: %v\n", c.ps.state, err, c.ps.argBuf)
|
|
}
|
|
err = c.parse(info[11:])
|
|
if err != nil || c.ps.state != OP_START || c.ps.argBuf != nil {
|
|
t.Fatalf("Unexpected: %d err: %v argBuf: %v\n", c.ps.state, err, c.ps.argBuf)
|
|
}
|
|
if !reflect.DeepEqual(c.info, expectedServer) {
|
|
t.Fatalf("Expected server info to be: %v, got: %v", expectedServer, c.info)
|
|
}
|
|
|
|
// Good INFOs
|
|
good := []string{"INFO {}\r\n", "INFO {}\r\n", "INFO {} \r\n", "INFO { \"server_id\": \"test\" } \r\n", "INFO {\"connect_urls\":[]}\r\n"}
|
|
for _, gi := range good {
|
|
c.ps = &parseState{}
|
|
err = c.parse([]byte(gi))
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Protocol %q should be fine. Err=%v state=%v", gi, err, c.ps.state)
|
|
}
|
|
}
|
|
|
|
// Wrong INFOs
|
|
wrong := []string{"IxNFO {}\r\n", "INxFO {}\r\n", "INFxO {}\r\n", "INFOx {}\r\n", "INFO{}\r\n", "INFO {}"}
|
|
for _, wi := range wrong {
|
|
c.ps = &parseState{}
|
|
err = c.parse([]byte(wi))
|
|
if err == nil && c.ps.state == OP_START {
|
|
t.Fatalf("Protocol %q should have failed", wi)
|
|
}
|
|
}
|
|
|
|
checkPool := func(urls ...string) {
|
|
// Check both pool and urls map
|
|
if len(c.srvPool) != len(urls) {
|
|
stackFatalf(t, "Pool should have %d elements, has %d", len(urls), len(c.srvPool))
|
|
}
|
|
if len(c.urls) != len(urls) {
|
|
stackFatalf(t, "Map should have %d elements, has %d", len(urls), len(c.urls))
|
|
}
|
|
for _, url := range urls {
|
|
if _, present := c.urls[url]; !present {
|
|
stackFatalf(t, "Pool should have %q", url)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now test the decoding of "connect_urls"
|
|
|
|
// Reset the pool
|
|
c.setupServerPool()
|
|
// Reinitialize the parser
|
|
c.ps = &parseState{}
|
|
|
|
info = []byte("INFO {\"connect_urls\":[\"localhost:4222\", \"localhost:5222\"]}\r\n")
|
|
err = c.parse(info)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
// Pool now should contain localhost:4222 (the default URL) and localhost:5222
|
|
checkPool("localhost:4222", "localhost:5222")
|
|
|
|
// Make sure that if client receives the same, it is not added again.
|
|
err = c.parse(info)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
// Pool should still contain localhost:4222 (the default URL) and localhost:5222
|
|
checkPool("localhost:4222", "localhost:5222")
|
|
|
|
// Receive a new URL
|
|
info = []byte("INFO {\"connect_urls\":[\"localhost:4222\", \"localhost:5222\", \"localhost:6222\"]}\r\n")
|
|
err = c.parse(info)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
// Pool now should contain localhost:4222 (the default URL) localhost:5222 and localhost:6222
|
|
checkPool("localhost:4222", "localhost:5222", "localhost:6222")
|
|
|
|
// Check that pool may be randomized on setup, but new URLs are always
|
|
// added at end of pool.
|
|
c.Opts.NoRandomize = false
|
|
c.Opts.Servers = testServers
|
|
// Reset the pool
|
|
c.setupServerPool()
|
|
// Reinitialize the parser
|
|
c.ps = &parseState{}
|
|
// Capture the pool sequence after randomization
|
|
urlsAfterPoolSetup := make([]string, 0, len(c.srvPool))
|
|
for _, srv := range c.srvPool {
|
|
urlsAfterPoolSetup = append(urlsAfterPoolSetup, srv.url.Host)
|
|
}
|
|
checkPoolOrderDidNotChange := func() {
|
|
for i := 0; i < len(urlsAfterPoolSetup); i++ {
|
|
if c.srvPool[i].url.Host != urlsAfterPoolSetup[i] {
|
|
stackFatalf(t, "Pool should have %q at index %q, has %q", urlsAfterPoolSetup[i], i, c.srvPool[i].url.Host)
|
|
}
|
|
}
|
|
}
|
|
// Add new urls
|
|
newURLs := []string{
|
|
"localhost:6222",
|
|
"localhost:7222",
|
|
"localhost:8222\", \"localhost:9222",
|
|
"localhost:10222\", \"localhost:11222\", \"localhost:12222,",
|
|
}
|
|
for _, newURL := range newURLs {
|
|
info = []byte("INFO {\"connect_urls\":[\"" + newURL + "]}\r\n")
|
|
err = c.parse(info)
|
|
if err != nil || c.ps.state != OP_START {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
// Check that pool order does not change up to the new addition(s).
|
|
checkPoolOrderDidNotChange()
|
|
}
|
|
}
|
|
|
|
func TestConnServers(t *testing.T) {
|
|
opts := GetDefaultOptions()
|
|
c := &Conn{Opts: opts}
|
|
c.ps = &parseState{}
|
|
c.setupServerPool()
|
|
|
|
validateURLs := func(serverUrls []string, expectedUrls ...string) {
|
|
var found bool
|
|
if len(serverUrls) != len(expectedUrls) {
|
|
stackFatalf(t, "Array should have %d elements, has %d", len(expectedUrls), len(serverUrls))
|
|
}
|
|
|
|
for _, ev := range expectedUrls {
|
|
found = false
|
|
for _, av := range serverUrls {
|
|
if ev == av {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
stackFatalf(t, "array is missing %q in %v", ev, serverUrls)
|
|
}
|
|
}
|
|
}
|
|
|
|
// check the default url
|
|
validateURLs(c.Servers(), "nats://localhost:4222")
|
|
if len(c.DiscoveredServers()) != 0 {
|
|
t.Fatalf("Expected no discovered servers")
|
|
}
|
|
|
|
// Add a new URL
|
|
err := c.parse([]byte("INFO {\"connect_urls\":[\"localhost:5222\"]}\r\n"))
|
|
if err != nil {
|
|
t.Fatalf("Unexpected: %d : %v\n", c.ps.state, err)
|
|
}
|
|
// Server list should now contain both the default and the new url.
|
|
validateURLs(c.Servers(), "nats://localhost:4222", "nats://localhost:5222")
|
|
// Discovered servers should only contain the new url.
|
|
validateURLs(c.DiscoveredServers(), "nats://localhost:5222")
|
|
|
|
// verify user credentials are stripped out.
|
|
opts.Servers = []string{"nats://user:pass@localhost:4333", "nats://token@localhost:4444"}
|
|
c = &Conn{Opts: opts}
|
|
c.ps = &parseState{}
|
|
c.setupServerPool()
|
|
|
|
validateURLs(c.Servers(), "nats://localhost:4333", "nats://localhost:4444")
|
|
}
|
|
|
|
func TestConnAsyncCBDeadlock(t *testing.T) {
|
|
s := RunServerOnPort(TEST_PORT)
|
|
defer s.Shutdown()
|
|
|
|
ch := make(chan bool)
|
|
o := GetDefaultOptions()
|
|
o.Url = fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT)
|
|
o.ClosedCB = func(_ *Conn) {
|
|
ch <- true
|
|
}
|
|
o.AsyncErrorCB = func(nc *Conn, sub *Subscription, err error) {
|
|
// do something with nc that requires locking behind the scenes
|
|
_ = nc.LastError()
|
|
}
|
|
nc, err := o.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
|
|
total := 300
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(total)
|
|
for i := 0; i < total; i++ {
|
|
go func() {
|
|
// overwhelm asyncCB with errors
|
|
nc.processErr(AUTHORIZATION_ERR)
|
|
wg.Done()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
nc.Close()
|
|
if e := Wait(ch); e != nil {
|
|
t.Fatal("Deadlock")
|
|
}
|
|
}
|
|
|
|
func TestPingTimerLeakedOnClose(t *testing.T) {
|
|
s := RunServerOnPort(TEST_PORT)
|
|
defer s.Shutdown()
|
|
|
|
nc, err := Connect(fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT))
|
|
if err != nil {
|
|
t.Fatalf("Error on connect: %v", err)
|
|
}
|
|
nc.Close()
|
|
// There was a bug (issue #338) that if connection
|
|
// was created and closed quickly, the pinger would
|
|
// be created from a go-routine and would cause the
|
|
// connection object to be retained until the ping
|
|
// timer fired.
|
|
// Wait a little bit and check if the timer is set.
|
|
// With the defect it would be.
|
|
time.Sleep(100 * time.Millisecond)
|
|
nc.mu.Lock()
|
|
pingTimerSet := nc.ptmr != nil
|
|
nc.mu.Unlock()
|
|
if pingTimerSet {
|
|
t.Fatal("Pinger timer should not be set")
|
|
}
|
|
}
|
|
|
|
func TestNoEcho(t *testing.T) {
|
|
s := RunServerOnPort(TEST_PORT)
|
|
defer s.Shutdown()
|
|
|
|
url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT)
|
|
|
|
nc, err := Connect(url, NoEcho())
|
|
if err != nil {
|
|
t.Fatalf("Error on connect: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
r := int32(0)
|
|
_, err = nc.Subscribe("foo", func(m *Msg) {
|
|
atomic.AddInt32(&r, 1)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error on subscribe: %v", err)
|
|
}
|
|
|
|
err = nc.Publish("foo", []byte("Hello World"))
|
|
if err != nil {
|
|
t.Fatalf("Error on publish: %v", err)
|
|
}
|
|
nc.Flush()
|
|
nc.Flush()
|
|
|
|
if nr := atomic.LoadInt32(&r); nr != 0 {
|
|
t.Fatalf("Expected no messages echoed back, received %d\n", nr)
|
|
}
|
|
}
|
|
|
|
func TestNoEchoOldServer(t *testing.T) {
|
|
opts := GetDefaultOptions()
|
|
opts.Url = DefaultURL
|
|
opts.NoEcho = true
|
|
|
|
nc := &Conn{Opts: opts}
|
|
if err := nc.setupServerPool(); err != nil {
|
|
t.Fatalf("Problem setting up Server Pool: %v\n", err)
|
|
}
|
|
|
|
// Old style with no proto, meaning 0. We need Proto:1 for NoEcho support.
|
|
oldInfo := "{\"server_id\":\"22\",\"version\":\"1.1.0\",\"go\":\"go1.10.2\",\"port\":4222,\"max_payload\":1048576}"
|
|
|
|
err := nc.processInfo(oldInfo)
|
|
if err != nil {
|
|
t.Fatalf("Error processing old style INFO: %v\n", err)
|
|
}
|
|
|
|
// Make sure connectProto generates an error.
|
|
_, err = nc.connectProto()
|
|
if err == nil {
|
|
t.Fatalf("Expected an error but got none\n")
|
|
}
|
|
}
|