torque 79a45fd2e3
git subrepo clone (merge) --branch=v3.6.1 https://github.com/nats-io/nats.c.git deps/nats.c
subrepo:
  subdir:   "deps/nats.c"
  merged:   "66cec7f"
upstream:
  origin:   "https://github.com/nats-io/nats.c.git"
  branch:   "v3.6.1"
  commit:   "66cec7f"
git-subrepo:
  version:  "0.4.6"
  commit:   "b8b46501e"
2023-08-15 00:21:33 -07:00

34729 lines
1.1 MiB

// Copyright 2015-2022 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.
#include "natsp.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#ifdef _WIN32
#else
#include <dirent.h>
#include <sys/stat.h>
#endif
#include "buf.h"
#include "timer.h"
#include "url.h"
#include "opts.h"
#include "../src/util.h"
#include "hash.h"
#include "conn.h"
#include "sub.h"
#include "msg.h"
#include "stats.h"
#include "comsock.h"
#include "crypto.h"
#include "nkeys.h"
#include "parser.h"
#include "js.h"
#include "kv.h"
#if defined(NATS_HAS_STREAMING)
#include "stan/conn.h"
#include "stan/pub.h"
#include "stan/sub.h"
#include "stan/copts.h"
#include "stan/sopts.h"
#endif
static int tests = 0;
static bool failed = false;
static bool keepServerOutput = false;
static bool valgrind = false;
static bool runOnTravis = false;
static const char *natsServerExe = "nats-server";
static const char *serverVersion = NULL;
static const char *natsStreamingServerExe = "nats-streaming-server";
static natsMutex *slMu = NULL;
static natsHash *slMap = NULL;
#define test(s) { printf("#%02d ", ++tests); printf("%s", (s)); fflush(stdout); }
#ifdef _WIN32
#define NATS_INVALID_PID (NULL)
#define testCond(c) if(c) { printf("PASSED\n"); fflush(stdout); } else { printf("FAILED\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; return; }
#define testCondNoReturn(c) if(c) { printf("PASSED\n"); fflush(stdout); } else { printf("FAILED\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; }
#define LOGFILE_NAME "wserver.log"
#else
#define NATS_INVALID_PID (-1)
#define testCond(c) if(c) { printf("\033[0;32mPASSED\033[0;0m\n"); fflush(stdout); } else { printf("\033[0;31mFAILED\033[0;0m\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; return; }
#define testCondNoReturn(c) if(c) { printf("\033[0;32mPASSED\033[0;0m\n"); fflush(stdout); } else { printf("\033[0;31mFAILED\033[0;0m\n"); nats_PrintLastErrorStack(stdout); fflush(stdout); failed=true; }
#define LOGFILE_NAME "server.log"
#endif
#define FAIL(m) { printf("@@ %s @@\n", (m)); failed=true; return; }
#define CHECK_SERVER_STARTED(p) if ((p) == NATS_INVALID_PID) FAIL("Unable to start or verify that the server was started!")
static const char *testServers[] = {"nats://127.0.0.1:1222",
"nats://127.0.0.1:1223",
"nats://127.0.0.1:1224",
"nats://127.0.0.1:1225",
"nats://127.0.0.1:1226",
"nats://127.0.0.1:1227",
"nats://127.0.0.1:1228"};
#if defined(NATS_HAS_STREAMING)
static const char *clusterName = "test-cluster";
static const char *clientName = "client";
#endif
// Forward declaration
static void _startMockupServerThread(void *closure);
typedef natsStatus (*testCheckInfoCB)(char *buffer);
struct threadArg
{
natsMutex *m;
natsThread *t;
natsCondition *c;
natsCondition *b;
int control;
bool current;
int sum;
int timerFired;
int timerStopped;
natsStrHash *inboxes;
natsStatus status;
const char* string;
bool connected;
bool disconnected;
int64_t disconnectedAt[4];
int64_t disconnects;
bool closed;
bool reconnected;
int64_t reconnectedAt[4];
int reconnects;
bool msgReceived;
bool done;
int results[10];
const char *tokens[3];
int tokenCallCount;
testCheckInfoCB checkInfoCB;
natsSock sock;
natsSubscription *sub;
natsOptions *opts;
natsConnection *nc;
jsCtx *js;
natsBuffer *buf;
#if defined(NATS_HAS_STREAMING)
stanConnection *sc;
int redelivered;
const char* channel;
stanMsg *sMsg;
#endif
int attached;
int detached;
bool evStop;
bool doRead;
bool doWrite;
};
static bool
serverVersionAtLeast(int major, int minor, int update)
{
int ma = 0;
int mi = 0;
int up = 0;
char *version = NULL;
if (serverVersion == NULL)
return false;
version = strstr(serverVersion, "version ");
if (version != NULL)
{
version += 8;
}
else
{
version = strstr(serverVersion, " v");
if (version == NULL)
return false;
version += 2;
}
sscanf(version, "%d.%d.%d", &ma, &mi, &up);
if ((ma > major) || ((ma == major) && (mi > minor)) || ((ma == major) && (mi == minor) && (up >= update)))
return true;
return false;
}
static void
_waitSubPending(natsSubscription *sub, int expected)
{
int mc = 0;
do
{
natsSubscription_GetPending(sub, &mc, NULL);
if (mc != expected)
nats_Sleep(15);
}
while (mc != expected);
}
static natsStatus
_createDefaultThreadArgsForCbTests(
struct threadArg *arg)
{
natsStatus s;
memset(arg, 0, sizeof(struct threadArg));
s = natsMutex_Create(&(arg->m));
if (s == NATS_OK)
s = natsCondition_Create(&(arg->c));
return s;
}
void
_destroyDefaultThreadArgs(struct threadArg *args)
{
if (valgrind)
nats_Sleep(100);
natsMutex_Destroy(args->m);
natsCondition_Destroy(args->c);
}
static void
test_natsNowAndSleep(void)
{
int64_t start;
int64_t end;
test("Check now and sleep: ")
start = nats_Now();
nats_Sleep(1000);
end = nats_Now();
testCond(((end - start) >= 990) && ((end - start) <= 1010));
}
static void
test_natsAllocSprintf(void)
{
char smallStr[20];
char mediumStr[256]; // This is the size of the temp buffer in nats_asprintf
char largeStr[1024];
char *ptr = NULL;
int ret;
memset(smallStr, 'A', sizeof(smallStr) - 1);
smallStr[sizeof(smallStr) - 1] = '\0';
memset(mediumStr, 'B', sizeof(mediumStr) - 1);
mediumStr[sizeof(mediumStr) - 1] = '\0';
memset(largeStr, 'C', sizeof(largeStr) - 1);
largeStr[sizeof(largeStr) - 1] = '\0';
test("Check alloc sprintf with small string: ");
ret = nats_asprintf(&ptr, "%s", smallStr);
testCond((ret >= 0)
&& (strcmp(ptr, smallStr) == 0));
free(ptr);
ptr = NULL;
test("Check alloc sprintf with medium string: ");
ret = nats_asprintf(&ptr, "%s", mediumStr);
testCond((ret >= 0)
&& (strcmp(ptr, mediumStr) == 0));
free(ptr);
ptr = NULL;
test("Check alloc sprintf with large string: ");
ret = nats_asprintf(&ptr, "%s", largeStr);
testCond((ret >= 0)
&& (strcmp(ptr, largeStr) == 0));
free(ptr);
ptr = NULL;
}
static void
test_natsStrCaseStr(void)
{
const char *s1 = "Hello World!";
const char *s2 = "wo";
const char *res = NULL;
test("StrStr case insensitive (equal): ");
res = nats_strcasestr(s1, s1);
testCond((res != NULL)
&& (strcmp(res, s1) == 0)
&& (res == s1));
test("StrStr case insensitive (match): ");
res = nats_strcasestr(s1, s2);
testCond((res != NULL)
&& (strcmp(res, "World!") == 0)
&& (res == (s1 + 6)));
test("StrStr case insensitive (no match): ");
res = nats_strcasestr(s1, "xx");
testCond(res == NULL);
}
static void
test_natsSnprintf(void)
{
#if _WIN32
// This test is specific to older version of Windows
// that did not provide snprintf...
char buf[5];
test("snprintf over limit: ");
snprintf(buf, sizeof(buf), "%s", "abcdefghijklmnopqrstuvwxyz");
testCond(strcmp(buf, "abcd") == 0);
#else
test("Skip when not running on Windows: ");
testCond(true);
#endif
}
static void test_natsBuffer(void)
{
natsStatus s;
char backend[10];
natsBuffer *buf = NULL;
natsBuffer stackBuf;
int oldCapacity = 0;
printf("== Buffer without data ==\n");
test("Create buffer owning its data: ");
s = natsBuf_Create(&buf, 1);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 0)
&& (natsBuf_Capacity(buf) == 1));
test("Append less than capacity does not expand buffer: ");
s = natsBuf_Append(buf, "a", 1);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 1)
&& (natsBuf_Capacity(buf) == 1)
&& (natsBuf_Available(buf) == 0));
test("Appending one more (AppendByte) increases capacity: ");
oldCapacity = natsBuf_Capacity(buf);
s = natsBuf_AppendByte(buf, 'b');
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 2)
&& (natsBuf_Capacity(buf) > oldCapacity)
&& (natsBuf_Available(buf) > 0));
test("Checking content: ");
testCond((s == NATS_OK)
&& (natsBuf_Data(buf) != NULL)
&& (strncmp(natsBuf_Data(buf), "ab", 2) == 0));
natsBuf_Destroy(buf);
buf = NULL;
oldCapacity = 0;
test("Appending one more byte increases capacity: ");
s = natsBuf_Create(&buf, 1);
IFOK(s, natsBuf_Append(buf, "a", 1));
if (s == NATS_OK)
{
oldCapacity = natsBuf_Capacity(buf);
// Add one more!
s = natsBuf_Append(buf, "b", 1);
}
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 2)
&& (natsBuf_Capacity(buf) > oldCapacity)
&& (natsBuf_Available(buf) > 0));
natsBuf_Destroy(buf);
buf = NULL;
printf("\n== Buffer with data ==\n");
memset(backend, 0, sizeof(backend));
test("Create buffer with backend: ");
s = natsBuf_CreateWithBackend(&buf, backend, 0, 5);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 0)
&& (natsBuf_Capacity(buf) == 5));
test("Check that changes are reflected in backend")
s = natsBuf_Append(buf, "abcd", 4);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 4)
&& (natsBuf_Capacity(buf) == 5)
&& (natsBuf_Available(buf) > 0)
&& (strcmp(backend, "abcd") == 0));
test("Changing backend is reflected in buffer: ");
backend[1] = 'x';
testCond((s == NATS_OK)
&& (natsBuf_Data(buf)[1] == 'x'));
test("Append less than capacity does not expand buffer: ");
s = natsBuf_AppendByte(buf, 'e');
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 5)
&& (natsBuf_Capacity(buf) == 5)
&& (natsBuf_Available(buf) == 0));
test("Check natsBuf_Expand returns error for invalid arguments: ");
{
natsStatus ls;
ls = natsBuf_Expand(buf, -10);
if (ls != NATS_OK)
ls = natsBuf_Expand(buf, 0);
if (ls != NATS_OK)
ls = natsBuf_Expand(buf, natsBuf_Capacity(buf));
testCond(ls != NATS_OK);
}
test("Adding more causes expand: ");
oldCapacity = natsBuf_Capacity(buf);
s = natsBuf_Append(buf, "fghij", 5);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 10)
&& (natsBuf_Capacity(buf) > oldCapacity));
test("Check that the backend did not change");
testCond((s == NATS_OK)
&& (strcmp(backend, "axcde") == 0));
test("Checking content: ");
testCond((s == NATS_OK)
&& (natsBuf_Data(buf) != NULL)
&& (strncmp(natsBuf_Data(buf), "axcdefghij", 10) == 0));
test("Destroying buffer does not affect backend: ");
natsBuf_Destroy(buf);
buf = NULL;
testCond(strcmp(backend, "axcde") == 0);
printf("\n== Buffer Init without data ==\n");
test("Create buffer owning its data: ");
s = natsBuf_Init(&stackBuf, 10);
testCond((s == NATS_OK)
&& (buf = &stackBuf)
&& (natsBuf_Len(buf) == 0)
&& (natsBuf_Capacity(buf) == 10));
test("Append less than capacity does not expand buffer: ");
s = natsBuf_Append(buf, "abcdefghij", 10);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 10)
&& (natsBuf_Capacity(buf) == 10)
&& (natsBuf_Available(buf) == 0));
test("Appending one more increases capacity: ");
oldCapacity = natsBuf_Capacity(buf);
s = natsBuf_AppendByte(buf, 'k');
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 11)
&& (natsBuf_Capacity(buf) > oldCapacity)
&& (natsBuf_Available(buf) > 0));
test("Checking content: ");
testCond((s == NATS_OK)
&& (natsBuf_Data(buf) != NULL)
&& (strncmp(natsBuf_Data(buf), "abcdefghijk", 11) == 0));
test("Destroying buffer: ");
natsBuf_Destroy(buf);
testCond((natsBuf_Data(buf) == NULL)
&& (natsBuf_Len(buf) == 0)
&& (natsBuf_Capacity(buf) == 0)
&& (natsBuf_Available(buf) == 0));
buf = NULL;
printf("\n== Buffer Init with data ==\n");
memset(backend, 0, sizeof(backend));
test("Create buffer with backend: ");
s = natsBuf_InitWithBackend(&stackBuf, backend, 0, 5);
testCond((s == NATS_OK)
&& (buf = &stackBuf)
&& (natsBuf_Len(buf) == 0)
&& (natsBuf_Capacity(buf) == 5));
test("Check that changes are reflected in backend: ")
s = natsBuf_Append(buf, "abcd", 4);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 4)
&& (natsBuf_Capacity(buf) == 5)
&& (natsBuf_Available(buf) > 0)
&& (strcmp(backend, "abcd") == 0));
test("Changing backend is reflected in buffer: ");
testCond((s == NATS_OK)
&& (backend[1] = 'x')
&& (natsBuf_Data(buf)[1] == 'x'));
test("Append less than capacity does not expand buffer: ");
s = natsBuf_AppendByte(buf, 'e');
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 5)
&& (natsBuf_Capacity(buf) == 5)
&& (natsBuf_Available(buf) == 0));
test("Adding more causes expand: ");
s = natsBuf_Append(buf, "fghij", 5);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 10)
&& (natsBuf_Capacity(buf) >= 10));
test("Check that the backend did not change");
testCond((s == NATS_OK)
&& (strcmp(backend, "axcde") == 0));
test("Checking content: ");
testCond((s == NATS_OK)
&& (natsBuf_Data(buf) != NULL)
&& (strncmp(natsBuf_Data(buf), "axcdefghij", 10) == 0));
test("Destroying buffer does not affect backend: ");
natsBuf_Destroy(buf);
testCond(strcmp(backend, "axcde") == 0);
test("Destroyed buffer state is clean: ");
testCond((s == NATS_OK)
&& (natsBuf_Data(buf) == NULL)
&& (natsBuf_Len(buf) == 0)
&& (natsBuf_Capacity(buf) == 0)
&& (natsBuf_Available(buf) == 0));
buf = NULL;
test("Check maximum size: ");
s = natsBuf_Create(&buf, 5);
IFOK(s, natsBuf_Append(buf, "abcd", 4));
IFOK(s, natsBuf_Append(buf, "fake size that goes over int max size", 0x7FFFFFFC));
testCond(s == NATS_NO_MEMORY);
test("Check maximum size (append byte): ");
buf->len = 0x7FFFFFFE;
s = natsBuf_Append(buf, "e", 1);
testCond(s == NATS_NO_MEMORY);
natsBuf_Destroy(buf);
buf = NULL;
test("Consume half: ");
s = natsBuf_Create(&buf, 10);
IFOK(s, natsBuf_Append(buf, "abcdefghij", 10));
if (s == NATS_OK)
natsBuf_Consume(buf, 5);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 5)
&& (strncmp(natsBuf_Data(buf), "fghij", 5) == 0)
&& (natsBuf_Available(buf) == 5)
&& (*(buf->pos) == 'f'));
test("Consume rest: ");
natsBuf_Consume(buf, 5);
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 0)
&& (natsBuf_Available(buf) == 10)
&& (*(buf->pos) == 'f'));
natsBuf_Destroy(buf);
buf = NULL;
test("MoveTo (forward): ");
s = natsBuf_Create(&buf, 100);
if (s == NATS_OK)
{
memcpy(natsBuf_Data(buf), "this is a test", 14);
natsBuf_MoveTo(buf, 14);
memcpy(natsBuf_Data(buf)+14, " of move by", 11);
natsBuf_MoveTo(buf, 14+11);
}
IFOK(s, natsBuf_AppendByte(buf, '\0'));
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 26)
&& (strcmp(natsBuf_Data(buf), "this is a test of move by") == 0));
test("MoveTo (backward): ");
natsBuf_MoveTo(buf, 14);
s = natsBuf_AppendByte(buf, '\0');
testCond((s == NATS_OK)
&& (natsBuf_Len(buf) == 15)
&& (strcmp(natsBuf_Data(buf), "this is a test") == 0));
natsBuf_Destroy(buf);
buf = NULL;
}
static void
test_natsParseInt64(void)
{
int64_t n;
test("Parse with non numeric: ");
n = nats_ParseInt64("a", 1);
testCond(n == -1);
test("Parse with NULL buffer: ");
n = nats_ParseInt64(NULL, 0);
testCond(n == -1);
test("Parse with 0 buffer size: ");
n = nats_ParseInt64("whatever", 0);
testCond(n == -1);
test("Parse with '0': ");
n = nats_ParseInt64("0", 1);
testCond(n == 0);
test("Parse with '1': ");
n = nats_ParseInt64("1", 1);
testCond(n == 1);
test("Parse with '12': ");
n = nats_ParseInt64("12", 2);
testCond(n == 12);
test("Parse with '-12': ");
n = nats_ParseInt64("-12", 3);
testCond(n == -1);
test("Parse with trailing spaces: ");
n = nats_ParseInt64("12 ", 3);
testCond(n == -1);
test("Parse with leading spaces: ");
n = nats_ParseInt64(" 12", 3);
testCond(n == -1);
test("Parse with 'INT64_MAX': ");
n = nats_ParseInt64("9223372036854775807", 19);
testCond(n == INT64_MAX);
test("Parse with overflow(1): ");
n = nats_ParseInt64("9223372036854775809", 19);
testCond(n == -1);
test("Parse with overflow(2): ");
n = nats_ParseInt64("92233720368547758099223372036854775809", 38);
testCond(n == -1);
test("Parse with '12345': ");
n = nats_ParseInt64("12345", 5);
testCond(n == 12345);
test("Parse with '123.45': ");
n = nats_ParseInt64("123.45", 6);
testCond(n == -1);
}
static void
test_natsParseControl(void)
{
natsStatus s;
natsControl c;
c.op = NULL;
c.args = NULL;
test("Test with NULL line: ");
s = nats_ParseControl(&c, NULL);
testCond(s == NATS_PROTOCOL_ERROR);
test("Test line with single op: ");
s = nats_ParseControl(&c, "op");
testCond((s == NATS_OK)
&& (c.op != NULL)
&& (strcmp(c.op, "op") == 0)
&& (c.args == NULL));
free(c.op);
free(c.args);
c.op = NULL;
c.args = NULL;
test("Test line with trailing spaces: ");
s = nats_ParseControl(&c, "op ");
testCond((s == NATS_OK)
&& (c.op != NULL)
&& (strcmp(c.op, "op") == 0)
&& (c.args == NULL));
free(c.op);
free(c.args);
c.op = NULL;
c.args = NULL;
test("Test line with op and args: ");
s = nats_ParseControl(&c, "op args");
testCond((s == NATS_OK)
&& (c.op != NULL)
&& (strcmp(c.op, "op") == 0)
&& (c.args != NULL)
&& (strcmp(c.args, "args") == 0));
free(c.op);
free(c.args);
c.op = NULL;
c.args = NULL;
test("Test line with op and args and trailing spaces: ");
s = nats_ParseControl(&c, "op args ");
testCond((s == NATS_OK)
&& (c.op != NULL)
&& (strcmp(c.op, "op") == 0)
&& (c.args != NULL)
&& (strcmp(c.args, "args") == 0));
free(c.op);
free(c.args);
c.op = NULL;
c.args = NULL;
test("Test line with op and args args: ");
s = nats_ParseControl(&c, "op args args ");
testCond((s == NATS_OK)
&& (c.op != NULL)
&& (strcmp(c.op, "op") == 0)
&& (c.args != NULL)
&& (strcmp(c.args, "args args") == 0));
free(c.op);
free(c.args);
c.op = NULL;
c.args = NULL;
}
static void
test_natsNormalizeErr(void)
{
char error[1024];
char expected[256];
test("Check typical -ERR: ");
snprintf(expected, sizeof(expected), "%s", "Simple Error");
snprintf(error, sizeof(error), "-ERR '%s'", expected);
nats_NormalizeErr(error);
testCond(strcmp(error, expected) == 0);
test("Check -ERR without quotes: ");
snprintf(expected, sizeof(expected), "%s", "Error Without Quotes");
snprintf(error, sizeof(error), "-ERR %s", expected);
nats_NormalizeErr(error);
testCond(strcmp(error, expected) == 0);
test("Check -ERR with spaces: ");
snprintf(expected, sizeof(expected), "%s", "Error With Surrounding Spaces");
snprintf(error, sizeof(error), "-ERR '%s' ", expected);
nats_NormalizeErr(error);
testCond(strcmp(error, expected) == 0);
test("Check -ERR with spaces and without quotes: ");
snprintf(expected, sizeof(expected), "%s", "Error With Surrounding Spaces And Without Quotes");
snprintf(error, sizeof(error), "-ERR %s ", expected);
nats_NormalizeErr(error);
testCond(strcmp(error, expected) == 0);
test("Check -ERR with quote on the left: ");
snprintf(expected, sizeof(expected), "%s", "Error With Quote On Left");
snprintf(error, sizeof(error), "-ERR '%s", expected);
nats_NormalizeErr(error);
testCond(strcmp(error, expected) == 0);
test("Check -ERR with quote on right: ");
snprintf(expected, sizeof(expected), "%s", "Error With Quote On Right");
snprintf(error, sizeof(error), "-ERR %s'", expected);
nats_NormalizeErr(error);
testCond(strcmp(error, expected) == 0);
test("Check -ERR with spaces and single quote: ");
snprintf(error, sizeof(error), "%s", "-ERR ' ");
nats_NormalizeErr(error);
testCond(error[0] == '\0');
}
static void
test_natsMutex(void)
{
natsStatus s;
natsMutex *m = NULL;
bool locked = false;
test("Create mutex: ");
s = natsMutex_Create(&m);
testCond(s == NATS_OK);
test("Lock: ");
natsMutex_Lock(m);
testCond(1);
test("Recursive locking: ");
locked = natsMutex_TryLock(m);
testCond(locked);
test("Release recursive lock: ");
natsMutex_Unlock(m);
testCond(1);
test("Unlock: ");
natsMutex_Unlock(m);
testCond(1);
test("Destroy: ");
natsMutex_Destroy(m);
testCond(1);
}
static void
testThread(void *arg)
{
struct threadArg *tArg = (struct threadArg*) arg;
natsMutex_Lock(tArg->m);
tArg->control = 1;
tArg->current = natsThread_IsCurrent(tArg->t);
natsMutex_Unlock(tArg->m);
}
static void
sumThread(void *arg)
{
struct threadArg *tArg = (struct threadArg*) arg;
natsMutex_Lock(tArg->m);
tArg->sum++;
natsMutex_Unlock(tArg->m);
}
static int NUM_THREADS = 1000;
static void
test_natsThread(void)
{
natsStatus s = NATS_OK;
natsMutex *m = NULL;
natsThread *t = NULL;
bool current = false;
struct threadArg tArgs;
natsThread **threads = NULL;
int i,j;
if (valgrind)
NUM_THREADS = 100;
test("Create threads array: ");
threads = (natsThread**) calloc(NUM_THREADS, sizeof(natsThread*));
if (threads == NULL)
s = NATS_NO_MEMORY;
IFOK(s, natsMutex_Create(&m));
testCond(s == NATS_OK);
natsMutex_Lock(m);
tArgs.m = m;
tArgs.control = 0;
tArgs.current = false;
test("Create thread: ");
s = natsThread_Create(&t, testThread, &tArgs);
testCond(s == NATS_OK);
tArgs.t = t;
test("Check if thread current from other thread: ");
current = natsThread_IsCurrent(t);
testCond(!current);
natsMutex_Unlock(m);
test("Joining thread: ")
natsThread_Join(t);
testCond(1);
natsMutex_Lock(m);
test("Control updated: ");
testCond(tArgs.control == 1);
test("Check thread current works from current thread: ");
testCond(tArgs.current);
test("Destroy thread: ");
natsThread_Destroy(t);
testCond(1);
tArgs.sum = 0;
test("Creating multiple threads: ");
for (i=0; (s == NATS_OK) && (i<NUM_THREADS); i++)
s = natsThread_Create(&(threads[i]), sumThread, &tArgs);
testCond(s == NATS_OK);
if (s != NATS_OK)
i--;
natsMutex_Unlock(m);
test("Waiting all done: ");
for (j=0; j<i; j++)
{
natsThread_Join(threads[j]);
natsThread_Destroy(threads[j]);
}
testCond(s == NATS_OK);
test("Checking sum: ");
testCond((s == NATS_OK) && (tArgs.sum == NUM_THREADS));
natsMutex_Destroy(m);
free(threads);
}
static void
testSignal(void *arg)
{
struct threadArg *tArg = (struct threadArg*) arg;
natsMutex_Lock(tArg->m);
tArg->control = 1;
natsCondition_Signal(tArg->c);
natsMutex_Unlock(tArg->m);
}
static void
testBroadcast(void *arg)
{
struct threadArg *tArg = (struct threadArg*) arg;
natsMutex_Lock(tArg->m);
tArg->sum++;
natsCondition_Signal(tArg->c);
while (tArg->control == 0)
natsCondition_Wait(tArg->b, tArg->m);
tArg->sum--;
natsMutex_Unlock(tArg->m);
}
static void
_unblockLongWait(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
nats_Sleep(500);
natsMutex_Lock(args->m);
natsCondition_Signal(args->c);
natsMutex_Unlock(args->m);
}
static void
test_natsCondition(void)
{
natsStatus s;
natsMutex *m = NULL;
natsThread *t1 = NULL;
natsThread *t2 = NULL;
natsCondition *c1 = NULL;
natsCondition *c2 = NULL;
struct threadArg tArgs;
int64_t before = 0;
int64_t diff = 0;
int64_t target = 0;
test("Create mutex: ");
s = natsMutex_Create(&m);
testCond(s == NATS_OK);
test("Create condition variables: ");
s = natsCondition_Create(&c1);
IFOK(s, natsCondition_Create(&c2));
testCond(s == NATS_OK);
natsMutex_Lock(m);
tArgs.m = m;
tArgs.c = c1;
tArgs.control = 0;
test("Create thread: ");
s = natsThread_Create(&t1, testSignal, &tArgs);
testCond(s == NATS_OK);
test("Wait for signal: ");
while (tArgs.control != 1)
natsCondition_Wait(c1, m);
natsThread_Join(t1);
natsThread_Destroy(t1);
t1 = NULL;
testCond(tArgs.control == 1);
test("Wait timeout: ");
before = nats_Now();
s = natsCondition_TimedWait(c1, m, 1000);
diff = (nats_Now() - before);
testCond((s == NATS_TIMEOUT)
&& (diff >= 985) && (diff <= 1015));
test("Wait timeout with 0: ");
before = nats_Now();
s = natsCondition_TimedWait(c1, m, 0);
diff = (nats_Now() - before);
testCond((s == NATS_TIMEOUT)
&& (diff >= 0) && (diff <= 10));
test("Wait timeout with negative: ");
before = nats_Now();
s = natsCondition_TimedWait(c1, m, -10);
diff = (nats_Now() - before);
testCond((s == NATS_TIMEOUT)
&& (diff >= 0) && (diff <= 10));
test("Wait absolute time: ");
before = nats_Now();
target = nats_setTargetTime(1000);
s = natsCondition_AbsoluteTimedWait(c1, m, target);
diff = (nats_Now() - before);
testCond((s == NATS_TIMEOUT)
&& (diff >= 985) && (diff <= 1015));
test("Wait absolute time in the past: ");
before = nats_Now();
target = nats_setTargetTime(-1000);
s = natsCondition_AbsoluteTimedWait(c1, m, target);
diff = (nats_Now() - before);
testCond((s == NATS_TIMEOUT)
&& (diff >= 0) && (diff <= 10));
test("Wait absolute time with very large value: ");
tArgs.control = 0;
s = natsThread_Create(&t1, _unblockLongWait, &tArgs);
if (s == NATS_OK)
{
before = nats_Now();
target = nats_setTargetTime(0x7FFFFFFFFFFFFFFF);
s = natsCondition_AbsoluteTimedWait(c1, m, target);
diff = (nats_Now() - before);
}
testCond((s == NATS_OK)
&& (diff >= 400) && (diff <= 600));
natsThread_Join(t1);
natsThread_Destroy(t1);
t1 = NULL;
test("Signal before wait: ");
tArgs.control = 0;
test("Create thread: ");
s = natsThread_Create(&t1, testSignal, &tArgs);
testCond(s == NATS_OK);
while (tArgs.control == 0)
{
natsMutex_Unlock(m);
nats_Sleep(1000);
natsMutex_Lock(m);
}
s = natsCondition_TimedWait(c1, m, 1000);
testCond(s == NATS_TIMEOUT);
natsThread_Join(t1);
natsThread_Destroy(t1);
t1 = NULL;
test("Broadcast: ");
tArgs.control = 0;
tArgs.sum = 0;
tArgs.b = c2;
s = natsThread_Create(&t1, testBroadcast, &tArgs);
IFOK(s, natsThread_Create(&t2, testBroadcast, &tArgs));
if (s != NATS_OK)
{
natsMutex_Unlock(m);
FAIL("Unable to run test_natsCondition because got an error while creating thread!");
}
while (tArgs.sum != 2)
natsCondition_Wait(c1, m);
natsMutex_Unlock(m);
nats_Sleep(1000);
natsMutex_Lock(m);
tArgs.control = 1;
natsCondition_Broadcast(c2);
natsMutex_Unlock(m);
natsThread_Join(t1);
natsThread_Destroy(t1);
t1 = NULL;
natsThread_Join(t2);
natsThread_Destroy(t2);
t2 = NULL;
testCond(tArgs.sum == 0);
test("Destroy condition: ");
natsCondition_Destroy(c1);
natsCondition_Destroy(c2);
testCond(1);
natsMutex_Destroy(m);
}
static void
testTimerCb(natsTimer *timer, void *arg)
{
struct threadArg *tArg = (struct threadArg*) arg;
natsMutex_Lock(tArg->m);
tArg->timerFired++;
natsCondition_Signal(tArg->c);
natsMutex_Unlock(tArg->m);
if (tArg->control == 1)
natsTimer_Reset(timer, 500);
else if (tArg->control == 2)
natsTimer_Stop(timer);
else if (tArg->control == 3)
nats_Sleep(500);
natsMutex_Lock(tArg->m);
natsCondition_Signal(tArg->c);
natsMutex_Unlock(tArg->m);
}
static void
stopTimerCb(natsTimer *timer, void *arg)
{
struct threadArg *tArg = (struct threadArg*) arg;
natsMutex_Lock(tArg->m);
tArg->timerStopped++;
natsCondition_Signal(tArg->c);
natsMutex_Unlock(tArg->m);
}
static void
_dummyTimerCB(natsTimer *timer, void *arg) {}
static void
_timerStopCB(natsTimer *timer, void *arg)
{
natsTimer_Release(timer);
}
#define STOP_TIMER_AND_WAIT_STOPPED \
natsTimer_Stop(t); \
natsMutex_Lock(tArg.m); \
while (tArg.timerStopped == 0) \
natsCondition_Wait(tArg.c, tArg.m); \
natsMutex_Unlock(tArg.m)
static void
test_natsTimer(void)
{
natsStatus s;
natsTimer *t = NULL;
struct threadArg tArg;
int refs;
test("Setup test: ");
s = _createDefaultThreadArgsForCbTests(&tArg);
testCond(s == NATS_OK);
tArg.control = 0;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Create timer: ");
s = natsTimer_Create(&t, testTimerCb, stopTimerCb, 400, &tArg);
testCond(s == NATS_OK);
test("Stop timer: ");
tArg.control = 0;
natsTimer_Stop(t);
nats_Sleep(600);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired == 0)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& (nats_getTimersCount() == 0));
natsMutex_Unlock(tArg.m);
test("Firing of timer: ")
tArg.control = 0;
tArg.timerStopped = 0;
natsTimer_Reset(t, 200);
nats_Sleep(1100);
natsTimer_Stop(t);
nats_Sleep(600);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired > 0)
&& (tArg.timerFired <= 5)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& (nats_getTimersCount() == 0));
natsMutex_Unlock(tArg.m);
test("Stop stopped timer: ");
tArg.control = 0;
tArg.timerFired = 0;
tArg.timerStopped = 0;
natsTimer_Reset(t, 100);
nats_Sleep(300);
natsTimer_Stop(t);
nats_Sleep(100);
natsTimer_Stop(t);
nats_Sleep(100);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired > 0)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& (nats_getTimersCount() == 0));
natsMutex_Unlock(tArg.m);
tArg.control = 1;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Reset from callback: ");
natsTimer_Reset(t, 250);
nats_Sleep(900);
natsTimer_Stop(t);
nats_Sleep(600);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired == 2)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& nats_getTimersCount() == 0);
natsMutex_Unlock(tArg.m);
tArg.control = 0;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Multiple Reset: ");
natsTimer_Reset(t, 1000);
natsTimer_Reset(t, 800);
natsTimer_Reset(t, 200);
natsTimer_Reset(t, 500);
nats_Sleep(600);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired == 1)
&& (tArg.timerStopped == 0)
&& (refs == 1)
&& nats_getTimersCount() == 1);
natsMutex_Unlock(tArg.m);
STOP_TIMER_AND_WAIT_STOPPED;
tArg.control = 3;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Check refs while in callback: ");
natsTimer_Reset(t, 1);
// Wait that it is in callback
natsMutex_Lock(tArg.m);
while (tArg.timerFired != 1)
natsCondition_Wait(tArg.c, tArg.m);
natsMutex_Unlock(tArg.m);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
testCond((refs == 2)
&& nats_getTimersCountInList() == 0
&& nats_getTimersCount() == 1);
STOP_TIMER_AND_WAIT_STOPPED;
tArg.control = 2;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Stop from callback: ");
natsTimer_Reset(t, 250);
nats_Sleep(500);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired == 1)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& (nats_getTimersCount() == 0));
natsMutex_Unlock(tArg.m);
tArg.control = 3;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Slow callback: ");
natsTimer_Reset(t, 100);
nats_Sleep(800);
natsTimer_Stop(t);
nats_Sleep(500);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired <= 3)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& (nats_getTimersCount() == 0));
natsMutex_Unlock(tArg.m);
tArg.control = 3;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Stopped while in callback: ");
natsTimer_Reset(t, 100);
nats_Sleep(200);
natsTimer_Stop(t);
nats_Sleep(700);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired == 1)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& (nats_getTimersCount() == 0));
natsMutex_Unlock(tArg.m);
tArg.control = 4;
tArg.timerFired = 0;
tArg.timerStopped = 0;
test("Use very large timeout: ");
natsTimer_Reset(t, 0x7FFFFFFFFFFFFFFF);
nats_Sleep(200);
natsTimer_Stop(t);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
natsMutex_Lock(tArg.m);
testCond((tArg.timerFired == 0)
&& (tArg.timerStopped == 1)
&& (refs == 1)
&& (nats_getTimersCount() == 0));
natsMutex_Unlock(tArg.m);
test("Destroy timer: ");
natsMutex_Lock(t->mu);
t->refs++;
natsMutex_Unlock(t->mu);
natsTimer_Destroy(t);
natsMutex_Lock(t->mu);
refs = t->refs;
natsMutex_Unlock(t->mu);
testCond(refs == 1);
natsTimer_Release(t);
_destroyDefaultThreadArgs(&tArg);
// Create a timer that will not be stopped here to exercise
// code that cleans up timers when library is unloaded.
test("Create timer: ");
s = natsTimer_Create(&t, _dummyTimerCB, _timerStopCB, 1000, NULL);
testCond(s == NATS_OK);
}
static void
test_natsUrl(void)
{
natsStatus s;
natsUrl *u = NULL;
test("NULL: ");
s = natsUrl_Create(&u, NULL);
testCond((s != NATS_OK) && (u == NULL));
test("EMPTY: ");
s = natsUrl_Create(&u, "");
testCond((s != NATS_OK) && (u == NULL));
nats_clearLastError();
test("'localhost:':");
s = natsUrl_Create(&u, "localhost:");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222)
&& (strcmp(u->fullUrl, "nats://localhost:4222") == 0));
natsUrl_Destroy(u);
u = NULL;
test("'localhost:4223':");
s = natsUrl_Create(&u, "localhost:4223");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4223)
&& (strcmp(u->fullUrl, "nats://localhost:4223") == 0));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://localhost:4222':");
s = natsUrl_Create(&u, "tcp://localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://localhost':");
s = natsUrl_Create(&u, "tcp://localhost");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'localhost':");
s = natsUrl_Create(&u, "localhost");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://[::1]:4222':");
s = natsUrl_Create(&u, "tcp://[::1]:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "[::1]") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://[::1]:':");
s = natsUrl_Create(&u, "tcp://[::1]:");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "[::1]") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://[::1]':");
s = natsUrl_Create(&u, "tcp://[::1]");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "[::1]") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://':");
s = natsUrl_Create(&u, "tcp://");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://:':");
s = natsUrl_Create(&u, "tcp://:");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://ivan:localhost:4222':");
s = natsUrl_Create(&u, "tcp://ivan:localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "ivan:localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://ivan:pwd:localhost:4222':");
s = natsUrl_Create(&u, "tcp://ivan:pwd:localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "ivan:pwd:localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://ivan@localhost:4222':");
s = natsUrl_Create(&u, "tcp://ivan@localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "ivan") == 0)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://ivan:pwd@localhost:4222':");
s = natsUrl_Create(&u, "tcp://ivan:pwd@localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "ivan") == 0)
&& (u->password != NULL)
&& (strcmp(u->password, "pwd") == 0)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://ivan:pwd@localhost':");
s = natsUrl_Create(&u, "tcp://ivan:pwd@localhost");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "ivan") == 0)
&& (u->password != NULL)
&& (strcmp(u->password, "pwd") == 0)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://@localhost:4222':");
s = natsUrl_Create(&u, "tcp://@localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://@@localhost:4222':");
s = natsUrl_Create(&u, "tcp://@@localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "@") == 0)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://a:b:c@localhost:4222':");
s = natsUrl_Create(&u, "tcp://a:b:c@localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "a") == 0)
&& (u->password != NULL)
&& (strcmp(u->password, "b:c") == 0)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://::a:b:c@localhost:4222':");
s = natsUrl_Create(&u, "tcp://::a:b:c@localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password != NULL)
&& (strcmp(u->password, ":a:b:c") == 0)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://a:b@[::1]:4222':");
s = natsUrl_Create(&u, "tcp://a:b@[::1]:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "[::1]") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "a") == 0)
&& (u->password != NULL)
&& (strcmp(u->password, "b") == 0)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://a@[::1]:4222':");
s = natsUrl_Create(&u, "tcp://a@[::1]:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "[::1]") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "a") == 0)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://a:b@[::1]:4222':");
s = natsUrl_Create(&u, "tcp://a:b@[::1]:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "[::1]") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "a") == 0)
&& (u->password != NULL)
&& (strcmp(u->password, "b") == 0)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://a:b@[::1]':");
s = natsUrl_Create(&u, "tcp://a:b@[::1]");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "[::1]") == 0)
&& (u->username != NULL)
&& (strcmp(u->username, "a") == 0)
&& (u->password != NULL)
&& (strcmp(u->password, "b") == 0)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("' tcp://localhost:4222':");
s = natsUrl_Create(&u, " tcp://localhost:4222");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://localhost:4222 ':");
s = natsUrl_Create(&u, "tcp://localhost:4222 ");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("' tcp://localhost:4222 ':");
s = natsUrl_Create(&u, " tcp://localhost:4222 ");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222));
natsUrl_Destroy(u);
u = NULL;
test("'nats://localhost:4222/subject':");
s = natsUrl_Create(&u, " nats://localhost:4222/subject");
testCond((s == NATS_OK)
&& (u != NULL)
&& (u->host != NULL)
&& (strcmp(u->host, "localhost") == 0)
&& (u->username == NULL)
&& (u->password == NULL)
&& (u->port == 4222)
&& (strcmp(u->fullUrl, "nats://localhost:4222/subject") == 0));
natsUrl_Destroy(u);
u = NULL;
test("'tcp://localhost: 4222':");
s = natsUrl_Create(&u, "tcp://localhost: 4222");
testCond((s == NATS_INVALID_ARG)
&& (u == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid port ' 4222'") != NULL));
nats_clearLastError();
test("'tcp://localhost:a4222':");
s = natsUrl_Create(&u, "tcp://localhost:a4222");
testCond((s == NATS_INVALID_ARG)
&& (u == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid port 'a4222'") != NULL));
nats_clearLastError();
test("'tcp://localhost:2147483648':");
s = natsUrl_Create(&u, "tcp://localhost:2147483648");
testCond((s == NATS_INVALID_ARG)
&& (u == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid port '2147483648'") != NULL));
}
static void
test_natsCreateStringFromBuffer(void)
{
natsStatus s = NATS_OK;
natsBuffer buf;
char *str = NULL;
test("NULL buffer: ");
s = nats_CreateStringFromBuffer(&str, NULL);
testCond((s == NATS_OK)
&& (str == NULL))
test("Init buffer: ");
s = natsBuf_Init(&buf, 10);
testCond(s == NATS_OK);
test("Empty buffer: ");
s = nats_CreateStringFromBuffer(&str, &buf);
testCond((s == NATS_OK)
&& (str == NULL))
test("Append to buf: ");
s = natsBuf_Append(&buf, "123", 3);
testCond(s == NATS_OK);
test("Buffer containing '123': ");
s = nats_CreateStringFromBuffer(&str, &buf);
testCond((s == NATS_OK)
&& (str != NULL)
&& (strlen(str) == 3)
&& (strcmp(str, "123") == 0));
test("Destroying the buffer does not affect the created string: ");
natsBuf_Cleanup(&buf);
testCond((str != NULL)
&& (strlen(str) == 3)
&& (strcmp(str, "123") == 0));
free(str);
}
#define INBOX_THREADS_COUNT (10)
#define INBOX_COUNT_PER_THREAD (10000)
#define INBOX_TOTAL (INBOX_THREADS_COUNT * INBOX_COUNT_PER_THREAD)
static void
_testInbox(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsStatus s = NATS_OK;
natsInbox *inbox;
void *oldValue;
for (int i=0; (s == NATS_OK) && (i<INBOX_COUNT_PER_THREAD); i++)
{
inbox = NULL;
oldValue = NULL;
s = natsInbox_Create(&inbox);
if (s == NATS_OK)
s = natsStrHash_Set(args->inboxes, inbox, true, (void*) 1, (void**) &oldValue);
if ((s == NATS_OK) && (oldValue != NULL))
{
printf("Duplicate inbox: %s\n", inbox);
s = NATS_ERR;
}
natsInbox_Destroy(inbox);
}
args->status = s;
}
static void
test_natsInbox(void)
{
natsStatus s = NATS_OK;
natsThread *threads[INBOX_THREADS_COUNT];
struct threadArg args[INBOX_THREADS_COUNT];
int i, j;
natsInbox *key;
void *oldInbox;
natsStrHash *inboxes = NULL;
natsStrHashIter iter;
test("Test inboxes are unique: ");
for (i=0; i<INBOX_THREADS_COUNT; i++)
{
args[i].status = NATS_OK;
args[i].inboxes = NULL;
threads[i] = NULL;
}
s = natsStrHash_Create(&inboxes, 16);
for (i=0; (s == NATS_OK) && (i<INBOX_THREADS_COUNT); i++)
{
s = natsStrHash_Create(&(args[i].inboxes), 16);
if (s == NATS_OK)
s = natsThread_Create(&(threads[i]), _testInbox, &(args[i]));
}
for (i=0; (i<INBOX_THREADS_COUNT); i++)
{
natsThread_Join(threads[i]);
if (s == NATS_OK)
s = args[i].status;
if (s == NATS_OK)
{
j = 0;
natsStrHashIter_Init(&iter, args[i].inboxes);
while ((s == NATS_OK)
&& natsStrHashIter_Next(&iter, &key, NULL))
{
j++;
s = natsStrHash_Set(inboxes, key, true, (void*) 1, &oldInbox);
natsStrHashIter_RemoveCurrent(&iter);
}
if (j != INBOX_COUNT_PER_THREAD)
s = NATS_ERR;
natsStrHashIter_Done(&iter);
}
natsThread_Destroy(threads[i]);
}
testCond(s == NATS_OK);
for (i=0; i<INBOX_THREADS_COUNT; i++)
natsStrHash_Destroy(args[i].inboxes);
natsStrHash_Destroy(inboxes);
}
static int HASH_ITER = 10000000;
static void
test_natsHashing(void)
{
const char *keys[] = {"foo",
"bar",
"apcera.continuum.router.foo.bar",
"apcera.continuum.router.foo.bar.baz"};
const char *longKey = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$#%^&*()";
uint32_t results[] = {1058908168, 1061739001, 4242539713, 3332038527};
uint32_t r = 0;
uint32_t lr = 0;
natsStatus s = NATS_OK;
int64_t start, end;
int sizeLongKey = (int) strlen(longKey);
if (valgrind)
HASH_ITER = 10000;
test("Test hashing algo: ");
for (int i=0; i<(int)(sizeof(keys)/sizeof(char*)); i++)
{
r = natsStrHash_Hash(keys[i], (int) strlen(keys[i]));
if (r != results[i])
{
printf("Expected: %u got: %u\n", results[i], r);
s = NATS_ERR;
break;
}
}
testCond(s == NATS_OK);
test("Hashing performance: ");
s = NATS_OK;
start = nats_Now();
for (int i=0; i<HASH_ITER; i++)
{
r = natsStrHash_Hash(longKey, sizeLongKey);
if ((i > 0) && (r != lr))
{
s = NATS_ERR;
break;
}
lr = r;
}
end = nats_Now();
testCond((s == NATS_OK) && ((end - start) < 1000));
}
static void
test_natsHash(void)
{
natsStatus s;
natsHash *hash = NULL;
const char *t1 = "this is a test";
const char *t2 = "this is another test";
void *oldval = NULL;
int lastNumBkts = 0;
int i;
int64_t key;
int values[40];
natsHashIter iter;
for (int i=0; i<40; i++)
values[i] = (i+1);
test("Create hash with invalid 0 size: ");
s = natsHash_Create(&hash, 0);
testCond((s != NATS_OK) && (hash == NULL));
test("Create hash with invalid negative size: ");
s = natsHash_Create(&hash, -2);
testCond((s != NATS_OK) && (hash == NULL));
nats_clearLastError();
test("Create hash ok: ");
s = natsHash_Create(&hash, 7);
testCond((s == NATS_OK) && (hash != NULL) && (hash->used == 0)
&& (hash->numBkts == 8));
test("Set: ");
s = natsHash_Set(hash, 1234, (void*) t1, &oldval);
testCond((s == NATS_OK) && (oldval == NULL) && (hash->used == 1));
test("Set, get old value: ");
s = natsHash_Set(hash, 1234, (void*) t2, &oldval);
testCond((s == NATS_OK) && (oldval == t1) && (hash->used == 1))
test("Get, not found: ");
oldval = NULL;
oldval = natsHash_Get(hash, 3456);
testCond(oldval == NULL);
test("Get, found: ");
oldval = NULL;
oldval = natsHash_Get(hash, 1234);
testCond(oldval == t2);
test("Remove, not found: ");
oldval = NULL;
oldval = natsHash_Remove(hash, 3456);
testCond(oldval == NULL);
test("Remove, found: ");
oldval = NULL;
oldval = natsHash_Remove(hash, 1234);
testCond((oldval == t2) && (hash->used == 0));
test("Test collision: ");
oldval = NULL;
s = natsHash_Set(hash, 2, (void*) t1, &oldval);
if ((s == NATS_OK) && (oldval == NULL))
s = natsHash_Set(hash, 10, (void*) t2, &oldval);
testCond((s == NATS_OK)
&& (oldval == NULL)
&& (hash->used == 2)
&& (hash->bkts[2] != NULL)
&& (hash->bkts[2]->key == 10)
&& (hash->bkts[2]->next != NULL)
&& (hash->bkts[2]->next->key == 2));
test("Remove from collisions (front to back): ");
oldval = NULL;
oldval = natsHash_Remove(hash, 10);
if (oldval != t2)
s = NATS_ERR;
if (s == NATS_OK)
{
oldval = natsHash_Remove(hash, 2);
if (oldval != t1)
s = NATS_ERR;
}
testCond((s == NATS_OK) && (hash->used == 0));
test("Remove from collisions (back to front): ");
oldval = NULL;
s = natsHash_Set(hash, 2, (void*) t1, &oldval);
if ((s == NATS_OK) && (oldval == NULL))
s = natsHash_Set(hash, 10, (void*) t2, &oldval);
if (s == NATS_OK)
{
oldval = natsHash_Remove(hash, 2);
if (oldval != t1)
s = NATS_ERR;
}
if (s == NATS_OK)
{
oldval = natsHash_Remove(hash, 10);
if (oldval != t2)
s = NATS_ERR;
}
testCond((s == NATS_OK) && (hash->used == 0));
test("Grow: ");
for (int i=0; i<40; i++)
{
s = natsHash_Set(hash, (i+1), &(values[i]), &oldval);
if (oldval != NULL)
s = NATS_ERR;
if (s != NATS_OK)
break;
}
if (s == NATS_OK)
{
for (int i=0; i<40; i++)
{
oldval = natsHash_Get(hash, (i+1));
if ((oldval == NULL)
|| ((*(int*)oldval) != values[i]))
{
s = NATS_ERR;
break;
}
}
}
testCond((s == NATS_OK)
&& (hash->used == 40)
&& (hash->numBkts > 8));
lastNumBkts = hash->numBkts;
test("Shrink: ");
for (int i=0; i<31; i++)
{
oldval = natsHash_Remove(hash, (i+1));
if ((oldval == NULL)
|| ((*(int*)oldval) != values[i]))
{
s = NATS_ERR;
break;
}
}
testCond((s == NATS_OK)
&& (hash->used == 9)
&& (hash->numBkts < lastNumBkts));
test("Iterator: ");
natsHashIter_Init(&iter, hash);
i = 0;
while (natsHashIter_Next(&iter, &key, &oldval))
{
i++;
if (((key < 31) || (key > 40))
|| (oldval == NULL)
|| ((*(int*)oldval) != values[key-1]))
{
s = NATS_ERR;
break;
}
}
natsHashIter_Done(&iter);
testCond((s == NATS_OK) && (i == natsHash_Count(hash)));
test("Iterator, remove current: ");
natsHashIter_Init(&iter, hash);
while (natsHashIter_Next(&iter, &key, NULL))
{
s = natsHashIter_RemoveCurrent(&iter);
if (s != NATS_OK)
break;
}
testCond((s == NATS_OK)
&& (natsHash_Count(hash) == 0)
&& (hash->canResize == false)
&& (hash->numBkts > 8));
natsHashIter_Done(&iter);
test("Grow again: ");
oldval = NULL;
for (int i=0; i<40; i++)
{
s = natsHash_Set(hash, (i+1), &(values[i]), &oldval);
if (oldval != NULL)
s = NATS_ERR;
if (s != NATS_OK)
break;
}
testCond((s == NATS_OK)
&& (hash->used == 40)
&& (hash->numBkts > 8));
lastNumBkts = hash->numBkts;
test("Iterator, remove current, hash does not shrink: ");
natsHashIter_Init(&iter, hash);
i = 0;
while (natsHashIter_Next(&iter, &key, NULL))
{
s = natsHashIter_RemoveCurrent(&iter);
if ((s != NATS_OK) || (++i == 31))
break;
}
testCond((s == NATS_OK)
&& (natsHash_Count(hash) == 9)
&& (hash->canResize == false)
&& (hash->numBkts == lastNumBkts));
natsHashIter_Done(&iter);
test("After iterator done, shrink works: ");
oldval = NULL;
s = natsHash_Set(hash, 100, (void*) "last", &oldval);
if ((s == NATS_OK) && (oldval == NULL))
{
oldval = natsHash_Remove(hash, 100);
if ((oldval == NULL)
|| (strcmp((const char*) oldval, "last") != 0))
{
s = NATS_ERR;
}
}
testCond((s == NATS_OK)
&& hash->canResize
&& (hash->numBkts != lastNumBkts));
test("Destroy: ");
natsHash_Destroy(hash);
hash = NULL;
testCond(1);
test("Create new: ");
s = natsHash_Create(&hash, 4);
testCond(s == NATS_OK);
test("Populate: ");
s = natsHash_Set(hash, 1, (void*) 1, NULL);
IFOK(s, natsHash_Set(hash, 2, (void*) 2, NULL));
IFOK(s, natsHash_Set(hash, 3, (void*) 3, NULL));
testCond(s == NATS_OK);
test("Remove one: ");
s = (natsHash_Remove(hash, 2) == (void*) 2) ? NATS_OK : NATS_ERR;
testCond(s == NATS_OK);
test("RemoveSingle fails if more than one: ");
s = natsHash_RemoveSingle(hash, &key, NULL);
testCond(s == NATS_ERR);
nats_clearLastError();
test("Remove second: ");
s = (natsHash_Remove(hash, 1) == (void*) 1) ? NATS_OK : NATS_ERR;
testCond(s == NATS_OK);
test("Remove single: ");
key = 0;
oldval = NULL;
s = natsHash_RemoveSingle(hash, &key, &oldval);
testCond((s == NATS_OK)
&& (hash->used == 0)
&& (key == 3)
&& (oldval == (void*) 3));
natsHash_Destroy(hash);
}
static void
test_natsStrHash(void)
{
natsStatus s;
natsStrHash *hash = NULL;
const char *t1 = "this is a test";
const char *t2 = "this is another test";
void *oldval = NULL;
char *myKey = NULL;
int lastNumBkts = 0;
int i;
char *key;
int values[40];
char k[64];
uint32_t hk;
natsStrHashIter iter;
for (int i=0; i<40; i++)
values[i] = (i+1);
test("Create hash with invalid 0 size: ");
s = natsStrHash_Create(&hash, 0);
testCond((s != NATS_OK) && (hash == NULL));
test("Create hash with invalid negative size: ");
s = natsStrHash_Create(&hash, -2);
testCond((s != NATS_OK) && (hash == NULL));
nats_clearLastError();
test("Create hash ok: ");
s = natsStrHash_Create(&hash, 7);
testCond((s == NATS_OK) && (hash != NULL) && (hash->used == 0)
&& (hash->numBkts == 8));
test("Set: ");
s = natsStrHash_Set(hash, (char*) "1234", false, (void*) t1, &oldval);
testCond((s == NATS_OK) && (oldval == NULL) && (hash->used == 1));
test("Set, get old value: ");
s = natsStrHash_Set(hash, (char*) "1234", false, (void*) t2, &oldval);
testCond((s == NATS_OK) && (oldval == t1) && (hash->used == 1))
test("Get, not found: ");
oldval = NULL;
oldval = natsStrHash_Get(hash, (char*) "3456");
testCond(oldval == NULL);
test("Get, found: ");
oldval = NULL;
oldval = natsStrHash_Get(hash, (char*) "1234");
testCond(oldval == t2);
test("Remove, not found: ");
oldval = NULL;
oldval = natsStrHash_Remove(hash, (char*) "3456");
testCond(oldval == NULL);
test("Remove, found: ");
oldval = NULL;
oldval = natsStrHash_Remove(hash, (char*) "1234");
testCond((oldval == t2) && (hash->used == 0));
test("Grow: ");
for (int i=0; i<40; i++)
{
snprintf(k, sizeof(k), "%d", (i+1));
s = natsStrHash_Set(hash, k, true, &(values[i]), &oldval);
if (oldval != NULL)
s = NATS_ERR;
if (s != NATS_OK)
break;
}
if (s == NATS_OK)
{
for (int i=0; i<40; i++)
{
snprintf(k, sizeof(k), "%d", (i+1));
oldval = natsStrHash_Get(hash, k);
if ((oldval == NULL)
|| ((*(int*)oldval) != values[i]))
{
s = NATS_ERR;
break;
}
}
}
testCond((s == NATS_OK)
&& (hash->used == 40)
&& (hash->numBkts > 8));
lastNumBkts = hash->numBkts;
test("Shrink: ");
for (int i=0; i<31; i++)
{
snprintf(k, sizeof(k), "%d", (i+1));
oldval = natsStrHash_Remove(hash, k);
if ((oldval == NULL)
|| ((*(int*)oldval) != values[i]))
{
s = NATS_ERR;
break;
}
}
testCond((s == NATS_OK)
&& (hash->used == 9)
&& (hash->numBkts < lastNumBkts));
test("Iterator: ");
natsStrHashIter_Init(&iter, hash);
i = 0;
while (natsStrHashIter_Next(&iter, &key, &oldval))
{
i++;
if (((atoi(key) < 31) || (atoi(key) > 40))
|| (oldval == NULL)
|| ((*(int*)oldval) != values[atoi(key)-1]))
{
s = NATS_ERR;
break;
}
}
natsStrHashIter_Done(&iter);
testCond((s == NATS_OK) && (i == natsStrHash_Count(hash)));
test("Iterator, remove current: ");
natsStrHashIter_Init(&iter, hash);
while (natsStrHashIter_Next(&iter, &key, NULL))
{
s = natsStrHashIter_RemoveCurrent(&iter);
if (s != NATS_OK)
break;
}
testCond((s == NATS_OK)
&& (natsStrHash_Count(hash) == 0)
&& (hash->canResize == false)
&& (hash->numBkts > 8));
natsStrHashIter_Done(&iter);
test("Grow again: ");
oldval = NULL;
for (int i=0; i<40; i++)
{
snprintf(k, sizeof(k), "%d", (i+1));
s = natsStrHash_Set(hash, k, true, &(values[i]), &oldval);
if (oldval != NULL)
s = NATS_ERR;
if (s != NATS_OK)
break;
}
testCond((s == NATS_OK)
&& (hash->used == 40)
&& (hash->numBkts > 8));
lastNumBkts = hash->numBkts;
test("Iterator, remove current, hash does not shrink: ");
natsStrHashIter_Init(&iter, hash);
i = 0;
while (natsStrHashIter_Next(&iter, &key, NULL))
{
s = natsStrHashIter_RemoveCurrent(&iter);
if ((s != NATS_OK) || (++i == 31))
break;
}
testCond((s == NATS_OK)
&& (natsStrHash_Count(hash) == 9)
&& (hash->canResize == false)
&& (hash->numBkts == lastNumBkts));
natsStrHashIter_Done(&iter);
test("After iterator done, shrink works: ");
oldval = NULL;
s = natsStrHash_Set(hash, (char*) "100", true, (void*) "last", &oldval);
if ((s == NATS_OK) && (oldval == NULL))
{
oldval = natsStrHash_Remove(hash, (char*) "100");
if ((oldval == NULL)
|| (strcmp((const char*) oldval, "last") != 0))
{
s = NATS_ERR;
}
}
testCond((s == NATS_OK)
&& hash->canResize
&& (hash->numBkts != lastNumBkts));
test("Copy key: ");
snprintf(k, sizeof(k), "%s", "keycopied");
hk = natsStrHash_Hash(k, (int) strlen(k));
s = natsStrHash_Set(hash, k, true, (void*) t1, &oldval);
if (s == NATS_OK)
{
// Changing the key does not affect the hash
snprintf(k, sizeof(k), "%s", "keychanged");
if (natsStrHash_Get(hash, (char*) "keycopied") != t1)
s = NATS_ERR;
}
testCond((s == NATS_OK)
&& (oldval == NULL)
&& (hash->bkts[hk & hash->mask]->hk == hk)
&& (hash->bkts[hk & hash->mask]->freeKey == true));
test("Key referenced: ");
snprintf(k, sizeof(k), "%s", "keyreferenced");
hk = natsStrHash_Hash(k, (int) strlen(k));
s = natsStrHash_Set(hash, k, false, (void*) t2, &oldval);
if (s == NATS_OK)
{
// Changing the key affects the hash
snprintf(k, sizeof(k), "%s", "keychanged");
if (natsStrHash_Get(hash, (char*) "keyreferenced") == t2)
s = NATS_ERR;
}
testCond((s == NATS_OK)
&& (oldval == NULL)
&& (hash->bkts[hk & hash->mask]->hk == hk)
&& (hash->bkts[hk & hash->mask]->freeKey == false)
&& (strcmp(hash->bkts[hk & hash->mask]->key, "keychanged") == 0));
test("Key not copied, but asking to free when destroyed: ");
myKey = strdup("mykey");
hk = natsStrHash_Hash(myKey, (int) strlen(myKey));
s = natsStrHash_SetEx(hash, myKey, false, true, (void*) t1, &oldval);
testCond((s == NATS_OK)
&& (oldval == NULL)
&& (hash->bkts[hk & hash->mask]->hk == hk)
&& (hash->bkts[hk & hash->mask]->freeKey == true));
test("Destroy: ");
natsStrHash_Destroy(hash);
hash = NULL;
testCond(1);
test("Create new: ");
s = natsStrHash_Create(&hash, 4);
testCond(s == NATS_OK);
test("Populate: ");
s = natsStrHash_Set(hash, (char*) "1", true, (void*) 1, NULL);
IFOK(s, natsStrHash_Set(hash, (char*) "2", true, (void*) 2, NULL));
IFOK(s, natsStrHash_Set(hash, (char*) "3", true, (void*) 3, NULL));
testCond(s == NATS_OK);
test("Remove one: ");
s = (natsStrHash_Remove(hash, (char*) "2") == (void*) 2) ? NATS_OK : NATS_ERR;
testCond(s == NATS_OK);
test("RemoveSingle fails if more than one: ");
s = natsStrHash_RemoveSingle(hash, &key, NULL);
testCond(s == NATS_ERR);
nats_clearLastError();
test("Remove second: ");
s = (natsStrHash_Remove(hash, (char*) "1") == (void*) 1) ? NATS_OK : NATS_ERR;
testCond(s == NATS_OK);
test("Remove single (copy of key): ");
key = NULL;
oldval = NULL;
s = natsStrHash_RemoveSingle(hash, &key, &oldval);
testCond((s == NATS_OK)
&& (hash->used == 0)
&& (strcmp(key, "3") == 0)
&& (oldval == (void*) 3));
free(key);
key = NULL;
oldval = NULL;
test("Add key without copy: ");
s = natsStrHash_Set(hash, (char*) "4", false, (void*) 4, NULL);
testCond(s == NATS_OK);
test("Remove single (no copy of key): ");
s = natsStrHash_RemoveSingle(hash, &key, &oldval);
testCond((s == NATS_OK)
&& (hash->used == 0)
&& (strcmp(key, "4") == 0)
&& (oldval == (void*) 4));
natsStrHash_Destroy(hash);
}
static const char*
_dummyTokenHandler(void *closure)
{
return "token";
}
static void
_dummyErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus err,
void *closure)
{
// do nothing
}
static void
_dummyConnHandler(natsConnection *nc, void *closure)
{
// do nothing
}
static natsStatus
_dummyUserJWTCb(char **userJWT, char **customErrTxt, void *closure)
{
// do nothing
return NATS_OK;
}
static natsStatus
_dummySigCb(char **customErrTxt, unsigned char **psig, int *sigLen, const char* nonce, void *closure)
{
// do nothing
return NATS_OK;
}
static void
test_natsOptions(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsOptions *cloned = NULL;
const char *servers[] = {"1", "2", "3"};
const char *servers2[] = {"1", "2", "3", "4"};
const char *servers3[] = {" nats://localhost:4222", "nats://localhost:4223 ", " nats://localhost:4224 "};
const char *servers3t[] = {"nats://localhost:4222", "nats://localhost:4223", "nats://localhost:4224"};
test("Create options: ");
s = natsOptions_Create(&opts);
testCond(s == NATS_OK);
test("Test defaults: ");
testCond((opts->allowReconnect == true)
&& (opts->maxReconnect == 60)
&& (opts->reconnectWait == 2 * 1000)
&& (opts->timeout == 2 * 1000)
&& (opts->pingInterval == 2 * 60 *1000)
&& (opts->maxPingsOut == 2)
&& (opts->ioBufSize == 32 * 1024)
&& (opts->maxPendingMsgs == 65536)
&& (opts->user == NULL)
&& (opts->password == NULL)
&& (opts->token == NULL)
&& (opts->tokenCb == NULL)
&& (opts->orderIP == 0)
&& (opts->writeDeadline == natsLib_defaultWriteDeadline())
&& !opts->noEcho
&& !opts->retryOnFailedConnect
&& !opts->ignoreDiscoveredServers)
test("Add URL: ");
s = natsOptions_SetURL(opts, "test");
testCond((s == NATS_OK)
&& (opts->url != NULL)
&& (strcmp(opts->url, "test") == 0));
test("Replace URL: ");
s = natsOptions_SetURL(opts, "test2");
testCond((s == NATS_OK)
&& (opts->url != NULL)
&& (strcmp(opts->url, "test2") == 0));
test("URL trimmed: ");
s = natsOptions_SetURL(opts, " nats://localhost:4222 ");
testCond((s == NATS_OK)
&& (opts->url != NULL)
&& (strcmp(opts->url, "nats://localhost:4222") == 0));
test("Remove URL: ");
s = natsOptions_SetURL(opts, NULL);
testCond((s == NATS_OK)
&& (opts->url == NULL));
test("Set Servers (invalid args): ");
s = natsOptions_SetServers(opts, servers, -2);
if (s != NATS_OK)
s = natsOptions_SetServers(opts, servers, 0);
testCond(s != NATS_OK);
test("Set Servers: ");
s = natsOptions_SetServers(opts, servers, 3);
testCond((s == NATS_OK)
&& (opts->servers != NULL)
&& (opts->serversCount == 3));
test("Replace Servers: ");
s = natsOptions_SetServers(opts, servers2, 4);
if ((s == NATS_OK) && (opts->servers != NULL) && (opts->serversCount == 4))
{
for (int i=0; i<4; i++)
{
if (strcmp(opts->servers[i], servers2[i]) != 0)
{
s = NATS_ERR;
break;
}
}
}
testCond(s == NATS_OK);
test("Trimmed servers: ")
s = natsOptions_SetServers(opts, servers3, 3);
if ((s == NATS_OK) && (opts->servers != NULL) && (opts->serversCount == 3))
{
for (int i=0; i<3; i++)
{
if (strcmp(opts->servers[i], servers3t[i]) != 0)
{
s = NATS_ERR;
break;
}
}
}
testCond(s == NATS_OK);
test("Remove servers: ");
s = natsOptions_SetServers(opts, NULL, 0);
testCond((s == NATS_OK)
&& (opts->servers == NULL)
&& (opts->serversCount == 0));
test("Set NoRandomize: ");
s = natsOptions_SetNoRandomize(opts, true);
testCond((s == NATS_OK) && (opts->noRandomize == true));
test("Remove NoRandomize: ");
s = natsOptions_SetNoRandomize(opts, false);
testCond((s == NATS_OK) && (opts->noRandomize == false));
test("Set Timeout (invalid args): ");
s = natsOptions_SetTimeout(opts, -10);
testCond(s != NATS_OK);
test("Set Timeout to zero: ");
s = natsOptions_SetTimeout(opts, 0);
testCond((s == NATS_OK) && (opts->timeout == 0));
test("Set Timeout: ");
s = natsOptions_SetTimeout(opts, 2000);
testCond((s == NATS_OK) && (opts->timeout == 2000));
test("Set Name: ");
s = natsOptions_SetName(opts, "test");
testCond((s == NATS_OK) && (opts->name != NULL)
&& (strcmp(opts->name, "test") == 0));
test("Remove Name: ");
s = natsOptions_SetName(opts, NULL);
testCond((s == NATS_OK) && (opts->name == NULL));
test("Set Verbose: ");
s = natsOptions_SetVerbose(opts, true);
testCond((s == NATS_OK) && (opts->verbose == true));
test("Remove Verbose: ");
s = natsOptions_SetVerbose(opts, false);
testCond((s == NATS_OK) && (opts->verbose == false));
test("Set NoEcho: ");
s = natsOptions_SetNoEcho(opts, true);
testCond((s == NATS_OK) && (opts->noEcho == true));
test("Remove NoEcho: ");
s = natsOptions_SetNoEcho(opts, false);
testCond((s == NATS_OK) && (opts->noEcho == false));
test("Set RetryOnFailedConnect: ");
s = natsOptions_SetRetryOnFailedConnect(opts, true, _dummyConnHandler, (void*)1);
testCond((s == NATS_OK)
&& (opts->retryOnFailedConnect == true)
&& (opts->connectedCb == _dummyConnHandler)
&& (opts->connectedCbClosure == (void*) 1));
test("Remove RetryOnFailedConnect: ");
// If `retry` is false, connect CB and closure are ignored and should
// be internally set to NULL.
s = natsOptions_SetRetryOnFailedConnect(opts, false, _dummyConnHandler, (void*)1);
testCond((s == NATS_OK)
&& (opts->retryOnFailedConnect == false)
&& (opts->connectedCb == NULL)
&& (opts->connectedCbClosure == NULL));
test("Set Secure: ");
s = natsOptions_SetSecure(opts, true);
#if defined(NATS_HAS_TLS)
testCond((s == NATS_OK) && (opts->secure == true));
#else
testCond((s == NATS_ILLEGAL_STATE) && (opts->secure == false));
#endif
test("Remove Secure: ");
s = natsOptions_SetSecure(opts, false);
#if defined(NATS_HAS_TLS)
testCond((s == NATS_OK) && (opts->secure == false));
#else
testCond((s == NATS_ILLEGAL_STATE) && (opts->secure == false));
#endif
test("Set Pedantic: ");
s = natsOptions_SetPedantic(opts, true);
testCond((s == NATS_OK) && (opts->pedantic == true));
test("Remove Pedantic: ");
s = natsOptions_SetPedantic(opts, false);
testCond((s == NATS_OK) && (opts->pedantic == false));
test("Set Ping Interval (negative or 0 ok): ");
s = natsOptions_SetPingInterval(opts, -1000);
if ((s == NATS_OK) && (opts->pingInterval != -1000))
s = NATS_ERR;
IFOK(s, natsOptions_SetPingInterval(opts, 0));
if ((s == NATS_OK) && (opts->pingInterval != 0))
s = NATS_ERR;
IFOK(s, natsOptions_SetPingInterval(opts, 1000));
testCond((s == NATS_OK) && (opts->pingInterval == 1000));
test("Set MaxPingsOut: ");
s = natsOptions_SetMaxPingsOut(opts, -2);
IFOK(s, natsOptions_SetMaxPingsOut(opts, 0));
IFOK(s, natsOptions_SetMaxPingsOut(opts, 1));
IFOK(s, natsOptions_SetMaxPingsOut(opts, 10));
testCond((s == NATS_OK) && (opts->maxPingsOut == 10));
test("Set IOBufSize: ");
s = natsOptions_SetIOBufSize(opts, -1);
if ((s != NATS_OK) && (opts->ioBufSize == NATS_OPTS_DEFAULT_IO_BUF_SIZE))
s = natsOptions_SetIOBufSize(opts, 0);
if ((s == NATS_OK) && (opts->ioBufSize == 0))
s = natsOptions_SetIOBufSize(opts, 1024 * 1024);
testCond((s == NATS_OK) && (opts->ioBufSize == 1024 * 1024));
test("Set AllowReconnect: ");
s = natsOptions_SetAllowReconnect(opts, true);
testCond((s == NATS_OK) && (opts->allowReconnect == true));
test("Remove AllowReconnect: ");
s = natsOptions_SetAllowReconnect(opts, false);
testCond((s == NATS_OK) && (opts->allowReconnect == false));
test("Set MaxReconnect (negative ok): ");
s = natsOptions_SetMaxReconnect(opts, -10);
if ((s == NATS_OK) && (opts->maxReconnect != -10))
s = NATS_ERR;
IFOK(s, natsOptions_SetMaxReconnect(opts, 0));
if ((s == NATS_OK) && (opts->maxReconnect != 0))
s = NATS_ERR;
IFOK(s, natsOptions_SetMaxReconnect(opts, 10));
testCond((s == NATS_OK) && (opts->maxReconnect == 10));
test("Set Reconnect Wait (invalid args: ");
s = natsOptions_SetReconnectWait(opts, -1000);
testCond(s != NATS_OK);
test("Set Reconnect Wait: ");
s = natsOptions_SetReconnectWait(opts, 1000);
testCond((s == NATS_OK) && (opts->reconnectWait == 1000));
test("Remove Reconnect Wait: ");
s = natsOptions_SetReconnectWait(opts, 0);
testCond((s == NATS_OK) && (opts->reconnectWait == 0));
test("Set Max Pending Msgs (invalid args: ");
s = natsOptions_SetMaxPendingMsgs(opts, -1000);
if (s != NATS_OK)
s = natsOptions_SetMaxPendingMsgs(opts, 0);
testCond(s != NATS_OK);
test("Set Max Pending Msgs : ");
s = natsOptions_SetMaxPendingMsgs(opts, 10000);
testCond((s == NATS_OK) && (opts->maxPendingMsgs == 10000));
test("Set Error Handler: ");
s = natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL);
testCond((s == NATS_OK) && (opts->asyncErrCb == _dummyErrHandler));
test("Remove Error Handler: ");
s = natsOptions_SetErrorHandler(opts, NULL, NULL);
testCond((s == NATS_OK) && (opts->asyncErrCb == natsConn_defaultErrHandler));
test("Set ClosedCB: ");
s = natsOptions_SetClosedCB(opts, _dummyConnHandler, NULL);
testCond((s == NATS_OK) && (opts->closedCb == _dummyConnHandler));
test("Remove ClosedCB: ");
s = natsOptions_SetClosedCB(opts, NULL, NULL);
testCond((s == NATS_OK) && (opts->closedCb == NULL));
test("Set DisconnectedCB: ");
s = natsOptions_SetDisconnectedCB(opts, _dummyConnHandler, NULL);
testCond((s == NATS_OK) && (opts->disconnectedCb == _dummyConnHandler));
test("Remove DisconnectedCB: ");
s = natsOptions_SetDisconnectedCB(opts, NULL, NULL);
testCond((s == NATS_OK) && (opts->disconnectedCb == NULL));
test("Set ReconnectedCB: ");
s = natsOptions_SetReconnectedCB(opts, _dummyConnHandler, NULL);
testCond((s == NATS_OK) && (opts->reconnectedCb == _dummyConnHandler));
test("Remove ReconnectedCB: ");
s = natsOptions_SetReconnectedCB(opts, NULL, NULL);
testCond((s == NATS_OK) && (opts->reconnectedCb == NULL));
test("Set UserInfo: ");
s = natsOptions_SetUserInfo(opts, "ivan", "pwd");
testCond((s == NATS_OK)
&& (strcmp(opts->user, "ivan") == 0)
&& (strcmp(opts->password, "pwd") == 0));
test("Remove UserInfo: ");
s = natsOptions_SetUserInfo(opts, NULL, NULL);
testCond((s == NATS_OK) && (opts->user == NULL) && (opts->password == NULL));
test("Set Token: ");
s = natsOptions_SetToken(opts, "token");
testCond((s == NATS_OK) && (strcmp(opts->token, "token") == 0));
test("Remove Token: ");
s = natsOptions_SetToken(opts, NULL);
testCond((s == NATS_OK) && (opts->token == NULL));
test("Set TokenHandler: ");
s = natsOptions_SetTokenHandler(opts, _dummyTokenHandler, NULL);
testCond((s == NATS_OK) && (opts->tokenCb == _dummyTokenHandler)
&& (strcmp(opts->tokenCb(NULL), "token") == 0));
test("Remove TokenHandler: ");
s = natsOptions_SetTokenHandler(opts, NULL, NULL);
testCond((s == NATS_OK) && (opts->tokenCb == NULL));
test("Set write deadline: ");
s = natsOptions_SetWriteDeadline(opts, 1000);
testCond((s == NATS_OK) && (opts->writeDeadline == 1000));
test("Remove write deadline: ");
s = natsOptions_SetWriteDeadline(opts, 0);
testCond((s == NATS_OK) && (opts->writeDeadline == 0));
test("IP order invalid values: ");
s = natsOptions_IPResolutionOrder(opts, -1);
if (s != NATS_OK)
s = natsOptions_IPResolutionOrder(opts, 1);
if (s != NATS_OK)
s = natsOptions_IPResolutionOrder(opts, 466);
if (s != NATS_OK)
s = natsOptions_IPResolutionOrder(opts, 644);
testCond(s != NATS_OK);
test("IP order valid values: ");
s = natsOptions_IPResolutionOrder(opts, 0);
if ((s == NATS_OK) && (opts->orderIP == 0))
s = natsOptions_IPResolutionOrder(opts, 4);
if ((s == NATS_OK) && (opts->orderIP == 4))
s = natsOptions_IPResolutionOrder(opts, 6);
if ((s == NATS_OK) && (opts->orderIP == 6))
s = natsOptions_IPResolutionOrder(opts, 46);
if ((s == NATS_OK) && (opts->orderIP == 46))
s = natsOptions_IPResolutionOrder(opts, 64);
testCond((s == NATS_OK) && (opts->orderIP == 64));
test("Set UseOldRequestStyle: ");
s = natsOptions_UseOldRequestStyle(opts, true);
testCond((s == NATS_OK) && (opts->useOldRequestStyle == true));
test("Remove UseOldRequestStyle: ");
s = natsOptions_UseOldRequestStyle(opts, false);
testCond((s == NATS_OK) && (opts->useOldRequestStyle == false));
test("Set SendAsap: ");
s = natsOptions_SetSendAsap(opts, true);
testCond((s == NATS_OK) && (opts->sendAsap == true));
test("Remove SendAsap: ");
s = natsOptions_SetSendAsap(opts, false);
testCond((s == NATS_OK) && (opts->sendAsap == false));
test("Set UserCreds: ");
s = natsOptions_SetUserCredentialsCallbacks(opts, _dummyUserJWTCb, (void*) 1, _dummySigCb, (void*) 2);
testCond((s == NATS_OK)
&& (opts->userJWTHandler == _dummyUserJWTCb)
&& (opts->userJWTClosure == (void*) 1)
&& (opts->sigHandler == _dummySigCb)
&& (opts->sigClosure == (void*) 2));
test("Remove UserCreds: ");
s = natsOptions_SetUserCredentialsCallbacks(opts, NULL, NULL, NULL, NULL);
testCond((s == NATS_OK)
&& (opts->userJWTHandler == NULL)
&& (opts->userJWTClosure == NULL)
&& (opts->sigHandler == NULL)
&& (opts->sigClosure == NULL));
test("Set UserCredsFromFile: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar");
testCond((s == NATS_OK)
&& (opts->userCreds != NULL)
&& (strcmp(opts->userCreds->userOrChainedFile, "foo") == 0)
&& (strcmp(opts->userCreds->seedFile, "bar") == 0)
&& (opts->userCreds->jwtAndSeedContent == NULL)
&& (opts->userJWTHandler == natsConn_userCreds)
&& (opts->userJWTClosure == (void*) opts->userCreds)
&& (opts->sigHandler == natsConn_signatureHandler)
&& (opts->sigClosure == (void*) opts->userCreds));
test("Remove UserCredsFromFile: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, NULL, NULL);
testCond((s == NATS_OK)
&& (opts->userCreds == NULL)
&& (opts->userJWTHandler == NULL)
&& (opts->userJWTClosure == NULL)
&& (opts->sigHandler == NULL)
&& (opts->sigClosure == NULL));
test("Set UserCredsFromMemory: ");
const char *jwtAndSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n";
s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed);
testCond((s == NATS_OK)
&& (opts->userCreds != NULL)
&& (opts->userCreds->userOrChainedFile == NULL)
&& (opts->userCreds->seedFile == NULL)
&& (strcmp(opts->userCreds->jwtAndSeedContent, jwtAndSeed) == 0)
&& (opts->userJWTHandler == natsConn_userCreds)
&& (opts->userJWTClosure == (void*) opts->userCreds)
&& (opts->sigHandler == natsConn_signatureHandler)
&& (opts->sigClosure == (void*) opts->userCreds));
test("Remove UserCredsFromMemory: ");
s = natsOptions_SetUserCredentialsFromMemory(opts, NULL);
testCond((s == NATS_OK)
&& (opts->userCreds == NULL)
&& (opts->userJWTHandler == NULL)
&& (opts->userJWTClosure == NULL)
&& (opts->sigHandler == NULL)
&& (opts->sigClosure == NULL));
test("Set NKey: ");
s = natsOptions_SetNKey(opts, "pubkey", _dummySigCb, (void*) 1);
testCond((s == NATS_OK)
&& (opts->nkey != NULL)
&& (strcmp(opts->nkey, "pubkey") == 0)
&& (opts->sigHandler == _dummySigCb)
&& (opts->sigClosure == (void*) 1));
test("Remove NKey: ");
s = natsOptions_SetNKey(opts, NULL, NULL, NULL);
testCond((s == NATS_OK)
&& (opts->nkey == NULL)
&& (opts->sigHandler == NULL)
&& (opts->sigClosure == NULL));
test("Set NKeyFromSeed: ");
s = natsOptions_SetNKeyFromSeed(opts, "pubkey", "seed.file");
testCond((s == NATS_OK)
&& (opts->nkey != NULL)
&& (strcmp(opts->nkey, "pubkey") == 0)
&& (opts->sigHandler == natsConn_signatureHandler)
&& (opts->sigClosure == (void*) opts->userCreds)
&& (opts->userCreds != NULL)
&& (opts->userCreds->seedFile != NULL)
&& (strcmp(opts->userCreds->seedFile, "seed.file") == 0));
test("Remove NKeyFromSeed: ");
s = natsOptions_SetNKeyFromSeed(opts, NULL, NULL);
testCond((s == NATS_OK)
&& (opts->nkey == NULL)
&& (opts->sigHandler == NULL)
&& (opts->sigClosure == NULL)
&& (opts->userCreds == NULL));
test("Disable no responders: ");
s = natsOptions_DisableNoResponders(opts, true);
testCond((s == NATS_OK) && opts->disableNoResponders);
test("Enable no responders: ");
s = natsOptions_DisableNoResponders(opts, false);
testCond((s == NATS_OK) && !opts->disableNoResponders);
test("Set custom inbox prefix: ");
s = natsOptions_SetCustomInboxPrefix(opts, "my.prefix");
testCond((s == NATS_OK)
&& (opts->inboxPfx != NULL)
&& (strcmp(opts->inboxPfx, "my.prefix.") == 0));
test("Clear custom inbox prefix: ");
s = natsOptions_SetCustomInboxPrefix(opts, NULL);
if ((s == NATS_OK) && (opts->inboxPfx == NULL))
s = natsOptions_SetCustomInboxPrefix(opts, "");
testCond((s == NATS_OK) && (opts->inboxPfx == NULL));
test("Set ignore discovered servers: ");
s = natsOptions_SetIgnoreDiscoveredServers(opts, true);
testCond((s == NATS_OK) && opts->ignoreDiscoveredServers);
test("Reset ignore discovered servers: ");
s = natsOptions_SetIgnoreDiscoveredServers(opts, false);
testCond((s == NATS_OK) && !opts->ignoreDiscoveredServers);
// Prepare some values for the clone check
s = natsOptions_SetURL(opts, "url");
IFOK(s, natsOptions_SetServers(opts, servers, 3));
IFOK(s, natsOptions_SetName(opts, "name"));
IFOK(s, natsOptions_SetPingInterval(opts, 3000));
IFOK(s, natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL));
IFOK(s, natsOptions_SetUserInfo(opts, "ivan", "pwd"));
IFOK(s, natsOptions_SetToken(opts, "token"));
IFOK(s, natsOptions_IPResolutionOrder(opts, 46));
IFOK(s, natsOptions_SetNoEcho(opts, true));
IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, true, _dummyConnHandler, NULL));
if (s != NATS_OK)
FAIL("Unable to test natsOptions_clone() because of failure while setting");
test("Cloning: ");
s = NATS_OK;
cloned = natsOptions_clone(opts);
if (cloned == NULL)
s = NATS_NO_MEMORY;
else if ((cloned->pingInterval != 3000)
|| (cloned->asyncErrCb != _dummyErrHandler)
|| (cloned->name == NULL)
|| (strcmp(cloned->name, "name") != 0)
|| (cloned->url == NULL)
|| (strcmp(cloned->url, "url") != 0)
|| (cloned->servers == NULL)
|| (cloned->serversCount != 3)
|| (strcmp(cloned->user, "ivan") != 0)
|| (strcmp(cloned->password, "pwd") != 0)
|| (strcmp(cloned->token, "token") != 0)
|| (cloned->orderIP != 46)
|| (!cloned->noEcho)
|| (!cloned->retryOnFailedConnect)
|| (cloned->connectedCb != _dummyConnHandler))
{
s = NATS_ERR;
}
if (s == NATS_OK)
{
for (int i=0; i<3; i++)
{
if (strcmp(cloned->servers[i], servers[i]) != 0)
{
s = NATS_ERR;
break;
}
}
}
testCond(s == NATS_OK);
test("Destroy original does not affect clone: ");
natsOptions_Destroy(opts);
testCond((cloned != NULL)
&& (cloned->url != NULL)
&& (strcmp(cloned->url, "url") == 0));
natsOptions_Destroy(cloned);
opts = NULL;
cloned = NULL;
test("If original has default err handler, cloned has it too: ");
s = natsOptions_Create(&opts);
if (s == NATS_OK)
cloned = natsOptions_clone(opts);
testCond((s == NATS_OK) && (cloned != NULL)
&& (cloned->asyncErrCb == natsConn_defaultErrHandler)
&& (cloned->asyncErrCbClosure == NULL));
natsOptions_Destroy(cloned);
natsOptions_Destroy(opts);
}
static void
test_natsSock_ReadLine(void)
{
char buffer[20];
natsStatus s;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
snprintf(buffer, sizeof(buffer), "%s", "+OK\r\nPONG\r\nFOO\r\nxxx");
buffer[3] = '\0';
test("Read second line from buffer: ");
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
testCond((s == NATS_OK) && (strcmp(buffer, "PONG") == 0));
test("Read third line from buffer: ");
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
testCond((s == NATS_OK) && (strcmp(buffer, "FOO") == 0));
test("Next call should trigger recv, which is expected to fail: ");
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
testCond(s != NATS_OK);
}
static natsStatus
_dummyJSONCb(void *userInfo, const char *fieldName, nats_JSONField *f)
{
if (userInfo == NULL)
return NATS_OK;
if (strcmp(fieldName, "fail") == 0)
return nats_setError(NATS_INVALID_ARG, "%s", "on purpose");
*(uint64_t*)userInfo = f->value.vuint;
return NATS_OK;
}
static void
test_natsJSON(void)
{
natsStatus s;
nats_JSON *json = NULL;
char buf[256];
int i;
int intVal = 0;
int64_t longVal = 0;
char *strVal = NULL;
bool boolVal = false;
long double doubleVal = 0;
char **arrVal = NULL;
bool *arrBoolVal = NULL;
long double *arrDoubleVal = NULL;
int *arrIntVal = NULL;
int64_t *arrLongVal = NULL;
uint64_t *arrULongVal = NULL;
nats_JSON **arrObjVal = NULL;
nats_JSONArray **arrArrVal = NULL;
int arrCount = 0;
uint64_t ulongVal = 0;
nats_JSON *obj1 = NULL;
nats_JSON *obj2 = NULL;
nats_JSON *obj3 = NULL;
int32_t int32Val = 0;
uint16_t uint16Val = 0;
const char *wrong[] = {
"{",
"}",
"{start quote missing\":0}",
"{\"end quote missing: 0}",
"{\"test\":start quote missing\"}",
"{\"test\":\"end quote missing}",
"{\"test\":1.2x}",
"{\"test\":tRUE}",
"{\"test\":true,}",
"{\"test\":true}, xxx}",
"{\"test\": \"abc\\error here\"}",
"{\"test\": \"abc\\u123\"}",
"{\"test\": \"abc\\u123g\"}",
"{\"test\": \"abc\\u 23f\"}",
("{\"test\": \"abc\\"""),
"{\"test\": \"abc\\u1234",
"{\"test\": \"abc\\uabc",
"{\"test\" \"separator missing\"}",
"{\"test\":[1, \"abc\", true]}",
};
const char *good[] = {
"{}",
" {}",
" { }",
" { } ",
"{ \"test\":{}}",
"{ \"test\":1.2}",
"{ \"test\" :1.2}",
"{ \"test\" : 1.2}",
"{ \"test\" : 1.2 }",
"{ \"test\" : 1.2,\"test2\":1}",
"{ \"test\" : 1.2, \"test2\":1}",
"{ \"test\":0}",
"{ \"test\" :0}",
"{ \"test\" : 0}",
"{ \"test\" : 0 }",
"{ \"test\" : 0,\"test2\":1}",
"{ \"test\" : 0, \"test2\":1}",
"{ \"test\":true}",
"{ \"test\": true}",
"{ \"test\": true }",
"{ \"test\":true,\"test2\":1}",
"{ \"test\": true,\"test2\":1}",
"{ \"test\": true ,\"test2\":1}",
"{ \"test\":false}",
"{ \"test\": false}",
"{ \"test\": false }",
"{ \"test\":false,\"test2\":1}",
"{ \"test\": false,\"test2\":1}",
"{ \"test\": false ,\"test2\":1}",
"{ \"test\":\"abc\"}",
"{ \"test\": \"abc\"}",
"{ \"test\": \"abc\" }",
"{ \"test\":\"abc\",\"test2\":1}",
"{ \"test\": \"abc\",\"test2\":1}",
"{ \"test\": \"abc\" ,\"test2\":1}",
"{ \"test\": \"a\\\"b\\\"c\" }",
"{ \"test\": [\"a\", \"b\", \"c\"]}",
"{ \"test\": [\"a\\\"b\\\"c\"]}",
"{ \"test\": [\"abc,def\"]}",
"{ \"test\": [{\"a\": 1}, {\"b\": \"c\"}]}",
"{ \"test\": [[{\"a\": 1}], [{\"b\": \"c\"}]]}",
"{ \"test\": []}",
"{ \"test\": {\"inner\":\"a\",\"inner2\":2,\"inner3\":false,\"inner4\":{\"inner_inner1\" : 1.234}}}",
"{ \"test\": \"a\\\"b\\\"c\"}",
"{ \"test\": \"\\\"\\\\/\b\f\n\r\t\\uabcd\"}",
"{ \"test\": \"\\ua12f\"}",
"{ \"test\": \"\\uA01F\"}",
"{ \"test\": null}",
};
nats_JSONField *f = NULL;
unsigned char *bytes = NULL;
int bl = 0;
for (i=0; i<(int)(sizeof(wrong)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "Negative test %d: ", (i+1));
test(buf);
s = nats_JSONParse(&json, wrong[i], -1);
testCond((s != NATS_OK) && (json == NULL));
json = NULL;
}
nats_clearLastError();
for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "Positive test %d: ", (i+1));
test(buf);
s = nats_JSONParse(&json, good[i], -1);
testCond((s == NATS_OK) && (json != NULL));
nats_JSONDestroy(json);
json = NULL;
}
nats_clearLastError();
// Check values
test("Empty string: ");
s = nats_JSONParse(&json, "{}", -1);
IFOK(s, nats_JSONGetInt(json, "test", &intVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 0)
&& (intVal == 0));
nats_JSONDestroy(json);
json = NULL;
test("Single field, string: ");
s = nats_JSONParse(&json, "{\"test\":\"abc\"}", -1);
IFOK(s, nats_JSONGetStr(json, "test", &strVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (strcmp(strVal, "abc") == 0));
nats_JSONDestroy(json);
json = NULL;
free(strVal);
strVal = NULL;
test("Single field, string with escape chars: ");
s = nats_JSONParse(&json, "{\"test\":\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"}", -1);
IFOK(s, nats_JSONGetStr(json, "test", &strVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (strcmp(strVal, "\"\\/\b\f\n\r\t") == 0));
nats_JSONDestroy(json);
json = NULL;
free(strVal);
strVal = NULL;
test("Single field, string with unicode: ");
s = nats_JSONParse(&json, "{\"test\":\"\\u0026\\u003c\\u003e\"}", -1);
IFOK(s, nats_JSONGetStr(json, "test", &strVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (strcmp(strVal, "&<>") == 0));
nats_JSONDestroy(json);
json = NULL;
free(strVal);
strVal = NULL;
test("Single field, int: ");
s = nats_JSONParse(&json, "{\"test\":1234}", -1);
IFOK(s, nats_JSONGetInt(json, "test", &intVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (intVal == 1234));
nats_JSONDestroy(json);
json = NULL;
intVal = 0;
test("Single field, int32: ");
s = nats_JSONParse(&json, "{\"test\":1234}", -1);
IFOK(s, nats_JSONGetInt32(json, "test", &int32Val));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (int32Val == 1234));
nats_JSONDestroy(json);
json = NULL;
int32Val = 0;
test("Single field, uint16: ");
s = nats_JSONParse(&json, "{\"test\":1234}", -1);
IFOK(s, nats_JSONGetUInt16(json, "test", &uint16Val));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (uint16Val == 1234));
nats_JSONDestroy(json);
json = NULL;
uint16Val = 0;
test("Single field, long: ");
s = nats_JSONParse(&json, "{\"test\":9223372036854775807}", -1);
IFOK(s, nats_JSONGetLong(json, "test", &longVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (longVal == 9223372036854775807L));
nats_JSONDestroy(json);
json = NULL;
longVal = 0;
test("Single field, neg long: ");
s = nats_JSONParse(&json, "{\"test\":-9223372036854775808}", -1);
IFOK(s, nats_JSONGetLong(json, "test", &longVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (longVal == (int64_t) 0x8000000000000000));
nats_JSONDestroy(json);
json = NULL;
longVal = 0;
test("Single field, neg long as ulong: ");
s = nats_JSONParse(&json, "{\"test\":-123456789}", -1);
IFOK(s, nats_JSONGetULong(json, "test", &ulongVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (ulongVal == 0xFFFFFFFFF8A432EB));
nats_JSONDestroy(json);
json = NULL;
ulongVal = 0;
test("Single field, ulong: ");
s = nats_JSONParse(&json, "{\"test\":18446744073709551615}", -1);
IFOK(s, nats_JSONGetULong(json, "test", &ulongVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (ulongVal == 0xFFFFFFFFFFFFFFFF));
nats_JSONDestroy(json);
json = NULL;
ulongVal = 0;
test("Single field, ulong: ");
s = nats_JSONParse(&json, "{\"test\":9007199254740993}", -1);
IFOK(s, nats_JSONGetULong(json, "test", &ulongVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (ulongVal == 9007199254740993));
nats_JSONDestroy(json);
json = NULL;
ulongVal = 0;
test("Single field, double: ");
s = nats_JSONParse(&json, "{\"test\":1234.5e3}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 1234.5e+3));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double negative: ");
s = nats_JSONParse(&json, "{\"test\":-1234}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) -1234));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp negative 1: ");
s = nats_JSONParse(&json, "{\"test\":1234e-3}", -1);
IFOK(s, nats_JSONGetDouble(json, "test",&doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 1234.0/1000.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp negative 2: ");
s = nats_JSONParse(&json, "{\"test\":1234.5e-3}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345.0/10000.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp negative 3: ");
s = nats_JSONParse(&json, "{\"test\":1234.5e-1}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345.0/100.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp negative 4: ");
s = nats_JSONParse(&json, "{\"test\":1234.5e-0}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345.0/10.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp positive 1: ");
s = nats_JSONParse(&json, "{\"test\":1234e+3}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 1234.0*1000));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp positive 2: ");
s = nats_JSONParse(&json, "{\"test\":1234.5e+3}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345.0*100.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp positive 3: ");
s = nats_JSONParse(&json, "{\"test\":1234.5678e+2}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345678.0/100.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp positive 4: ");
s = nats_JSONParse(&json, "{\"test\":1234.5678e+4}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345678.0/10000.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp positive 5: ");
s = nats_JSONParse(&json, "{\"test\":1234.5678e+5}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345678.0*10.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp positive 6: ");
s = nats_JSONParse(&json, "{\"test\":1234.5678e+0}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345678.0/10000.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, double exp positive 6: ");
s = nats_JSONParse(&json, "{\"test\":1234.5678e1}", -1);
IFOK(s, nats_JSONGetDouble(json, "test", &doubleVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (doubleVal == (long double) 12345678.0/1000.0));
nats_JSONDestroy(json);
json = NULL;
doubleVal = 0;
test("Single field, bool: ");
s = nats_JSONParse(&json, "{\"test\":true}", -1);
IFOK(s, nats_JSONGetBool(json, "test", &boolVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& boolVal);
nats_JSONDestroy(json);
json = NULL;
boolVal = false;
test("Single field, string array: ");
s = nats_JSONParse(&json, "{\"test\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}", -1);
IFOK(s, nats_JSONGetArrayStr(json, "test", &arrVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 5)
&& (strcmp(arrVal[0], "a") == 0)
&& (strcmp(arrVal[1], "b") == 0)
&& (strcmp(arrVal[2], "c") == 0)
&& (strcmp(arrVal[3], "d") == 0)
&& (strcmp(arrVal[4], "e") == 0));
nats_JSONDestroy(json);
json = NULL;
for (i=0; i<arrCount; i++)
free(arrVal[i]);
free(arrVal);
arrVal = NULL;
arrCount = 0;
test("Single field, null string array: ");
s = nats_JSONParse(&json, "{\"test\": null}", -1);
IFOK(s, nats_JSONGetArrayStr(json, "test", &arrVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrVal == NULL)
&& (arrCount == 0));
nats_JSONDestroy(json);
json = NULL;
test("Single field, bool array: ");
s = nats_JSONParse(&json, "{\"test\":[true, false, true]}", -1);
IFOK(s, nats_JSONGetArrayBool(json, "test", &arrBoolVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 3)
&& arrBoolVal[0]
&& !arrBoolVal[1]
&& arrBoolVal[2]);
nats_JSONDestroy(json);
json = NULL;
free(arrBoolVal);
arrBoolVal = NULL;
arrCount = 0;
test("Single field, double array: ");
s = nats_JSONParse(&json, "{\"test\":[1.0, 2.0, 3.0]}", -1);
IFOK(s, nats_JSONGetArrayDouble(json, "test", &arrDoubleVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 3)
&& (arrDoubleVal[0] == 1.0)
&& (arrDoubleVal[1] == 2.0)
&& (arrDoubleVal[2] == 3.0));
nats_JSONDestroy(json);
json = NULL;
free(arrDoubleVal);
arrDoubleVal = NULL;
arrCount = 0;
test("Single field, int array: ");
s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1);
IFOK(s, nats_JSONGetArrayInt(json, "test", &arrIntVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 3)
&& (arrIntVal[0] == 1)
&& (arrIntVal[1] == 2)
&& (arrIntVal[2] == 3));
nats_JSONDestroy(json);
json = NULL;
free(arrIntVal);
arrIntVal = NULL;
arrCount = 0;
test("Single field, long array: ");
s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1);
IFOK(s, nats_JSONGetArrayLong(json, "test", &arrLongVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 3)
&& (arrLongVal[0] == 1)
&& (arrLongVal[1] == 2)
&& (arrLongVal[2] == 3));
nats_JSONDestroy(json);
json = NULL;
free(arrLongVal);
arrLongVal = NULL;
arrCount = 0;
test("Single field, ulong array: ");
s = nats_JSONParse(&json, "{\"test\":[1, 2, 3]}", -1);
IFOK(s, nats_JSONGetArrayULong(json, "test", &arrULongVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 3)
&& (arrULongVal[0] == 1)
&& (arrULongVal[1] == 2)
&& (arrULongVal[2] == 3));
nats_JSONDestroy(json);
json = NULL;
free(arrULongVal);
arrULongVal = NULL;
arrCount = 0;
test("Single field, object array: ");
s = nats_JSONParse(&json, "{\"test\":[{\"a\": 1},{\"b\": true}]}", -1);
IFOK(s, nats_JSONGetArrayObject(json, "test", &arrObjVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 2)
&& (nats_JSONGetInt(arrObjVal[0], "a", &intVal) == NATS_OK)
&& (intVal == 1)
&& (nats_JSONGetBool(arrObjVal[1], "b", &boolVal) == NATS_OK)
&& boolVal);
nats_JSONDestroy(json);
json = NULL;
free(arrObjVal);
arrObjVal = NULL;
arrCount = 0;
intVal = 0;
boolVal = false;
test("Single field, array null: ");
s = nats_JSONParse(&json, "{\"test\":null}", -1);
IFOK(s, nats_JSONGetArrayObject(json, "test", &arrObjVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrObjVal == NULL)
&& (arrCount == 0));
nats_JSONDestroy(json);
json = NULL;
test("Single field, array array: ");
s = nats_JSONParse(&json, "{\"test\":[[\"a\", \"b\"],[1, 2, 3],[{\"c\": true}]]}", -1);
IFOK(s, nats_JSONGetArrayArray(json, "test", &arrArrVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 3)
&& (nats_JSONArrayAsStrings(arrArrVal[0], &arrVal, &arrCount) == NATS_OK)
&& (arrCount == 2)
&& (strcmp(arrVal[0], "a") == 0)
&& (strcmp(arrVal[1], "b") == 0)
&& (nats_JSONArrayAsInts(arrArrVal[1], &arrIntVal, &arrCount) == NATS_OK)
&& (arrCount == 3)
&& (arrIntVal[0] == 1)
&& (arrIntVal[1] == 2)
&& (arrIntVal[2] == 3)
&& (nats_JSONArrayAsObjects(arrArrVal[2], &arrObjVal, &arrCount) == NATS_OK)
&& (arrCount == 1)
&& (nats_JSONGetBool(arrObjVal[0], "c", &boolVal) == NATS_OK)
&& boolVal);
nats_JSONDestroy(json);
json = NULL;
for (i=0; i<2; i++)
free(arrVal[i]);
free(arrVal);
arrVal = NULL;
free(arrIntVal);
arrIntVal = NULL;
free(arrArrVal);
arrArrVal = NULL;
free(arrObjVal);
arrObjVal = NULL;
boolVal = false;
arrCount = 0;
test("Object: ");
s = nats_JSONParse(&json, "{\"obj1\":{\"obj2\":{\"obj3\":{\"a\": 1},\"b\":true},\"c\":1.2},\"d\":3}", -1);
IFOK(s, nats_JSONGetObject(json, "obj1", &obj1));
IFOK(s, nats_JSONGetObject(obj1, "obj2", &obj2));
IFOK(s, nats_JSONGetObject(obj2, "obj3", &obj3));
IFOK(s, nats_JSONGetInt(obj3, "a", &intVal));
IFOK(s, nats_JSONGetBool(obj2, "b", &boolVal));
IFOK(s, nats_JSONGetDouble(obj1, "c", &doubleVal));
IFOK(s, nats_JSONGetLong(json, "d", &longVal));
testCond((s == NATS_OK)
&& (intVal == 1)
&& boolVal
&& (doubleVal == (long double) 12.0/10.0)
&& (longVal == 3));
nats_JSONDestroy(json);
json = NULL;
intVal = 0;
boolVal = false;
doubleVal = 0.0;
longVal = 0;
test("Object, null: ");
s = nats_JSONParse(&json, "{\"obj\":null}", -1);
IFOK(s, nats_JSONGetObject(json, "obj", &obj1));
testCond((s == NATS_OK) && (obj1 == NULL));
nats_JSONDestroy(json);
json = NULL;
test("All field types: ");
s = nats_JSONParse(&json, "{\"bool\":true,\"str\":\"abc\",\"int\":123,\"long\":456,\"double\":123.5,\"array\":[\"a\"]}", -1);
IFOK(s, nats_JSONGetBool(json, "bool", &boolVal));
IFOK(s, nats_JSONGetStr(json, "str", &strVal));
IFOK(s, nats_JSONGetInt(json, "int", &intVal));
IFOK(s, nats_JSONGetLong(json, "long", &longVal));
IFOK(s, nats_JSONGetDouble(json, "double", &doubleVal));
IFOK(s, nats_JSONGetArrayStr(json, "array", &arrVal, &arrCount));
testCond((s == NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 6)
&& boolVal
&& (strcmp(strVal, "abc") == 0)
&& (intVal == 123)
&& (longVal == 456)
&& (doubleVal == (long double) 1235.0/10.0)
&& (arrCount == 1)
&& (strcmp(arrVal[0], "a") == 0));
test("Unknown field type: ");
if (s == NATS_OK)
s = nats_JSONGetField(json, "int", 255, &f);
testCond(s != NATS_OK);
nats_JSONDestroy(json);
json = NULL;
free(strVal);
strVal = NULL;
boolVal = false;
intVal = 0;
longVal = 0;
doubleVal = 0;
for (i=0; i<arrCount; i++)
free(arrVal[i]);
free(arrVal);
arrVal = NULL;
arrCount = 0;
test("Ask for wrong type: ");
s = nats_JSONParse(&json, "{\"test\":true}", -1);
IFOK(s, nats_JSONGetInt(json, "test", &intVal));
testCond((s != NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (intVal == 0));
nats_JSONDestroy(json);
json = NULL;
test("Ask for wrong type (array): ");
s = nats_JSONParse(&json, "{\"test\":[\"a\", \"b\"]}", -1);
IFOK(s, nats_JSONGetArrayField(json, "test", TYPE_INT, &f));
testCond((s != NATS_OK)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (arrCount == 0)
&& (arrVal == NULL));
nats_JSONDestroy(json);
json = NULL;
test("Ask for unknown type: ");
s = nats_JSONParse(&json, "{\"test\":true}", -1);
IFOK(s, nats_JSONGetField(json, "test", 9999, &f));
testCond((s == NATS_INVALID_ARG)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1));
nats_JSONDestroy(json);
json = NULL;
test("Ask for unknown type (array): ");
s = nats_JSONParse(&json, "{\"test\":true}", -1);
IFOK(s, nats_JSONGetArrayField(json, "test", 9999, &f));
testCond((s == NATS_INVALID_ARG)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1));
nats_JSONDestroy(json);
json = NULL;
test("Check no error and set to default for vars for unknown fields: ");
{
const char *initStr = "test";
const char *initStrArr[] = {"a", "b"};
strVal = (char*) initStr;
boolVal = true;
intVal = 123;
longVal = 456;
doubleVal = 789;
arrVal = (char**)initStrArr;
arrCount = 2;
s = nats_JSONParse(&json, "{\"test\":true}", -1);
IFOK(s, nats_JSONGetStr(json, "str", &strVal));
IFOK(s, nats_JSONGetInt(json, "int", &intVal));
IFOK(s, nats_JSONGetLong(json, "long", &longVal));
IFOK(s, nats_JSONGetBool(json, "bool", &boolVal));
IFOK(s, nats_JSONGetDouble(json, "bool", &doubleVal));
IFOK(s, nats_JSONGetArrayStr(json, "array", &arrVal, &arrCount));
testCond((s == NATS_OK)
&& (strVal == NULL)
&& (boolVal == false)
&& (intVal == 0)
&& (longVal == 0)
&& (doubleVal == 0)
&& (arrCount == 0)
&& (arrVal == NULL));
nats_JSONDestroy(json);
json = NULL;
}
test("Wrong string type: ");
strVal = NULL;
s = nats_JSONParse(&json, "{\"test\":12345678901112}", -1);
IFOK(s, nats_JSONGetStr(json, "test", &strVal));
testCond((s == NATS_INVALID_ARG)
&& (json != NULL)
&& (json->fields != NULL)
&& (json->fields->used == 1)
&& (strVal == NULL));
nats_JSONDestroy(json);
json = NULL;
test("NULL string with -1 len: ");
s = nats_JSONParse(&json, NULL, -1);
testCond((s == NATS_INVALID_ARG) && (json == NULL));
nats_clearLastError();
test("Field reused: ");
s = nats_JSONParse(&json, "{\"field\":1,\"field\":2}", -1);
IFOK(s, nats_JSONGetInt(json, "field", &intVal));
testCond((s == NATS_OK) && (intVal == 2));
nats_JSONDestroy(json);
json = NULL;
test("Nested arrays ok: ");
jsonMaxNested = 10;
s = nats_JSONParse(&json, "{\"test\":[[[1, 2]]]}", -1);
testCond(s == NATS_OK);
nats_JSONDestroy(json);
json = NULL;
test("Nested arrays not ok: ");
jsonMaxNested = 10;
s = nats_JSONParse(&json, "{\"test\":[[[[[[[[[[[[[1, 2]]]]]]]]]]]]]}", -1);
testCond((s == NATS_ERR) && (json == NULL)
&& (strstr(nats_GetLastError(NULL), " nested arrays of 10") != NULL));
nats_clearLastError();
test("Nested objects ok: ");
s = nats_JSONParse(&json, "{\"test\":{\"a\":{\"b\":{\"c\":1}}}}", -1);
testCond(s == NATS_OK);
nats_JSONDestroy(json);
json = NULL;
test("Nested arrays not ok: ");
jsonMaxNested = 10;
s = nats_JSONParse(&json, "{\"test\":{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"f\":{\"g\":{\"h\":{\"i\":{\"j\":{\"k\":{\"l\":{\"m\":1}}}}}}}}}}}}}}", -1);
testCond((s == NATS_ERR) && (json == NULL)
&& (strstr(nats_GetLastError(NULL), " nested objects of 10") != NULL));
nats_clearLastError();
jsonMaxNested = JSON_MAX_NEXTED;
// Negative tests
{
const char *badTimes[] = {
"{\"time\":\"too small\"}",
"{\"time\":\"2021-06-23T18:22:00.123456789-08:00X\"}",
"{\"time\":\"2021-06-23T18:22:00X\"}",
"{\"time\":\"2021-06-23T18:22:00-0800\"}",
"{\"time\":\"2021-06-23T18:22:00-08.00\"}",
"{\"time\":\"2021-06-23T18:22:00.Z\"}",
"{\"time\":\"2021-06-23T18:22:00.abcZ\"}",
"{\"time\":\"2021-06-23T18:22:00.abc-08:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1234567890-08:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1234567890Z\"}",
"{\"time\":\"2021-06-23T18:22:00.123-0800\"}",
};
const char *errorsTxt[] = {
"too small",
"too long",
"invalid UTC offset",
"invalid UTC offset",
"invalid UTC offset",
"is invalid",
"is invalid",
"is invalid",
"too long",
"second fraction",
"invalid UTC offset",
};
for (i=0; i<(int)(sizeof(errorsTxt)/sizeof(char*)); i++)
{
longVal = 0;
snprintf(buf, sizeof(buf), "Bad time '%s': ", badTimes[i]);
test(buf);
s = nats_JSONParse(&json, badTimes[i], -1);
IFOK(s, nats_JSONGetTime(json, "time", &longVal));
testCond((s != NATS_OK)
&& (json != NULL)
&& (longVal == 0)
&& (strstr(nats_GetLastError(NULL), errorsTxt[i]) != NULL));
nats_clearLastError();
nats_JSONDestroy(json);
json = NULL;
}
}
// Positive tests
{
const char *goodTimes[] = {
"{\"time\":\"0001-01-01T00:00:00Z\"}",
"{\"time\":\"1970-01-01T01:00:00+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00Z\"}",
"{\"time\":\"2021-06-23T18:22:00.1Z\"}",
"{\"time\":\"2021-06-23T18:22:00.12Z\"}",
"{\"time\":\"2021-06-23T18:22:00.123Z\"}",
"{\"time\":\"2021-06-23T18:22:00.1234Z\"}",
"{\"time\":\"2021-06-23T18:22:00.12345Z\"}",
"{\"time\":\"2021-06-23T18:22:00.123456Z\"}",
"{\"time\":\"2021-06-23T18:22:00.1234567Z\"}",
"{\"time\":\"2021-06-23T18:22:00.12345678Z\"}",
"{\"time\":\"2021-06-23T18:22:00.123456789Z\"}",
"{\"time\":\"2021-06-23T18:22:00-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.12-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.123-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1234-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.12345-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.123456-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1234567-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.12345678-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00.123456789-07:00\"}",
"{\"time\":\"2021-06-23T18:22:00+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.12+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.123+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1234+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.12345+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.123456+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.1234567+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.12345678+01:00\"}",
"{\"time\":\"2021-06-23T18:22:00.123456789+01:00\"}",
};
int64_t results[] = {
0,
0,
1624472520000000000,
1624472520100000000,
1624472520120000000,
1624472520123000000,
1624472520123400000,
1624472520123450000,
1624472520123456000,
1624472520123456700,
1624472520123456780,
1624472520123456789,
1624497720000000000,
1624497720100000000,
1624497720120000000,
1624497720123000000,
1624497720123400000,
1624497720123450000,
1624497720123456000,
1624497720123456700,
1624497720123456780,
1624497720123456789,
1624468920000000000,
1624468920100000000,
1624468920120000000,
1624468920123000000,
1624468920123400000,
1624468920123450000,
1624468920123456000,
1624468920123456700,
1624468920123456780,
1624468920123456789,
};
for (i=0; i<(int)(sizeof(results)/sizeof(int64_t)); i++)
{
longVal = 0;
snprintf(buf, sizeof(buf), "Time '%s' -> %" PRId64 ": ", goodTimes[i], results[i]);
test(buf);
s = nats_JSONParse(&json, goodTimes[i], -1);
IFOK(s, nats_JSONGetTime(json, "time", &longVal));
testCond((s == NATS_OK)
&& (json != NULL)
&& (longVal == results[i]));
nats_JSONDestroy(json);
json = NULL;
}
}
test("GetStr bad type: ");
s = nats_JSONParse(&json, "{\"test\":true}", -1);
IFOK(s, nats_JSONGetStrPtr(json, "test", (const char**) &strVal));
testCond((s != NATS_OK) && (strVal == NULL));
nats_clearLastError();
nats_JSONDestroy(json);
json = NULL;
test("GetStr: ");
s = nats_JSONParse(&json, "{\"test\":\"direct\"}", -1);
IFOK(s, nats_JSONGetStrPtr(json, "test", (const char**) &strVal));
testCond((s == NATS_OK) && (strVal != NULL) && (strcmp(strVal, "direct") == 0));
nats_JSONDestroy(json);
json = NULL;
test("GetBytes bad type: ");
s = nats_JSONParse(&json, "{\"test\":true}", -1);
IFOK(s, nats_JSONGetBytes(json, "test", &bytes, &bl));
testCond((s != NATS_OK) && (bytes == NULL) && (bl == 0));
nats_clearLastError();
nats_JSONDestroy(json);
json = NULL;
test("GetBytes: ");
s = nats_JSONParse(&json, "{\"test\":\"dGhpcyBpcyB0ZXN0aW5nIGJhc2U2NCBlbmNvZGluZw==\"}", -1);
IFOK(s, nats_JSONGetBytes(json, "test", &bytes, &bl));
testCond((s == NATS_OK) && (bytes != NULL) && (bl == 31)
&& (strncmp((const char*) bytes, "this is testing base64 encoding", bl) == 0));
nats_clearLastError();
nats_JSONDestroy(json);
json = NULL;
free(bytes);
test("Range with wrong type: ");
s = nats_JSONParse(&json, "{\"test\":123}", -1);
IFOK(s, nats_JSONRange(json, TYPE_STR, 0, _dummyJSONCb, NULL));
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "expected value type of")));
nats_clearLastError();
test("Range with wrong num type: ");
s = nats_JSONRange(json, TYPE_NUM, TYPE_INT, _dummyJSONCb, NULL);
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "expected numeric type of")));
nats_clearLastError();
test("Range ok: ");
ulongVal = 0;
s = nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _dummyJSONCb, &ulongVal);
testCond((s == NATS_OK) && (ulongVal == 123));
nats_JSONDestroy(json);
json = NULL;
test("Range cb returns error: ");
ulongVal = 0;
s = nats_JSONParse(&json, "{\"fail\":123}", -1);
IFOK(s, nats_JSONRange(json, TYPE_NUM, TYPE_UINT, _dummyJSONCb, &ulongVal));
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), "on purpose")));
nats_clearLastError();
nats_JSONDestroy(json);
json = NULL;
test("Parse empty array: ");
s = nats_JSONParse(&json, "{\"empty\":[]}", -1);
testCond(s == NATS_OK);
test("Get empty array array: ");
s = nats_JSONGetArrayArray(json, "empty", &arrArrVal, &arrCount);
testCond((s == NATS_OK) && (arrArrVal == NULL) && (arrCount == 0));
test("Get empty obj array: ");
s = nats_JSONGetArrayObject(json, "empty", &arrObjVal, &arrCount);
testCond((s == NATS_OK) && (arrObjVal == NULL) && (arrCount == 0));
test("Get empty ulong array: ");
s = nats_JSONGetArrayULong(json, "empty", &arrULongVal, &arrCount);
testCond((s == NATS_OK) && (arrULongVal == NULL) && (arrCount == 0));
test("Get empty long array: ");
s = nats_JSONGetArrayLong(json, "empty", &arrLongVal, &arrCount);
testCond((s == NATS_OK) && (arrLongVal == NULL) && (arrCount == 0));
test("Get empty int array: ");
s = nats_JSONGetArrayInt(json, "empty", &arrIntVal, &arrCount);
testCond((s == NATS_OK) && (arrIntVal == NULL) && (arrCount == 0));
test("Get empty double array: ");
s = nats_JSONGetArrayDouble(json, "empty", &arrDoubleVal, &arrCount);
testCond((s == NATS_OK) && (arrDoubleVal == NULL) && (arrCount == 0));
test("Get empty bool array: ");
s = nats_JSONGetArrayBool(json, "empty", &arrBoolVal, &arrCount);
testCond((s == NATS_OK) && (arrBoolVal == NULL) && (arrCount == 0));
test("Get empty string array: ");
s = nats_JSONGetArrayStr(json, "empty", &arrVal, &arrCount);
testCond((s == NATS_OK) && (arrVal == NULL) && (arrCount == 0));
nats_JSONDestroy(json);
json = NULL;
}
static void
test_natsEncodeTimeUTC(void)
{
natsStatus s;
char buf[36] = {'\0'};
int i;
int64_t times[] = {
0,
1624472520000000000,
1624472520100000000,
1624472520120000000,
1624472520123000000,
1624472520123400000,
1624472520123450000,
1624472520123456000,
1624472520123456700,
1624472520123456780,
1624472520123456789,
};
const char *results[] = {
"0001-01-01T00:00:00Z",
"2021-06-23T18:22:00Z",
"2021-06-23T18:22:00.1Z",
"2021-06-23T18:22:00.12Z",
"2021-06-23T18:22:00.123Z",
"2021-06-23T18:22:00.1234Z",
"2021-06-23T18:22:00.12345Z",
"2021-06-23T18:22:00.123456Z",
"2021-06-23T18:22:00.1234567Z",
"2021-06-23T18:22:00.12345678Z",
"2021-06-23T18:22:00.123456789Z",
};
test("Buffer too small: ");
s = nats_EncodeTimeUTC(buf, 10, 0);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), "too small") != NULL));
nats_clearLastError();
for (i=0; i<(int)(sizeof(times)/sizeof(int64_t)); i++)
{
char txt[100];
snprintf(txt, sizeof(txt), "Time %" PRId64 " -> '%s': ", times[i], results[i]);
test(txt);
s = nats_EncodeTimeUTC(buf, sizeof(buf), times[i]);
testCond((s == NATS_OK) && (strcmp(buf, results[i]) == 0));
}
}
static void
test_natsErrWithLongText(void)
{
natsStatus s;
char errTxt[300];
const char *output = NULL;
int i;
nats_clearLastError();
for (i=0; i<(int) sizeof(errTxt)-1; i++)
errTxt[i] = 'A';
errTxt[i-1] = '\0';
test("nats_setError with long text: ");
s = nats_setError(NATS_ERR, "This is the error: %s", errTxt);
if (s == NATS_ERR)
output = nats_GetLastError(&s);
if (output != NULL)
{
int pos = ((int) strlen(output))-1;
// End of text should contain `...` to indicate that it was truncated.
for (i=0; i<3; i++)
{
if (output[pos--] != '.')
{
s = NATS_ILLEGAL_STATE;
break;
}
}
}
else
{
s = NATS_ILLEGAL_STATE;
}
testCond(s == NATS_ERR);
nats_clearLastError();
}
static void
test_natsErrStackMoreThanMaxFrames(void)
{
int i;
const int total = MAX_FRAMES+10;
char funcName[MAX_FRAMES+10][64];
char result[(MAX_FRAMES+10)*100];
natsStatus s = NATS_OK;
test("Check natsUpdateErrStack called more than MAX_FRAMES: ");
// When a stack trace is formed, it goes from the most recent
// function called to the oldest. We are going to call more than
// MAX_FRAMES with function names being numbers from total down
// to 0. We expect not to crash and have at least from total to
// total-MAX_FRAMES.
for (i=total-1;i>=0;i--)
{
snprintf(funcName[i], sizeof(funcName[i]), "%d", (i+1));
nats_updateErrStack(NATS_ERR, funcName[i]);
}
s = nats_GetLastErrorStack(result, sizeof(result));
if (s == NATS_OK)
{
char *ptr = result;
int funcID;
char expected[64];
snprintf(expected, sizeof(expected), "%d more...", total-MAX_FRAMES);
for (i=total;i>total-MAX_FRAMES;i--)
{
if (sscanf(ptr, "%d", &funcID) != 1)
{
s = NATS_ERR;
break;
}
if (funcID != i)
{
s = NATS_ERR;
break;
}
if (funcID > 10)
ptr += 3;
else
ptr +=2;
}
// The last should be something like: xx more...
// where xx is total-MAX_FRAMES
if ((s == NATS_OK) && (strcmp(ptr, expected) != 0))
s = NATS_ERR;
}
testCond(s == NATS_OK);
}
static void
test_natsMsg(void)
{
natsMsg *msg = NULL;
natsStatus s = NATS_OK;
test("Check invalid subj (NULL): ");
s = natsMsg_Create(&msg, NULL, "reply", "data", 4);
testCond((msg == NULL) && (s == NATS_INVALID_ARG));
test("Check invalid subj (empty): ");
s = natsMsg_Create(&msg, "", "reply", "data", 4);
testCond((msg == NULL) && (s == NATS_INVALID_ARG));
test("Check invalid reply (empty): ");
s = natsMsg_Create(&msg, "foo", "", "data", 4);
testCond((msg == NULL) && (s == NATS_INVALID_ARG));
test("GetSubject with NULL msg: ");
testCond(natsMsg_GetSubject(NULL) == NULL);
test("GetReply with NULL msg: ");
testCond(natsMsg_GetReply(NULL) == NULL);
test("GetData with NULL msg: ");
testCond(natsMsg_GetData(NULL) == NULL);
test("GetDataLength with NULL msg: ");
testCond(natsMsg_GetDataLength(NULL) == 0);
test("Create ok: ");
s = natsMsg_Create(&msg, "foo", "reply", "data", 4);
testCond((s == NATS_OK) && (msg != NULL));
natsMsg_Destroy(msg);
}
static void
test_natsBase32Decode(void)
{
natsStatus s;
const char *src = "KRUGS4ZANFZSA5DIMUQHEZLTOVWHIIDPMYQGCIDCMFZWKMZSEBSGKY3PMRUW4ZY";
const char *expected = "This is the result of a base32 decoding";
char dst[256];
int dstLen = 0;
test("Decode: ");
s = nats_Base32_DecodeString((char*) src, dst, (int) sizeof(dst), &dstLen);
testCond((s == NATS_OK)
&& (dstLen == (int) strlen(expected))
&& (memcmp((void*) expected, (void*) dst, dstLen) == 0));
test("Dest too small: ");
s = nats_Base32_DecodeString((char*) src, dst, 10, &dstLen);
testCond((s == NATS_INSUFFICIENT_BUFFER) && (dstLen == 0));
nats_clearLastError();
test("Invalid string: ");
s = nats_Base32_DecodeString((char*)"This is invalid content", dst, (int) sizeof(dst), &dstLen);
testCond((s == NATS_ERR)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), "invalid") != NULL));
}
static void
test_natsBase64Encode(void)
{
natsStatus s;
char *enc = NULL;
int i;
const char *testStrings[] = {
"this is testing base64 encoding",
"dfslfdlkjsfdllkjfds dfsjlklkfsda dfsalkjklfdsalkj adfskjllkjfdaslkjfdslk",
"This is another with numbers like 12345678.90 and special characters !@#$%^&*()-=+/",
};
const char *expectedResults[] = {
"dGhpcyBpcyB0ZXN0aW5nIGJhc2U2NCBlbmNvZGluZw",
"ZGZzbGZkbGtqc2ZkbGxramZkcyBkZnNqbGtsa2ZzZGEgZGZzYWxramtsZmRzYWxraiBhZGZza2psbGtqZmRhc2xramZkc2xr",
"VGhpcyBpcyBhbm90aGVyIHdpdGggbnVtYmVycyBsaWtlIDEyMzQ1Njc4LjkwIGFuZCBzcGVjaWFsIGNoYXJhY3RlcnMgIUAjJCVeJiooKS09Ky8",
};
const char *expectedResultsStd[] = {
"dGhpcyBpcyB0ZXN0aW5nIGJhc2U2NCBlbmNvZGluZw==",
"ZGZzbGZkbGtqc2ZkbGxramZkcyBkZnNqbGtsa2ZzZGEgZGZzYWxramtsZmRzYWxraiBhZGZza2psbGtqZmRhc2xramZkc2xr",
"VGhpcyBpcyBhbm90aGVyIHdpdGggbnVtYmVycyBsaWtlIDEyMzQ1Njc4LjkwIGFuZCBzcGVjaWFsIGNoYXJhY3RlcnMgIUAjJCVeJiooKS09Ky8=",
};
const uint8_t someBytes[] = {1, 2, 0, 3, 4, 5, 0, 6, 7, 8, 0, 9, 0};
const char *sbe = "AQIAAwQFAAYHCAAJAA==";
int sbl = 13;
int sl = 0;
int dl = 0;
unsigned char *dec = NULL;
test("EncodeURL nil: ");
s = nats_Base64RawURL_EncodeString(NULL, 0, &enc);
testCond((s == NATS_OK) && (enc == NULL));
test("EncodeURL empty: ");
s = nats_Base64RawURL_EncodeString((const unsigned char*) "", 0, &enc);
testCond((s == NATS_OK) && (enc == NULL));
test("EncodeURL strings: ");
for (i=0; i<(int)(sizeof(testStrings)/sizeof(char*)); i++)
{
s = nats_Base64RawURL_EncodeString((const unsigned char*) testStrings[i], (int)strlen(testStrings[i]), &enc);
if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, expectedResults[i]) != 0)))
s = NATS_ERR;
free(enc);
enc = NULL;
if (s != NATS_OK)
break;
}
testCond(s == NATS_OK);
test("EncodeURL bytes: ");
{
s = nats_Base64RawURL_EncodeString((const unsigned char*) &someBytes, sbl, &enc);
if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, "AQIAAwQFAAYHCAAJAA") != 0)))
s = NATS_ERR;
free(enc);
enc = NULL;
}
testCond(s == NATS_OK);
test("Encode nil: ");
s = nats_Base64_Encode(NULL, 0, &enc);
testCond((s == NATS_OK) && (enc == NULL));
test("Encode empty: ");
s = nats_Base64_Encode((const unsigned char*) "", 0, &enc);
testCond((s == NATS_OK) && (enc == NULL));
test("Encode strings: ");
for (i=0; i<(int)(sizeof(testStrings)/sizeof(char*)); i++)
{
s = nats_Base64_Encode((const unsigned char*) testStrings[i], (int)strlen(testStrings[i]), &enc);
if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, expectedResultsStd[i]) != 0)))
s = NATS_ERR;
free(enc);
enc = NULL;
if (s != NATS_OK)
break;
}
testCond(s == NATS_OK);
test("Encode bytes: ");
{
s = nats_Base64_Encode((const unsigned char*) &someBytes, sbl, &enc);
if ((s == NATS_OK) && ((enc == NULL) || (strcmp(enc, sbe) != 0)))
s = NATS_ERR;
free(enc);
enc = NULL;
}
testCond(s == NATS_OK);
test("DecodeLen src needed: ");
s = nats_Base64_DecodeLen(NULL, &sl, &dl);
if (s == NATS_INVALID_ARG)
s = nats_Base64_DecodeLen("", &sl, &dl);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("DecodeLen bad src len: ");
s = nats_Base64_DecodeLen("foo", &sl, &dl);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), "invalid base64 length") != NULL));
nats_clearLastError();
test("DecodeLen bad content: ");
s = nats_Base64_DecodeLen("f=oo", &sl, &dl);
if (s == NATS_INVALID_ARG)
s = nats_Base64_DecodeLen("@!^*.#_$(foo", &sl, &dl);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), "invalid base64 character") != NULL));
nats_clearLastError();
test("DecodeLen: ");
s = nats_Base64_DecodeLen(sbe, &sl, &dl);
testCond((s == NATS_OK) && (sl == (int) strlen(sbe)) && (dl == sbl));
test("Decode strings: ");
for (i=0; i<(int)(sizeof(expectedResultsStd)/sizeof(char*)); i++)
{
s = nats_Base64_Decode(expectedResultsStd[i], &dec, &dl);
if ((s == NATS_OK)
&& ((dec == NULL) || (dl != (int) strlen(testStrings[i]))
|| (strncmp((const char*) dec, testStrings[i], dl) != 0)))
{
s = NATS_ERR;
}
free(dec);
dec = NULL;
dl = 0;
}
testCond(s == NATS_OK);
test("Decode bytes: ");
s = nats_Base64_Decode(sbe, &dec, &dl);
if ((s == NATS_OK)
&& ((dec == NULL) || (dl != sbl)
|| (memcmp((const void*) someBytes, (const void*) dec, sbl) != 0)))
{
s = NATS_ERR;
}
testCond(s == NATS_OK);
free(dec);
}
static void
test_natsCRC16(void)
{
unsigned char a[] = {153, 209, 36, 74, 103, 32, 65, 34, 111, 68, 104, 156, 50, 14, 164, 140, 144, 230};
uint16_t crc = 0;
uint16_t expected = 10272;
test("Compute: ");
crc = nats_CRC16_Compute(a, (int)sizeof(a));
testCond(crc == expected);
test("Verify: ");
testCond(nats_CRC16_Validate(a, (int)sizeof(a), expected));
test("Expect failure: ");
a[3] = 63;
testCond(!nats_CRC16_Validate(a, (int)sizeof(a), expected));
}
static void
test_natsKeys(void)
{
natsStatus s;
unsigned char sig[NATS_CRYPTO_SIGN_BYTES];
const char *nonceVal = "nonce";
const unsigned char *nonce = (const unsigned char*) nonceVal;
const unsigned char expected[] = {
155, 157, 8, 183, 93, 154, 78, 7,
219, 39, 11, 16, 134, 231, 46, 142,
168, 87, 110, 202, 187, 180, 179, 62,
49, 255, 225, 74, 48, 80, 176, 111,
248, 162, 121, 188, 203, 101, 100, 195,
162, 70, 213, 182, 220, 14, 71, 113,
93, 239, 141, 131, 66, 190, 237, 127,
104, 191, 138, 217, 227, 1, 92, 14,
};
test("Invalid key: ");
s = natsKeys_Sign("ABC", nonce, 0, sig);
testCond((s == NATS_ERR)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), NKEYS_INVALID_ENCODED_KEY) != NULL));
nats_clearLastError();
// This is generated from XYTHISISNOTAVALIDSEED with correct checksum.
// Expect to get invalid seed
test("Invalid seed: ");
s = natsKeys_Sign("LBMVISCJKNEVGTSPKRAVMQKMJFCFGRKFIQ52C", nonce, 0, sig);
testCond((s == NATS_ERR)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), NKEYS_INVALID_SEED) != NULL));
nats_clearLastError();
test("Invalid prefix: ");
s = natsKeys_Sign("SBAUEQ2EIVDEOSCJJJFUYTKOJ5IFCUSTKRKVMV2YLFNECQSDIRCUMR2IJFFEWTCNJZHVAUKSKNKFKVSXLBMVUQKCINCEKRSHJBEUUS2MJVHE6UCRKJJVIVKWK5MFSWV2QA", nonce, 0, sig);
testCond((s == NATS_ERR)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), NKEYS_INVALID_PREFIX) != NULL));
nats_clearLastError();
// This is the valid seed: SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY
// Make the checksum incorrect by changing last 2 bytes.
test("Invalid checksum: ");
s = natsKeys_Sign("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4AA", nonce, 0, sig);
testCond((s == NATS_ERR)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), NKEYS_INVALID_CHECKSUM) != NULL));
nats_clearLastError();
// Now use valid SEED
test("Sign ok: ");
s = natsKeys_Sign("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY", nonce, 0, sig);
testCond((s == NATS_OK)
&& (memcmp(sig, expected, sizeof(expected)) == 0));
}
static void
test_natsReadFile(void)
{
natsStatus s = NATS_OK;
natsBuffer *buf = NULL;
FILE *f = NULL;
const char *fn = "test_readfile.txt";
const char *content = "This is some content.\nThere are 2 lines in this file.\n";
test("Invalid arg 1: ");
s = nats_ReadFile(&buf, 0, "file.txt");
testCond((s == NATS_INVALID_ARG) && (buf == NULL));
test("Invalid arg 2: ");
s = nats_ReadFile(&buf, -1, "file.txt");
testCond((s == NATS_INVALID_ARG) && (buf == NULL));
test("Invalid arg 3: ");
s = nats_ReadFile(&buf, 100, NULL);
testCond((s == NATS_INVALID_ARG) && (buf == NULL));
test("Invalid arg 4: ");
s = nats_ReadFile(&buf, 100, "");
testCond((s == NATS_INVALID_ARG) && (buf == NULL));
test("File not found: ");
s = nats_ReadFile(&buf, 100, "fileNotFound.txt");
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "fileNotFound.txt") != NULL)
&& (buf == NULL));
nats_clearLastError();
// Create temp file with some content...
f = fopen(fn, "w");
if (f == NULL)
{
FAIL("Unable to create test file");
}
else
{
int res = fputs(content, f);
if (res < 0)
FAIL("Unable to write content of test file");
fclose(f);
f = NULL;
}
test("Read with large buffer: ");
s = nats_ReadFile(&buf, 1024, fn);
testCond((s == NATS_OK)
&& (buf != NULL)
&& (natsBuf_Capacity(buf) == 1024)
&& (natsBuf_Len(buf) == (int) strlen(content)+1)
&& (strcmp(natsBuf_Data(buf), content) == 0));
natsBuf_Destroy(buf);
buf = NULL;
test("Read with small buffer: ");
// The content is 55 bytes. We start with buffer of 10 and x2
// when expanding, so capacity should be 10, 20, 40, 80.
s = nats_ReadFile(&buf, 10, fn);
testCond((s == NATS_OK)
&& (buf != NULL)
&& (natsBuf_Capacity(buf) == 80)
&& (natsBuf_Len(buf) == (int) strlen(content)+1)
&& (strcmp(natsBuf_Data(buf), content) == 0));
natsBuf_Destroy(buf);
buf = NULL;
test("Read with buffer of exact file content: ");
// Use a buf capacity that matches exactly the content on file.
// But since we need to add the terminating `\0`, then we will
// need to expand.
s = nats_ReadFile(&buf, (int)strlen(content), fn);
testCond((s == NATS_OK)
&& (buf != NULL)
&& (natsBuf_Capacity(buf) == (int)strlen(content)*2)
&& (natsBuf_Len(buf) == (int) strlen(content)+1)
&& (strcmp(natsBuf_Data(buf), content) == 0));
natsBuf_Destroy(buf);
buf = NULL;
s = nats_ReadFile(&buf, (int)strlen(content)+1, fn);
testCond((s == NATS_OK)
&& (buf != NULL)
&& (natsBuf_Capacity(buf) == (int)strlen(content)+1)
&& (natsBuf_Len(buf) == (int) strlen(content)+1)
&& (strcmp(natsBuf_Data(buf), content) == 0));
natsBuf_Destroy(buf);
buf = NULL;
remove(fn);
}
static void
test_natsGetJWTOrSeed(void)
{
natsStatus s;
char *val = NULL;
char buf[256];
const char *valids[] = {
"--- START JWT ---\nsome value\n--- END JWT ---\n",
"--- ---\nsome value\n--- ---\n",
"------\nsome value\n------\n",
"---\nabc\n--\n---START---\nsome value\n---END---\n----\ndef\n--- ---\n",
"nothing first\nthen it starts\n --- START ---\nsome value\n--- END ---\n---START---\nof something else\n---END---\n",
"--- START ---\nsome value\n\n\n--- END ---\n",
};
const char *invalids[] = {
"-- JWT -- START ----\nsome value\n--- END ---\n",
"--- START --- \nsome value\n--- END ---\n",
"--- START ---\nsome value\n-- END ---\n",
"--- START ---\nsome value\n---- END --- \n",
};
int i;
int iter;
for (iter=0; iter<2; iter++)
{
for (i=0; i<(int)(sizeof(valids)/sizeof(char*));i++)
{
snprintf(buf, sizeof(buf), "%s %d: ", (iter==0 ? "JWT" : "Seed"), (i+1));
test(buf);
snprintf(buf, sizeof(buf), "%s%s", (iter == 0 ? "" : "------\njwt\n------\n"), valids[i]);
s = nats_GetJWTOrSeed(&val, buf, iter);
testCond((s == NATS_OK) && (val != NULL) && (strcmp(val, "some value") == 0));
free(val);
val = NULL;
}
for (i=0; i<(int)(sizeof(invalids)/sizeof(char*));i++)
{
snprintf(buf, sizeof(buf), "%s invalid %d: ", (iter == 0 ? "JWT" : "Seed"), (i+1));
test(buf);
snprintf(buf, sizeof(buf), "%s%s", (iter == 0 ? "" : "------\njwt\n------\n"), invalids[i]);
s = nats_GetJWTOrSeed(&val, buf, iter);
testCond((s == NATS_NOT_FOUND) && (val == NULL));
}
}
}
static void
test_natsHostIsIP(void)
{
struct _testHost {
const char *host;
bool isIP;
};
const struct _testHost hosts[] = {
{ "1.2.3.4", true },
{ "::1", true },
{ "localhost", false },
{ "www.host.name.com", false },
};
int i;
for (i=0; i<(int)(sizeof(hosts)/sizeof(struct _testHost)); i++)
{
char buf[256];
snprintf(buf, sizeof(buf), "Check '%s': ", hosts[i].host);
test(buf);
testCond(nats_HostIsIP(hosts[i].host) == hosts[i].isIP)
}
}
static void
_testWaitReadyServer(void *closure)
{
struct addrinfo *servinfo = NULL;
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsSock cliSock = NATS_SOCK_INVALID;
struct threadArg *arg = (struct threadArg*) closure;
struct addrinfo hints;
int res;
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
if ((res = getaddrinfo("127.0.0.1", "1234", &hints, &servinfo)) != 0)
s = NATS_SYS_ERROR;
if (s == NATS_OK)
{
sock = socket(servinfo->ai_family, servinfo->ai_socktype,
servinfo->ai_protocol);
if (sock == NATS_SOCK_INVALID)
s = NATS_SYS_ERROR;
IFOK(s, natsSock_SetCommonTcpOptions(sock));
IFOK(s, natsSock_SetBlocking(sock, true));
}
if ((s == NATS_OK)
&& (bind(sock, servinfo->ai_addr, (natsSockLen) servinfo->ai_addrlen) == NATS_SOCK_ERROR))
{
s = NATS_SYS_ERROR;
}
if ((s == NATS_OK) && (listen(sock, 100) == 0))
{
cliSock = accept(sock, NULL, NULL);
if ((cliSock != NATS_SOCK_INVALID)
&& (natsSock_SetCommonTcpOptions(cliSock) == NATS_OK))
{
nats_Sleep(500);
send(cliSock, "*", 1, 0);
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->done)
s = natsCondition_TimedWait(arg->c, arg->m, 10000);
natsMutex_Unlock(arg->m);
natsSock_Close(cliSock);
}
}
natsSock_Close(sock);
nats_FreeAddrInfo(servinfo);
}
static void
_testSockShutdownThread(void *closure)
{
natsSockCtx *ctx = (natsSockCtx*) closure;
nats_Sleep(500);
natsSock_Shutdown(ctx->fd);
}
static void
test_natsWaitReady(void)
{
natsStatus s = NATS_OK;
natsThread *t = NULL;
natsThread *t2 = NULL;
natsSockCtx ctx;
int64_t start, dur;
char buffer[1];
int i;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test");
if (natsThread_Create(&t, _testWaitReadyServer, &arg) != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("Connect: ");
natsSock_Init(&ctx);
ctx.orderIP = 4;
natsSock_ClearDeadline(&ctx);
for (i=0; i<20; i++)
{
s = natsSock_ConnectTcp(&ctx, "127.0.0.1", 1234);
if (s == NATS_OK)
break;
nats_Sleep(100);
}
testCond(s == NATS_OK);
test("Set non blocking: ");
s = natsSock_SetCommonTcpOptions(ctx.fd);
IFOK(s, natsSock_SetBlocking(ctx.fd, false));
testCond(s == NATS_OK);
// Ensure that we get a would_block on read..
while (recv(ctx.fd, buffer, 1, 0) != -1) {}
test("WaitReady no deadline: ");
natsSock_ClearDeadline(&ctx);
start = nats_Now();
s = natsSock_WaitReady(WAIT_FOR_READ, &ctx);
dur = nats_Now()-start;
testCond((s == NATS_OK) && (dur >= 450) && (dur <= 600));
// Ensure that we get a would_block on read..
while (recv(ctx.fd, buffer, 1, 0) != -1) {}
test("WaitReady deadline timeout: ");
natsSock_InitDeadline(&ctx, 50);
start = nats_Now();
s = natsSock_WaitReady(WAIT_FOR_READ, &ctx);
dur = nats_Now()-start;
testCond((s == NATS_TIMEOUT) && (dur >= 40) && (dur <= 100));
// Ensure that we get a would_block on read..
while (recv(ctx.fd, buffer, 1, 0) != -1) {}
test("WaitReady kicked out by shutdown: ");
natsSock_ClearDeadline(&ctx);
start = nats_Now();
s = natsThread_Create(&t2, _testSockShutdownThread, &ctx);
IFOK(s, natsSock_WaitReady(WAIT_FOR_READ, &ctx));
dur = nats_Now()-start;
testCond((s == NATS_OK) && (dur <= 3000));
natsSock_Close(ctx.fd);
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
if (t2 != NULL)
{
natsThread_Join(t2);
natsThread_Destroy(t2);
}
_destroyDefaultThreadArgs(&arg);
}
static void
test_natsSign(void)
{
unsigned char *sig = NULL;
int sigLen = 0;
char *sig64 = NULL;
natsStatus s;
test("nats_Sign invalid param 1: ");
s = nats_Sign(NULL, "nonce", &sig, &sigLen);
testCond(s == NATS_INVALID_ARG);
test("nats_Sign invalid param 2: ");
s = nats_Sign("seed", NULL, &sig, &sigLen);
testCond(s == NATS_INVALID_ARG);
test("nats_Sign invalid param 3: ");
s = nats_Sign("seed", "nonce", NULL, &sigLen);
testCond(s == NATS_INVALID_ARG);
test("nats_Sign invalid param 4: ");
s = nats_Sign("seed", "nonce", &sig, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Sign ok: ");
s = nats_Sign(
"SUACSSL3UAHUDXKFSNVUZRF5UHPMWZ6BFDTJ7M6USDXIEDNPPQYYYCU3VY",
"nonce", &sig, &sigLen);
IFOK(s, nats_Base64RawURL_EncodeString((const unsigned char*) sig, sigLen, &sig64));
testCond((s == NATS_OK)
&& (sig != NULL)
&& (sig64 != NULL)
&& (sigLen == NATS_CRYPTO_SIGN_BYTES)
&& (memcmp((void*) sig64,
(void*) "AVfpO7Pw3rc56hoO1OJcFxXUCfBmO2qouchBchSlL45Fuur9zS15UzytEI1QC5wwVG7uiHIdqyfmOS6uPrwqCg",
NATS_CRYPTO_SIGN_BYTES) == 0));
free(sig);
free(sig64);
}
static void
_testHeader(const char *testName, char *buf, natsStatus expected, const char *errTxt,
const char *key, const char *value)
{
natsStatus s = NATS_OK;
natsMsg *msg = NULL;
const char *val = NULL;
const char *k = (key == NULL ? "k" : key);
test(testName);
s = natsMsg_create(&msg, "foo", 3, NULL, 0, buf, (int)strlen(buf), (int) strlen(buf));
IFOK(s, natsMsgHeader_Get(msg, k, &val));
if (expected == NATS_OK)
{
testCond((s == NATS_OK) && (val != NULL) && (strcmp(val, value) == 0));
}
else
{
const char *le = nats_GetLastError(&s);
testCond((s == expected) && (strstr(le, errTxt) != NULL));
nats_clearLastError();
}
natsMsg_Destroy(msg);
}
static void
_testStatus(const char *testName, char *buf, const char *expectedStatus, const char *expectedDescription)
{
natsStatus s = NATS_OK;
natsMsg *msg = NULL;
const char *sts = NULL;
const char *desc = NULL;
test(testName);
s = natsMsg_create(&msg, "foo", 3, NULL, 0, buf, (int)strlen(buf), (int) strlen(buf));
IFOK(s, natsMsgHeader_Get(msg, STATUS_HDR, &sts));
IFOK(s, natsMsgHeader_Get(msg, DESCRIPTION_HDR, &desc));
testCond((s == (expectedDescription == NULL ? NATS_NOT_FOUND : NATS_OK)
&& ((sts != NULL) && (strcmp(sts, expectedStatus) == 0))
&& (expectedDescription == NULL
? desc == NULL
: ((desc != NULL) && (strcmp(desc, expectedDescription) == 0)))));
natsMsg_Destroy(msg);
}
static void
test_natsMsgHeadersLift(void)
{
char buf[512];
snprintf(buf, sizeof(buf), "%sk:v\r\n\r\n", HDR_LINE);
_testHeader("Valid simple header: ", buf, NATS_OK, "", "k", "v");
snprintf(buf, sizeof(buf), "%sk e y:v\r\n\r\n", HDR_LINE);
_testHeader("Key with spaces ok: ", buf, NATS_OK, "", "k e y", "v");
snprintf(buf, sizeof(buf), "%sk e y :v\r\n\r\n", HDR_LINE);
_testHeader("Key with spaces (including traling) ok: ", buf, NATS_OK, "", "k e y ", "v");
snprintf(buf, sizeof(buf), "%sk: v \r\n\r\n", HDR_LINE);
_testHeader("Trim spaces for value: ", buf, NATS_OK, "", "k", "v");
snprintf(buf, sizeof(buf), "%sk: a\r\n bc\r\n def\r\n\r\n", HDR_LINE);
_testHeader("Multiline values: ", buf, NATS_OK, "", "k", "a bc def");
snprintf(buf, sizeof(buf), "%s", "NATS\r\nk:v\r\n\r\n");
_testHeader("NATS header missing: ", buf, NATS_PROTOCOL_ERROR, "header prefix missing", NULL, NULL);
snprintf(buf, sizeof(buf), "%s", HDR_LINE);
_testHeader("NATS header missing CRLF: ", buf, NATS_PROTOCOL_ERROR, "early termination of headers", NULL, NULL);
snprintf(buf, sizeof(buf), "%sk:v\r\n\rbad\r\n", HDR_LINE);
_testHeader("Invalid key start: ", buf, NATS_PROTOCOL_ERROR, "invalid start of a key", NULL, NULL);
snprintf(buf, sizeof(buf), "%s k:v\r\n\r\n", HDR_LINE);
_testHeader("Space in key name: ", buf, NATS_PROTOCOL_ERROR, "key cannot start with a space", NULL, NULL);
snprintf(buf, sizeof(buf), "%sk\r\n\r\n", HDR_LINE);
_testHeader("Column missing: ", buf, NATS_PROTOCOL_ERROR, "column delimiter not found", NULL, NULL);
snprintf(buf, sizeof(buf), "%sk:\r\n\r\n", HDR_LINE);
_testHeader("No value: ", buf, NATS_PROTOCOL_ERROR, "no value found for key", NULL, NULL);
snprintf(buf, sizeof(buf), "%sk: \r\n\r\n", HDR_LINE);
_testHeader("No value (extra spaces): ", buf, NATS_PROTOCOL_ERROR, "no value found for key", NULL, NULL);
// Check status description in header line prefix...
snprintf(buf, sizeof(buf), "%s 503\r\n\r\n", HDR_LINE_PRE);
_testStatus("Status no description: ", buf, "503", NULL);
snprintf(buf, sizeof(buf), "%s 503 \r\n\r\n", HDR_LINE_PRE);
_testStatus("Status no description (extra space): ", buf, "503", NULL);
snprintf(buf, sizeof(buf), "%s 503 \r\n\r\n", HDR_LINE_PRE);
_testStatus("Status no description (extra spaces): ", buf, "503", NULL);
snprintf(buf, sizeof(buf), "%s 503 No Responders\r\n\r\n", HDR_LINE_PRE);
_testStatus("Status with description: ", buf, "503", "No Responders");
snprintf(buf, sizeof(buf), "%s 404 No Messages \r\n\r\n", HDR_LINE_PRE);
_testStatus("Status with description (extra space): ", buf, "404", "No Messages");
snprintf(buf, sizeof(buf), "%s 404 No Messages \r\n\r\n", HDR_LINE_PRE);
_testStatus("Status with description (extra spaces): ", buf, "404", "No Messages");
}
static void
test_natsMsgHeaderAPIs(void)
{
natsStatus s = NATS_OK;
natsMsg *msg = NULL;
const char *val = NULL;
const char* *values = NULL;
const char* *keys = NULL;
int count = 0;
const char *longKey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
test("Create message: ");
s = natsMsg_Create(&msg, "foo", NULL, "body", 4);
testCond(s == NATS_OK);
test("Key cannot be NULL: ");
s = natsMsgHeader_Set(msg, NULL, "value");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Key cannot be empty: ");
s = natsMsgHeader_Set(msg, "", "value");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Set msg cannot be NULL: ");
s = natsMsgHeader_Set(NULL, "key", "value");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Set value: ");
s = natsMsgHeader_Set(msg, "my-key", "value1");
testCond(s == NATS_OK);
test("Get msg cannot be NULL: ");
s = natsMsgHeader_Get(NULL, "my-key", &val);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Get must provide mem location: ");
s = natsMsgHeader_Get(msg, "my-key", NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Get: ");
s = natsMsgHeader_Get(msg, "my-key", &val);
testCond((s == NATS_OK) &&
(val != NULL) &&
(strcmp(val, "value1") == 0));
val = NULL;
test("Get value with different case: ");
s = natsMsgHeader_Get(msg, "my-Key", &val);
testCond((s == NATS_NOT_FOUND) && (val == NULL));
val = NULL;
test("Key not found: ");
s = natsMsgHeader_Get(msg, "unknown-key", &val);
testCond((s == NATS_NOT_FOUND) && (val == NULL));
val = NULL;
test("Set value replace old: ");
s = natsMsgHeader_Set(msg, "my-key", "value2");
testCond(s == NATS_OK);
test("Get value: ");
s = natsMsgHeader_Get(msg, "my-key", &val);
testCond((s == NATS_OK) &&
(val != NULL) &&
(strcmp(val, "value2") == 0));
val = NULL;
test("Set NULL value: ");
s = natsMsgHeader_Set(msg, "my-key", NULL);
testCond(s == NATS_OK);
test("Get value: ");
s = natsMsgHeader_Get(msg, "my-key", &val);
testCond((s == NATS_OK) && (val == NULL));
val = NULL;
test("Set empty value: ");
s = natsMsgHeader_Set(msg, "my-key", "");
testCond(s == NATS_OK);
test("Get value: ");
s = natsMsgHeader_Get(msg, "my-key", &val);
testCond((s == NATS_OK) && (val != NULL) && (val[0] == '\0'));
val = NULL;
test("Add msg cannot be NULL: ");
s = natsMsgHeader_Add(NULL, "key", "value");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Add first: ");
s = natsMsgHeader_Add(msg, "two-fields", "val1");
testCond(s == NATS_OK);
test("Add second: ");
s = natsMsgHeader_Add(msg, "two-fields", "val2");
testCond(s == NATS_OK);
test("Get should return first: ");
s = natsMsgHeader_Get(msg, "two-fields", &val);
testCond((s == NATS_OK) &&
(val != NULL) &&
(strcmp(val, "val1") == 0));
val = NULL;
test("Values: ");
s = natsMsgHeader_Values(msg, "two-fields", &values, &count);
testCond((s == NATS_OK) && (values != NULL) && (count == 2) &&
(strcmp(values[0], "val1") == 0) &&
(strcmp(values[1], "val2") == 0));
if (values != NULL)
free((void*) values);
values = NULL;
count = 0;
test("Add after a Set: ");
s = natsMsgHeader_Set(msg, "my-other-key", "val3");
IFOK(s, natsMsgHeader_Add(msg, "my-other-key", "val4"));
IFOK(s, natsMsgHeader_Values(msg, "my-other-key", &values, &count));
testCond((s == NATS_OK) && (values != NULL) && (count == 2) &&
(strcmp(values[0], "val3") == 0) &&
(strcmp(values[1], "val4") == 0));
if (values != NULL)
free((void*) values);
values = NULL;
count = 0;
test("Keys msg cannot be NULL: ");
s = natsMsgHeader_Keys(NULL, &keys, &count);
testCond((s == NATS_INVALID_ARG) && (keys == NULL) && (count == 0));
if (s == NATS_INVALID_ARG)
{
s = NATS_OK;
nats_clearLastError();
}
test("Keys keys cannot be NULL: ");
s = natsMsgHeader_Keys(msg, NULL, &count);
testCond((s == NATS_INVALID_ARG) && (keys == NULL) && (count == 0));
nats_clearLastError();
test("Keys count cannot be NULL: ");
s = natsMsgHeader_Keys(msg, &keys, NULL);
testCond((s == NATS_INVALID_ARG) && (keys == NULL) && (count == 0));
nats_clearLastError();
test("Keys: ");
s = natsMsgHeader_Keys(msg, &keys, &count);
if ((s == NATS_OK) && ((keys == NULL) || (count != 3)))
{
s = NATS_ERR;
}
else
{
int i;
bool ok1 = false;
bool ok2 = false;
bool ok3 = false;
for (i=0; i<count; i++)
{
if (strcmp(keys[i], "my-key") == 0)
ok1 = true;
else if (strcmp(keys[i], "two-fields") == 0)
ok2 = true;
else if (strcmp(keys[i], "my-other-key") == 0)
ok3 = true;
}
if (!ok1 || !ok2 || !ok3)
s = NATS_ERR;
}
testCond(s == NATS_OK);
if (keys != NULL)
free((void*) keys);
count = 0;
test("Set with long key: ");
s = natsMsgHeader_Set(msg, longKey, "val1");
testCond(s == NATS_OK);
test("Add with long key: ");
s = natsMsgHeader_Add(msg, longKey, "val2");
testCond(s == NATS_OK);
test("Get with long key: ");
s = natsMsgHeader_Get(msg, longKey, &val);
testCond((s == NATS_OK) && (val != NULL) && (strcmp(val, "val1") == 0));
test("Values with long key: ");
s = natsMsgHeader_Values(msg, longKey, &values, &count);
testCond((s == NATS_OK) && (values != NULL) && (count == 2) &&
(strcmp(values[0], "val1") == 0) &&
(strcmp(values[1], "val2") == 0));
free((void*) values);
count = 0;
test("Delete msg cannot be NULL: ");
s = natsMsgHeader_Delete(NULL, "key");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Delete key cannot be NULL: ");
s = natsMsgHeader_Delete(msg, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Delete key cannot be empty: ");
s = natsMsgHeader_Delete(msg, "");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Delete: ");
s = natsMsgHeader_Delete(msg, "my-other-key");
testCond(s == NATS_OK);
test("Should be gone: ");
s = natsMsgHeader_Get(msg, "my-other-key", &val);
testCond((s == NATS_NOT_FOUND) && (val == NULL));
natsMsg_Destroy(msg);
}
static void
test_natsMsgIsJSCtrl(void)
{
struct testCase {
const char *buf;
int dataLen;
bool isCtrl;
int ct;
};
const struct testCase cases[] = {
{"key:value\r\n\r\n", 0, false, 0},
{"data", 4, false, 0},
{"NATS/1.0 \r\n\r\n", 0, false, 0},
{"NATS/1.0 100 Idle Heartbeat\r\n\r\ndata", 4, false, 0},
{"NATS/1.0 200 Some status...\r\n\r\n", 0, false, 0},
{"NATS/1.0100 Idle Heartbeat\r\n\r\n", 0, false, 0},
{"NATS/1.0 1000 Some status\r\n\r\n", 0, false, 0},
{"NATS/1.0 100-Some status\r\n\r\n", 0, false, 0},
{"NATS/1.0 100\r\n\r\n", 0, true, 0},
{"NATS/1.0 100\r\n\r\n", 0, true, 0},
{"NATS/1.0 100\r\n\r\n", 0, true, 0},
{"NATS/1.0 100 \r\n\r\n", 0, true, 0},
{"NATS/1.0 100 \r\n\r\n", 0, true, 0},
{"NATS/1.0 100 \r\n\r\n", 0, true, 0},
{"NATS/1.0 100 \r\n\r\n", 0, true, 0},
{"NATS/1.0 100 \r\n\r\n", 0, true, 0},
{"NATS/1.0 100\n\r\n", 0, true, 0},
{"NATS/1.0 100 Idle Heartbeat\r\n\r\n", 0, true, jsCtrlHeartbeat},
{"NATS/1.0 100 Idle Heartbeat\r\n\r\n", 0, true, jsCtrlHeartbeat},
{"NATS/1.0 100 FlowControl Request\r\n\r\n", 0, true, jsCtrlFlowControl},
{"NATS/1.0 100 FlowControl Request\r\n\r\n", 0, true, jsCtrlFlowControl},
};
natsStatus s;
natsMsg *msg = NULL;
char temp[64];
int i;
for (i=0; i<(int)(sizeof(cases)/sizeof(struct testCase)); i++)
{
int bufLen;
snprintf(temp, sizeof(temp), "Case %d - isCtrl=%d: ", (i+1), cases[i].isCtrl);
test(temp);
bufLen = (int)strlen(cases[i].buf);
s = natsMsg_create(&msg, "foo", 3, NULL, 0, cases[i].buf, bufLen, bufLen-cases[i].dataLen);
if (s == NATS_OK)
{
int ctrlType = 0;
bool isCtrl = false;
isCtrl = natsMsg_isJSCtrl(msg, &ctrlType);
s = ((isCtrl == cases[i].isCtrl) && (ctrlType == cases[i].ct) ? NATS_OK : NATS_ERR);
}
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
}
}
static void
test_natsSrvVersionAtLeast(void)
{
natsOptions *opts = NULL;
natsConnection *nc = NULL;
const char *infos[]= {
"INFO {\"version\":\"2.7.2\"}\r\n",
"INFO {\"version\":\"bad.version\"}\r\n",
"INFO {\"no_version\":\"2.7.2\"}\r\n",
"INFO {\"version\":\"2.7.3-beta01\"}\r\n",
};
const int res[][3] = {
{2, 7, 2},
{0, 0, 0},
{0, 0, 0},
{2, 7, 3},
};
const int checksOK[][3] = {
{1, 0, 0},
{1, 1, 4},
{1, 8, 0},
{1, 8, 4},
{2, 7, 3},
{2, 6, 4},
};
const int checksBad[][3] = {
{2, 7, 4},
{2, 8, 0},
{3, 0, 0},
};
natsStatus s = NATS_OK;
int i;
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup the test");
test("Check version parsing: ");
for (i=0; (s == NATS_OK) && (i<(int)(sizeof(infos)/sizeof(char*))); i++)
{
IFOK(s, natsParser_Parse(nc, (char*) infos[i], (int) strlen(infos[i])));
if (s == NATS_OK)
{
natsConn_Lock(nc);
if ((nc->srvVersion.ma != res[i][0]) || (nc->srvVersion.mi != res[i][1])
|| (nc->srvVersion.up != res[i][2]))
{
s = NATS_ERR;
}
}
}
testCond(s == NATS_OK);
// Server version is 2.7.3 from last parsing
test("Check OK: ");
for (i=0; (s == NATS_OK) && (i<(int)(sizeof(checksOK)/sizeof(int[3]))); i++)
s = natsConn_srvVersionAtLeast(nc, checksOK[i][0], checksOK[i][1], checksOK[i][2]) ? NATS_OK : NATS_ERR;
testCond(s == NATS_OK);
test("Check Bad: ");
for (i=0; (s == NATS_OK) && (i<(int)(sizeof(checksBad)/sizeof(int[3]))); i++)
s = natsConn_srvVersionAtLeast(nc, checksBad[i][0], checksBad[i][1], checksBad[i][2]) ? NATS_ERR : NATS_OK;
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
}
static natsStatus
_checkStart(const char *url, int orderIP, int maxAttempts)
{
natsStatus s = NATS_OK;
natsUrl *nUrl = NULL;
int attempts = 0;
natsSockCtx ctx;
natsSock_Init(&ctx);
ctx.orderIP = orderIP;
natsDeadline_Init(&(ctx.writeDeadline), 2000);
s = natsUrl_Create(&nUrl, url);
if (s == NATS_OK)
{
while (((s = natsSock_ConnectTcp(&ctx,
nUrl->host, nUrl->port)) != NATS_OK)
&& (attempts++ < maxAttempts))
{
nats_Sleep(200);
}
natsUrl_Destroy(nUrl);
if (s == NATS_OK)
natsSock_Close(ctx.fd);
else
s = NATS_NO_SERVER;
}
nats_clearLastError();
return s;
}
static natsStatus
_checkStreamingStart(const char *url, int maxAttempts)
{
natsStatus s = NATS_NOT_PERMITTED;
#if defined(NATS_HAS_STREAMING)
stanConnOptions *opts = NULL;
stanConnection *sc = NULL;
int attempts = 0;
s = stanConnOptions_Create(&opts);
IFOK(s, stanConnOptions_SetURL(opts, url));
IFOK(s, stanConnOptions_SetConnectionWait(opts, 250));
if (s == NATS_OK)
{
while (((s = stanConnection_Connect(&sc, clusterName, "checkStart", opts)) != NATS_OK)
&& (attempts++ < maxAttempts))
{
nats_Sleep(200);
}
}
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
if (s != NATS_OK)
nats_clearLastError();
#else
#endif
return s;
}
#ifdef _WIN32
typedef PROCESS_INFORMATION *natsPid;
static HANDLE logHandle = NULL;
static void
_stopServer(natsPid pid)
{
if (pid == NATS_INVALID_PID)
return;
TerminateProcess(pid->hProcess, 0);
WaitForSingleObject(pid->hProcess, INFINITE);
CloseHandle(pid->hProcess);
CloseHandle(pid->hThread);
natsMutex_Lock(slMu);
if (slMap != NULL)
natsHash_Remove(slMap, (int64_t) pid);
natsMutex_Unlock(slMu);
free(pid);
}
static natsPid
_startServerImpl(const char *serverExe, const char *url, const char *cmdLineOpts, bool checkStart)
{
SECURITY_ATTRIBUTES sa;
STARTUPINFO si;
HANDLE h;
PROCESS_INFORMATION *pid;
DWORD flags = 0;
BOOL createdOk = FALSE;
BOOL hInheritance = FALSE;
char *exeAndCmdLine = NULL;
int ret;
pid = calloc(1, sizeof(PROCESS_INFORMATION));
if (pid == NULL)
return NATS_INVALID_PID;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ret = nats_asprintf(&exeAndCmdLine, "%s%s%s", serverExe,
(cmdLineOpts != NULL ? " " : ""),
(cmdLineOpts != NULL ? cmdLineOpts : ""));
if (ret < 0)
{
printf("No memory allocating command line string!\n");
free(pid);
return NATS_INVALID_PID;
}
if (!keepServerOutput)
{
ZeroMemory(&sa, sizeof(sa));
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
h = logHandle;
if (h == NULL)
{
h = CreateFile(LOGFILE_NAME,
GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
&sa,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = NULL;
si.hStdError = h;
si.hStdOutput = h;
hInheritance = TRUE;
flags = CREATE_NO_WINDOW;
if (logHandle == NULL)
logHandle = h;
}
// Start the child process.
if (!CreateProcess(NULL,
(LPSTR) exeAndCmdLine,
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
hInheritance, // Set handle inheritance
flags, // Creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
pid)) // Pointer to PROCESS_INFORMATION structure
{
printf("Unable to start '%s': error (%d).\n",
exeAndCmdLine, GetLastError());
free(exeAndCmdLine);
return NATS_INVALID_PID;
}
free(exeAndCmdLine);
if (checkStart)
{
natsStatus s;
if (strcmp(serverExe, natsServerExe) == 0)
s = _checkStart(url, 46, 10);
else
s = _checkStreamingStart(url, 10);
if (s != NATS_OK)
{
_stopServer(pid);
return NATS_INVALID_PID;
}
}
natsMutex_Lock(slMu);
if (slMap != NULL)
natsHash_Set(slMap, (int64_t) pid, NULL, NULL);
natsMutex_Unlock(slMu);
return (natsPid) pid;
}
#else
typedef pid_t natsPid;
static void
_stopServer(natsPid pid)
{
int status = 0;
if (pid == NATS_INVALID_PID)
return;
if (kill(pid, SIGINT) < 0)
{
perror("kill with SIGINT");
if (kill(pid, SIGKILL) < 0)
{
perror("kill with SIGKILL");
}
}
waitpid(pid, &status, 0);
natsMutex_Lock(slMu);
if (slMap != NULL)
natsHash_Remove(slMap, (int64_t) pid);
natsMutex_Unlock(slMu);
}
static natsPid
_startServerImpl(const char *serverExe, const char *url, const char *cmdLineOpts, bool checkStart)
{
natsPid pid = fork();
if (pid == -1)
{
perror("fork");
return NATS_INVALID_PID;
}
if (pid == 0)
{
char *exeAndCmdLine = NULL;
char *argvPtrs[64];
char *line = NULL;
int index = 0;
int ret = 0;
bool overrideAddr = false;
if ((cmdLineOpts == NULL) || (strstr(cmdLineOpts, "-a ") == NULL))
overrideAddr = true;
ret = nats_asprintf(&exeAndCmdLine, "%s%s%s%s%s", serverExe,
(cmdLineOpts != NULL ? " " : ""),
(cmdLineOpts != NULL ? cmdLineOpts : ""),
(overrideAddr ? " -a 127.0.0.1" : ""),
(keepServerOutput ? "" : " -l " LOGFILE_NAME));
if (ret < 0)
{
perror("No memory allocating command line string!\n");
exit(1);
}
memset(argvPtrs, 0, sizeof(argvPtrs));
line = exeAndCmdLine;
while (*line != '\0')
{
while ((*line == ' ') || (*line == '\t') || (*line == '\n'))
*line++ = '\0';
argvPtrs[index++] = line;
while ((*line != '\0') && (*line != ' ')
&& (*line != '\t') && (*line != '\n'))
{
line++;
}
}
argvPtrs[index++] = NULL;
// Child process. Replace with NATS server
execvp(argvPtrs[0], argvPtrs);
perror("Exec failed: ");
exit(1);
}
else if (checkStart)
{
natsStatus s;
if (strcmp(serverExe, natsServerExe) == 0)
s = _checkStart(url, 46, 10);
else
s = _checkStreamingStart(url, 10);
if (s != NATS_OK)
{
_stopServer(pid);
return NATS_INVALID_PID;
}
}
natsMutex_Lock(slMu);
if (slMap != NULL)
natsHash_Set(slMap, (int64_t) pid, NULL, NULL);
natsMutex_Unlock(slMu);
// parent, return the child's PID back.
return pid;
}
#endif
static natsPid
_startServer(const char *url, const char *cmdLineOpts, bool checkStart)
{
return _startServerImpl(natsServerExe, url, cmdLineOpts, checkStart);
}
static natsPid
_startStreamingServer(const char* url, const char *cmdLineOpts, bool checkStart)
{
return _startServerImpl(natsStreamingServerExe, url, cmdLineOpts, checkStart);
}
static void
test_natsSock_IPOrder(void)
{
natsStatus s;
natsPid serverPid;
test("Server listen to IPv4: ");
serverPid = _startServer("", "-a 127.0.0.1 -p 4222", false);
testCond(true);
test("IPv4 only: ");
s = _checkStart("nats://localhost:4222", 4, 5);
testCond(s == NATS_OK);
test("IPv4+v6: ");
s = _checkStart("nats://localhost:4222", 46, 5);
testCond(s == NATS_OK);
test("IPv6+v4: ");
s = _checkStart("nats://localhost:4222", 64, 5);
testCond(s == NATS_OK);
test("IP any: ");
s = _checkStart("nats://localhost:4222", 0, 5);
testCond(s == NATS_OK);
// This one should fail.
test("IPv6 only: ");
s = _checkStart("nats://localhost:4222", 6, 5);
testCond(s != NATS_OK);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
if (!runOnTravis)
{
test("Server listen to IPv6: ");
serverPid = _startServer("", "-a :: -p 4222", false);
testCond(true);
test("IPv6 only: ");
s = _checkStart("nats://localhost:4222", 6, 5);
testCond(s == NATS_OK);
test("IPv4+v6: ");
s = _checkStart("nats://localhost:4222", 46, 5);
testCond(s == NATS_OK);
test("IPv6+v4: ");
s = _checkStart("nats://localhost:4222", 64, 5);
testCond(s == NATS_OK);
test("IP any: ");
s = _checkStart("nats://localhost:4222", 0, 5);
testCond(s == NATS_OK);
// This one should fail, but the server when listening
// to [::] is actually accepting IPv4 connections,
// so be tolerant of that.
test("IPv4 only: ");
s = _checkStart("nats://localhost:4222", 4, 5);
if (s == NATS_OK)
fprintf(stderr, ">>>> IPv4 to [::] should have failed, but server accepted it\n");
else
s = NATS_OK;
testCond(s == NATS_OK);
_stopServer(serverPid);
}
}
static void
test_natsSock_ConnectTcp(void)
{
natsPid serverPid = NATS_INVALID_PID;
test("Check connect tcp: ");
serverPid = _startServer("nats://127.0.0.1:4222", "-p 4222", true);
testCond(serverPid != NATS_INVALID_PID);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
test("Check connect tcp hostname: ");
serverPid = _startServer("nats://localhost:4222", "-p 4222", true);
testCond(serverPid != NATS_INVALID_PID);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
test("Check connect tcp (force server to listen to IPv4): ");
serverPid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true);
testCond(serverPid != NATS_INVALID_PID);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
}
static bool
listOrder(struct addrinfo *head, bool ordered)
{
struct addrinfo *p;
int i;
p = head;
for (i=0; i<10; i++)
{
if (ordered && (p->ai_flags != (i+1)))
return false;
p = p->ai_next;
}
return true;
}
static void
test_natsSock_ShuffleIPs(void)
{
struct addrinfo *tmp[10];
struct addrinfo *head = NULL;
struct addrinfo *tail = NULL;
struct addrinfo *list = NULL;
struct addrinfo *p;
natsSockCtx ctx;
int i=0;
// Create a fake list that has `ai_flags` set to 1 to 10.
// We will use that to check that the list is shuffled or not.
for (i=0; i<10; i++)
{
p = calloc(1, sizeof(struct addrinfo));
p->ai_flags = (i+1);
if (head == NULL)
head=p;
if (tail != NULL)
tail->ai_next = p;
tail = p;
}
test("No randomize, so no shuffling: ");
natsSock_Init(&ctx);
ctx.noRandomize = true;
list = head;
natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 10);
testCond((list == head) && listOrder(list, true));
test("Shuffling bad args 2: ");
natsSock_Init(&ctx);
list = head;
natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), NULL, 10);
testCond((list == head) && listOrder(list, true));
test("Shuffling bad args 1: ");
natsSock_Init(&ctx);
list = head;
natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 0);
testCond((list == head) && listOrder(list, true));
test("No shuffling count==1: ");
natsSock_Init(&ctx);
list = head;
natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 1);
testCond((list == head) && listOrder(list, true));
test("Shuffling: ");
natsSock_Init(&ctx);
list = head;
natsSock_ShuffleIPs(&ctx, tmp, sizeof(tmp), &list, 10);
testCond(listOrder(list, false));
// Reorder the list, and we will try with a tmp buffer too small,
// so API is going to allocate memory.
p = list;
for (i=0; i<10; i++)
p->ai_flags = (i+1);
head = list;
test("Shuffling mem alloc: ");
natsSock_Init(&ctx);
natsSock_ShuffleIPs(&ctx, tmp, 5, &list, 10);
testCond(listOrder(list, false));
for (p = list; p != NULL; p = list)
{
list = list->ai_next;
free(p);
}
}
static natsOptions*
_createReconnectOptions(void)
{
natsStatus s;
natsOptions *opts = NULL;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222"));
IFOK(s, natsOptions_SetAllowReconnect(opts, true));
IFOK(s, natsOptions_SetMaxReconnect(opts, 10));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
if (s == NATS_OK)
#ifdef WIN32
s = natsOptions_SetTimeout(opts, 500);
#else
s = natsOptions_SetTimeout(opts, NATS_OPTS_DEFAULT_TIMEOUT);
#endif
if (s != NATS_OK)
{
natsOptions_Destroy(opts);
opts = NULL;
}
return opts;
}
static void
_reconnectedCb(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
int64_t now = nats_Now();
natsMutex_Lock(arg->m);
arg->reconnected = true;
arg->reconnects++;
if (arg->control == 9)
{
if (arg->reconnects <= 4)
arg->reconnectedAt[arg->reconnects-1] = now;
}
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
}
static void
test_ReconnectServerStats(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSrv *srv = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsStatistics *stats = NULL;
uint64_t reconnects= 0;
struct threadArg args;
test("Reconnect Server Stats: ");
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to create reconnect options!");
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
s = natsOptions_SetDisconnectedCB(opts, _reconnectedCb, &args);
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_Flush(nc));
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
if (s == NATS_OK)
{
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.reconnected)
s = natsCondition_TimedWait(args.c, args.m, 5000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_FlushTimeout(nc, 5000));
}
if (s == NATS_OK)
{
srv = natsSrvPool_GetCurrentServer(nc->srvPool, nc->cur, NULL);
if (srv == NULL)
s = NATS_ILLEGAL_STATE;
}
testCond((s == NATS_OK) && (srv->reconnects == 0));
test("Tracking Reconnects stats: ");
s = natsStatistics_Create(&stats);
IFOK(s, natsConnection_GetStats(nc, stats));
IFOK(s, natsStatistics_GetCounts(stats, NULL, NULL, NULL, NULL, &reconnects));
testCond((s == NATS_OK) && (reconnects == 1));
natsStatistics_Destroy(stats);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
_destroyDefaultThreadArgs(&args);
}
static void
_disconnectedCb(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
int64_t now = nats_Now();
natsMutex_Lock(arg->m);
arg->disconnected = true;
arg->disconnects++;
if ((arg->control == 9) && (arg->disconnects > 1))
{
if (arg->disconnects <= 5)
arg->disconnectedAt[arg->disconnects-2] = now;
}
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
}
static void
_recvTestString(natsConnection *nc, natsSubscription *sub, natsMsg *msg,
void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
bool doSignal = true;
natsMutex_Lock(arg->m);
switch (arg->control)
{
case 0:
{
if (strncmp(arg->string,
natsMsg_GetData(msg),
natsMsg_GetDataLength(msg)) != 0)
{
arg->status = NATS_ERR;
}
break;
}
case 1:
{
if (sub == NULL)
arg->status = NATS_ERR;
else if (strncmp(arg->string,
natsMsg_GetData(msg),
natsMsg_GetDataLength(msg)) != 0)
{
arg->status = NATS_ERR;
}
break;
}
case 2:
{
if (strcmp(arg->string, natsMsg_GetReply(msg)) != 0)
{
arg->status = NATS_ERR;
}
break;
}
case 3:
case 9:
{
doSignal = false;
arg->sum++;
if ((arg->control != 9) && (arg->sum == 10))
{
arg->status = natsSubscription_Unsubscribe(sub);
doSignal = true;
}
break;
}
case 11:
case 4:
{
arg->status = natsConnection_PublishString(nc,
natsMsg_GetReply(msg),
arg->string);
if (arg->status == NATS_OK)
arg->status = natsConnection_Flush(nc);
if (arg->control == 11)
arg->sum++;
break;
}
case 5:
{
arg->status = natsConnection_Flush(nc);
break;
}
case 6:
{
char seqnoStr[10];
int seqno = 0;
doSignal = false;
snprintf(seqnoStr, sizeof(seqnoStr), "%.*s", natsMsg_GetDataLength(msg),
natsMsg_GetData(msg));
seqno = atoi(seqnoStr);
if (seqno >= 10)
arg->status = NATS_ERR;
else
arg->results[seqno] = (arg->results[seqno] + 1);
break;
}
case 7:
{
arg->msgReceived = true;
natsCondition_Signal(arg->c);
while (!arg->closed)
natsCondition_Wait(arg->c, arg->m);
break;
}
case 8:
{
arg->sum++;
while (!arg->closed)
natsCondition_Wait(arg->c, arg->m);
break;
}
case 10:
{
arg->status = (natsMsg_IsNoResponders(msg) ? NATS_OK : NATS_ERR);
break;
}
}
natsMsg_Destroy(msg);
if (doSignal)
{
arg->msgReceived = true;
natsCondition_Broadcast(arg->c);
}
natsMutex_Unlock(arg->m);
}
static void
_closedCb(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->closed = true;
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
}
static natsStatus
_waitForConnClosed(struct threadArg *arg)
{
natsStatus s = NATS_OK;
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->closed)
s = natsCondition_TimedWait(arg->c, arg->m, 2000);
arg->closed = false;
natsMutex_Unlock(arg->m);
return s;
}
static void
test_ParseStateReconnectFunctionality(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
test("Parse State Reconnect Functionality: ");
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
arg.string = "bar";
arg.status = NATS_OK;
}
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg) != NATS_OK)
|| (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK))
{
FAIL("Unable to create reconnect options!");
}
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg));
IFOK(s, natsConnection_Flush(nc));
if (s == NATS_OK)
{
// Simulate partialState, this needs to be cleared
natsConn_Lock(nc);
nc->ps->state = OP_PON;
natsConn_Unlock(nc);
}
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 500);
natsMutex_Unlock(arg.m);
}
IFOK(s, natsConnection_PublishString(nc, "foo", arg.string));
if (s == NATS_OK)
{
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
}
IFOK(s, natsConnection_FlushTimeout(nc, 5000));
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 1500);
natsMutex_Unlock(arg.m);
if (s == NATS_OK)
s = arg.status;
}
testCond((s == NATS_OK) && (nc->stats.reconnects == 1));
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_waitForConnClosed(&arg);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_ServersRandomize(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsPid pid = NATS_INVALID_PID;
int serversCount;
serversCount = sizeof(testServers) / sizeof(char *);
test("Server Pool with Randomize: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount));
if (s == NATS_OK)
{
int same = 0;
int allSame = 0;
for (int iter=0; (s == NATS_OK) && (iter<1000); iter++)
{
s = natsConn_create(&nc, natsOptions_clone(opts));
if (s == NATS_OK)
{
// In theory, this could happen...
for (int i=0; i<serversCount; i++)
{
if (strcmp(testServers[i],
nc->srvPool->srvrs[i]->url->fullUrl) == 0)
{
same++;
}
}
if (same == serversCount)
allSame++;
}
natsConn_release(nc);
nc = NULL;
}
if (allSame > 10)
s = NATS_ERR;
}
testCond(s == NATS_OK);
// Now test that we do not randomize if proper flag is set.
test("Server Pool With NoRandomize: ")
s = natsOptions_SetNoRandomize(opts, true);
IFOK(s, natsConn_create(&nc, natsOptions_clone(opts)));
if (s == NATS_OK)
{
for (int i=0; i<serversCount; i++)
if (strcmp(testServers[i],
nc->srvPool->srvrs[i]->url->fullUrl) != 0)
{
s = NATS_ERR;
break;
}
}
testCond(s == NATS_OK);
natsConn_release(nc);
nc = NULL;
// 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.
test("If Options.URL is set, it should be first: ")
s = natsOptions_SetNoRandomize(opts, false);
IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL));
IFOK(s, natsConn_create(&nc, natsOptions_clone(opts)));
if (s == NATS_OK)
{
int same = 0;
// In theory, this could happen...
for (int i=0; i<serversCount; i++)
{
if (strcmp(testServers[i],
nc->srvPool->srvrs[i+1]->url->fullUrl) == 0)
{
same++;
}
}
if (same == serversCount)
s = NATS_ERR;
}
if ((s == NATS_OK)
&& strcmp(nc->srvPool->srvrs[0]->url->fullUrl,
NATS_DEFAULT_URL) != 0)
{
s = NATS_ERR;
}
testCond(s == NATS_OK);
natsConn_release(nc);
nc = NULL;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("NoRandomize==true passed to context: ");
s = natsOptions_SetNoRandomize(opts, true);
IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL));
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConn_Lock(nc);
if (!nc->sockCtx.noRandomize)
s = NATS_ERR;
natsConn_Unlock(nc);
}
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
test("NoRandomize==false passed to context: ");
s = natsOptions_SetNoRandomize(opts, false);
IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL));
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConn_Lock(nc);
if (nc->sockCtx.noRandomize)
s = NATS_ERR;
natsConn_Unlock(nc);
}
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(pid);
}
static void
test_SelectNextServer(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsSrv *srv = NULL;
int serversCount;
serversCount = sizeof(testServers) / sizeof(char *);
test("Test default server pool selection: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount));
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsConn_create(&nc, natsOptions_clone(opts)));
testCond((s == NATS_OK)
&& (nc->cur->url == nc->srvPool->srvrs[0]->url));
test("Get next server: ");
if (s == NATS_OK)
{
srv = natsSrvPool_GetNextServer(nc->srvPool, nc->opts, nc->cur);
if (srv != NULL)
nc->cur = srv;
}
testCond((s == NATS_OK)
&& (nc != NULL)
&& (nc->cur != NULL));
test("Check list size: ");
testCond((s == NATS_OK)
&& (nc != NULL)
&& (nc->srvPool != NULL)
&& (nc->srvPool->size == serversCount));
test("Check selection: ");
testCond((s == NATS_OK)
&& (nc != NULL)
&& (nc->cur->url != NULL)
&& (nc->cur->url->fullUrl != NULL)
&& (strcmp(nc->cur->url->fullUrl, testServers[1]) == 0));
test("Check old was pushed to last position: ");
testCond((s == NATS_OK)
&& (nc != NULL)
&& (nc->srvPool != NULL)
&& (nc->srvPool->srvrs != NULL)
&& (nc->srvPool->size > 0)
&& (nc->srvPool->srvrs[nc->srvPool->size - 1] != NULL)
&& (nc->srvPool->srvrs[nc->srvPool->size - 1]->url != NULL)
&& (nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl != NULL)
&& (strcmp(nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl,
testServers[0]) == 0));
test("Got correct server: ");
testCond((s == NATS_OK)
&& (srv != NULL)
&& (nc != NULL)
&& (nc->srvPool != NULL)
&& (nc->srvPool->srvrs != NULL)
&& (nc->srvPool->size > 0)
&& (srv == (nc->srvPool->srvrs[0])));
// Test that we do not keep servers where we have tried to reconnect past our limit.
if (s == NATS_OK)
{
test("Get next server: ");
if ((nc == NULL)
|| (nc->srvPool == NULL)
|| (nc->srvPool->srvrs == NULL)
|| (nc->srvPool->srvrs[0] == NULL))
{
s = NATS_ERR;
}
else
{
nc->srvPool->srvrs[0]->reconnects = nc->opts->maxReconnect;
}
if (s == NATS_OK)
{
srv = natsSrvPool_GetNextServer(nc->srvPool, nc->opts, nc->cur);
if (srv != NULL)
nc->cur = srv;
}
testCond((s == NATS_OK) && (nc->cur != NULL));
}
// Check that we are now looking at #3, and current is not in the list.
test("Check list size: ");
testCond((s == NATS_OK)
&& (nc->srvPool->size == (serversCount - 1)));
test("Check selection: ");
testCond((s == NATS_OK)
&& (nc != NULL)
&& (nc->cur != NULL)
&& (nc->cur->url != NULL)
&& (nc->cur->url->fullUrl != NULL)
&& (strcmp(nc->cur->url->fullUrl, testServers[2]) == 0));
test("Check last server was discarded: ");
testCond((s == NATS_OK)
&& (nc != NULL)
&& (nc->srvPool != NULL)
&& (nc->srvPool->srvrs != NULL)
&& (nc->srvPool->size > 0)
&& (nc->srvPool->srvrs[nc->srvPool->size - 1] != NULL)
&& (nc->srvPool->srvrs[nc->srvPool->size - 1]->url != NULL)
&& (nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl != NULL)
&& (strcmp(nc->srvPool->srvrs[nc->srvPool->size - 1]->url->fullUrl,
testServers[1]) != 0));
natsConn_release(nc);
natsOptions_Destroy(opts);
}
static void
parserNegTest(int lineNum)
{
char txt[64];
snprintf(txt, sizeof(txt), "Test line %d: ", lineNum);
test(txt);
}
#define PARSER_START_TEST parserNegTest(__LINE__)
static void
test_ParserPing(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char ping[64];
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
IFOK(s, natsBuf_Create(&(nc->pending), 1000));
if (s == NATS_OK)
nc->usePending = true;
if (s != NATS_OK)
FAIL("Unable to setup test");
PARSER_START_TEST;
testCond(nc->ps->state == OP_START);
snprintf(ping, sizeof(ping), "PING\r\n");
PARSER_START_TEST;
s = natsParser_Parse(nc, ping, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_P));
PARSER_START_TEST;
s = natsParser_Parse(nc, ping + 1, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_PI));
PARSER_START_TEST;
s = natsParser_Parse(nc, ping + 2, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_PIN));
PARSER_START_TEST;
s = natsParser_Parse(nc, ping + 3, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_PING));
PARSER_START_TEST;
s = natsParser_Parse(nc, ping + 4, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_PING));
PARSER_START_TEST;
s = natsParser_Parse(nc, ping + 5, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
PARSER_START_TEST;
s = natsParser_Parse(nc, ping, (int)strlen(ping));
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
// Should tolerate spaces
snprintf(ping, sizeof(ping), "%s", "PING \r");
PARSER_START_TEST;
s = natsParser_Parse(nc, ping, (int)strlen(ping));
testCond((s == NATS_OK) && (nc->ps->state == OP_PING));
nc->ps->state = OP_START;
snprintf(ping, sizeof(ping), "%s", "PING \r \n");
PARSER_START_TEST;
s = natsParser_Parse(nc, ping, (int)strlen(ping));
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
natsConnection_Destroy(nc);
}
static void
test_ParserErr(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char errProto[1024];
char expected[256];
int len;
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
IFOK(s, natsBuf_Create(&(nc->pending), 1000));
if (s == NATS_OK)
{
nc->usePending = true;
nc->status = NATS_CONN_STATUS_CLOSED;
}
if (s != NATS_OK)
FAIL("Unable to setup test");
// This test focuses on the parser only, not how the error is
// actually processed by the upper layer.
PARSER_START_TEST;
testCond(nc->ps->state == OP_START);
snprintf(expected, sizeof(expected), "%s", "'Any kind of error'");
snprintf(errProto, sizeof(errProto), "-ERR %s\r\n", expected);
len = (int) strlen(errProto);
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS));
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 1, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_E));
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 2, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ER));
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 3, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ERR));
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 4, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ERR_SPC));
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 5, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_MINUS_ERR_SPC));
// Check with split arg buffer
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 6, 1);
testCond((s == NATS_OK) && (nc->ps->state == MINUS_ERR_ARG));
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 7, 3);
testCond((s == NATS_OK) && (nc->ps->state == MINUS_ERR_ARG));
// Verify content
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + 10, len - 10 - 2);
testCond((s == NATS_OK)
&& (nc->ps->state == MINUS_ERR_ARG)
&& (nc->ps->argBuf != NULL)
&& (strncmp(nc->ps->argBuf->data, expected, nc->ps->argBuf->len) == 0));
// Finish parsing
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto + len - 1, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
// Check without split arg buffer
snprintf(errProto, sizeof(errProto), "-ERR '%s'\r\n", "Any Error");
PARSER_START_TEST;
s = natsParser_Parse(nc, errProto, (int)strlen(errProto));
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
natsConnection_Destroy(nc);
}
static void
test_ParserOK(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char okProto[256];
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup test");
PARSER_START_TEST;
testCond(nc->ps->state == OP_START);
snprintf(okProto, sizeof(okProto), "+OKay\r\n");
PARSER_START_TEST;
s = natsParser_Parse(nc, okProto, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_PLUS));
PARSER_START_TEST;
s = natsParser_Parse(nc, okProto + 1, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_PLUS_O));
PARSER_START_TEST;
s = natsParser_Parse(nc, okProto + 2, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_PLUS_OK));
PARSER_START_TEST;
s = natsParser_Parse(nc, okProto + 3, (int)strlen(okProto) - 3);
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
natsConnection_Destroy(nc);
}
static void
test_ParseINFO(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char infoProto[256];
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup test");
PARSER_START_TEST;
testCond(nc->ps->state == OP_START);
snprintf(infoProto, sizeof(infoProto), "INFO \t{\"server_id\": \"abc\"}\r\n");
PARSER_START_TEST;
s = natsParser_Parse(nc, infoProto, 7);
testCond((s == NATS_OK) && (nc->ps->state == INFO_ARG) && (infoProto[nc->ps->afterSpace] == '{'));
PARSER_START_TEST;
s = natsParser_Parse(nc, infoProto +7, (int)strlen(infoProto) - 7);
testCond((s == NATS_OK)
&& (nc->ps->state == OP_START)
&& (nc->info.id != NULL) && (strcmp(nc->info.id, "abc") == 0));
natsConnection_Destroy(nc);
}
static void
test_ParserShouldFail(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char buf[64];
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup test");
// Negative tests:
PARSER_START_TEST;
snprintf(buf, sizeof(buf), "%s", " PING");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "POO");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "Px");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "PIx");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "PINx");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
// Stop here because 'PING' protos are tolerant for anything between PING and \n
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "POx");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "PONx");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
// Stop here because 'PONG' protos are tolerant for anything between PONG and \n
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "ZOO");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "Mx\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSx\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSGx\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSG foo\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSG \r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSG foo 1\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSG foo bar 1\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSG foo bar 1 baz\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "MSG foo 1 bar baz\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "+x\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "+Ox\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "-x\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "-Ex\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "-ERx\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
PARSER_START_TEST;
nc->ps->state = OP_START;
snprintf(buf, sizeof(buf), "%s", "-ERRx\r\n");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond(s != NATS_OK);
natsConnection_Destroy(nc);
}
static void
test_ParserSplitMsg(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char buf[2*MAX_CONTROL_LINE_SIZE];
uint64_t expectedCount;
uint64_t expectedSize;
int msgSize, start, i;
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup test");
expectedCount = 1;
expectedSize = 3;
snprintf(buf, sizeof(buf), "%s", "MSG a 1 3\r\nfoo\r\n");
// parsing: 'MSG a'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, 5);
testCond((s == NATS_OK) && (nc->ps->argBuf != NULL));
// parsing: ' 1 3\r\nf'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf + 5, 7);
testCond((s == NATS_OK)
&& (nc->ps->ma.size == 3)
&& (nc->ps->ma.sid == 1)
&& (nc->ps->ma.subject->len == 1)
&& (strncmp(nc->ps->ma.subject->data, "a", 1) == 0)
&& (nc->ps->msgBuf != NULL));
// parsing: 'oo\r\n'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf + 12, (int)strlen(buf) - 12);
testCond((s == NATS_OK)
&& (nc->stats.inMsgs == expectedCount)
&& (nc->stats.inBytes == expectedSize)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->state == OP_START));
// parsing: 'MSG a 1 3\r\nfo'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, 13);
testCond((s == NATS_OK)
&& (nc->ps->ma.size == 3)
&& (nc->ps->ma.sid == 1)
&& (nc->ps->ma.subject->len == 1)
&& (strncmp(nc->ps->ma.subject->data, "a", 1) == 0)
&& (nc->ps->argBuf != NULL)
&& (nc->ps->msgBuf != NULL));
expectedCount++;
expectedSize += 3;
// parsing: 'o\r\n'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf + 13, (int)strlen(buf) - 13);
testCond((s == NATS_OK)
&& (nc->stats.inMsgs == expectedCount)
&& (nc->stats.inBytes == expectedSize)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->state == OP_START));
snprintf(buf, sizeof(buf), "%s", "MSG a 1 6\r\nfoobar\r\n");
// parsing: 'MSG a 1 6\r\nfo'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, 13);
testCond((s == NATS_OK)
&& (nc->ps->ma.size == 6)
&& (nc->ps->ma.sid == 1)
&& (nc->ps->ma.subject->len == 1)
&& (strncmp(nc->ps->ma.subject->data, "a", 1) == 0)
&& (nc->ps->argBuf != NULL)
&& (nc->ps->msgBuf != NULL));
// parsing: 'ob'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf + 13, 2);
testCond(s == NATS_OK)
expectedCount++;
expectedSize += 6;
// parsing: 'ar\r\n'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf + 15, (int)strlen(buf) - 15);
testCond((s == NATS_OK)
&& (nc->stats.inMsgs == expectedCount)
&& (nc->stats.inBytes == expectedSize)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->state == OP_START));
// 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 = sizeof(nc->ps->scratch) + 100 + 3;
snprintf(buf, sizeof(buf), "MSG a 1 b %d\r\nfoo", msgSize);
start = (int)strlen(buf);
for (i=0; i<msgSize - 3; i++)
buf[start + i] = 'a' + (i % 26);
buf[start + i++] = '\r';
buf[start + i++] = '\n';
buf[start + i++] = '\0';
// parsing: 'MSG a 1 b <size>\r\nfoo'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, start);
testCond((s == NATS_OK)
&& (nc->ps->ma.size == msgSize)
&& (nc->ps->ma.sid == 1)
&& (nc->ps->ma.subject->len == 1)
&& (strncmp(nc->ps->ma.subject->data, "a", 1) == 0)
&& (nc->ps->ma.reply->len == 1)
&& (strncmp(nc->ps->ma.reply->data, "b", 1) == 0)
&& (nc->ps->argBuf != NULL)
&& (nc->ps->msgBuf != NULL));
expectedCount++;
expectedSize += (uint64_t)msgSize;
// parsing: 'abcde...'
PARSER_START_TEST;
s = natsParser_Parse(nc, buf + start, (int)strlen(buf) - start - 2);
testCond((s == NATS_OK)
&& (nc->ps->argBuf != NULL)
&& (nc->ps->msgBuf != NULL)
&& (nc->ps->state == MSG_PAYLOAD));
// Verify content
PARSER_START_TEST;
s = ((strncmp(nc->ps->msgBuf->data, "foo", 3) == 0) ? NATS_OK : NATS_ERR);
if (s == NATS_OK)
{
int k;
for (k=3; (s == NATS_OK) && (k<nc->ps->ma.size); k++)
s = (nc->ps->msgBuf->data[k] == (char)('a' + ((k-3) % 26)) ? NATS_OK : NATS_ERR);
}
testCond(s == NATS_OK)
PARSER_START_TEST;
s = natsParser_Parse(nc, buf + (int)strlen(buf) - 2, 2);
testCond((s == NATS_OK)
&& (nc->stats.inMsgs == expectedCount)
&& (nc->stats.inBytes == expectedSize)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->state == OP_START));
natsConnection_Destroy(nc);
}
#define RECREATE_PARSER \
natsParser_Destroy(nc->ps); \
s = natsParser_Create(&(nc->ps)); \
if (s != NATS_OK) \
FAIL("Unable to setup test"); \
static void
test_ProcessMsgArgs(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char buf[2048];
const char* le = NULL;
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup test");
snprintf(buf, sizeof(buf), "%s", "MSG a b c d e\r\n");
test("Parsing MSG with too many arguments: ")
// parsing: 'MSG a'
natsParser_Parse(nc, buf, 5);
// parse the rest..
natsParser_Parse(nc, buf + 5, 10);
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "wrong number of arguments") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG foo 1\r\n");
test("Parsing MSG with not enough arguments: ")
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "wrong number of arguments") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG foo abc 2\r\n");
test("Parsing MSG with bad sid: ")
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Sid") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG foo 1 abc\r\n");
test("Parsing MSG with bad size: ")
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Size") != NULL));
snprintf(buf, sizeof(buf), "%s", "MSG foo 1 bar abc\r\n");
test("Parsing MSG with bad size (with reply): ")
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Size") != NULL));
// Test extra spaces first without reply
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG foo 1 2\r\n");
test("Parsing MSG with extra space before sid: ")
s = natsParser_Parse(nc, buf, (int) strlen(buf));
testCond((s == NATS_OK)
&& (natsBuf_Len(nc->ps->ma.subject) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.subject), "foo", 3) == 0)
&& (nc->ps->ma.sid == 1)
&& (nc->ps->ma.reply == NULL)
&& (nc->ps->ma.size == 2));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG bar 1 2\r\n");
test("Parsing MSG with extra space before size: ")
s = natsParser_Parse(nc, buf, (int) strlen(buf));
testCond((s == NATS_OK)
&& (natsBuf_Len(nc->ps->ma.subject) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.subject), "bar", 3) == 0)
&& (nc->ps->ma.sid == 1)
&& (nc->ps->ma.reply == NULL)
&& (nc->ps->ma.size == 2));
// Test extra spaces first with reply
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG baz 3 bat 4\r\n");
test("Parsing MSG with extra space before sid: ")
s = natsParser_Parse(nc, buf, (int) strlen(buf));
testCond((s == NATS_OK)
&& (natsBuf_Len(nc->ps->ma.subject) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.subject), "baz", 3) == 0)
&& (nc->ps->ma.sid == 3)
&& (natsBuf_Len(nc->ps->ma.reply) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.reply), "bat", 3) == 0)
&& (nc->ps->ma.size == 4));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG boo 5 baa 6\r\n");
test("Parsing MSG with extra space before reply: ")
s = natsParser_Parse(nc, buf, (int) strlen(buf));
testCond((s == NATS_OK)
&& (natsBuf_Len(nc->ps->ma.subject) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.subject), "boo", 3) == 0)
&& (nc->ps->ma.sid == 5)
&& (natsBuf_Len(nc->ps->ma.reply) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.reply), "baa", 3) == 0)
&& (nc->ps->ma.size == 6));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG coo 7 caa 8\r\n");
test("Parsing MSG with extra space before size: ")
s = natsParser_Parse(nc, buf, (int) strlen(buf));
testCond((s == NATS_OK)
&& (natsBuf_Len(nc->ps->ma.subject) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.subject), "coo", 3) == 0)
&& (nc->ps->ma.sid == 7)
&& (natsBuf_Len(nc->ps->ma.reply) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.reply), "caa", 3) == 0)
&& (nc->ps->ma.size == 8));
// Test with extra space everywhere
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "MSG doo 8 daa 9 \r\n");
test("Parsing MSG with extra space everywhere: ")
s = natsParser_Parse(nc, buf, (int) strlen(buf));
testCond((s == NATS_OK)
&& (natsBuf_Len(nc->ps->ma.subject) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.subject), "doo", 3) == 0)
&& (nc->ps->ma.sid == 8)
&& (natsBuf_Len(nc->ps->ma.reply) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.reply), "daa", 3) == 0)
&& (nc->ps->ma.size == 9));
// Test HMSG
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 bar 2 3\r\n");
test("Parsing HMSG: ");
s = natsParser_Parse(nc, buf, (int) strlen(buf));
testCond((s == NATS_OK)
&& (natsBuf_Len(nc->ps->ma.subject) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.subject), "foo", 3) == 0)
&& (nc->ps->ma.sid == 1)
&& (natsBuf_Len(nc->ps->ma.reply) == 3)
&& (strncmp(natsBuf_Data(nc->ps->ma.reply), "bar", 3) == 0)
&& (nc->ps->ma.hdr == 2)
&& (nc->ps->ma.size == 3));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 3\r\n");
test("Parsing HMSG not enough args: ");
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "wrong number of arguments") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG a b c d e f\r\n");
test("Parsing HMSG too many args: ");
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "wrong number of arguments") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG foo abc 2 4\r\n");
test("Parsing HMSG with bad sid: ");
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Sid") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 baz 10\r\n");
test("Parsing HMSG with bad header size: ");
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Header Size") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 bar baz 10\r\n");
test("Parsing HMSG with bad header size (with reply): ");
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Header Size") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 10 4\r\n");
test("Parsing HMSG with bad header size (out of range): ");
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Header Size") != NULL));
RECREATE_PARSER;
snprintf(buf, sizeof(buf), "%s", "HMSG foo 1 bar 10 4\r\n");
test("Parsing HMSG with bad header size (out of range with reply): ");
natsParser_Parse(nc, buf, (int) strlen(buf));
s = natsConnection_GetLastError(nc, &le);
testCond((s == NATS_PROTOCOL_ERROR)
&& (nc->ps->argBuf == NULL)
&& (nc->ps->msgBuf == NULL)
&& (nc->ps->ma.subject == NULL)
&& (nc->ps->ma.reply == NULL)
&& (le != NULL)
&& (strstr(le, "Bad or Missing Header Size") != NULL));
natsConnection_Destroy(nc);
}
static natsStatus
_checkPool(natsConnection *nc, char **expectedURLs, int expectedURLsCount)
{
int i, j, attempts;
natsSrv *srv;
char *url;
char buf[64];
bool ok;
natsMutex_Lock(nc->mu);
if (nc->srvPool->size != expectedURLsCount)
{
printf("Expected pool size to be %d, got %d\n", expectedURLsCount, nc->srvPool->size);
natsMutex_Unlock(nc->mu);
return NATS_ERR;
}
for (attempts=0; attempts<20; attempts++)
{
for (i=0; i<expectedURLsCount; i++)
{
url = expectedURLs[i];
ok = false;
for (j=0; j<nc->srvPool->size; j++)
{
srv = nc->srvPool->srvrs[j];
snprintf(buf, sizeof(buf), "%s:%d", srv->url->host, srv->url->port);
if (strcmp(buf, url))
{
ok = true;
break;
}
}
if (!ok)
{
natsMutex_Unlock(nc->mu);
nats_Sleep(100);
natsMutex_Lock(nc->mu);
continue;
}
}
natsMutex_Unlock(nc->mu);
return NATS_OK;
}
natsMutex_Unlock(nc->mu);
return NATS_ERR;
}
static natsStatus
checkNewURLsAddedRandomly(natsConnection *nc, char **urlsAfterPoolSetup, int initialPoolSize)
{
natsStatus s;
int i;
char **currentPool = NULL;
int currentPoolSize = 0;
s = natsConnection_GetServers(nc, &currentPool, &currentPoolSize);
if (s == NATS_OK)
{
// Reset status to error by default. If we find a new URL
// before the end of the initial list, we consider success
// and return.
s = NATS_ERR;
for (i= 0; i < initialPoolSize; i++)
{
// If one of the position in initial list is occupied
// by a new URL, we are ok.
if (strcmp(urlsAfterPoolSetup[i], currentPool[i]))
{
s = NATS_OK;
break;
}
}
}
if (currentPool != NULL)
{
for (i=0; i<currentPoolSize; i++)
free(currentPool[i]);
free(currentPool);
}
return s;
}
static void
test_AsyncINFO(void)
{
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsStatus s;
char buf[2048];
int i;
const char *good[] = {"INFO {}\r\n", "INFO {}\r\n", "INFO {} \r\n",
"INFO { \"server_id\": \"test\" } \r\n",
"INFO {\"connect_urls\":[]}\r\n"};
const char *wrong[] = {"IxNFO {}\r\n", "INxFO {}\r\n", "INFxO {}\r\n",
"INFOx {}\r\n", "INFO{}\r\n", "INFO {}"};
const char *lastErr = NULL;
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup test");
snprintf(buf, sizeof(buf), "%s", "INFO {\"test\":\"abcde\"x\r\n");
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, 9);
testCond((s == NATS_OK)
&& (nc->ps->state == INFO_ARG)
&& (nc->ps->argBuf != NULL));
PARSER_START_TEST;
natsParser_Parse(nc, buf+9, 30);
lastErr = nats_GetLastError(&s);
testCond((s == NATS_ERR)
&& (lastErr != NULL)
&& (strstr(lastErr, "missing") != NULL));
nats_clearLastError();
snprintf(buf, sizeof(buf), "%s", "INFO {}\r\n");
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_I));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf+1, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_IN));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf+2, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_INF));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf+3, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_INFO));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf+4, 1);
testCond((s == NATS_OK) && (nc->ps->state == OP_INFO_SPC));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf+5, (int)strlen(buf)-5);
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
// All at once
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, (int)strlen(buf));
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
// Server pool was setup in natsConn_create()
// Partials requiring argBuf
snprintf(buf, sizeof(buf), "INFO {\"server_id\":\"%s\", \"host\":\"%s\", \"port\": %d, " \
"\"auth_required\":%s, \"tls_required\": %s, \"max_payload\":%d}\r\n",
"test", "localhost", 4222, "true", "true", 2*1024*1024);
PARSER_START_TEST;
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, 9);
testCond((s == NATS_OK)
&& (nc->ps->state == INFO_ARG)
&& (nc->ps->argBuf != NULL));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf+9, 2);
testCond((s == NATS_OK)
&& (nc->ps->state == INFO_ARG)
&& (nc->ps->argBuf != NULL));
PARSER_START_TEST;
s = natsParser_Parse(nc, buf+11, (int)strlen(buf)-11);
testCond((s == NATS_OK)
&& (nc->ps->state == OP_START)
&& (nc->ps->argBuf == NULL));
test("Check INFO is correct: ");
testCond((s == NATS_OK)
&& (strcmp(nc->info.id, "test") == 0)
&& (strcmp(nc->info.host, "localhost") == 0)
&& (nc->info.port == 4222)
&& nc->info.authRequired
&& nc->info.tlsRequired
&& (nc->info.maxPayload == 2*1024*1024));
// Destroy parser, it will be recreated in the loops.
natsParser_Destroy(nc->ps);
nc->ps = NULL;
// Good INFOs
for (i=0; i<(int) (sizeof(good)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "Test with good INFO proto number %d: ", (i+1));
test(buf);
s = natsParser_Create(&(nc->ps));
IFOK(s, natsParser_Parse(nc, (char*) good[i], (int)strlen(good[i])));
testCond((s == NATS_OK)
&& (nc->ps->state == OP_START)
&& (nc->ps->argBuf == NULL));
natsParser_Destroy(nc->ps);
nc->ps = NULL;
}
// Wrong INFOs
for (i=0; i<(int) (sizeof(wrong)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "Test with wrong INFO proto number %d: ", (i+1));
test(buf);
s = natsParser_Create(&(nc->ps));
IFOK(s, natsParser_Parse(nc, (char*) wrong[i], (int)strlen(wrong[i])));
testCond(!((s == NATS_OK) && (nc->ps->state == OP_START)));
natsParser_Destroy(nc->ps);
nc->ps = NULL;
}
nats_clearLastError();
// Now test the decoding of "connect_urls"
// Destroy, we create a new one
natsConnection_Destroy(nc);
nc = NULL;
s = natsOptions_Create(&opts);
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
if (s != NATS_OK)
FAIL("Unable to setup test");
snprintf(buf, sizeof(buf), "%s", "INFO {\"connect_urls\":[\"localhost:4222\",\"localhost:5222\"]}\r\n");
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, (int)strlen(buf));
if (s == NATS_OK)
{
// Pool now should contain localhost:4222 (the default URL) and localhost:5222
const char *urls[] = {"localhost:4222", "localhost:5222"};
s = _checkPool(nc, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
}
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
// Make sure that if client receives the same, it is not added again.
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, (int)strlen(buf));
if (s == NATS_OK)
{
// Pool should still contain localhost:4222 (the default URL) and localhost:5222
const char *urls[] = {"localhost:4222", "localhost:5222"};
s = _checkPool(nc, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
}
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
// Receive a new URL
snprintf(buf, sizeof(buf), "%s", "INFO {\"connect_urls\":[\"localhost:4222\",\"localhost:5222\",\"localhost:6222\"]}\r\n");
PARSER_START_TEST;
s = natsParser_Parse(nc, buf, (int)strlen(buf));
if (s == NATS_OK)
{
// Pool now should contain localhost:4222 (the default URL) localhost:5222 and localhost:6222
const char *urls[] = {"localhost:4222", "localhost:5222", "localhost:6222"};
s = _checkPool(nc, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
}
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
natsConnection_Destroy(nc);
nc = NULL;
// Check that new URLs are added at random places in the pool
if (s == NATS_OK)
{
int initialPoolSize = 0;
char **urlsAfterPoolSetup = NULL;
const char *newURLs = "\"impA:4222\", \"impB:4222\", \"impC:4222\", "\
"\"impD:4222\", \"impE:4222\", \"impF:4222\", \"impG:4222\", "\
"\"impH:4222\", \"impI:4222\", \"impJ:4222\"";
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, false));
IFOK(s, natsOptions_SetServers(opts, testServers, sizeof(testServers)/sizeof(char*)));
IFOK(s, natsConn_create(&nc, opts));
IFOK(s, natsParser_Create(&(nc->ps)));
// Capture the pool sequence after randomization
IFOK(s, natsConnection_GetServers(nc, &urlsAfterPoolSetup, &initialPoolSize));
if (s != NATS_OK)
FAIL("Unable to setup test");
// Add new urls
snprintf(buf, sizeof(buf), "INFO {\"connect_urls\":[%s]}\r\n", newURLs);
test("New URLs are added randomly: ");
s = natsParser_Parse(nc, buf, (int)strlen(buf));
IFOK(s, checkNewURLsAddedRandomly(nc, urlsAfterPoolSetup, initialPoolSize));
testCond((s == NATS_OK) && (nc->ps->state == OP_START));
test("First URL should not have been changed: ")
testCond((s == NATS_OK) && !strcmp(nc->srvPool->srvrs[0]->url->fullUrl, urlsAfterPoolSetup[0]));
if (urlsAfterPoolSetup != NULL)
{
for (i=0; i<initialPoolSize; i++)
free(urlsAfterPoolSetup[i]);
free(urlsAfterPoolSetup);
}
natsConnection_Destroy(nc);
}
}
static void
_parallelRequests(void *closure)
{
natsConnection *nc = (natsConnection*) closure;
natsMsg *msg;
// Expecting this to timeout. This is to force parallel
// requests.
natsConnection_RequestString(&msg, nc, "foo", "test", 500);
}
static void
test_RequestPool(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
int i;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
int numThreads = RESP_INFO_POOL_MAX_SIZE+5;
natsThread *threads[RESP_INFO_POOL_MAX_SIZE+5];
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
if (s != NATS_OK)
FAIL("Unable to setup test!");
// For part of this test, we really need to have the requestor
// timeout with the given timeout value (to increase number)
// of parallel requests. So we create a sync subscription that
// will not send a response back.
s = natsConnection_SubscribeSync(&sub, nc, "foo");
if (s != NATS_OK)
{
natsConnection_Destroy(nc);
FAIL("Unable to setup test!");
}
// With current implementation, the pool should not
// increase at all.
test("Pool not growing: ");
for (i=0; (i<RESP_INFO_POOL_MAX_SIZE); i++)
natsConnection_RequestString(&msg, nc, "foo", "test", 1);
natsMutex_Lock(nc->mu);
testCond(nc->respPoolSize == 1);
natsMutex_Unlock(nc->mu);
test("Pool max size: ");
for (i=0; i<numThreads; i++)
threads[i] = NULL;
s = NATS_OK;
for (i=0; (s == NATS_OK) && (i<numThreads); i++)
s = natsThread_Create(&threads[i], _parallelRequests, (void*) nc);
for (i=0; i<numThreads; i++)
{
if (threads[i] != NULL)
{
natsThread_Join(threads[i]);
natsThread_Destroy(threads[i]);
}
}
natsMutex_Lock(nc->mu);
testCond((s == NATS_OK) && (nc->respPoolSize == RESP_INFO_POOL_MAX_SIZE));
natsMutex_Unlock(nc->mu);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(pid);
}
static void
test_NoFlusherIfSendAsap(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
struct threadArg arg;
int i;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetURL(opts, "nats://127.0.0.1:4222") != NATS_OK)
|| (natsOptions_SetSendAsap(opts, true) != NATS_OK)
|| (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)))
{
FAIL("Failed to setup test");
}
arg.string = "test";
arg.control = 1;
pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true);
CHECK_SERVER_STARTED(pid);
test("Connect/subscribe ok: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
for (i=0; i<2; i++)
{
test("Send ok: ");
s = natsConnection_PublishString(nc, "foo", "test");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 1500);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Flusher does not exist: ");
natsMutex_Lock(nc->mu);
s = (nc->flusherThread == NULL ? NATS_OK : NATS_ERR);
natsMutex_Unlock(nc->mu);
testCond(s == NATS_OK);
if (i == 0)
{
_stopServer(pid);
pid = NATS_INVALID_PID;
pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true);
CHECK_SERVER_STARTED(pid);
}
}
natsSubscription_Destroy(sub);
natsConnection_Close(nc);
_waitForConnClosed(&arg);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
test_HeadersAndSubPendingBytes(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
natsConnection *nc = NULL;
natsSubscription *sub1 = NULL;
natsSubscription *sub2 = NULL;
natsMsg *msg = NULL;
natsMsg *smsg = NULL;
int msgs = 0;
int bytes = 0;
struct threadArg arg;
int i;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test");
arg.string = "test";
pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true);
CHECK_SERVER_STARTED(pid);
test("Connect/subscribe ok: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub1, nc, "foo", _recvTestString, (void*) &arg));
IFOK(s, natsSubscription_SetPendingLimits(sub1, 1000, 100));
IFOK(s, natsConnection_SubscribeSync(&sub2, nc, "foo"));
IFOK(s, natsSubscription_SetPendingLimits(sub2, 1000, 100));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Create message with header: ");
s = natsMsg_Create(&msg, "foo", NULL, "hello", 5);
// Set a header with "large" value (50 bytes)
IFOK(s, natsMsgHeader_Set(msg, "Key", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
testCond(s == NATS_OK);
for (i=0; i<10; i++) {
test("Publish and receive message: ");
s = natsConnection_PublishMsg(nc, msg);
IFOK(s, natsSubscription_NextMsg(&smsg, sub2, 1000));
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
arg.msgReceived = false;
natsMutex_Unlock(arg.m);
}
natsMsg_Destroy(smsg);
smsg = NULL;
testCond(s == NATS_OK);
}
test("Check sub1's pending: ");
s = natsSubscription_GetPending(sub1, &msgs, &bytes);
testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0));
test("Check sub2's pending: ");
s = natsSubscription_GetPending(sub1, &msgs, &bytes);
testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub1);
natsSubscription_Destroy(sub2);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
_dummyMsgHandler(natsConnection *nc, natsSubscription *sub, natsMsg *msg,
void *closure)
{
// do nothing
natsMsg_Destroy(msg);
}
static void
test_LibMsgDelivery(void)
{
natsStatus s;
natsPid serverPid = NATS_INVALID_PID;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsSubscription *s1 = NULL;
natsSubscription *s2 = NULL;
natsSubscription *s3 = NULL;
natsSubscription *s4 = NULL;
natsSubscription *s5 = NULL;
natsMsgDlvWorker *lmd1 = NULL;
natsMsgDlvWorker *lmd2 = NULL;
natsMsgDlvWorker *lmd3 = NULL;
natsMsgDlvWorker *lmd4 = NULL;
natsMsgDlvWorker *lmd5 = NULL;
natsMsgDlvWorker **pwks = NULL;
int psize = 0;
int pmaxSize = 0;
int pidx = 0;
// First, close the library and re-open, to reset things
nats_Close();
nats_Sleep(100);
nats_Open(-1);
// Check some pre-conditions that need to be met for the test to work.
test("Check initial values: ")
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
testCond((pmaxSize == 1) && (psize == 0) && (pidx == 0));
test("Check pool size not negative: ")
s = nats_SetMessageDeliveryPoolSize(-1);
testCond(s != NATS_OK);
test("Check pool size not zero: ")
s = nats_SetMessageDeliveryPoolSize(0);
testCond(s != NATS_OK);
// Reset stack since we know the above generated errors.
nats_clearLastError();
test("Increase size to 2: ")
s = nats_SetMessageDeliveryPoolSize(2);
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
testCond((s == NATS_OK) && (pmaxSize == 2) && (psize == 0));
test("Check pool size decreased (no error): ")
s = nats_SetMessageDeliveryPoolSize(1);
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
testCond((s == NATS_OK) && (pmaxSize == 2) && (psize == 0));
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_Subscribe(&s1, nc, "foo", _dummyMsgHandler, NULL));
if (s == NATS_OK)
{
natsMutex_Lock(s1->mu);
lmd1 = s1->libDlvWorker;
natsMutex_Unlock(s1->mu);
}
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
test("Check 1st sub assigned 1st worker: ")
testCond((s == NATS_OK) && (psize == 1) && (lmd1 != NULL)
&& (pidx == 1) && (pwks != NULL) && (lmd1 == pwks[0]));
s = natsConnection_Subscribe(&s2, nc, "foo", _dummyMsgHandler, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(s2->mu);
lmd2 = s2->libDlvWorker;
natsMutex_Unlock(s2->mu);
}
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
test("Check 2nd sub assigned 2nd worker: ")
testCond((s == NATS_OK) && (psize == 2) && (lmd2 != lmd1)
&& (pidx == 0) && (pwks != NULL) && (lmd2 == pwks[1]));
s = natsConnection_Subscribe(&s3, nc, "foo", _dummyMsgHandler, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(s3->mu);
lmd3 = s3->libDlvWorker;
natsMutex_Unlock(s3->mu);
}
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
test("Check 3rd sub assigned 1st worker: ")
testCond((s == NATS_OK) && (psize == 2) && (lmd3 == lmd1)
&& (pidx == 1) && (pwks != NULL) && (lmd3 == pwks[0]));
// Bump the pool size to 4
s = nats_SetMessageDeliveryPoolSize(4);
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
test("Check increase of pool size: ");
testCond((s == NATS_OK) && (psize == 2) && (pidx == 1)
&& (pmaxSize == 4) && (pwks != NULL));
s = natsConnection_Subscribe(&s4, nc, "foo", _dummyMsgHandler, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(s4->mu);
lmd4 = s4->libDlvWorker;
natsMutex_Unlock(s4->mu);
}
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
test("Check 4th sub assigned 2nd worker: ")
testCond((s == NATS_OK) && (psize == 2) && (lmd4 == lmd2)
&& (pidx == 2) && (pwks != NULL) && (lmd4 == pwks[1]));
s = natsConnection_Subscribe(&s5, nc, "foo", _dummyMsgHandler, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(s5->mu);
lmd5 = s5->libDlvWorker;
natsMutex_Unlock(s5->mu);
}
natsLib_getMsgDeliveryPoolInfo(&pmaxSize, &psize, &pidx, &pwks);
test("Check 5th sub assigned 3rd worker: ")
testCond((s == NATS_OK) && (psize == 3) && (lmd5 != lmd2)
&& (pidx == 3) && (pwks != NULL) && (lmd5 == pwks[2]));
natsSubscription_Destroy(s5);
natsSubscription_Destroy(s4);
natsSubscription_Destroy(s3);
natsSubscription_Destroy(s2);
natsSubscription_Destroy(s1);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
// Close the library and re-open, to reset things
nats_Close();
nats_Sleep(100);
nats_Open(-1);
}
static void
test_DefaultConnection(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsOptions *opts = NULL;
int i;
const char *urls[] = {
NATS_DEFAULT_URL,
"tcp://",
"nats://localhost",
":4222",
};
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetTimeout(opts, 500));
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Check connection fails without running server: ");
#ifndef _WIN32
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
if (s != NATS_OK)
#endif
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_NO_SERVER);
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
for (i=0; i<(int)(sizeof(urls)/sizeof(char*)); i++)
{
char buf[256];
snprintf(buf, sizeof(buf), "Test connect with '%s':", urls[i]);
test(buf);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
}
natsOptions_Destroy(opts);
_stopServer(serverPid);
}
static void
test_SimplifiedURLs(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
#if defined(NATS_HAS_TLS)
natsOptions *opts = NULL;
#endif
const char *urls[] = {
"nats://127.0.0.1:4222",
"nats://127.0.0.1:",
"nats://127.0.0.1",
"127.0.0.1:",
"127.0.0.1"
};
int urlsCount = sizeof(urls) / sizeof(char *);
int i;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test simplified URLs to non TLS server: ");
for (i=0; ((s == NATS_OK) && (i<urlsCount)); i++)
{
s = natsConnection_ConnectTo(&nc, urls[i]);
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
}
testCond(s == NATS_OK);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
#if defined(NATS_HAS_TLS)
serverPid = _startServer("nats://127.0.0.1:4222", "-c tls_default_port.conf -DV", true);
CHECK_SERVER_STARTED(serverPid);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SkipServerVerification(opts, true));
test("Test simplified URLs to TLS server: ");
for (i=0; ((s == NATS_OK) && (i<urlsCount)); i++)
{
s = natsOptions_SetURL(opts, urls[i]);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
}
testCond(s == NATS_OK);
natsOptions_Destroy(opts);
_stopServer(serverPid);
#endif
}
static void
test_IPResolutionOrder(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsOptions *opts = NULL;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, "nats://localhost:4222"));
IFOK(s, natsOptions_SetTimeout(opts, 500));
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Server listens to IPv4: ");
serverPid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true);
CHECK_SERVER_STARTED(serverPid);
testCond(serverPid != NATS_INVALID_PID);
test("Order: 4: ");
s = natsOptions_IPResolutionOrder(opts, 4);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 46: ");
s = natsOptions_IPResolutionOrder(opts, 46);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 64: ");
s = natsOptions_IPResolutionOrder(opts, 64);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 0: ");
s = natsOptions_IPResolutionOrder(opts, 0);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 6: ");
s = natsOptions_IPResolutionOrder(opts, 6);
if (s == NATS_OK)
{
s = natsConnection_Connect(&nc, opts);
if (s == NATS_OK)
{
// Should not have connected
natsConnection_Destroy(nc);
nc = NULL;
s = NATS_ERR;
}
else
{
s = NATS_OK;
}
}
testCond(s == NATS_OK);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
if (!runOnTravis)
{
test("Server listens to IPv6: ");
serverPid = _startServer("nats://[::1]:4222", "-a :: -p 4222", true);
CHECK_SERVER_STARTED(serverPid);
testCond(serverPid != NATS_INVALID_PID);
test("Order: 6: ");
s = natsOptions_IPResolutionOrder(opts, 6);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 46: ");
s = natsOptions_IPResolutionOrder(opts, 46);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 64: ");
s = natsOptions_IPResolutionOrder(opts, 64);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 0: ");
s = natsOptions_IPResolutionOrder(opts, 0);
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsConnection_Destroy(nc);
nc = NULL;
}
testCond(s == NATS_OK);
test("Order: 4: ");
s = natsOptions_IPResolutionOrder(opts, 4);
if (s == NATS_OK)
{
s = natsConnection_Connect(&nc, opts);
// This should fail, but server listening to
// [::] still accepts IPv4 connections, so be
// tolerant of that.
if (s == NATS_OK)
{
fprintf(stderr, ">>>> Server listening on [::] accepted an IPv4 connection");
natsConnection_Destroy(nc);
nc = NULL;
}
else
{
s = NATS_OK;
}
}
testCond(s == NATS_OK);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
}
natsOptions_Destroy(opts);
}
static void
test_UseDefaultURLIfNoServerSpecified(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
s = natsOptions_Create(&opts);
if (s != NATS_OK)
FAIL("Unable to create options!");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Check we can connect even if no server is specified: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ConnectToWithMultipleURLs(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
char buf[256];
buf[0] = '\0';
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Check multiple URLs work: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4444,nats://127.0.0.1:4443,nats://127.0.0.1:4222");
IFOK(s, natsConnection_Flush(nc));
IFOK(s, natsConnection_GetConnectedUrl(nc, buf, sizeof(buf)));
testCond((s == NATS_OK)
&& (strcmp(buf, "nats://127.0.0.1:4222") == 0));
natsConnection_Destroy(nc);
test("Check multiple URLs work, even with spaces: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4444 , nats://127.0.0.1:4443 , nats://127.0.0.1:4222 ");
IFOK(s, natsConnection_Flush(nc));
IFOK(s, natsConnection_GetConnectedUrl(nc, buf, sizeof(buf)));
testCond((s == NATS_OK)
&& (strcmp(buf, "nats://127.0.0.1:4222") == 0));
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ConnectionToWithNullURLs(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
char buf[256];
test("Check NULL URLs: ");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NULL);
IFOK(s, natsConnection_Flush(nc));
IFOK(s, natsConnection_GetConnectedUrl(nc, buf, sizeof(buf)));
testCond((s == NATS_OK) && (strcmp(buf, NATS_DEFAULT_URL) == 0));
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ConnectionWithNullOptions(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Check connect with NULL options is allowed: ");
s = natsConnection_Connect(&nc, NULL);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ConnectionStatus(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
test("Test connection status should be CONNECTED: ");
testCond((s == NATS_OK)
&& (natsConnection_Status(nc) == NATS_CONN_STATUS_CONNECTED));
if (s == NATS_OK)
{
natsConnection_Close(nc);
test("Test connection status should be CLOSED: ");
testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_CLOSED);
}
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ConnClosedCB(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)
|| (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg) != NATS_OK))
{
FAIL("Unable to setup test for ConnClosedCB!");
}
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
if (s == NATS_OK)
natsConnection_Close(nc);
test("Test connection closed CB invoked: ");
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.closed);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_CloseDisconnectedCB(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)
|| (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)
|| (natsOptions_SetDisconnectedCB(opts, _closedCb, (void*) &arg) != NATS_OK))
{
FAIL("Unable to setup test for ConnClosedCB!");
}
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
if (s == NATS_OK)
natsConnection_Close(nc);
test("Test connection disconnected CB invoked: ");
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.closed);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_ServerStopDisconnectedCB(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)
|| (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)
|| (natsOptions_SetDisconnectedCB(opts, _closedCb, (void*) &arg) != NATS_OK))
{
FAIL("Unable to setup test for ConnClosedCB!");
}
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
_stopServer(serverPid);
test("Test connection disconnected CB invoked on server shutdown: ");
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.closed);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
}
static void
test_ClosedConnections(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *goodsub = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&goodsub, nc, "foo"));
if (s == NATS_OK)
natsConnection_Close(nc);
// Test all API endpoints do the right thing with a closed connection.
test("Publish on closed should fail: ")
s = natsConnection_Publish(nc, "foo", NULL, 0);
testCond(s == NATS_CONNECTION_CLOSED);
test("PublishMsg on closed should fail: ")
s = natsMsg_Create(&msg, "foo", NULL, NULL, 0);
IFOK(s, natsConnection_PublishMsg(nc, msg));
testCond(s == NATS_CONNECTION_CLOSED);
natsMsg_Destroy(msg);
msg = NULL;
test("Flush on closed should fail: ")
s = natsConnection_Flush(nc);
testCond(s == NATS_CONNECTION_CLOSED);
test("Subscribe on closed should fail: ")
s = natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL);
testCond(s == NATS_CONNECTION_CLOSED);
test("SubscribeSync on closed should fail: ")
s = natsConnection_SubscribeSync(&sub, nc, "foo");
testCond(s == NATS_CONNECTION_CLOSED);
test("QueueSubscribe on closed should fail: ")
s = natsConnection_QueueSubscribe(&sub, nc, "foo", "bar", _dummyMsgHandler, NULL);
testCond(s == NATS_CONNECTION_CLOSED);
test("QueueSubscribeSync on closed should fail: ")
s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", "bar");
testCond(s == NATS_CONNECTION_CLOSED);
test("Request on closed should fail: ")
s = natsConnection_Request(&msg, nc, "foo", NULL, 0, 10);
testCond(s == NATS_CONNECTION_CLOSED);
test("NextMsg on closed should fail: ")
s = natsSubscription_NextMsg(&msg, goodsub, 10);
testCond(s == NATS_CONNECTION_CLOSED);
test("Unsubscribe on closed should fail: ")
s = natsSubscription_Unsubscribe(goodsub);
testCond(s == NATS_CONNECTION_CLOSED);
natsSubscription_Destroy(goodsub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ConnectVerboseOption(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
{
opts = _createReconnectOptions();
if (opts == NULL)
s = NATS_ERR;
}
IFOK(s, natsOptions_SetVerbose(opts, true));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args));
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Check connect OK with Verbose option: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
_stopServer(serverPid);
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Check reconnect OK with Verbose option: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.reconnected)
s = natsCondition_TimedWait(args.c, args.m, 5000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
}
static void
test_ReconnectThreadLeak(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsConnection *nc = NULL;
int i;
struct threadArg arg;
serverPid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222", true);
CHECK_SERVER_STARTED(serverPid);
s = _createDefaultThreadArgsForCbTests(&arg);
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetURL(opts, "nats://127.0.0.1:4222") != NATS_OK)
|| (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg) != NATS_OK)
|| (natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg) != NATS_OK)
|| (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg) != NATS_OK))
{
FAIL("Unable to setup test");
}
test("Connect ok: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
for (i=0; i<10; i++)
{
natsMutex_Lock(nc->mu);
natsSock_Shutdown(nc->sockCtx.fd);
natsMutex_Unlock(nc->mu);
test("Waiting for disconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
arg.disconnected = false;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Waiting for reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
arg.reconnected = false;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
}
natsConnection_Close(nc);
_waitForConnClosed(&arg);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_ReconnectTotalTime(void)
{
natsStatus s;
natsOptions *opts = NULL;
test("Check reconnect time: ");
s = natsOptions_Create(&opts);
testCond((s == NATS_OK)
&& ((opts->maxReconnect * opts->reconnectWait) >= (2 * 60 * 1000)));
natsOptions_Destroy(opts);
}
static void
test_ReconnectDisallowedFlags(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222"));
IFOK(s, natsOptions_SetAllowReconnect(opts, false));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
_stopServer(serverPid);
test("Test connection closed CB invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
}
static void
test_ReconnectAllowedFlags(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Create options and connect: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222"));
IFOK(s, natsOptions_SetAllowReconnect(opts, true));
IFOK(s, natsOptions_SetMaxReconnect(opts, 2));
IFOK(s, natsOptions_SetReconnectWait(opts, 1000));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
_stopServer(serverPid);
test("Test reconnecting in progress: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 500);
natsMutex_Unlock(arg.m);
testCond((s == NATS_TIMEOUT)
&& !arg.disconnected
&& natsConnection_IsReconnecting(nc));
natsConnection_Close(nc);
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 500);
natsMutex_Unlock(arg.m);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
}
static void
_closeConn(void *arg)
{
natsConnection *nc = (natsConnection*) arg;
natsConnection_Close(nc);
}
static void
test_ConnCloseBreaksReconnectLoop(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsThread *t = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
opts = _createReconnectOptions();
if (opts == NULL)
s = NATS_NO_MEMORY;
}
IFOK(s, natsOptions_SetMaxReconnect(opts, 1000));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg));
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Connection close breaks out reconnect loop: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Flush(nc));
// Shutdown the server
_stopServer(serverPid);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 3000);
natsMutex_Unlock(arg.m);
// Wait a bit before trying to close the connection to make sure
// that the reconnect loop has started.
nats_Sleep(1000);
// Close the connection, this should break the reconnect loop.
// Do this in a go routine since the issue was that Close()
// would block until the reconnect loop is done.
s = natsThread_Create(&t, _closeConn, (void*) nc);
// Even on Windows (where a createConn takes more than a second)
// we should be able to break the reconnect loop with the following
// timeout.
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 3000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.closed);
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static void
test_BasicReconnectFunctionality(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
arg.string = "bar";
arg.status = NATS_OK;
}
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg) != NATS_OK)
|| (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK))
{
FAIL("Unable to create reconnect options!");
}
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg));
IFOK(s, natsConnection_Flush(nc));
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
test("Disconnected CB invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 500);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
test("Publish message: ");
s = natsConnection_PublishString(nc, "foo", arg.string);
if (s == NATS_OK)
{
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
}
IFOK(s, natsConnection_FlushTimeout(nc, 5000));
testCond(s == NATS_OK);
test("Check message received after reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 1500);
natsMutex_Unlock(arg.m);
if (s == NATS_OK)
s = arg.status;
testCond((s == NATS_OK) && (nc->stats.reconnects == 1));
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_waitForConnClosed(&arg);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
_doneCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
natsMsg_Destroy(msg);
}
static void
test_ExtendedReconnectFunctionality(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsSubscription *sub2 = NULL;
natsSubscription *sub3 = NULL;
natsSubscription *sub4 = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
arg.string = "bar";
arg.status = NATS_OK;
arg.control=3;
}
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg) != NATS_OK)
|| (natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg) != NATS_OK)
|| (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK))
{
FAIL("Unable to create reconnect options!");
}
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Setup: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg));
IFOK(s, natsConnection_Subscribe(&sub2, nc, "foobar", _recvTestString, &arg));
IFOK(s, natsConnection_PublishString(nc, "foo", arg.string));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
test("Disconnected CB invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
test("Some protos while disconnected: ");
// Sub while disconnected
s = natsConnection_Subscribe(&sub3, nc, "bar", _recvTestString, &arg);
// Unsubscribe foo and bar while disconnected
IFOK(s, natsSubscription_Unsubscribe(sub2));
IFOK(s, natsConnection_PublishString(nc, "foo", arg.string));
IFOK(s, natsConnection_PublishString(nc, "bar", arg.string));
testCond(s == NATS_OK);
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
// Server is restarted, wait for reconnect
test("Check reconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.reconnected);
test("Publish more: ");
s = natsConnection_PublishString(nc, "foobar", arg.string);
IFOK(s, natsConnection_PublishString(nc, "foo", arg.string));
IFOK(s, natsConnection_Subscribe(&sub4, nc, "done", _doneCb, &arg));
IFOK(s, natsConnection_PublishString(nc, "done", "done"));
testCond(s == NATS_OK);
test("Done msg received: ")
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.done);
nats_Sleep(50);
test("All msgs were received: ");
testCond(arg.sum == 4);
natsSubscription_Destroy(sub);
natsSubscription_Destroy(sub2);
natsSubscription_Destroy(sub3);
natsSubscription_Destroy(sub4);;
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_waitForConnClosed(&arg);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_QueueSubsOnReconnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub1 = NULL;
natsSubscription *sub2 = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
arg.string = "bar";
arg.status = NATS_OK;
arg.control= 6;
}
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg) != NATS_OK))
{
FAIL("Unable to create reconnect options!");
}
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_QueueSubscribe(&sub1, nc, "foo.bar", "workers",
_recvTestString, &arg));
IFOK(s, natsConnection_QueueSubscribe(&sub2, nc, "foo.bar", "workers",
_recvTestString, &arg));
IFOK(s, natsConnection_Flush(nc));
for (int i=0; (s == NATS_OK) && (i < 10); i++)
{
char seq[20];
snprintf(seq, sizeof(seq), "%d", i);
s = natsConnection_PublishString(nc, "foo.bar", seq);
}
IFOK(s, natsConnection_Flush(nc));
nats_Sleep(50);
natsMutex_Lock(arg.m);
for (int i=0; (s == NATS_OK) && (i<10); i++)
{
if (arg.results[i] != 1)
s = NATS_ERR;
}
IFOK(s, arg.status);
memset(&arg.results, 0, sizeof(arg.results));
natsMutex_Unlock(arg.m);
test("Base results: ");
testCond(s == NATS_OK);
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Reconnects: ")
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.reconnected);
for (int i=0; (s == NATS_OK) && (i < 10); i++)
{
char seq[20];
snprintf(seq, sizeof(seq), "%d", i);
s = natsConnection_PublishString(nc, "foo.bar", seq);
}
IFOK(s, natsConnection_Flush(nc));
nats_Sleep(50);
natsMutex_Lock(arg.m);
for (int i=0; (s == NATS_OK) && (i<10); i++)
{
if (arg.results[i] != 1)
s = NATS_ERR;
}
IFOK(s, arg.status);
memset(&arg.results, 0, sizeof(arg.results));
natsMutex_Unlock(arg.m);
test("Reconnect results: ");
testCond(s == NATS_OK);
natsSubscription_Destroy(sub1);
natsSubscription_Destroy(sub2);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_IsClosed(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:22222");
test("Check IsClosed is correct: ");
testCond((s == NATS_OK) && !natsConnection_IsClosed(nc));
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
test("Check IsClosed after server shutdown: ");
testCond((s == NATS_OK) && !natsConnection_IsClosed(nc));
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Check IsClosed after server restart: ");
testCond((s == NATS_OK) && !natsConnection_IsClosed(nc));
natsConnection_Close(nc);
test("Check IsClosed after connection closed: ");
testCond((s == NATS_OK) && natsConnection_IsClosed(nc));
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_IsReconnectingAndStatus(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
test("Check reconnecting state: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:22222"));
IFOK(s, natsOptions_SetAllowReconnect(opts, true));
IFOK(s, natsOptions_SetMaxReconnect(opts, 10000));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
// Connect, verify initial reconnecting state check, then stop the server
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_OK) && !natsConnection_IsReconnecting(nc));
test("Check status: ");
testCond((s == NATS_OK) && (natsConnection_Status(nc) == NATS_CONN_STATUS_CONNECTED));
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
// Wait until we get the disconnected callback
test("Check we are disconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
test("Check IsReconnecting is correct: ");
testCond(natsConnection_IsReconnecting(nc));
test("Check Status is correct: ");
testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_RECONNECTING);
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
// Wait until we get the reconnect callback
test("Check we are reconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.reconnected);
test("Check IsReconnecting is correct: ");
testCond(!natsConnection_IsReconnecting(nc));
test("Check Status is correct: ");
testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_CONNECTED);
// Close the connection, reconnecting should still be false
natsConnection_Close(nc);
test("Check IsReconnecting is correct: ");
testCond(!natsConnection_IsReconnecting(nc));
test("Check Status is correct: ");
testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_CLOSED);
natsMutex_Lock(arg.m);
while (!arg.closed)
natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_ReconnectBufSize(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
opts = _createReconnectOptions();
if (opts == NULL)
s = NATS_ERR;
}
if (s == NATS_OK)
s = natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg);
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Option invalid settings. NULL options: ");
s = natsOptions_SetReconnectBufSize(NULL, 1);
testCond(s != NATS_OK);
test("Option invalid settings. Negative value: ");
s = natsOptions_SetReconnectBufSize(opts, -1);
testCond(s != NATS_OK);
test("Option valid settings. Zero: ");
s = natsOptions_SetReconnectBufSize(opts, 0);
testCond(s == NATS_OK);
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
// For this test, set to a low value.
s = natsOptions_SetReconnectBufSize(opts, 32);
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_Flush(nc));
_stopServer(serverPid);
// Wait until we get the disconnected callback
test("Check we are disconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
// Publish 2 messages, they should be accepted
test("Can publish while server is down: ");
s = natsConnection_PublishString(nc, "foo", "abcd");
IFOK(s, natsConnection_PublishString(nc, "foo", "abcd"));
testCond(s == NATS_OK);
// This publish should fail
test("Exhausted buffer should return an error: ");
s = natsConnection_PublishString(nc, "foo", "abcd");
testCond(s == NATS_INSUFFICIENT_BUFFER);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
}
static void
_startServerForRetryOnConnect(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsPid pid = NATS_INVALID_PID;
// Delay start a bit...
nats_Sleep(300);
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
natsMutex_Lock(arg->m);
while (!arg->done)
natsCondition_Wait(arg->c, arg->m);
natsMutex_Unlock(arg->m);
_stopServer(pid);
}
static void
_connectedCb(natsConnection *nc, void* closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->connected = true;
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
}
static int64_t
_testCustomReconnectDelayOnInitialConnect(natsConnection *nc, int attempts, void *closure)
{
if (attempts == 10)
natsConnection_Close(nc);
return 50;
}
static void
test_RetryOnFailedConnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
int64_t start = 0;
int64_t end = 0;
natsThread *t = NULL;
natsSubscription *sub = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, true, NULL, NULL));
IFOK(s, natsOptions_SetMaxReconnect(opts, 10));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
#ifdef _WIN32
// Windows takes the full timeout to report connect failure, so reduce
// timeout here.
IFOK(s, natsOptions_SetTimeout(opts, 100));
#endif
if (s != NATS_OK)
{
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
start = nats_Now();
test("Connect failed: ");
s = natsConnection_Connect(&nc, opts);
end = nats_Now();
testCond(s == NATS_NO_SERVER);
test("Retried: ")
#ifdef _WIN32
testCond((((end-start) >= 1000) && ((end-start) <= 2800)));
#else
testCond((((end-start) >= 300) && ((end-start) <= 1500)));
#endif
test("Connects ok: ");
s = natsOptions_SetMaxReconnect(opts, 20);
IFOK(s, natsThread_Create(&t, _startServerForRetryOnConnect, (void*) &arg));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
// close to avoid reconnect when shutting down server.
natsConnection_Close(nc);
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
natsConnection_Destroy(nc);
nc = NULL;
// Try with async connect
test("Connect does not block: ");
s = natsOptions_SetRetryOnFailedConnect(opts, true, _connectedCb, (void*)&arg);
// Set disconnected/reconnected to make sure that these are not
// invoked as part of async connect.
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetMaxReconnect(opts, -1));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_NOT_YET_CONNECTED) && (nc != NULL));
nats_clearLastError();
test("Subscription ok: ");
arg.control = 99;
s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*)&arg);
testCond(s == NATS_OK);
test("Publish ok: ");
s = natsConnection_Publish(nc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
// Start server
arg.done = false;
s = natsThread_Create(&t, _startServerForRetryOnConnect, (void*) &arg);
test("Connected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.connected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("No disconnected and reconnected callbacks: ");
natsMutex_Lock(arg.m);
s = ((arg.disconnected || arg.reconnected) ? NATS_ERR : NATS_OK);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Message received: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
// Close nc to avoid reconnect when shutting down server
natsConnection_Close(nc);
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Broadcast(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
nc = NULL;
// Try the custom reconnect handler and close the connection after
// certain number of attempts.
test("Close in custom reconnect delay: ")
s = natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg);
IFOK(s, natsOptions_SetCustomReconnectDelay(opts, _testCustomReconnectDelayOnInitialConnect, NULL));
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_NOT_YET_CONNECTED)
s = NATS_OK;
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static void
_closeConnWithDelay(void *arg)
{
natsConnection *nc = (natsConnection*) arg;
nats_Sleep(200);
natsConnection_Close(nc);
}
static void
_connectToMockupServer(void *closure)
{
struct threadArg *arg = (struct threadArg *) closure;
natsConnection *nc = NULL;
natsOptions *opts = arg->opts;
natsStatus s = NATS_OK;
int control;
// Make sure that the server is ready to accept our connection.
nats_Sleep(100);
if (opts == NULL)
{
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetAllowReconnect(opts, false));
}
IFOK(s, natsConnection_Connect(&nc, opts));
natsOptions_Destroy(opts);
natsMutex_Lock(arg->m);
control = arg->control;
natsMutex_Unlock(arg->m);
if (control == 2)
{
int64_t payload = 0;
if (s == NATS_OK)
{
test("Check expected max payload: ")
payload = natsConnection_GetMaxPayload(nc);
if (payload != 10)
s = NATS_ERR;
testCondNoReturn(s == NATS_OK);
}
if (s == NATS_OK)
{
test("Expect getting an error when publish more than max payload: ");
s = natsConnection_PublishString(nc, "hello", "Hello World!");
testCondNoReturn(s != NATS_OK);
// reset status
s = NATS_OK;
}
if (s == NATS_OK)
{
test("Expect success if publishing less than max payload: ");
s = natsConnection_PublishString(nc, "hello", "a");
testCondNoReturn(s == NATS_OK);
}
natsMutex_Lock(arg->m);
arg->closed = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
else if (control == 3)
{
natsThread *t = NULL;
s = natsThread_Create(&t, _closeConnWithDelay, (void*) nc);
if (s == NATS_OK)
{
s = natsConnection_Flush(nc);
natsThread_Join(t);
natsThread_Destroy(t);
}
}
else if (control == 4)
{
s = natsConnection_Flush(nc);
}
else if ((control == 5) || (control == 6))
{
// Wait for disconnect Cb
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->disconnected)
s = natsCondition_TimedWait(arg->c, arg->m, 5000);
natsMutex_Unlock(arg->m);
if ((s == NATS_OK) && (control == 5))
{
// Should reconnect
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->reconnected)
s = natsCondition_TimedWait(arg->c, arg->m, 5000);
natsMutex_Unlock(arg->m);
natsConnection_Close(nc);
}
else if (s == NATS_OK)
{
// Wait that we are closed, then check nc's last error.
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->closed)
s = natsCondition_TimedWait(arg->c, arg->m, 5000);
natsMutex_Unlock(arg->m);
if (s == NATS_OK)
{
const char* lastErr = NULL;
s = natsConnection_GetLastError(nc, &lastErr);
if (strcmp(lastErr, arg->string) != 0)
s = NATS_ILLEGAL_STATE;
}
}
}
else if (control == 7)
{
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->done)
s = natsCondition_TimedWait(arg->c, arg->m, 5000);
natsMutex_Unlock(arg->m);
}
natsConnection_Destroy(nc);
natsMutex_Lock(arg->m);
arg->status = s;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static natsStatus
_startMockupServer(natsSock *serverSock, const char *host, const char *port)
{
struct addrinfo hints;
struct addrinfo *servinfo = NULL;
int res;
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
if ((res = getaddrinfo(host, port, &hints, &servinfo)) != 0)
{
hints.ai_family = AF_INET6;
if ((res = getaddrinfo(host, port, &hints, &servinfo)) != 0)
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
sock = socket(servinfo->ai_family, servinfo->ai_socktype,
servinfo->ai_protocol);
if (sock == NATS_SOCK_INVALID)
s = NATS_SYS_ERROR;
IFOK(s, natsSock_SetCommonTcpOptions(sock));
IFOK(s, natsSock_SetBlocking(sock, true));
}
if ((s == NATS_OK)
&& (bind(sock, servinfo->ai_addr, (natsSockLen) servinfo->ai_addrlen) == NATS_SOCK_ERROR))
{
s = NATS_SYS_ERROR;
}
if ((s == NATS_OK) && (listen(sock, 100) == NATS_SOCK_ERROR))
s = NATS_SYS_ERROR;
if (s == NATS_OK)
*serverSock = sock;
else
natsSock_Close(sock);
nats_FreeAddrInfo(servinfo);
return s;
}
static void
test_ErrOnConnectAndDeadlock(void)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
arg.control = 1;
test("Verify that bad INFO does not cause deadlock in client: ");
// We will hand run a fake server that will timeout and not return a proper
// INFO proto. This is to test that we do not deadlock.
s = _startMockupServer(&sock, "localhost", "4222");
// Start the thread that will try to connect to our server...
IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg));
if ((s == NATS_OK)
&& (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
const char* badInfo = "INFOZ \r\n";
// Send back a mal-formed INFO.
s = natsSock_WriteFully(&ctx, badInfo, (int) strlen(badInfo));
}
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status == NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 3000);
natsMutex_Unlock(arg.m);
}
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
testCond((s == NATS_OK) && (arg.status != NATS_OK));
_destroyDefaultThreadArgs(&arg);
natsSock_Close(ctx.fd);
natsSock_Close(sock);
}
static void
test_ErrOnMaxPayloadLimit(void)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsThread *t = NULL;
int expectedMaxPayLoad = 10;
struct threadArg arg;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
arg.control = 2;
s = _startMockupServer(&sock, "localhost", "4222");
if ((s == NATS_OK) && (listen(sock, 100) == NATS_SOCK_ERROR))
s = NATS_SYS_ERROR;
// Start the thread that will try to connect to our server...
IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg));
if ((s == NATS_OK)
&& (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char info[1024];
snprintf(info, sizeof(info),
"INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":%d}\r\n",
expectedMaxPayLoad);
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_));
}
// Wait for the client to be about to close the connection.
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 3000);
natsMutex_Unlock(arg.m);
natsSock_Close(ctx.fd);
natsSock_Close(sock);
// Wait for the client to finish.
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
test("Test completed ok: ");
testCond(s == NATS_OK);
_destroyDefaultThreadArgs(&arg);
}
static void
test_Auth(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsOptions *opts = NULL;
test("Server with auth on, client without should fail: ");
serverPid = _startServer("nats://127.0.0.1:8232", "--user ivan --pass foo -p 8232", false);
CHECK_SERVER_STARTED(serverPid);
nats_Sleep(1000);
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:8232");
testCond((s == NATS_CONNECTION_AUTH_FAILED)
&& (nats_strcasestr(nats_GetLastError(NULL), "Authorization Violation") != NULL));
test("Server with auth on, client with proper auth should succeed: ");
s = natsConnection_ConnectTo(&nc, "nats://ivan:foo@127.0.0.1:8232");
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
// Use Options
test("Connect using SetUserInfo: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:8232"));
IFOK(s, natsOptions_SetUserInfo(opts, "ivan", "foo"));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
// Verify that credentials in URL take precedence.
test("URL takes precedence: ");
s = natsOptions_SetURL(opts, "nats://ivan:foo@127.0.0.1:8232");
IFOK(s, natsOptions_SetUserInfo(opts, "foo", "bar"));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
}
static void
test_AuthFailNoDisconnectCB(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:8232", "--user ivan --pass foo -p 8232", true);
CHECK_SERVER_STARTED(serverPid);
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to create options!");
test("Connect should fail: ");
s = natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s != NATS_OK);
test("DisconnectCb should not be invoked on auth failure: ");
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_TIMEOUT) && !arg.disconnected);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_AuthToken(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsOptions *opts = NULL;
serverPid = _startServer("nats://testSecret@127.0.0.1:8232", "-auth testSecret -p 8232", true);
CHECK_SERVER_STARTED(serverPid);
test("Server with token authorization, client without should fail: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:8232");
testCond(s != NATS_OK);
test("Server with token authorization, client with proper auth should succeed: ");
s = natsConnection_ConnectTo(&nc, "nats://testSecret@127.0.0.1:8232");
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
// Use Options
test("Connect using SetUserInfo: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:8232"));
IFOK(s, natsOptions_SetToken(opts, "testSecret"));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
// Verify that token in URL take precedence.
test("URL takes precedence: ");
s = natsOptions_SetURL(opts, "nats://testSecret@127.0.0.1:8232");
IFOK(s, natsOptions_SetToken(opts, "badtoken"));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
}
static const char*
_tokenHandler(void* closure)
{
return (char*) closure;
}
static void
test_AuthTokenHandler(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsOptions *opts = NULL;
serverPid = _startServer("nats://testSecret@127.0.0.1:8232", "-auth testSecret -p 8232", true);
CHECK_SERVER_STARTED(serverPid);
test("Connect using SetTokenHandler: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:8232"));
IFOK(s, natsOptions_SetTokenHandler(opts, _tokenHandler, (char*) "testSecret"));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
test("cannot set a tokenHandler when token set: ");
s = natsOptions_SetTokenHandler(opts, NULL, NULL);
IFOK(s, natsOptions_SetToken(opts, "token"));
IFOK(s, natsOptions_SetTokenHandler(opts, _tokenHandler, (char*) "testSecret"));
testCond(s == NATS_ILLEGAL_STATE);
test("cannot set a token when tokenHandler set: ");
s = natsOptions_SetToken(opts, NULL);
IFOK(s, natsOptions_SetTokenHandler(opts, _tokenHandler, (char*) "testSecret"));
IFOK(s, natsOptions_SetToken(opts, "token"));
testCond(s == NATS_ILLEGAL_STATE);
test("token in URL not valid with tokenHandler: ");
s = natsOptions_SetURL(opts, "nats://testSecret@127.0.0.1:8232");
IFOK(s, natsOptions_SetTokenHandler(opts, _dummyTokenHandler, NULL));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_ILLEGAL_STATE);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
}
static void
_permsViolationHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
const char *lastError = NULL;
// Technically, there is no guarantee that the connection's last error
// be still the one that is given to this callback.
if (err == NATS_NOT_PERMITTED)
{
bool ok = true;
// So consider ok if currently not the same or if the same, the
// error string is as expected.
if (natsConnection_GetLastError(nc, &lastError) == NATS_NOT_PERMITTED)
ok = (nats_strcasestr(lastError, args->string) != NULL ? true : false);
if (ok)
{
natsMutex_Lock(args->m);
args->done = true;
natsCondition_Broadcast(args->c);
natsMutex_Unlock(args->m);
}
}
}
static void
test_PermViolation(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsSubscription *sub = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
int i;
bool cbCalled;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
{
args.string = PERMISSIONS_ERR;
s = natsOptions_Create(&opts);
}
IFOK(s, natsOptions_SetURL(opts, "nats://ivan:pwd@127.0.0.1:8232"));
IFOK(s, natsOptions_SetErrorHandler(opts, _permsViolationHandler, &args));
if (s != NATS_OK)
FAIL("Error setting up test");
pid = _startServer("nats://127.0.0.1:8232", "-c permissions.conf -a 127.0.0.1 -p 8232", false);
CHECK_SERVER_STARTED(pid);
s = _checkStart("nats://ivan:pwd@127.0.0.1:8232", 4, 10);
if (s != NATS_OK)
{
_stopServer(pid);
FAIL("Error starting server!");
}
test("Check connection created: ");
s = natsConnection_Connect(&conn, opts);
testCond(s == NATS_OK);
for (i=0; i<2; i++)
{
cbCalled = false;
test("Should get perm violation: ");
if (i == 0)
s = natsConnection_PublishString(conn, "bar", "fail");
else
s = natsConnection_Subscribe(&sub, conn, "foo", _dummyMsgHandler, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(args.m);
while (!args.done && s == NATS_OK)
s = natsCondition_TimedWait(args.c, args.m, 2000);
cbCalled = args.done;
args.done = false;
natsMutex_Unlock(args.m);
}
testCond((s == NATS_OK) && cbCalled);
}
test("Connection not closed: ");
testCond((s == NATS_OK) && !natsConnection_IsClosed(conn));
natsSubscription_Destroy(sub);
natsConnection_Destroy(conn);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
_authViolationHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
const char *lastError = NULL;
// Technically, there is no guarantee that the connection's last error
// be still the one that is given to this callback.
if (err == NATS_CONNECTION_AUTH_FAILED)
{
bool ok = true;
// So consider ok if currently not the same or if the same, the
// error string is as expected.
if (natsConnection_GetLastError(nc, &lastError) == NATS_CONNECTION_AUTH_FAILED)
ok = (nats_strcasestr(nc->errStr, AUTHORIZATION_ERR) != NULL ? true : false);
if (ok)
{
natsMutex_Lock(args->m);
args->results[0]++;
args->done = true;
natsCondition_Broadcast(args->c);
natsMutex_Unlock(args->m);
}
}
}
static void
test_AuthViolation(void)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&(arg.opts)));
IFOK(s, natsOptions_SetAllowReconnect(arg.opts, false));
IFOK(s, natsOptions_SetErrorHandler(arg.opts, _authViolationHandler, &arg));
IFOK(s, natsOptions_SetClosedCB(arg.opts, _closedCb, &arg));
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
arg.control = 7;
arg.string = AUTHORIZATION_ERR;
test("Behavior of connection on Server Error: ")
s = _startMockupServer(&sock, "localhost", "4222");
// Start the thread that will try to connect to our server...
IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg));
if ((s == NATS_OK)
&& (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char info[1024];
strncpy(info,
"INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n",
sizeof(info));
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx,
_PONG_PROTO_, _PONG_PROTO_LEN_));
if (s == NATS_OK)
{
// Wait a tiny, and simulate an error sent by the server
nats_Sleep(50);
snprintf(info, sizeof(info), "-ERR '%s'\r\n", arg.string);
s = natsSock_WriteFully(&ctx, info, (int)strlen(info));
}
}
if (s == NATS_OK)
{
// Wait for the client to process the async err
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
natsSock_Close(ctx.fd);
}
natsSock_Close(sock);
// Wait for the client to finish.
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
// Wait for closed CB
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
if ((s == NATS_OK) && arg.reconnects != 0)
s = NATS_ERR;
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
_destroyDefaultThreadArgs(&arg);
}
static void
_startServerSendErrThread(void *closure)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
struct threadArg *arg = (struct threadArg*) closure;
natsSockCtx ctx;
char buffer[1024];
int connect = 0;
memset(&ctx, 0, sizeof(natsSockCtx));
_startMockupServer(&sock, "localhost", "4222");
for (connect=1; connect<4; connect++)
{
if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
const char info[] = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
natsMutex_Lock(arg->m);
arg->control++;
natsMutex_Unlock(arg->m);
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
}
if ((s == NATS_OK) && connect == 1)
{
// Send PONG
s = natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_);
nats_Sleep(500);
snprintf(buffer, sizeof(buffer), "-ERR '%s'\r\n", AUTHENTICATION_EXPIRED_ERR);
}
else if (s == NATS_OK)
{
snprintf(buffer, sizeof(buffer), "-ERR '%s'\r\n", AUTHORIZATION_ERR);
}
if (s == NATS_OK)
{
s = natsSock_WriteFully(&ctx, buffer, (int)strlen(buffer));
nats_Sleep(200);
}
natsSock_Close(ctx.fd);
}
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->disconnected)
s = natsCondition_TimedWait(arg->c, arg->m, 5000);
natsMutex_Unlock(arg->m);
natsSock_Close(sock);
}
static void
_authExpiredHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
const char *lastError = NULL;
// Technically, there is no guarantee that the connection's last error
// be still the one that is given to this callback.
if (err == NATS_CONNECTION_AUTH_FAILED)
{
bool ok = true;
natsMutex_Lock(args->m);
// So consider ok if currently not the same or if the same, the
// error string is as expected.
if (natsConnection_GetLastError(nc, &lastError) == NATS_CONNECTION_AUTH_FAILED)
{
if (args->control == 1)
ok = (nats_strcasestr(nc->errStr, AUTHENTICATION_EXPIRED_ERR) != NULL ? true : false);
else
ok = (nats_strcasestr(nc->errStr, AUTHORIZATION_ERR) != NULL ? true : false);
}
if (ok)
{
args->results[0]++;
args->done = true;
natsCondition_Broadcast(args->c);
}
natsMutex_Unlock(args->m);
}
}
static void
test_AuthenticationExpired(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts= NULL;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
int i;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetMaxReconnect(opts, -1));
IFOK(s, natsOptions_SetReconnectWait(opts, 25));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetErrorHandler(opts, _authExpiredHandler, &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg));
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
s = natsThread_Create(&t, _startServerSendErrThread, (void*) &arg);
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("Should connect ok: ");
for (i=0; i<10; i++)
{
s = natsConnection_Connect(&nc, opts);
if (s == NATS_OK)
break;
nats_Sleep(100);
}
testCond(s == NATS_OK);
test("Should have been closed: ");
s = _waitForConnClosed(&arg);
testCond(s == NATS_OK);
// First we would have gotten the async authentication expired error,
// then 2 consecutive auth violations that causes the lib to stop
// trying to reconnect.
test("Should have posted 3 errors: ");
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
s = (((arg.results[0] == 3) && arg.done) ? NATS_OK : NATS_ERR);
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
natsMutex_Lock(arg.m);
arg.disconnected = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static void
_startServerSendErr2Thread(void *closure)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
struct threadArg *arg = (struct threadArg*) closure;
natsSockCtx ctx;
char buffer[1024];
int connect = 0;
bool done = false;
memset(&ctx, 0, sizeof(natsSockCtx));
_startMockupServer(&sock, "localhost", "4222");
for (connect=1; !done; connect++)
{
if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
const char info[] = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
natsMutex_Lock(arg->m);
arg->control++;
natsMutex_Unlock(arg->m);
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
if (s == NATS_OK)
{
// Send PONG
s = natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_);
}
}
if ((s == NATS_OK) && connect == 1)
{
nats_Sleep(500);
snprintf(buffer, sizeof(buffer), "-ERR '%s'\r\n", AUTHENTICATION_EXPIRED_ERR);
s = natsSock_WriteFully(&ctx, buffer, (int)strlen(buffer));
nats_Sleep(200);
}
else if (s == NATS_OK)
{
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !arg->disconnected)
s = natsCondition_TimedWait(arg->c, arg->m, 5000);
natsMutex_Unlock(arg->m);
done = true;
}
natsSock_Close(ctx.fd);
}
natsSock_Close(sock);
}
static void
test_AuthenticationExpiredReconnect(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts= NULL;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
int i;
const char *lastErr = NULL;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetMaxReconnect(opts, 2));
IFOK(s, natsOptions_SetReconnectWait(opts, 25));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetErrorHandler(opts, _authExpiredHandler, &arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg));
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
s = natsThread_Create(&t, _startServerSendErr2Thread, (void*) &arg);
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("Should connect ok: ");
for (i=0; i<10; i++)
{
s = natsConnection_Connect(&nc, opts);
if (s == NATS_OK)
break;
nats_Sleep(100);
}
testCond(s == NATS_OK);
test("Should have posted 1 error: ");
if (s == NATS_OK)
{
// Waiting for the err handler to fire
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done && arg.results[0] != 1)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
test("Should have reconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
// Wait a tiny bit and make sure connection is still connected
nats_Sleep(100);
test("Still connected: ");
testCond(!natsConnection_IsClosed(nc));
// Check last error is cleared
test("Check last error cleared: ");
s = natsConnection_GetLastError(nc, &lastErr);
testCond((s == NATS_OK) && (lastErr[0] == '\0'));
test("Close: ");
natsConnection_Destroy(nc);
s = _waitForConnClosed(&arg);
testCond(s == NATS_OK);
nc = NULL;
natsMutex_Lock(arg.m);
arg.disconnected = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static void
test_ConnectedServer(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
char buffer[128];
buffer[0] = '\0';
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Verify ConnectedUrl is correct: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)));
testCond((s == NATS_OK)
&& (buffer[0] != '\0')
&& (strcmp(buffer, NATS_DEFAULT_URL) == 0));
buffer[0] = '\0';
test("Verify ConnectedServerId is not null: ")
s = natsConnection_GetConnectedServerId(nc, buffer, sizeof(buffer));
testCond((s == NATS_OK) && (buffer[0] != '\0'));
buffer[0] = '\0';
test("Verify ConnectedUrl is empty after disconnect: ")
natsConnection_Close(nc);
s = natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer));
testCond((s == NATS_OK) && (buffer[0] == '\0'));
buffer[0] = '\0';
test("Verify ConnectedServerId is empty after disconnect: ")
s = natsConnection_GetConnectedServerId(nc, buffer, sizeof(buffer));
testCond((s == NATS_OK) && (buffer[0] == '\0'));
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_MultipleClose(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsThread *threads[10];
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test that multiple Close are fine: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
for (int i=0; (s == NATS_OK) && (i<10); i++)
s = natsThread_Create(&(threads[i]), _closeConn, (void*) nc);
for (int i=0; (s == NATS_OK) && (i<10); i++)
{
natsThread_Join(threads[i]);
natsThread_Destroy(threads[i]);
}
testCond((s == NATS_OK)
&& (nc->status == NATS_CONN_STATUS_CLOSED)
&& (nc->refs == 1));
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_SimplePublish(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test simple publish: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_PublishString(nc, "foo", "Hello world!"));
IFOK(s, natsConnection_Publish(nc, "foo", (const void*) "Hello world!",
(int) strlen("Hello world!")));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_SimplePublishNoData(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test simple publish with no data: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_PublishString(nc, "foo", NULL));
IFOK(s, natsConnection_PublishString(nc, "foo", ""));
IFOK(s, natsConnection_Publish(nc, "foo", NULL, 0));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_PublishMsg(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
arg.string = "hello!";
arg.status = NATS_OK;
}
if ( s != NATS_OK)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test simple publishMsg: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, &arg));
IFOK(s, natsConnection_Flush(nc));
if (s == NATS_OK)
{
const char data[] = {104, 101, 108, 108, 111, 33};
natsMsg *msg = NULL;
s = natsMsg_Create(&msg, "foo", NULL, data, sizeof(data));
IFOK(s, natsConnection_PublishMsg(nc, msg));
natsMsg_Destroy(msg);
}
IFOK(s, natsConnection_Flush(nc));
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 1500);
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
_destroyDefaultThreadArgs(&arg);
}
static void
test_InvalidSubsArgs(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
if (s != NATS_OK)
FAIL("Unable to setup test");
// ASYNC Subscription
test("Test async subscriber, invalid connection: ")
s = natsConnection_Subscribe(&sub, NULL, "foo", _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async subscriber, invalid subject: ")
s = natsConnection_Subscribe(&sub, nc, NULL, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async subscriber, invalid subject (empty string): ")
s = natsConnection_Subscribe(&sub, nc, "", _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async subscriber, invalid cb: ")
s = natsConnection_Subscribe(&sub, nc, "foo", NULL, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber, invalid connection: ")
s = natsConnection_QueueSubscribe(&sub, NULL, "foo", "group", _recvTestString, NULL);
testCond(s != NATS_OK);
// Async Subscription Timeout
test("Test async subscriber timeout, invalid connection: ")
s = natsConnection_SubscribeTimeout(&sub, NULL, "foo", 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async subscriber timeout, invalid subject: ")
s = natsConnection_SubscribeTimeout(&sub, nc, NULL, 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async subscriber timeout, invalid subject (empty string): ")
s = natsConnection_SubscribeTimeout(&sub, nc, "", 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async subscriber timeout, invalid cb: ")
s = natsConnection_SubscribeTimeout(&sub, nc, "foo", 100, NULL, NULL);
testCond(s != NATS_OK);
test("Test async subscriber timeout, invalid timeout (<0): ")
s = natsConnection_SubscribeTimeout(&sub, nc, "foo", -100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async subscriber timeout, invalid timeout (0): ")
s = natsConnection_SubscribeTimeout(&sub, nc, "foo", 0, _recvTestString, NULL);
testCond(s != NATS_OK);
// ASYNC Queue Subscription
test("Test async queue subscriber timeout, invalid connection: ")
s = natsConnection_QueueSubscribe(&sub, NULL, "foo", "group", _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber, invalid subject: ")
s = natsConnection_QueueSubscribe(&sub, nc, NULL, "group", _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber, invalid subject (empty string): ")
s = natsConnection_QueueSubscribe(&sub, nc, "", "group", _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber, invalid group name: ")
s = natsConnection_QueueSubscribe(&sub, nc, "foo", NULL, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber, invalid group name (empty): ")
s = natsConnection_QueueSubscribe(&sub, nc, "foo", "", _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber, invalid cb: ")
s = natsConnection_QueueSubscribe(&sub, nc, "foo", "group", NULL, NULL);
testCond(s != NATS_OK);
// ASYNC Queue Subscription Timeout
test("Test async queue subscriber timeout, invalid connection: ")
s = natsConnection_QueueSubscribeTimeout(&sub, NULL, "foo", "group", 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber timeout, invalid subject: ")
s = natsConnection_QueueSubscribeTimeout(&sub, nc, NULL, "group", 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber timeout, invalid subject (empty string): ")
s = natsConnection_QueueSubscribeTimeout(&sub, nc, "", "group", 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber timeout, invalid group name: ")
s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", NULL, 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber timeout, invalid group name (empty): ")
s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "", 100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber timeout, invalid cb: ")
s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group", 100, NULL, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber timeout, invalid timeout (<0): ")
s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group", -100, _recvTestString, NULL);
testCond(s != NATS_OK);
test("Test async queue subscriber timeout, invalid timeout (0): ")
s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group", 0, _recvTestString, NULL);
testCond(s != NATS_OK);
// SYNC Subscription
test("Test sync subscriber, invalid connection: ")
s = natsConnection_SubscribeSync(&sub, NULL, "foo");
testCond(s != NATS_OK);
test("Test sync subscriber, invalid subject: ")
s = natsConnection_SubscribeSync(&sub, nc, NULL);
testCond(s != NATS_OK);
test("Test sync subscriber, invalid subject (empty string): ")
s = natsConnection_SubscribeSync(&sub, nc, "");
testCond(s != NATS_OK);
// SYNC Queue Subscription
test("Test sync queue subscriber, invalid connection: ")
s = natsConnection_QueueSubscribeSync(&sub, NULL, "foo", "group");
testCond(s != NATS_OK);
test("Test sync queue subscriber, invalid subject: ")
s = natsConnection_QueueSubscribeSync(&sub, nc, NULL, "group");
testCond(s != NATS_OK);
test("Test sync queue subscriber, invalid subject (empty string): ")
s = natsConnection_QueueSubscribeSync(&sub, nc, "", "group");
testCond(s != NATS_OK);
test("Test sync queue subscriber, invalid group name: ")
s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", NULL);
testCond(s != NATS_OK);
test("Test sync queue subscriber, invalid group name (empty): ")
s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", "");
testCond(s != NATS_OK);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_AsyncSubscribe(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.string = "Hello World";
arg.status = NATS_OK;
arg.control= 1;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test async subscriber: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString,
(void*) &arg));
IFOK(s, natsConnection_PublishString(nc, "foo", arg.string));
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 1500);
natsMutex_Unlock(arg.m);
IFOK(s, arg.status);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
typedef struct __asyncTimeoutInfo
{
struct threadArg *arg;
int64_t timeout;
int64_t timeAfterFirstMsg;
int64_t timeSecondMsg;
int64_t timeFirstTimeout;
int64_t timeSecondTimeout;
} _asyncTimeoutInfo;
static void
_asyncTimeoutCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
_asyncTimeoutInfo *ai = (_asyncTimeoutInfo*) closure;
natsMutex_Lock(ai->arg->m);
if (msg != NULL)
{
ai->arg->sum++;
switch (ai->arg->sum)
{
case 1:
{
// Release lock for sleep...
natsMutex_Unlock(ai->arg->m);
// Sleep for 1.5x the timeout value
nats_Sleep(ai->timeout+ai->timeout/2);
natsMutex_Lock(ai->arg->m);
ai->timeAfterFirstMsg = nats_Now();
break;
}
case 2: ai->timeSecondMsg = nats_Now(); break;
case 3:
{
ai->arg->done = true;
natsSubscription_Destroy(sub);
natsCondition_Signal(ai->arg->c);
break;
}
default:
{
ai->arg->status = NATS_ERR;
break;
}
}
natsMsg_Destroy(msg);
}
else
{
ai->arg->timerFired++;
switch (ai->arg->timerFired)
{
case 1:
{
ai->timeFirstTimeout = nats_Now();
// Notify the main thread to send the second message
// after waiting 1/2 of the timeout period.
natsCondition_Signal(ai->arg->c);
break;
}
case 2:
{
ai->timeSecondTimeout = nats_Now();
// Signal that we timed-out for the 2nd time.
ai->arg->timerStopped = 1;
natsCondition_Signal(ai->arg->c);
break;
}
default:
{
ai->arg->status = NATS_ERR;
break;
}
}
}
natsMutex_Unlock(ai->arg->m);
}
static void
test_AsyncSubscribeTimeout(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsOptions *opts = NULL;
struct threadArg arg;
bool useLibDlv = false;
int i;
char testText[128];
int64_t timeout = 100;
_asyncTimeoutInfo ai;
for (i=0; i<4; i++)
{
memset(&ai, 0, sizeof(_asyncTimeoutInfo));
memset(&arg, 0, sizeof(struct threadArg));
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, useLibDlv));
IFOK(s, _createDefaultThreadArgsForCbTests(&arg));
if ( s != NATS_OK)
FAIL("Unable to setup test!");
ai.arg = &arg;
ai.timeout = timeout;
arg.status = NATS_OK;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
snprintf(testText, sizeof(testText), "Test async %ssubscriber timeout%s: ",
((i == 1 || i == 3) ? "queue " : ""),
(i > 1 ? " (lib msg delivery)" : ""));
test(testText);
s = natsConnection_Connect(&nc, opts);
if (s == NATS_OK)
{
if (i == 0 || i == 2)
s = natsConnection_SubscribeTimeout(&sub, nc, "foo", timeout,
_asyncTimeoutCb, (void*) &ai);
else
s = natsConnection_QueueSubscribeTimeout(&sub, nc, "foo", "group",
timeout, _asyncTimeoutCb, (void*) &ai);
}
IFOK(s, natsConnection_PublishString(nc, "foo", "msg1"));
// Wait to be notified that sub timed-out 2 times
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.timerFired != 1))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
// Wait half the timeout
nats_Sleep(timeout/2);
// Send the second message. This should reset the timeout timer.
IFOK(s, natsConnection_PublishString(nc, "foo", "msg2"));
IFOK(s, natsConnection_Flush(nc));
// Wait for 2nd timeout
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.timerStopped == 0))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
// Send two more messages, only one should be received since the
// subscription will be unsubscribed/closed when receiving the
// first.
IFOK(s, natsConnection_PublishString(nc, "foo", "msg3"));
IFOK(s, natsConnection_PublishString(nc, "foo", "msg4"));
IFOK(s, natsConnection_Flush(nc));
// Wait for end of test
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
// Wait more than the timeout time to see if extra timeout callbacks
// incorrectly fire
nats_Sleep(timeout+timeout/2);
// Check for success
natsMutex_Lock(arg.m);
testCond((s == NATS_OK) && (arg.status == NATS_OK)
&& (arg.sum == 3) && (arg.timerFired == 2)
&& (ai.timeFirstTimeout >= ai.timeAfterFirstMsg + timeout - 50)
&& (ai.timeFirstTimeout <= ai.timeAfterFirstMsg + timeout + 50)
&& (ai.timeSecondTimeout >= ai.timeSecondMsg + timeout - 50)
&& (ai.timeSecondTimeout <= ai.timeSecondMsg + timeout + 50))
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
if (i >= 1)
useLibDlv = true;
}
}
static void
test_SyncSubscribe(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
const char *string = "Hello World";
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test sync subscriber: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsConnection_PublishString(nc, "foo", string));
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strncmp(string, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)) == 0));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_PubSubWithReply(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
const char *string = "Hello World";
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test PubSub with reply: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsConnection_PublishRequestString(nc, "foo", "bar", string));
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strncmp(string, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)) == 0)
&& (strncmp(natsMsg_GetReply(msg), "bar", 3) == 0));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_NoResponders(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
const char *string = "Hello World";
struct threadArg arg;
if (!serverVersionAtLeast(2,2,0))
{
char txt[200];
snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 2.2.0, got %s: ", serverVersion);
test(txt);
testCond(true);
return;
}
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("No responders on NextMsg: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsConnection_PublishRequestString(nc, "bar", "foo", string));
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond(NATS_NO_RESPONDERS);
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
sub = NULL;
arg.status = NATS_ERR;
arg.control = 10;
test("No responders in callback: ");
s = natsConnection_Subscribe(&sub, nc, "bar", _recvTestString, (void*)&arg);
IFOK(s, natsConnection_PublishRequestString(nc, "foo", "bar", string));
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
if (s == NATS_OK)
s = arg.status;
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
natsConnection_Destroy(nc);
nc = NULL;
test("Disable no responders: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_DisableNoResponders(opts, true));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_RequestString(&msg, nc, "foo", string, 500));
testCond((s == NATS_TIMEOUT) && (msg == NULL));
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
struct flushArg
{
natsConnection *nc;
natsStatus s;
int count;
int64_t timeout;
int64_t initialSleep;
int64_t loopSleep;
};
static void
_doFlush(void *arg)
{
struct flushArg *p = (struct flushArg*) arg;
int i;
nats_Sleep(p->initialSleep);
for (i = 0; (p->s == NATS_OK) && (i < p->count); i++)
{
p->s = natsConnection_FlushTimeout(p->nc, p->timeout);
if ((p->s == NATS_OK) && (p->loopSleep > 0))
nats_Sleep(p->loopSleep);
}
}
static void
test_Flush(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
const char *string = "Hello World";
natsThread *threads[3] = { NULL, NULL, NULL };
struct flushArg args[3];
int64_t start = 0;
int64_t elapsed = 0;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetPingInterval(opts, 100));
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test Flush empties buffer: ")
s = natsConnection_Connect(&nc, opts);
for (int i=0; (s == NATS_OK) && (i < 1000); i++)
s = natsConnection_PublishString(nc, "flush", string);
IFOK(s, natsConnection_Flush(nc));
testCond((s == NATS_OK)
&& natsConnection_Buffered(nc) == 0);
test("Check parallel Flush: ")
for (int i=0; (s == NATS_OK) && (i < 3); i++)
{
args[i].nc = nc;
args[i].s = NATS_OK;
args[i].timeout = 5000;
#ifdef _WIN32
args[i].count = 100;
#else
args[i].count = 1000;
#endif
args[i].initialSleep = 500;
args[i].loopSleep = 1;
s = natsThread_Create(&(threads[i]), _doFlush, (void*) &(args[i]));
}
for (int i=0; (s == NATS_OK) && (i < 10000); i++)
s = natsConnection_PublishString(nc, "flush", "Hello world");
for (int i=0; (i < 3); i++)
{
if (threads[i] == NULL)
continue;
natsThread_Join(threads[i]);
natsThread_Destroy(threads[i]);
if (args[i].s != NATS_OK)
s = args[i].s;
}
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
test("Check Flush while in doReconnect: ")
s = natsOptions_SetReconnectWait(opts, 3000);
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
// Capture the moment we connected
start = nats_Now();
// Stop the server
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
// We can restart right away, since the client library will wait 3 sec
// before reconnecting.
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
// Attempt to Flush. This should wait for the reconnect to occur, then
// proceed.
for (int i=0; (s == NATS_OK) && (i < 3); i++)
{
args[i].nc = nc;
args[i].s = NATS_OK;
args[i].timeout = 5000;
args[i].count = 1;
args[i].initialSleep = 1000;
args[i].loopSleep = 0;
s = natsThread_Create(&(threads[i]), _doFlush, (void*) &(args[i]));
}
}
for (int i=0; (i < 3); i++)
{
if (threads[i] == NULL)
continue;
natsThread_Join(threads[i]);
natsThread_Destroy(threads[i]);
if ((s == NATS_OK) && (args[i].s != NATS_OK))
{
s = args[i].s;
printf("t=%d s=%u\n", i, s);
}
}
if (s == NATS_OK)
elapsed = (nats_Now() - start);
testCond((s == NATS_OK) && (elapsed >= 2500) && (elapsed <= 3200));
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ConnCloseDoesFlush(void)
{
natsStatus s = NATS_OK;
natsPid pid = NATS_INVALID_PID;
natsConnection *nc1 = NULL;
natsConnection *nc2 = NULL;
natsSubscription *sub = NULL;
int tc = 100000;
int i, iter;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
if (valgrind)
tc = 1000;
test("Connection close flushes: ");
for (iter=0; (s == NATS_OK) && (iter<10); iter++)
{
s = natsConnection_ConnectTo(&nc1, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc1, "foo"));
IFOK(s, natsSubscription_SetPendingLimits(sub, -1, -1));
IFOK(s, natsConnection_Flush(nc1));
IFOK(s, natsConnection_ConnectTo(&nc2, NATS_DEFAULT_URL));
for (i=0; (s == NATS_OK) && (i<tc); i++)
s = natsConnection_PublishString(nc2, "foo", "hello");
if (s == NATS_OK)
natsConnection_Close(nc2);
for (i=0; (s == NATS_OK) && (i<tc); i++)
{
natsMsg *msg = NULL;
s = natsSubscription_NextMsg(&msg, sub, 1000);
natsMsg_Destroy(msg);
}
natsConnection_Destroy(nc2);
nc2 = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
natsConnection_Destroy(nc1);
nc1 = NULL;
}
testCond(s == NATS_OK);
_stopServer(pid);
}
static void
test_QueueSubscriber(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *s1 = NULL;
natsSubscription *s2 = NULL;
natsMsg *msg = NULL;
uint64_t r1 = 0;
uint64_t r2 = 0;
float v = 1000.0 * 0.15;
int64_t d1;
int64_t d2;
natsPid serverPid = NATS_INVALID_PID;
const char *string = "Hello World";
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test QueueSubscriber receive correct amount: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_QueueSubscribeSync(&s1, nc, "foo", "bar"));
IFOK(s, natsConnection_QueueSubscribeSync(&s2, nc, "foo", "bar"));
IFOK(s, natsConnection_PublishString(nc, "foo", string));
IFOK(s, natsConnection_Flush(nc));
IFOK(s, natsSubscription_QueuedMsgs(s1, &r1));
IFOK(s, natsSubscription_QueuedMsgs(s2, &r2));
testCond((s == NATS_OK)
&& (r1 + r2 == 1));
(void)natsSubscription_NextMsg(&msg, s1, 0);
natsMsg_Destroy(msg);
msg = NULL;
(void)natsSubscription_NextMsg(&msg, s2, 0);
natsMsg_Destroy(msg);
msg = NULL;
test("Test correct amount when more messages are sent: ");
for (int i=0; (s == NATS_OK) && (i<1000); i++)
s = natsConnection_PublishString(nc, "foo", string);
IFOK(s, natsConnection_Flush(nc));
r1 = r2 = 0;
IFOK(s, natsSubscription_QueuedMsgs(s1, &r1));
IFOK(s, natsSubscription_QueuedMsgs(s2, &r2));
testCond((s == NATS_OK) && (r1 + r2 == 1000));
test("Variance acceptable: ");
d1 = 500 - r1;
if (d1 < 0)
d1 *= -1;
d2 = 500 - r1;
if (d2 < 0)
d2 *= -1;
testCond((d1 <= v) && (d2 <= v));
natsSubscription_Destroy(s1);
natsSubscription_Destroy(s2);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ReplyArg(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.string = "bar";
arg.status = NATS_OK;
arg.control= 2;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test for correct Reply arg in callback: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString,
(void*) &arg));
IFOK(s, natsConnection_PublishRequestString(nc, "foo", "bar", "hello"));
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 1500);
natsMutex_Unlock(arg.m);
IFOK(s, arg.status);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_SyncReplyArg(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test for correct Reply arg in msg: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsConnection_PublishRequestString(nc, "foo", "bar", "hello"));
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond((s == NATS_OK)
&& (msg != NULL)
&& (natsMsg_GetReply(msg) != NULL)
&& (strcmp("bar", natsMsg_GetReply(msg)) == 0));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_Unsubscribe(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.string = "bar";
arg.status = NATS_OK;
arg.control= 3;
arg.sum = 0;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect and create sub: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
testCond(s == NATS_OK);
test("Send messages and flush: ");
for (int i=0; (s == NATS_OK) && (i<20); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Unsubscribe from callback: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
IFOK(s, arg.status);
testCond(s == NATS_OK);
test("No more callback: ");
nats_Sleep(250);
natsMutex_Lock(arg.m);
testCond((s == NATS_OK) && (arg.sum == 10));
natsMutex_Unlock(arg.m);
natsSubscription_Destroy(sub);
sub = NULL;
test("Create new sub: ");
s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg);
testCond(s == NATS_OK);
test("Unsubscribe after connection close: ");
natsConnection_Destroy(nc);
s = natsSubscription_Unsubscribe(sub);
testCond(s == NATS_CONNECTION_CLOSED);
natsSubscription_Destroy(sub);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_DoubleUnsubscribe(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect and create subscription: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
testCond(s == NATS_OK);
test("Unsubscribe: ");
s = natsSubscription_Unsubscribe(sub);
testCond(s == NATS_OK);
test("Double Unsubscribe: ");
s = natsSubscription_Unsubscribe(sub);
testCond(s == NATS_INVALID_SUBSCRIPTION);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_SubRemovedWhileProcessingMsg(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect and create sub: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
testCond(s == NATS_OK);
// Lock the sub so that we can remove while the connection
// readLoop is trying to push to the sub.
natsSub_Lock(sub);
test("Send message: ");
s = natsConnection_PublishString(nc, "foo", "hello");
testCond(s == NATS_OK);
test("Close sub: ");
natsSub_Unlock(sub);
natsSub_close(sub, false);
testCond(s == NATS_OK);
test("Check msg not given: ");
natsSub_Lock(sub);
testCond(sub->msgList.msgs == 0);
natsSub_Unlock(sub);
natsSubscription_Destroy(sub);
sub = NULL;
natsConnection_Destroy(nc);
nc = NULL;
// Repeat with global message delivery option.
test("Set global delivery option: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true));
testCond(s == NATS_OK);
test("Connect and create sub: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL));
testCond(s == NATS_OK);
natsSub_Lock(sub);
natsMutex_Lock(sub->libDlvWorker->lock);
test("Send message: ");
s = natsConnection_PublishString(nc, "foo", "hello");
testCond(s == NATS_OK);
test("Close sub: ");
natsMutex_Unlock(sub->libDlvWorker->lock);
natsSub_Unlock(sub);
natsSub_close(sub, false);
testCond(s == NATS_OK);
test("Check msg not given: ");
natsSub_Lock(sub);
natsMutex_Lock(sub->libDlvWorker->lock);
testCond(sub->msgList.msgs == 0);
natsMutex_Unlock(sub->libDlvWorker->lock);
natsSub_Unlock(sub);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
}
static void
test_RequestTimeout(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test Request should timeout: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_RequestString(&msg, nc, "foo", "bar", 500));
testCond(serverVersionAtLeast(2, 2, 0) ? (s == NATS_NO_RESPONDERS) : (s == NATS_TIMEOUT));
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_Request(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsMsg *req = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
int i;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.string = "I will help you";
arg.status = NATS_OK;
arg.control= 4;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect and subscribe: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
testCond(s == NATS_OK);
test("Test Request: ")
s = natsConnection_RequestString(&msg, nc, "foo", "help", 500);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
IFOK(s, arg.status);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strncmp(arg.string,
natsMsg_GetData(msg),
natsMsg_GetDataLength(msg)) == 0));
natsMsg_Destroy(msg);
msg = NULL;
test("Create req message: ");
s = natsMsg_Create(&req, "foo", NULL, "help", 4);
testCond(s == NATS_OK);
test("Test RequestMsg: ");
s = natsConnection_RequestMsg(&msg, nc, req, 500);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
IFOK(s, arg.status);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strncmp(arg.string,
natsMsg_GetData(msg),
natsMsg_GetDataLength(msg)) == 0));
natsMsg_Destroy(msg);
msg = NULL;
natsMsg_Destroy(req);
req = NULL;
natsMutex_Lock(arg.m);
arg.control = 11;
natsMutex_Unlock(arg.m);
test("Race on timeout: ");
for (i=0; (s == NATS_OK) && (i<100); i++)
{
s = natsConnection_Request(&msg, nc, "foo", "help!", 5, 1);
// Make sure that we get either OK with msg != NULL
// or TIMEOUT but with msg == NULL
if (s == NATS_OK)
{
if (msg == NULL)
s = NATS_ERR;
else
{
natsMsg_Destroy(msg);
msg = NULL;
}
}
else if ((s == NATS_TIMEOUT) && (msg == NULL))
{
s = NATS_OK;
nats_clearLastError();
}
// else if timeout and msg != NULL, that is a bug!
}
testCond(s == NATS_OK);
// Ensure the last callback returns to avoid accessing data that has been freed.
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && (arg.sum != 100))
natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_RequestNoBody(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.string = "I will help you";
arg.status = NATS_OK;
arg.control= 4;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect and subscribe: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
testCond(s == NATS_OK);
test("Test Request with no body content: ")
s = natsConnection_RequestString(&msg, nc, "foo", NULL, 500);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT)
&& !arg.msgReceived)
{
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
}
natsMutex_Unlock(arg.m);
IFOK(s, arg.status);
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strncmp(arg.string,
natsMsg_GetData(msg),
natsMsg_GetDataLength(msg)) == 0));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
_serverForMuxWithMappedSubject(void *closure)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
struct threadArg *arg = (struct threadArg*) closure;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _startMockupServer(&sock, "127.0.0.1", "4222");
natsMutex_Lock(arg->m);
arg->status = s;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char info[1024];
snprintf(info, sizeof(info), "%s", "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n");
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
}
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_));
// Now wait for the SUB proto and the Request
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
// Send the reply on a different subject
IFOK(s, natsSock_WriteFully(&ctx, "MSG bar 1 2\r\nok\r\n", 17));
if (s == NATS_OK)
{
// Wait for client to tell us it is done
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !(arg->done))
s = natsCondition_TimedWait(arg->c, arg->m, 10000);
natsMutex_Unlock(arg->m);
}
natsSock_Close(ctx.fd);
}
natsSock_Close(sock);
}
static void
test_RequestMuxWithMappedSubject(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsMsg *msg = NULL;
natsThread *t = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
test("Start server: ");
arg.status = NATS_ERR;
s = natsThread_Create(&t, _serverForMuxWithMappedSubject, (void*) &arg);
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
s = arg.status;
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Request: ");
s = natsConnection_RequestString(&msg, nc, "foo", "help", 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
natsConnection_Destroy(nc);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
_destroyDefaultThreadArgs(&arg);
}
static void
test_OldRequest(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.string = "I will help you";
arg.status = NATS_OK;
arg.control= 4;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Setup: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_UseOldRequestStyle(opts, true));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
testCond(s == NATS_OK);
test("Test Old Request Style: ")
s = natsConnection_RequestString(&msg, nc, "foo", "help", 500);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT)
&& !arg.msgReceived)
{
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
}
natsMutex_Unlock(arg.m);
IFOK(s, arg.status);
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strncmp(arg.string,
natsMsg_GetData(msg),
natsMsg_GetDataLength(msg)) == 0));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
_sendRequest(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsStatus s;
natsMsg *msg = NULL;
nats_Sleep(250);
s = natsConnection_RequestString(&msg, arg->nc, "foo", "Help!", 2000);
natsMutex_Lock(arg->m);
if ((s == NATS_OK)
&& (msg != NULL)
&& strncmp(arg->string,
natsMsg_GetData(msg),
natsMsg_GetDataLength(msg)) == 0)
{
arg->sum++;
}
else
{
arg->status = NATS_ERR;
}
natsMutex_Unlock(arg->m);
natsMsg_Destroy(msg);
}
static void
test_SimultaneousRequest(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsThread *threads[10];
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
int i;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.string = "ok";
arg.status = NATS_OK;
arg.control= 4;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
if (s == NATS_OK)
{
arg.nc = nc;
s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg);
}
for (i=0; i<10; i++)
threads[i] = NULL;
test("Test simultaneous requests: ")
for (i=0; (s == NATS_OK) && (i<10); i++)
s = natsThread_Create(&(threads[i]), _sendRequest, (void*) &arg);
for (i=0; i<10; i++)
{
if (threads[i] != NULL)
{
natsThread_Join(threads[i]);
natsThread_Destroy(threads[i]);
}
}
natsMutex_Lock(arg.m);
if ((s != NATS_OK)
|| ((s = arg.status) != NATS_OK)
|| (arg.sum != 10))
{
s = NATS_ERR;
}
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_RequestClose(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsThread *t = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
// Because of no responders, we would get an immediate timeout.
// So we need to create a sync subscriber that is simply not
// going to send a reply back.
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
test("Test Request is kicked out with a connection close: ")
IFOK(s, natsThread_Create(&t, _closeConnWithDelay, (void*) nc));
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsConnection_RequestString(&msg, nc, "foo", "help", 2000));
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
testCond((s == NATS_CONNECTION_CLOSED) && (msg == NULL));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_CustomInbox(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub1 = NULL;
natsSubscription *sub2 = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
const char *badPfx[] = {"bad prefix", "bad\tprefix", "bad..prefix", "bad.*.prefix",
"bad.>.prefix", "bad.prefix.*", "bad.prefix.>",
"bad.prefix.", "bad.prefix.."};
struct threadArg arg;
int i;
int mode;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Create options: ");
s = natsOptions_Create(&opts);
testCond(s == NATS_OK);
for (i = 0; i<(int)(sizeof(badPfx)/sizeof(char*)); i++)
{
char tmp[128];
snprintf(tmp, sizeof(tmp), "Bad prefix '%s': ", badPfx[i]);
test(tmp);
s = natsOptions_SetCustomInboxPrefix(opts, badPfx[i]);
testCond((s == NATS_INVALID_ARG) && (opts->inboxPfx == NULL));
nats_clearLastError();
}
test("Good prefix: ");
s = natsOptions_SetCustomInboxPrefix(opts, "my.prefix");
testCond((s == NATS_OK) && (opts->inboxPfx != NULL)
&& (strcmp(opts->inboxPfx, "my.prefix.") == 0));
arg.string = "I will help you";
arg.control= 4;
for (mode=0; mode<2; mode++)
{
test("Set old request style: ");
s = natsOptions_UseOldRequestStyle(opts, true);
testCond(s == NATS_OK);
test("Connect and setup sub: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_SubscribeSync(&sub1, nc, "my.prefix.>"));
IFOK(s, natsConnection_Subscribe(&sub2, nc, "foo", _recvTestString, (void*) &arg));
testCond(s == NATS_OK);
test("Send request: ");
s = natsConnection_RequestString(&msg, nc, "foo", "help", 1000);
testCond((s == NATS_OK) && (msg != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check custom inbox: ");
s = natsSubscription_NextMsg(&msg, sub1, 500);
testCond((s == NATS_OK) && (msg != NULL)
&& (strstr(natsMsg_GetSubject(msg), "my.prefix.") == natsMsg_GetSubject(msg)));
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub1);
sub1 = NULL;
natsSubscription_Destroy(sub2);
sub2 = NULL;
natsConnection_Destroy(nc);
nc = NULL;
}
for (mode = 0; mode < 2; mode++)
{
test("Set option: ");
s = natsOptions_SetCustomInboxPrefix(opts, (mode == 0 ? NULL : "my.prefix"));
testCond(s == NATS_OK);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Inbox init: ");
{
char inboxBuf[NATS_DEFAULT_INBOX_PRE_LEN+NUID_BUFFER_LEN+1];
char *inbox = NULL;
bool allocated = false;
s = natsConn_initInbox(nc, inboxBuf, sizeof(inboxBuf), &inbox, &allocated);
if (s == NATS_OK)
{
if (mode == 0)
{
if (allocated || (inbox != inboxBuf) ||
(strstr(inbox, NATS_DEFAULT_INBOX_PRE) != inbox))
{
s = NATS_ERR;
}
}
else
{
// Since the custom prefix "my.prefix." is more than "_INBOX.",
// init should have allocated memory
if (!allocated || (inbox == inboxBuf) ||
(strstr(inbox, "my.prefix.") != inbox))
{
s = NATS_ERR;
}
else if (allocated)
free(inbox);
}
}
}
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_MessageBufferPadding(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
const char *string = "Hello World";
const char *servers[] = { "nats://127.0.0.1:4222" };
int serversCount = 1;
int paddingSize = 32;
bool paddingIsZeros = true;
serverPid = _startServer(servers[0], NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Create options: ");
s = natsOptions_Create(&opts);
testCond(s == NATS_OK);
test("Setting message buffer padding: ");
s = natsOptions_SetMessageBufferPadding(opts, paddingSize);
testCond(s == NATS_OK);
test("Setting servers: ");
s = natsOptions_SetServers(opts, servers, serversCount);
testCond(s == NATS_OK);
test("Test generating message for subscriber: ")
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsConnection_PublishString(nc, "foo", string));
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strncmp(string, natsMsg_GetData(msg), natsMsg_GetDataLength(msg)) == 0));
test("Test access to memory in message buffer beyond data length: ");
// This test can pass even if padding doesn't work as excepted.
// But valgrind will show access to unallocated memory
for (int i=natsMsg_GetDataLength(msg); i<natsMsg_GetDataLength(msg)+paddingSize; i++) {
if (natsMsg_GetData(msg)[i])
paddingIsZeros = false;
}
testCond(paddingIsZeros);
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
}
static void
test_FlushInCb(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 5;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Test Flush in callback: ")
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
IFOK(s, natsConnection_PublishString(nc, "foo", "hello"));
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT)
&& !arg.msgReceived)
{
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
}
natsMutex_Unlock(arg.m);
IFOK(s, arg.status);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_ReleaseFlush(void)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
test("Check that Flush() release on connection close: ")
arg.control = 3;
// We will hand run a fake server that will to reply to the second PING
s = _startMockupServer(&sock, "localhost", "4222");
// Start the thread that will try to connect to our server...
IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg));
if ((s == NATS_OK)
&& (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char buffer[1024];
char info[1024];
strncpy(info,
"INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n",
sizeof(info));
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_));
// Get the PING
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Now wait for the client to close the connection...
nats_Sleep(500);
// Need to close those for the client side to unblock.
natsSock_Close(ctx.fd);
natsSock_Close(sock);
// Wait for the client to finish.
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
testCond((s == NATS_OK) && (arg.status != NATS_OK));
_destroyDefaultThreadArgs(&arg);
}
static void
test_FlushErrOnDisconnect(void)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
test("Check that Flush() returns an error during a disconnect: ");
arg.control = 4;
// We will hand run a fake server that will to reply to the second PING
s = _startMockupServer(&sock, "localhost", "4222");
// Start the thread that will try to connect to our server...
IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg));
if ((s == NATS_OK)
&& (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char info[1024];
strncpy(info,
"INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n",
sizeof(info));
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_));
}
// Wait a bit and kill the server.
nats_Sleep(500);
natsSock_Close(ctx.fd);
natsSock_Close(sock);
// Wait for the client to finish.
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
testCond(arg.status != NATS_OK);
if (valgrind)
nats_Sleep(900);
_destroyDefaultThreadArgs(&arg);
}
static void
test_Inbox(void)
{
natsStatus s;
natsInbox *inbox = NULL;
test("Inbox starts with correct prefix: ");
s = natsInbox_Create(&inbox);
testCond((s == NATS_OK)
&& (inbox != NULL)
&& (strncmp(inbox, "_INBOX.", 7) == 0));
natsInbox_Destroy(inbox);
}
static void
test_Stats(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsStatistics *stats = NULL;
natsSubscription *s1 = NULL;
natsSubscription *s2 = NULL;
natsPid serverPid = NATS_INVALID_PID;
const char *data = "The quick brown fox jumped over the lazy dog";
int iter = 10;
uint64_t outMsgs = 0;
uint64_t outBytes = 0;
uint64_t inMsgs = 0;
uint64_t inBytes = 0;
test("Check invalid arg: ");
s = natsStatistics_GetCounts(NULL, NULL, NULL, NULL, NULL, NULL);
testCond(s == NATS_INVALID_ARG);
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
for (int i=0; (s == NATS_OK) && (i<iter); i++)
s = natsConnection_PublishString(nc, "foo", data);
IFOK(s, natsStatistics_Create(&stats));
IFOK(s, natsConnection_GetStats(nc, stats));
IFOK(s, natsStatistics_GetCounts(stats, NULL, NULL, &outMsgs, &outBytes, NULL));
test("Tracking OutMsgs properly: ");
testCond((s == NATS_OK) && (outMsgs == (uint64_t) iter));
test("Tracking OutBytes properly: ");
testCond((s == NATS_OK) && (outBytes == (uint64_t) (iter * strlen(data))));
// Test both sync and async versions of subscribe.
s = natsConnection_Subscribe(&s1, nc, "foo", _dummyMsgHandler, NULL);
IFOK(s, natsConnection_SubscribeSync(&s2, nc, "foo"));
for (int i=0; (s == NATS_OK) && (i<iter); i++)
s = natsConnection_PublishString(nc, "foo", data);
IFOK(s, natsConnection_Flush(nc));
IFOK(s, natsConnection_GetStats(nc, stats));
IFOK(s, natsStatistics_GetCounts(stats, &inMsgs, &inBytes, NULL, NULL, NULL));
test("Tracking inMsgs properly: ");
testCond((s == NATS_OK) && (inMsgs == (uint64_t)(2 * iter)));
test("Tracking inBytes properly: ");
testCond((s == NATS_OK) && (inBytes == (uint64_t)(2 * (iter * strlen(data)))));
natsStatistics_Destroy(stats);
natsSubscription_Destroy(s1);
natsSubscription_Destroy(s2);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_BadSubject(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Should get an error with empty subject: ");
s = natsConnection_PublishString(nc, "", "hello");
testCond(s != NATS_OK);
test("Error should be NATS_INVALID_SUBJECT: ");
testCond(s == NATS_INVALID_SUBJECT);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_SubBadSubjectAndQueueName(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid pid = NATS_INVALID_PID;
const char *badSubjs[] = {NULL, "", "foo bar", "foo..bar", ".foo", "bar.baz.", "baz\t.foo"};
const char *goodSubjs[]= {"foo.bar", "a.bcd", "abc.d"};
const char *wcSubjs[] = {"*", "*.*", "*.foo.bar", "foo.*.bar", "foo.bar.*", ">", "foo.bar.>"};
const char *badWCs[] = {">.foo", "foo.>.bar", ">.>"};
const char *badQueues[]= {"queue name", "queue.name ", " queue.name",
"queue\tname", "\tqueue.name", "\t.queue.name", "queue.name\t", "queue.name.\t",
"queue\rname", "\rqueue.name", "\r.queue.name", "queue.name\r", "queue.name.\r",
"queue\nname", "\nqueue.name", "\n.queue.name", "queue.name\n", "queue.name.\n"};
char buf[256];
int i;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect ok: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
for (i=0; i<(int) (sizeof(badSubjs)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "test subject '%s': ", badSubjs[i]);
test(buf)
s = (nats_IsSubjectValid(badSubjs[i], true) ? NATS_ERR : NATS_OK);
testCond(s == NATS_OK);
}
for (i=0; i<(int) (sizeof(goodSubjs)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "test subject '%s': ", goodSubjs[i]);
test(buf)
s = (nats_IsSubjectValid(goodSubjs[i], true) ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
}
for (i=0; i<(int) (sizeof(badQueues)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "test queue '%s': ", badQueues[i]);
test(buf)
s = natsConnection_QueueSubscribeSync(&sub, nc, "foo", badQueues[i]);
testCond((s == NATS_INVALID_QUEUE_NAME) && (sub == NULL));
nats_clearLastError();
}
for (i=0; i<(int) (sizeof(wcSubjs)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "test wildcard ok '%s': ", wcSubjs[i]);
test(buf)
s = (nats_IsSubjectValid(wcSubjs[i], true) ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
}
for (i=0; i<(int) (sizeof(wcSubjs)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "test no wildcard allowed '%s': ", wcSubjs[i]);
test(buf)
s = (nats_IsSubjectValid(wcSubjs[i], false) ? NATS_ERR : NATS_OK);
testCond(s == NATS_OK);
}
for (i=0; i<(int) (sizeof(badWCs)/sizeof(char*)); i++)
{
snprintf(buf, sizeof(buf), "bad wildcard '%s': ", badWCs[i]);
test(buf)
s = (nats_IsSubjectValid(badWCs[i], true) ? NATS_ERR : NATS_OK);
testCond(s == NATS_OK);
}
natsConnection_Destroy(nc);
_stopServer(pid);
}
static void
_subComplete(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
test_ClientAsyncAutoUnsub(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
int checks;
int64_t nd = 0;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 9;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
IFOK(s, natsSubscription_AutoUnsubscribe(sub, 10));
for (int i=0; (s == NATS_OK) && (i<100); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
// Wait for the subscription to become invalid
checks = 0;
while (natsSubscription_IsValid(sub)
&& (checks++ < 10))
{
nats_Sleep(100);
}
test("IsValid should be false: ");
testCond((sub != NULL) && !natsSubscription_IsValid(sub));
test("Received no more than max: ");
testCond(arg.sum == 10);
natsSubscription_Destroy(sub);
sub = NULL;
test("Subscribe and publish 100 msgs: ");
s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg);
IFOK(s, natsSubscription_SetOnCompleteCB(sub, _subComplete, (void*) &arg));
for (int i=0; (s == NATS_OK) && (i<100); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
checks = 0;
while ((natsSubscription_GetDelivered(sub, &nd) == NATS_OK)
&& (checks++ < 10))
{
nats_Sleep(100);
}
testCond((s == NATS_OK) && (nd == 100));
test("Auto-unsub with 10, should close the sub right away: ");
s = natsSubscription_AutoUnsubscribe(sub, 10);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_ClientSyncAutoUnsub(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
int received = 0;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsSubscription_AutoUnsubscribe(sub, 10));
for (int i=0; (s == NATS_OK) && (i<100); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
test("Get correct error: ");
for (int i=0; (s == NATS_OK) && (i<100); i++)
{
s = natsSubscription_NextMsg(&msg, sub, 10);
if (s == NATS_OK)
{
received++;
natsMsg_Destroy(msg);
}
}
testCond(s == NATS_MAX_DELIVERED_MSGS);
test("Received no more than max: ");
testCond(received == 10);
test("IsValid should be false: ");
testCond((sub != NULL) && !natsSubscription_IsValid(sub));
natsSubscription_Destroy(sub);
sub = NULL;
test("Subscribe and publish 100 msgs: ");
s = natsConnection_SubscribeSync(&sub, nc, "foo");
for (int i=0; (s == NATS_OK) && (i<100); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
for (int i=0; (s == NATS_OK) && (i<100); i++)
{
s = natsSubscription_NextMsg(&msg, sub, 1000);
received++;
natsMsg_Destroy(msg);
}
testCond(s == NATS_OK);
test("Auto-unsub with 10, should close the sub right away: ");
s = natsSubscription_AutoUnsubscribe(sub, 10);
testCond(!natsSubscription_IsValid(sub));
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_ClientAutoUnsubAndReconnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
opts = _createReconnectOptions();
if ((opts == NULL)
|| ((s = _createDefaultThreadArgsForCbTests(&arg)) != NATS_OK))
{
FAIL("Unable to setup test!");
}
arg.status = NATS_OK;
arg.control= 9;
s = natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
IFOK(s, natsSubscription_AutoUnsubscribe(sub, 10));
// Send less than the max
for (int i=0; (s == NATS_OK) && (i<5); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
// Restart the server
_stopServer(serverPid);
serverPid = _startServer("nats://127.0.0.1:22222", "-p 22222", true);
CHECK_SERVER_STARTED(serverPid);
// Wait for reconnect
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
// Now send more than the max
for (int i=0; (s == NATS_OK) && (i<50); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
nats_Sleep(10);
test("Received no more than max: ");
testCond((s == NATS_OK) && (arg.sum == 10));
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
_autoUnsub(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsMutex_Lock(args->m);
args->done = true;
natsCondition_Signal(args->c);
natsMutex_Unlock(args->m);
}
static void
test_AutoUnsubNoUnsubOnDestroy(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsBuffer *buf = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 9;
serverPid = _startServer("nats://127.0.0.1:4222", "-DV", true);
CHECK_SERVER_STARTED(serverPid);
test("Auto-unsub: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _autoUnsub, (void*) &arg));
IFOK(s, natsSubscription_AutoUnsubscribe(sub, 1));
IFOK(s, natsConnection_PublishString(nc, "foo", "hello"));
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
test("Read logfile: ");
s = nats_ReadFile(&buf, 1024, LOGFILE_NAME);
testCond(s == NATS_OK);
test("Check UNSUB not sent: ");
s = (strstr(natsBuf_Data(buf), "[UNSUB 1 ]") == NULL ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
natsBuf_Destroy(buf);
}
static void
test_NextMsgOnClosedSub(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Setup: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsSubscription_Unsubscribe(sub));
testCond(s == NATS_OK);
test("Get correct error: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_INVALID_SUBSCRIPTION);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
_nextMsgKickedOut(void *closure)
{
natsSubscription *sub = (natsSubscription*) closure;
natsMsg *msg = NULL;
(void) natsSubscription_NextMsg(&msg, sub, 10000);
}
static void
test_CloseSubRelease(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsThread *t = NULL;
natsThread *subs[3];
natsPid serverPid = NATS_INVALID_PID;
int64_t start, end;
int i;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
for (i=0; i<3; i++)
s = natsThread_Create(&(subs[i]), _nextMsgKickedOut, (void*) sub);
start = nats_Now();
IFOK(s, natsThread_Create(&t, _closeConnWithDelay, (void*) nc));
for (i=0; i<3; i++)
{
if (subs[i] != NULL)
{
natsThread_Join(subs[i]);
natsThread_Destroy(subs[i]);
}
}
end = nats_Now();
test("Test that NexMsg was kicked out properly: ");
testCond((s != NATS_TIMEOUT)
&& ((end - start) <= 1000));
natsThread_Join(t);
natsThread_Destroy(t);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_IsValidSubscriber(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
test("Sub is valid: ");
testCond((s == NATS_OK) && natsSubscription_IsValid(sub));
test("Publish some msgs: ");
for (int i=0; (s == NATS_OK) && (i<10); i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Received msg ok: ")
s = natsSubscription_NextMsg(&msg, sub, 200);
testCond((s == NATS_OK) && (msg != NULL));
natsMsg_Destroy(msg);
test("Unsub: ");
s = natsSubscription_Unsubscribe(sub);
testCond(s == NATS_OK);
test("Received msg should fail after unsubscribe: ")
s = natsSubscription_NextMsg(&msg, sub, 200);
testCond(s != NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_SlowSubscriber(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsOptions *opts = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
int total = 100;
int64_t start, end;
s = natsOptions_Create(&opts);
if (s == NATS_OK)
s = natsOptions_SetMaxPendingMsgs(opts, total);
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Setup: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
for (int i=0;
(s == NATS_OK) && (i < total + 100); i++)
{
s = natsConnection_PublishString(nc, "foo", "hello");
}
testCond(s == NATS_OK);
test("Check flush returns before timeout: ");
start = nats_Now();
(void) natsConnection_FlushTimeout(nc, 5000);
end = nats_Now();
testCond((end - start) < 5000);
test("NextMsg should report error: ");
// Make sure NextMsg returns an error to indicate slow consumer
s = natsSubscription_NextMsg(&msg, sub, 200);
testCond(s != NATS_OK);
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_SlowAsyncSubscriber(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsOptions *opts = NULL;
const char *lastErr = NULL;
natsPid serverPid = NATS_INVALID_PID;
int total = 100;
int64_t start, end;
struct threadArg arg;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetMaxPendingMsgs(opts, total));
if (s != NATS_OK)
FAIL("Unable to setup test");
s = _createDefaultThreadArgsForCbTests(&arg);
if ( s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 7;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
for (int i=0;
(s == NATS_OK) && (i < (total + 100)); i++)
{
s = natsConnection_PublishString(nc, "foo", "hello");
}
test("Check Publish does not fail due to SlowConsumer: ");
testCond(s == NATS_OK);
test("Check flush returns before timeout: ");
start = nats_Now();
s = natsConnection_FlushTimeout(nc, 5000);
end = nats_Now();
testCond((end - start) < 5000);
test("Flush should not report an error: ");
testCond(s == NATS_OK);
// Make sure the callback blocks before checking for slow consumer
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !(arg.msgReceived))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
test("Last Error should be SlowConsumer: ");
testCond((s == NATS_OK)
&& natsConnection_GetLastError(nc, &lastErr) == NATS_SLOW_CONSUMER);
// Release the sub
natsMutex_Lock(arg.m);
// Unblock the wait
arg.closed = true;
// And destroy the subscription here so that the next msg callback
// is not invoked.
natsSubscription_Destroy(sub);
natsCondition_Signal(arg.c);
arg.msgReceived = false;
natsMutex_Unlock(arg.m);
// Let the callback finish
natsMutex_Lock(arg.m);
while (!arg.msgReceived)
natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
if (valgrind)
nats_Sleep(900);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
_slowConsErrCB(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
if (err == NATS_SLOW_CONSUMER)
{
arg->sum++;
natsCondition_Signal(arg->c);
}
natsMutex_Unlock(arg->m);
}
static void
test_SlowConsumerCB(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 1));
IFOK(s, natsOptions_SetErrorHandler(opts, _slowConsErrCB, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Create sub: ")
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"))
testCond(s == NATS_OK);
test("Publish 2 messages: ");
IFOK(s, natsConnection_PublishString(nc, "foo", "msg1"));
IFOK(s, natsConnection_PublishString(nc, "foo", "msg2"));
testCond(s == NATS_OK);
test("Error handler invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum != 1))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Produce 1 message: ");
IFOK(s, natsConnection_PublishString(nc, "foo", "msg3"));
testCond(s == NATS_OK);
test("Check handler is not invoked: ");
nats_Sleep(50);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum != 1))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsSubscription_Destroy(sub);
natsOptions_Destroy(opts);
_stopServer(pid);
_destroyDefaultThreadArgs(&arg);
}
static void
test_PendingLimitsDeliveredAndDropped(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
const char *lastErr = NULL;
natsPid serverPid = NATS_INVALID_PID;
int total = 100;
int sent = total + 20;
int msgsLimit = 0;
int bytesLimit= 0;
int msgs = 0;
int bytes = 0;
int64_t dropped = 0;
int64_t delivered = 0;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 7;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test!");
test("Settings, invalid args, NULL sub: ");
s = natsSubscription_SetPendingLimits(NULL, 1, 1);
testCond(s != NATS_OK);
test("Settings, invalid args, zero msgs: ");
s = natsSubscription_SetPendingLimits(sub, 0, 1);
testCond(s != NATS_OK);
test("Settings, invalid args, zero bytes: ");
s = natsSubscription_SetPendingLimits(sub, 1, 0);
testCond(s != NATS_OK);
test("Check pending limits, NULL sub: ");
s = natsSubscription_GetPendingLimits(NULL, &msgsLimit, &bytesLimit);
testCond(s != NATS_OK);
test("Check pending limits, other params NULL are OK: ");
s = natsSubscription_GetPendingLimits(sub, NULL, NULL);
testCond(s == NATS_OK);
test("Check pending limits, msgsLimit NULL is OK: ");
s = natsSubscription_GetPendingLimits(sub, NULL, &bytesLimit);
testCond((s == NATS_OK) && (bytesLimit == NATS_OPTS_DEFAULT_MAX_PENDING_MSGS * 1024));
test("Check pending limits, msgsLibytesLimitmit NULL is OK: ");
s = natsSubscription_GetPendingLimits(sub, &msgsLimit, NULL);
testCond((s == NATS_OK) && (msgsLimit == NATS_OPTS_DEFAULT_MAX_PENDING_MSGS));
test("Set negative value for msgs OK: ");
s = natsSubscription_SetPendingLimits(sub, -1, 100);
testCond(s == NATS_OK);
test("Set negative value for bytes OK: ");
s = natsSubscription_SetPendingLimits(sub, 100, -1);
testCond(s == NATS_OK);
test("Set negative values OK: ");
s = natsSubscription_SetPendingLimits(sub, -10, -10);
testCond(s == NATS_OK);
test("Get pending with negative values returned OK: ");
s = natsSubscription_GetPendingLimits(sub, &msgsLimit, &bytesLimit);
testCond((s == NATS_OK) && (msgsLimit == -10) && (bytesLimit == -10));
msgsLimit = 0;
bytesLimit = 0;
test("Set valid values: ");
s = natsSubscription_SetPendingLimits(sub, total, total * 1024);
testCond(s == NATS_OK);
test("Check pending limits: ");
s = natsSubscription_GetPendingLimits(sub, &msgsLimit, &bytesLimit);
testCond((s == NATS_OK) && (msgsLimit == total) && (bytesLimit == total * 1024));
for (int i=0;
(s == NATS_OK) && (i < sent); i++)
{
s = natsConnection_PublishString(nc, "foo", "hello");
}
IFOK(s, natsConnection_Flush(nc));
// Make sure the callback blocks before checking for slow consumer
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
test("Last Error should be SlowConsumer: ");
testCond((s == NATS_OK)
&& natsConnection_GetLastError(nc, &lastErr) == NATS_SLOW_CONSUMER);
// Check the pending values
test("Check pending values, NULL sub: ");
s = natsSubscription_GetPending(NULL, &msgs, &bytes);
testCond(s != NATS_OK);
test("Check pending values, NULL msgs: ");
s = natsSubscription_GetPending(sub, NULL, &bytes);
testCond(s == NATS_OK);
test("Check pending values, NULL bytes: ");
s = natsSubscription_GetPending(sub, &msgs, NULL);
testCond(s == NATS_OK);
msgs = 0;
bytes = 0;
test("Check pending values: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond((s == NATS_OK)
&& ((msgs == total) || (msgs == total - 1))
&& ((bytes == total * 5) || (bytes == (total - 1) * 5)));
test("Check dropped: NULL sub: ");
s = natsSubscription_GetDropped(NULL, &dropped);
testCond(s != NATS_OK);
test("Check dropped, NULL msgs: ");
s = natsSubscription_GetDropped(sub, NULL);
testCond(s != NATS_OK);
msgs = 0;
test("Check dropped: ");
s = natsSubscription_GetDropped(sub, &dropped);
testCond((s == NATS_OK)
&& ((dropped == (int64_t)(sent - total))
|| (dropped == (int64_t)(sent - total - 1))));
test("Check delivered: NULL sub: ");
s = natsSubscription_GetDelivered(NULL, &delivered);
testCond(s != NATS_OK);
test("Check delivered: NULL msgs: ");
s = natsSubscription_GetDelivered(sub, NULL);
testCond(s != NATS_OK);
msgs = 0;
test("Check delivered: ");
s = natsSubscription_GetDelivered(sub, &delivered);
testCond((s == NATS_OK) && (delivered == 1));
test("Check get stats pending: ");
s = natsSubscription_GetStats(sub, &msgs, &bytes, NULL, NULL, NULL, NULL);
testCond((s == NATS_OK)
&& ((msgs == total) || (msgs == total - 1))
&& ((bytes == total * 5) || (bytes == (total - 1) * 5)));
test("Check get stats max pending: ");
s = natsSubscription_GetStats(sub, NULL, NULL, &msgs, &bytes, NULL, NULL);
testCond((s == NATS_OK)
&& (msgs >= total - 1) && (msgs <= total)
&& (bytes >= (total - 1) * 5) && (bytes <= total * 5));
test("Check get stats delivered: ");
s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, &delivered, NULL);
testCond((s == NATS_OK) && (delivered == 1));
test("Check get stats dropped: ");
s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, NULL, &dropped);
testCond((s == NATS_OK)
&& ((dropped == (int64_t) (sent - total))
|| (dropped == (int64_t) (sent - total - 1))));
test("Check get stats all NULL: ");
s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, NULL, NULL);
testCond(s == NATS_OK);
// Release the sub
natsMutex_Lock(arg.m);
// Unblock the wait
arg.closed = true;
// And close the subscription here so that the next msg callback
// is not invoked.
natsSubscription_Unsubscribe(sub);
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
// All these calls should fail with a closed subscription
test("SetPendingLimit on closed sub: ");
s = natsSubscription_SetPendingLimits(sub, 1, 1);
testCond(s != NATS_OK);
test("GetPendingLimit on closed sub: ");
s = natsSubscription_GetPendingLimits(sub, NULL, NULL);
testCond(s != NATS_OK);
test("GetPending on closed sub: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond(s != NATS_OK);
test("GetDelivered on closed sub: ");
s = natsSubscription_GetDelivered(sub, &delivered);
testCond(s != NATS_OK);
test("GetDropped on closed sub: ");
s = natsSubscription_GetDropped(sub, &dropped);
testCond(s != NATS_OK);
test("Check get stats on closed sub: ");
s = natsSubscription_GetStats(sub, NULL, NULL, NULL, NULL, NULL, NULL);
testCond(s != NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_PendingLimitsWithSyncSub(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
int msgsLimit = 0;
int bytesLimit= 0;
int msgs = 0;
int bytes = 0;
int64_t dropped = 0;
int64_t delivered = 0;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
IFOK(s, natsSubscription_SetPendingLimits(sub, 10000, 10));
if (s != NATS_OK)
FAIL("Unable to setup test!");
test("Check pending limits: ");
s = natsSubscription_GetPendingLimits(sub, &msgsLimit, &bytesLimit);
testCond((s == NATS_OK) && (msgsLimit == 10000) && (bytesLimit == 10));
test("Can publish: ");
s = natsConnection_PublishString(nc, "foo", "abcde");
IFOK(s, natsConnection_PublishString(nc, "foo", "abcdefghijklmnopqrstuvwxyz"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
// Check the pending values
test("Check pending values: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond((s == NATS_OK) && (msgs == 1) && (bytes == 5));
msgs = 0;
test("Check dropped: ");
s = natsSubscription_GetDropped(sub, &dropped);
testCond((s == NATS_OK) && (dropped == 1));
test("Can publish small: ");
s = natsConnection_PublishString(nc, "foo", "abc");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Receive first msg: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (msg != NULL)
&& (strcmp(natsMsg_GetData(msg), "abcde") == 0));
msgs = 0;
test("Check delivered: ");
s = natsSubscription_GetDelivered(sub, &delivered);
testCond((s == NATS_OK) && (delivered == 1));
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_AsyncSubscriptionPending(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
int total = 100;
int msgs = 0;
int bytes = 0;
int mlen = 10;
int totalSize = total * mlen;
uint64_t queuedMsgs= 0;
int i;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 7;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test!");
// Check with invalid args
test("Call MaxPending with invalid args: NULL sub: ");
s = natsSubscription_GetMaxPending(NULL, &msgs, &bytes);
testCond(s != NATS_OK);
test("Call MaxPending with invalid args: other NULL params OK: ");
s = natsSubscription_GetMaxPending(sub, NULL, &bytes);
IFOK(s, natsSubscription_GetMaxPending(sub, &msgs, NULL));
IFOK(s, natsSubscription_GetMaxPending(sub, NULL, NULL));
testCond(s == NATS_OK);
for (i = 0; (s == NATS_OK) && (i < total); i++)
s = natsConnection_PublishString(nc, "foo", "0123456789");
IFOK(s, natsConnection_Flush(nc));
// Wait that a message is received, so checks are safe
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
// Test old way
test("Test queued msgs old way: ");
s = natsSubscription_QueuedMsgs(sub, &queuedMsgs);
testCond((s == NATS_OK)
&& (((int)queuedMsgs == total) || ((int)queuedMsgs == total - 1)));
// New way, make sure the same and check bytes.
test("Test new way: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond((s == NATS_OK)
&& ((msgs == total) || (msgs == total - 1))
&& ((bytes == totalSize) || (bytes == totalSize - mlen)));
// Make sure max has been set. Since we block after the first message is
// received, MaxPending should be >= total - 1 and <= total
test("Check max pending: ");
s = natsSubscription_GetMaxPending(sub, &msgs, &bytes);
testCond((s == NATS_OK)
&& ((msgs <= total) && (msgs >= total-1))
&& ((bytes <= totalSize) && (bytes >= totalSize-mlen)));
test("Check ClearMaxPending: ");
s = natsSubscription_ClearMaxPending(sub);
if (s == NATS_OK)
s = natsSubscription_GetMaxPending(sub, &msgs, &bytes);
testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0));
natsMutex_Lock(arg.m);
arg.closed = true;
natsSubscription_Unsubscribe(sub);
natsCondition_Signal(arg.c);
arg.msgReceived = false;
natsMutex_Unlock(arg.m);
// These calls should fail once the subscription is closed.
test("Check MaxPending on closed sub: ");
s = natsSubscription_GetMaxPending(sub, &msgs, &bytes);
testCond(s != NATS_OK);
test("Check ClearMaxPending on closed sub: ");
s = natsSubscription_ClearMaxPending(sub);
testCond(s != NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsMutex_Lock(arg.m);
while (!arg.msgReceived)
natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_AsyncSubscriptionPendingDrain(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
int total = 100;
int msgs = 0;
int bytes = 0;
int64_t delivered = 0;
int i;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.string = "0123456789";
arg.control= 1;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test!");
for (i = 0; (s == NATS_OK) && (i < total); i++)
s = natsConnection_PublishString(nc, "foo", arg.string);
IFOK(s, natsConnection_Flush(nc));
test("Wait for all delivered: ");
msgs = 0;
for (i=0; (s == NATS_OK) && (i<500); i++)
{
s = natsSubscription_GetDelivered(sub, &delivered);
if ((s == NATS_OK) && (delivered == (int64_t) total))
break;
nats_Sleep(10);
}
testCond((s == NATS_OK) && (delivered == (int64_t) total));
test("Check pending: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0));
natsSubscription_Unsubscribe(sub);
test("Check Delivered on closed sub: ");
s = natsSubscription_GetDelivered(sub, &delivered);
testCond(s != NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_SyncSubscriptionPending(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsMsg *msg = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
int total = 100;
int msgs = 0;
int bytes = 0;
int mlen = 10;
int totalSize = total * mlen;
uint64_t queuedMsgs= 0;
int i;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
if (s != NATS_OK)
FAIL("Unable to setup test!");
// Check with invalid args
test("Call MaxPending with invalid args: NULL sub: ");
s = natsSubscription_GetMaxPending(NULL, &msgs, &bytes);
testCond(s != NATS_OK);
test("Call MaxPending with invalid args: other NULL params OK: ");
s = natsSubscription_GetMaxPending(sub, NULL, &bytes);
IFOK(s, natsSubscription_GetMaxPending(sub, &msgs, NULL));
IFOK(s, natsSubscription_GetMaxPending(sub, NULL, NULL));
testCond(s == NATS_OK);
for (i = 0; (s == NATS_OK) && (i < total); i++)
s = natsConnection_PublishString(nc, "foo", "0123456789");
IFOK(s, natsConnection_Flush(nc));
// Test old way
test("Test queued msgs old way: ");
s = natsSubscription_QueuedMsgs(sub, &queuedMsgs);
testCond((s == NATS_OK)
&& (((int)queuedMsgs == total) || ((int)queuedMsgs == total - 1)));
// New way, make sure the same and check bytes.
test("Test new way: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond((s == NATS_OK)
&& ((msgs == total) || (msgs == total - 1))
&& ((bytes == totalSize) || (bytes == totalSize - mlen)));
// Make sure max has been set. Since we block after the first message is
// received, MaxPending should be >= total - 1 and <= total
test("Check max pending: ");
s = natsSubscription_GetMaxPending(sub, &msgs, &bytes);
testCond((s == NATS_OK)
&& ((msgs <= total) && (msgs >= total-1))
&& ((bytes <= totalSize) && (bytes >= totalSize-mlen)));
test("Check ClearMaxPending: ");
s = natsSubscription_ClearMaxPending(sub);
IFOK(s, natsSubscription_GetMaxPending(sub, &msgs, &bytes));
testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0));
// Drain all but one
for (i=0; (s == NATS_OK) && (i<total-1); i++)
{
s = natsSubscription_NextMsg(&msg, sub, 1000);
if (s == NATS_OK)
natsMsg_Destroy(msg);
}
test("Check pending: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond((s == NATS_OK) && (msgs == 1) && (bytes == mlen));
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_SyncSubscriptionPendingDrain(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsMsg *msg = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
int total = 100;
int msgs = 0;
int bytes = 0;
int64_t delivered = 0;
int i;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
if (s != NATS_OK)
FAIL("Unable to setup test!");
for (i = 0; (s == NATS_OK) && (i < total); i++)
s = natsConnection_PublishString(nc, "foo", "0123456789");
IFOK(s, natsConnection_Flush(nc));
test("Wait for all delivered: ");
do
{
do
{
s = natsSubscription_NextMsg(&msg, sub, 10);
if (s == NATS_OK)
natsMsg_Destroy(msg);
}
while (s == NATS_OK);
s = natsSubscription_GetDelivered(sub, &delivered);
if ((s == NATS_OK) && (delivered == (int64_t) total))
break;
nats_Sleep(100);
i++;
}
while ((s == NATS_OK) && (i < 50));
testCond((s == NATS_OK) && (delivered == (int64_t) total));
test("Check pending: ");
s = natsSubscription_GetPending(sub, &msgs, &bytes);
testCond((s == NATS_OK) && (msgs == 0) && (bytes == 0));
natsSubscription_Unsubscribe(sub);
test("Check Delivered on closed sub: ");
s = natsSubscription_GetDelivered(sub, &delivered);
testCond(s != NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
_asyncErrCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void* closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
if (arg->sum == 1)
{
natsMutex_Unlock(arg->m);
return;
}
arg->sum = 1;
if (sub != arg->sub)
arg->status = NATS_ERR;
if ((arg->status == NATS_OK) && (err != NATS_SLOW_CONSUMER))
arg->status = NATS_ERR;
arg->closed = true;
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
test_AsyncErrHandler(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 7;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL));
IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10));
IFOK(s, natsOptions_SetErrorHandler(opts, _asyncErrCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to create options for test AsyncErrHandler");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg));
natsMutex_Lock(arg.m);
arg.sub = sub;
natsMutex_Unlock(arg.m);
for (int i=0;
(s == NATS_OK) && (i < (opts->maxPendingMsgs + 100)); i++)
{
s = natsConnection_PublishString(nc, "async_test", "hello");
}
IFOK(s, natsConnection_Flush(nc));
// Wait for async err callback
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
test("Aync fired properly, and all checks are good: ");
testCond((s == NATS_OK)
&& arg.done
&& arg.closed
&& (arg.status == NATS_OK));
natsOptions_Destroy(opts);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
_responseCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->closed = true;
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
natsMsg_Destroy(msg);
}
static void
_startCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsInbox *response = NULL;
natsStatus s;
natsMutex_Lock(arg->m);
s = natsInbox_Create(&response);
IFOK(s, natsConnection_Subscribe(&(arg->sub), nc, response, _responseCb, (void*) arg));
IFOK(s, natsConnection_PublishRequestString(nc, "helper", response, "Help Me!"));
if (s != NATS_OK)
arg->status = s;
// We need to destroy the inbox. It has been copied by the
// natsConnection_Subscribe() call.
natsInbox_Destroy(response);
natsMutex_Unlock(arg->m);
natsMsg_Destroy(msg);
}
static void
test_AsyncSubscriberStarvation(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsSubscription *sub2 = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 4;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "helper",
_recvTestString, (void*) &arg));
IFOK(s, natsConnection_Subscribe(&sub2, nc, "start",
_startCb, (void*) &arg));
IFOK(s, natsConnection_PublishString(nc, "start", "Begin"));
IFOK(s, natsConnection_Flush(nc));
// Wait for end of test
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
test("Test not stalled in cb waiting for other cb: ");
testCond((s == NATS_OK)
&& arg.done
&& (arg.status == NATS_OK));
natsSubscription_Destroy(arg.sub);
natsSubscription_Destroy(sub);
natsSubscription_Destroy(sub2);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_AsyncSubscriberOnClose(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsSubscription *sub2 = NULL;
natsPid serverPid = NATS_INVALID_PID;
int seen = 0;
int checks = 0;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
arg.status = NATS_OK;
arg.control= 8;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo",
_recvTestString, (void*) &arg));
for (int i=0; (s == NATS_OK) && (i < 10); i++)
s = natsConnection_PublishString(nc, "foo", "Hello World");
IFOK(s, natsConnection_Flush(nc));
// Wait to receive the first message
test("Wait for first message: ");
natsMutex_Lock(arg.m);
while ((s == NATS_OK) && (arg.sum != 1))
{
natsMutex_Unlock(arg.m);
nats_Sleep(100);
natsMutex_Lock(arg.m);
if (checks++ > 10)
s = NATS_ILLEGAL_STATE;
}
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsConnection_Close(nc);
// Release callbacks
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Broadcast(arg.c);
natsMutex_Unlock(arg.m);
// Wait for some time
nats_Sleep(100);
natsMutex_Lock(arg.m);
seen = arg.sum;
natsMutex_Unlock(arg.m);
test("Make sure only one callback fired: ");
testCond(seen == 1);
natsSubscription_Destroy(sub);
natsSubscription_Destroy(sub2);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_NextMsgCallOnAsyncSub(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid serverPid = NATS_INVALID_PID;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Setup: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, NULL))
testCond(s == NATS_OK);
test("NextMsg should fail for async sub: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s != NATS_OK) && (msg == NULL));
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
testSubOnComplete(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->status = (arg->control == 2 ? NATS_OK : NATS_ERR);
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
testOnCompleteMsgHandler(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->control = 1;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
// Sleep here so that main thread invokes Unsubscribe() and we make sure that the
// onComplete is not invoked until this function returns.
nats_Sleep(500);
natsMutex_Lock(arg->m);
arg->control = 2;
natsMutex_Unlock(arg->m);
natsMsg_Destroy(msg);
}
static void
test_SubOnComplete(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Invalid arg: ");
s = natsSubscription_SetOnCompleteCB(NULL, testSubOnComplete, NULL);
testCond(s == NATS_INVALID_ARG);
test("Connect + sub: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "foo"));
testCond(s == NATS_OK);
test("Invalid sub (NULL): ");
s= natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, NULL);
testCond(s == NATS_INVALID_SUBSCRIPTION);
natsSubscription_Unsubscribe(sub);
test("Invalid sub (sync): ");
s = natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, NULL);
testCond(s == NATS_INVALID_SUBSCRIPTION);
natsSubscription_Destroy(sub);
sub = NULL;
arg.status = NATS_ERR;
test("SetOnCompleteCB ok: ");
s = natsConnection_Subscribe(&sub, nc, "foo", testOnCompleteMsgHandler, &arg);
IFOK(s, natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, &arg));
testCond(s == NATS_OK);
test("SetOnCompleteCB to NULL ok: ");
s = natsSubscription_SetOnCompleteCB(sub, NULL, NULL);
if (s == NATS_OK)
{
natsSub_Lock(sub);
s = (((sub->onCompleteCB == NULL) && (sub->onCompleteCBClosure == NULL)) ? NATS_OK : NATS_ERR);
natsSub_Unlock(sub);
}
testCond(s == NATS_OK);
test("OnComplete invoked after last message: ");
s = natsSubscription_SetOnCompleteCB(sub, testSubOnComplete, &arg);
IFOK(s, natsConnection_PublishString(nc, "foo", "hello"));
IFOK(s, natsConnection_Flush(nc));
if (s == NATS_OK)
{
// Wait for message handler to be invoked
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.control != 1))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
IFOK(s, natsSubscription_Unsubscribe(sub));
if (s == NATS_OK)
{
// Now wait for the onComplete to return.
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
// This will be OK if complete callback was invoked after msg handler returned,
// otherwise will be NATS_ERR;
s = arg.status;
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_ServersOption(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
char buffer[128];
int serversCount;
serversCount = sizeof(testServers) / sizeof(char *);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, true));
#if _WIN32
IFOK(s, natsOptions_SetTimeout(opts, 250));
#endif
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
test("Connect should fail with NATS_NO_SERVER: ");
s = natsConnection_Connect(&nc, opts);
testCond((nc == NULL) && (s == NATS_NO_SERVER));
test("Connect with list of servers should fail with NATS_NO_SERVER: ");
s = natsOptions_SetServers(opts, testServers, serversCount);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((nc == NULL) && (s == NATS_NO_SERVER));
serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid);
buffer[0] = '\0';
test("Can connect to first: ")
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)));
testCond((s == NATS_OK)
&& (buffer[0] != '\0')
&& (strcmp(buffer, testServers[0]) == 0));
natsConnection_Destroy(nc);
nc = NULL;
_stopServer(serverPid);
serverPid = NATS_INVALID_PID;
// Make sure we can connect to a non first server if running
serverPid = _startServer("nats://127.0.0.1:1223", "-p 1223", true);
CHECK_SERVER_STARTED(serverPid);
buffer[0] = '\0';
test("Can connect to second: ")
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)));
testCond((s == NATS_OK)
&& (buffer[0] != '\0')
&& (strcmp(buffer, testServers[1]) == 0));
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_stopServer(serverPid);
}
static void
test_AuthServers(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid1= NATS_INVALID_PID;
natsPid serverPid2= NATS_INVALID_PID;
char buffer[128];
const char *plainServers[] = {"nats://127.0.0.1:1222",
"nats://127.0.0.1:1224"};
const char *authServers[] = {"nats://127.0.0.1:1222",
"nats://ivan:foo@127.0.0.1:1224"};
int serversCount = 2;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetServers(opts, plainServers, serversCount));
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
serverPid1 = _startServer("nats://127.0.0.1:1222", "-p 1222 --user ivan --pass foo", false);
CHECK_SERVER_STARTED(serverPid1);
serverPid2 = _startServer("nats://127.0.0.1:1224", "-p 1224 --user ivan --pass foo", false);
if (serverPid2 == NATS_INVALID_PID)
_stopServer(serverPid1);
CHECK_SERVER_STARTED(serverPid2);
nats_Sleep(500);
test("Connect fails due to auth error: ");
s = natsConnection_Connect(&nc, opts);
testCond((s == NATS_CONNECTION_AUTH_FAILED) && (nc == NULL));
buffer[0] = '\0';
test("Connect succeeds with correct servers list: ")
s = natsOptions_SetServers(opts, authServers, serversCount);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_OK)
&& (nc != NULL)
&& (natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)) == NATS_OK)
// Even though we are using the url nats://127.0.0.1:1222, the library
// will use the user/pwd info found in the second url. So we should have
// connected OK to the first (no randomization option set at beginning of test).
&& (strcmp(buffer, authServers[0]) == 0));
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_stopServer(serverPid1);
_stopServer(serverPid2);
}
static void
test_AuthFailToReconnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid1= NATS_INVALID_PID;
natsPid serverPid2= NATS_INVALID_PID;
natsPid serverPid3= NATS_INVALID_PID;
char buffer[64];
const char *servers[] = {"nats://127.0.0.1:22222",
"nats://127.0.0.1:22223",
"nats://127.0.0.1:22224"};
struct threadArg args;
int serversCount = 3;
s = _createDefaultThreadArgsForCbTests(&args);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetServers(opts, servers, serversCount));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &args));
IFOK(s, natsOptions_SetMaxReconnect(opts, 10));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid1 = _startServer("nats://127.0.0.1:22222", "-p 22222", false);
CHECK_SERVER_STARTED(serverPid1);
serverPid2 = _startServer("nats://127.0.0.1:22223", "-p 22223 --user ivan --pass foo", false);
if (serverPid2 == NATS_INVALID_PID)
_stopServer(serverPid1);
CHECK_SERVER_STARTED(serverPid2);
serverPid3 = _startServer("nats://127.0.0.1:22224", "-p 22224", false);
if (serverPid3 == NATS_INVALID_PID)
{
_stopServer(serverPid1);
_stopServer(serverPid2);
}
CHECK_SERVER_STARTED(serverPid3);
nats_Sleep(1000);
test("Connect should succeed: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
// Stop the server which will trigger the reconnect
_stopServer(serverPid1);
serverPid1 = NATS_INVALID_PID;
// The client will try to connect to the second server, and that
// should fail. It should then try to connect to the third and succeed.
// Wait for the reconnect CB.
test("Reconnect callback should be triggered: ")
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT)
&& !(args.reconnected))
{
s = natsCondition_TimedWait(args.c, args.m, 5000);
}
natsMutex_Unlock(args.m);
testCond((s == NATS_OK) && args.reconnected);
test("Connection should not be closed: ");
testCond(natsConnection_IsClosed(nc) == false);
buffer[0] = '\0';
test("Should have connected to third server: ");
s = natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer));
testCond((s == NATS_OK) && (buffer[0] != '\0')
&& (strcmp(buffer, servers[2]) == 0));
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid2);
_stopServer(serverPid3);
}
static void
test_BasicClusterReconnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid1= NATS_INVALID_PID;
natsPid serverPid2= NATS_INVALID_PID;
char buffer[128];
int serversCount;
int64_t reconnectTimeStart, reconnectTime;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serversCount = sizeof(testServers) / sizeof(char*);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_IPResolutionOrder(opts, 4));
IFOK(s, natsOptions_SetTimeout(opts, 500));
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
serverPid1 = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid1);
serverPid2 = _startServer("nats://127.0.0.1:1224", "-p 1224", true);
if (serverPid2 == NATS_INVALID_PID)
_stopServer(serverPid1);
CHECK_SERVER_STARTED(serverPid2);
test("Check connected to the right server: ");
s = natsConnection_Connect(&nc, opts);
_stopServer(serverPid1);
// wait for disconnect
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
reconnectTimeStart = nats_Now();
// wait for reconnect
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 3000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK)
&& (natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer)) == NATS_OK)
&& (strcmp(buffer, testServers[2]) == 0));
// Make sure we did not wait on reconnect for default time.
// Reconnect should be fast since it will be a switch to the
// second server and not be dependent on server restart time.
reconnectTime = nats_Now() - reconnectTimeStart;
test("Check reconnect time did not take too long: ");
#if _WIN32
testCond(reconnectTime <= 1300);
#else
testCond(reconnectTime <= 100);
#endif
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_waitForConnClosed(&arg);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid2);
}
static const char*
_reconnectTokenHandler(void* closure)
{
const char *token;
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
token = args->tokens[args->tokenCallCount % (sizeof(args->tokens)/sizeof(char*))];
args->tokenCallCount++;
if (args->nc != NULL)
{
char buffer[256] = {'\0'};
natsStatus s;
s = natsConnection_GetConnectedUrl(args->nc, buffer, sizeof(buffer));
if (s == NATS_OK)
{
if (((args->tokenCallCount == 2) && (strcmp(buffer, "nats://127.0.0.1:22223") == 0))
|| ((args->tokenCallCount == 3) && (strcmp(buffer, "nats://127.0.0.1:22224") == 0)))
{
args->results[0]++;
buffer[0] = '\0';
s = natsConnection_GetConnectedServerId(args->nc, buffer, sizeof(buffer));
if ((s == NATS_OK) && (strlen(buffer) > 0))
args->results[0]++;
}
}
}
natsMutex_Unlock(args->m);
return token;
}
static void
test_ReconnectWithTokenHandler(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid1= NATS_INVALID_PID;
natsPid serverPid2= NATS_INVALID_PID;
natsPid serverPid3= NATS_INVALID_PID;
char buffer[64];
const char *servers[] = {"nats://127.0.0.1:22222",
"nats://127.0.0.1:22223",
"nats://127.0.0.1:22224"};
struct threadArg args;
int serversCount = 3;
s = _createDefaultThreadArgsForCbTests(&args);
args.tokenCallCount = 0;
args.tokens[0] = "token1";
args.tokens[1] = "badtoken";
args.tokens[2] = "token3";
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetServers(opts, servers, serversCount));
IFOK(s, natsOptions_SetTokenHandler(opts, _reconnectTokenHandler, (void*) &args));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &args));
IFOK(s, natsOptions_SetMaxReconnect(opts, 10));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid1 = _startServer("nats://token1@127.0.0.1:22222", "-p 22222 --auth token1", true);
CHECK_SERVER_STARTED(serverPid1);
serverPid2 = _startServer("nats://user:foo@127.0.0.1:22223", "-p 22223 --user ivan --pass foo", true);
if (serverPid2 == NATS_INVALID_PID)
_stopServer(serverPid1);
CHECK_SERVER_STARTED(serverPid2);
serverPid3 = _startServer("nats://token3@127.0.0.1:22224", "-p 22224 --auth token3", true);
if (serverPid3 == NATS_INVALID_PID)
{
_stopServer(serverPid1);
_stopServer(serverPid2);
}
CHECK_SERVER_STARTED(serverPid3);
test("Connect should succeed: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
natsMutex_Lock(args.m);
args.nc = nc;
natsMutex_Unlock(args.m);
// Stop the server which will trigger the reconnect
_stopServer(serverPid1);
serverPid1 = NATS_INVALID_PID;
// The client will try to connect to the second server, and that
// should fail. It should then try to connect to the third and succeed.
// Wait for the reconnect CB.
test("Reconnect callback should be triggered: ")
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT)
&& !(args.reconnected))
{
s = natsCondition_TimedWait(args.c, args.m, 5000);
}
natsMutex_Unlock(args.m);
testCond((s == NATS_OK) && args.reconnected);
test("Connection should not be closed: ");
testCond(natsConnection_IsClosed(nc) == false);
buffer[0] = '\0';
test("Should have connected to third server: ");
s = natsConnection_GetConnectedUrl(nc, buffer, sizeof(buffer));
testCond((s == NATS_OK) && (buffer[0] != '\0')
&& (strcmp(buffer, servers[2]) == 0));
test("ConnectedURL and ServerID OKs during reconnect process: ");
natsMutex_Lock(args.m);
s = ((args.results[0] == 4) ? NATS_OK : NATS_ERR);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid2);
_stopServer(serverPid3);
}
#define NUM_CLIENTS (100)
struct hashCount
{
int count;
};
static void
test_HotSpotReconnect(void)
{
natsStatus s;
natsConnection *nc[NUM_CLIENTS];
natsOptions *opts = NULL;
natsPid serverPid1= NATS_INVALID_PID;
natsPid serverPid2= NATS_INVALID_PID;
natsPid serverPid3= NATS_INVALID_PID;
char buffer[128];
int serversCount;
natsStrHash *cs = NULL;
struct threadArg arg;
struct hashCount *count = NULL;
#if _WIN32
test("Skip when running on Windows: ");
testCond(true);
return;
#endif
memset(nc, 0, sizeof(nc));
s = natsStrHash_Create(&cs, 4);
IFOK(s, _createDefaultThreadArgsForCbTests(&arg));
if (s != NATS_OK)
FAIL("Unable to setup test!");
serversCount = sizeof(testServers) / sizeof(char*);
serverPid1 = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid1);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount))
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test!");
for (int i=0; (s == NATS_OK) && (i<NUM_CLIENTS); i++)
{
s = natsConnection_Connect(&(nc[i]), opts);
IFOK(s, natsConnection_GetConnectedUrl(nc[i], buffer, sizeof(buffer)));
if ((s == NATS_OK)
&& (strcmp(buffer, testServers[0]) != 0))
{
s = NATS_ERR;
}
}
if (s == NATS_OK)
{
serverPid2 = _startServer("nats://127.0.0.1:1224", "-p 1224", true);
serverPid3 = _startServer("nats://127.0.0.1:1226", "-p 1226", true);
if ((serverPid2 == NATS_INVALID_PID)
|| (serverPid3 == NATS_INVALID_PID))
{
_stopServer(serverPid1);
_stopServer(serverPid2);
_stopServer(serverPid3);
FAIL("Unable to start or verify that the server was started!");
}
}
_stopServer(serverPid1);
// Wait on all reconnects
test("Check all reconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.reconnects != NUM_CLIENTS))
s = natsCondition_TimedWait(arg.c, arg.m, 10000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.reconnects == NUM_CLIENTS);
// Walk the clients and calculate how many of each..
for (int i=0; (s == NATS_OK) && (i<NUM_CLIENTS); i++)
{
if (nc[i] == NULL)
{
s = NATS_ERR;
break;
}
buffer[0] = '\0';
s = natsConnection_GetConnectedUrl(nc[i], buffer, sizeof(buffer));
if (s == NATS_OK)
{
count = (struct hashCount*) natsStrHash_Get(cs, buffer);
if (count == NULL)
{
count = (struct hashCount*) calloc(1, sizeof(struct hashCount));
if (count == NULL)
s = NATS_NO_MEMORY;
}
if (s == NATS_OK)
{
count->count++;
s = natsStrHash_Set(cs, buffer, true, (void*) count, NULL);
}
}
natsConnection_Close(nc[i]);
}
test("Check correct number of servers: ");
testCond((s == NATS_OK) && (natsStrHash_Count(cs) == 2));
if (s == NATS_OK)
{
// numClients = 100
// numServers = 2
// expected = numClients / numServers
// v = expected * 0.3
natsStrHashIter iter;
struct hashCount *val;
int total;
int delta;
int v = (int) (((float)NUM_CLIENTS / 2) * 0.30);
void *p = NULL;
natsStrHashIter_Init(&iter, cs);
while (natsStrHashIter_Next(&iter, NULL, &p))
{
val = (struct hashCount*) p;
total = val->count;
delta = ((NUM_CLIENTS / 2) - total);
if (delta < 0)
delta *= -1;
if (delta > v)
s = NATS_ERR;
free(val);
}
test("Check variance: ");
testCond(s == NATS_OK);
}
for (int i=0; i<NUM_CLIENTS; i++)
natsConnection_Destroy(nc[i]);
natsStrHash_Destroy(cs);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid2);
_stopServer(serverPid3);
}
static void
test_ProperReconnectDelay(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
int serversCount;
struct threadArg arg;
#if _WIN32
test("Skip when running on Windows: ");
testCond(true);
return;
#endif
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serversCount = sizeof(testServers) / sizeof(char*);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
_stopServer(serverPid);
// wait for disconnect
test("Wait for disconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
// Wait, want to make sure we don't spin on reconnect to non-existent servers.
nats_Sleep(1000);
// Make sure we are still reconnecting..
test("ClosedCB should not be invoked: ")
natsMutex_Lock(arg.m);
testCond(arg.closed == false);
natsMutex_Unlock(arg.m);
test("Should still be reconnecting: ");
testCond(natsConnection_Status(nc) == NATS_CONN_STATUS_RECONNECTING);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
// Now that the connection is destroyed, the callback will be invoked.
// Make sure that we wait until then before destroying 'arg'.
_waitForConnClosed(&arg);
_destroyDefaultThreadArgs(&arg);
}
static void
test_ProperFalloutAfterMaxAttempts(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
int serversCount;
struct threadArg arg;
#if _WIN32
test("Skip when running on Windows: ");
testCond(true);
return;
#endif
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serversCount = sizeof(testServers) / sizeof(char*);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetMaxReconnect(opts, 5));
IFOK(s, natsOptions_SetReconnectWait(opts, 25));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
_stopServer(serverPid);
// wait for disconnect
test("Wait for disconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
// wait for closed
test("Wait for closed: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.closed);
test("Disconnected should have been called only once: ");
testCond((s == NATS_OK) && arg.disconnects == 1);
test("Connection should be closed: ")
testCond((s == NATS_OK)
&& natsConnection_IsClosed(nc));
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
}
static void
test_StopReconnectAfterTwoAuthErr(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsPid serverPid2= NATS_INVALID_PID;
const char *servers[]= {"nats://127.0.0.1:1222", "nats://127.0.0.1:1223"};
natsStatistics *stats = NULL;
int serversCount;
struct threadArg arg;
//#if _WIN32
// test("Skip when running on Windows: ");
// testCond(true);
// return;
//#endif
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serversCount = sizeof(servers) / sizeof(char*);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetMaxReconnect(opts, -1));
IFOK(s, natsOptions_SetReconnectWait(opts, 25));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetServers(opts, servers, serversCount));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
#if _WIN32
IFOK(s, natsOptions_SetTimeout(opts, 500));
#endif
IFOK(s, natsStatistics_Create(&stats));
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid);
serverPid2 = _startServer("nats://127.0.0.1:1223", "-p 1223 -user ivan -pass secret", true);
if (serverPid == NATS_INVALID_PID)
_stopServer(serverPid);
CHECK_SERVER_STARTED(serverPid2);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
_stopServer(serverPid);
// wait for disconnect
test("Wait for disconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
// wait for closed
test("Wait for closed: ");
s = _waitForConnClosed(&arg);
testCond(s == NATS_OK);
// Make sure we have not exceeded MaxReconnect
test("Check reconnect twice: ");
s = natsConnection_GetStats(nc, stats);
testCond((s == NATS_OK)
&& (stats != NULL)
&& (stats->reconnects == 2));
// Disconnect CB only from disconnect from server 1.
test("Disconnected should have been called once: ");
testCond((s == NATS_OK) && arg.disconnects == 1);
test("Connection should be closed: ")
testCond((s == NATS_OK)
&& natsConnection_IsClosed(nc));
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
natsStatistics_Destroy(stats);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid2);
}
static void
test_TimeoutOnNoServer(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
int serversCount;
int64_t startWait, timedWait;
struct threadArg arg;
#if _WIN32
test("Skip when running on Windows: ");
testCond(true);
return;
#endif
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
serversCount = sizeof(testServers) / sizeof(char*);
// 1000 milliseconds total time wait
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetMaxReconnect(opts, 10));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
_stopServer(serverPid);
// wait for disconnect
test("Wait for disconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.disconnected);
startWait = nats_Now();
// wait for closed
test("Wait for closed: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000 + serversCount*50);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && arg.closed);
timedWait = nats_Now() - startWait;
// The client will try to reconnect to serversCount servers.
// It will do that for MaxReconnects==10 times.
// For a server that has been already tried, it should sleep
// ReconnectWait==100ms. When a server is not running, the connect
// failure on UNIXes should be fast, still account for that.
test("Check wait time for closed cb: ");
testCond(timedWait <= ((opts->maxReconnect * opts->reconnectWait) + serversCount*opts->maxReconnect*50));
natsOptions_Destroy(opts);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
}
static void
test_PingReconnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
int serversCount;
int64_t disconnectedAt, reconnectedAt, pingCycle;
struct threadArg arg;
#if _WIN32
test("Skip when running on Windows: ");
testCond(true);
return;
#endif
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test!");
arg.control = 9;
serversCount = sizeof(testServers) / sizeof(char*);
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetReconnectWait(opts, 200));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetPingInterval(opts, 50));
IFOK(s, natsOptions_SetMaxPingsOut(opts, -1));
IFOK(s, natsOptions_SetServers(opts, testServers, serversCount));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to create options for test ServerOptions");
serverPid = _startServer("nats://127.0.0.1:1222", "-p 1222", true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Pings cause reconnects: ")
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.reconnects != 4))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && (arg.reconnects == 4));
natsConnection_Destroy(nc);
for (int i=0; i<(4-1); i++)
{
disconnectedAt = arg.disconnectedAt[i];
reconnectedAt = arg.reconnectedAt[i];
pingCycle = reconnectedAt - disconnectedAt;
if (pingCycle > 2 * opts->pingInterval)
{
s = NATS_ERR;
break;
}
}
test("Reconnect due to ping cycle correct: ");
testCond(s == NATS_OK);
// Wait for connection closed before destroying arg.
natsMutex_Lock(arg.m);
while (!arg.closed)
natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(serverPid);
}
static void
test_GetServers(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsPid s1Pid = NATS_INVALID_PID;
natsPid s2Pid = NATS_INVALID_PID;
natsPid s3Pid = NATS_INVALID_PID;
char **servers = NULL;
int count = 0;
s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats://127.0.0.1:5222 -cluster_name abc", true);
CHECK_SERVER_STARTED(s1Pid);
s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats://127.0.0.1:5223 -cluster_name abc -routes nats://127.0.0.1:5222", true);
if (s2Pid == NATS_INVALID_PID)
_stopServer(s1Pid);
CHECK_SERVER_STARTED(s2Pid);
s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats://127.0.0.1:5224 -cluster_name abc -routes nats://127.0.0.1:5222", true);
if (s3Pid == NATS_INVALID_PID)
{
_stopServer(s1Pid);
_stopServer(s2Pid);
}
CHECK_SERVER_STARTED(s3Pid);
test("Get Servers: ");
s = natsConnection_ConnectTo(&conn, "nats://127.0.0.1:4222");
IFOK(s, natsConnection_GetServers(conn, &servers, &count));
if (s == NATS_OK)
{
int i;
// Be tolerant that if we were to connect to an older
// server, we would just get 1 url back.
if ((count != 1) && (count != 3))
s = nats_setError(NATS_ERR, "Unexpected number of servers: %d instead of 1 or 3", count);
for (i=0; (s == NATS_OK) && (i < count); i++)
{
if ((strcmp(servers[i], "nats://127.0.0.1:4222") != 0)
&& (strcmp(servers[i], "nats://127.0.0.1:4223") != 0)
&& (strcmp(servers[i], "nats://127.0.0.1:4224") != 0))
{
s = nats_setError(NATS_ERR, "Unexpected server URL: %s", servers[i]);
}
}
for (i=0; i<count; i++)
free(servers[i]);
free(servers);
}
testCond(s == NATS_OK);
natsConnection_Destroy(conn);
conn = NULL;
_stopServer(s3Pid);
_stopServer(s2Pid);
_stopServer(s1Pid);
s1Pid = NATS_INVALID_PID;
s1Pid = _startServer("nats://ivan:password@127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -user ivan -pass password", true);
CHECK_SERVER_STARTED(s1Pid);
test("Get Servers does not return credentials: ");
s = natsConnection_ConnectTo(&conn, "nats://ivan:password@127.0.0.1:4222");
IFOK(s, natsConnection_GetServers(conn, &servers, &count));
if (s == NATS_OK)
{
if (count != 1)
s = nats_setError(NATS_ERR, "Unexpected number of servers: %d instead of 1", count);
else if (strcmp(servers[0], "nats://127.0.0.1:4222") != 0)
s = nats_setError(NATS_ERR, "Unexpected server URL: %s", servers[0]);
free(servers[0]);
free(servers);
}
testCond(s == NATS_OK);
natsConnection_Destroy(conn);
_stopServer(s1Pid);
}
static void
test_GetDiscoveredServers(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsPid s1Pid = NATS_INVALID_PID;
natsPid s2Pid = NATS_INVALID_PID;
char **servers = NULL;
int count = 0;
s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats://127.0.0.1:5222 -cluster_name abc", true);
CHECK_SERVER_STARTED(s1Pid);
s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats://127.0.0.1:5223 -cluster_name abc -routes nats://127.0.0.1:5222", true);
if (s2Pid == NATS_INVALID_PID)
_stopServer(s1Pid);
CHECK_SERVER_STARTED(s2Pid);
test("GetDiscoveredServers: ");
s = natsConnection_ConnectTo(&conn, "nats://127.0.0.1:4222");
IFOK(s, natsConnection_GetDiscoveredServers(conn, &servers, &count));
if (s == NATS_OK)
{
int i;
// Be tolerant that if we were to connect to an older
// server, we would get nothing
if (count > 1)
s = nats_setError(NATS_ERR, "Unexpected number of servers: %d instead of 1 or 0", count);
for (i=0; (s == NATS_OK) && (i < count); i++)
{
if (strcmp(servers[i], "nats://127.0.0.1:4223") != 0)
s = nats_setError(NATS_ERR, "Unexpected server URL: %s", servers[i]);
}
for (i=0; i<count; i++)
free(servers[i]);
free(servers);
}
testCond(s == NATS_OK);
natsConnection_Destroy(conn);
conn = NULL;
_stopServer(s2Pid);
_stopServer(s1Pid);
}
static void
_discoveredServersCb(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->sum++;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
test_DiscoveredServersCb(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsPid s1Pid = NATS_INVALID_PID;
natsPid s2Pid = NATS_INVALID_PID;
natsPid s3Pid = NATS_INVALID_PID;
natsOptions *opts = NULL;
struct threadArg arg;
int invoked = 0;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, &arg));
if (s != NATS_OK)
FAIL("Unable to setup test");
s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats-route://127.0.0.1:5222 -cluster_name abc", true);
CHECK_SERVER_STARTED(s1Pid);
s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats-route://127.0.0.1:5223 -cluster_name abc -routes nats-route://127.0.0.1:5222", true);
if (s2Pid == NATS_INVALID_PID)
_stopServer(s1Pid);
CHECK_SERVER_STARTED(s2Pid);
test("DiscoveredServersCb not triggered on initial connect: ");
s = natsConnection_Connect(&conn, opts);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum == 0))
s = natsCondition_TimedWait(arg.c, arg.m, 500);
invoked = arg.sum;
natsMutex_Unlock(arg.m);
testCond((s == NATS_TIMEOUT) && (invoked == 0));
s = NATS_OK;
s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats-route://127.0.0.1:5224 -cluster_name abc -routes nats-route://127.0.0.1:5222", true);
if (s3Pid == NATS_INVALID_PID)
{
_stopServer(s1Pid);
_stopServer(s2Pid);
}
CHECK_SERVER_STARTED(s3Pid);
test("DiscoveredServersCb triggered on new server joining the cluster: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum == 0))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
invoked = arg.sum;
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && (invoked == 1));
natsConnection_Destroy(conn);
natsOptions_Destroy(opts);
_stopServer(s3Pid);
_stopServer(s2Pid);
_stopServer(s1Pid);
_destroyDefaultThreadArgs(&arg);
}
static void
test_IgnoreDiscoveredServers(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsPid s1Pid = NATS_INVALID_PID;
natsPid s2Pid = NATS_INVALID_PID;
natsOptions *opts = NULL;
struct threadArg arg;
int invoked = 0;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetIgnoreDiscoveredServers(opts, true));
IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, &arg));
if (s != NATS_OK)
FAIL("Unable to setup test");
s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats-route://127.0.0.1:5222 -cluster_name abc", true);
CHECK_SERVER_STARTED(s1Pid);
test("Connect: ");
s = natsConnection_Connect(&conn, opts);
testCond(s == NATS_OK);
test("Start new server: ");
s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats-route://127.0.0.1:5223 -cluster_name abc -routes nats-route://127.0.0.1:5222", true);
CHECK_SERVER_STARTED(s2Pid);
testCond(true);
test("Check discovered ignored: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum == 0))
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
invoked = arg.sum;
natsMutex_Unlock(arg.m);
testCond((s == NATS_TIMEOUT) && (invoked == 0));
nats_clearLastError();
test("Check server pool: ");
natsConn_Lock(conn);
s = (natsSrvPool_GetSize(conn->srvPool) == 1 ? NATS_OK : NATS_ERR);
natsConn_Unlock(conn);
testCond(s == NATS_OK);
natsConnection_Destroy(conn);
natsOptions_Destroy(opts);
_stopServer(s2Pid);
_stopServer(s1Pid);
_destroyDefaultThreadArgs(&arg);
}
static void
_serverSendsINFOAfterPONG(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
// We will hand run a fake server that will send an INFO protocol
// right after sending the initial PONG.
s = _startMockupServer(&sock, "127.0.0.1", "4222");
natsMutex_Lock(arg->m);
arg->status = s;
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
if ((s == NATS_OK)
&& (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
const char* info = "INFO {}\r\n";
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
}
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG + INFO
if (s == NATS_OK)
{
char buffer[1024];
snprintf(buffer, sizeof(buffer), "PONG\r\nINFO {\"connect_urls\":[\"127.0.0.1:4222\",\"me:1\"]}\r\n");
s = natsSock_WriteFully(&ctx, buffer, (int) strlen(buffer));
}
// Wait for a signal from the client thread.
natsMutex_Lock(arg->m);
while (!arg->closed)
natsCondition_Wait(arg->c, arg->m);
arg->status = s;
natsMutex_Unlock(arg->m);
natsSock_Close(ctx.fd);
natsSock_Close(sock);
}
static void
test_ReceiveINFORightAfterFirstPONG(void)
{
natsStatus s = NATS_OK;
natsThread *t = NULL;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetAllowReconnect(opts, false));
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Verify that INFO right after PONG is ok: ");
s = natsThread_Create(&t, _serverSendsINFOAfterPONG, (void*) &arg);
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while (!arg.done)
natsCondition_Wait(arg.c, arg.m);
s = arg.status;
natsMutex_Unlock(arg.m);
}
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
int i, j;
char **servers = NULL;
int serversCount = 0;
bool ok = false;
for (i = 0; i < 100; i++)
{
s = natsConnection_GetDiscoveredServers(nc, &servers, &serversCount);
if (s != NATS_OK)
break;
ok = ((serversCount == 1)
&& (strcmp(servers[0], "nats://me:1") == 0));
for (j = 0; j < serversCount; j++)
free(servers[j]);
free(servers);
if (ok)
break;
nats_Sleep(15);
s = NATS_ERR;
}
}
if (t != NULL)
{
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
natsMutex_Lock(arg.m);
if ((s == NATS_OK) && (arg.status != NATS_OK))
s = arg.status;
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static void
test_ServerPoolUpdatedOnClusterUpdate(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsPid s1Pid = NATS_INVALID_PID;
natsPid s2Pid = NATS_INVALID_PID;
natsPid s3Pid = NATS_INVALID_PID;
natsOptions *opts = NULL;
struct threadArg arg;
int invoked = 0;
bool restartS2 = false;
if (!serverVersionAtLeast(1,0,7))
{
char txt[200];
snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 1.0.7, got %s: ", serverVersion);
test(txt);
testCond(true);
return;
}
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
opts = _createReconnectOptions();
if ((opts == NULL)
|| (natsOptions_SetURL(opts, "nats://127.0.0.1:4222") != NATS_OK)
|| (natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, &arg))
|| (natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg) != NATS_OK)
|| (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK))
{
FAIL("Unable to create reconnect options!");
}
s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats://127.0.0.1:6222 -cluster_name abc -routes nats://127.0.0.1:6223,nats://127.0.0.1:6224", true);
CHECK_SERVER_STARTED(s1Pid);
test("Connect ok: ");
s = natsConnection_Connect(&conn, opts);
testCond(s == NATS_OK);
s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats://127.0.0.1:6223 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6224", true);
if (s2Pid == NATS_INVALID_PID)
_stopServer(s1Pid);
CHECK_SERVER_STARTED(s2Pid);
test("DiscoveredServersCb triggered: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum == 0))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
invoked = arg.sum;
arg.sum = 0;
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && (invoked == 1));
{
const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223"};
test("Check pool: ");
s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
testCond(s == NATS_OK);
}
s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats://127.0.0.1:6224 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223", true);
if (s3Pid == NATS_INVALID_PID)
{
_stopServer(s1Pid);
_stopServer(s2Pid);
}
CHECK_SERVER_STARTED(s3Pid);
test("DiscoveredServersCb triggered: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum == 0))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
invoked = arg.sum;
arg.sum = 0;
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && (invoked == 1));
{
const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223", "127.0.0.1:4224"};
test("Check pool: ");
s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
testCond(s == NATS_OK);
}
// Stop s1. Since this was passed to the Connect() call, this one should
// still be present.
_stopServer(s1Pid);
s1Pid = NATS_INVALID_PID;
test("Wait for reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
arg.reconnected = false;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
{
const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223", "127.0.0.1:4224"};
test("Check pool: ");
s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
testCond(s == NATS_OK);
}
{
const char *urls[] = {"127.0.0.1:4222", ""};
int port = 0;
// Check the server we reconnected to.
natsMutex_Lock(conn->mu);
port = conn->cur->url->port;
natsMutex_Unlock(conn->mu);
if (port == 4223)
{
urls[1] = "127.0.0.1:4224";
_stopServer(s2Pid);
s2Pid = NATS_INVALID_PID;
restartS2 = true;
}
else
{
urls[1] = "127.0.0.1:4223";
_stopServer(s3Pid);
s3Pid = NATS_INVALID_PID;
}
test("Wait for reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
arg.reconnected = false;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
// The implicit server that we just shutdown should have been removed from the pool
if (s == NATS_OK)
{
test("Check pool: ");
s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
testCond(s == NATS_OK);
}
}
{
const char *urls[] = {"127.0.0.1:4222", "127.0.0.1:4223", "127.0.0.1:4224"};
if (restartS2)
{
s2Pid = _startServer("nats://127.0.0.1:4223", "-a 127.0.0.1 -p 4223 -cluster nats://127.0.0.1:6223 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6224", true);
if (s2Pid == NATS_INVALID_PID)
_stopServer(s3Pid);
CHECK_SERVER_STARTED(s2Pid);
}
else
{
s3Pid = _startServer("nats://127.0.0.1:4224", "-a 127.0.0.1 -p 4224 -cluster nats://127.0.0.1:6224 -cluster_name abc -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223", true);
if (s3Pid == NATS_INVALID_PID)
_stopServer(s2Pid);
CHECK_SERVER_STARTED(s3Pid);
}
// Since this is not a "new" server, the DiscoveredServersCB won't be invoked.
// Checking the pool may fail for a while.
test("Check pool: ");
s = _checkPool(conn, (char**)urls, (int)(sizeof(urls)/sizeof(char*)));
testCond(s == NATS_OK);
}
natsConnection_Close(conn);
_waitForConnClosed(&arg);
natsConnection_Destroy(conn);
conn = NULL;
// Restart s1
s1Pid = _startServer("nats://127.0.0.1:4222", "-a 127.0.0.1 -p 4222 -cluster nats://127.0.0.1:6222 -cluster_name abc -routes nats://127.0.0.1:6223,nats://127.0.0.1:6224", true);
if (s1Pid == NATS_INVALID_PID)
{
_stopServer(s2Pid);
_stopServer(s3Pid);
}
CHECK_SERVER_STARTED(s1Pid);
// We should have all 3 servers running now...
test("Connect ok: ");
s = natsConnection_Connect(&conn, opts);
testCond(s == NATS_OK);
{
int i;
natsSrv *srvrs[3];
test("Server pool size should be 3: ");
natsMutex_Lock(conn->mu);
s = (conn->srvPool->size == 3 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(conn->mu);
testCond(s == NATS_OK);
// Save references to servers from pool
natsMutex_Lock(conn->mu);
for (i=0; i<3; i++)
srvrs[i] = conn->srvPool->srvrs[i];
natsMutex_Unlock(conn->mu);
for (i=0; (s == NATS_OK) && (i<9); i++)
{
natsMutex_Lock(conn->mu);
natsSock_Shutdown(conn->sockCtx.fd);
natsMutex_Unlock(conn->mu);
test("Wait for reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
arg.reconnected = false;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
}
{
int j;
natsMutex_Lock(conn->mu);
test("Server pool size should be 3: ");
s = (conn->srvPool->size == 3 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(conn->mu);
testCond(s == NATS_OK);
test("Servers in pool have not been replaced: ");
natsMutex_Lock(conn->mu);
for (i=0; (s == NATS_OK) && (i<3); i++)
{
natsSrv *srv = conn->srvPool->srvrs[i];
s = NATS_ERR;
for (j=0; j<3; j++)
{
if (srvrs[j] == srv)
{
s = NATS_OK;
break;
}
}
}
natsMutex_Unlock(conn->mu);
testCond(s == NATS_OK);
}
natsConnection_Close(conn);
_waitForConnClosed(&arg);
}
natsConnection_Destroy(conn);
natsOptions_Destroy(opts);
_stopServer(s3Pid);
_stopServer(s2Pid);
_stopServer(s1Pid);
_destroyDefaultThreadArgs(&arg);
}
static void
test_ReconnectJitter(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
int64_t start = 0;
int64_t dur = 0;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Default jitter values: ");
natsMutex_Lock(opts->mu);
s = (((opts->reconnectJitter == NATS_OPTS_DEFAULT_RECONNECT_JITTER)
&& (opts->reconnectJitterTLS == NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS)) ? NATS_OK : NATS_ERR);
natsMutex_Unlock(opts->mu);
testCond(s == NATS_OK);
s = natsOptions_SetURL(opts, "nats://127.0.0.1:4222");
IFOK(s, natsOptions_SetMaxReconnect(opts, -1));
IFOK(s, natsOptions_SetReconnectWait(opts, 50));
IFOK(s, natsOptions_SetReconnectJitter(opts, 500, 0));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
// Shutdown server
_stopServer(pid);
pid = NATS_INVALID_PID;
// Wait for several iterations of failed attempts and make
// sure that overall wait time is a bit more than just
// the number of reconnect attempts.
start = nats_Now();
nats_Sleep(400);
pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true);
CHECK_SERVER_STARTED(pid);
test("Check jitter: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
if (s == NATS_OK)
dur = nats_Now() - start;
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK) && (dur >=500));
natsConnection_Destroy(nc);
nc = NULL;
// Use a long reconnect wait
s = natsOptions_SetReconnectWait(opts, 10*60*1000); // 10 minutes
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
// Cause a disconnect
_stopServer(pid);
pid = NATS_INVALID_PID;
// Wait a bit for the reconnect loop to go into wait mode.
nats_Sleep(50);
pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true);
CHECK_SERVER_STARTED(pid);
// Now close and expect the reconnect thread to return..
natsConnection_Close(nc);
test("Wait for closed: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Check reconnect thread done: ");
natsConn_Lock(nc);
s = (nc->reconnectThread == NULL ? NATS_OK : NATS_ERR);
natsConn_Unlock(nc);
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(pid);
_destroyDefaultThreadArgs(&arg);
}
static int64_t
_customReconnectDelayCB(natsConnection *nc, int attempts, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
int64_t delay = 0;
natsMutex_Lock(arg->m);
if (attempts != arg->control)
{
arg->status = NATS_ERR;
natsCondition_Signal(arg->c);
}
else
{
arg->control++;
if (attempts <= 4)
delay = 100;
else
natsConnection_Close(nc);
}
natsMutex_Unlock(arg->m);
return delay;
}
static void
test_CustomReconnectDelay(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
int64_t start = 0;
int64_t dur = 0;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
#if _WIN32
IFOK(s, natsOptions_SetTimeout(opts, 100));
#endif
IFOK(s, natsOptions_SetMaxReconnect(opts, -1));
IFOK(s, natsOptions_SetCustomReconnectDelay(opts, _customReconnectDelayCB, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test");
arg.control = 1;
arg.status = NATS_OK;
pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
// Cause disconnect
_stopServer(pid);
pid = NATS_INVALID_PID;
// We should be trying to reconnect 4 times
start = nats_Now();
// Wait on error or completion of test.
test("Check custom delay cb: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed && (arg.status == NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
IFOK(s, arg.status);
if (s == NATS_OK)
dur = nats_Now()-start;
natsMutex_Unlock(arg.m);
#if _WIN32
testCond((s == NATS_OK) && (dur <= 1000));
#else
testCond((s == NATS_OK) && (dur <= 600));
#endif
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static void
_lameDuckDiscoveredCb(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsStatus s;
char **servers = NULL;
int count = 0;
natsMutex_Lock(arg->m);
s = natsConnection_GetDiscoveredServers(nc, &servers, &count);
if (s == NATS_OK)
{
int i;
if ((count != 1) || (strcmp(servers[0], "nats://127.0.0.1:1234") != 0))
arg->status = NATS_ERR;
for (i=0; i<count; i++)
free(servers[i]);
free(servers);
}
// Use this flag to indicate that discovered cb was invoked.
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
_lameDuckCb(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
// Use this flag to indicate that LDM cb was invoked.
arg->disconnected = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
_lameDuckMockupServerThread(void *closure)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
struct threadArg *arg = (struct threadArg*) closure;
char buffer[1024];
const char *ldm[] = {"INFO {\"ldm\":true}\r\n", "INFO {\"connect_urls\":[\"127.0.0.1:1234\"],\"ldm\":true}\r\n"};
natsSockCtx ctx;
int i;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _startMockupServer(&sock, "127.0.0.1", "4222");
natsMutex_Lock(arg->m);
arg->status = s;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
for (i=0; (s == NATS_OK) && (i<2); i++)
{
if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
const char *info = "INFO {\"server_id\":\"foobar\"}\r\n";
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx,_PONG_PROTO_, _PONG_PROTO_LEN_));
if (s == NATS_OK)
{
// Wait a bit and then send a INFO with LDM
nats_Sleep(100);
s = natsSock_WriteFully(&ctx, ldm[i], (int) strlen(ldm[i]));
// Wait for client to close
if (s == NATS_OK)
natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
}
natsSock_Close(ctx.fd);
}
}
natsSock_Close(sock);
}
static void
test_LameDuckMode(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsThread *t = NULL;
int i;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetMaxReconnect(opts, -1));
IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _lameDuckDiscoveredCb, (void*) &arg));
IFOK(s, natsOptions_SetLameDuckModeCB(opts, _lameDuckCb, (void*) &arg));
// IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
if (s != NATS_OK)
FAIL("Unable to setup test");
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.status = NATS_ERR;
s = natsThread_Create(&t, _lameDuckMockupServerThread, (void*) &arg);
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
for (i=0; i<2; i++)
{
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Lame duck callback invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
if (i == 0)
{
test("Discovered not invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 200);
if ((arg.status == NATS_OK) && (s == NATS_TIMEOUT))
s = NATS_OK;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
}
else
{
test("Discovered servers ok: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
IFOK(s, arg.status);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
}
natsConnection_Destroy(nc);
nc = NULL;
}
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static void
test_Version(void)
{
const char *str = NULL;
test("Compatibility: ");
testCond(nats_CheckCompatibility() == true);
test("Version string: ");
str = nats_GetVersion();
testCond((str != NULL)
&& (strcmp(str, LIB_NATS_VERSION_STRING) == 0));
test("Version number: ");
testCond(nats_GetVersionNumber() == LIB_NATS_VERSION_NUMBER);
}
static void
test_VersionMatchesTag(void)
{
natsStatus s = NATS_OK;
const char *tag;
tag = getenv("TRAVIS_TAG");
if ((tag == NULL) || (tag[0] == '\0'))
{
test("Skipping test since no tag detected: ");
testCond(true);
return;
}
test("Check tag and version match: ");
// 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')
s = NATS_ERR;
else
{
// Strip the `v` from the tag for the version comparison.
s = (strcmp(nats_GetVersion(), tag+1) == 0 ? NATS_OK : NATS_ERR);
}
testCond(s == NATS_OK);
}
static void
_openCloseAndWaitMsgCB(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
nats_Sleep(300);
natsMsg_Destroy(msg);
natsMutex_Lock(arg->m);
arg->results[0]++;
natsMutex_Unlock(arg->m);
}
static void
_openCloseAndWaitConnClosedCB(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->sum++;
natsMutex_Unlock(arg->m);
}
static void
_openCloseAndWaitCloseFromThread(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->status = nats_CloseAndWait(0);
arg->done = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
_openCloseAndWaitThread(void *closure)
{
nats_Sleep(300);
natsLib_Release();
}
static void
test_OpenCloseAndWait(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts= NULL;
natsSubscription *sub = NULL;
natsPid pid = NATS_INVALID_PID;
int i;
natsThread *t = NULL;
struct threadArg arg;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
if (_createDefaultThreadArgsForCbTests(&arg) != NATS_OK)
FAIL("Unable to setup test");
// First close the library since it is opened in main
test("Close to prepare for test: ");
s = nats_CloseAndWait(0);
testCond(s == NATS_OK);
test("Open/Close in loop: ");
for (i=0;i<2;i++)
{
s = nats_Open(-1);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetClosedCB(opts, _openCloseAndWaitConnClosedCB, (void*)&arg));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _openCloseAndWaitMsgCB, (void*)&arg));
IFOK(s, natsConnection_PublishString(nc, "foo", "hello"));
IFOK(s, natsConnection_Flush(nc));
if (s == NATS_OK)
{
for (;;)
{
natsMutex_Lock(arg.m);
if (arg.results[0] == (i+1))
{
natsMutex_Unlock(arg.m);
break;
}
natsMutex_Unlock(arg.m);
nats_Sleep(100);
}
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
nats_CloseAndWait(0);
}
}
testCond(s == NATS_OK);
test("Check async cb count: ");
natsMutex_Lock(arg.m);
testCond(arg.sum == 2);
natsMutex_Unlock(arg.m);
test("Check msgs count: ");
natsMutex_Lock(arg.m);
testCond(arg.results[0] == 2);
natsMutex_Unlock(arg.m);
test("Close while not opened returns error: ");
s = nats_CloseAndWait(0);
testCond(s == NATS_NOT_INITIALIZED);
// Re-open for rest of test
nats_Open(-1);
test("Check Close from thread returns error: ");
s = natsThread_Create(&t, _openCloseAndWaitCloseFromThread, (void*)&arg);
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
s = (arg.status == NATS_ILLEGAL_STATE ? NATS_OK : NATS_ERR);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
}
testCond(s == NATS_OK);
test("No timeout: ");
natsLib_Retain();
s = natsThread_Create(&t, _openCloseAndWaitThread, NULL);
IFOK(s, nats_CloseAndWait(0));
testCond(s == NATS_OK);
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
nats_Open(-1);
test("Timeout: ");
natsLib_Retain();
s = natsThread_Create(&t, _openCloseAndWaitThread, NULL);
IFOK(s, nats_CloseAndWait(100));
testCond(s == NATS_TIMEOUT);
// Now wait for thread to exit
natsThread_Join(t);
natsThread_Destroy(t);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
_testGetLastErrInThread(void *arg)
{
natsStatus getLastErrSts;
natsOptions *opts = NULL;
const char *getLastErr = NULL;
test("Check that new thread has get last err clear: ");
getLastErr = nats_GetLastError(&getLastErrSts);
testCond((getLastErr == NULL) && (getLastErrSts == NATS_OK));
natsOptions_Destroy(opts);
}
static void
test_GetLastError(void)
{
natsStatus s, getLastErrSts;
natsOptions *opts = NULL;
const char *getLastErr = NULL;
natsThread *t = NULL;
char stackBuf[256];
FILE *stackFile = NULL;
stackBuf[0] = '\0';
test("Check GetLastError returns proper status: ");
s = natsOptions_SetAllowReconnect(NULL, false);
getLastErr = nats_GetLastError(&getLastErrSts);
testCond((s == getLastErrSts)
&& (getLastErr != NULL)
&& (strstr(getLastErr, "Invalid") != NULL));
test("Check GetLastErrorStack with invalid args: ");
s = nats_GetLastErrorStack(NULL, 10);
if (s != NATS_OK)
s = nats_GetLastErrorStack(stackBuf, 0);
testCond(s == NATS_INVALID_ARG);
test("Check GetLastErrorStack returns proper insufficient buffer: ");
s = nats_GetLastErrorStack(stackBuf, 10);
testCond(s == NATS_INSUFFICIENT_BUFFER);
test("Check GetLastErrorStack: ");
s = nats_GetLastErrorStack(stackBuf, sizeof(stackBuf));
testCond((s == NATS_OK)
&& (strlen(stackBuf) > 0)
&& (strstr(stackBuf, "natsOptions_SetAllowReconnect") != NULL));
test("Check PrintStack: ");
stackBuf[0] = '\0';
stackFile = fopen("stack.txt", "w");
if (stackFile == NULL)
FAIL("Unable to create a file for print stack test");
s = NATS_OK;
nats_PrintLastErrorStack(stackFile);
fclose(stackFile);
stackFile = fopen("stack.txt", "r");
s = (fgets(stackBuf, sizeof(stackBuf), stackFile) == NULL ? NATS_ERR : NATS_OK);
if ((s == NATS_OK)
&& ((strlen(stackBuf) == 0)
|| (strstr(stackBuf, "Invalid Argument") == NULL)))
{
s = NATS_ERR;
}
s = (fgets(stackBuf, sizeof(stackBuf), stackFile) == NULL ? NATS_ERR : NATS_OK);
if (s == NATS_OK)
s = (fgets(stackBuf, sizeof(stackBuf), stackFile) == NULL ? NATS_ERR : NATS_OK);
if ((s == NATS_OK)
&& ((strlen(stackBuf) == 0)
|| (strstr(stackBuf, "natsOptions_SetAllowReconnect") == NULL)))
{
s = NATS_ERR;
}
testCond(s == NATS_OK);
fclose(stackFile);
remove("stack.txt");
test("Check the error not cleared until next error occurs: ");
s = natsOptions_Create(&opts);
getLastErr = nats_GetLastError(&getLastErrSts);
testCond((s == NATS_OK)
&& (getLastErrSts != NATS_OK)
&& (getLastErr != NULL)
&& (strstr(getLastErr, "Invalid") != NULL));
s = natsThread_Create(&t, _testGetLastErrInThread, NULL);
if (s == NATS_OK)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
nats_clearLastError();
stackBuf[0] = '\0';
test("Check stack not updated when asked: ");
nats_doNotUpdateErrStack(true);
s = natsConnection_Publish(NULL, NULL, NULL, 0);
nats_GetLastErrorStack(stackBuf, sizeof(stackBuf));
testCond((s != NATS_OK)
&& (stackBuf[0] == '\0'));
test("Check call reentrant: ");
nats_doNotUpdateErrStack(true);
nats_doNotUpdateErrStack(false);
s = natsConnection_Publish(NULL, NULL, NULL, 0);
nats_GetLastErrorStack(stackBuf, sizeof(stackBuf));
testCond((s != NATS_OK)
&& (stackBuf[0] == '\0'));
nats_doNotUpdateErrStack(false);
test("Check stack updates again: ");
s = natsConnection_Publish(NULL, NULL, NULL, 0);
nats_GetLastErrorStack(stackBuf, sizeof(stackBuf));
testCond((s != NATS_OK)
&& (stackBuf[0] != '\0'));
nats_clearLastError();
}
static void
test_StaleConnection(void)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
int i;
const char *stale_conn_err = "-ERR 'Stale Connection'\r\n";
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&(arg.opts)));
IFOK(s, natsOptions_SetReconnectWait(arg.opts, 20));
IFOK(s, natsOptions_SetReconnectJitter(arg.opts, 0, 0));
IFOK(s, natsOptions_SetMaxReconnect(arg.opts, 100));
IFOK(s, natsOptions_SetDisconnectedCB(arg.opts, _disconnectedCb, &arg));
IFOK(s, natsOptions_SetReconnectedCB(arg.opts, _reconnectedCb, &arg));
IFOK(s, natsOptions_SetClosedCB(arg.opts, _closedCb, &arg));
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
arg.control = 5;
test("Behavior of connection on Stale Connection: ")
s = _startMockupServer(&sock, "localhost", "4222");
// Start the thread that will try to connect to our server...
IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg));
for (i = 0; (i < 2) && (s == NATS_OK); i++)
{
if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char info[1024];
strncpy(info,
"INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n",
sizeof(info));
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_));
if ((s == NATS_OK) && (i == 0))
{
// Wait a tiny, and simulate a Stale Connection
nats_Sleep(50);
s = natsSock_WriteFully(&ctx, stale_conn_err,
(int)strlen(stale_conn_err));
// The client should try to reconnect. When getting the
// disconnected callback, wait for the disconnect cb
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !(arg.disconnected))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
}
else if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
// We should check on !arg.closed here, but unfortunately,
// on Windows, the thread calling natsConnection_Close()
// would be blocked waiting for the readLoop thread to
// exit, which it would not because fd shutdown is not
// causing a socket error if the server is not closing
// its side.
while ((s != NATS_TIMEOUT) && (arg.disconnects != 2))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
}
natsSock_Close(ctx.fd);
}
}
natsSock_Close(sock);
// Wait for the client to finish.
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsMutex_Lock(arg.m);
IFOK(s, arg.status);
if (s == NATS_OK)
{
// Wait for closed CB
while ((s != NATS_TIMEOUT) && !(arg.closed))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
IFOK(s, arg.status);
}
natsMutex_Unlock(arg.m);
testCond((s == NATS_OK)
&& (arg.disconnects == 2)
&& (arg.reconnects == 1)
&& (arg.closed));
_destroyDefaultThreadArgs(&arg);
}
static void
test_ServerErrorClosesConnection(void)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
natsThread *t = NULL;
struct threadArg arg;
natsSockCtx ctx;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&(arg.opts)));
IFOK(s, natsOptions_SetReconnectWait(arg.opts, 20));
IFOK(s, natsOptions_SetReconnectJitter(arg.opts, 0, 0));
IFOK(s, natsOptions_SetMaxReconnect(arg.opts, 100));
IFOK(s, natsOptions_SetDisconnectedCB(arg.opts, _disconnectedCb, &arg));
IFOK(s, natsOptions_SetReconnectedCB(arg.opts, _reconnectedCb, &arg));
IFOK(s, natsOptions_SetClosedCB(arg.opts, _closedCb, &arg));
if (s != NATS_OK)
FAIL("@@ Unable to setup test!");
arg.control = 6;
arg.string = "Any Error";
test("Behavior of connection on Server Error: ")
s = _startMockupServer(&sock, "localhost", "4222");
// Start the thread that will try to connect to our server...
IFOK(s, natsThread_Create(&t, _connectToMockupServer, (void*) &arg));
if ((s == NATS_OK)
&& (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK)))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char info[1024];
strncpy(info,
"INFO {\"server_id\":\"foobar\",\"version\":\"latest\",\"go\":\"latest\",\"host\":\"localhost\",\"port\":4222,\"auth_required\":false,\"tls_required\":false,\"max_payload\":1048576}\r\n",
sizeof(info));
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_));
if (s == NATS_OK)
{
// Wait a tiny, and simulate an error sent by the server
nats_Sleep(50);
snprintf(info, sizeof(info), "-ERR '%s'\r\n", arg.string);
s = natsSock_WriteFully(&ctx, info, (int)strlen(info));
}
// Wait for the client to be done.
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !(arg.closed))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
natsSock_Close(ctx.fd);
}
natsSock_Close(sock);
// Wait for the client to finish.
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsMutex_Lock(arg.m);
if (s == NATS_OK)
{
// Wait for closed CB
while ((s != NATS_TIMEOUT) && !(arg.closed))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
IFOK(s, arg.status);
}
natsMutex_Unlock(arg.m);
testCond((s == NATS_ERR)
&& (arg.disconnects == 1)
&& (arg.reconnects == 0)
&& (arg.closed));
_destroyDefaultThreadArgs(&arg);
}
static void
test_NoEcho(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *conn = NULL;
natsSubscription *sub = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetNoEcho(opts, true));
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
arg.control = 0;
arg.string = "test";
test("Setup: ");
s = natsConnection_Connect(&conn, opts);
IFOK(s, natsConnection_Subscribe(&sub, conn, "foo", _recvTestString, (void*)&arg));
IFOK(s, natsConnection_PublishString(conn, "foo", arg.string));
IFOK(s, natsConnection_Flush(conn));
// repeat
IFOK(s, natsConnection_Flush(conn));
testCond(s == NATS_OK);
test("NoEcho: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 500);
natsMutex_Unlock(arg.m);
// Message should not be received.
testCond(s == NATS_TIMEOUT);
natsSubscription_Destroy(sub);
natsConnection_Destroy(conn);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
_startMockupServerThread(void *closure)
{
natsStatus s = NATS_OK;
natsSock sock = NATS_SOCK_INVALID;
struct threadArg *arg = (struct threadArg*) closure;
natsSockCtx ctx;
testCheckInfoCB checkInfoCB = NULL;
memset(&ctx, 0, sizeof(natsSockCtx));
s = _startMockupServer(&sock, "localhost", "4222");
natsMutex_Lock(arg->m);
arg->status = s;
natsCondition_Signal(arg->c);
checkInfoCB = arg->checkInfoCB;
natsMutex_Unlock(arg->m);
if (((ctx.fd = accept(sock, NULL, NULL)) == NATS_SOCK_INVALID)
|| (natsSock_SetCommonTcpOptions(ctx.fd) != NATS_OK))
{
s = NATS_SYS_ERROR;
}
if (s == NATS_OK)
{
char info[1024];
snprintf(info, sizeof(info), "%s", arg->string);
// Send INFO.
s = natsSock_WriteFully(&ctx, info, (int) strlen(info));
if (s == NATS_OK)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Read connect and ping commands sent from the client
s = natsSock_ReadLine(&ctx, buffer, sizeof(buffer));
if ((s == NATS_OK) && (checkInfoCB != NULL))
s = (*checkInfoCB)(buffer);
IFOK(s, natsSock_ReadLine(&ctx, buffer, sizeof(buffer)));
}
// Send PONG
IFOK(s, natsSock_WriteFully(&ctx, _PONG_PROTO_, _PONG_PROTO_LEN_));
if (s == NATS_OK)
{
// Wait for client to tell us it is done
natsMutex_Lock(arg->m);
while ((s != NATS_TIMEOUT) && !(arg->done))
s = natsCondition_TimedWait(arg->c, arg->m, 10000);
natsMutex_Unlock(arg->m);
}
natsSock_Close(ctx.fd);
}
natsSock_Close(sock);
}
static void
test_NoEchoOldServer(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsOptions *opts = NULL;
natsThread *t = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetNoEcho(opts, true));
if (s == NATS_OK)
{
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.status = NATS_ERR;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("NoEcho with old server: ");
s = natsConnection_Connect(&conn, opts);
testCond(s == NATS_NO_SERVER_SUPPORT);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsOptions_Destroy(opts);
natsThread_Join(t);
natsThread_Destroy(t);
_destroyDefaultThreadArgs(&arg);
}
static void
test_DrainSub(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub= NULL;
natsSubscription *sub2 = NULL;
natsSubscription *sub3 = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test");
arg.control = 8;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect and create subscriptions: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
IFOK(s, natsConnection_SubscribeSync(&sub2, nc, "foo"));
IFOK(s, natsConnection_SubscribeSync(&sub3, nc, "foo"));
IFOK(s, natsSubscription_AutoUnsubscribe(sub3, 2));
testCond(s == NATS_OK);
test("WaitForDrainCompletion returns invalid arg: ");
s = natsSubscription_WaitForDrainCompletion(NULL, 2000);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("WaitForDrainCompletion returns illegal state: ");
s = natsSubscription_WaitForDrainCompletion(sub, 2000);
testCond(s == NATS_ILLEGAL_STATE);
nats_clearLastError();
test("Send 2 messages: ");
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_PublishString(nc, "foo", "msg"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Call Drain on subscription: ");
// Pass 0 or negative value to represent "for ever" timeout.
s = natsSubscription_DrainTimeout(sub, -1);
testCond(s == NATS_OK);
test("Call Drain a second time is ok: ");
s = natsSubscription_Drain(sub);
testCond(s == NATS_OK);
test("Drain sync subs: ");
s = natsSubscription_Drain(sub2);
IFOK(s, natsSubscription_Drain(sub3));
testCond(s == NATS_OK);
test("Wait for Drain times out: ");
s = natsSubscription_WaitForDrainCompletion(sub, 10);
if (s == NATS_TIMEOUT)
s = natsSubscription_WaitForDrainCompletion(sub2, 10);
testCond(s == NATS_TIMEOUT);
nats_clearLastError();
test("Send 1 more message: ");
s = natsConnection_PublishString(nc, "foo", "msg");
testCond(s == NATS_OK);
// Unblock the callback.
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
test("Wait for Drain to complete: ");
s = natsSubscription_WaitForDrainCompletion(sub, -1);
testCond(s == NATS_OK);
// Wait a bit and make sure that we did not receive the 3rd msg
test("Third message not received: ");
nats_Sleep(100);
natsMutex_Lock(arg.m);
if ((s == NATS_OK) && (arg.sum != 2))
s = NATS_ERR;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Drain on closed sub fails: ");
s = natsSubscription_Drain(sub);
testCond(s == NATS_INVALID_SUBSCRIPTION);
nats_clearLastError();
test("Consume sync messages: ");
{
natsMsg *msg = NULL;
int i;
s = NATS_OK;
for (i=0; (s == NATS_OK) && (i<2); i++)
{
s = natsSubscription_NextMsg(&msg, sub2, 2000);
natsMsg_Destroy(msg);
msg = NULL;
}
for (i=0; (s == NATS_OK) && (i<2); i++)
{
s = natsSubscription_NextMsg(&msg, sub3, 2000);
natsMsg_Destroy(msg);
msg = NULL;
}
}
testCond(s == NATS_OK);
test("Wait for drain to complete: ");
s = natsSubscription_WaitForDrainCompletion(sub2, 1000);
IFOK(s, natsSubscription_WaitForDrainCompletion(sub3, 1000));
testCond(s == NATS_OK);
// Repeat async test with auto-unsubscribe
natsSubscription_Destroy(sub);
sub = NULL;
natsMutex_Lock(arg.m);
arg.sum = 0;
arg.closed = false;
natsMutex_Unlock(arg.m);
test("Async sub with auto-unsub: ");
s = natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*)&arg);
IFOK(s, natsSubscription_AutoUnsubscribe(sub, 2));
testCond(s == NATS_OK);
test("Send 2 messages: ");
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_PublishString(nc, "foo", "msg"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Check drain status with invalid arg: ");
s = natsSubscription_DrainCompletionStatus(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Check drain status fails: ");
s = natsSubscription_DrainCompletionStatus(sub);
testCond(s == NATS_ILLEGAL_STATE);
test("Call Drain on subscription: ");
s = natsSubscription_Drain(sub);
testCond(s == NATS_OK);
test("Send 1 more message: ");
s = natsConnection_PublishString(nc, "foo", "msg");
testCond(s == NATS_OK);
// Unblock the callback.
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
test("Wait for Drain to complete: ");
s = natsSubscription_WaitForDrainCompletion(sub, -1);
testCond(s == NATS_OK);
test("Check drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
testCond(s == NATS_OK);
// Wait a bit and make sure that we did not receive the 3rd msg
test("Third message not received: ");
nats_Sleep(100);
natsMutex_Lock(arg.m);
s = (arg.sum == 2 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
// Close connection
natsConnection_Close(nc);
test("Drain on closed conn fails: ");
s = natsSubscription_Drain(sub);
if (s == NATS_CONNECTION_CLOSED)
s = natsSubscription_Drain(sub2);
if (s == NATS_CONNECTION_CLOSED)
s = natsSubscription_Drain(sub3);
testCond(s == NATS_CONNECTION_CLOSED);
natsSubscription_Destroy(sub);
sub = NULL;
natsSubscription_Destroy(sub2);
sub2 = NULL;
natsSubscription_Destroy(sub3);
sub3 = NULL;
natsConnection_Destroy(nc);
nc = NULL;
natsMutex_Lock(arg.m);
arg.sum = 0;
arg.closed = false;
natsMutex_Unlock(arg.m);
test("Connect and create sub: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
testCond(s == NATS_OK);
test("Send 2 messages: ");
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_PublishString(nc, "foo", "msg"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Disconnect: ");
_stopServer(pid);
testCond(s == NATS_OK);
test("Wait for disconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Call Drain on subscriptions: ");
s = natsSubscription_DrainTimeout(sub, 500);
testCond(s == NATS_OK);
// Unblock the callback.
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
test("Wait for Drain to complete: ");
s = natsSubscription_WaitForDrainCompletion(sub, -1);
testCond(s == NATS_OK);
test("Check drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
// Since the flush has timed-out, this is what the status will be.
testCond(s == NATS_TIMEOUT);
s = NATS_OK;
natsSubscription_Destroy(sub);
sub = NULL;
natsConnection_Destroy(nc);
nc = NULL;
natsOptions_Destroy(opts);
opts = NULL;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
natsMutex_Lock(arg.m);
arg.sum = 0;
arg.closed = false;
natsMutex_Unlock(arg.m);
test("Create options for global msg delivery: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true));
testCond(s == NATS_OK);
test("Connect and create sub: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*)&arg));
IFOK(s, natsConnection_Subscribe(&sub2, nc, "foo", _recvTestString, (void*)&arg));
IFOK(s, natsSubscription_AutoUnsubscribe(sub, 2));
testCond(s == NATS_OK);
test("Send 2 messages: ");
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_PublishString(nc, "foo", "msg"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Call Drain on subscriptions: ");
s = natsSubscription_Drain(sub);
IFOK(s, natsSubscription_Drain(sub2));
testCond(s == NATS_OK);
// Unblock the callback.
nats_Sleep(250);
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
test("Wait for Drain to complete: ");
s = natsSubscription_WaitForDrainCompletion(sub, -1);
testCond(s == NATS_OK);
test("Check drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsSubscription_Destroy(sub2);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
_msgCBForDrainSubTest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMsg_Destroy(msg);
natsMutex_Lock(arg->m);
if (++arg->sum == 1)
{
// Signal that we got the message
natsCondition_Signal(arg->c);
// Wait for main thread to switch to drain mode
while (!arg->done)
natsCondition_Wait(arg->c, arg->m);
// Report unsubscribe status
arg->status = natsSubscription_Unsubscribe(sub);
}
natsMutex_Unlock(arg->m);
}
static void
_drainSubCompleteCB(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
if (arg->sum == 1)
{
arg->closed = true;
natsCondition_Signal(arg->c);
}
natsMutex_Unlock(arg->m);
}
static void
test_DrainSubStops(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub= NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
int i;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect and create subscriptions: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _msgCBForDrainSubTest, (void*) &arg));
IFOK(s, natsSubscription_SetOnCompleteCB(sub, _drainSubCompleteCB, (void*) &arg))
testCond(s == NATS_OK);
test("Send 10 messages: ");
for (i=0; (s == NATS_OK) && (i<10); i++)
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Wait for 1st message to be received: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum != 1))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Drain subscription: ");
s = natsSubscription_Drain(sub);
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Wait for drain completion: ");
s = natsSubscription_WaitForDrainCompletion(sub, 0);
testCond(s == NATS_OK);
test("Check drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
testCond(s == NATS_INVALID_SUBSCRIPTION);
test("Check that drain stopped on unsubscribe: ");
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
// Get the status from unsubscribe in the callback
IFOK(s, arg.status);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
natsMutex_Lock(arg.m);
arg.done = false;
arg.closed = false;
arg.sum = 0;
natsMutex_Unlock(arg.m);
test("Create subscription: ");
s = natsConnection_Subscribe(&sub, nc, "foo", _msgCBForDrainSubTest, (void*) &arg);
IFOK(s, natsSubscription_SetOnCompleteCB(sub, _drainSubCompleteCB, (void*) &arg))
testCond(s == NATS_OK);
test("Send 10 messages: ");
for (i=0; (s == NATS_OK) && (i<10); i++)
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Wait for 1st message to be received: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum != 1))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Drain connection: ");
s = natsConnection_Drain(nc);
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Wait for Drain to complete: ");
s = natsSubscription_WaitForDrainCompletion(sub, 0);
testCond(s == NATS_OK);
test("Check that drain stopped on unsubscribe: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
// Get the status from unsubscribe in the callback
IFOK(s, arg.status);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
test_DrainSubRaceOnAutoUnsub(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub= NULL;
natsPid pid = NATS_INVALID_PID;
int i;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
testCond(s == NATS_OK);
testDrainAutoUnsubRace = true;
test("Drain with auto-unsub race: ");
for (i=0; (s == NATS_OK) && (i<500); i++)
{
s = natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL);
IFOK(s, natsSubscription_AutoUnsubscribe(sub, 1));
IFOK(s, natsConnection_PublishString(nc, "foo", "msg"));
nats_Sleep(1);
if (s == NATS_OK)
{
s = natsSubscription_Drain(sub);
// Here, it is possible that the subscription is already
// invalid. In which case, don't attempt to wait for completion.
if (s == NATS_INVALID_SUBSCRIPTION)
{
s = NATS_OK;
nats_clearLastError();
}
else
{
IFOK(s, natsSubscription_WaitForDrainCompletion(sub, -1));
IFOK(s, natsSubscription_DrainCompletionStatus(sub));
}
}
natsSubscription_Destroy(sub);
sub = NULL;
}
testCond(s == NATS_OK);
testDrainAutoUnsubRace = false;
natsConnection_Destroy(nc);
_stopServer(pid);
}
static void
test_DrainSubNotResentOnReconnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
natsStatistics stats;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetMaxReconnect(opts, -1));
IFOK(s, natsOptions_SetReconnectWait(opts, 10));
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
natsMutex_Lock(arg.m);
arg.control = 8;
natsMutex_Unlock(arg.m);
test("Connect and create subscription: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
testCond(s == NATS_OK);
test("Send 1 message: ");
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Wait for message to be received: ");
nats_Sleep(150);
testCond(s == NATS_OK);
test("Drain subscription: ");
s = natsSubscription_Drain(sub);
testCond(s == NATS_OK);
test("Disconnect: ");
nats_Sleep(250);
_stopServer(pid);
testCond(s == NATS_OK);
test("Restart server: ");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
testCond(s == NATS_OK);
test("Wait for reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Release cb: ");
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Wait for drain completion: ");
s = natsSubscription_WaitForDrainCompletion(sub, 0);
testCond(s == NATS_OK);
test("Check drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
testCond(s == NATS_OK);
test("Send new message: ");
s = natsConnection_PublishString(nc, "foo", "msg");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Msg not received by connection: ");
s = natsConnection_GetStats(nc, &stats);
IFOK(s, (stats.inMsgs == 1 ? NATS_OK : NATS_ERR));
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
_drainConnBarSub(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->results[1]++;
if (args->results[1] == args->results[0])
{
args->done = true;
natsCondition_Broadcast(args->c);
}
natsMutex_Unlock(args->m);
natsMsg_Destroy(msg);
}
static void
_drainConnFooSub(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
nats_Sleep(10);
natsMutex_Lock(args->m);
args->sum++;
if (args->status == NATS_OK)
args->status = natsConnection_PublishString(nc, natsMsg_GetReply(msg), "Stop bugging me");
natsMutex_Unlock(args->m);
natsMsg_Destroy(msg);
}
static void
_drainConnErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
const char *lastError = NULL;
natsStatus s = NATS_OK;
natsMutex_Lock(args->m);
// Since error handler is async, there is no guarantee that
// natsConnection_GetLastError() returns the error we expect.
// Only check if the error matches `err`.
if (err == NATS_TIMEOUT)
{
s = natsConnection_GetLastError(nc, &lastError);
if ((s != NATS_TIMEOUT)
|| (strstr(lastError, args->string) != NULL))
{
args->done = true;
natsCondition_Broadcast(args->c);
}
}
natsMutex_Unlock(args->m);
}
static void
test_DrainConn(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsConnection *nc2 = NULL;
natsSubscription *sub2 = NULL;
natsSubscription *sub3 = NULL;
natsPid pid = NATS_INVALID_PID;
int expected= 50;
int64_t start = 0;
int i;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*)&arg));
IFOK(s, natsOptions_SetErrorHandler(opts, _drainConnErrHandler, (void*) &arg));
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
natsOptions_Destroy(opts);
FAIL("Unable to setup test");
}
arg.results[0] = expected;
arg.string = "Drain error";
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Drain with invalid NULL: ");
s = natsConnection_Drain(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Drain with no sub/pub ok: ");
s = natsConnection_Drain(nc);
testCond(s == NATS_OK);
test("Closed CB invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
arg.closed = false;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("No async error reported: ");
natsMutex_Lock(arg.m);
s = (arg.done == false ? NATS_OK : NATS_ERR);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_ConnectTo(&nc2, "nats://127.0.0.1:4222"));
testCond(s == NATS_OK);
test("Create listener for responses on bar: ");
s = natsConnection_Subscribe(&sub2, nc2, "bar", _drainConnBarSub, (void*) &arg);
testCond(s == NATS_OK);
test("Create slow consumer for responder: ");
s = natsConnection_Subscribe(&sub, nc, "foo", _drainConnFooSub, (void*) &arg);
testCond(s == NATS_OK);
test("Send messages: ");
for (i=0; (s == NATS_OK) && (i<expected); i++)
s = natsConnection_PublishRequestString(nc, "foo", "bar", "Slow Slow");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Drain connection: ");
start = nats_Now();
// 0 or Negative means "wait for ever".
s = natsConnection_DrainTimeout(nc, -1);
testCond(s == NATS_OK);
test("Check IsDraining: ");
s = (natsConnection_IsDraining(nc) ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Second drain ok: ");
s = natsConnection_Drain(nc);
testCond(s == NATS_OK);
test("Cannot create new subs: ");
s = natsConnection_Subscribe(&sub3, nc, "foo", _dummyMsgHandler, NULL);
testCond(s == NATS_DRAINING);
nats_clearLastError();
test("Publish should be ok: ");
s = natsConnection_PublishString(nc, "baz", "should work");
testCond(s == NATS_OK);
test("Closed CB should be invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Drain took as expected: ");
s = ((nats_Now() - start) >= 10*expected ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Received all messages: ");
natsMutex_Lock(arg.m);
s = ((arg.sum == expected) ? NATS_OK : NATS_ERR);
if (s == NATS_OK)
s = arg.status;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("All responses received: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
if ((s == NATS_OK) && (arg.results[1] != expected))
s = NATS_ERR;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Check sub drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
testCond(s == NATS_OK);
test("Check IsDraining: ");
s = (natsConnection_IsDraining(nc) ? NATS_ERR : NATS_OK);
testCond(s == NATS_OK);
test("Drain after closed should fail: ");
s = natsConnection_DrainTimeout(nc, 1);
testCond(s == NATS_CONNECTION_CLOSED);
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
natsConnection_Destroy(nc);
nc = NULL;
natsMutex_Lock(arg.m);
arg.done = false;
arg.sum = 0;
arg.string = "timeout";
natsMutex_Unlock(arg.m);
test("Connect and subscribe: ");
s = natsConnection_Connect(&nc, opts);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _drainConnFooSub, (void*) &arg));
testCond(s == NATS_OK);
test("Publish: ");
for (i=0;(s==NATS_OK) && i<25;i++)
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Drain timeout: ");
s = natsConnection_DrainTimeout(nc, 10);
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 1000);
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
test("Wait for subscription to drain: ");
s = natsSubscription_WaitForDrainCompletion(sub, -1);
testCond(s == NATS_OK);
test("Check sub drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
// Since the connection drain timed-out, we should report as a timeout,
// not as the connection closed.
testCond(s == NATS_TIMEOUT);
natsSubscription_Destroy(sub);
sub = NULL;
natsSubscription_Destroy(sub2);
natsSubscription_Destroy(sub3);
natsConnection_Destroy(nc);
nc = NULL;
natsConnection_Destroy(nc2);
nc2 = NULL;
natsOptions_Destroy(opts);
natsMutex_Lock(arg.m);
arg.closed = false;
arg.sum = 0;
arg.control= 8;
natsMutex_Unlock(arg.m);
test("Connect and create sub: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_Subscribe(&sub, nc, "foo", _recvTestString, (void*) &arg));
IFOK(s, natsConnection_ConnectTo(&nc2, NATS_DEFAULT_URL));
testCond(s == NATS_OK);
test("Send messages: ");
s = natsConnection_PublishString(nc, "foo", "msg1");
IFOK(s, natsConnection_PublishString(nc, "foo", "msg2"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Drain: ");
s = natsConnection_DrainTimeout(nc, 10000);
testCond(s == NATS_OK);
test("Drain sub directly should fail: ");
s = natsSubscription_Drain(sub);
testCond(s == NATS_DRAINING);
nats_clearLastError();
s = NATS_OK;
test("Disconnect: ");
_stopServer(pid);
testCond(s == NATS_OK);
nats_Sleep(100);
test("Drain while disconnected fails: ");
s = natsConnection_Drain(nc2);
testCond(s == NATS_ILLEGAL_STATE);
nats_clearLastError();
s = NATS_OK;
test("Release cb: ");
natsMutex_Lock(arg.m);
arg.closed = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Wait for completion: ");
s = natsSubscription_WaitForDrainCompletion(sub, 1000);
testCond(s == NATS_OK);
test("Check drain status: ");
s = natsSubscription_DrainCompletionStatus(sub);
testCond(s == NATS_CONNECTION_CLOSED);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsConnection_Destroy(nc2);
// Since the drain timed-out and closed the connection,
// the subscription will be closed but there is no guarantee
// that the callback is not in the middle of execution at that
// time. So to avoid valgrind reports, sleep a bit before
// destroying sub's closure.
nats_Sleep(100);
_destroyDefaultThreadArgs(&arg);
}
static void
_noDoubleCloseCb(natsConnection *nc, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->sum++;
arg->closed = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
_noDoubleCbSubCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
nats_Sleep(200);
natsMsg_Destroy(msg);
}
static void
test_NoDoubleConnClosedOnDrain(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetClosedCB(opts, _noDoubleCloseCb, (void*)&arg));
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
natsOptions_Destroy(opts);
FAIL("Unable to setup test");
}
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Create sub: ");
s = natsConnection_Subscribe(&sub, nc, "foo", _noDoubleCbSubCb, (void*)&arg);
testCond(s == NATS_OK);
test("Publish msg: ");
s = natsConnection_PublishString(nc, "foo", "hello");
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Drain: ");
s = natsConnection_Drain(nc);
testCond(s == NATS_OK);
nats_Sleep(200);
test("Closing: ");
natsConnection_Close(nc);
testCond(s == NATS_OK);
test("Wait for close CB: ");
s = _waitForConnClosed(&arg);
testCond(s == NATS_OK);
// Now wait for connection close and make sure it was invoked once.
test("Check closeCb invoked once: ")
nats_Sleep(300);
natsMutex_Lock(arg.m);
s = (arg.sum == 1 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
test_GetClientID(void)
{
natsStatus s;
natsPid pid1 = NATS_INVALID_PID;
natsPid pid2 = NATS_INVALID_PID;
natsConnection *nc1 = NULL;
natsConnection *nc2 = NULL;
natsOptions *opts = NULL;
uint64_t cid = 0;
uint64_t newcid = 0;
natsThread *t = NULL;
struct threadArg arg;
if (!serverVersionAtLeast(1,2,0))
{
char txt[200];
snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 1.2.0, got %s: ", serverVersion);
test(txt);
testCond(true);
return;
}
pid1 = _startServer("nats://127.0.0.1:4222", "-cluster nats://127.0.0.1:6222 -cluster_name abc", true);
CHECK_SERVER_STARTED(pid1);
test("Create nc1: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetReconnectWait(opts, 50));
IFOK(s, natsOptions_SetTimeout(opts, 250));
IFOK(s, natsOptions_SetDiscoveredServersCB(opts, _discoveredServersCb, (void*)&arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*)&arg));
IFOK(s, natsConnection_Connect(&nc1, opts));
testCond(s == NATS_OK);
test("GetClientID for nc1: ");
s = natsConnection_GetClientID(nc1, &cid);
testCond((s == NATS_OK) && (cid != 0));
test("Wait for discovered callback: ");
pid2 = _startServer("nats://127.0.0.1:4223", "-p 4223 -cluster nats://127.0.0.1:6223 -cluster_name abc -routes nats://127.0.0.1:6222", true);
CHECK_SERVER_STARTED(pid2);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum != 1))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
s = (arg.sum == 1 ? NATS_OK: NATS_ERR);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Check CID same: ");
s = natsConnection_GetClientID(nc1, &newcid);
testCond((s == NATS_OK) && (newcid == cid));
test("Connect to server 2: ");
s = natsConnection_ConnectTo(&nc2, "nats://127.0.0.1:4223");
testCond(s == NATS_OK);
test("Stop server 1: ");
_stopServer(pid1);
testCond(s == NATS_OK);
test("Wait for nc1 to reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 4000);
s = (arg.reconnected ? NATS_OK : NATS_ERR);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Check CID is different: ");
s = natsConnection_GetClientID(nc1, &newcid);
testCond((s == NATS_OK) && (newcid != cid));
// Stop clients and remaining server
natsConnection_Destroy(nc1);
natsConnection_Destroy(nc2);
natsOptions_Destroy(opts);
_stopServer(pid2);
// Now have dummy server that returns no CID and check we get expected error.
nc1 = NULL;
arg.status = NATS_ERR;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
s = arg.status;
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("CID not supported: ");
s = natsConnection_ConnectTo(&nc1, NATS_DEFAULT_URL);
IFOK(s, natsConnection_GetClientID(nc1, &cid));
testCond((s == NATS_NO_SERVER_SUPPORT) && (cid == 0));
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc1);
natsThread_Join(t);
natsThread_Destroy(t);
_destroyDefaultThreadArgs(&arg);
}
static void
test_GetClientIP(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid serverPid = NATS_INVALID_PID;
char *ip = NULL;
natsThread *t = NULL;
struct threadArg arg;
test("Check server version: ");
if (!serverVersionAtLeast(2,1,6))
{
char txt[200];
snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 2.1.6, got %s: ", serverVersion);
test(txt);
testCond(true);
return;
}
testCond(true);
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
testCond(s == NATS_OK);
test("Get client IP - no conn: ");
s = natsConnection_GetClientIP(NULL, &ip);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Get client IP - no ip loc: ");
s = natsConnection_GetClientIP(nc, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Get client IP: ");
s = natsConnection_GetClientIP(nc, &ip);
testCond((s == NATS_OK) && (strcmp(ip, "127.0.0.1")==0));
free(ip);
ip = NULL;
natsConnection_Close(nc);
test("Get client IP after conn closed: ");
s = natsConnection_GetClientIP(nc, &ip);
testCond((s == NATS_CONNECTION_CLOSED) && (ip == NULL));
nats_clearLastError();
natsConnection_Destroy(nc);
nc = NULL;
_stopServer(serverPid);
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.status = NATS_ERR;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Get client IP with old server: ");
s = natsConnection_GetClientIP(nc, &ip);
testCond ((s == NATS_NO_SERVER_SUPPORT) && (ip == NULL));
nats_clearLastError();
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Close(nc);
natsConnection_Destroy(nc);
natsThread_Join(t);
natsThread_Destroy(t);
_destroyDefaultThreadArgs(&arg);
}
static void
test_GetRTT(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
int64_t rtt = 0;
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetReconnectWait(opts, 10));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
test("Get RTT - no conn: ");
s = natsConnection_GetRTT(NULL, &rtt);
testCond(s == NATS_INVALID_ARG);
test("Get RTT - no rtt loc: ");
s = natsConnection_GetRTT(nc, NULL);
testCond(s == NATS_INVALID_ARG);
test("Get RTT: ");
s = natsConnection_GetRTT(nc, &rtt);
// Check that it is below 500ms...
testCond((s == NATS_OK) && (rtt/1000000 <= 500));
_stopServer(serverPid);
test("Get RTT while not connected: ");
s = natsConnection_GetRTT(nc, &rtt);
testCond(s == NATS_CONNECTION_DISCONNECTED);
natsConnection_Close(nc);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
}
static void
test_GetLocalIPAndPort(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
char *ip = NULL;
int port = 0;
struct threadArg arg;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, &arg));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
test("Get Local IP and Port - no conn: ");
s = natsConnection_GetLocalIPAndPort(NULL, &ip, &port);
testCond(s == NATS_INVALID_ARG);
test("Get Local IP and Port - no ip loc: ");
s = natsConnection_GetLocalIPAndPort(nc, NULL, &port);
testCond(s == NATS_INVALID_ARG);
test("Get Local IP and Port - no port loc: ");
s = natsConnection_GetLocalIPAndPort(nc, &ip, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Get Local IP and Port: ");
s = natsConnection_GetLocalIPAndPort(nc, &ip, &port);
testCond((s == NATS_OK)
&& ((ip != NULL) && (strcmp(ip, "127.0.0.1") == 0))
&& (port != 0));
free(ip);
test("Wait for disconnect: ");
s = NATS_OK;
_stopServer(pid);
natsMutex_Lock(arg.m);
while ((s == NATS_OK) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Get Local IP and Port while disconnected: ");
s = natsConnection_GetLocalIPAndPort(nc, &ip, &port);
testCond(s == NATS_CONNECTION_DISCONNECTED);
nats_clearLastError();
// Close connection
natsConnection_Close(nc);
test("Get Local IP and Port with closed connection: ");
s = natsConnection_GetLocalIPAndPort(nc, &ip, &port);
testCond(s == NATS_CONNECTION_CLOSED);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
}
static natsStatus
_userJWTCB(char **userJWT, char **customErrTxt, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
if (closure != NULL)
{
bool done = true;
natsMutex_Lock(arg->m);
if (arg->string != NULL)
*customErrTxt = strdup(arg->string);
else if (arg->nc != NULL)
natsConnection_Destroy(arg->nc);
else
done = false;
natsMutex_Unlock(arg->m);
if (done)
{
if (*customErrTxt != NULL)
return NATS_ERR;
return NATS_OK;
}
}
*userJWT = strdup("some user jwt");
return NATS_OK;
}
static natsStatus
_sigCB(char **customErrTxt, unsigned char **psig, int *sigLen, const char* nonce, void *closure)
{
const unsigned char correctSign[] = {
155, 157, 8, 183, 93, 154, 78, 7,
219, 39, 11, 16, 134, 231, 46, 142,
168, 87, 110, 202, 187, 180, 179, 62,
49, 255, 225, 74, 48, 80, 176, 111,
248, 162, 121, 188, 203, 101, 100, 195,
162, 70, 213, 182, 220, 14, 71, 113,
93, 239, 141, 131, 66, 190, 237, 127,
104, 191, 138, 217, 227, 1, 92, 14,
};
unsigned char *sig = NULL;
if (closure != NULL)
{
struct threadArg *arg = (struct threadArg*) closure;
bool done = true;
natsMutex_Lock(arg->m);
if (arg->string != NULL)
*customErrTxt = strdup(arg->string);
else if (arg->nc != NULL)
natsConnection_Destroy(arg->nc);
else
done = false;
natsMutex_Unlock(arg->m);
if (done)
{
if (*customErrTxt != NULL)
return NATS_ERR;
return NATS_OK;
}
}
sig = malloc(NATS_CRYPTO_SIGN_BYTES);
memcpy(sig, correctSign, NATS_CRYPTO_SIGN_BYTES);
*psig = sig;
if (sigLen != NULL)
*sigLen = NATS_CRYPTO_SIGN_BYTES;
return NATS_OK;
}
static natsStatus
_checkJWTAndSigCB(char *buffer)
{
// UserJWT callback should have returned this...
if (strstr(buffer, "some user jwt") == NULL)
return NATS_ERR;
// The server is sending the nonce "nonce" and we
// use a seed that should have produced a signature
// that converted to base64 should be:
if (strstr(buffer, "m50It12aTgfbJwsQhucujqhXbsq7tLM-Mf_hSjBQsG_4onm8y2Vkw6JG1bbcDkdxXe-Ng0K-7X9ov4rZ4wFcDg") == NULL)
return NATS_ERR;
return NATS_OK;
}
static void
test_UserCredsCallbacks(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsOptions *opts2 = NULL;
natsPid pid = NATS_INVALID_PID;
natsThread *t = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
if (s != NATS_OK)
FAIL("Unable to create options for test UserCredsCallbacks");
test("Invalid arg 1: ");
s = natsOptions_SetUserCredentialsCallbacks(NULL, _dummyUserJWTCb, NULL, _dummySigCb, NULL);
testCond(s == NATS_INVALID_ARG);
test("Invalid arg 2: ");
s = natsOptions_SetUserCredentialsCallbacks(opts, NULL, NULL, _dummySigCb, NULL);
testCond(s == NATS_INVALID_ARG);
test("Clone: ");
s = natsOptions_SetUserCredentialsCallbacks(opts, _dummyUserJWTCb, (void*) 1, _dummySigCb, (void*) 2);
if (s == NATS_OK)
{
opts2 = natsOptions_clone(opts);
if (opts2 == NULL)
s = NATS_NO_MEMORY;
}
IFOK(s, natsOptions_SetUserCredentialsCallbacks(opts, NULL, NULL, NULL, NULL));
testCond((s == NATS_OK)
&& (opts2->userJWTHandler == _dummyUserJWTCb)
&& (opts2->userJWTClosure == (void*) 1)
&& (opts2->sigHandler == _dummySigCb)
&& (opts2->sigClosure == (void*) 2));
natsOptions_Destroy(opts2);
// We first check that we get error when callbacks do return error.
// For that part, we don't need that the server sends the nonce.
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
// Create a connection. The user JWT callback is going to return
// an error, ensure connection fails.
test("UserJWTCB returns error: ");
natsMutex_Lock(arg.m);
arg.string = "some jwt error";
arg.nc = NULL;
natsMutex_Unlock(arg.m);
s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, (void*) &arg, _sigCB, NULL);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "some jwt error") != NULL));
s = NATS_OK;
nats_clearLastError();
test("SignatureCB returns error: ");
natsMutex_Lock(arg.m);
arg.string = "some sig error";
arg.nc = NULL;
natsMutex_Unlock(arg.m);
s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, NULL, _sigCB, (void*) &arg);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "some sig error") != NULL));
s = NATS_OK;
nats_clearLastError();
test("UserJWTCB destroys connection: ");
natsMutex_Lock(arg.m);
arg.string = NULL;
arg.nc = NULL;
arg.closed = false;
natsMutex_Unlock(arg.m);
s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, (void*) &arg, _sigCB, NULL);
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
arg.nc = nc;
natsMutex_Unlock(arg.m);
_stopServer(pid);
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
}
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (!arg.closed))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
nc = NULL;
s = NATS_OK;
nats_clearLastError();
test("SigCB destroys connection: ");
natsMutex_Lock(arg.m);
arg.string = NULL;
arg.nc = NULL;
arg.closed = false;
natsMutex_Unlock(arg.m);
s = natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, NULL, _sigCB, (void*) &arg);
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
IFOK(s, natsConnection_Connect(&nc, opts));
if (s == NATS_OK)
{
natsMutex_Lock(arg.m);
arg.nc = nc;
natsMutex_Unlock(arg.m);
_stopServer(pid);
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
}
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (!arg.closed))
s = natsCondition_TimedWait(arg.c, arg.m, 5000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
nc = NULL;
_stopServer(pid);
// Start fake server that will send predefined "nonce" so we can check
// that connection is sending appropriate jwt and signature.
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.status = NATS_ERR;
arg.checkInfoCB = _checkJWTAndSigCB;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("Connect sends proper JWT and Signature: ");
natsOptions_Destroy(opts);
opts = NULL;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetUserCredentialsCallbacks(opts, _userJWTCB, NULL, _sigCB, NULL));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
natsThread_Join(t);
natsThread_Destroy(t);
_destroyDefaultThreadArgs(&arg);
}
static void
test_UserCredsFromMemory(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsOptions *opts2 = NULL;
natsPid pid = NATS_INVALID_PID;
natsThread *t = NULL;
struct threadArg arg;
const char *jwtAndSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n";
const char *jwtWithoutSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n";
s = natsOptions_Create(&opts);
if (s != NATS_OK)
FAIL("Unable to create options for test UserCredsFromFiles");
test("Clone: ");
s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed);
if (s == NATS_OK)
{
opts2 = natsOptions_clone(opts);
if (opts2 == NULL)
s = NATS_NO_MEMORY;
}
IFOK(s, natsOptions_SetUserCredentialsFromMemory(opts, NULL));
testCond((s == NATS_OK)
&& (opts2->userCreds != NULL)
&& (opts2->userJWTHandler == natsConn_userCreds)
&& (opts2->userJWTClosure == (void*) opts2->userCreds)
&& (opts2->sigHandler == natsConn_signatureHandler)
&& (opts2->sigClosure == (void*) opts2->userCreds));
natsOptions_Destroy(opts2);
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("invalidCreds provided: ");
s = natsOptions_SetUserCredentialsFromMemory(opts, "invalidCreds");
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_NOT_FOUND);
// Use a file that contains no seed
test("jwtAndSeed string has no seed: ");
s = natsOptions_SetUserCredentialsFromMemory(opts, jwtWithoutSeed);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_NOT_FOUND);
nc = NULL;
_stopServer(pid);
// Start fake server that will send predefined "nonce" so we can check
// that connection is sending appropriate jwt and signature.
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.done = false;
arg.status = NATS_ERR;
arg.checkInfoCB = _checkJWTAndSigCB;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
s = NATS_OK;
test("Connect with jwtAndSeed string: ");
s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc);
nc = NULL;
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
_destroyDefaultThreadArgs(&arg);
natsOptions_Destroy(opts);
}
static void
test_UserCredsFromFiles(void)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsOptions *opts2 = NULL;
natsPid pid = NATS_INVALID_PID;
natsThread *t = NULL;
int i;
struct threadArg arg;
const char *ucfn = "user.creds";
const char *sfn = "seed.txt";
const char *snhfn = "seednh.txt";
const char *nusfn = "nouors.txt";
FILE *f = NULL;
f = fopen(ucfn, "w");
if (f == NULL)
s = NATS_ERR;
else
{
int res = fputs("-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n", f);
if (res < 0)
s = NATS_ERR;
if (fclose(f) != 0)
s = NATS_ERR;
f = NULL;
}
if (s == NATS_OK)
{
f = fopen(sfn, "w");
if (f == NULL)
s = NATS_ERR;
else
{
int res = fputs("-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n", f);
if (res < 0)
s = NATS_ERR;
if (fclose(f) != 0)
s = NATS_ERR;
f = NULL;
}
}
if (s == NATS_OK)
{
f = fopen(snhfn, "w");
if (f == NULL)
s = NATS_ERR;
else
{
int res = fputs("This file does not have the proper header\nand also has spaces before the seed:\n \tSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\nthis should work\n", f);
if (res < 0)
s = NATS_ERR;
if (fclose(f) != 0)
s = NATS_ERR;
f = NULL;
}
}
if (s == NATS_OK)
{
f = fopen(nusfn, "w");
if (f == NULL)
s = NATS_ERR;
else
{
int res = fputs("This file does not have a jwt\nnor a valid seed\n", f);
if (res < 0)
s = NATS_ERR;
if (fclose(f) != 0)
s = NATS_ERR;
f = NULL;
}
}
if (s != NATS_OK)
FAIL("Unable to create creds test files");
s = natsOptions_Create(&opts);
if (s != NATS_OK)
FAIL("Unable to create options for test UserCredsFromFiles");
test("Invalid arg 1: ");
s = natsOptions_SetUserCredentialsFromFiles(NULL, "foo", "bar");
testCond(s == NATS_INVALID_ARG);
test("Invalid arg 2: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, NULL, "bar");
testCond(s == NATS_INVALID_ARG);
test("Invalid arg 3: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "", "bar");
testCond(s == NATS_INVALID_ARG);
test("Clone: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar");
if (s == NATS_OK)
{
opts2 = natsOptions_clone(opts);
if (opts2 == NULL)
s = NATS_NO_MEMORY;
}
IFOK(s, natsOptions_SetUserCredentialsFromFiles(opts, NULL, NULL));
testCond((s == NATS_OK)
&& (opts2->userCreds != NULL)
&& (opts2->userJWTHandler == natsConn_userCreds)
&& (opts2->userJWTClosure == (void*) opts2->userCreds)
&& (opts2->sigHandler == natsConn_signatureHandler)
&& (opts2->sigClosure == (void*) opts2->userCreds));
natsOptions_Destroy(opts2);
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("UserOrChainedFile not found: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "userCredsNotFound", NULL);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "error opening file 'userCredsNotFound'") != NULL));
// Use a file that contains no userJWT..
test("UserOrChainedFile has no JWT: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "list.txt", NULL);
IFOK(s, natsConnection_Connect(&nc, opts));
// Since we return the whole content of the file when we don't find
// the key for the user, but we don't for seed, the error we'll get
// is about no user seed found.
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "no nkey user seed found") != NULL));
test("SeedFile not found: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, "seedFileNotFound");
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "error opening file 'seedFileNotFound'") != NULL));
// Use a seed file that contains no seed..
test("SeedFile has no seed: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, "list.txt");
IFOK(s, natsConnection_Connect(&nc, opts));
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "no nkey user seed found") != NULL));
_stopServer(pid);
for (i=0; i<3; i++)
{
// Start fake server that will send predefined "nonce" so we can check
// that connection is sending appropriate jwt and signature.
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.done = false;
arg.status = NATS_ERR;
arg.checkInfoCB = _checkJWTAndSigCB;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
s = NATS_OK;
if (i == 0)
{
test("Connect with chained file: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, NULL);
}
else if (i == 1)
{
test("Connect with user and seed files: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, sfn);
}
else if (i == 2)
{
test("Connect with user and seed files (seed does not contain header): ");
s = natsOptions_SetUserCredentialsFromFiles(opts, ucfn, snhfn);
}
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc);
nc = NULL;
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
_destroyDefaultThreadArgs(&arg);
}
natsOptions_Destroy(opts);
remove(ucfn);
remove(sfn);
remove(snhfn);
remove(nusfn);
}
static natsStatus
_checkNKeyAndSig(char *buffer)
{
// NKey should have been included
if (strstr(buffer, "pubKey") == NULL)
return NATS_ERR;
// The server is sending the nonce "nonce" and we
// use a seed that should have produced a signature
// that converted to base64 should be:
if (strstr(buffer, "m50It12aTgfbJwsQhucujqhXbsq7tLM-Mf_hSjBQsG_4onm8y2Vkw6JG1bbcDkdxXe-Ng0K-7X9ov4rZ4wFcDg") == NULL)
return NATS_ERR;
return NATS_OK;
}
static void
test_NKey(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsOptions *opts2 = NULL;
natsConnection *nc = NULL;
natsThread *t = NULL;
struct threadArg arg;
s = natsOptions_Create(&opts);
if (s != NATS_OK)
FAIL("Failed to setup test");
test("Invalid arg 1: ");
s = natsOptions_SetNKey(NULL, "pubkey", _dummySigCb, (void*) 1);
testCond(s == NATS_INVALID_ARG);
test("Invalid arg 2: ");
s = natsOptions_SetNKey(opts, "pubkey", NULL, NULL);
testCond(s == NATS_INVALID_ARG);
test("Clone: ");
s = natsOptions_SetNKey(opts, "pubkey", _dummySigCb, (void*) 1);
if (s == NATS_OK)
{
opts2 = natsOptions_clone(opts);
if (opts2 == NULL)
s = NATS_NO_MEMORY;
}
IFOK(s, natsOptions_SetNKey(opts, NULL, NULL, NULL));
testCond((s == NATS_OK)
&& (opts2->nkey != NULL)
&& (strcmp(opts2->nkey, "pubkey") == 0)
&& (opts2->sigHandler == _dummySigCb)
&& (opts2->sigClosure == (void*) 1));
natsOptions_Destroy(opts2);
test("NKey erase JWT: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar");
IFOK(s, natsOptions_SetNKey(opts, "pubkey2", _dummySigCb, (void*) 2));
testCond((s == NATS_OK)
&& (opts->nkey != NULL)
&& (strcmp(opts->nkey, "pubkey2") == 0)
&& (opts->userJWTHandler == NULL)
&& (opts->userJWTClosure == NULL)
&& (opts->sigHandler == _dummySigCb)
&& (opts->sigClosure == (void*) 2));
test("UserCreds erase NKey: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar");
testCond((s == NATS_OK)
&& (opts->nkey == NULL)
&& (opts->userJWTHandler == natsConn_userCreds)
&& (opts->userJWTClosure == (void*) opts->userCreds)
&& (opts->sigHandler == natsConn_signatureHandler)
&& (opts->sigClosure == (void*) opts->userCreds));
// Start fake server that will send predefined "nonce" so we can check
// that connection is sending appropriate jwt and signature.
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.done = false;
arg.status = NATS_ERR;
arg.checkInfoCB = _checkNKeyAndSig;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("NKey works ok: ");
s = natsOptions_SetNKey(opts, "pubKey", _sigCB, NULL);
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc);
nc = NULL;
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
_destroyDefaultThreadArgs(&arg);
natsOptions_Destroy(opts);
}
static natsStatus
_checkNKeyFromSeed(char *buffer)
{
// NKey should have been included
if (strstr(buffer, "UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4") == NULL)
return NATS_ERR;
// The server is sending the nonce "nonce" and we
// use a seed that should have produced a signature
// that converted to base64 should be:
if (strstr(buffer, "AVfpO7Pw3rc56hoO1OJcFxXUCfBmO2qouchBchSlL45Fuur9zS15UzytEI1QC5wwVG7uiHIdqyfmOS6uPrwqCg") == NULL)
return NATS_ERR;
return NATS_OK;
}
static void
test_NKeyFromSeed(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsOptions *opts2 = NULL;
natsConnection *nc = NULL;
natsThread *t = NULL;
FILE *f = NULL;
struct threadArg arg;
s = natsOptions_Create(&opts);
if (s != NATS_OK)
FAIL("Failed to setup test");
test("Invalid arg 1: ");
s = natsOptions_SetNKeyFromSeed(NULL, "pubkey", "seed.file");
testCond(s == NATS_INVALID_ARG);
test("Invalid arg 2: ");
s = natsOptions_SetNKeyFromSeed(opts, "pubkey", NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Clone: ");
s = natsOptions_SetNKeyFromSeed(opts, "pubkey", "seed.file");
if (s == NATS_OK)
{
opts2 = natsOptions_clone(opts);
if (opts2 == NULL)
s = NATS_NO_MEMORY;
}
IFOK(s, natsOptions_SetNKeyFromSeed(opts, NULL, NULL));
testCond((s == NATS_OK)
&& (opts2->nkey != NULL)
&& (strcmp(opts2->nkey, "pubkey") == 0)
&& (opts2->sigHandler == natsConn_signatureHandler)
&& (opts2->sigClosure == (void*) opts2->userCreds)
&& (opts2->userCreds != NULL)
&& (opts2->userCreds->seedFile != NULL)
&& (strcmp(opts2->userCreds->seedFile, "seed.file") == 0));
natsOptions_Destroy(opts2);
test("NKeyFromSeed erase JWT: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar");
IFOK(s, natsOptions_SetNKeyFromSeed(opts, "pubkey2", "seed.file"));
testCond((s == NATS_OK)
&& (opts->nkey != NULL)
&& (strcmp(opts->nkey, "pubkey2") == 0)
&& (opts->userJWTHandler == NULL)
&& (opts->userJWTClosure == NULL)
&& (opts->sigHandler == natsConn_signatureHandler)
&& (opts->sigClosure == (void*) opts->userCreds)
&& (opts->userCreds != NULL)
&& (opts->userCreds->seedFile != NULL)
&& (strcmp(opts->userCreds->seedFile, "seed.file") == 0));
test("UserCreds erase NKeyFromSeed: ");
s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", NULL);
testCond((s == NATS_OK)
&& (opts->nkey == NULL)
&& (opts->userJWTHandler == natsConn_userCreds)
&& (opts->userJWTClosure == (void*) opts->userCreds)
&& (opts->sigHandler == natsConn_signatureHandler)
&& (opts->sigClosure == (void*) opts->userCreds)
&& (opts->userCreds != NULL)
&& (opts->userCreds->seedFile == NULL));
// Start fake server that will send predefined "nonce" so we can check
// that connection is sending appropriate jwt and signature.
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.done = false;
arg.status = NATS_ERR;
arg.checkInfoCB = _checkNKeyFromSeed;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("NKeyFromSeed works ok: ");
f = fopen("seed.file", "w");
if (f == NULL)
s = NATS_ERR;
else
{
fputs("SUACSSL3UAHUDXKFSNVUZRF5UHPMWZ6BFDTJ7M6USDXIEDNPPQYYYCU3VY\n", f);
fclose(f);
f = NULL;
}
IFOK(s, natsOptions_SetNKeyFromSeed(opts, "UDXU4RCSJNZOIQHZNWXHXORDPRTGNJAHAHFRGZNEEJCPQTT2M7NLCNF4", "seed.file"));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc);
nc = NULL;
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
_destroyDefaultThreadArgs(&arg);
natsOptions_Destroy(opts);
remove("seed.file");
}
static void
test_ConnSign(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
const char *ucfn = "user.creds";
FILE *f = NULL;
unsigned char sig[64];
const unsigned char expected[] = {
155, 157, 8, 183, 93, 154, 78, 7,
219, 39, 11, 16, 134, 231, 46, 142,
168, 87, 110, 202, 187, 180, 179, 62,
49, 255, 225, 74, 48, 80, 176, 111,
248, 162, 121, 188, 203, 101, 100, 195,
162, 70, 213, 182, 220, 14, 71, 113,
93, 239, 141, 131, 66, 190, 237, 127,
104, 191, 138, 217, 227, 1, 92, 14,
};
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect ok: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
testCond(s == NATS_OK);
test("Can't sign without user creds: ");
s = natsConnection_Sign(nc, (const unsigned char*) "payload", 7, sig);
testCond((s == NATS_ERR)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), "unable to sign") != NULL));
natsConnection_Destroy(nc);
nc = NULL;
s = NATS_OK;
test("Set proper option: ");
f = fopen(ucfn, "w");
if (f == NULL)
s = NATS_ERR;
else
{
int res = fputs("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n", f);
if (res < 0)
s = NATS_ERR;
if (fclose(f) != 0)
s = NATS_ERR;
f = NULL;
}
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetUserCredentialsFromFiles(opts, ucfn, ucfn));
testCond(s == NATS_OK);
test("Connect ok: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Sign with NULL message: ");
s = natsConnection_Sign(nc, NULL, 0, sig);
testCond(s == NATS_OK);
test("Sign message: ");
s = natsConnection_Sign(nc, (const unsigned char*) "nonce", 5, sig);
testCond((s == NATS_OK)
&& (sig != NULL)
&& (memcmp((void*) sig, expected, sizeof(expected)) == 0));
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(pid);
remove(ucfn);
}
static void
test_WriteDeadline(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsThread *t = NULL;
char data[1024] = {0};
struct threadArg arg;
test("Create options: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetAllowReconnect(opts, false));
testCond(s == NATS_OK);
test("Set invalid write deadline: ");
s = natsOptions_SetWriteDeadline(opts, -1);
testCond(s == NATS_INVALID_ARG);
test("Start mock server: ");
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
arg.status = NATS_ERR;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
test("Write deadline kicks publish out: ");
s = natsOptions_SetIOBufSize(opts, 100);
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, &arg));
IFOK(s, natsOptions_SetWriteDeadline(opts, 1));
IFOK(s, natsConnection_Connect(&nc, opts));
while (s == NATS_OK)
s = natsConnection_Publish(nc, "foo", data, sizeof(data));
testCond(s == NATS_TIMEOUT);
test("Caused a disconnect: ");
// Since we are not allowing for reconnect, we should
// get the closed CB.
natsMutex_Lock(arg.m);
s = NATS_OK;
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
natsThread_Join(t);
natsThread_Destroy(t);
_destroyDefaultThreadArgs(&arg);
}
static void
_publish(void *arg)
{
natsConnection *nc = (natsConnection*) arg;
char data[1024] = {0};
int i;
natsStatus s = NATS_OK;
for (i=0; ((s == NATS_OK) && (i<1000)); i++)
s = natsConnection_Publish(nc, "foo", data, sizeof(data));
}
static void
test_NoPartialOnReconnect(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsThread *t = NULL;
natsThread *t2 = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
const char *servers[2] = {"nats://127.0.0.1:4222", "nats://127.0.0.1:4223"};
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("unable to setup test");
test("Create options: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetAllowReconnect(opts, true));
IFOK(s, natsOptions_SetReconnectWait(opts, 10));
IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0));
IFOK(s, natsOptions_SetMaxReconnect(opts, 10000));
IFOK(s, natsOptions_SetServers(opts, servers, 2));
IFOK(s, natsOptions_SetNoRandomize(opts, true));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &arg));
testCond(s == NATS_OK);
test("Start real backup server: ");
pid = _startServer("nats://127.0.0.1:4223", "-p 4223", true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Start mock server: ");
if (s == NATS_OK)
{
arg.status = NATS_ERR;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
testCond(s == NATS_OK);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK)
test("Start Publish: ");
s = natsThread_Create(&t2, _publish, (void*) nc);
testCond(s == NATS_OK);
nats_Sleep(1000);
test("Kill server: ");
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Wait for reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !(arg.reconnected))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
if (t2 != NULL)
{
natsThread_Join(t2);
natsThread_Destroy(t2);
}
test("Check no proto error: ");
{
const char *le = NULL;
s = natsConnection_GetLastError(nc, &le);
}
testCond(s == NATS_OK);
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
_stopServerInThread(void *closure)
{
natsPid pid = *((natsPid*) closure);
nats_Sleep(150);
_stopServer(pid);
}
static void
test_ReconnectFailsPendingRequest(void)
{
natsStatus s;
natsOptions *opts = NULL;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsThread *t = NULL;
natsPid pid = NATS_INVALID_PID;
bool failr = false;
int iter;
for (iter=1; iter<=2; iter++)
{
failr = (iter == 2 ? true : false);
test("Create options: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetFailRequestsOnDisconnect(opts, failr));
testCond(s == NATS_OK);
test("Start server: ");
pid = _startServer("nats://127.0.0.1:4222", "-p 4222", true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Create service provider: ");
s = natsConnection_SubscribeSync(&sub, nc, "requests");
testCond(s == NATS_OK);
test("Start thread that will stop server: ");
s = natsThread_Create(&t, _stopServerInThread, (void*) &pid);
testCond(s == NATS_OK);
test((failr ? "Fails due to disconnect: " : "Fails due to timeout: "));
s = natsConnection_RequestString(&msg, nc, "requests", "help", 300);
testCond(s == (failr ? NATS_CONNECTION_DISCONNECTED : NATS_TIMEOUT));
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
natsConnection_Destroy(nc);
nc = NULL;
natsOptions_Destroy(opts);
opts = NULL;
}
}
static void
test_HeadersNotSupported(void)
{
natsStatus s;
natsConnection *conn = NULL;
natsMsg *msg = NULL;
natsMsg *reply= NULL;
natsThread *t = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
// Set this to error, the mock server should set it to OK
// if it can start successfully.
arg.status = NATS_ERR;
arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576}\r\n";
s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg);
}
if (s == NATS_OK)
{
// Wait for server to be ready
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
}
if (s != NATS_OK)
{
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
_destroyDefaultThreadArgs(&arg);
FAIL("Unable to setup test");
}
test("Headers not supported with old server: ");
s = natsConnection_ConnectTo(&conn, NATS_DEFAULT_URL);
IFOK(s, natsConnection_HasHeaderSupport(conn));
testCond(s == NATS_NO_SERVER_SUPPORT);
test("Create msg with heades: ");
s = natsMsg_Create(&msg, "foo", NULL, "body", 4);
IFOK(s, natsMsgHeader_Set(msg, "Header", "Hello Headers!"));
testCond(s == NATS_OK);
test("Publish fails: ");
s = natsConnection_PublishMsg(conn, msg);
testCond(s == NATS_NO_SERVER_SUPPORT);
test("Request fails: ");
s = natsConnection_RequestMsg(&reply, conn, msg, 1000);
testCond((s == NATS_NO_SERVER_SUPPORT) && (reply == NULL));
natsConnection_Destroy(conn);
// Notify mock server we are done
natsMutex_Lock(arg.m);
arg.done = true;
natsCondition_Signal(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(t);
natsThread_Destroy(t);
natsMsg_Destroy(msg);
natsMsg_Destroy(reply);
_destroyDefaultThreadArgs(&arg);
}
static void
test_HeadersBasic(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid pid = NATS_INVALID_PID;
natsMsg *msg = NULL;
natsMsg *rmsg = NULL;
natsSubscription *sub = NULL;
const char *val = NULL;
if (!serverVersionAtLeast(2, 2, 0))
{
char txt[200];
snprintf(txt, sizeof(txt), "Skipping since requires server version of at least 2.2.0, got %s: ", serverVersion);
test(txt);
testCond(true);
return;
}
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect ok: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
testCond(s == NATS_OK);
test("Headers supported: ");
s = natsConnection_HasHeaderSupport(nc);
testCond(s == NATS_OK);
test("Create sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "foo");
testCond(s == NATS_OK);
test("Create msg with headers: ");
s = natsMsg_Create(&msg, "foo", NULL, "body", 4);
IFOK(s, natsMsgHeader_Set(msg, "Headers", "Hello Headers!"))
testCond(s == NATS_OK);
test("Publish with headers ok: ");
s = natsConnection_PublishMsg(nc, msg);
testCond(s == NATS_OK);
test("Receive msg: ")
s = natsSubscription_NextMsg(&rmsg, sub, 1000);
testCond((s == NATS_OK) && (rmsg != NULL));
test("Resend msg without lift: ");
s = natsConnection_PublishMsg(nc, rmsg);
testCond(s == NATS_OK);
natsMsg_Destroy(rmsg);
rmsg = NULL;
test("Receive msg: ")
s = natsSubscription_NextMsg(&rmsg, sub, 1000);
testCond((s == NATS_OK) && (rmsg != NULL));
test("Check headers: ");
s = natsMsgHeader_Get(rmsg, "Headers", &val);
testCond((s == NATS_OK)
&& (val != NULL) && (strcmp(val, "Hello Headers!") == 0)
&& (natsMsg_GetDataLength(rmsg) == 4)
&& (strncmp(natsMsg_GetData(msg), "body", 4) == 0));
natsMsg_Destroy(rmsg);
rmsg = NULL;
test("Value with CRLFs replaced with spaces: ");
s = natsMsgHeader_Set(msg, "Headers", "value1\r\nvalue2\r\nvalue3");
IFOK(s, natsConnection_PublishMsg(nc, msg));
IFOK(s, natsSubscription_NextMsg(&rmsg, sub, 1000));
IFOK(s, natsMsgHeader_Get(rmsg, "Headers", &val));
testCond((s == NATS_OK)
&& (val != NULL) && (strcmp(val, "value1 value2 value3") == 0)
&& (natsMsg_GetDataLength(rmsg) == 4)
&& (strncmp(natsMsg_GetData(msg), "body", 4) == 0));
natsMsg_Destroy(msg);
natsMsg_Destroy(rmsg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(pid);
}
static void
_msgFilterNoOp(natsConnection *nc, natsMsg **msg, void *closure) { }
static void
_msgFilterAlterMsg(natsConnection *nc, natsMsg **msg, void *closure)
{
natsStatus s;
natsMsg *nm = NULL;
s = natsMsg_Create(&nm, natsMsg_GetSubject(*msg), NULL, "replaced", 8);
if (s != NATS_OK)
nats_PrintLastErrorStack(stderr);
natsMsg_Destroy(*msg);
*msg = nm;
}
static void
_msgFilterDropMsg(natsConnection *nc, natsMsg **msg, void *closure)
{
natsMsg_Destroy(*msg);
*msg = NULL;
natsConn_setFilter(nc, NULL);
}
static void
test_natsMsgsFilter(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsPid pid = NATS_INVALID_PID;
natsMsg *msg = NULL;
natsSubscription *sub = NULL;
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect ok: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
testCond(s == NATS_OK);
test("Create sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "foo");
testCond(s == NATS_OK);
test("Add no-op filter: ");
natsConn_setFilter(nc, _msgFilterNoOp);
s = natsConnection_PublishString(nc, "foo", "original");
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond((s == NATS_OK) && (msg != NULL)
&& (strcmp(natsMsg_GetData(msg), "original") == 0));
natsMsg_Destroy(msg);
msg = NULL;
test("Add alter-msg filter: ");
natsConn_setFilter(nc, _msgFilterAlterMsg);
s = natsConnection_PublishString(nc, "foo", "original");
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond((s == NATS_OK) && (msg != NULL)
&& (strcmp(natsMsg_GetData(msg), "replaced") == 0));
natsMsg_Destroy(msg);
msg = NULL;
test("Add drop-msg filter: ");
natsConn_setFilter(nc, _msgFilterDropMsg);
s = natsConnection_PublishString(nc, "foo", "will be dropped");
IFOK(s, natsSubscription_NextMsg(&msg, sub, 100));
testCond((s == NATS_TIMEOUT) && (msg == NULL));
nats_clearLastError();
test("Filter is removed from previous filter: ");
s = natsConnection_PublishString(nc, "foo", "got it");
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
testCond((s == NATS_OK) && (msg != NULL)
&& (strcmp(natsMsg_GetData(msg), "got it") == 0));
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
_stopServer(pid);
}
static natsStatus
_evLoopAttach(void **userData, void *loop, natsConnection *nc, natsSock socket)
{
struct threadArg *arg = (struct threadArg *) loop;
natsMutex_Lock(arg->m);
*userData = arg;
arg->nc = nc;
arg->sock = socket;
arg->attached++;
arg->doRead = true;
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
return NATS_OK;
}
static natsStatus
_evLoopRead(void *userData, bool add)
{
struct threadArg *arg = (struct threadArg *) userData;
natsMutex_Lock(arg->m);
arg->doRead = add;
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
return NATS_OK;
}
static natsStatus
_evLoopWrite(void *userData, bool add)
{
struct threadArg *arg = (struct threadArg *) userData;
natsMutex_Lock(arg->m);
arg->doWrite = add;
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
return NATS_OK;
}
static natsStatus
_evLoopDetach(void *userData)
{
struct threadArg *arg = (struct threadArg *) userData;
natsMutex_Lock(arg->m);
arg->detached++;
natsCondition_Broadcast(arg->c);
natsMutex_Unlock(arg->m);
return NATS_OK;
}
static void
_eventLoop(void *closure)
{
struct threadArg *arg = (struct threadArg *) closure;
natsSock sock = NATS_SOCK_INVALID;
natsConnection *nc = NULL;
bool read = false;
bool write= false;
bool stop = false;
while (!stop)
{
nats_Sleep(100);
natsMutex_Lock(arg->m);
while (!arg->evStop && ((sock = arg->sock) == NATS_SOCK_INVALID))
natsCondition_Wait(arg->c, arg->m);
stop = arg->evStop;
nc = arg->nc;
read = arg->doRead;
write = arg->doWrite;
natsMutex_Unlock(arg->m);
if (!stop)
{
if (read)
natsConnection_ProcessReadEvent(nc);
if (write)
natsConnection_ProcessWriteEvent(nc);
}
}
}
static void
test_EventLoop(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
test("Set options: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetMaxReconnect(opts, 100));
IFOK(s, natsOptions_SetReconnectWait(opts, 50));
IFOK(s, natsOptions_SetEventLoop(opts, (void*) &arg,
_evLoopAttach,
_evLoopRead,
_evLoopWrite,
_evLoopDetach));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
testCond(s == NATS_OK);
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Start event loop: ");
natsMutex_Lock(arg.m);
arg.sock = NATS_SOCK_INVALID;
natsMutex_Unlock(arg.m);
s = natsThread_Create(&arg.t, _eventLoop, (void*) &arg);
testCond(s == NATS_OK);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK)
test("Create sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "foo");
testCond(s == NATS_OK);
test("Stop server and wait for disconnect: ");
_stopServer(pid);
pid = NATS_INVALID_PID;
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Restart server: ");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
testCond(s == NATS_OK);
test("Wait for reconnect: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Publish: ");
s = natsConnection_PublishString(nc, "foo", "bar");
testCond(s == NATS_OK);
test("Check msg received: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
test("Close and wait for close cb: ");
natsConnection_Close(nc);
_waitForConnClosed(&arg);
testCond(s == NATS_OK);
natsMutex_Lock(arg.m);
arg.evStop = true;
natsCondition_Broadcast(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(arg.t);
natsThread_Destroy(arg.t);
test("Check ev loop: ");
natsMutex_Lock(arg.m);
if (arg.attached != 2 || !arg.detached)
s = NATS_ERR;
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
test_EventLoopRetryOnFailedConnect(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
test("Set options: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetMaxReconnect(opts, 100));
IFOK(s, natsOptions_SetReconnectWait(opts, 50));
IFOK(s, natsOptions_SetRetryOnFailedConnect(opts,
true,
_connectedCb,
(void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
IFOK(s, natsOptions_SetEventLoop(opts, (void*) &arg,
_evLoopAttach,
_evLoopRead,
_evLoopWrite,
_evLoopDetach));
testCond(s == NATS_OK);
test("Start event loop: ");
natsMutex_Lock(arg.m);
arg.sock = NATS_SOCK_INVALID;
natsMutex_Unlock(arg.m);
s = natsThread_Create(&arg.t, _eventLoop, (void*) &arg);
testCond(s == NATS_OK);
test("Start connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_NOT_YET_CONNECTED);
if (s == NATS_NOT_YET_CONNECTED)
s = NATS_OK;
test("Create sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "foo");
testCond(s == NATS_OK);
test("Start server: ");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
testCond(s == NATS_OK);
test("Check connected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.connected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Publish: ");
s = natsConnection_PublishString(nc, "foo", "bar");
testCond(s == NATS_OK);
test("Check msg received: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
test("Close and wait for close cb: ");
natsConnection_Close(nc);
s = _waitForConnClosed(&arg);
testCond(s == NATS_OK);
natsMutex_Lock(arg.m);
arg.evStop = true;
natsCondition_Broadcast(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(arg.t);
natsThread_Destroy(arg.t);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
}
static void
test_EventLoopTLS(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg arg;
test("Set options: ");
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetURL(opts, "nats://localhost:4443"));
IFOK(s, natsOptions_SkipServerVerification(opts, true));
IFOK(s, natsOptions_SetSecure(opts, true));
IFOK(s, natsOptions_SetMaxReconnect(opts, 100));
IFOK(s, natsOptions_SetReconnectWait(opts, 50));
IFOK(s, natsOptions_SetDisconnectedCB(opts, _disconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &arg));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg));
IFOK(s, natsOptions_SetEventLoop(opts, (void*) &arg,
_evLoopAttach,
_evLoopRead,
_evLoopWrite,
_evLoopDetach));
testCond(s == NATS_OK);
test("Start server: ");
pid = _startServer("nats://127.0.0.1:4443", "-config tls.conf -DV", true);
CHECK_SERVER_STARTED(pid);
testCond(s == NATS_OK);
test("Start event loop: ");
natsMutex_Lock(arg.m);
arg.sock = NATS_SOCK_INVALID;
natsMutex_Unlock(arg.m);
s = natsThread_Create(&arg.t, _eventLoop, (void*) &arg);
testCond(s == NATS_OK);
test("Connect: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Disconnect: ");
_stopServer(pid);
pid = NATS_INVALID_PID;
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.disconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Restart server: ");
pid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(pid);
testCond(s == NATS_OK);
test("Check reconnected: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.reconnected)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Shutdown evLoop: ");
natsMutex_Lock(arg.m);
arg.evStop = true;
natsCondition_Broadcast(arg.c);
natsMutex_Unlock(arg.m);
natsThread_Join(arg.t);
natsThread_Destroy(arg.t);
testCond(s == NATS_OK);
test("Close and wait for close cb: ");
natsConnection_Close(nc);
s = _waitForConnClosed(&arg);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&arg);
_stopServer(pid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLBasic(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Check that connect switches to TLS automatically: ");
s = natsOptions_SetURL(opts, "nats://localhost:4443");
// For this test skip server verification
IFOK(s, natsOptions_SkipServerVerification(opts, true));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
nc = NULL;
test("Check connects OK with SSL options: ");
s = natsOptions_SetSecure(opts, true);
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args));
// For test purposes, we provide the CA trusted certs
IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem"));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
test("Check reconnects OK: ");
_stopServer(serverPid);
nats_Sleep(100);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !(args.reconnected))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLVerify(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to create reconnect options!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Check that connect fails if no SSL certs: ");
s = natsOptions_SetURL(opts, "nats://localhost:4443");
IFOK(s, natsOptions_SetSecure(opts, true));
// For test purposes, we provide the CA trusted certs
IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem"));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s != NATS_OK);
test("Check that connect succeeds with proper certs: ");
s = natsOptions_LoadCertificatesChain(opts,
"certs/client-cert.pem",
"certs/client-key.pem");
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Check reconnects OK: ");
_stopServer(serverPid);
nats_Sleep(100);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !(args.reconnected))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLLoadCAFromMemory(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsBuffer *certBuf = NULL;
struct threadArg args;
s = nats_ReadFile(&certBuf, 10000, "certs/ca.pem");
IFOK(s, _createDefaultThreadArgsForCbTests(&args));
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to create reconnect options!");
test("Check NULL certs: ");
s = natsOptions_SetCATrustedCertificates(opts, NULL);
testCond(s == NATS_INVALID_ARG);
test("Check empty certs: ");
s = natsOptions_SetCATrustedCertificates(opts, "");
testCond(s == NATS_INVALID_ARG);
test("Check invalid cert: ");
s = natsOptions_SetCATrustedCertificates(opts, "invalid");
testCond(s == NATS_SSL_ERROR);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Check that connect succeeds with proper certs: ");
s = natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args);
IFOK(s, natsOptions_SetURL(opts, "nats://localhost:4443"));
IFOK(s, natsOptions_SetSecure(opts, true));
// For test purposes, we provide the CA trusted certs
if (s == NATS_OK)
{
s = natsOptions_SetCATrustedCertificates(opts, (const char*) natsBuf_Data(certBuf));
// Demonstrate that we can free the memory after this call..
natsBuf_Destroy(certBuf);
}
IFOK(s, natsOptions_LoadCertificatesChain(opts,
"certs/client-cert.pem",
"certs/client-key.pem"));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Check reconnects OK: ");
_stopServer(serverPid);
nats_Sleep(100);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !(args.reconnected))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLCertAndKeyFromMemory(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsBuffer *certBuf = NULL;
natsBuffer *keyBuf = NULL;
struct threadArg args;
s = nats_ReadFile(&certBuf, 10000, "certs/client-cert.pem");
IFOK(s, nats_ReadFile(&keyBuf, 10000, "certs/client-key.pem"));
IFOK(s, _createDefaultThreadArgsForCbTests(&args));
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to create reconnect options!");
test("Check NULL cert: ");
s = natsOptions_SetCertificatesChain(opts, NULL, (const char*) natsBuf_Data(keyBuf));
testCond(s == NATS_INVALID_ARG);
test("Check empty cert: ");
s = natsOptions_SetCertificatesChain(opts, "", (const char*) natsBuf_Data(keyBuf));
testCond(s == NATS_INVALID_ARG);
test("Check NULL key: ");
s = natsOptions_SetCertificatesChain(opts, (const char*) natsBuf_Data(certBuf), NULL);
testCond(s == NATS_INVALID_ARG);
test("Check empty key: ");
s = natsOptions_SetCertificatesChain(opts, (const char*) natsBuf_Data(certBuf), "");
testCond(s == NATS_INVALID_ARG);
test("Check invalid cert: ");
s = natsOptions_SetCertificatesChain(opts, "invalid", (const char*) natsBuf_Data(keyBuf));
testCond(s == NATS_SSL_ERROR);
test("Check invalid key: ");
s = natsOptions_SetCertificatesChain(opts, (const char*) natsBuf_Data(certBuf), "invalid");
testCond(s == NATS_SSL_ERROR);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Check that connect succeeds with proper certs: ");
s = natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args);
IFOK(s, natsOptions_SetURL(opts, "nats://localhost:4443"));
IFOK(s, natsOptions_SetSecure(opts, true));
// For test purposes, we provide the CA trusted certs
IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem"));
IFOK(s, natsOptions_SetCertificatesChain(opts,
(const char*) natsBuf_Data(certBuf),
(const char*) natsBuf_Data(keyBuf)));
// Demonstrate that we can free the memory after this call..
natsBuf_Destroy(certBuf);
natsBuf_Destroy(keyBuf);
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Check reconnects OK: ");
_stopServer(serverPid);
nats_Sleep(100);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsverify.conf", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !(args.reconnected))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLVerifyHostname(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to create reconnect options!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls_noip.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Check that connect fails if url is IP: ");
s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443");
IFOK(s, natsOptions_SetSecure(opts, true));
// For test purposes, we provide the CA trusted certs
IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem"));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_SSL_ERROR);
test("Check that connect fails if wrong expected hostname: ");
s = natsOptions_SetURL(opts, "nats://localhost:4443");
IFOK(s, natsOptions_SetExpectedHostname(opts, "foo"));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_SSL_ERROR);
test("Check that connect succeeds if hostname ok and no expected hostname set: ");
s = natsOptions_SetURL(opts, "nats://localhost:4443");
IFOK(s, natsOptions_SetExpectedHostname(opts, NULL));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
nc = NULL;
test("Check that connect succeeds with proper expected hostname: ");
s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443");
IFOK(s, natsOptions_SetExpectedHostname(opts, "localhost"));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
test("Check reconnects OK: ");
_stopServer(serverPid);
nats_Sleep(100);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !(args.reconnected))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLSkipServerVerification(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to create reconnect options!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Check that connect fails due to server verification: ");
s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443");
IFOK(s, natsOptions_SetSecure(opts, true));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_SSL_ERROR);
test("Check that connect succeeds with server verification disabled: ");
s = natsOptions_SkipServerVerification(opts, true);
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
test("Check reconnects OK: ");
_stopServer(serverPid);
nats_Sleep(100);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !(args.reconnected))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLCiphers(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
opts = _createReconnectOptions();
if (opts == NULL)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("SetCipherSuites requires OpenSSL 1.1: ");
s = natsOptions_SetCipherSuites(opts, "TLS_AES_128_GCM_SHA256");
#if defined(NATS_USE_OPENSSL_1_1)
testCond(s == NATS_OK);
#else
testCond(s == NATS_ERR);
#endif
test("Check that connect fails if improper ciphers: ");
s = natsOptions_SetURL(opts, "nats://localhost:4443");
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args));
IFOK(s, natsOptions_SetSecure(opts, true));
// For test purposes, we provide the CA trusted certs
IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem"));
IFOK(s, natsOptions_SetCiphers(opts, "-ALL:RSA"));
#if defined(NATS_USE_OPENSSL_1_1)
IFOK(s, natsOptions_SetCipherSuites(opts, ""));
#endif
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s != NATS_OK);
test("Check connects OK with proper ciphers: ");
s = natsOptions_SetCiphers(opts, "-ALL:HIGH");
#if defined(NATS_USE_OPENSSL_1_1)
IFOK(s, natsOptions_SetCipherSuites(opts, "TLS_AES_128_GCM_SHA256"));
#endif
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
test("Check reconnects OK: ");
_stopServer(serverPid);
nats_Sleep(100);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !(args.reconnected))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_PublishString(nc, "foo", "test"));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK)
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
#if defined(NATS_HAS_TLS)
static void
_sslMT(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
char subj[64];
int i;
const char *msgPayload = "this is a test payload";
int count = 50;
natsMutex_Lock(args->m);
snprintf(subj, sizeof(subj), "foo.%d", ++(args->sum));
while (!(args->current) && (s == NATS_OK))
s = natsCondition_TimedWait(args->c, args->m, 2000);
natsMutex_Unlock(args->m);
if (valgrind)
count = 10;
for (i=0; (s == NATS_OK) && (i < count); i++)
{
s = natsConnection_Connect(&nc, args->opts);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, subj));
IFOK(s, natsConnection_PublishString(nc, subj, msgPayload));
IFOK(s, natsSubscription_NextMsg(&msg, sub, 2000));
if (s == NATS_OK)
{
if (strcmp(natsMsg_GetData(msg), msgPayload) != 0)
s = NATS_ERR;
}
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
natsConnection_Destroy(nc);
nc = NULL;
}
if (s != NATS_OK)
{
natsMutex_Lock(args->m);
if (args->status == NATS_OK)
args->status = s;
natsMutex_Unlock(args->m);
}
}
#define SSL_THREADS (3)
#endif
static void
test_SSLMultithreads(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
natsThread *t[SSL_THREADS];
int i;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
s = natsOptions_Create(&opts);
if (opts == NULL)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Create options: ");
s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443");
IFOK(s, natsOptions_SetSecure(opts, true));
// For test purposes, we provide the CA trusted certs
IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem"));
IFOK(s, natsOptions_SetExpectedHostname(opts, "localhost"));
testCond(s == NATS_OK);
args.opts = opts;
for (i=0; (s == NATS_OK) && (i<SSL_THREADS); i++)
s = natsThread_Create(&t[i], _sslMT, &args);
test("Create connections from multiple threads using same ssl ctx: ");
natsMutex_Lock(args.m);
args.current = true;
natsCondition_Broadcast(args.c);
natsMutex_Unlock(args.m);
for (i=0; i<SSL_THREADS; i++)
{
if (t[i] == NULL)
continue;
natsThread_Join(t[i]);
natsThread_Destroy(t[i]);
}
natsMutex_Lock(args.m);
IFOK(s, args.status);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsOptions_Destroy(opts);
if (valgrind)
nats_Sleep(900);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLConnectVerboseOption(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s == NATS_OK)
{
opts = _createReconnectOptions();
if (opts == NULL)
s = NATS_ERR;
}
IFOK(s, natsOptions_SetVerbose(opts, true));
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, &args));
if (s != NATS_OK)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
s = natsOptions_SetURL(opts, "nats://127.0.0.1:4443");
IFOK(s, natsOptions_SetSecure(opts, true));
// For test purposes, we provide the CA trusted certs
IFOK(s, natsOptions_LoadCATrustedCertificates(opts, "certs/ca.pem"));
IFOK(s, natsOptions_SetExpectedHostname(opts, "localhost"));
test("Check that SSL connect OK when Verbose set: ");
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
_stopServer(serverPid);
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
test("Check that SSL reconnect OK when Verbose set: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.reconnected)
s = natsCondition_TimedWait(args.c, args.m, 5000);
natsMutex_Unlock(args.m);
IFOK(s, natsConnection_Flush(nc));
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
if (valgrind)
nats_Sleep(900);
_destroyDefaultThreadArgs(&args);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
#if defined(NATS_HAS_TLS)
static natsStatus
_elDummyAttach(void **userData, void *loop, natsConnection *nc, natsSock socket) { return NATS_OK; }
static natsStatus
_elDummyRead(void *userData, bool add) { return NATS_OK; }
static natsStatus
_elDummyWrite(void *userData, bool add) { return NATS_OK; }
static natsStatus
_elDummyDetach(void *userData) { return NATS_OK; }
#endif
static void
test_SSLSocketLeakWithEventLoop(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
s = natsOptions_Create(&opts);
if (s == NATS_OK)
{
// Not really using a real event loop, just setting the option.
s = natsOptions_SetEventLoop(opts, (void*) 1,
_elDummyAttach,
_elDummyRead,
_elDummyWrite,
_elDummyDetach);
}
IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4443"));
IFOK(s, natsOptions_SetSecure(opts, true));
if (s != NATS_OK)
FAIL("Unable to setup test!");
serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true);
CHECK_SERVER_STARTED(serverPid);
// The options have not been set properly for a successful TLS connection.
test("Check that SSL fails: ");
s = natsConnection_Connect(&nc, opts);
testCond(s != NATS_OK);
// Valgrind will tell us if we have leaked socket.
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_stopServer(serverPid);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
test_SSLReconnectWithAuthError(void)
{
#if defined(NATS_HAS_TLS)
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsPid pid1 = NATS_INVALID_PID;
natsPid pid2 = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
IFOK(s, natsOptions_Create(&opts));
IFOK(s, natsOptions_SetSecure(opts, true));
IFOK(s, natsOptions_SkipServerVerification(opts, true));
IFOK(s, natsOptions_SetTimeout(opts, 250));
IFOK(s, natsOptions_SetMaxReconnect(opts, 1000));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &args));
IFOK(s, natsOptions_SetServers(opts, (const char*[2]){"tls://user:pwd@127.0.0.1:4443", "tls://bad:pwd@127.0.0.1:4444"}, 2));
if (opts == NULL)
FAIL("Unable to create reconnect options!");
pid1 = _startServer("nats://127.0.0.1:4443", "-p 4443 -cluster_name abc -cluster nats://127.0.0.1:6222 -tls -tlscert certs/server-cert.pem -tlskey certs/server-key.pem -tlscacert certs/ca.pem -user user -pass pwd", true);
CHECK_SERVER_STARTED(pid1);
pid2 = _startServer("nats://127.0.0.1:4444", "-p 4444 -cluster_name abc -cluster nats://127.0.0.1:6223 -routes nats://127.0.0.1:6222 -tls -tlscert certs/server-cert.pem -tlskey certs/server-key.pem -tlscacert certs/ca.pem -user user -pass pwd", true);
CHECK_SERVER_STARTED(pid2);
test("Connect to server1: ");
s = natsConnection_Connect(&nc, opts);
testCond(s == NATS_OK);
test("Stop server1: ");
_stopServer(pid1);
testCond(true);
test("Check that client stops after auth errors: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.closed)
s = natsCondition_TimedWait(args.c, args.m, 5000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsConnection_Destroy(nc);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(pid2);
#else
test("Skipped when built with no SSL support: ");
testCond(true);
#endif
}
static void
rmtree(const char *path)
{
#ifdef _WIN32
WIN32_FIND_DATA ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
char *dir = NULL;
if (nats_asprintf(&dir, "%s\\*", path) < 0)
abort();
hFind = FindFirstFile(dir, &ffd);
if (hFind == INVALID_HANDLE_VALUE)
{
free(dir);
return;
}
do
{
char *fullPath = NULL;
if (!strcmp(ffd.cFileName, ".") || !strcmp(ffd.cFileName, ".."))
continue;
if (nats_asprintf(&fullPath, "%s\\%s", path, ffd.cFileName) < 0)
abort();
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
rmtree(fullPath);
else
DeleteFile(fullPath);
free(fullPath);
}
while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
RemoveDirectory(path);
free(dir);
#else
DIR *dir;
struct stat statPath, statEntry;
struct dirent *entry;
memset(&statPath, 0, sizeof(struct stat));
stat(path, &statPath);
if (S_ISDIR(statPath.st_mode) == 0)
return;
if ((dir = opendir(path)) == NULL)
return;
while ((entry = readdir(dir)) != NULL)
{
char *fullPath = NULL;
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
if (nats_asprintf(&fullPath, "%s/%s", path, entry->d_name) < 0)
abort();
memset(&statEntry, 0, sizeof(struct stat));
stat(fullPath, &statEntry);
if (S_ISDIR(statEntry.st_mode) != 0)
rmtree(fullPath);
else
unlink(fullPath);
free(fullPath);
}
closedir(dir);
rmdir(path);
#endif
}
static void
_makeUniqueDir(char *buf, int bufLen, const char *path)
{
int n;
if ((int) strlen(path) + 1 + NUID_BUFFER_LEN + 1 > bufLen)
abort();
n = snprintf(buf, bufLen, "%s", path);
natsNUID_Next(buf+n, NUID_BUFFER_LEN+1);
buf[n+NUID_BUFFER_LEN+1] = '\0';
}
static void
_createConfFile(char *buf, int bufLen, const char *content)
{
FILE *f = NULL;
if (5 + NUID_BUFFER_LEN + 1 > bufLen)
abort();
memcpy(buf, "conf_", 5);
natsNUID_Next(buf+5, NUID_BUFFER_LEN+1);
buf[5+NUID_BUFFER_LEN+1] = '\0';
f = fopen(buf, "w");
if (f == NULL)
abort();
fputs(content, f);
fclose(f);
}
static void
test_ReconnectImplicitUserInfo(void)
{
natsStatus s;
natsPid pid1 = NATS_INVALID_PID;
natsPid pid2 = NATS_INVALID_PID;
natsOptions *o1 = NULL;
natsConnection *nc1 = NULL;
natsSubscription *sub = NULL;
natsConnection *nc2 = NULL;
natsMsg *msg = NULL;
char conf[256];
char cmdLine[1024];
struct threadArg args;
_createConfFile(conf, sizeof(conf),
"accounts { "\
" A { "\
" users: [{user: a, password: pwd}] "\
" }\n"\
" B { "\
" users: [{user: b, password: pwd}] "\
" }\n"\
"}\n"\
"no_auth_user: b\n");
test("Start server1: ");
snprintf(cmdLine, sizeof(cmdLine), "-cluster_name \"local\" -cluster nats://127.0.0.1:6222 -c %s", conf);
pid1 = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid1);
testCond(true);
test("Connect1: ");
s = _createDefaultThreadArgsForCbTests(&args);
IFOK(s, natsOptions_Create(&o1));
IFOK(s, natsOptions_SetDiscoveredServersCB(o1, _discoveredServersCb, (void*) &args));
IFOK(s, natsOptions_SetURL(o1, "nats://a:pwd@127.0.0.1:4222"));
IFOK(s, natsOptions_SetReconnectWait(o1, 100));
IFOK(s, natsOptions_SetReconnectedCB(o1, _reconnectedCb, (void*) &args));
IFOK(s, natsConnection_Connect(&nc1, o1));
testCond(s == NATS_OK);
test("Start server2: ");
snprintf(cmdLine, sizeof(cmdLine), "-p 4223 -cluster_name \"local\" -cluster nats://127.0.0.1:6223 -routes nats://127.0.0.1:6222 -c %s", conf);
pid2 = _startServer("nats://127.0.0.1:4223", cmdLine, true);
CHECK_SERVER_STARTED(pid2);
testCond(true);
test("Check s2 discovered: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum == 0))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Stop srv1: ");
_stopServer(pid1);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.reconnected)
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Create sub: ");
s = natsConnection_SubscribeSync(&sub, nc1, "foo");
testCond(s == NATS_OK);
test("Flush: ");
s = natsConnection_Flush(nc1);
testCond(s == NATS_OK);
test("Connect 2: ");
s = natsConnection_ConnectTo(&nc2, "nats://a:pwd@127.0.0.1:4223");
testCond(s == NATS_OK);
test("Publish: ");
s = natsConnection_PublishString(nc2, "foo", "msg");
testCond(s == NATS_OK);
test("Check received: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc1);
natsOptions_Destroy(o1);
natsConnection_Destroy(nc2);
_stopServer(pid2);
_destroyDefaultThreadArgs(&args);
remove(conf);
}
static void
test_JetStreamUnmarshalAccountInfo(void)
{
natsStatus s;
nats_JSON *json = NULL;
jsAccountInfo *ai = NULL;
jsTier *r = NULL;
const char *bad[] = {
"{\"memory\":\"abc\"}",
"{\"storage\":\"abc\"}",
"{\"streams\":\"abc\"}",
"{\"consumers\":\"abc\"}",
"{\"domain\":123}",
"{\"api\":123}",
"{\"api\":{\"total\":\"abc\"}}",
"{\"api\":{\"errors\":\"abc\"}}",
"{\"limits\":123}",
"{\"limits\":{\"max_memory\":\"abc\"}}",
"{\"limits\":{\"max_storage\":\"abc\"}}",
"{\"limits\":{\"max_streams\":\"abc\"}}",
"{\"limits\":{\"max_consumers\":\"abc\"}}",
"{\"limits\":{\"max_ack_pending\":\"abc\"}}",
"{\"limits\":{\"memory_max_stream_bytes\":\"abc\"}}",
"{\"limits\":{\"storage_max_stream_bytes\":\"abc\"}}",
"{\"limits\":{\"max_bytes_required\":\"abc\"}}",
"{\"tier\":123}",
"{\"tier\":{1, 2}}",
"{\"tier\":{\"R1\":123}}",
"{\"tier\":{\"R1\":{\"memory\":\"abc\"}}}",
};
char tmp[2048];
int i;
for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++)
{
test("Bad fields: ");
s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i]));
IFOK(s, js_unmarshalAccountInfo(json, &ai));
testCond((s != NATS_OK) && (ai == NULL));
nats_JSONDestroy(json);
json = NULL;
nats_clearLastError();
}
test("Account info complete: ");
snprintf(tmp, sizeof(tmp), "{\"memory\":1000,\"storage\":2000,\"streams\":5,\"consumers\":7,"\
"\"domain\":\"MyDomain\","\
"\"api\":{\"total\":8,\"errors\":2},"\
"\"limits\":{\"max_memory\":3000,\"max_storage\":4000,\"max_streams\":10,\"max_consumers\":20,"\
"\"max_ack_pending\":100,\"memory_max_stream_bytes\":1000000,\"storage_max_stream_bytes\":2000000,\"max_bytes_required\":true},"\
"\"tier\":{\"R1\":{\"memory\":1000,\"storage\":2000,\"streams\":5,\"consumers\":7,"\
"\"limits\":{\"max_memory\":3000,\"max_storage\":4000,\"max_streams\":10,\"max_consumers\":20,"\
"\"max_ack_pending\":100,\"memory_max_stream_bytes\":1000000,\"storage_max_stream_bytes\":2000000,\"max_bytes_required\":true}},"\
"\"R2\":{\"memory\":2000,\"storage\":3000,\"streams\":8,\"consumers\":9,"\
"\"limits\":{\"max_memory\":4000,\"max_storage\":5000,\"max_streams\":20,\"max_consumers\":30,"\
"\"max_ack_pending\":200,\"memory_max_stream_bytes\":2000000,\"storage_max_stream_bytes\":3000000}}}}");
s = nats_JSONParse(&json, tmp, (int) strlen(tmp));
IFOK(s, js_unmarshalAccountInfo(json, &ai));
testCond((s == NATS_OK) && (ai != NULL)
&& (ai->Memory == 1000)
&& (ai->Store == 2000)
&& (ai->Streams == 5)
&& (ai->Consumers == 7)
&& (ai->Domain != NULL)
&& (strcmp(ai->Domain, "MyDomain") == 0)
&& (ai->API.Total == 8)
&& (ai->API.Errors == 2)
&& (ai->Limits.MaxMemory == 3000)
&& (ai->Limits.MaxStore == 4000)
&& (ai->Limits.MaxStreams == 10)
&& (ai->Limits.MaxConsumers == 20)
&& (ai->Limits.MaxAckPending == 100)
&& (ai->Limits.MemoryMaxStreamBytes == 1000000)
&& (ai->Limits.StoreMaxStreamBytes == 2000000)
&& (ai->Limits.MaxBytesRequired)
&& (ai->Tiers != NULL)
&& (ai->TiersLen == 2));
test("Check tier R1: ");
if (strcmp(ai->Tiers[0]->Name, "R1") == 0)
r = ai->Tiers[0];
else if (strcmp(ai->Tiers[1]->Name, "R1") == 0)
r = ai->Tiers[1];
else
s = NATS_ERR;
testCond((s == NATS_OK)
&& (r->Memory == 1000)
&& (r->Store == 2000)
&& (r->Streams == 5)
&& (r->Consumers == 7)
&& (r->Limits.MaxMemory == 3000)
&& (r->Limits.MaxStore == 4000)
&& (r->Limits.MaxStreams == 10)
&& (r->Limits.MaxConsumers == 20)
&& (r->Limits.MaxAckPending == 100)
&& (r->Limits.MemoryMaxStreamBytes == 1000000)
&& (r->Limits.StoreMaxStreamBytes == 2000000)
&& r->Limits.MaxBytesRequired);
test("Check tier R2: ");
if (strcmp(ai->Tiers[0]->Name, "R2") == 0)
r = ai->Tiers[0];
else if (strcmp(ai->Tiers[1]->Name, "R2") == 0)
r = ai->Tiers[1];
else
s = NATS_ERR;
testCond((s == NATS_OK)
&& (r->Memory == 2000)
&& (r->Store == 3000)
&& (r->Streams == 8)
&& (r->Consumers == 9)
&& (r->Limits.MaxMemory == 4000)
&& (r->Limits.MaxStore == 5000)
&& (r->Limits.MaxStreams == 20)
&& (r->Limits.MaxConsumers == 30)
&& (r->Limits.MaxAckPending == 200)
&& (r->Limits.MemoryMaxStreamBytes == 2000000)
&& (r->Limits.StoreMaxStreamBytes == 3000000)
&& (!r->Limits.MaxBytesRequired));
nats_JSONDestroy(json);
jsAccountInfo_Destroy(ai);
}
static void
test_JetStreamUnmarshalStreamState(void)
{
natsStatus s;
nats_JSON *json = NULL;
jsStreamState state;
const char *bad[] = {
"{\"state\":{\"messages\":\"abc\"}}",
"{\"state\":{\"bytes\":\"abc\"}}",
"{\"state\":{\"first_seq\":\"abc\"}}",
"{\"state\":{\"first_ts\":123}}",
"{\"state\":{\"last_seq\":\"abc\"}}",
"{\"state\":{\"last_ts\":123}}",
"{\"state\":{\"num_deleted\":\"abc\"}}",
"{\"state\":{\"deleted\":\"abc\"}}",
"{\"state\":{\"lost\":\"abc\"}}",
"{\"state\":{\"lost\":{\"msgs\":\"abc\"}}}",
"{\"state\":{\"lost\":{\"bytes\":\"abc\"}}}",
"{\"state\":{\"consumer_count\":\"abc\"}}",
"{\"state\":{\"num_subjects\":\"abc\"}}",
};
int i;
for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++)
{
test("Bad fields: ");
memset(&state, 0, sizeof(jsStreamState));
s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i]));
IFOK(s, js_unmarshalStreamState(json, "state", &state));
testCond(s != NATS_OK);
nats_JSONDestroy(json);
json = NULL;
nats_clearLastError();
}
test("Unmarshal: ");
memset(&state, 0, sizeof(jsStreamState));
s = nats_JSONParse(&json, "{\"state\":{\"messages\":1,\"bytes\":2,"\
"\"first_seq\":3,\"first_ts\":\"2021-06-23T18:22:00.123Z\","\
"\"last_seq\":4,\"last_ts\":\"2021-06-23T18:22:00.123456789Z\","\
"\"num_deleted\":5,\"deleted\":[6,7,8,9,10],"\
"\"lost\":{\"msgs\":[11,12,13],\"bytes\":14},"\
"\"consumer_count\":15,\"num_subjects\":3}}", -1);
IFOK(s, js_unmarshalStreamState(json, "state", &state));
testCond((s == NATS_OK) && (json != NULL)
&& (state.Msgs == 1)
&& (state.Bytes == 2)
&& (state.FirstSeq == 3)
&& (state.FirstTime == 1624472520123000000)
&& (state.LastSeq == 4)
&& (state.LastTime == 1624472520123456789)
&& (state.NumDeleted == 5)
&& (state.Deleted != NULL)
&& (state.DeletedLen == 5)
&& (state.Deleted[0] == 6)
&& (state.Deleted[1] == 7)
&& (state.Deleted[2] == 8)
&& (state.Deleted[3] == 9)
&& (state.Deleted[4] == 10)
&& (state.Lost != NULL)
&& (state.Lost->MsgsLen == 3)
&& (state.Lost->Msgs != NULL)
&& (state.Lost->Msgs[0] == 11)
&& (state.Lost->Msgs[1] == 12)
&& (state.Lost->Msgs[2] == 13)
&& (state.Lost->Bytes == 14)
&& (state.Consumers == 15)
&& (state.NumSubjects == 3));
test("Cleanup: ");
js_cleanStreamState(&state);
testCond(true);
// Check that this is fine
js_cleanStreamState(NULL);
nats_JSONDestroy(json);
}
static void
test_JetStreamUnmarshalStreamConfig(void)
{
natsStatus s;
nats_JSON *json = NULL;
jsStreamConfig *sc = NULL;
const char *missing[] = {
"{\"name\":\"TEST\"}",
"{\"name\":\"TEST\",\"retention\":\"limits\"}",
"{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5}",
"{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10}",
"{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000}",
"{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_age\":20000000}",
"{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_age\":20000000,\"discard\":\"new\"}",
"{\"name\":\"TEST\",\"retention\":\"limits\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_age\":20000000,\"discard\":\"new\",\"storage\":\"memory\"}",
};
char tmp[2048];
int i;
for (i=0; i<(int)(sizeof(missing)/sizeof(char*)); i++)
{
test("Missing fields: ");
s = nats_JSONParse(&json, missing[i], (int) strlen(missing[i]));
IFOK(s, js_unmarshalStreamConfig(json, NULL, &sc));
testCond((s == NATS_OK) && (sc != NULL));
js_destroyStreamConfig(sc);
sc = NULL;
nats_JSONDestroy(json);
json = NULL;
}
test("Stream config with all required: ");
snprintf(tmp, sizeof(tmp), "{\"name\":\"TEST\",\"retention\":\"workqueue\","\
"\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,\"max_msgs_per_subject\":1,\"max_age\":20000000,"\
"\"discard\":\"new\",\"storage\":\"memory\",\"num_replicas\":3}");
s = nats_JSONParse(&json, tmp, (int) strlen(tmp));
IFOK(s, js_unmarshalStreamConfig(json, NULL, &sc));
testCond((s == NATS_OK) && (sc != NULL)
&& (strcmp(sc->Name, "TEST") == 0)
&& (sc->Retention == js_WorkQueuePolicy)
&& (sc->MaxConsumers == 5)
&& (sc->MaxMsgs == 10)
&& (sc->MaxBytes == 1000)
&& (sc->MaxAge == 20000000)
&& (sc->MaxMsgsPerSubject == 1)
&& (sc->Discard == js_DiscardNew)
&& (sc->Storage == js_MemoryStorage)
&& (sc->Replicas == 3));
js_destroyStreamConfig(sc);
sc = NULL;
nats_JSONDestroy(json);
json = NULL;
test("Stream config with all: ");
if (snprintf(tmp, sizeof(tmp), "{\"name\":\"TEST\",\"description\":\"this is my stream\",\"subjects\":[\"foo\",\"bar\"],"\
"\"retention\":\"workqueue\",\"max_consumers\":5,\"max_msgs\":10,\"max_bytes\":1000,"\
"\"max_age\":20000000,\"max_msg_size\":1024,\"max_msgs_per_subject\":1,\"discard\":\"new\",\"storage\":\"memory\","\
"\"num_replicas\":3,\"no_ack\":true,\"template_owner\":\"owner\","\
"\"duplicate_window\":100000000000,\"placement\":{\"cluster\":\"cluster\",\"tags\":[\"tag1\",\"tag2\"]},"\
"\"mirror\":{\"name\":\"TEST2\",\"opt_start_seq\":10,\"filter_subject\":\"foo\",\"external\":{\"api\":\"my_prefix\",\"deliver\":\"deliver_prefix\"}},"\
"\"sources\":[{\"name\":\"TEST3\",\"opt_start_seq\":20,\"filter_subject\":\"bar\",\"external\":{\"api\":\"my_prefix2\",\"deliver\":\"deliver_prefix2\"}}],"\
"\"sealed\":true,\"deny_delete\":true,\"deny_purge\":true,\"allow_rollup_hdrs\":true,\"republish\":{\"src\":\"foo\",\"dest\":\"bar\"},"\
"\"allow_direct\":true,\"mirror_direct\":true}") >= (int) sizeof(tmp))
{
abort();
}
s = nats_JSONParse(&json, tmp, (int) strlen(tmp));
IFOK(s, js_unmarshalStreamConfig(json, NULL, &sc));
testCond((s == NATS_OK) && (sc != NULL)
&& (sc->Name != NULL)
&& (strcmp(sc->Name, "TEST") == 0)
&& (sc->Description != NULL)
&& (strcmp(sc->Description, "this is my stream") == 0)
&& (sc->Subjects != NULL)
&& (sc->SubjectsLen == 2)
&& (strcmp(sc->Subjects[0], "foo") == 0)
&& (strcmp(sc->Subjects[1], "bar") == 0)
&& (sc->MaxMsgsPerSubject == 1)
&& (sc->MaxMsgSize == 1024)
&& (sc->NoAck)
&& (strcmp(sc->Template, "owner") == 0)
&& (sc->Duplicates == 100000000000)
&& (sc->Placement != NULL)
&& (strcmp(sc->Placement->Cluster, "cluster") == 0)
&& (sc->Placement->TagsLen == 2)
&& (strcmp(sc->Placement->Tags[0], "tag1") == 0)
&& (strcmp(sc->Placement->Tags[1], "tag2") == 0)
&& (sc->Mirror != NULL)
&& (strcmp(sc->Mirror->Name, "TEST2") == 0)
&& (sc->Mirror->OptStartSeq == 10)
&& (strcmp(sc->Mirror->FilterSubject, "foo") == 0)
&& (sc->Mirror->External != NULL)
&& (strcmp(sc->Mirror->External->APIPrefix, "my_prefix") == 0)
&& (strcmp(sc->Mirror->External->DeliverPrefix, "deliver_prefix") == 0)
&& (sc->SourcesLen == 1)
&& (strcmp(sc->Sources[0]->Name, "TEST3") == 0)
&& (sc->Sources[0]->OptStartSeq == 20)
&& (strcmp(sc->Sources[0]->FilterSubject, "bar") == 0)
&& (sc->Sources[0]->External != NULL)
&& (strcmp(sc->Sources[0]->External->APIPrefix, "my_prefix2") == 0)
&& (strcmp(sc->Sources[0]->External->DeliverPrefix, "deliver_prefix2") == 0)
&& sc->Sealed && sc->DenyDelete && sc->DenyPurge && sc->AllowRollup
&& ((sc->RePublish != NULL)
&& (sc->RePublish->Source != NULL)
&& (strcmp(sc->RePublish->Source, "foo") == 0)
&& (sc->RePublish->Destination != NULL)
&& (strcmp(sc->RePublish->Destination, "bar") == 0))
&& sc->AllowDirect && sc->MirrorDirect);
js_destroyStreamConfig(sc);
sc = NULL;
nats_JSONDestroy(json);
json = NULL;
}
static void
test_JetStreamUnmarshalStreamInfo(void)
{
natsStatus s;
nats_JSON *json = NULL;
jsStreamInfo *si = NULL;
const char *good[] = {
"{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\"}}",
"{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\",\"replicas\":[{\"name\":\"S1\",\"current\":true,\"offline\":false,\"active\":123,\"lag\":456},{\"name\":\"S1\",\"current\":false,\"offline\":true,\"active\":123,\"lag\":456}]}}",
"{\"mirror\":{\"name\":\"M\",\"lag\":123,\"active\":456}}",
"{\"mirror\":{\"name\":\"M\",\"external\":{\"api\":\"MyApi\",\"deliver\":\"deliver.prefix\"},\"lag\":123,\"active\":456}}",
"{\"sources\":[{\"name\":\"S1\",\"lag\":123,\"active\":456}]}",
"{\"sources\":[{\"name\":\"S1\",\"lag\":123,\"active\":456},{\"name\":\"S2\",\"lag\":123,\"active\":456}]}",
"{\"sources\":[{\"name\":\"S1\",\"external\":{\"api\":\"MyApi\",\"deliver\":\"deliver.prefix\"},\"lag\":123,\"active\":456},{\"name\":\"S2\",\"lag\":123,\"active\":456}]}",
"{\"alternates\":[{\"name\":\"S1\",\"domain\":\"domain\",\"cluster\":\"abc\"}]}",
"{\"alternates\":[{\"name\":\"S1\",\"domain\":\"domain\",\"cluster\":\"abc\"},{\"name\":\"S2\",\"domain\":\"domain\",\"cluster\":\"abc\"}]}",
};
const char *bad[] = {
"{\"config\":123}",
"{\"config\":{\"retention\":\"bad_policy\"}}",
"{\"config\":{\"discard\":\"bad_policy\"}}",
"{\"config\":{\"storage\":\"bad_policy\"}}",
"{\"config\":{\"placement\":{\"cluster\":123}}}",
"{\"config\":{\"placement\":{\"tags\":123}}}",
"{\"config\":{\"mirror\":{\"name\":123}}}",
"{\"config\":{\"mirror\":{\"opt_start_seq\":\"abc\"}}}",
"{\"config\":{\"mirror\":{\"opt_start_time\":123}}}",
"{\"config\":{\"mirror\":{\"filter_subject\":123}}}",
"{\"config\":{\"mirror\":{\"external\":123}}}",
"{\"config\":{\"mirror\":{\"external\":{\"api\":123}}}}",
"{\"config\":{\"mirror\":{\"external\":{\"deliver\":123}}}}",
"{\"state\":123}",
"{\"state\":{\"messages\":\"abc\"}}",
"{\"state\":{\"bytes\":\"abc\"}}",
"{\"state\":{\"first_seq\":\"abc\"}}",
"{\"state\":{\"first_ts\":\"abc\"}}",
"{\"state\":{\"last_seq\":\"abc\"}}",
"{\"state\":{\"last_ts\":\"abc\"}}",
"{\"state\":{\"num_deleted\":\"abc\"}}",
"{\"state\":{\"deleted\":\"abc\"}}",
"{\"state\":{\"deleted\":[\"abc\",\"def\"]}}",
"{\"state\":{\"lost\":\"abc\"}}",
"{\"state\":{\"lost\":{\"msgs\":\"abc\"}}}",
"{\"state\":{\"lost\":{\"msgs\":[\"abc\",\"def\"]}}}",
"{\"state\":{\"lost\":{\"bytes\":\"abc\"}}}",
"{\"state\":{\"consumer_count\":\"abc\"}}",
"{\"state\":{\"num_subjects\":\"abc\"}}",
"{\"state\":{\"subjects\":\"abc\"}}",
"{\"state\":{\"subjects\":{\"abc\"}}}",
"{\"state\":{\"subjects\":{\"abc\":1,\"def\":\"ghi\"}}}",
"{\"cluster\":123}",
"{\"cluster\":{\"name\":123}}",
"{\"cluster\":{\"name\":\"S1\",\"leader\":123}}",
"{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\",\"replicas\":123}}",
"{\"cluster\":{\"name\":\"S1\",\"leader\":\"S2\",\"replicas\":[{\"name\":123}]}}",
"{\"mirror\":123}",
"{\"mirror\":{\"name\":123}}",
"{\"mirror\":{\"name\":\"S1\",\"external\":123}}",
"{\"mirror\":{\"name\":\"S1\",\"external\":{\"api\":123}}}",
"{\"sources\":123}",
"{\"sources\":[{\"name\":123}]}",
"{\"sources\":[{\"name\":\"S1\",\"external\":123}]}",
"{\"sources\":[{\"name\":\"S1\",\"external\":{\"deliver\":123}}]}",
"{\"alternates\":123}",
"{\"alternates\":[{\"name\":123}]}",
"{\"alternates\":[{\"name\":\"S1\",\"domain\":123}]}",
"{\"alternates\":[{\"name\":\"S1\",\"domain\":\"domain\",\"cluster\":123}]}",
};
int i;
char tmp[64];
for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++)
{
snprintf(tmp, sizeof(tmp), "Positive case %d: ", i+1);
test(tmp);
s = nats_JSONParse(&json, good[i], (int) strlen(good[i]));
IFOK(s, js_unmarshalStreamInfo(json, &si));
testCond((s == NATS_OK) && (si != NULL));
jsStreamInfo_Destroy(si);
si = NULL;
nats_JSONDestroy(json);
json = NULL;
}
for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++)
{
snprintf(tmp, sizeof(tmp), "Negative case %d: ", i+1);
test(tmp);
s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i]));
IFOK(s, js_unmarshalStreamInfo(json, &si));
testCond((s != NATS_OK) && (si == NULL));
nats_JSONDestroy(json);
json = NULL;
nats_clearLastError();
}
}
static void
test_JetStreamMarshalStreamConfig(void)
{
natsStatus s;
jsStreamConfig sc;
jsPlacement p;
jsStreamSource m;
jsExternalStream esm;
jsStreamSource s1;
jsExternalStream esmS1;
jsStreamSource s2;
jsExternalStream esmS2;
natsBuffer *buf = NULL;
nats_JSON *json = NULL;
jsStreamConfig *rsc = NULL;
int64_t optStartTime = 1624583232123456000;
jsRePublish rp;
test("init bad args: ");
s = jsStreamConfig_Init(NULL);
if (s == NATS_INVALID_ARG)
s = jsPlacement_Init(NULL);
if (s == NATS_INVALID_ARG)
s = jsStreamSource_Init(NULL);
if (s == NATS_INVALID_ARG)
s = jsExternalStream_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
jsStreamConfig_Init(&sc);
sc.Name = "MyStream";
sc.Description = "this is my stream";
sc.Subjects = (const char*[2]){"foo", "bar"};
sc.SubjectsLen = 2;
sc.Retention = js_InterestPolicy;
sc.MaxConsumers = 1;
sc.MaxMsgs = 2;
sc.MaxBytes = 3;
sc.MaxAge = 4;
sc.MaxMsgSize = 5;
sc.Duplicates = 6;
sc.MaxMsgsPerSubject = 1;
sc.Discard = js_DiscardNew;
sc.Storage = js_MemoryStorage;
sc.Replicas = 3;
sc.NoAck = true;
sc.Template = "template";
jsPlacement_Init(&p);
p.Cluster = "MyCluster";
p.Tags = (const char*[2]){"tag1", "tag2"};
p.TagsLen = 2;
sc.Placement = &p;
jsStreamSource_Init(&m);
m.Name = "AStream";
m.OptStartSeq = 100;
m.OptStartTime = optStartTime;
m.FilterSubject = "foo";
jsExternalStream_Init(&esm);
esm.APIPrefix = "mirror.prefix";
esm.DeliverPrefix = "deliver.prefix";
m.External = &esm;
sc.Mirror = &m;
jsStreamSource_Init(&s1);
s1.Name = "StreamOne";
s1.OptStartSeq = 10;
s1.FilterSubject = "stream.one";
jsExternalStream_Init(&esmS1);
esmS1.APIPrefix = "source1.prefix";
esmS1.DeliverPrefix = "source1.deliver.prefix";
s1.External = &esmS1;
jsStreamSource_Init(&s2);
s2.Name = "StreamTwo";
s2.OptStartSeq = 20;
s2.FilterSubject = "stream.two";
jsExternalStream_Init(&esmS2);
esmS2.APIPrefix = "source2.prefix";
esmS2.DeliverPrefix = "source2.deliver.prefix";
s2.External = &esmS2;
sc.Sources = (jsStreamSource *[2]){&s1, &s2};
sc.SourcesLen = 2;
// Seal, deny purge, etc..
sc.Sealed = true;
sc.DenyDelete = true;
sc.DenyPurge = true;
sc.AllowRollup = true;
sc.AllowDirect = true;
sc.MirrorDirect = true;
sc.DiscardNewPerSubject = true;
test("RePublish init err: ");
s = jsRePublish_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
s = NATS_OK;
// Republish
jsRePublish_Init(&rp);
rp.Source = ">";
rp.Destination = "RP.>";
rp.HeadersOnly = true;
sc.RePublish = &rp;
test("Marshal stream config: ");
s = js_marshalStreamConfig(&buf, &sc);
testCond((s == NATS_OK) && (buf != NULL) && (natsBuf_Len(buf) > 0));
test("Verify with unmarshal: ");
s = nats_JSONParse(&json, natsBuf_Data(buf), natsBuf_Len(buf));
IFOK(s, js_unmarshalStreamConfig(json, NULL, &rsc));
testCond((s == NATS_OK) && (rsc != NULL)
&& (rsc->Name != NULL)
&& (strcmp(rsc->Name, "MyStream") == 0)
&& (rsc->Description != NULL)
&& (strcmp(rsc->Description, "this is my stream") == 0)
&& (rsc->SubjectsLen == 2)
&& (rsc->Subjects != NULL)
&& (strcmp(rsc->Subjects[0], "foo") == 0)
&& (strcmp(rsc->Subjects[1], "bar") == 0)
&& (rsc->Retention == js_InterestPolicy)
&& (rsc->MaxConsumers == 1)
&& (rsc->MaxMsgs == 2)
&& (rsc->MaxBytes == 3)
&& (rsc->MaxAge == 4)
&& (rsc->MaxMsgSize == 5)
&& (rsc->MaxMsgsPerSubject == 1)
&& (rsc->Duplicates == 6)
&& (rsc->Discard == js_DiscardNew)
&& (rsc->Storage == js_MemoryStorage)
&& (rsc->Replicas == 3)
&& rsc->NoAck
&& (strcmp(rsc->Template, "template") == 0)
&& (rsc->Placement != NULL)
&& (strcmp(rsc->Placement->Cluster, "MyCluster") == 0)
&& (rsc->Placement->TagsLen == 2)
&& (rsc->Placement->Tags != NULL)
&& (strcmp(rsc->Placement->Tags[0], "tag1") == 0)
&& (strcmp(rsc->Placement->Tags[1], "tag2") == 0)
&& (rsc->Mirror != NULL)
&& (strcmp(rsc->Mirror->Name, "AStream") == 0)
&& (rsc->Mirror->OptStartSeq == 100)
&& (rsc->Mirror->OptStartTime == optStartTime)
&& (strcmp(rsc->Mirror->FilterSubject, "foo") == 0)
&& (rsc->Mirror->External != NULL)
&& (strcmp(rsc->Mirror->External->APIPrefix, "mirror.prefix") == 0)
&& (strcmp(rsc->Mirror->External->DeliverPrefix, "deliver.prefix") == 0)
&& (rsc->SourcesLen == 2)
&& (rsc->Sources != NULL)
&& (strcmp(rsc->Sources[0]->Name, "StreamOne") == 0)
&& (rsc->Sources[0]->OptStartSeq == 10)
&& (strcmp(rsc->Sources[0]->FilterSubject, "stream.one") == 0)
&& (rsc->Sources[0]->External != NULL)
&& (strcmp(rsc->Sources[0]->External->APIPrefix, "source1.prefix") == 0)
&& (strcmp(rsc->Sources[0]->External->DeliverPrefix, "source1.deliver.prefix") == 0)
&& (strcmp(rsc->Sources[1]->Name, "StreamTwo") == 0)
&& (rsc->Sources[1]->OptStartSeq == 20)
&& (strcmp(rsc->Sources[1]->FilterSubject, "stream.two") == 0)
&& (rsc->Sources[1]->External != NULL)
&& (strcmp(rsc->Sources[1]->External->APIPrefix, "source2.prefix") == 0)
&& (strcmp(rsc->Sources[1]->External->DeliverPrefix, "source2.deliver.prefix") == 0)
&& rsc->Sealed
&& rsc->DenyDelete
&& rsc->DenyPurge
&& rsc->AllowRollup
&& (rsc->RePublish != NULL)
&& (rsc->RePublish->Source != NULL)
&& (strcmp(rsc->RePublish->Source, ">") == 0)
&& (rsc->RePublish->Destination != NULL)
&& (strcmp(rsc->RePublish->Destination, "RP.>") == 0)
&& rsc->RePublish->HeadersOnly
&& rsc->AllowDirect
&& rsc->MirrorDirect
&& rsc->DiscardNewPerSubject);
js_destroyStreamConfig(rsc);
rsc = NULL;
// Check that this does not crash
js_destroyStreamConfig(NULL);
natsBuf_Destroy(buf);
buf = NULL;
nats_JSONDestroy(json);
json = NULL;
test("WorkQueue policy: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Retention = js_WorkQueuePolicy;
s = js_marshalStreamConfig(&buf, &sc);
testCond((s == NATS_OK) && (buf != NULL) && (natsBuf_Len(buf) > 0));
test("Verify with unmarshal: ");
s = nats_JSONParse(&json, natsBuf_Data(buf), natsBuf_Len(buf));
IFOK(s, js_unmarshalStreamConfig(json, NULL, &rsc));
testCond((s == NATS_OK) && (rsc != NULL) && (rsc->Retention == js_WorkQueuePolicy));
js_destroyStreamConfig(rsc);
rsc = NULL;
natsBuf_Destroy(buf);
buf = NULL;
nats_JSONDestroy(json);
json = NULL;
test("Bad retention: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Retention = (jsRetentionPolicy) 100;
s = js_marshalStreamConfig(&buf, &sc);
testCond((s == NATS_INVALID_ARG) && (buf == NULL));
test("Bad discard: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Discard = (jsDiscardPolicy) 100;
s = js_marshalStreamConfig(&buf, &sc);
testCond((s == NATS_INVALID_ARG) && (buf == NULL));
test("Bad storage: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Storage = (jsStorageType) 100;
s = js_marshalStreamConfig(&buf, &sc);
testCond((s == NATS_INVALID_ARG) && (buf == NULL));
}
static void
test_JetStreamUnmarshalConsumerInfo(void)
{
natsStatus s;
jsConsumerInfo *ci = NULL;
nats_JSON *json = NULL;
const char *good[] = {
"{\"stream_name\":\"TEST\"}",
"{\"name\":\"abc\"}",
"{\"created\":\"2021-06-23T18:22:00.123456789Z\"}",
"{\"delivered\":{\"consumer_seq\":1,\"stream_seq\":1,\"last_active\":\"2021-08-21T11:49:00.123456789Z\"}}",
"{\"ack_floor\":{\"consumer_seq\":1,\"stream_seq\":1,\"last_active\":\"2021-08-21T11:49:00.123456789Z\"}}",
"{\"num_ack_pending\":1}",
"{\"num_redelivered\":1}",
"{\"num_waiting\":1}",
"{\"num_pending\":1}",
"{\"push_bound\":true}",
"{\"config\":{\"deliver_policy\":\"" jsDeliverAllStr "\"}}",
"{\"config\":{\"deliver_policy\":\"" jsDeliverLastStr "\"}}",
"{\"config\":{\"deliver_policy\":\"" jsDeliverNewStr "\"}}",
"{\"config\":{\"deliver_policy\":\"" jsDeliverBySeqStr "\"}}",
"{\"config\":{\"deliver_policy\":\"" jsDeliverByTimeStr "\"}}",
"{\"config\":{\"deliver_policy\":\"" jsDeliverLastPerSubjectStr "\"}}",
"{\"config\":{\"ack_policy\":\"" jsAckNoneStr "\"}}",
"{\"config\":{\"ack_policy\":\"" jsAckAllStr "\"}}",
"{\"config\":{\"ack_policy\":\"" jsAckExplictStr "\"}}",
"{\"config\":{\"replay_policy\":\"" jsReplayInstantStr "\"}}",
"{\"config\":{\"replay_policy\":\"" jsReplayOriginalStr "\"}}",
"{\"config\":{\"deliver_group\":\"queue_name\"}}",
"{\"config\":{\"headers_only\":true}}",
"{\"config\":{\"max_batch\":1}}",
"{\"config\":{\"max_expires\":123456789}}",
"{\"config\":{\"max_bytes\":1024}}",
"{\"config\":{\"inactive_threshold\":123456789}}",
"{\"config\":{\"backoff\":[50000000,250000000]}}",
"{\"config\":{\"num_replicas\":1}}",
"{\"config\":{\"mem_storage\":true}}",
"{\"config\":{\"name\":\"my_name\"}}",
};
const char *bad[] = {
"{\"stream_name\":123}",
"{\"name\":123}",
"{\"created\":123}",
"{\"config\":123}",
"{\"config\":{\"durable_name\":123}}",
"{\"config\":{\"description\":123}}",
"{\"config\":{\"deliver_subject\":123}}",
"{\"config\":{\"deliver_policy\":\"bad_policy\"}}",
"{\"config\":{\"opt_start_seq\":\"abc\"}}",
"{\"config\":{\"opt_start_time\":123}}",
"{\"config\":{\"ack_policy\":\"bad_policy\"}}",
"{\"config\":{\"ack_wait\":\"abc\"}}",
"{\"config\":{\"max_deliver\":\"abc\"}}",
"{\"config\":{\"filter_subject\":123}}",
"{\"config\":{\"replay_policy\":\"bad_policy\"}}",
"{\"config\":{\"rate_limit_bps\":\"abc\"}}",
"{\"config\":{\"sample_freq\":123}}",
"{\"config\":{\"max_waiting\":\"abc\"}}",
"{\"config\":{\"max_ack_pending\":\"abc\"}}",
"{\"config\":{\"flow_control\":\"abc\"}}",
"{\"config\":{\"idle_heartbeat\":\"abc\"}}",
"{\"config\":{\"deliver_group\":123}}",
"{\"config\":{\"headers_only\":123}}",
"{\"config\":{\"max_batch\":\"1\"}}",
"{\"config\":{\"max_expires\":\"123456789\"}}",
"{\"config\":{\"max_bytes\":\"123456789\"}}",
"{\"config\":{\"inactive_threshold\":\"123456789\"}}",
"{\"config\":{\"backoff\":true}}",
"{\"config\":{\"max_batch\":\"abc\"}}",
"{\"config\":{\"max_expires\":false}}",
"{\"config\":{\"max_bytes\":false}}",
"{\"config\":{\"mem_storage\":\"abc\"}}",
"{\"delivered\":123}",
"{\"delivered\":{\"consumer_seq\":\"abc\"}}",
"{\"delivered\":{\"stream_seq\":\"abc\"}}",
"{\"delivered\":{\"last_active\":123}}",
"{\"ack_floor\":123}",
"{\"ack_floor\":{\"consumer_seq\":\"abc\"}}",
"{\"ack_floor\":{\"stream_seq\":\"abc\"}}",
"{\"ack_floor\":{\"last_active\":123}}",
"{\"num_ack_pending\":\"abc\"}",
"{\"num_redelivered\":\"abc\"}",
"{\"num_waiting\":\"abc\"}",
"{\"num_pending\":\"abc\"}",
"{\"cluster\":123}",
"{\"cluster\":{\"name\":123}}",
"{\"push_bound\":123}",
};
int i;
char tmp[64];
for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++)
{
snprintf(tmp, sizeof(tmp), "Positive case %d: ", i+1);
test(tmp);
s = nats_JSONParse(&json, good[i], (int) strlen(good[i]));
IFOK(s, js_unmarshalConsumerInfo(json, &ci));
testCond((s == NATS_OK) && (ci != NULL));
jsConsumerInfo_Destroy(ci);
ci = NULL;
nats_JSONDestroy(json);
json = NULL;
}
for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++)
{
snprintf(tmp, sizeof(tmp), "Negative case %d: ", i+1);
test(tmp);
s = nats_JSONParse(&json, bad[i], (int) strlen(bad[i]));
IFOK(s, js_unmarshalConsumerInfo(json, &ci));
testCond((s != NATS_OK) && (ci == NULL));
nats_JSONDestroy(json);
json = NULL;
nats_clearLastError();
}
}
static void
testRespParsing(void)
{
natsStatus s;
nats_JSON *json = NULL;
natsMsg *msg = NULL;
const char *bad[] = {
"not a JSON",
"{\"error\":123}",
"{\"error\":{\"code\":\"abc\"}}",
"{\"error\":{\"err_code\":\"abc\"}}",
"{\"error\":{\"description\":123}}",
};
const char *good[] = {
"{\"not_an_error\":123}",
"{\"error\":{\"code\":100}}",
"{\"error\":{\"code\":404,\"description\":\"not found\"}}",
"{\"error\":{\"code\":404,\"err_code\":10014,\"description\":\"not found\"}}",
};
struct _good {
bool isErr;
int code;
uint16_t err_code;
const char *desc;
};
const struct _good goodRes[] = {
{ false, 0, 0, NULL },
{ true, 100, 0, NULL },
{ true, 404, 0, "not found" },
{ true, 404, JSConsumerNotFoundErr, "not found" },
};
char tmp[64];
int i;
jsApiResponse ar;
for (i=0; i<(int)(sizeof(bad)/sizeof(char*)); i++)
{
snprintf(tmp, sizeof(tmp), "Negative test %d: ", i+1);
test(tmp);
s = natsMsg_Create(&msg, "foo", NULL, bad[i], (int)strlen(bad[i]));
IFOK(s, js_unmarshalResponse(&ar, &json, msg));
testCond((s != NATS_OK) && (json == NULL));
natsMsg_Destroy(msg);
msg = NULL;
js_freeApiRespContent(&ar);
}
for (i=0; i<(int)(sizeof(good)/sizeof(char*)); i++)
{
snprintf(tmp, sizeof(tmp), "Positive test %d: ", i+1);
test(tmp);
s = natsMsg_Create(&msg, "foo", NULL, good[i], (int)strlen(good[i]));
IFOK(s, js_unmarshalResponse(&ar, &json, msg));
if (s == NATS_OK)
{
if (!goodRes[i].isErr)
{
if (js_apiResponseIsErr(&ar) || (ar.Error.Code != 0)
|| (ar.Error.ErrCode != 0)
|| !nats_IsStringEmpty(ar.Error.Description))
{
s = NATS_ERR;
}
}
else
{
if ((goodRes[i].code != ar.Error.Code)
|| (goodRes[i].err_code != ar.Error.ErrCode)
|| ((goodRes[i].desc == NULL) && !nats_IsStringEmpty(ar.Error.Description))
|| ((goodRes[i].desc != NULL) && nats_IsStringEmpty(ar.Error.Description))
|| ((goodRes[i].desc != NULL) && strcmp(goodRes[i].desc, ar.Error.Description) != 0))
{
s = NATS_ERR;
}
}
}
testCond((s == NATS_OK) && (json != NULL));
natsMsg_Destroy(msg);
msg = NULL;
nats_JSONDestroy(json);
json = NULL;
js_freeApiRespContent(&ar);
}
// Check that this is ok
js_freeApiRespContent(NULL);
}
#define ENSURE_JS_VERSION(major, minor, update) \
if (!serverVersionAtLeast((major), (minor), (update))) \
{ \
char txt[200]; \
snprintf(txt, sizeof(txt), "Skipping since requires server version of at least %d.%d.%d, got %s: ", \
(major), (minor), (update), serverVersion); \
test(txt); \
testCond(true); \
return; \
}
#define JS_SETUP(major, minor, update) \
natsConnection *nc = NULL; \
jsCtx *js = NULL; \
natsPid pid = NATS_INVALID_PID; \
char datastore[256] = {'\0'}; \
char cmdLine[1024] = {'\0'}; \
\
ENSURE_JS_VERSION((major), (minor), (update)); \
\
_makeUniqueDir(datastore, sizeof(datastore), "datastore_"); \
test("Start JS Server: "); \
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore);\
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true); \
CHECK_SERVER_STARTED(pid); \
testCond(true); \
\
test("Connect: "); \
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL); \
testCond(s == NATS_OK); \
\
test("Get context: "); \
s = natsConnection_JetStream(&js, nc, NULL); \
testCond(s == NATS_OK);
#define JS_TEARDOWN \
jsCtx_Destroy(js); \
natsConnection_Destroy(nc); \
_stopServer(pid); \
rmtree(datastore);
static void
test_JetStreamContext(void)
{
natsStatus s;
natsConnection *nc = NULL;
jsCtx *js = NULL;
natsPid pid = NATS_INVALID_PID;
jsOptions o;
jsAccountInfo *ai = NULL;
jsErrCode jerr = 0;
char datastore[256] = {'\0'};
char cmdLine[1024] = {'\0'};
char confFile[256] = {'\0'};
ENSURE_JS_VERSION(2, 3, 3);
test("Check response parsing:\n");
testRespParsing();
testCond(true);
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
pid = _startServer("nats://127.0.0.1:4222", "", true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Check JS context invalid args: ")
s = natsConnection_JetStream(NULL, nc, NULL);
if (s == NATS_INVALID_ARG)
s = natsConnection_JetStream(&js, NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Init options (bad args): ");
s = jsOptions_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Check JS context with negative wait: ")
jsOptions_Init(&o);
o.Wait = -10;
s = natsConnection_JetStream(&js, nc, &o);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), "negative") != NULL));
o.Wait = 0;
nats_clearLastError();
test("Get context: ");
s = natsConnection_JetStream(&js, nc, NULL);
testCond(s == NATS_OK);
test("Get account info (bad args): ");
s = js_GetAccountInfo(NULL, js, NULL, NULL);
s = js_GetAccountInfo(&ai, NULL, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (ai == NULL));
nats_clearLastError();
test("Get account fail: ");
s = js_GetAccountInfo(&ai, js, NULL, &jerr);
testCond((s == NATS_NO_RESPONDERS) && (ai == NULL) && (jerr == JSNotEnabledErr));
nats_clearLastError();
jerr = 0;
jsCtx_Destroy(js);
js = NULL;
// Check this is ok
jsCtx_Destroy(NULL);
natsConnection_Destroy(nc);
nc = NULL;
// Restart the server with JS enabled
_stopServer(pid);
pid = NATS_INVALID_PID;
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Get context set unknown prefix: ");
o.Prefix = "MyPrefix";
s = natsConnection_JetStream(&js, nc, &o);
testCond(s == NATS_OK);
test("Get account info with unknown prefix: ");
s = js_GetAccountInfo(&ai, js, NULL, &jerr);
testCond((s == NATS_NO_RESPONDERS) && (ai == NULL) && (jerr == JSNotEnabledErr));
nats_clearLastError();
jerr = 0;
test("Get account info override prefix: ");
o.Prefix = jsDefaultAPIPrefix;
s = js_GetAccountInfo(&ai, js, &o, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (ai != NULL)
&& (ai->Limits.MaxMemory == -1)
&& (ai->Limits.MaxStore == -1));
jsAccountInfo_Destroy(ai);
ai = NULL;
test("Prefix with trailing '.' works: ");
o.Prefix = "$JS.API.";
s = js_GetAccountInfo(&ai, js, &o, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (ai != NULL)
&& (ai->Limits.MaxMemory == -1)
&& (ai->Limits.MaxStore == -1));
jsAccountInfo_Destroy(ai);
ai = NULL;
jsCtx_Destroy(js);
js = NULL;
test("Create context with prefix with traling '.': ");
s = natsConnection_JetStream(&js, nc, &o);
IFOK(s, js_GetAccountInfo(&ai, js, NULL, &jerr));
testCond((s == NATS_OK)
&& (jerr == 0)
&& (js != NULL)
// Verify we don't touch the provided prefix
&& (strcmp(o.Prefix, "$JS.API.") == 0)
&& (ai != NULL)
&& (ai->Limits.MaxMemory == -1)
&& (ai->Limits.MaxStore == -1));
jsAccountInfo_Destroy(ai);
ai = NULL;
// Check this does not crash
jsAccountInfo_Destroy(NULL);
jsCtx_Destroy(js);
js = NULL;
natsConnection_Destroy(nc);
nc = NULL;
_stopServer(pid);
pid = NATS_INVALID_PID;
_createConfFile(confFile, sizeof(confFile),
" accounts { "\
" NOJS { "\
" users: [ "\
" {user: ivan, pass: pwd} "\
" ] "\
" } "\
" }\n"
);
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, "nats://ivan:pwd@127.0.0.1:4222");
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, NULL);
testCond(s == NATS_OK);
test("Get account info error: ");
s = js_GetAccountInfo(&ai, js, NULL, &jerr);
testCond((s == NATS_ERR) && (ai == NULL) && (jerr == JSNotEnabledForAccountErr)
&& (strstr(nats_GetLastError(NULL), "not enabled") != NULL));
nats_clearLastError();
JS_TEARDOWN;
remove(confFile);
}
static void
test_JetStreamContextDomain(void)
{
natsStatus s;
natsConnection *nc = NULL;
jsCtx *js = NULL;
natsPid pid = NATS_INVALID_PID;
jsOptions o;
jsAccountInfo *ai = NULL;
jsErrCode jerr = 0;
char datastore[256] = {'\0'};
char cmdLine[1024] = {'\0'};
char confFile[256] = {'\0'};
ENSURE_JS_VERSION(2, 3, 3);
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
_createConfFile(confFile, sizeof(confFile),
" jetstream: { domain: ABC }\n"
);
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Create context with domain: ");
jsOptions_Init(&o);
o.Domain = "ABC";
s = natsConnection_JetStream(&js, nc, &o);
if (s == NATS_OK)
{
js_lock(js);
// The option "Domain" should create the following prefix.
// The JS internal option should not have domain set.
if ((strcmp(js->opts.Prefix, "$JS.ABC.API") != 0)
|| (js->opts.Domain != NULL))
{
s = NATS_ERR;
}
js_unlock(js);
}
testCond((s == NATS_OK) && (js != NULL));
test("Get account: ");
s = js_GetAccountInfo(&ai, js, NULL, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (ai != NULL)
&& (strcmp(ai->Domain, "ABC") == 0)
&& (ai->Limits.MaxMemory == -1)
&& (ai->Limits.MaxStore == -1));
jsAccountInfo_Destroy(ai);
ai = NULL;
test("Get account with wrong domain override: ");
o.Domain = "DEF";
s = js_GetAccountInfo(&ai, js, &o, &jerr);
testCond((s == NATS_NO_RESPONDERS) && (ai == NULL) && (jerr == JSNotEnabledErr));
nats_clearLastError();
jerr = 0;
jsCtx_Destroy(js);
js = NULL;
test("Create context with mix prefix/domain: ");
jsOptions_Init(&o);
o.Prefix = "should.not.be.used";
o.Domain = "ABC";
s = natsConnection_JetStream(&js, nc, &o);
testCond((s == NATS_OK) && (js != NULL));
test("Get account: ");
s = js_GetAccountInfo(&ai, js, NULL, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (ai != NULL)
&& (strcmp(ai->Domain, "ABC") == 0)
&& (ai->Limits.MaxMemory == -1)
&& (ai->Limits.MaxStore == -1));
jsAccountInfo_Destroy(ai);
ai = NULL;
jsCtx_Destroy(js);
js = NULL;
test("Create context without domain: ");
jsOptions_Init(&o);
s = natsConnection_JetStream(&js, nc, &o);
testCond((s == NATS_OK) && (js != NULL));
test("Get account with domain override: ");
o.Domain = "ABC";
s = js_GetAccountInfo(&ai, js, &o, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (ai != NULL)
&& (strcmp(ai->Domain, "ABC") == 0)
&& (ai->Limits.MaxMemory == -1)
&& (ai->Limits.MaxStore == -1));
jsAccountInfo_Destroy(ai);
ai = NULL;
test("Get account with domain override (prefix option ignored): ");
o.Prefix = "should.not.be.used";
o.Domain = "ABC";
s = js_GetAccountInfo(&ai, js, &o, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (ai != NULL)
&& (strcmp(ai->Domain, "ABC") == 0)
&& (ai->Limits.MaxMemory == -1)
&& (ai->Limits.MaxStore == -1));
jsAccountInfo_Destroy(ai);
ai = NULL;
JS_TEARDOWN;
remove(confFile);
}
static void
_streamsInfoListReq(natsConnection *nc, natsMsg **msg, void *closure)
{
int *count = (int*) closure;
const char *payload = NULL;
natsMsg *newMsg = NULL;
if (strstr(natsMsg_GetData(*msg), "stream_list_response") == NULL)
return;
(*count)++;
if (*count == 1)
{
// Pretend limit is 2 and send 2 simplified stream infos
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_list_response\",\"total\":5,\"offset\":0,\"limit\":2,"\
"\"streams\":[{\"config\":{\"name\":\"S1\"}},{\"config\":{\"name\":\"S2\"}}]}";
}
else if (*count == 2)
{
// Pretend that there is a repeat of a stream name to check
// that we are properly replacing and not leaking memory.
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_list_response\",\"total\":5,\"offset\":2,\"limit\":2,"\
"\"streams\":[{\"config\":{\"name\":\"S2\"}},{\"config\":{\"name\":\"S3\"}}]}";
}
else if (*count == 3)
{
// Pretend that our next page was over the limit (say streams were removed)
// and therefore the server returned no streams (but set offset to total)
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_list_response\",\"total\":3,\"offset\":3,\"limit\":2,"\
"\"streams\":[]}";
}
else
{
// Use original message
return;
}
if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0,
payload, (int) strlen(payload), 0) == NATS_OK)
{
natsMsg_Destroy(*msg);
*msg = newMsg;
}
}
static void
_streamsNamesListReq(natsConnection *nc, natsMsg **msg, void *closure)
{
int *count = (int*) closure;
const char *payload = NULL;
natsMsg *newMsg = NULL;
if (strstr(natsMsg_GetData(*msg), "stream_names_response") == NULL)
return;
(*count)++;
if (*count == 1)
{
// Pretend limit is 2 and send 2 stream names
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_names_response\",\"total\":5,\"offset\":0,\"limit\":2,"\
"\"streams\":[\"S1\",\"S2\"]}";
}
else if (*count == 2)
{
// Pretend that there is a repeat of a stream name to check
// that we are properly replacing and not leaking memory.
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_names_response\",\"total\":5,\"offset\":2,\"limit\":2,"\
"\"streams\":[\"S2\",\"S3\"]}";
}
else if (*count == 3)
{
// Pretend that our next page was over the limit (say streams were removed)
// and therefore the server returned no streams (but set offset to total)
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_names_response\",\"total\":3,\"offset\":3,\"limit\":2,"\
"\"streams\":null}";
}
else
{
// Use original message
return;
}
if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0,
payload, (int) strlen(payload), 0) == NATS_OK)
{
natsMsg_Destroy(*msg);
*msg = newMsg;
}
}
static void
test_JetStreamMgtStreams(void)
{
natsStatus s;
jsCtx *js2= NULL;
jsStreamInfo *si = NULL;
jsStreamConfig cfg;
jsErrCode jerr = 0;
natsMsg *resp = NULL;
natsMsg *msg = NULL;
natsSubscription *sub = NULL;
char *desc = NULL;
jsStreamInfoList *siList = NULL;
jsStreamNamesList *snList = NULL;
int count = 0;
jsStreamSource ss;
jsExternalStream se;
jsOptions o;
int i;
JS_SETUP(2, 3, 3);
test("Get context 2: ");
// Set some purge options that will be saved in the context.
// They can be overridden when invoking Purge API.
jsOptions_Init(&o);
o.Stream.Purge.Subject = "baz";
o.Stream.Purge.Sequence = 10;
s = natsConnection_JetStream(&js2, nc, &o);
testCond(s == NATS_OK);
test("Stream config init, bad args: ");
s = jsStreamConfig_Init(NULL);
testCond(s == NATS_INVALID_ARG);
test("Stream config init: ");
s = jsStreamConfig_Init(&cfg);
testCond(s == NATS_OK);
test("Add stream, bad args: ");
s = js_AddStream(&si, NULL, &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (si == NULL));
nats_clearLastError();
test("Stream name required: ");
s = js_AddStream(&si, js, NULL, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_AddStream(&si, js, &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG)
&& (si == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Invalid stream name: ");
cfg.Name = "invalid.stream.name";
s = js_AddStream(&si, js, &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG)
&& (si == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid stream name") != NULL));
nats_clearLastError();
test("Create basic: ");
cfg.Name = "TEST";
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK)
&& (si != NULL)
&& (si->Config != NULL)
&& (strcmp(si->Config->Name, "TEST") == 0)
&& (jerr == 0));
jsStreamInfo_Destroy(si);
si = NULL;
test("Destroy null stream info ok: ");
jsStreamInfo_Destroy(NULL);
testCond(true);
test("Create failed because already used: ");
cfg.Subjects = (const char*[2]){"foo", "bar"};
cfg.SubjectsLen = 2;
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_ERR)
&& (si == NULL)
&& (strstr(nats_GetLastError(NULL), "already in use") != NULL)
&& ((jerr == 0) || (jerr == JSStreamNameExistErr)));
nats_clearLastError();
jerr = 0;
// Reset config
jsStreamConfig_Init(&cfg);
test("Add stream without getting stream info back: ");
cfg.Name = "TEST2";
s = js_AddStream(NULL, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si == NULL));
test("Update stream config missing: ");
s = js_UpdateStream(&si, js, NULL, NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrStreamConfigRequired) != NULL));
nats_clearLastError();
test("Update stream stream name missing: ");
cfg.Name = "";
s = js_UpdateStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Update stream stream name invalid: ");
cfg.Name = "bad.stream.name";
s = js_UpdateStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
cfg.Name = "TEST";
test("Update stream: ");
cfg.MaxMsgs = 1000;
s = js_UpdateStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0)
&& (si != NULL)
&& (si->Config != NULL)
&& (si->Config->MaxMsgs == 1000));
jsStreamInfo_Destroy(si);
si = NULL;
test("Update stream (not found): ");
cfg.Name = "NOT_FOUND";
s = js_UpdateStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_NOT_FOUND)
&& (jerr == JSStreamNotFoundErr)
&& (si == NULL));
nats_clearLastError();
test("Send 1 message: ");
s = natsConnection_Request(&resp, nc, "TEST2", "hello", 5, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(resp);
resp = NULL;
test("Get stream info (bad args): ");
s = js_GetStreamInfo(NULL, NULL, "TEST2", NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_GetStreamInfo(NULL, js, "TEST2", NULL, NULL);
testCond(s == NATS_INVALID_ARG);
test("Get stream info (stream name missing): ");
s = js_GetStreamInfo(&si, js, NULL, NULL, NULL);
testCond((s == NATS_INVALID_ARG)
&& (si == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Get stream info (invalid stream name): ");
s = js_GetStreamInfo(&si, js, "bad.stream.name", NULL, NULL);
testCond((s == NATS_INVALID_ARG)
&& (si == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
test("Get stream info: ");
s = js_GetStreamInfo(&si, js, "TEST2", NULL, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (si != NULL)
&& (si->Config != NULL)
&& (strcmp(si->Config->Subjects[0], "TEST2") == 0)
&& (si->State.Msgs == 1));
jsStreamInfo_Destroy(si);
si = NULL;
test("Get stream info (not found): ");
s = js_GetStreamInfo(&si, js, "NOT_FOUND", NULL, &jerr);
testCond((s == NATS_NOT_FOUND)
&& (jerr == JSStreamNotFoundErr)
&& (si == NULL)
&& (nats_GetLastError(NULL) == NULL));
test("Purge stream (bad args): ");
s = js_PurgeStream(NULL, "TEST2", NULL, &jerr);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Purge stream (stream name missing): ");
s = js_PurgeStream(js, NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_PurgeStream(js, "", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Purge stream (stream name invalid): ");
s = js_PurgeStream(js, "bad.stream.name", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
test("Purge stream: ");
s = js_PurgeStream(js, "TEST2", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Purge stream (not found): ");
s = js_PurgeStream(js, "NOT_FOUND", NULL, &jerr);
testCond((s == NATS_NOT_FOUND)
&& (jerr == JSStreamNotFoundErr));
nats_clearLastError();
test("Get stream info (verify purged): ");
s = js_GetStreamInfo(&si, js, "TEST2", NULL, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (si != NULL)
&& (si->Config != NULL)
&& (strcmp(si->Config->Subjects[0], "TEST2") == 0)
&& (si->State.Msgs == 0));
jsStreamInfo_Destroy(si);
si = NULL;
test("Create sub to check requests: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.MSG.DELETE.TEST_MSG_DELETE");
testCond((s == NATS_OK) && (sub != NULL));
test("Create stream for delete msg: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "TEST_MSG_DELETE";
cfg.Subjects = (const char*[1]){"delete.msg"};
cfg.SubjectsLen = 1;
cfg.Storage = js_MemoryStorage;
s = js_AddStream(NULL, js, &cfg, NULL, &jerr);
testCond(s == NATS_OK);
test("Send 1 message: ");
s = natsConnection_Request(&resp, nc, "delete.msg", "hello", 5, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(resp);
resp = NULL;
test("Delete msg (stream missing): ");
s = js_DeleteMsg(js, NULL, 2, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_DeleteMsg(js, "", 2, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (jerr == 0));
nats_clearLastError();
test("Delete msg: ");
s = js_DeleteMsg(js, "TEST_MSG_DELETE", 1, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Check no_erase present: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_OK : NATS_ERR));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Delete msg (not found): ");
s = js_DeleteMsg(js, "TEST_MSG_DELETE", 2, NULL, &jerr);
testCond((s == NATS_ERR) && (jerr == JSSequenceNotFoundErr));
nats_clearLastError();
test("Check no_erase present: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_OK : NATS_ERR));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Send 1 message: ");
s = natsConnection_Request(&resp, nc, "delete.msg", "hello", 5, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(resp);
resp = NULL;
test("Erase msg: ");
s = js_EraseMsg(js, "TEST_MSG_DELETE", 2, &o, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Check no_erase absent: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_ERR : NATS_OK));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Erase msg (not found): ");
s = js_EraseMsg(js, "TEST_MSG_DELETE", 3, &o, &jerr);
testCond((s == NATS_ERR) && (jerr == JSSequenceNotFoundErr));
nats_clearLastError();
test("Check no_erase absent: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, (strstr(natsMsg_GetData(msg), "no_erase") != NULL ? NATS_ERR : NATS_OK));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Get stream info (verify deleted): ");
s = js_GetStreamInfo(&si, js, "TEST_MSG_DELETE", NULL, &jerr);
testCond((s == NATS_OK)
&& (jerr == 0)
&& (si != NULL)
&& (si->Config != NULL)
&& (strcmp(si->Config->Subjects[0], "delete.msg") == 0)
&& (si->State.Msgs == 0));
jsStreamInfo_Destroy(si);
si = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Create stream: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "TEST3";
cfg.Subjects = (const char*[2]){"foo", "bar"};
cfg.SubjectsLen = 2;
s = js_AddStream(NULL, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Create sub to check purge req: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.PURGE.TEST3");
testCond(s == NATS_OK);
test("Purge with options (subj+seq): ");
jsOptions_Init(&o);
// Will purge only messages from "foo"
o.Stream.Purge.Subject = "foo";
// Purge up-to but do not include this sequence.
o.Stream.Purge.Sequence = 4;
// We care only on the outbound request, not the result of the API call.
js_PurgeStream(js, "TEST3", &o, NULL);
nats_clearLastError();
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strncmp("{\"filter\":\"foo\",\"seq\":4}",
natsMsg_GetData(resp),
natsMsg_GetDataLength(resp)) == 0));
natsMsg_Destroy(resp);
resp = NULL;
test("Purge without options and check context used: ");
js_PurgeStream(js2, "TEST3", NULL, NULL);
nats_clearLastError();
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strncmp("{\"filter\":\"baz\",\"seq\":10}",
natsMsg_GetData(resp),
natsMsg_GetDataLength(resp)) == 0));
natsMsg_Destroy(resp);
resp = NULL;
test("Purge with options (seq and keep mutually exclusive): ");
jsOptions_Init(&o);
o.Stream.Purge.Subject = "bar";
o.Stream.Purge.Sequence = 8;
o.Stream.Purge.Keep = 2;
s = js_PurgeStream(js, "TEST3", &o, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (jerr == 0)
&& (strstr(nats_GetLastError(NULL), "exclusive") != NULL));
nats_clearLastError();
test("Check no request was sent: ");
s = natsSubscription_NextMsg(&resp, sub, 500);
testCond((s == NATS_TIMEOUT) && (resp == NULL));
nats_clearLastError();
test("Purge with options (subj+keep): ");
jsOptions_Init(&o);
o.Stream.Purge.Subject = "bar";
// Keep 2 messages in the stream's bar subject space.
o.Stream.Purge.Keep = 2;
// We care only on the outbound request, not the result of the API call.
js_PurgeStream(js, "TEST3", &o, NULL);
nats_clearLastError();
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strncmp("{\"filter\":\"bar\",\"keep\":2}",
natsMsg_GetData(resp),
natsMsg_GetDataLength(resp)) == 0));
natsMsg_Destroy(resp);
resp = NULL;
test("Delete stream (bad args): ");
s = js_DeleteStream(NULL, "TEST2", NULL, &jerr);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Delete stream (stream name missing): ");
s = js_DeleteStream(js, NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_DeleteStream(js, "", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Delete stream (stream name invalid): ");
s = js_DeleteStream(js, "bad.stream.name", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
test("Delete stream (not found): ");
s = js_DeleteStream(js, "NOT_FOUND", NULL, &jerr);
testCond((s == NATS_NOT_FOUND)
&& (jerr == JSStreamNotFoundErr));
nats_clearLastError();
test("Delete stream: ");
s = js_DeleteStream(js, "TEST2", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Get stream info (not found): ");
s = js_GetStreamInfo(&si, js, "TEST2", NULL, &jerr);
testCond((s == NATS_NOT_FOUND)
&& (jerr == JSStreamNotFoundErr)
&& (si == NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Create sub to check stream info req: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.INFO.TEST3");
testCond(s == NATS_OK);
test("StreamInfo with detailed delete: ");
o.Stream.Info.DeletedDetails = true;
IFOK(s, js_GetStreamInfo(&si, js, "TEST3", &o, &jerr));
IFOK(s, natsSubscription_NextMsg(&resp, sub, 1000));
testCond((s == NATS_OK)
&& (resp != NULL)
&& (natsMsg_GetDataLength(resp) > 0)
&& (strncmp("{\"deleted_details\":true}",
natsMsg_GetData(resp),
natsMsg_GetDataLength(resp)) == 0));
jsStreamInfo_Destroy(si);
si = NULL;
natsMsg_Destroy(resp);
resp = NULL;
test("Create stream with description: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "TEST_DESC";
cfg.Description = "MyDescription";
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (si != NULL) && (jerr == 0)
&& (si->Config != NULL)
&& (si->Config->Description != NULL)
&& (strcmp(si->Config->Description, "MyDescription") == 0));
jsStreamInfo_Destroy(si);
si = NULL;
test("Create stream with description too long: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "TEST_DESC2";
desc = malloc(4*1024+2);
for (i=0;i<4*1024+1;i++)
desc[i] = 'a';
desc[i]='\0';
cfg.Description = (const char*) desc;
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_ERR) && (si == NULL) && (jerr == JSStreamInvalidConfig));
nats_clearLastError();
free(desc);
jsCtx_Destroy(js2);
natsSubscription_Destroy(sub);
sub = NULL;
test("Create stream with wilcards: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "STREAM_WITH_WC";
cfg.Subjects = (const char*[2]){"foo.>", "bar.*"};
cfg.SubjectsLen = 2;
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) & (si != NULL) && (si->Config != NULL)
&& (si->Config->SubjectsLen == 2)
&& (strcmp(si->Config->Subjects[0], "foo.>") == 0)
&& (strcmp(si->Config->Subjects[1], "bar.*") == 0));
jsStreamInfo_Destroy(si);
si = NULL;
test("List stream infos (bad args): ");
s = js_Streams(NULL, js, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_Streams(&siList, NULL, NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Create sub for pagination check: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.LIST");
testCond(s == NATS_OK);
natsConn_setFilterWithClosure(nc, _streamsInfoListReq, (void*) &count);
test("List stream infos: ");
s = js_Streams(&siList, js, NULL, &jerr);
testCond((s == NATS_OK) && (siList != NULL) && (siList->List != NULL) && (siList->Count == 3));
natsConn_setFilter(nc, NULL);
test("Check 1st request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":0") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 2nd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":2") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 3rd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":4") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Destroy list: ");
// Will see with valgrind if this is doing the right thing
jsStreamInfoList_Destroy(siList);
siList = NULL;
// Check this does not crash
jsStreamInfoList_Destroy(siList);
testCond(true);
test("List stream infos with filter: ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = "TEST";
s = js_Streams(&siList, js, &o, &jerr);
testCond((s == NATS_OK) && (siList != NULL) && (siList->List != NULL) && (siList->Count == 1)
&& (strcmp(siList->List[0]->Config->Name, "TEST") == 0));
jsStreamInfoList_Destroy(siList);
siList = NULL;
test("List stream infos with filter no match: ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = "no.match";
s = js_Streams(&siList, js, &o, &jerr);
testCond((s == NATS_NOT_FOUND) && (siList == NULL));
// Do names now
test("List stream names (bad args): ");
s = js_StreamNames(NULL, js, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_StreamNames(&snList, NULL, NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Create sub for pagination check: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.NAMES");
testCond(s == NATS_OK);
count = 0;
natsConn_setFilterWithClosure(nc, _streamsNamesListReq, (void*) &count);
test("List stream names: ");
s = js_StreamNames(&snList, js, NULL, &jerr);
testCond((s == NATS_OK) && (snList != NULL) && (snList->List != NULL) && (snList->Count == 3));
natsConn_setFilter(nc, NULL);
test("Check 1st request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":0") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 2nd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":2") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 3rd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":4") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Destroy list: ");
// Will see with valgrind if this is doing the right thing
jsStreamNamesList_Destroy(snList);
snList = NULL;
// Check this does not crash
jsStreamNamesList_Destroy(snList);
testCond(true);
test("List stream names with filter: ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = "TEST";
s = js_StreamNames(&snList, js, &o, &jerr);
testCond((s == NATS_OK) && (snList != NULL) && (snList->List != NULL) && (snList->Count == 1)
&& (strcmp(snList->List[0], "TEST") == 0));
jsStreamNamesList_Destroy(snList);
snList = NULL;
test("List stream names with filter no match: ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = "no.match";
s = js_StreamNames(&snList, js, &o, &jerr);
testCond((s == NATS_NOT_FOUND) && (snList == NULL));
test("Mirror domain and external set error: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "MDESET";
jsStreamSource_Init(&ss);
ss.Domain = "Domain";
jsExternalStream_Init(&se);
se.DeliverPrefix = "some.prefix";
ss.External = &se;
cfg.Mirror = &ss;
s = js_AddStream(&si, js, &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (si == NULL)
&& (strstr(nats_GetLastError(NULL), "domain and external are both set") != NULL));
nats_clearLastError();
test("Source domain and external set error: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "SDESET";
jsStreamSource_Init(&ss);
ss.Domain = "Domain";
jsExternalStream_Init(&se);
se.DeliverPrefix = "some.prefix";
ss.External = &se;
cfg.Sources = (jsStreamSource*[1]){&ss};
cfg.SourcesLen = 1;
s = js_AddStream(&si, js, &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (si == NULL)
&& (strstr(nats_GetLastError(NULL), "domain and external are both set") != NULL));
nats_clearLastError();
JS_TEARDOWN;
}
static void
_consumersInfoListReq(natsConnection *nc, natsMsg **msg, void *closure)
{
int *count = (int*) closure;
const char *payload = NULL;
natsMsg *newMsg = NULL;
if (strstr(natsMsg_GetData(*msg), "consumer_list_response") == NULL)
return;
(*count)++;
if (*count == 1)
{
// Pretend limit is 2 and send 2 simplified consumer infos
payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_list_response\",\"total\":5,\"offset\":0,\"limit\":2,"\
"\"consumers\":[{\"stream_name\":\"A\",\"name\":\"a\"},{\"stream_name\":\"A\",\"name\":\"b\"}]}";
}
else if (*count == 2)
{
// Pretend that there is a repeat of a stream name to check
// that we are properly replacing and not leaking memory.
payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_list_response\",\"total\":5,\"offset\":2,\"limit\":2,"\
"\"consumers\":[{\"stream_name\":\"A\",\"name\":\"b\"},{\"stream_name\":\"A\",\"name\":\"c\"}]}";
}
else if (*count == 3)
{
// Pretend that our next page was over the limit (say streams were removed)
// and therefore the server returned no streams (but set offset to total)
payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_list_response\",\"total\":3,\"offset\":3,\"limit\":2,"\
"\"consumers\":[]}";
}
else
{
// Use original message
return;
}
if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0,
payload, (int) strlen(payload), 0) == NATS_OK)
{
natsMsg_Destroy(*msg);
*msg = newMsg;
}
}
static void
_consumerNamesListReq(natsConnection *nc, natsMsg **msg, void *closure)
{
int *count = (int*) closure;
const char *payload = NULL;
natsMsg *newMsg = NULL;
if (strstr(natsMsg_GetData(*msg), "consumer_names_response") == NULL)
return;
(*count)++;
if (*count == 1)
{
// Pretend limit is 2 and send 2 stream names
payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_names_response\",\"total\":5,\"offset\":0,\"limit\":2,"\
"\"consumers\":[\"a\",\"b\"]}";
}
else if (*count == 2)
{
// Pretend that there is a repeat of a stream name to check
// that we are properly replacing and not leaking memory.
payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_names_response\",\"total\":5,\"offset\":0,\"limit\":2,"\
"\"consumers\":[\"b\",\"c\"]}";
}
else if (*count == 3)
{
// Pretend that our next page was over the limit (say streams were removed)
// and therefore the server returned no streams (but set offset to total)
payload = "{\"type\":\"io.nats.jetstream.api.v1.consumer_names_response\",\"total\":3,\"offset\":3,\"limit\":2,"\
"\"consumers\":[]}";
}
else
{
// Use original message
return;
}
if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject), NULL, 0,
payload, (int) strlen(payload), 0) == NATS_OK)
{
natsMsg_Destroy(*msg);
*msg = newMsg;
}
}
static void
test_JetStreamMgtConsumers(void)
{
natsStatus s;
jsConsumerInfo *ci = NULL;
jsConsumerConfig cfg;
jsErrCode jerr = 0;
natsMsg *resp = NULL;
natsSubscription *sub = NULL;
char *desc = NULL;
jsStreamConfig scfg;
int i;
const char *dlvPoliciesStr[] = {
"\"deliver_policy\":\"" jsDeliverAllStr "\"",
"\"deliver_policy\":\"" jsDeliverLastStr "\"",
"\"deliver_policy\":\"" jsDeliverNewStr "\"",
"\"deliver_policy\":\"" jsDeliverBySeqStr "\"",
"\"deliver_policy\":\"" jsDeliverByTimeStr "\"",
"\"deliver_policy\":\"" jsDeliverLastPerSubjectStr "\"",
};
jsDeliverPolicy dlvPolicies[] = {
js_DeliverAll, js_DeliverLast, js_DeliverNew, js_DeliverByStartSequence,
js_DeliverByStartTime, js_DeliverLastPerSubject};
const char *ackPoliciesStr[] = {
"\"ack_policy\":\"" jsAckNoneStr "\"",
"\"ack_policy\":\"" jsAckAllStr "\"",
"\"ack_policy\":\"" jsAckExplictStr "\"",
};
jsAckPolicy ackPolicies[] = {
js_AckNone, js_AckAll, js_AckExplicit};
const char *replayPoliciesStr[] = {
"\"replay_policy\":\"" jsReplayInstantStr "\"",
"\"replay_policy\":\"" jsReplayOriginalStr "\"",
};
jsReplayPolicy replayPolicies[] = {
js_ReplayInstant, js_ReplayOriginal};
const char *badConsNames[] = {
"foo.bar",
".foobar",
"foobar.",
"foo bar",
" foobar",
"foobar ",
"foo*bar",
"*foobar",
"foobar*",
"foo>bar",
">foobar",
"foobar>",
};
jsConsumerInfoList *ciList = NULL;
jsConsumerNamesList *cnList = NULL;
int count = 0;
natsMsg *msg = NULL;
jsConsumerConfig *cloneCfg = NULL;
JS_SETUP(2, 9, 0);
test("Consumer config init, bad args: ");
s = jsConsumerConfig_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Consumer config init: ");
s = jsConsumerConfig_Init(&cfg);
testCond(s == NATS_OK);
test("Add consumer, ctx missing: ");
s = js_AddConsumer(&ci, NULL, "MY_STREAM", &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (ci == NULL));
nats_clearLastError();
test("Add consumer, config missing: ");
s = js_AddConsumer(&ci, js, "MY_STREAM", NULL, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (ci == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrConsumerConfigRequired) != NULL));
nats_clearLastError();
test("Add consumer, stream name required: ");
s = js_AddConsumer(&ci, js, NULL, &cfg, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_AddConsumer(&ci, js, "", &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (ci == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Add consumer, stream name invalid: ");
s = js_AddConsumer(&ci, js, "bad.stream.name", &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (ci == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
test("Add consumer, invalid durable name: ");
cfg.Durable = "invalid.durable.name";
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (ci == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidDurableName) != NULL));
nats_clearLastError();
s = NATS_OK;
test("Add consumer, invalid name: ");
jsConsumerConfig_Init(&cfg);
for (i=0; (s == NATS_OK) && (i<(int)(sizeof(badConsNames)/sizeof(char*))); i++)
{
cfg.Name = badConsNames[i];
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, NULL);
if ((s == NATS_INVALID_ARG) && (ci == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidConsumerName) != NULL))
{
nats_clearLastError();
s = NATS_OK;
}
}
testCond(s == NATS_OK);
test("Create check sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.CREATE.MY_STREAM");
testCond(s == NATS_OK);
for (i=0; i<6; i++)
{
test("Deliver policy: ");
jsConsumerConfig_Init(&cfg);
cfg.DeliverPolicy = dlvPolicies[i];
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL));
nats_clearLastError();
test("Verify config: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK) && (resp != NULL)
&& (strstr(natsMsg_GetData(resp), dlvPoliciesStr[i]) != NULL));
natsMsg_Destroy(resp);
resp = NULL;
}
test("Deliver policy bad: ");
jsConsumerConfig_Init(&cfg);
cfg.DeliverPolicy = (jsDeliverPolicy) 100;
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_INVALID_ARG) && (jerr == 0) && (ci == NULL));
nats_clearLastError();
for (i=0; i<3; i++)
{
test("Ack policy: ");
jsConsumerConfig_Init(&cfg);
cfg.AckPolicy = ackPolicies[i];
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL));
nats_clearLastError();
test("Verify config: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK) && (resp != NULL)
&& (strstr(natsMsg_GetData(resp), ackPoliciesStr[i]) != NULL));
natsMsg_Destroy(resp);
resp = NULL;
}
test("Ack policy bad: ");
jsConsumerConfig_Init(&cfg);
cfg.AckPolicy = (jsAckPolicy) 100;
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_INVALID_ARG) && (jerr == 0) && (ci == NULL));
nats_clearLastError();
for (i=0; i<2; i++)
{
test("Replay policy: ");
jsConsumerConfig_Init(&cfg);
cfg.ReplayPolicy = replayPolicies[i];
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL));
nats_clearLastError();
test("Verify config: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK) && (resp != NULL)
&& (strstr(natsMsg_GetData(resp), replayPoliciesStr[i]) != NULL));
natsMsg_Destroy(resp);
resp = NULL;
}
test("Replay policy bad: ");
jsConsumerConfig_Init(&cfg);
cfg.ReplayPolicy = (jsReplayPolicy) 100;
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_INVALID_ARG) && (jerr == 0) && (ci == NULL));
nats_clearLastError();
test("Add consumer (non durable): ");
cfg.Durable = NULL;
cfg.Description = "MyDescription";
cfg.DeliverSubject = "foo";
cfg.DeliverPolicy = js_DeliverLast;
cfg.OptStartSeq = 100;
cfg.OptStartTime = 1624472520123450000;
cfg.AckPolicy = js_AckExplicit;
cfg.AckWait = 200;
cfg.MaxDeliver = 300;
cfg.FilterSubject = "bar";
cfg.ReplayPolicy = js_ReplayInstant;
cfg.RateLimit = 400;
cfg.SampleFrequency = "60%%";
cfg.MaxWaiting = 500;
cfg.MaxAckPending = 600;
cfg.FlowControl = true;
cfg.Heartbeat = 700;
cfg.Replicas = 1;
cfg.MemoryStorage = true;
// We create a consumer with non existing stream, so we
// expect this to fail. We are just checking that the config
// is properly serialized.
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL));
nats_clearLastError();
test("Verify config: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK) && (resp != NULL)
&& (strncmp(natsMsg_GetData(resp),
"{\"stream_name\":\"MY_STREAM\","\
"\"config\":{\"deliver_policy\":\"last\","\
"\"description\":\"MyDescription\","\
"\"deliver_subject\":\"foo\","\
"\"opt_start_seq\":100,"\
"\"opt_start_time\":\"2021-06-23T18:22:00.12345Z\",\"ack_policy\":\"explicit\","\
"\"ack_wait\":200,\"max_deliver\":300,\"filter_subject\":\"bar\","\
"\"replay_policy\":\"instant\",\"rate_limit_bps\":400,"\
"\"sample_freq\":\"60%%\",\"max_waiting\":500,\"max_ack_pending\":600,"\
"\"flow_control\":true,\"idle_heartbeat\":700,"\
"\"num_replicas\":1,\"mem_storage\":true}}",
natsMsg_GetDataLength(resp)) == 0));
natsMsg_Destroy(resp);
resp = NULL;
test("Create check sub: ");
natsSubscription_Destroy(sub);
sub = NULL;
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.DURABLE.CREATE.MY_STREAM.dur");
testCond(s == NATS_OK);
test("Add consumer (durable): ");
cfg.Durable = "dur";
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL));
nats_clearLastError();
test("Verify config: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK) && (resp != NULL)
&& (strncmp(natsMsg_GetData(resp),
"{\"stream_name\":\"MY_STREAM\","\
"\"config\":{\"deliver_policy\":\"last\","\
"\"description\":\"MyDescription\","\
"\"durable_name\":\"dur\",\"deliver_subject\":\"foo\","\
"\"opt_start_seq\":100,"\
"\"opt_start_time\":\"2021-06-23T18:22:00.12345Z\",\"ack_policy\":\"explicit\","\
"\"ack_wait\":200,\"max_deliver\":300,\"filter_subject\":\"bar\","\
"\"replay_policy\":\"instant\",\"rate_limit_bps\":400,"\
"\"sample_freq\":\"60%%\",\"max_waiting\":500,\"max_ack_pending\":600,"\
"\"flow_control\":true,\"idle_heartbeat\":700,"\
"\"num_replicas\":1,\"mem_storage\":true}}",
natsMsg_GetDataLength(resp)) == 0));
natsMsg_Destroy(resp);
resp = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Create check sub: ");
natsSubscription_Destroy(sub);
sub = NULL;
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.CREATE.MY_STREAM.>");
testCond(s == NATS_OK);
test("Add consumer (name): ");
cfg.Durable = NULL;
cfg.Name = "my_name";
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s = NATS_ERR) && (jerr == JSStreamNotFoundErr) && (ci == NULL));
nats_clearLastError();
test("Verify config: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK) && (resp != NULL)
&& (strncmp(natsMsg_GetData(resp),
"{\"stream_name\":\"MY_STREAM\","\
"\"config\":{\"deliver_policy\":\"last\","\
"\"name\":\"my_name\","\
"\"description\":\"MyDescription\","\
"\"deliver_subject\":\"foo\","\
"\"opt_start_seq\":100,"\
"\"opt_start_time\":\"2021-06-23T18:22:00.12345Z\",\"ack_policy\":\"explicit\","\
"\"ack_wait\":200,\"max_deliver\":300,\"filter_subject\":\"bar\","\
"\"replay_policy\":\"instant\",\"rate_limit_bps\":400,"\
"\"sample_freq\":\"60%%\",\"max_waiting\":500,\"max_ack_pending\":600,"\
"\"flow_control\":true,\"idle_heartbeat\":700,"\
"\"num_replicas\":1,\"mem_storage\":true}}",
natsMsg_GetDataLength(resp)) == 0));
natsMsg_Destroy(resp);
resp = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Create stream: ");
jsStreamConfig_Init(&scfg);
scfg.Name = "MY_STREAM";
scfg.Subjects = (const char*[1]){"bar.>"};
scfg.SubjectsLen = 1;
s = js_AddStream(NULL, js, &scfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Add consumer (name): ");
jsConsumerConfig_Init(&cfg);
cfg.Name = "my_name";
cfg.DeliverSubject = "mn.foo";
cfg.FilterSubject = "bar.>";
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL)
&& (strcmp(ci->Stream, "MY_STREAM") == 0)
&& (strcmp(ci->Name, "my_name") == 0)
&& (strcmp(ci->Config->Name, "my_name") == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Add consumer (durable): ");
jsConsumerConfig_Init(&cfg);
cfg.Durable = "dur";
cfg.DeliverSubject = "foo";
cfg.FilterSubject = "bar.>";
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL)
&& (strcmp(ci->Stream, "MY_STREAM") == 0)
&& (strcmp(ci->Name, "dur") == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Publish: ");
s = natsConnection_Publish(nc, "bar.baz", "hello", 5);
testCond(s == NATS_OK);
test("Get consumer info (bad args): ");
s = js_GetConsumerInfo(NULL, js, "MY_STREAM", "dur", NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetConsumerInfo(&ci, NULL, "MY_STREAM", "dur", NULL, &jerr);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Get consumer info, stream name missing: ");
s = js_GetConsumerInfo(&ci, js, NULL, "dur", NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetConsumerInfo(&ci, js, "", "dur", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Get consumer info, stream name invalid: ");
s = js_GetConsumerInfo(&ci, js, "bad.stream.name", "dur", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
test("Get consumer info, consumer name missing: ");
s = js_GetConsumerInfo(&ci, js, "MY_STREAM", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrConsumerNameRequired) != NULL));
nats_clearLastError();
test("Get consumer info, consumer name invalid: ");
s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "bad.consumer.name", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidConsumerName) != NULL));
nats_clearLastError();
test("Get consumer info: ");
s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "dur", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL)
&& (strcmp(ci->Stream, "MY_STREAM") == 0)
&& (strcmp(ci->Name, "dur") == 0)
&& (ci->Config != NULL)
&& (ci->Config->FilterSubject != NULL)
&& (strcmp(ci->Config->FilterSubject, "bar.>") == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Get consumer info (not found): ");
s = js_GetConsumerInfo(&ci, js, "MY_STREAM", "dur2", NULL, &jerr);
testCond((s == NATS_NOT_FOUND)
&& (jerr == JSConsumerNotFoundErr)
&& (ci == NULL)
&& (nats_GetLastError(NULL) == NULL));
test("Delete consumer (bad args): ");
s = js_DeleteConsumer(NULL, "MY_STREAM", "dur", NULL, &jerr);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Delete consumer, stream name missing: ");
s = js_DeleteConsumer(js, NULL, "dur", NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_DeleteConsumer(js, "", "dur", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Delete consumer, stream name invalid: ");
s = js_DeleteConsumer(js, "bad.stream.name", "dur", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
test("Delete consumer, consumer name missing: ");
s = js_DeleteConsumer(js, "MY_STREAM", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_DeleteConsumer(js, "MY_STREAM", "", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrConsumerNameRequired) != NULL));
nats_clearLastError();
test("Get consumer info, consumer name invalid: ");
s = js_DeleteConsumer(js, "MY_STREAM", "bad.consumer.name", NULL, &jerr);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidConsumerName) != NULL));
nats_clearLastError();
test("Delete consumer: ");
s = js_DeleteConsumer(js, "MY_STREAM", "dur", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Delete consumer (not found): ");
s = js_DeleteConsumer(js, "MY_STREAM", "dur2", NULL, &jerr);
testCond((s == NATS_NOT_FOUND)
&& (jerr == JSConsumerNotFoundErr));
nats_clearLastError();
test("Create consumer with description: ");
jsConsumerConfig_Init(&cfg);
cfg.Description = "MyDescription";
cfg.DeliverSubject = "desc1";
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0)
&& (ci->Config != NULL)
&& (ci->Config->Description != NULL)
&& (strcmp(ci->Config->Description, "MyDescription") == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Create consumer with description too long: ");
jsConsumerConfig_Init(&cfg);
desc = malloc(4*1024+2);
for (i=0;i<4*1024+1;i++)
desc[i] = 'a';
desc[i]='\0';
cfg.Description = (const char*) desc;
cfg.DeliverSubject = "desc2";
s = js_AddConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_ERR) && (ci == NULL) && (jerr == JSConsumerDescriptionTooLongErr));
nats_clearLastError();
free(desc);
test("Create consumer: ");
jsConsumerConfig_Init(&cfg);
cfg.Durable = "update_push_consumer";
cfg.DeliverSubject = "bar";
cfg.FilterSubject = "bar.baz";
cfg.AckPolicy = js_AckExplicit;
s = js_AddConsumer(NULL, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
// We will update this config and pass it to UpdateConsumer
// and check that the result after UpdateConsumer matches...
// Currently, server supports these fields:
// description, ack_wait, max_deliver, sample_freq, max_ack_pending, max_waiting and headers_only
cfg.Description = "my description";
cfg.AckWait = NATS_SECONDS_TO_NANOS(2);
cfg.MaxDeliver = 1;
cfg.SampleFrequency = "30";
cfg.MaxAckPending = 10;
cfg.HeadersOnly = true;
test("Update consumer, config missing: ");
s = js_UpdateConsumer(&ci, js, "MY_STREAM", NULL, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (ci == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrConsumerConfigRequired) != NULL));
nats_clearLastError();
test("Update consumer, stream name missing: ");
s = js_UpdateConsumer(&ci, js, NULL, &cfg, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_UpdateConsumer(&ci, js, "", &cfg, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (ci == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("Update consumer, stream name invalid: ");
s = js_UpdateConsumer(&ci, js, "bad.stream.name", &cfg, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (ci == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrInvalidStreamName) != NULL));
nats_clearLastError();
test("Update needs durable name: ");
cfg.Durable = NULL;
s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
if (s == NATS_INVALID_ARG)
{
cfg.Durable = "";
s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
}
testCond((s == NATS_INVALID_ARG) && (ci == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrDurRequired) != NULL));
nats_clearLastError();
cfg.Durable = "update_push_consumer";
cfg.FilterSubject = "bar.bat";
test("Update works ok: ");
s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL)
&& (strcmp(ci->Config->Description, "my description") == 0)
&& (ci->Config->AckWait == NATS_SECONDS_TO_NANOS(2))
&& (ci->Config->MaxDeliver == 1)
&& (strcmp(ci->Config->SampleFrequency, "30") == 0)
&& (ci->Config->MaxAckPending == 10)
&& (ci->Config->HeadersOnly)
&& (ci->Config->FilterSubject != NULL)
&& (strcmp(ci->Config->FilterSubject, "bar.bat") == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Add pull consumer: ");
jsConsumerConfig_Init(&cfg);
cfg.Durable = "update_pull_consumer";
cfg.AckPolicy = js_AckExplicit;
cfg.MaxWaiting = 1;
s = js_AddConsumer(NULL, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
cfg.Description = "my description";
cfg.AckWait = NATS_SECONDS_TO_NANOS(2);
cfg.MaxDeliver = 1;
cfg.SampleFrequency = "30";
cfg.MaxAckPending = 10;
cfg.HeadersOnly = true;
cfg.MaxRequestBatch = 10;
cfg.MaxRequestExpires = NATS_SECONDS_TO_NANOS(2);
test("Update works ok: ");
s = js_UpdateConsumer(&ci, js, "MY_STREAM", &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL)
&& (strcmp(ci->Config->Description, "my description") == 0)
&& (ci->Config->AckWait == NATS_SECONDS_TO_NANOS(2))
&& (ci->Config->MaxDeliver == 1)
&& (strcmp(ci->Config->SampleFrequency, "30") == 0)
&& (ci->Config->MaxAckPending == 10)
&& (ci->Config->HeadersOnly)
&& (ci->Config->MaxRequestBatch == 10)
&& (ci->Config->MaxRequestExpires == NATS_SECONDS_TO_NANOS(2)));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Create stream: ");
jsStreamConfig_Init(&scfg);
scfg.Name = "A";
scfg.Subjects = (const char*[2]){"foo", "bar"};
scfg.SubjectsLen = 2;
s = js_AddStream(NULL, js, &scfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Create sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.>");
testCond(s == NATS_OK);
test("Ephemeral with name: ");
jsConsumerConfig_Init(&cfg);
cfg.Name = "a";
cfg.AckPolicy = js_AckExplicit;
cfg.InactiveThreshold = NATS_SECONDS_TO_NANOS(1);
s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr);
testCond((s == NATS_OK)
&& (strcmp(ci->Name, "a") == 0)
&& (ci->Config->Durable == NULL)
&& (ci->Config->InactiveThreshold == NATS_SECONDS_TO_NANOS(1))
&& (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Check: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.a") == 0)
&& (strstr(natsMsg_GetData(resp), "\"name\":\"a\"") != NULL));
natsMsg_Destroy(resp);
resp = NULL;
test("Durable: ");
jsConsumerConfig_Init(&cfg);
cfg.Durable = "b";
cfg.AckPolicy = js_AckExplicit;
s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr);
testCond((s == NATS_OK)
&& (strcmp(ci->Name, "b") == 0)
&& (strcmp(ci->Config->Durable, "b") == 0)
&& (ci->Config->InactiveThreshold == 0)
&& (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Check: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.DURABLE.CREATE.A.b") == 0)
&& (strstr(natsMsg_GetData(resp), "\"name\":") == NULL));
natsMsg_Destroy(resp);
resp = NULL;
test("Durable and Name same: ");
jsConsumerConfig_Init(&cfg);
cfg.Name = "b";
cfg.Durable = "b";
cfg.AckPolicy = js_AckExplicit;
s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr);
testCond((s == NATS_OK)
&& (strcmp(ci->Name, "b") == 0)
&& (strcmp(ci->Config->Durable, "b") == 0)
&& (ci->Config->InactiveThreshold == 0)
&& (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Check subject: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.b") == 0)
&& (strstr(natsMsg_GetData(resp), "\"name\":\"b\"") != NULL));
natsMsg_Destroy(resp);
resp = NULL;
test("Durable and Name different: ");
jsConsumerConfig_Init(&cfg);
cfg.Name = "x";
cfg.Durable = "y";
cfg.AckPolicy = js_AckExplicit;
s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr);
testCond((s == NATS_ERR)
&& (jerr == JSConsumerDurableNameNotMatchSubjectErr)
&& (strstr(nats_GetLastError(NULL), "consumer name in subject does not match durable name in request") != NULL));
test("Check subject: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.x") == 0)
&& (strstr(natsMsg_GetData(resp), "\"name\":\"x\"") != NULL));
natsMsg_Destroy(resp);
resp = NULL;
test("Ephemeral with filter: ");
jsConsumerConfig_Init(&cfg);
cfg.Name = "c";
cfg.AckPolicy = js_AckExplicit;
cfg.FilterSubject = "bar";
s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr);
testCond((s == NATS_OK)
&& (strcmp(ci->Name, "c") == 0)
&& (ci->Config->Durable == NULL)
&& (ci->Config->InactiveThreshold != 0)
&& (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Check subject: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A.c.bar") == 0)
&& (strstr(natsMsg_GetData(resp), "\"name\":\"c\"") != NULL));
natsMsg_Destroy(resp);
resp = NULL;
test("Legacy ephemeral: ");
jsConsumerConfig_Init(&cfg);
cfg.AckPolicy = js_AckExplicit;
cfg.FilterSubject = "bar";
s = js_AddConsumer(&ci, js, "A", &cfg, NULL, &jerr);
testCond((s == NATS_OK)
&& (ci->Name != NULL)
&& (ci->Config->Durable == NULL)
&& (ci->Config->InactiveThreshold != 0)
&& (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Check subject: ");
s = natsSubscription_NextMsg(&resp, sub, 1000);
testCond((s == NATS_OK)
&& (resp != NULL)
&& (strcmp(natsMsg_GetSubject(resp), "$JS.API.CONSUMER.CREATE.A") == 0)
&& (strstr(natsMsg_GetData(resp), "\"name\":") == NULL));
natsMsg_Destroy(resp);
resp = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("List consumer infos (bad args): ");
s = js_Consumers(NULL, js, "A", NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_Consumers(&ciList, NULL, "A", NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_Consumers(&ciList, js, NULL, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_Consumers(&ciList, js, "", NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_Consumers(&ciList, js, "invalid.stream.name", NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("List consumer infos (unknown stream): ");
s = js_Consumers(&ciList, js, "unknown", NULL, &jerr);
testCond((s == NATS_NOT_FOUND) && (jerr == JSStreamNotFoundErr));
nats_clearLastError();
test("Create sub for pagination check: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.LIST.A");
testCond(s == NATS_OK);
natsConn_setFilterWithClosure(nc, _consumersInfoListReq, (void*) &count);
test("List consumers infos: ");
s = js_Consumers(&ciList, js, "A", NULL, &jerr);
testCond((s == NATS_OK) && (ciList != NULL) && (ciList->List != NULL) && (ciList->Count == 3));
natsConn_setFilter(nc, NULL);
test("Check 1st request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":0") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 2nd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":2") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 3rd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":4") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Destroy list: ");
// Will see with valgrind if this is doing the right thing
jsConsumerInfoList_Destroy(ciList);
ciList = NULL;
// Check this does not crash
jsConsumerInfoList_Destroy(ciList);
testCond(true);
// Do names now
test("List consumer names (bad args): ");
s = js_ConsumerNames(NULL, js, "A", NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_ConsumerNames(&cnList, NULL, "A", NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_ConsumerNames(&cnList, js, NULL, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_ConsumerNames(&cnList, js, "", NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_ConsumerNames(&cnList, js, "invalid.stream.name", NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("List consumer names (unknown stream): ");
s = js_ConsumerNames(&cnList, js, "unknown", NULL, &jerr);
testCond((s == NATS_NOT_FOUND) && (jerr == JSStreamNotFoundErr));
nats_clearLastError();
test("Create sub for pagination check: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.CONSUMER.NAMES.A");
testCond(s == NATS_OK);
count = 0;
natsConn_setFilterWithClosure(nc, _consumerNamesListReq, (void*) &count);
test("List consumer names: ");
s = js_ConsumerNames(&cnList, js, "A", NULL, &jerr);
testCond((s == NATS_OK) && (cnList != NULL) && (cnList->List != NULL) && (cnList->Count == 3));
natsConn_setFilter(nc, NULL);
test("Check 1st request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":0") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 2nd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":2") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 3rd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":4") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Destroy list: ");
// Will see with valgrind if this is doing the right thing
jsConsumerNamesList_Destroy(cnList);
cnList = NULL;
// Check this does not crash
jsConsumerNamesList_Destroy(cnList);
testCond(true);
test("Check clone: ");
jsConsumerConfig_Init(&cfg);
cfg.Name = "A";
cfg.Durable = "B";
cfg.Description = "C";
cfg.FilterSubject = "D";
cfg.SampleFrequency = "E";
cfg.DeliverSubject = "F";
cfg.DeliverGroup = "G";
cfg.BackOff = (int64_t[]){NATS_MILLIS_TO_NANOS(50), NATS_MILLIS_TO_NANOS(250)};
cfg.BackOffLen = 2;
s = js_cloneConsumerConfig(&cfg, &cloneCfg);
testCond((s == NATS_OK) && (cloneCfg != NULL)
&& (cloneCfg->Name != NULL) && (strcmp(cloneCfg->Name, "A") == 0)
&& (cloneCfg->Durable != NULL) && (strcmp(cloneCfg->Durable, "B") == 0)
&& (cloneCfg->Description != NULL) && (strcmp(cloneCfg->Description, "C") == 0)
&& (cloneCfg->FilterSubject != NULL) && (strcmp(cloneCfg->FilterSubject, "D") == 0)
&& (cloneCfg->SampleFrequency != NULL) && (strcmp(cloneCfg->SampleFrequency, "E") == 0)
&& (cloneCfg->DeliverSubject != NULL) && (strcmp(cloneCfg->DeliverSubject, "F") == 0)
&& (cloneCfg->DeliverGroup != NULL) && (strcmp(cloneCfg->DeliverGroup, "G") == 0)
&& (cloneCfg->BackOffLen == 2)
&& (cloneCfg->BackOff != NULL)
&& (cloneCfg->BackOff[0] == NATS_MILLIS_TO_NANOS(50))
&& (cloneCfg->BackOff[1] == NATS_MILLIS_TO_NANOS(250)));
js_destroyConsumerConfig(cloneCfg);
JS_TEARDOWN;
}
static void
test_JetStreamPublish(void)
{
natsStatus s;
natsConnection *nc = NULL;
jsCtx *js = NULL;
natsPid pid = NATS_INVALID_PID;
jsStreamConfig cfg;
jsPubOptions opts;
jsErrCode jerr = 0;
jsPubAck *pa = NULL;
natsMsg *msg = NULL;
char datastore[256] = {'\0'};
char cmdLine[1024] = {'\0'};
char confFile[256] = {'\0'};
jsOptions o;
ENSURE_JS_VERSION(2, 3, 5);
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
_createConfFile(confFile, sizeof(confFile),
" jetstream: { domain: ABC }\n"
);
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Get context: ");
jsOptions_Init(&o);
o.Domain = "ABC";
s = natsConnection_JetStream(&js, nc, &o);
testCond(s == NATS_OK);
test("Stream config init: ");
s = jsStreamConfig_Init(&cfg);
testCond(s == NATS_OK);
test("Add stream: ");
cfg.Name = "TEST";
cfg.Subjects = (const char*[2]){"foo", "bar"};
cfg.SubjectsLen = 2;
s = js_AddStream(NULL, js, &cfg, NULL, NULL);
testCond(s == NATS_OK);
test("Publish bad args: ");
s = js_Publish(NULL, NULL, NULL, "hello", 5, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_Publish(NULL, js, NULL, "hello", 5, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_Publish(NULL, js, "", "hello", 5, NULL, &jerr);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Options init bad args: ");
s = jsPubOptions_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Publish bad ttl option: ");
s = jsPubOptions_Init(&opts);
if (s == NATS_OK)
{
opts.MaxWait = -10;
s = js_Publish(NULL, js, "foo", "hello", 5, &opts, &jerr);
}
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), "negative") != NULL));
nats_clearLastError();
test("Publish data: ");
opts.MaxWait = 3000;
s = js_Publish(NULL, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Publish data with pubAck: ");
jsPubOptions_Init(&opts);
opts.MsgId = "msg2";
s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL)
&& (strcmp(pa->Domain, "ABC") == 0)
&& (strcmp(pa->Stream, "TEST") == 0)
&& (pa->Sequence == 2)
&& !pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
// Check this is ok
jsPubAck_Destroy(NULL);
test("Publish message with same msgID: ");
s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) && pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
test("Publish with wrong expected stream: ");
jsPubOptions_Init(&opts);
opts.ExpectStream = "WRONG";
s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamNotMatchErr)) && (pa == NULL));
jerr = 0;
test("Publish with wrong expected sequence: ");
jsPubOptions_Init(&opts);
opts.ExpectLastSeq = 4;
s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL));
jerr = 0;
test("Publish with wrong expected message ID: ");
jsPubOptions_Init(&opts);
opts.ExpectLastMsgId = "WRONG";
s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastMsgIDErr)) && (pa == NULL));
jerr = 0;
test("Publish 1 msg on bar: ");
jsPubOptions_Init(&opts);
s = js_Publish(&pa, js, "bar", "hello", 5, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL)
&& (strcmp(pa->Stream, "TEST") == 0)
&& (pa->Sequence == 3)
&& !pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
test("Publish with wrong expected subj sequence: ");
jsPubOptions_Init(&opts);
// There should be 3 messages now, with "foo, 1", "foo, 2" and "bar, 3"
// We are going to send on "foo" and say that last expected msg seq on "foo"
// is 3, which is wrong, so should fail.
opts.ExpectLastSubjectSeq = 3;
s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL));
jerr = 0;
nats_clearLastError();
test("Publish with correct expected subj sequence: ");
// Now set last expected for subject to 2, and it should be ok, and the sequence will be 4.
opts.ExpectLastSubjectSeq = 2;
s = js_Publish(&pa, js, "foo", "hello", 5, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL)
&& (strcmp(pa->Stream, "TEST") == 0)
&& (pa->Sequence == 4)
&& !pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
// ---- Same than above but with PublishMsg variant
test("Recreate stream: ");
s = js_DeleteStream(js, "TEST", NULL, NULL);
IFOK(s, js_AddStream(NULL, js, &cfg, NULL, NULL));
testCond(s == NATS_OK);
test("Publish bad args: ");
s = js_PublishMsg(NULL, NULL, NULL, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishMsg(NULL, js, NULL, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishMsg(NULL, NULL, msg, NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Create msg: ");
s = natsMsg_Create(&msg, "foo", NULL, "hello", 5);
testCond(s == NATS_OK);
test("Publish bad ttl option: ");
s = jsPubOptions_Init(&opts);
if (s == NATS_OK)
{
opts.MaxWait = -10;
s = js_PublishMsg(NULL, js, msg, &opts, &jerr);
}
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), "negative") != NULL));
nats_clearLastError();
test("Publish data: ");
opts.MaxWait = 3000;
s = js_PublishMsg(NULL, js, msg, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Publish data with pubAck: ");
jsPubOptions_Init(&opts);
opts.MsgId = "msg2";
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL)
&& (strcmp(pa->Stream, "TEST") == 0)
&& (pa->Sequence == 2)
&& !pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
natsMsg_Destroy(msg);
msg = NULL;
natsMsg_Create(&msg, "foo", NULL, "hello", 5);
test("Publish message with same msgID: ");
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL) && pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
natsMsg_Destroy(msg);
msg = NULL;
natsMsg_Create(&msg, "foo", NULL, "hello", 5);
test("Publish with wrong expected stream: ");
jsPubOptions_Init(&opts);
opts.ExpectStream = "WRONG";
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamNotMatchErr)) && (pa == NULL));
jerr = 0;
natsMsg_Destroy(msg);
msg = NULL;
natsMsg_Create(&msg, "foo", NULL, "hello", 5);
test("Publish with wrong expected sequence: ");
jsPubOptions_Init(&opts);
opts.ExpectLastSeq = 4;
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL));
jerr = 0;
natsMsg_Destroy(msg);
msg = NULL;
natsMsg_Create(&msg, "foo", NULL, "hello", 5);
test("Publish with wrong expected message ID: ");
jsPubOptions_Init(&opts);
opts.ExpectLastMsgId = "WRONG";
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastMsgIDErr)) && (pa == NULL));
jerr = 0;
natsMsg_Destroy(msg);
msg = NULL;
test("Publish 1 msg on bar: ");
jsPubOptions_Init(&opts);
natsMsg_Create(&msg, "bar", NULL, "hello", 5);
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL)
&& (strcmp(pa->Stream, "TEST") == 0)
&& (pa->Sequence == 3)
&& !pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Publish with wrong expected subj sequence: ");
jsPubOptions_Init(&opts);
natsMsg_Create(&msg, "foo", NULL, "hello", 5);
// There should be 3 messages now, with "foo, 1", "foo, 2" and "bar, 3"
// We are going to send on "foo" and say that last expected msg seq on "foo"
// is 3, which is wrong, so should fail.
opts.ExpectLastSubjectSeq = 3;
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_ERR) && ((jerr == 0) || (jerr == JSStreamWrongLastSequenceErr)) && (pa == NULL));
jerr = 0;
natsMsg_Destroy(msg);
msg = NULL;
test("Publish with correct expected subj sequence: ");
natsMsg_Create(&msg, "foo", NULL, "hello", 5);
// Now set last expected for subject to 2, and it should be ok, and the sequence will be 4.
opts.ExpectLastSubjectSeq = 2;
s = js_PublishMsg(&pa, js, msg, &opts, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (pa != NULL)
&& (strcmp(pa->Stream, "TEST") == 0)
&& (pa->Sequence == 4)
&& !pa->Duplicate);
jsPubAck_Destroy(pa);
pa = NULL;
natsMsg_Destroy(msg);
msg = NULL;
JS_TEARDOWN;
remove(confFile);
}
static void
_jsPubAckErrHandler(jsCtx *js, jsPubAckErr *pae, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->sum++;
if (strcmp(natsMsg_GetData(pae->Msg), "fail1") == 0)
{
if ((pae->Err == NATS_ERR)
&& ((pae->ErrCode == 0) || (pae->ErrCode == JSStreamWrongLastMsgIDErr))
&& (strstr(pae->ErrText, "wrong last") != NULL))
{
args->status = NATS_ERR;
}
}
else if (strcmp(natsMsg_GetData(pae->Msg), "fail2") == 0)
{
// Resend only once
if (args->sum == 2)
js_PublishMsgAsync(js, &(pae->Msg), NULL);
}
else if (strcmp(natsMsg_GetData(pae->Msg), "fail3") == 0)
{
while (!args->done)
natsCondition_Wait(args->c, args->m);
// Destroy context
jsCtx_Destroy(js);
// Notify that we are done
args->closed = true;
natsCondition_Broadcast(args->c);
}
else if (strcmp(natsMsg_GetData(pae->Msg), "block") == 0)
{
while (!args->done)
natsCondition_Wait(args->c, args->m);
nats_Sleep(500);
args->closed = true;
natsCondition_Broadcast(args->c);
}
else if (strcmp(natsMsg_GetData(pae->Msg), "destroyed") == 0)
{
// Notify that we are in the callback.
args->msgReceived = true;
natsCondition_Broadcast(args->c);
// Now wait to be notified that the context was destroyed.
while (!args->closed)
natsCondition_Wait(args->c, args->m);
// Then access the message content again to make sure that message
// is still valid.
if (strcmp(natsMsg_GetData(pae->Msg), "destroyed") != 0)
args->status = NATS_ERR;
// Notify that we are done.
args->done = true;
natsCondition_Broadcast(args->c);
}
else if ((pae->Err == NATS_NO_RESPONDERS) || (pae->Err == NATS_TIMEOUT))
{
args->msgReceived = true;
natsCondition_Broadcast(args->c);
}
natsMutex_Unlock(args->m);
}
static void
test_JetStreamPublishAsync(void)
{
natsStatus s;
natsSubscription *sub= NULL;
jsOptions o;
jsStreamConfig cfg;
jsPubOptions opts;
natsMsg *msg = NULL;
natsMsg *cmsg = NULL;
natsMsg *msg1 = NULL;
natsMsg *msg2 = NULL;
const char *val = NULL;
const char **keys = NULL;
int keysCount = 0;
struct threadArg args;
int i;
bool ok1 = false;
bool ok2 = false;
natsMsgList pending;
JS_SETUP(2, 3, 3);
jsCtx_Destroy(js);
js = NULL;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Create control sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "foo");
testCond(s == NATS_OK);
test("Prepare JS options: ");
s = jsOptions_Init(&o);
if (s == NATS_OK)
{
o.PublishAsync.ErrHandler = _jsPubAckErrHandler;
o.PublishAsync.ErrHandlerClosure = &args;
}
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, &o);
testCond(s == NATS_OK);
test("Stream config init: ");
s = jsStreamConfig_Init(&cfg);
testCond(s == NATS_OK);
test("Add stream: ");
cfg.Name = "foo";
s = js_AddStream(NULL, js, &cfg, NULL, NULL);
testCond(s == NATS_OK);
test("Publish bad args: ");
s = js_PublishAsync(NULL, NULL, "hello", 5, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishAsync(js, NULL, "hello", 5, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishAsync(js, "", "hello", 5, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishAsync(NULL, "foo", "hello", 5, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("PublishAsyncComplete bad args: ");
s = js_PublishAsyncComplete(NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("PublishAsyncComplete with no pending: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
test("Publish data: ");
s = js_PublishAsync(js, "foo", "ok1", 3, NULL);
IFOK(s, js_PublishAsyncComplete(js, NULL));
testCond(s == NATS_OK);
test("Check pub msg no header and reply set: ");
s = natsSubscription_NextMsg(&cmsg, sub, 1000);
testCond((s == NATS_OK) && (cmsg != NULL)
&& !nats_IsStringEmpty(natsMsg_GetReply(cmsg))
&& (natsMsgHeader_Keys(cmsg, &keys, &keysCount) == NATS_NOT_FOUND));
natsMsg_Destroy(cmsg);
test("Publish msg (bad args): ");
s = js_PublishMsgAsync(NULL, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishMsgAsync(NULL, &msg, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Publish msg: ");
s = jsPubOptions_Init(&opts);
if (s == NATS_OK)
opts.MsgId = "msgID";
IFOK(s, natsMsg_Create(&msg, "foo", NULL, "ok2", 3));
IFOK(s, js_PublishMsgAsync(js, &msg, &opts));
// Check that library took ownership of message by checking
// that msg is now NULL.
testCond((s == NATS_OK) && (msg == NULL));
test("Check pub msg reply set: ");
s = natsSubscription_NextMsg(&cmsg, sub, 1000);
testCond((s == NATS_OK) && (cmsg != NULL)
&& !nats_IsStringEmpty(natsMsg_GetReply(cmsg)));
test("Check msg ID header set: ");
s = natsMsgHeader_Get(cmsg, jsMsgIdHdr, &val);
testCond((s == NATS_OK) && (strcmp(val, "msgID") == 0));
natsMsg_Destroy(cmsg);
val = NULL;
test("Wait for complete (bad args): ");
opts.MaxWait = -1000;
s = js_PublishAsyncComplete(js, &opts);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Wait for complete: ");
opts.MaxWait = 1000;
s = js_PublishAsyncComplete(js, &opts);
testCond(s == NATS_OK);
test("Send fails due to wrong last ID: ");
args.status = NATS_OK;
opts.MsgId = NULL;
opts.ExpectLastMsgId = "wrong";
s = natsMsg_Create(&msg, "foo", NULL, "fail1", 5);
IFOK(s, js_PublishMsgAsync(js, &msg, &opts));
testCond((s == NATS_OK) && (msg == NULL));
test("Check pub msg reply set: ");
s = natsSubscription_NextMsg(&cmsg, sub, 1000);
testCond((s == NATS_OK) && (cmsg != NULL)
&& !nats_IsStringEmpty(natsMsg_GetReply(cmsg)));
test("Check msg ID header not set: ");
s = natsMsgHeader_Get(cmsg, jsMsgIdHdr, &val);
testCond((s == NATS_NOT_FOUND) && (val == NULL));
test("Check expect last msg ID header set: ");
s = natsMsgHeader_Get(cmsg, jsExpectedLastMsgIdHdr, &val);
testCond((s == NATS_OK) && (val != NULL) && (strcmp(val, "wrong") == 0));
natsMsg_Destroy(cmsg);
val = NULL;
test("Wait for complete: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
test("Check cb got proper failure: ")
natsMutex_Lock(args.m);
s = ((args.status == NATS_ERR) && (args.sum == 1) ? NATS_OK : NATS_ERR);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Send new failed message, will be resent in cb: ");
s = js_PublishAsync(js, "foo", "fail2", 5, &opts);
testCond(s == NATS_OK);
test("Wait complete: ")
s = js_PublishAsyncComplete(js, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(args.m);
s = (args.sum == 3 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(args.m);
}
testCond(s == NATS_OK);
test("Send new failed messages which will block cb: ");
s = js_PublishAsync(js, "foo", "fail3", 5, &opts);
// Send another message, which should not be delivered to CB
// since we will destroy context from CB on releasing CB
// after fail3 msg is processed.
IFOK(s, js_PublishAsync(js, "foo", "fail4", 5, &opts));
testCond(s == NATS_OK);
test("Check complete timeout: ");
opts.MaxWait = 100;
s = js_PublishAsyncComplete(js, &opts);
testCond(s == NATS_TIMEOUT);
nats_clearLastError();
test("Release cb which will destroy context: ");
s = NATS_OK;
natsMutex_Lock(args.m);
args.done = true;
natsCondition_Broadcast(args.c);
while ((s != NATS_TIMEOUT) && !args.closed)
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check that last msg was not delivered to CB: ");
natsMutex_Lock(args.m);
// cb has seen: fail1, fail2 twice, fail3, so sum == 4
s = (args.sum == 4 ? NATS_OK: NATS_ERR);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
js = NULL;
jsOptions_Init(&o);
test("Stall wait bad args: ");
o.PublishAsync.StallWait = -10;
s = natsConnection_JetStream(&js, nc, &o);
testCond((s == NATS_INVALID_ARG) && (js == NULL));
nats_clearLastError();
test("Recreate context: ");
o.PublishAsync.MaxPending = 1;
o.PublishAsync.StallWait = 100;
o.PublishAsync.ErrHandler = _jsPubAckErrHandler;
o.PublishAsync.ErrHandlerClosure = &args;
s = natsConnection_JetStream(&js, nc, &o);
testCond(s == NATS_OK);
test("Block CB: ");
natsMutex_Lock(args.m);
args.done = false;
args.closed = false;
natsMutex_Unlock(args.m);
// Pass options so that we add an expected last msg ID which will
// cause failure.
jsPubOptions_Init(&opts);
opts.ExpectLastMsgId = "WRONG";
s = js_PublishAsync(js, "foo", "block", 5, &opts);
testCond(s == NATS_OK);
test("Send should fail due to stall: ");
s = js_PublishAsync(js, "foo", "stalled", 7, NULL);
testCond((s == NATS_TIMEOUT)
&& (strstr(nats_GetLastError(NULL), "too many outstanding") != NULL));
nats_clearLastError();
// Release CB, which will sleep a bit before returning, so that we
// have time to start a publish here that we will check gets unstalled.
// Artificially increase the stallWait so that we don't flap on Travis/etc..
natsMutex_Lock(js->mu);
js->opts.PublishAsync.StallWait = 10000;
natsMutex_Unlock(js->mu);
// should unstall the publish that we are going to make here.
natsMutex_Lock(args.m);
args.done = true;
natsCondition_Broadcast(args.c);
natsMutex_Unlock(args.m);
test("Pub will stall: ");
s = js_PublishAsync(js, "foo", "ok", 2, NULL);
testCond(s == NATS_OK);
test("Wait complete: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
test("Wait for CB to return: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.closed)
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Msg needs to be destroyed on failure: ");
natsSubscription_Destroy(sub);
natsConnection_Destroy(nc);
nc = NULL;
s = natsMsg_Create(&msg, "foo", NULL, "conclosed", 9);
IFOK(s, js_PublishMsgAsync(js, &msg, NULL));
testCond((s == NATS_CONNECTION_CLOSED) && (msg != NULL));
nats_clearLastError();
test("Msg destroy: ");
natsMsg_Destroy(msg);
testCond(true);
jsCtx_Destroy(js);
js = NULL;
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Create context: ");
s = jsOptions_Init(&o);
if (s == NATS_OK)
{
o.PublishAsync.ErrHandler = _jsPubAckErrHandler;
o.PublishAsync.ErrHandlerClosure = &args;
}
IFOK(s, natsConnection_JetStream(&js, nc, &o));
testCond((s == NATS_OK) && (js != NULL));
test("Publish async no responders: ");
s = js_PublishAsync(js, "no.responder.check", "hello", 5, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 2000);
args.msgReceived = false;
natsMutex_Unlock(args.m);
}
testCond(s == NATS_OK);
test("Enqueue message with bad subject: ");
s = natsMsg_Create(&msg, "some.subject", NULL, "hello", 5);
if (s == NATS_OK)
{
natsSubscription *rsub;
js_lock(js);
rsub = js->rsub;
js_unlock(js);
_waitSubPending(rsub, 0);
natsSub_Lock(rsub);
rsub->msgList.head = msg;
rsub->msgList.tail = msg;
rsub->msgList.msgs = 1;
rsub->msgList.bytes = natsMsg_dataAndHdrLen(msg);
natsCondition_Signal(rsub->cond);
natsSub_Unlock(rsub);
// Message is owned by subscription, do not destroy it here.
}
testCond(s == NATS_OK);
test("Publish async cb received non existent pid: ");
{
char subj[64];
snprintf(subj, sizeof(subj), "%sabcdefgh", js->rpre);
s = natsConnection_Publish(nc, subj, NULL, 0);
}
testCond(s == NATS_OK);
test("Produce failed message: ");
jsPubOptions_Init(&opts);
opts.ExpectStream = "WRONG";
natsMutex_Lock(args.m);
args.done = false;
args.closed = false;
args.msgReceived = false;
args.status = NATS_OK;
natsMutex_Unlock(args.m);
// Pass options with wrong expected stream, so pub will fail.
s = js_PublishAsync(js, "foo", "destroyed", 9, &opts);
testCond(s == NATS_OK);
test("Wait for msg in CB: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 2000);
args.msgReceived = false;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Destroy context, notify CB: ");
jsCtx_Destroy(js);
js = NULL;
natsMutex_Lock(args.m);
args.closed = true;
natsCondition_Broadcast(args.c);
natsMutex_Unlock(args.m);
testCond(true);
test("Wait for CB to return: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.done)
s = natsCondition_TimedWait(args.c, args.m, 2000);
if (s == NATS_OK)
s = args.status;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Reply subject can be set: ");
s = natsConnection_JetStream(&js, nc, NULL);
IFOK(s, natsMsg_Create(&msg, "bar", "baz", "bat", 3));
IFOK(s, js_PublishMsgAsync(js, &msg, NULL));
testCond((s == NATS_OK) && (msg == NULL));
test("Wait complete: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
test("Publish async: ");
_stopServer(pid);
pid = NATS_INVALID_PID;
s = natsMsg_Create(&msg1, "foo", NULL, "hello1", 6);
IFOK(s, natsMsg_Create(&msg2, "foo", NULL, "hello2", 6));
IFOK(s, js_PublishMsgAsync(js, &msg1, NULL));
IFOK(s, js_PublishMsgAsync(js, &msg2, NULL));
testCond((s == NATS_OK) && (msg1 == NULL) && (msg2 == NULL));
test("Get pending (bad args): ");
s = js_PublishAsyncGetPendingList(NULL, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishAsyncGetPendingList(&pending, NULL);
if (s == NATS_INVALID_ARG)
s = js_PublishAsyncGetPendingList(NULL, js);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Get pending: ");
s = js_PublishAsyncGetPendingList(&pending, js);
testCond((s == NATS_OK)
&& (pending.Msgs != NULL)
&& (pending.Count == 2));
test("Verify pending list: ");
for (i=0; i<pending.Count; i++)
{
natsMsg *msg = pending.Msgs[i];
// Cannot assume order, but we should get hello1 and hello2 in all.
if (strncmp(natsMsg_GetData(msg), "hello1", 6) == 0)
{
ok1 = true;
msg1 = msg;
pending.Msgs[i] = NULL;
}
else if (strncmp(natsMsg_GetData(msg), "hello2", 6) == 0)
ok2 = true;
}
testCond(ok1 && ok2 && (msg1 != NULL));
test("Destroy list leaves msg1 valid: ");
natsMsgList_Destroy(&pending);
// Access something from msg1 to make sure that memory is still valid
s = (strncmp(natsMsg_GetData(msg1), "hello1", 6) == 0 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
natsMsg_Destroy(msg1);
msg1 = NULL;
test("Get pending, no msg: ");
s = js_PublishAsyncGetPendingList(&pending, js);
testCond((s == NATS_NOT_FOUND) && (pending.Msgs == NULL) && (pending.Count == 0));
test("Publish async: ");
s = natsMsg_Create(&msg1, "foo", NULL, "hello1", 6);
IFOK(s, natsMsg_Create(&msg2, "foo", NULL, "hello2", 6));
IFOK(s, js_PublishMsgAsync(js, &msg1, NULL));
IFOK(s, js_PublishMsgAsync(js, &msg2, NULL));
testCond((s == NATS_OK) && (msg1 == NULL) && (msg2 == NULL));
test("Get pending: ");
s = js_PublishAsyncGetPendingList(&pending, js);
testCond((s == NATS_OK)
&& (pending.Msgs != NULL)
&& (pending.Count == 2));
msg1 = pending.Msgs[0];
msg2 = pending.Msgs[1];
test("Check that if Msgs set to NULL, no crash: ");
{
natsMsg **l = pending.Msgs;
pending.Msgs = NULL;
natsMsgList_Destroy(&pending);
free(l);
}
testCond(true);
natsMsg_Destroy(msg1);
natsMsg_Destroy(msg2);
test("Publish timeout: ");
jsCtx_Destroy(js);
js = NULL;
o.PublishAsync.ErrHandler = _jsPubAckErrHandler;
o.PublishAsync.ErrHandlerClosure = (void*) &args;
s = natsConnection_JetStream(&js, nc, &o);
if (s == NATS_OK)
{
opts.MaxWait = 100;
s = js_PublishAsync(js, "foo", "timeout", 7, &opts);
}
if (s == NATS_OK)
{
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 2000);
args.msgReceived = false;
natsMutex_Unlock(args.m);
}
testCond(s == NATS_OK);
JS_TEARDOWN;
_destroyDefaultThreadArgs(&args);
}
static void
_jsPubAckHandler(jsCtx *js, natsMsg *msg, jsPubAck *pa, jsPubAckErr *pae, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->sum++;
args->status = NATS_ERR;
switch (args->sum) {
case 1:
if ((pae == NULL) && (pa != NULL)
&& ((pa->Stream != NULL) && (strcmp(pa->Stream, "TEST") == 0))
&& (pa->Sequence == 1))
{
// Test resending message from callback...
args->status = js_PublishMsgAsync(js, &msg, NULL);
}
break;
case 2:
if ((pae == NULL) && (pa != NULL)
&& ((pa->Stream != NULL) && (strcmp(pa->Stream, "TEST") == 0))
&& (pa->Sequence == 1) && (pa->Duplicate))
{
args->status = NATS_OK;
}
break;
case 3:
if ((pa == NULL) && (pae != NULL)
&& (pae->Err == NATS_ERR) && (pae->ErrCode == JSStreamStoreFailedErr)
&& (strstr(pae->ErrText, "maximum messages exceeded") != NULL))
{
args->status = NATS_OK;
}
break;
case 4:
if ((msg != NULL) && (natsMsg_GetData(msg)[0] == '1')
&& (pa == NULL) && (pae != NULL)
&& (pae->Err == NATS_TIMEOUT) && (pae->ErrCode == 0))
{
args->status = NATS_OK;
}
break;
case 5:
if ((msg != NULL) && (natsMsg_GetData(msg)[0] == '2')
&& (pa == NULL) && (pae != NULL)
&& (pae->Err == NATS_TIMEOUT) && (pae->ErrCode == 0))
{
args->status = NATS_OK;
}
break;
case 6:
if ((msg != NULL) && (natsMsg_GetData(msg)[0] == '3')
&& (pa == NULL) && (pae != NULL)
&& (pae->Err == NATS_TIMEOUT) && (pae->ErrCode == 0))
{
args->status = NATS_OK;
}
break;
}
natsMsg_Destroy(msg);
args->done = true;
natsCondition_Broadcast(args->c);
natsMutex_Unlock(args->m);
}
static natsStatus
_checkPubAckResult(natsStatus s, struct threadArg *args)
{
if (s != NATS_OK)
return s;
natsMutex_Lock(args->m);
while ((s != NATS_TIMEOUT) && !args->done)
s = natsCondition_TimedWait(args->c, args->m, 1000);
IFOK(s, args->status);
args->done = false;
args->status = NATS_OK;
natsMutex_Unlock(args->m);
return s;
}
static void
test_JetStreamPublishAckHandler(void)
{
natsStatus s;
jsOptions o;
jsStreamConfig cfg;
jsPubOptions opts;
// natsMsg *msg = NULL;
struct threadArg args;
JS_SETUP(2, 3, 3);
jsCtx_Destroy(js);
js = NULL;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Prepare JS options: ");
s = jsOptions_Init(&o);
if (s == NATS_OK)
{
o.PublishAsync.AckHandler = _jsPubAckHandler;
o.PublishAsync.AckHandlerClosure = &args;
}
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, &o);
testCond(s == NATS_OK);
test("Add stream: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "TEST";
cfg.Subjects = (const char*[1]){"foo"};
cfg.SubjectsLen = 1;
cfg.MaxMsgs = 1;
cfg.Discard = js_DiscardNew;
s = js_AddStream(NULL, js, &cfg, NULL, NULL);
testCond(s == NATS_OK);
test("Publish async ok: ");
jsPubOptions_Init(&opts);
opts.MsgId = "msg1";
s = js_PublishAsync(js, "foo", (const void*) "ok", 2, &opts);
s = _checkPubAckResult(s, &args);
testCond(s == NATS_OK);
test("Publish async (duplicate): ");
// Message above is resent from the ack handler callback.
s = _checkPubAckResult(s, &args);
testCond(s == NATS_OK);
test("Publish async (max msgs): ");
s = js_PublishAsync(js, "foo", (const void*) "ok", 2, NULL);
s = _checkPubAckResult(s, &args);
testCond(s == NATS_OK);
_stopServer(pid);
pid = NATS_INVALID_PID;
test("Publish async with timeouts: ");
opts.MsgId = NULL;
opts.MaxWait = 250;
s = js_PublishAsync(js, "foo", (const void*) "2", 1, &opts);
opts.MaxWait = 500;
IFOK(s, js_PublishAsync(js, "foo", (const void*) "3", 1, &opts));
opts.MaxWait = 100;
IFOK(s, js_PublishAsync(js, "foo", (const void*) "1", 1, &opts));
testCond(s == NATS_OK);
test("Publish async timeout (1): ");
s = _checkPubAckResult(s, &args);
testCond(s == NATS_OK);
test("Publish async timeout (2): ");
s = _checkPubAckResult(s, &args);
testCond(s == NATS_OK);
test("Publish async timeout (3): ");
s = _checkPubAckResult(s, &args);
testCond(s == NATS_OK);
test("Ctx destroy releases timer: ");
opts.MaxWait = 250;
s = js_PublishAsync(js, "foo", (const void*) "4", 1, &opts);
IFOK(s, js_PublishAsync(js, "foo", (const void*) "5", 1, &opts));
if (s == NATS_OK)
{
js_lock(js);
js->refs++;
nats_Sleep(300);
jsCtx_Destroy(js);
js_unlock(js);
}
testCond(s == NATS_OK);
test("Check refs: ");
nats_Sleep(100);
js_lock(js);
s = (js->refs == 1 ? NATS_OK : NATS_ERR);
js_unlock(js);
testCond(s == NATS_OK);
js_release(js);
nats_Sleep(100);
js = NULL;
JS_TEARDOWN;
_destroyDefaultThreadArgs(&args);
}
static void
_jsMsgHandler(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->sum++;
if (args->control == 1)
natsMsg_Ack(msg, NULL);
else if (args->control == 3)
{
if (args->sum == 1)
natsMsg_Nak(msg, NULL);
else
{
while (!args->done)
natsCondition_Wait(args->c, args->m);
args->msgReceived = true;
}
}
if ((args->control != 2) || (args->sum == args->results[0]))
natsCondition_Broadcast(args->c);
natsMutex_Unlock(args->m);
natsMsg_Destroy(msg);
}
static void
_jsSubComplete(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->done = true;
natsCondition_Broadcast(args->c);
natsMutex_Unlock(args->m);
}
static void
_jsCreateSubThread(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsStatus s;
jsCtx *js;
jsSubOptions so;
natsSubscription *sub = NULL;
natsMutex_Lock(args->m);
js = args->js;
natsMutex_Unlock(args->m);
nats_Sleep(100);
jsSubOptions_Init(&so);
so.Stream = "TEST_CONCURRENT";
so.Config.Durable = "my_durable";
s = js_Subscribe(&sub, js, "concurrent", _jsMsgHandler, (void*) args, NULL, &so, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(args->m);
args->attached++;
args->sub = sub;
natsMutex_Unlock(args->m);
}
else
{
natsMutex_Lock(args->m);
args->detached++;
natsMutex_Unlock(args->m);
}
}
static void
_jsDrainErrCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
if ((err == NATS_NOT_FOUND)
&& (strstr(nats_GetLastError(NULL), "delete consumer") != NULL))
{
args->msgReceived = true;
natsCondition_Broadcast(args->c);
}
natsMutex_Unlock(args->m);
}
static void
test_JetStreamSubscribe(void)
{
natsStatus s;
natsOptions *ncOpts = NULL;
natsConnection *nc = NULL;
natsSubscription *sub= NULL;
jsCtx *js = NULL;
natsPid pid = NATS_INVALID_PID;
jsErrCode jerr= 0;
char datastore[256] = {'\0'};
char cmdLine[1024] = {'\0'};
natsSubscription *ackSub = NULL;
natsMsg *ack = NULL;
natsSubscription *sub2= NULL;
jsStreamConfig sc;
jsConsumerConfig cc;
jsSubOptions so;
struct threadArg args;
int i;
jsConsumerInfo *ci = NULL;
#ifndef _WIN32
char longsn[256];
#endif
natsThread *threads[10] = {NULL};
ENSURE_JS_VERSION(2, 3, 5);
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Create options: ");
s = natsOptions_Create(&ncOpts);
IFOK(s, natsOptions_SetErrorHandler(ncOpts, _jsDrainErrCb, (void*) &args));
testCond(s == NATS_OK);
test("Connect: ");
s = natsConnection_Connect(&nc, ncOpts);
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, NULL);
testCond(s == NATS_OK);
test("Sub options init (bad args): ");
s = jsSubOptions_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Create async sub (invalid args): ");
s = js_Subscribe(NULL, js, "foo", NULL, NULL, NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_Subscribe(&sub, NULL, "foo", NULL, NULL, NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_Subscribe(&sub, js, NULL, NULL, NULL, NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_Subscribe(&sub, js, "", NULL, NULL, NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_Subscribe(&sub, js, "foo", NULL, NULL, NULL, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0));
nats_clearLastError();
test("Create, no stream exists: ");
s = js_Subscribe(&sub, js, "foo", _dummyMsgHandler, NULL, NULL, NULL, &jerr);
testCond((s != NATS_OK) && (sub == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrNoStreamMatchesSubject) != NULL));
nats_clearLastError();
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
s = js_Publish(NULL, js, "foo", "msg1", 4, NULL, &jerr);
IFOK(s, js_Publish(NULL, js, "foo", "msg2", 4, NULL, &jerr));
IFOK(s, js_Publish(NULL, js, "foo", "msg3", 4, NULL, &jerr));
testCond(s == NATS_OK);
test("Create sub to check lib sends ACKs: ");
s = natsConnection_SubscribeSync(&ackSub, nc, "$JS.ACK.TEST.>");
testCond(s == NATS_OK);
test("Subscribe, no options: ");
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, &args, NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msgs received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 3))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check acks sent: ");
// Sub should have its own autoAck CB, so usrCb should be != NULL
natsMutex_Lock(sub->mu);
s = (sub->jsi->usrCb != NULL ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sub->mu);
for (i=0; (s == NATS_OK) && (i < 3); i++)
{
s = natsSubscription_NextMsg(&ack, ackSub, 1000);
if (s == NATS_OK)
{
// Make sure this was not a sync call...
s = (natsMsg_GetReply(ack) == NULL ? NATS_OK : NATS_ERR);
}
natsMsg_Destroy(ack);
ack = NULL;
}
testCond(s == NATS_OK);
test("Create sub with manual ack: ");
natsSubscription_Destroy(sub);
sub = NULL;
natsMutex_Lock(args.m);
args.control = 1;
args.sum = 0;
natsMutex_Unlock(args.m);
jsSubOptions_Init(&so);
so.ManualAck = true;
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msgs received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 3))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check acks sent: ");
// Sub should have NOT have its own autoAck CB, so usrCb should be NULL
natsMutex_Lock(sub->mu);
s = (sub->jsi->usrCb == NULL ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sub->mu);
for (i=0; (s == NATS_OK) && (i < 3); i++)
{
s = natsSubscription_NextMsg(&ack, ackSub, 1000);
natsMsg_Destroy(ack);
ack = NULL;
}
testCond(s == NATS_OK);
test("Check no auto-ack behavior: ");
s = natsSubscription_NextMsg(&ack, ackSub, 150);
testCond((s == NATS_TIMEOUT) && (ack == NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Create sub with auto-ack: ");
natsMutex_Lock(args.m);
args.control = 3;
args.sum = 0;
natsMutex_Unlock(args.m);
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Config.DeliverPolicy = js_DeliverLast;
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*)&args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msgs received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum < 1))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check NAck sent: ");
s = natsSubscription_NextMsg(&ack, ackSub, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(ack);
ack = NULL;
test("Check no auto-ack: ");
s = natsSubscription_NextMsg(&ack, ackSub, 100);
testCond((s == NATS_TIMEOUT) && (ack == NULL));
nats_clearLastError();
s = NATS_OK;
natsMutex_Lock(args.m);
args.done = true;
natsCondition_Broadcast(args.c);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 1000);
args.done = false;
args.msgReceived = false;
natsMutex_Unlock(args.m);
test("Check auto-ack sent: ");
s = natsSubscription_NextMsg(&ack, ackSub, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(ack);
ack = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Create queue sub: ");
natsMutex_Lock(args.m);
args.control = 0;
args.sum = 0;
natsMutex_Unlock(args.m);
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Queue = "queue";
so.Config.Durable = "qdurable";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*)&args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msgs received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 3))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check acks sent: ");
for (i=0; (s == NATS_OK) && (i < 3); i++)
{
s = natsSubscription_NextMsg(&ack, ackSub, 1000);
natsMsg_Destroy(ack);
ack = NULL;
}
testCond(s == NATS_OK);
test("Add second member with binding: ");
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Queue = "queue";
so.Consumer = "qdurable";
s = js_Subscribe(&sub2, js, "foo", _jsMsgHandler, (void*)&args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub2 != NULL) && (jerr == 0));
// Wait a bit and make sure no new message is delivered
test("No new message: ");
natsMutex_Lock(args.m);
natsCondition_TimedWait(args.c, args.m, 250);
s = (args.sum == 3 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Attached consumer not destroyed on unsub: ");
s = natsSubscription_Unsubscribe(sub2);
IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "qdurable", NULL, &jerr));
testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
natsSubscription_Destroy(sub2);
sub2 = NULL;
test("No auto-ack for AckNone: ");
natsMutex_Lock(args.m);
args.control = 0;
args.sum = 0;
args.done = false;
natsMutex_Unlock(args.m);
jsSubOptions_Init(&so);
so.Config.DeliverPolicy = js_DeliverAll;
so.Config.AckPolicy = js_AckNone;
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msgs received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 3))
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check no ack was sent: ");
natsMutex_Lock(sub->mu);
// We should not have set an internal autoAck CB, so usrCb should be NULL.
s = (sub->jsi->usrCb == NULL ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sub->mu);
IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 300));
testCond((s == NATS_TIMEOUT) && (ack == NULL));
nats_clearLastError();
test("Check user setting onComplete is ok: ");
s = natsSubscription_SetOnCompleteCB(sub, _jsSubComplete, (void*)&args);
testCond(s == NATS_OK);
test("Wait for complete: ");
natsSubscription_Destroy(sub);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.done)
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
#ifndef _WIN32
test("Create stream with long subject: ");
for (i=0; i<((int)sizeof(longsn))-1; i++)
longsn[i] = 'a';
longsn[i]='\0';
jsStreamConfig_Init(&sc);
sc.Name = longsn;
sc.Subjects = (const char*[1]){"baz"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, NULL);
testCond(s == NATS_OK);
test("Create sub to check lib sends ACKs: ");
natsSubscription_Destroy(ackSub);
ackSub = NULL;
{
char tmp[512];
snprintf(tmp, sizeof(tmp), "$JS.ACK.%s.>", longsn);
s = natsConnection_SubscribeSync(&ackSub, nc, tmp);
}
testCond(s == NATS_OK);
test("Create sub with auto-ack: ");
sub = NULL;
natsMutex_Lock(args.m);
args.sum = 0;
args.control = 0;
natsMutex_Unlock(args.m);
s = js_Subscribe(&sub, js, "baz", _jsMsgHandler, &args, NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Send 1 msg: ");
s = js_Publish(NULL, js, "baz", "hello", 5, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Check msg received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 1))
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check ack sent: ");
s = natsSubscription_NextMsg(&ack, ackSub, 1000);
natsMsg_Destroy(ack);
ack = NULL;
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
#endif
sub = NULL;
test("Create stream with several subjects: ");
jsStreamConfig_Init(&sc);
sc.Name = "MULTIPLE_SUBJS";
sc.Subjects = (const char*[2]){"sub.1", "sub.2"};
sc.SubjectsLen = 2;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Create consumer with filter: ");
jsConsumerConfig_Init(&cc);
cc.Durable = "dur";
cc.DeliverSubject = "push.dur.sub.2";
cc.FilterSubject = "sub.2";
s = js_AddConsumer(NULL, js, "MULTIPLE_SUBJS", &cc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Subscribe subj != filter: ");
jsSubOptions_Init(&so);
so.Stream = "MULTIPLE_SUBJS";
so.Consumer = "dur";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, &args, NULL, &so, &jerr);
testCond((s == NATS_ERR) && (sub == NULL)
&& (strstr(nats_GetLastError(NULL), "filter subject") != NULL));
nats_clearLastError();
test("Subject not required when binding to stream/consumer: ");
s = js_Subscribe(&sub, js, NULL, _jsMsgHandler, &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
natsSubscription_Destroy(sub);
sub = NULL;
test("Create consumer for pull: ");
jsConsumerConfig_Init(&cc);
cc.Durable = "dur2";
s = js_AddConsumer(NULL, js, "MULTIPLE_SUBJS", &cc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Subscribe subj not a pull: ");
jsSubOptions_Init(&so);
so.Stream = "MULTIPLE_SUBJS";
so.Consumer = "dur2";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, &args, NULL, &so, &jerr);
testCond((s == NATS_ERR) && (sub == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrPullSubscribeRequired) != NULL));
nats_clearLastError();
test("Create stream for concurrent test: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST_CONCURRENT";
sc.Subjects = (const char*[1]){"concurrent"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Start concurrent creation of subs: ");
natsMutex_Lock(args.m);
args.attached = 0;
args.detached = 0;
args.control = 0;
args.js = js;
natsMutex_Unlock(args.m);
s = NATS_OK;
for (i=0; ((s == NATS_OK) && (i<10)); i++)
s = natsThread_Create(&threads[i], _jsCreateSubThread, (void*) &args);
testCond(s == NATS_OK);
test("Wait for threads to return: ");
for (i=0; i<10; i++)
{
natsThread_Join(threads[i]);
natsThread_Destroy(threads[i]);
}
testCond(true);
test("Only 1 should be started, 9 should have failed: ");
natsMutex_Lock(args.m);
s = ((args.attached == 1) && (args.detached == 9)) ? NATS_OK : NATS_ERR;
sub = args.sub;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
natsSubscription_Destroy(ackSub);
ackSub = NULL;
test("Create consumer: ");
jsSubOptions_Init(&so);
so.Config.Durable = "delcons1";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Unsub deletes consumer: ");
s = natsSubscription_Unsubscribe(sub);
IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "delcons1", NULL, &jerr));
testCond((s == NATS_NOT_FOUND) && (ci == NULL) && (jerr == JSConsumerNotFoundErr)
&& (nats_GetLastError(NULL) == NULL));
natsSubscription_Destroy(sub);
sub = NULL;
test("Create consumer: ");
jsSubOptions_Init(&so);
so.Config.Durable = "delcons2";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Drain deletes consumer: ");
s = natsSubscription_Drain(sub);
IFOK(s, natsSubscription_WaitForDrainCompletion(sub, 1000));
IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "delcons2", NULL, &jerr));
testCond((s == NATS_NOT_FOUND) && (ci == NULL) && (jerr == JSConsumerNotFoundErr)
&& (nats_GetLastError(NULL) == NULL));
natsSubscription_Destroy(sub);
sub = NULL;
test("Create consumer: ");
jsSubOptions_Init(&so);
so.Config.Durable = "delcons2sync";
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Drain deletes consumer: ");
s = natsSubscription_Drain(sub);
for (i=0; i<3; i++)
{
natsMsg *msg = NULL;
IFOK(s, natsSubscription_NextMsg(&msg, sub, 1000));
IFOK(s, natsMsg_Ack(msg, NULL));
natsMsg_Destroy(msg);
msg = NULL;
}
IFOK(s, natsSubscription_WaitForDrainCompletion(sub, 1000));
IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "delcons2sync", NULL, &jerr));
testCond((s == NATS_NOT_FOUND) && (ci == NULL) && (jerr == JSConsumerNotFoundErr)
&& (nats_GetLastError(NULL) == NULL));
natsSubscription_Destroy(sub);
sub = NULL;
test("Create consumer: ");
jsSubOptions_Init(&so);
so.Config.Durable = "delcons3";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Delete consumer: ");
s = js_DeleteConsumer(js, "TEST", "delcons3", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Unsub report error: ");
s = natsSubscription_Unsubscribe(sub);
testCond((s != NATS_OK)
&& (strstr(nats_GetLastError(NULL), "not found") != NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
natsMutex_Lock(args.m);
args.msgReceived = false;
natsMutex_Unlock(args.m);
test("Create consumer: ");
jsSubOptions_Init(&so);
so.Config.Durable = "delcons4";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Delete consumer: ");
s = js_DeleteConsumer(js, "TEST", "delcons4", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Drain report error: ");
s = natsSubscription_Drain(sub);
if (s == NATS_OK)
{
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
}
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
test("Destroy sub does not delete consumer: ");
jsSubOptions_Init(&so);
so.Config.Durable = "delcons5";
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
if (s == NATS_OK)
{
natsSubscription_Destroy(sub);
sub = NULL;
s = js_GetConsumerInfo(&ci, js, "TEST", "delcons5", NULL, &jerr);
}
testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Queue and HB is invalid: ");
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Queue = "queue";
so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(2);
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrNoHeartbeatForQueueSub) != NULL));
nats_clearLastError();
test("Queue and FlowControl is invalid: ");
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Queue = "queue";
so.Config.FlowControl = true;
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrNoFlowControlForQueueSub) != NULL));
nats_clearLastError();
test("Queue name can't contain dots: ");
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Queue = "queue.name";
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), "cannot contain '.'") != NULL));
nats_clearLastError();
test("Queue group serves as durable: ");
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Queue = "qgroup";
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", "qgroup", NULL, &jerr));
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0)
&& (ci != NULL) && (ci->Config != NULL)
&& (ci->Config->Durable != NULL)
&& (strcmp(ci->Config->Durable, "qgroup") == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("Durable name invalid (push): ");
jsSubOptions_Init(&so);
so.Config.DeliverSubject = "bar";
so.Config.Durable = "dur.invalid";
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), "cannot contain '.'") != NULL));
nats_clearLastError();
test("Durable name invalid (pull): ");
s = js_PullSubscribe(&sub, js, "foo", "dur.invalid", NULL, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), "cannot contain '.'") != NULL));
nats_clearLastError();
JS_TEARDOWN;
natsOptions_Destroy(ncOpts);
_destroyDefaultThreadArgs(&args);
}
static void
test_JetStreamSubscribeSync(void)
{
natsStatus s;
natsSubscription *sub= NULL;
jsErrCode jerr= 0;
natsSubscription *ackSub = NULL;
natsMsg *ack = NULL;
jsStreamConfig sc;
int i;
natsMsg *msgs[4];
natsMsg *msg = NULL;
const char *consName;
jsConsumerInfo *ci = NULL;
jsConsumerInfo *ci2 = NULL;
jsMsgMetaData *meta = NULL;
jsSubOptions so;
JS_SETUP(2, 7, 0);
test("Create sync sub (invalid args): ");
s = js_SubscribeSync(NULL, js, "foo", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_SubscribeSync(&sub, NULL, "foo", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_SubscribeSync(&sub, js, NULL, NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_SubscribeSync(&sub, js, "", NULL, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0));
nats_clearLastError();
test("Create, no stream exists: ");
s = js_SubscribeSync(&sub, js, "foo", NULL, NULL, &jerr);
testCond((s != NATS_OK) && (sub == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrNoStreamMatchesSubject) != NULL));
nats_clearLastError();
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
s = js_Publish(NULL, js, "foo", "msg1", 4, NULL, &jerr);
IFOK(s, js_Publish(NULL, js, "foo", "msg2", 4, NULL, &jerr));
IFOK(s, js_Publish(NULL, js, "foo", "msg3", 4, NULL, &jerr));
IFOK(s, js_Publish(NULL, js, "foo", "msg4", 4, NULL, &jerr));
testCond(s == NATS_OK);
test("Create sub to check ACKs: ");
s = natsConnection_SubscribeSync(&ackSub, nc, "$JS.ACK.TEST.>");
testCond(s == NATS_OK);
test("Subscribe, no options: ");
s = js_SubscribeSync(&sub, js, "foo", NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msgs received: ");
for (i=0; (s == NATS_OK) && (i<4); i++)
s = natsSubscription_NextMsg(&msgs[i], sub, 1000);
testCond(s == NATS_OK);
test("Check get meta data (bad args): ");
s = natsMsg_GetMetaData(NULL, NULL);
if (s == NATS_INVALID_ARG)
s = natsMsg_GetMetaData(NULL, msgs[0]);
if (s == NATS_INVALID_ARG)
s = natsMsg_GetMetaData(&meta, NULL);
testCond((s == NATS_INVALID_ARG) && (meta == NULL));
nats_clearLastError();
test("Check get meta: ");
s = natsMsg_GetMetaData(&meta, msgs[0]);
testCond((s == NATS_OK) && (meta != NULL)
&& (strcmp(meta->Stream, "TEST") == 0)
&& (strlen(meta->Consumer) > 0)
&& (meta->NumPending == 3)
&& (meta->NumDelivered == 1)
&& (meta->Sequence.Consumer == 1)
&& (meta->Sequence.Stream == 1)
&& (meta->Timestamp > 0));
jsMsgMetaData_Destroy(meta);
meta = NULL;
// Check this is ok
jsMsgMetaData_Destroy(NULL);
test("Ack: ");
s = natsMsg_Ack(msgs[0], NULL);
IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000));
if (s == NATS_OK)
{
s = (strncmp(
natsMsg_GetData(ack),
jsAckAck,
natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR);
natsMsg_Destroy(ack);
ack = NULL;
}
testCond(s == NATS_OK);
test("Second ack does nothing: ");
s = natsMsg_Ack(msgs[0], NULL);
IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 300));
testCond(s == NATS_TIMEOUT);
natsMsg_Destroy(msgs[0]);
test("AckSync: ");
s = natsMsg_AckSync(msgs[1], NULL, &jerr);
IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000));
if (s == NATS_OK)
{
s = (strncmp(
natsMsg_GetData(ack),
jsAckAck,
natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR);
natsMsg_Destroy(ack);
ack = NULL;
}
natsMsg_Destroy(msgs[1]);
testCond((s == NATS_OK) && (jerr == 0));
test("Nack: ");
s = natsMsg_Nak(msgs[2], NULL);
IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000));
if (s == NATS_OK)
{
s = (strncmp(
natsMsg_GetData(ack),
jsAckNak,
natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR);
natsMsg_Destroy(ack);
ack = NULL;
}
natsMsg_Destroy(msgs[2]);
testCond((s == NATS_OK) && (jerr == 0));
test("Nack msg resent: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK) && (msg != NULL));
// Should be able to send InProgress more than once.
for (i=0; i<2; i++)
{
test("InProgress: ");
s = natsMsg_InProgress(msg, NULL);
IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000));
if (s == NATS_OK)
{
s = (strncmp(
natsMsg_GetData(ack),
jsAckInProgress,
natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR);
natsMsg_Destroy(ack);
ack = NULL;
}
testCond(s == NATS_OK);
}
natsMsg_Destroy(msg);
msg = NULL;
test("Term: ");
s = natsMsg_Term(msgs[3], NULL);
IFOK(s, natsSubscription_NextMsg(&ack, ackSub, 1000));
if (s == NATS_OK)
{
s = (strncmp(
natsMsg_GetData(ack),
jsAckTerm,
natsMsg_GetDataLength(ack)) == 0 ? NATS_OK : NATS_ERR);
natsMsg_Destroy(ack);
ack = NULL;
}
natsMsg_Destroy(msgs[3]);
testCond(s == NATS_OK);
test("Ack failure (bad args): ");
s = natsMsg_Ack(NULL, NULL);
if (s == NATS_INVALID_ARG)
s = natsMsg_AckSync(NULL, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = natsMsg_Nak(NULL, NULL);
if (s == NATS_INVALID_ARG)
s = natsMsg_InProgress(NULL, NULL);
if (s == NATS_INVALID_ARG)
s = natsMsg_Term(NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Ack failure (not bound): ");
natsMsg_Create(&msg, "foo", NULL, "test", 4);
s = natsMsg_Ack(msg, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_AckSync(msg, NULL, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_Nak(msg, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_InProgress(msg, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_Term(msg, NULL);
testCond((s == NATS_ILLEGAL_STATE)
&& (strstr(nats_GetLastError(NULL), jsErrMsgNotBound) != NULL));
nats_clearLastError();
test("Get Meta failure (not bound): ")
s = natsMsg_GetMetaData(&meta, msg);
testCond((s == NATS_ILLEGAL_STATE) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrMsgNotBound) != NULL));
nats_clearLastError();
test("Ack failure (no reply): ");
// artificially bind to existing sub
msg->sub = sub;
s = natsMsg_Ack(msg, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_AckSync(msg, NULL, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_Nak(msg, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_InProgress(msg, NULL);
if (s == NATS_ILLEGAL_STATE)
s = natsMsg_Term(msg, NULL);
testCond((s == NATS_ILLEGAL_STATE)
&& (strstr(nats_GetLastError(NULL), jsErrMsgNotJS) != NULL));
nats_clearLastError();
test("Get Meta failure (no reply): ");
s = natsMsg_GetMetaData(&meta, msg);
testCond((s == NATS_ILLEGAL_STATE) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrMsgNotJS) != NULL));
nats_clearLastError();
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta failure (not JS Ack): ");
s = natsMsg_Create(&msg, "foo", "not.a.js.ack", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_ERR) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid meta") != NULL));
nats_clearLastError();
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta failure (terminal dot): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK.TEST.CONSUMER.1.1.1.1629415486698860000.3.", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_ERR) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid meta") != NULL));
nats_clearLastError();
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta failure (too small): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK.TEST.CONSUMER.1.1.1.1629415486698860000", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_ERR) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid meta") != NULL));
nats_clearLastError();
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta failure (too small v2): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK.HUB.accHash.TEST.CONSUMER.1.1.1.1629415486698860000", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_ERR) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid meta") != NULL));
nats_clearLastError();
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta failure (invalid content): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK.TEST.CONSUMER.and.some.bad.other.things", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_ERR) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid meta") != NULL));
nats_clearLastError();
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta failure (invalid content v2): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK.HUB.accHash.TEST.CONSUMER.and.some.bad.other.things", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_ERR) && (meta == NULL)
&& (strstr(nats_GetLastError(NULL), "invalid meta") != NULL));
nats_clearLastError();
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta v2: ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK.HUB.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.random", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_OK) && (meta != NULL)
&& (strcmp(meta->Domain, "HUB") == 0)
&& (strcmp(meta->Stream, "TEST") == 0)
&& (strcmp(meta->Consumer, "CONSUMER") == 0)
&& (meta->NumDelivered == 1)
&& (meta->Sequence.Stream == 2)
&& (meta->Sequence.Consumer == 3)
&& (meta->Timestamp == 1629415486698860000)
&& (meta->NumPending == 4));
jsMsgMetaData_Destroy(meta);
meta = NULL;
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta v2 (empty domain): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK._.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.random", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_OK) && (meta != NULL)
&& (meta->Domain == NULL)
&& (strcmp(meta->Stream, "TEST") == 0)
&& (strcmp(meta->Consumer, "CONSUMER") == 0)
&& (meta->NumDelivered == 1)
&& (meta->Sequence.Stream == 2)
&& (meta->Sequence.Consumer == 3)
&& (meta->Timestamp == 1629415486698860000)
&& (meta->NumPending == 4));
jsMsgMetaData_Destroy(meta);
meta = NULL;
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta v2 (no failure with appended tokens): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK._.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.random.new_one", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_OK) && (meta != NULL)
&& (meta->Domain == NULL)
&& (strcmp(meta->Stream, "TEST") == 0)
&& (strcmp(meta->Consumer, "CONSUMER") == 0)
&& (meta->NumDelivered == 1)
&& (meta->Sequence.Stream == 2)
&& (meta->Sequence.Consumer == 3)
&& (meta->Timestamp == 1629415486698860000)
&& (meta->NumPending == 4));
jsMsgMetaData_Destroy(meta);
meta = NULL;
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Get Meta v2 (no failure with appended tokens, malformed ok): ");
s = natsMsg_Create(&msg, "foo", "$JS.ACK._.accHash.TEST.CONSUMER.1.2.3.1629415486698860000.4.", NULL, 0);
if (s == NATS_OK)
msg->sub = sub;
IFOK(s, natsMsg_GetMetaData(&meta, msg));
testCond((s == NATS_OK) && (meta != NULL)
&& (meta->Domain == NULL)
&& (strcmp(meta->Stream, "TEST") == 0)
&& (strcmp(meta->Consumer, "CONSUMER") == 0)
&& (meta->NumDelivered == 1)
&& (meta->Sequence.Stream == 2)
&& (meta->Sequence.Consumer == 3)
&& (meta->Timestamp == 1629415486698860000)
&& (meta->NumPending == 4));
jsMsgMetaData_Destroy(meta);
meta = NULL;
msg->sub = NULL;
natsMsg_Destroy(msg);
msg = NULL;
test("Send msg: ");
s = js_Publish(NULL, js, "foo", "block", 5, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Drain does not delete consumer: ");
consName = sub->jsi->consumer;
s = natsSubscription_Drain(sub);
nats_Sleep(500);
IFOK(s, js_GetConsumerInfo(&ci, js, "TEST", consName, NULL, &jerr));
testCond((s == NATS_OK) && (ci != NULL) && (jerr == 0));
jsConsumerInfo_Destroy(ci);
ci = NULL;
test("Wait for drain to complete: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
natsMsg_Destroy(msg);
IFOK(s, natsSubscription_WaitForDrainCompletion(sub, 1000));
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
test("Create sub with ack-none: ");
jsSubOptions_Init(&so);
so.Config.AckPolicy = js_AckNone;
so.Config.DeliverPolicy = js_DeliverLast;
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msg received: ");
msg = NULL;
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_OK);
test("Check ack: ");
s = natsMsg_InProgress(msg, NULL);
IFOK(s, natsMsg_Nak(msg, NULL));
IFOK(s, natsMsg_Ack(msg, NULL));
IFOK(s, natsMsg_Term(msg, NULL));
testCond(s == NATS_OK);
test("Check no ack sent: ");
natsMsg_Destroy(msg);
msg = NULL;
s = natsSubscription_NextMsg(&msg, ackSub, 100);
testCond((s == NATS_TIMEOUT) && (msg == NULL));
natsSubscription_Destroy(sub);
sub = NULL;
natsSubscription_Destroy(ackSub);
ackSub = NULL;
test("Test InactiveThreshold (bad value): ");
jsSubOptions_Init(&so);
so.Config.InactiveThreshold = NATS_MILLIS_TO_NANOS(-100);
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), "invalid InactiveThreshold") != NULL));
nats_clearLastError();
test("Create normal sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "test");
testCond((s == NATS_OK) && (sub != NULL));
test("sub get info (invalid args): ");
s = natsSubscription_GetConsumerInfo(NULL, sub, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = natsSubscription_GetConsumerInfo(&ci, NULL, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (jerr == 0));
nats_clearLastError();
test("sub get info (invalid sub): ");
s = natsSubscription_GetConsumerInfo(&ci, sub, NULL, &jerr);
testCond((s == NATS_INVALID_SUBSCRIPTION) && (ci == NULL) && (jerr == 0));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Create sub: ");
jsSubOptions_Init(&so);
so.Config.InactiveThreshold = NATS_MILLIS_TO_NANOS(50);
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Get consumer info: ");
s = natsSubscription_GetConsumerInfo(&ci, sub, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL)
&& (ci->Config->InactiveThreshold == NATS_MILLIS_TO_NANOS(50)));
test("Close conn: ");
jsCtx_Destroy(js);
js = NULL;
natsConnection_Destroy(nc);
nc = NULL;
testCond(true);
nats_Sleep(150);
test("Check consumer gone: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_JetStream(&js, nc, NULL));
IFOK(s, js_GetConsumerInfo(&ci2, js, "TEST", ci->Name, NULL, &jerr));
testCond((s == NATS_NOT_FOUND) && (jerr == JSConsumerNotFoundErr) && (ci2 == NULL));
jsConsumerInfo_Destroy(ci);
natsSubscription_Destroy(sub);
JS_TEARDOWN;
}
static void
test_JetStreamSubscribeConfigCheck(void)
{
natsStatus s;
natsSubscription *sub= NULL;
const char *name = NULL;
jsErrCode jerr= 0;
char durName[64];
char testName[64];
jsStreamConfig sc;
jsConsumerConfig cc;
int i;
int64_t backOffListOf3[3] = {1, 2, 3};
int64_t backOffListOf2[2] = {1, 2};
JS_SETUP(2, 9, 0);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
for (i=0; i<17; i++)
{
jsSubOptions so1;
jsSubOptions so2;
natsNUID_Next(durName, sizeof(durName));
jsSubOptions_Init(&so1);
jsSubOptions_Init(&so2);
switch (i)
{
case 0:
{
name = "description";
so1.Config.Description = "a";
so2.Config.Description = "b";
break;
}
case 1:
{
name = "deliver policy";
so1.Config.DeliverPolicy = js_DeliverAll;
so2.Config.DeliverPolicy = js_DeliverLast;
break;
}
case 2:
{
name = "optional start sequence";
so1.Config.OptStartSeq = 1;
so2.Config.OptStartSeq = 10;
break;
}
case 3:
{
name = "optional start time";
so1.Config.OptStartTime = 1000000000;
so2.Config.OptStartTime = 2000000000;
break;
}
case 4:
{
name = "ack wait";
so1.Config.AckWait = NATS_SECONDS_TO_NANOS(10);
so2.Config.AckWait = NATS_SECONDS_TO_NANOS(15);
break;
}
case 5:
{
name = "max deliver";
so1.Config.MaxDeliver = 3;
so2.Config.MaxDeliver = 5;
break;
}
case 6:
{
name = "replay policy";
so1.Config.ReplayPolicy = js_ReplayOriginal;
so2.Config.ReplayPolicy = js_ReplayInstant;
break;
}
case 7:
{
name = "max waiting";
so1.Config.MaxWaiting = 10;
so2.Config.MaxWaiting = 20;
break;
}
case 8:
{
name = "max ack pending";
so1.Config.MaxAckPending = 10;
so2.Config.MaxAckPending = 20;
break;
}
case 9:
{
name = "sample frequency";
so1.Config.SampleFrequency = "100%";
so2.Config.SampleFrequency = "50%";
break;
}
case 10:
{
name = "backoff";
so1.Config.BackOff = backOffListOf3;
so1.Config.BackOffLen = 3;
so1.Config.MaxDeliver = 5;
so2.Config.BackOff = backOffListOf2;
so2.Config.BackOffLen = 2;
so2.Config.MaxDeliver = 5;
break;
}
case 11:
{
name = "headers only";
so1.Config.HeadersOnly = true;
// Not setting it for the 2nd subscribe call should fail.
break;
}
case 12:
{
name = "max request batch";
so1.Config.MaxRequestBatch = 100;
so2.Config.MaxRequestBatch = 200;
break;
}
case 13:
{
name = "max request expires";
so1.Config.MaxRequestExpires = NATS_SECONDS_TO_NANOS(1);
so2.Config.MaxRequestExpires = NATS_SECONDS_TO_NANOS(2);
break;
}
case 14:
{
name = "inactive threshold";
so1.Config.InactiveThreshold = NATS_SECONDS_TO_NANOS(1);
so2.Config.InactiveThreshold = NATS_SECONDS_TO_NANOS(2);
break;
}
case 15:
{
name = "replicas";
so1.Config.Replicas = 1;
so2.Config.Replicas = 3;
break;
}
case 16:
{
name = "memory storage";
so1.Config.MemoryStorage = true;
// Not setting it for the 2nd subscribe call should fail.
break;
}
}
snprintf(testName, sizeof(testName), "Check %s: ", name);
test(testName);
s = js_PullSubscribe(&sub, js, "foo", durName, NULL, &so1, &jerr);
if ((s == NATS_OK) && (jerr == 0))
{
natsSubscription *nsub = NULL;
s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, &so2, &jerr);
if ((s != NATS_OK) && (nsub == NULL) && (strstr(nats_GetLastError(NULL), name) != NULL))
{
s = NATS_OK;
nats_clearLastError();
}
else
s = NATS_ERR;
}
testCond(s == NATS_OK);
natsSubscription_Unsubscribe(sub);
natsSubscription_Destroy(sub);
sub = NULL;
}
for (i=0; i<4; i++)
{
jsConsumerConfig cc;
natsInbox *inbox = NULL;
natsInbox_Create(&inbox);
natsNUID_Next(durName, sizeof(durName));
jsConsumerConfig_Init(&cc);
switch (i)
{
case 0: cc.AckPolicy = js_AckAll; break;
case 1: cc.RateLimit = 10; break;
case 2: cc.FlowControl = false; break;
case 3: cc.Heartbeat = NATS_SECONDS_TO_NANOS(10); break;
}
cc.Durable = durName;
cc.DeliverSubject = inbox;
s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr);
if ((s == NATS_OK) && (jerr == 0))
{
jsSubOptions so;
jsSubOptions_Init(&so);
so.Config.Durable = durName;
switch (i)
{
case 0: name="ack policy"; so.Config.AckPolicy = js_AckNone; break;
case 1: name="rate limit"; so.Config.RateLimit = 100; break;
case 2: name="flow control"; so.Config.FlowControl = true; break;
case 3: name="heartbeat"; so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(20); break;
}
snprintf(testName, sizeof(testName), "Check %s: ", name);
test(testName);
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
if ((s != NATS_OK) && (sub == NULL) && (strstr(nats_GetLastError(NULL), name) != NULL))
{
s = NATS_OK;
nats_clearLastError();
}
else
s = NATS_ERR;
testCond(s == NATS_OK);
}
natsInbox_Destroy(inbox);
inbox = NULL;
}
// Verify that we don't fail if user did not set it.
for (i=0; i<14; i++)
{
natsSubscription *nsub = NULL;
jsSubOptions so;
jsSubOptions_Init(&so);
switch (i)
{
case 0: name = "description"; so.Config.Description = "a"; break;
case 1: name = "deliver policy"; so.Config.DeliverPolicy = js_DeliverLast; break;
case 2: name = "optional start sequence"; so.Config.OptStartSeq = 10; break;
case 3: name = "optional start time"; so.Config.OptStartTime = 1000000000; break;
case 4: name = "ack wait"; so.Config.AckWait = NATS_SECONDS_TO_NANOS(10); break;
case 5: name = "max deliver"; so.Config.MaxDeliver = 3; break;
case 6: name = "replay policy"; so.Config.ReplayPolicy = js_ReplayInstant; break;
case 7: name = "max waiting"; so.Config.MaxWaiting = 10; break;
case 8: name = "max ack pending"; so.Config.MaxAckPending = 10; break;
case 9:
{
name = "backoff";
so.Config.BackOff = backOffListOf3;
so.Config.BackOffLen = 3;
so.Config.MaxDeliver = 4;
break;
}
case 10: name = "max request batch"; so.Config.MaxRequestBatch = 100; break;
case 11: name = "max request expires"; so.Config.MaxRequestExpires = NATS_SECONDS_TO_NANOS(2); break;
case 12: name = "inactive threshold"; so.Config.InactiveThreshold = NATS_SECONDS_TO_NANOS(2); break;
case 13: name = "replicas"; so.Config.Replicas = 1; break;
}
natsNUID_Next(durName, sizeof(durName));
snprintf(testName, sizeof(testName), "Check %s: ", name);
test(testName);
s = js_PullSubscribe(&sub, js, "foo", durName, NULL, &so, &jerr);
if (s == NATS_OK)
{
// If not explicitly asked by the user, we are ok
s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, NULL, &jerr);
natsSubscription_Unsubscribe(nsub);
natsSubscription_Destroy(nsub);
nsub = NULL;
}
testCond(s == NATS_OK);
natsSubscription_Unsubscribe(sub);
natsSubscription_Destroy(sub);
sub = NULL;
}
// If the option is the same as the server default, it is not an error either.
for (i=0; i<5; i++)
{
natsSubscription *nsub = NULL;
jsSubOptions so;
jsSubOptions_Init(&so);
switch (i)
{
case 1: name = "default deliver policy"; so.Config.DeliverPolicy = js_DeliverAll; break;
case 4: name = "default ack wait"; so.Config.AckWait = NATS_SECONDS_TO_NANOS(30); break;
case 6: name = "default replay policy"; so.Config.ReplayPolicy = js_ReplayInstant; break;
case 7: name = "default max waiting"; so.Config.MaxWaiting = 512; break;
case 8: name = "default max ack pending"; so.Config.MaxAckPending = 65536; break;
}
natsNUID_Next(durName, sizeof(durName));
snprintf(testName, sizeof(testName), "Check %s: ", name);
test(testName);
s = js_PullSubscribe(&sub, js, "foo", durName, NULL, NULL, &jerr);
if (s == NATS_OK)
{
s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, &so, &jerr);
natsSubscription_Unsubscribe(nsub);
natsSubscription_Destroy(nsub);
nsub = NULL;
}
testCond(s == NATS_OK);
natsSubscription_Unsubscribe(sub);
natsSubscription_Destroy(sub);
sub = NULL;
}
for (i=0; i<5; i++)
{
jsSubOptions so;
jsSubOptions_Init(&so);
switch (i)
{
case 0: name = "deliver policy"; so.Config.DeliverPolicy = js_DeliverNew; break;
case 1: name = "ack wait"; so.Config.AckWait = NATS_SECONDS_TO_NANOS(31); break;
case 2: name = "replay policy"; so.Config.ReplayPolicy = js_ReplayOriginal; break;
case 3: name = "max waiting"; so.Config.MaxWaiting = 513; break;
case 4: name = "max ack pending"; so.Config.MaxAckPending = 2; break;
}
natsNUID_Next(durName, sizeof(durName));
snprintf(testName, sizeof(testName), "Check %s: ", name);
test(testName);
s = js_PullSubscribe(&sub, js, "foo", durName, NULL, NULL, &jerr);
if (s == NATS_OK)
{
natsSubscription *nsub = NULL;
// First time it was created with defaults and the
// second time a change is attempted, so it is an error.
s = js_PullSubscribe(&nsub, js, "foo", durName, NULL, &so, &jerr);
if ((s != NATS_OK) && (nsub == NULL) && (strstr(nats_GetLastError(NULL), name) != NULL))
{
s = NATS_OK;
nats_clearLastError();
}
else
s = NATS_ERR;
}
testCond(s == NATS_OK);
natsSubscription_Unsubscribe(sub);
natsSubscription_Destroy(sub);
sub = NULL;
}
// Check that binding to a durable (without specifying durable option) works
test("Create durable: ");
jsConsumerConfig_Init(&cc);
cc.Durable = "BindDurable";
cc.DeliverSubject = "bar";
s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr);
if (s == NATS_OK)
{
jsSubOptions so;
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Consumer = "BindDurable";
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
natsSubscription_Destroy(sub);
}
testCond(s == NATS_OK);
JS_TEARDOWN;
}
static void
_jsSeqMismatch(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
if (err == NATS_MISMATCH)
args->done = true;
else if (err == NATS_MISSED_HEARTBEAT)
args->results[args->control]++;
natsCondition_Broadcast(args->c);
natsMutex_Unlock(args->m);
}
static void
_setMsgReply(natsConnection *nc, natsMsg **msg, void* closure)
{
natsMsg *m = NULL;
natsMsg_Create(&m, (*msg)->subject, (const char*) closure, (*msg)->data, (*msg)->dataLen);
natsMsg_Destroy(*msg);
*msg = m;
natsConn_setFilter(nc, NULL);
}
static void
test_JetStreamSubscribeIdleHearbeat(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsSubscription *sub= NULL;
jsCtx *js = NULL;
natsPid pid = NATS_INVALID_PID;
jsErrCode jerr= 0;
char datastore[256] = {'\0'};
char cmdLine[1024] = {'\0'};
jsStreamConfig sc;
jsSubOptions so;
natsMsg *msg;
struct threadArg args;
natsOptions *opts = NULL;
natsSubscription *nsub = NULL;
char *inbox = NULL;
int i;
jsConsumerSequenceMismatch csm;
jsConsumerConfig cc;
ENSURE_JS_VERSION(2, 3, 5);
s = natsOptions_Create(&opts);
IFOK(s, _createDefaultThreadArgsForCbTests(&args));
if (s != NATS_OK)
FAIL("Unable to setup test");
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect: ");
s = natsOptions_SetErrorHandler(opts, _jsSeqMismatch, (void*) &args);
IFOK(s, natsOptions_SetReconnectWait(opts, 50));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, NULL);
testCond(s == NATS_OK);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
s = js_Publish(NULL, js, "foo", "msg1", 4, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Subscribe async: ");
jsSubOptions_Init(&so);
so.Config.Durable = "dur1";
so.Config.Heartbeat = 150*1000000;
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msg received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 1))
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check HB received: ");
nats_Sleep(300);
natsSubAndLdw_Lock(sub);
s = (sub->jsi->mismatch.dseq == 1 ? NATS_OK : NATS_ERR);
natsSubAndLdw_Unlock(sub);
testCond(s == NATS_OK);
test("Check HB is not given to app: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum == 1))
s = natsCondition_TimedWait(args.c, args.m, 300);
natsMutex_Unlock(args.m);
testCond(s == NATS_TIMEOUT);
test("Check get mismatch (bad args): ");
// Create regular sub to check invalid args.
s = natsConnection_SubscribeSync(&nsub, nc, "normal_sub");
IFOK(s, natsSubscription_GetSequenceMismatch(NULL, NULL));
if (s == NATS_INVALID_ARG)
s = natsSubscription_GetSequenceMismatch(NULL, sub);
if (s == NATS_INVALID_ARG)
s = natsSubscription_GetSequenceMismatch(&csm, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Not a JS sub: ")
s = natsSubscription_GetSequenceMismatch(&csm, nsub);
testCond((s == NATS_INVALID_SUBSCRIPTION)
&& (strstr(nats_GetLastError(NULL), jsErrNotAJetStreamSubscription) != NULL));
nats_clearLastError();
test("Check get mismatch returns not found: ");
s = natsSubscription_GetSequenceMismatch(&csm, sub);
testCond((s == NATS_NOT_FOUND) && (nats_GetLastError(NULL) == NULL));
test("Send new message: ");
s = js_Publish(NULL, js, "foo", "msg2", 4, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Check seq mismatch: ");
natsSub_Lock(sub);
inbox = sub->subject;
natsSub_Unlock(sub);
// Cheat by pretending that the server sends message seq 3, while client received only seq 1.
natsConn_setFilterWithClosure(nc, _setMsgReply, (void*) "$JS.ACK.TEST.dur1.1.3.3.1624472520000000000.0");
s = natsConnection_PublishString(nc, inbox, "msg3");
// Wait for past the next HB and we should get an async error
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.done)
s = natsCondition_TimedWait(args.c, args.m, 300);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check that notification is sent only once: ");
natsMutex_Lock(args.m);
args.done = false;
natsMutex_Unlock(args.m);
// Wait for more than 1 HB, the callback should not
// be invoked because we have notified once and
// the mismatch still exist.
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.done)
s = natsCondition_TimedWait(args.c, args.m, 300);
natsMutex_Unlock(args.m);
testCond(s == NATS_TIMEOUT);
test("Check get mismatch: ");
// Server will say that the consumer seq is at 2
s = natsSubscription_GetSequenceMismatch(&csm, sub);
testCond((s == NATS_OK)
&& (csm.Stream == 3)
&& (csm.ConsumerClient == 3)
&& (csm.ConsumerServer == 2));
test("Check mismatch suppression cleared: ");
// Send real message so that all clears up
s = js_Publish(NULL, js, "foo", "msg3", 4, NULL, &jerr);
nats_Sleep(300);
natsSubAndLdw_Lock(sub);
s = (sub->jsi->ssmn == false ? NATS_OK : NATS_ERR);
natsSubAndLdw_Unlock(sub);
testCond(s == NATS_OK);
test("Skip again: ");
natsConn_setFilterWithClosure(nc, _setMsgReply, (void*) "$JS.ACK.TEST.dur1.1.4.4.1624482520000000000.0");
s = natsConnection_PublishString(nc, inbox, "msg4");
testCond(s == NATS_OK);
test("Check async cb invoked: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.done)
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check HB timer reports missed HB: ");
_stopServer(pid);
// The HB interval is 150ms, but we check *2, so be a bit more
// than 300ms to avoid flapping.
nats_Sleep(500);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.results[0] == 0))
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
natsMutex_Lock(args.m);
args.sum = 0;
args.done = false;
args.control = 1;
natsMutex_Unlock(args.m);
test("Subscribe sync: ");
jsSubOptions_Init(&so);
so.Config.Durable = "dur2";
so.Config.Heartbeat = 150*1000000;
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msgs received: ");
for (i=0; (s == NATS_OK) && (i<3); i++)
{
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, natsMsg_Ack(msg, NULL));
natsMsg_Destroy(msg);
msg = NULL;
}
testCond(s == NATS_OK);
test("Check HB received: ");
nats_Sleep(300);
natsMutex_Lock(sub->mu);
s = (sub->jsi->mismatch.dseq == 3 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sub->mu);
testCond(s == NATS_OK);
test("Check HB is not given to app: ");
s = natsSubscription_NextMsg(&msg, sub, 250);
testCond((s == NATS_TIMEOUT) && (msg == NULL));
nats_clearLastError();
test("Skip: ");
natsSub_Lock(sub);
inbox = sub->subject;
natsSub_Unlock(sub);
natsConn_setFilterWithClosure(nc, _setMsgReply, (void*) "$JS.ACK.TEST.dur2.1.4.4.1624482520000000000.0");
s = natsConnection_PublishString(nc, inbox, "msg4");
testCond(s == NATS_OK);
// For sync subs, we should not get async error
test("No async error: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.done)
s = natsCondition_TimedWait(args.c, args.m, 300);
natsMutex_Unlock(args.m);
testCond(s == NATS_TIMEOUT);
nats_clearLastError();
test("NextMsg reports error: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_MISMATCH) && (msg == NULL));
nats_clearLastError();
test("NextMsg again is ok: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK) && (msg != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check get mismatch: ");
s = natsSubscription_GetSequenceMismatch(&csm, sub);
testCond((s == NATS_OK)
&& (csm.Stream == 4)
&& (csm.ConsumerClient == 4)
&& (csm.ConsumerServer == 3));
test("Check mismatch suppression cleared: ");
// Send real message so that all clears up
s = js_Publish(NULL, js, "foo", "msg4", 4, NULL, &jerr);
nats_Sleep(300);
natsSubAndLdw_Lock(sub);
s = (sub->jsi->ssmn == false && sub->jsi->sm == false ? NATS_OK : NATS_ERR);
natsSubAndLdw_Unlock(sub);
testCond(s == NATS_OK);
test("Skip again: ");
natsConn_setFilterWithClosure(nc, _setMsgReply, (void*) "$JS.ACK.TEST.dur1.1.5.5.1624492520000000000.0");
s = natsConnection_PublishString(nc, inbox, "msg5");
testCond(s == NATS_OK);
test("NextMsg reports error: ");
nats_Sleep(300);
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_MISMATCH) && (msg == NULL));
nats_clearLastError();
test("Check HB timer reports missed HB: ");
s = NATS_OK;
_stopServer(pid);
nats_Sleep(500);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.results[1] == 0))
s = natsCondition_TimedWait(args.c, args.m, 1000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
test("Create consumer with HB: ");
jsConsumerConfig_Init(&cc);
cc.Durable = "qdur";
cc.DeliverSubject = "bar";
cc.DeliverGroup = "queue";
cc.Heartbeat = 150*1000000;
s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Queue sub against this consumer: ");
jsSubOptions_Init(&so);
so.Queue = "queue";
so.Stream = "TEST";
so.Consumer = "qdur";
so.Config.DeliverSubject = "bar";
s = js_Subscribe(&sub, js, "bar", _jsMsgHandler, (void*)&args, NULL, &so, &jerr);
if (s == NATS_ERR)
s = js_SubscribeSync(&sub, js, "bar", NULL, &so, &jerr);
testCond((s == NATS_ERR) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrNoHeartbeatForQueueSub) != NULL));
nats_clearLastError();
natsSubscription_Destroy(nsub);
JS_TEARDOWN;
_destroyDefaultThreadArgs(&args);
natsOptions_Destroy(opts);
}
static void
test_JetStreamSubscribeFlowControl(void)
{
natsStatus s;
natsSubscription *sub= NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
jsSubOptions so;
natsMsg *msg = NULL;
struct threadArg args;
natsSubscription *nsub = NULL;
int i;
int total = 20000;
char *data = NULL;
char *subj = NULL;
natsBuffer *buf = NULL;
JS_SETUP(2, 3, 3);
data = malloc(100*1024);
if (data == NULL)
FAIL("Unable to allocate data");
if (valgrind)
total = 2000;
if (data == NULL)
FAIL("Unable to allocate data");
for (i=0; i<1024; i++)
data[i] = 'A';
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
args.control = 2;
args.results[0] = total;
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[2]){"foo", "bar"};
sc.SubjectsLen = 2;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
for (i=0; (s == NATS_OK) && (i<total); i++)
s = js_PublishAsync(js, "foo", data, 1024, NULL);
testCond(s == NATS_OK);
test("Wait complete: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
test("Create sub to check for FC: ");
s = natsConnection_SubscribeSync(&nsub, nc, "$JS.FC.TEST.>");
testCond((s == NATS_OK) && (nsub != NULL));
test("FC requires HB: ");
jsSubOptions_Init(&so);
so.Config.FlowControl = true;
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_ERR) && (sub == NULL) && (jerr == JSConsumerWithFlowControlNeedsHeartbeatsErr));
nats_clearLastError();
test("Subscribe async: ");
jsSubOptions_Init(&so);
so.Config.FlowControl = true;
so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(1);
s = js_Subscribe(&sub, js, "foo", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msg received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != total))
s = natsCondition_TimedWait(args.c, args.m, 10000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
test("Check FC responses were sent: ");
s = natsSubscription_NextMsg(&msg, nsub, 2000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
natsSubscription_Destroy(nsub);
nsub = NULL;
test("Create sub to check for FC: ");
s = natsConnection_SubscribeSync(&nsub, nc, "$JS.FC.TEST.>");
testCond((s == NATS_OK) && (nsub != NULL));
test("Subscribe sync: ");
jsSubOptions_Init(&so);
so.Config.FlowControl = true;
so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(1);
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check msg received: ");
for (i=0; (s == NATS_OK) && (i<total); i++)
{
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, natsMsg_Ack(msg, NULL));
natsMsg_Destroy(msg);
msg = NULL;
}
testCond(s == NATS_OK);
test("Check FC responses were sent: ");
s = natsSubscription_NextMsg(&msg, nsub, 2000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
free(data);
natsSubscription_Destroy(sub);
sub = NULL;
natsSubscription_Destroy(nsub);
nsub = NULL;
test("Create sub to check for FC: ");
s = natsConnection_SubscribeSync(&nsub, nc, "$JS.FC.TEST.>");
testCond((s == NATS_OK) && (nsub != NULL));
test("Subscribe: ");
jsSubOptions_Init(&so);
so.Config.FlowControl = true;
so.Config.Heartbeat = NATS_SECONDS_TO_NANOS(1);
s = js_Subscribe(&sub, js, "bar", _jsMsgHandler, (void*) &args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Send hb header: ");
natsSub_Lock(sub);
subj = sub->subject;
natsSub_Unlock(sub);
s = natsBuf_Create(&buf, 256);
IFOK(s, natsBuf_Append(buf, "HPUB ", -1));
IFOK(s, natsBuf_Append(buf, subj, -1));
IFOK(s, natsBuf_Append(buf, " 76 76\r\n", -1));
IFOK(s, natsBuf_Append(buf, "NATS/1.0 100 Idle Heartbeat\r\n", -1));
IFOK(s, natsBuf_Append(buf, "Nats-Consumer-Stalled: $JS.FC.TEST.fc.reply\r\n\r\n\r\n", -1));
natsConn_Lock(nc);
IFOK(s, natsSock_WriteFully(&nc->sockCtx, natsBuf_Data(buf), natsBuf_Len(buf)));
natsConn_Unlock(nc);
testCond(s == NATS_OK);
test("Check FC reply due to HB header: ");
IFOK(s, natsSubscription_NextMsg(&msg, nsub, 1000));
testCond(s == NATS_OK);
natsBuf_Destroy(buf);
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
natsSubscription_Destroy(nsub);
_destroyDefaultThreadArgs(&args);
JS_TEARDOWN;
}
static void
_jsPubThread(void *closure)
{
jsCtx *js = (jsCtx*) closure;
int i;
nats_Sleep(300);
for (i=0; i<5; i++)
js_Publish(NULL, js, "foo", "hello", 5, NULL, NULL);
}
static void
_sendToPullSub(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMsg *msg = NULL;
char *subj = NULL;
natsSubscription *sub = NULL;
uint64_t id = 0;
natsStatus s;
nats_Sleep(250);
natsMutex_Lock(args->m);
sub = args->sub;
natsSub_Lock(sub);
id = sub->jsi->fetchID;
if (args->sum != 0)
id = (uint64_t) args->sum;
if (nats_asprintf(&subj, "%.*s%" PRIu64, (int) strlen(sub->subject)-1, sub->subject, id) < 0)
s = NATS_NO_MEMORY;
else
s = natsMsg_create(&msg, subj, (int) strlen(subj),
NULL, 0, args->string, (int) strlen(args->string), (int) strlen(args->string));
natsSub_Unlock(sub);
IFOK(s, natsConnection_PublishMsg(args->nc, msg));
natsMutex_Unlock(args->m);
natsMsg_Destroy(msg);
free(subj);
}
static void
_fetchRequest(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsSubscription *sub = NULL;
jsFetchRequest fr;
natsStatus s;
natsMsgList list;
int64_t start;
natsMutex_Lock(args->m);
sub = args->sub;
natsMutex_Unlock(args->m);
jsFetchRequest_Init(&fr);
// With current messages, for a MaxBytes of 150, we should get 2 messages,
// for a total size of 142.
fr.Batch = 10;
fr.MaxBytes = 150;
fr.Expires = NATS_SECONDS_TO_NANOS(2);
start = nats_Now();
s = natsSubscription_FetchRequest(&list, sub, &fr);
if (s == NATS_OK)
{
int i;
int total = 0;
for (i=0; i<list.Count; i++)
{
total += list.Msgs[i]->wsz;
natsMsg_AckSync(list.Msgs[i], NULL, NULL);
}
if ((total > 150) || (list.Count != 2) || ((nats_Now() - start) >= 1900))
s = NATS_ERR;
natsMsgList_Destroy(&list);
}
natsMutex_Lock(args->m);
args->status = s;
natsMutex_Unlock(args->m);
}
static void
_dropIdleHBs(natsConnection *nc, natsMsg **msg, void* closure)
{
int ct = 0;
if (natsMsg_GetDataLength(*msg) > 0)
return;
if (!natsMsg_isJSCtrl(*msg, &ct))
return;
if (ct != jsCtrlHeartbeat)
return;
natsMsg_Destroy(*msg);
*msg = NULL;
}
static void
_dropTimeoutProto(natsConnection *nc, natsMsg **msg, void* closure)
{
const char *val = NULL;
if (natsMsgHeader_Get(*msg, STATUS_HDR, &val) != NATS_OK)
return;
natsMsg_Destroy(*msg);
*msg = NULL;
}
static void
test_JetStreamSubscribePull(void)
{
natsStatus s;
natsSubscription *sub= NULL;
natsMsg *msg = NULL;
jsErrCode jerr= 0;
natsMsgList list;
jsStreamConfig sc;
jsSubOptions so;
struct threadArg args;
int64_t start, dur;
int i;
natsThread *t = NULL;
jsConsumerConfig cc;
jsFetchRequest fr;
natsSubscription *sub2 = NULL;
natsSubscription *sub3 = NULL;
JS_SETUP(2, 9, 2);
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Create pull sub (invalid args): ");
s = js_PullSubscribe(NULL, js, "foo", "dur", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_PullSubscribe(&sub, NULL, "foo", "dur", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_PullSubscribe(&sub, js, NULL, "dur", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_PullSubscribe(&sub, js, "", "dur", NULL, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0));
nats_clearLastError();
test("Create reg sync sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "foo");
testCond((s == NATS_OK) && (sub != NULL));
test("Fetch bad args: ");
s = natsSubscription_Fetch(NULL, sub, 1, 1000, NULL);
if (s == NATS_INVALID_ARG)
s = natsSubscription_Fetch(&list, NULL, 1, 1000, NULL);
if (s == NATS_INVALID_ARG)
s = natsSubscription_Fetch(&list, sub, 0, 1000, NULL);
if (s == NATS_INVALID_ARG)
s = natsSubscription_Fetch(&list, sub, -1, 1000, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Fetch bad timeout: ");
s = natsSubscription_Fetch(&list, sub, 1, 0, NULL);
if (s == NATS_INVALID_TIMEOUT)
s = natsSubscription_Fetch(&list, sub, 1, -1, NULL);
testCond(s == NATS_INVALID_TIMEOUT);
nats_clearLastError();
test("Fetch bad sub: ");
s = natsSubscription_Fetch(&list, sub, 1, 1000, NULL);
testCond((s == NATS_INVALID_SUBSCRIPTION)
&& (strstr(nats_GetLastError(NULL), jsErrNotAPullSubscription) != NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Create reg async sub: ");
s = natsConnection_Subscribe(&sub, nc, "foo", _dummyMsgHandler, NULL);
testCond((s == NATS_OK) && (sub != NULL));
test("Fetch bad sub: ");
s = natsSubscription_Fetch(&list, sub, 1, 1000, NULL);
testCond((s == NATS_INVALID_SUBSCRIPTION)
&& (strstr(nats_GetLastError(NULL), jsErrNotAPullSubscription) != NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("AckNone ok: ");
jsSubOptions_Init(&so);
so.Config.AckPolicy = js_AckNone;
s = js_PullSubscribe(&sub, js, "foo", "ackNone", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
natsSubscription_Unsubscribe(sub);
natsSubscription_Destroy(sub);
sub = NULL;
test("AckAll ok: ");
jsSubOptions_Init(&so);
so.Config.AckPolicy = js_AckAll;
s = js_PullSubscribe(&sub, js, "foo", "ackAll", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
natsSubscription_Unsubscribe(sub);
natsSubscription_Destroy(sub);
sub = NULL;
test("Create push consumer: ");
jsConsumerConfig_Init(&cc);
cc.Durable = "push_dur";
cc.DeliverSubject = "push.deliver";
s = js_AddConsumer(NULL, js, "TEST", &cc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Try create pull sub from push consumer: ");
s = js_PullSubscribe(&sub, js, "foo", "push_dur", NULL, NULL, &jerr);
testCond((s == NATS_ERR) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrPullSubscribeToPushConsumer) != NULL));
nats_clearLastError();
test("Create pull bound failure: ");
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Consumer = "bar";
s = js_PullSubscribe(&sub, js, "foo", "bar", NULL, &so, &jerr);
testCond((s == NATS_NOT_FOUND) && (sub == NULL) && (jerr == JSConsumerNotFoundErr));
nats_clearLastError();
test("Create pull sub: ");
jsSubOptions_Init(&so);
so.Config.MaxAckPending = 10;
so.Config.AckWait = NATS_MILLIS_TO_NANOS(300);
s = js_PullSubscribe(&sub, js, "foo", "dur", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Can't call NextMsg: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_INVALID_SUBSCRIPTION) && (msg == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrNotApplicableToPullSub) != NULL));
nats_clearLastError();
test("Fetch, no msg avail, timeout: ");
start = nats_Now();
s = natsSubscription_Fetch(&list, sub, 1, 500, &jerr);
dur = nats_Now() - start;
testCond((s == NATS_TIMEOUT) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0)
&& (dur >= 450) && (dur <= 600));
nats_clearLastError();
test("Send a message: ");
s = js_Publish(NULL, js, "foo", "hello", 5, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Fetch batch 1: ");
start = nats_Now();
s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr);
dur = nats_Now() - start;
testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0)
&& (dur <= 500));
test("Ack: ");
for (i=0; (s == NATS_OK) && (i<list.Count); i++)
s = natsMsg_AckSync(list.Msgs[i], NULL, &jerr);
natsMsgList_Destroy(&list);
testCond((s == NATS_OK) && (jerr == 0));
test("Send a message: ");
s = js_Publish(NULL, js, "foo", "hello", 5, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Fetch batch more than avail: ");
start = nats_Now();
s = natsSubscription_Fetch(&list, sub, 5, 1000, &jerr);
dur = nats_Now() - start;
testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0)
&& (dur <= 500));
test("Ack: ");
for (i=0; (s == NATS_OK) && (i<list.Count); i++)
s = natsMsg_Ack(list.Msgs[i], NULL);
natsMsgList_Destroy(&list);
testCond(s == NATS_OK);
test("Start thread to send: ");
s = natsThread_Create(&t, _jsPubThread, (void*) js);
testCond(s == NATS_OK);
test("Fetch: ");
{
int got = 0;
while ((s == NATS_OK) && (got != 5))
{
s = natsSubscription_Fetch(&list, sub, 5-got, 2000, &jerr);
if (s == NATS_OK)
got += list.Count;
// Destroy msgs without ack'ing them.
natsMsgList_Destroy(&list);
}
testCond(s == NATS_OK);
}
// Wait for AckWait and messages should be redelivered.
nats_Sleep(500);
test("Fetch get redelivered msgs: ");
s = natsSubscription_Fetch(&list, sub, 5, 2000, &jerr);
testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 5) && (jerr == 0));
test("Ack: ");
for (i=0; (s == NATS_OK) && (i<list.Count); i++)
s = natsMsg_Ack(list.Msgs[i], NULL);
natsMsgList_Destroy(&list);
testCond(s == NATS_OK);
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
test("Receive msg with header no data: ");
s = natsMsg_create(&msg, sub->subject, (int) strlen(sub->subject), NULL, 0,
"NATS/1.0\r\nk:v\r\n\r\n", 17, 17);
IFOK(s, natsConnection_PublishMsg(nc, msg));
IFOK(s, natsSubscription_Fetch(&list, sub, 1, 1000, &jerr));
testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0));
natsMsg_Destroy(msg);
msg = NULL;
natsMsgList_Destroy(&list);
// Make sure there is no more pending.
_waitSubPending(sub, 0);
test("Msg with 404 status present before fetch: ");
s = natsMsg_create(&msg, sub->subject, (int) strlen(sub->subject), NULL, 0,
"NATS/1.0 404 No Messages\r\n\r\n", 28, 28);
IFOK(s, natsConnection_PublishMsg(nc, msg));
if (s == NATS_OK)
_waitSubPending(sub, 1);
IFOK(s, natsSubscription_Fetch(&list, sub, 1, 100, &jerr));
testCond((s == NATS_TIMEOUT) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0));
nats_clearLastError();
natsMsg_Destroy(msg);
msg = NULL;
natsMsgList_Destroy(&list);
// Since we faked the 404, the server is going to send a 408 when the request
// expires, so wait for it to be sent.
nats_Sleep(200);
test("Fetch returns on 408: ");
natsMutex_Lock(args.m);
args.nc = nc;
args.sub = sub;
args.string = "NATS/1.0 408 Request Timeout\r\n\r\n";
natsMutex_Unlock(args.m);
start = nats_Now();
s = natsThread_Create(&t, _sendToPullSub, (void*) &args);
IFOK(s, natsSubscription_Fetch(&list, sub, 1, 1000, &jerr));
dur = nats_Now() - start;
// Since we wait 250ms to publish, it will take aound 250ms
testCond((s == NATS_TIMEOUT) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0)
&& (dur < 500));
nats_clearLastError();
natsMsgList_Destroy(&list);
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
test("Fetch gets 503: ");
natsMutex_Lock(args.m);
args.string = "NATS/1.0 503\r\n\r\n";
natsMutex_Unlock(args.m);
s = natsThread_Create(&t, _sendToPullSub, (void*) &args);
IFOK(s, natsSubscription_Fetch(&list, sub, 1, 10000, &jerr));
testCond((s == NATS_NO_RESPONDERS) && (list.Msgs == NULL) && (list.Count == 0) && (jerr == 0));
nats_clearLastError();
natsMsg_Destroy(msg);
msg = NULL;
natsMsgList_Destroy(&list);
natsThread_Join(t);
natsThread_Destroy(t);
natsSubscription_Destroy(sub);
sub = NULL;
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST2";
sc.Subjects = (const char*[4]){"bar", "baz", "bat", "box"};
sc.SubjectsLen = 4;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Pull with max waiting: ");
jsSubOptions_Init(&so);
so.Config.MaxWaiting = 2;
s = js_PullSubscribe(&sub, js, "bar", "pullmaxwaiting", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Fill requests: ");
// Send requests manually to use the max requests
s = natsConnection_SubscribeSync(&sub2, nc, "my.pull.cons.inbox1");
IFOK(s, natsConnection_SubscribeSync(&sub3, nc, "my.pull.cons.inbox2"));
IFOK(s, natsConnection_PublishRequestString(nc, "$JS.API.CONSUMER.MSG.NEXT.TEST2.pullmaxwaiting", "my.pull.cons.inbox1", "{\"batch\":1,\"expires\":1000000000}"));
IFOK(s, natsConnection_PublishRequestString(nc, "$JS.API.CONSUMER.MSG.NEXT.TEST2.pullmaxwaiting", "my.pull.cons.inbox1", "{\"batch\":1,\"expires\":1000000000}"));
testCond(s == NATS_OK);
test("Max waiting error: ");
s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr);
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "Exceeded") != NULL));
nats_clearLastError();
natsSubscription_Destroy(sub2);
natsSubscription_Destroy(sub3);
natsSubscription_Destroy(sub);
sub = NULL;
test("Max request batch: ");
jsSubOptions_Init(&so);
so.Config.MaxRequestBatch = 2;
s = js_PullSubscribe(&sub, js, "baz", "max-request-batch", NULL, &so, &jerr);
IFOK(s, natsSubscription_Fetch(&list, sub, 10, 1000, &jerr));
testCond((s != NATS_OK) && (list.Count == 0) && (list.Msgs == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), "MaxRequestBatch of 2") != NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Max request expires: ");
jsSubOptions_Init(&so);
so.Config.MaxRequestExpires = NATS_MILLIS_TO_NANOS(50);
s = js_PullSubscribe(&sub, js, "baz", "max-request-expires", NULL, &so, &jerr);
IFOK(s, natsSubscription_Fetch(&list, sub, 10, 1000, &jerr));
testCond((s != NATS_OK) && (list.Count == 0) && (list.Msgs == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), "MaxRequestExpires of 50ms") != NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Ephemeral pull allowed (NULL): ");
s = js_PullSubscribe(&sub, js, "bar", NULL, NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Send a message: ");
s = js_Publish(NULL, js, "bar", "hello", 5, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Msgs received: ");
s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr);
testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0));
natsMsgList_Destroy(&list);
natsSubscription_Destroy(sub);
sub = NULL;
test("Ephemeral pull allowed (empty): ");
s = js_PullSubscribe(&sub, js, "bar", "", NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Msgs received: ");
s = natsSubscription_Fetch(&list, sub, 1, 1000, &jerr);
testCond((s == NATS_OK) && (list.Msgs != NULL) && (list.Count == 1) && (jerr == 0));
natsMsgList_Destroy(&list);
test("Fetch returns before 408: ");
natsConn_setFilter(nc, _dropTimeoutProto);
start = nats_Now();
s = natsSubscription_Fetch(&list, sub, 1, 1000, NULL);
dur = nats_Now() - start;
testCond((s == NATS_TIMEOUT) && (list.Count == 0) && (dur >= 600));
nats_clearLastError();
test("Next Fetch waits for proper timeout: ");
nats_Sleep(100);
natsConn_setFilter(nc, NULL);
natsMutex_Lock(args.m);
args.nc = nc;
args.sub = sub;
args.string = "NATS/1.0 408 Request Timeout\r\n\r\n";
// Will make the 408 sent to a subject ID with 1 while we are likely at 2 or above.
// So this will be considered a "late" 408 timeout and should be ignored.
args.sum = 1;
natsMutex_Unlock(args.m);
s = natsThread_Create(&t, _sendToPullSub, (void*) &args);
start = nats_Now();
IFOK(s, natsSubscription_Fetch(&list, sub, 1, 1000, NULL));
dur = nats_Now() - start;
testCond((s == NATS_TIMEOUT) && (list.Count == 0) && (dur >= 600));
nats_clearLastError();
natsThread_Join(t);
natsThread_Destroy(t);
t = NULL;
natsSubscription_Destroy(sub);
sub = NULL;
test("jsFetchRequest init bad args: ");
s = jsFetchRequest_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Create pull consumer with MaxRequestBytes: ");
jsSubOptions_Init(&so);
so.Config.MaxRequestMaxBytes = 1024;
s = js_PullSubscribe(&sub, js, "bat", "max-request-bytes", NULL, &so, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
jsFetchRequest_Init(&fr);
test("FetchRequest bad args: ");
s = natsSubscription_FetchRequest(NULL, sub, &fr);
if (s == NATS_INVALID_ARG)
s = natsSubscription_FetchRequest(&list, NULL, &fr);
if (s == NATS_INVALID_ARG)
s = natsSubscription_FetchRequest(&list, sub, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("FetchRequest no expiration err: ");
jsFetchRequest_Init(&fr);
// If NoWait is false, then Expires must be set.
fr.Batch = 1;
s = natsSubscription_FetchRequest(&list, sub, &fr);
testCond((s == NATS_INVALID_TIMEOUT && (list.Count == 0) && (list.Msgs == NULL)));
nats_clearLastError();
test("Batch must be set: ");
jsFetchRequest_Init(&fr);
fr.MaxBytes = 100;
fr.Expires = NATS_SECONDS_TO_NANOS(1);
s = natsSubscription_FetchRequest(&list, sub, &fr);
testCond((s == NATS_INVALID_ARG) && (list.Count == 0) && (list.Msgs == NULL));
nats_clearLastError();
test("MaxBytes must be > 0: ");
jsFetchRequest_Init(&fr);
fr.Batch = 1;
fr.MaxBytes = -100;
fr.Expires = NATS_SECONDS_TO_NANOS(1);
s = natsSubscription_FetchRequest(&list, sub, &fr);
testCond((s == NATS_INVALID_ARG) && (list.Count == 0) && (list.Msgs == NULL));
nats_clearLastError();
test("Requesting more than allowed max bytes: ");
jsFetchRequest_Init(&fr);
fr.Batch = 1;
fr.MaxBytes = 2048;
fr.Expires = NATS_SECONDS_TO_NANOS(1);
s = natsSubscription_FetchRequest(&list, sub, &fr);
testCond((s == NATS_ERR) && (list.Count == 0) && (list.Msgs == NULL)
&& (strstr(nats_GetLastError(NULL), "Exceeded MaxRequestMaxBytes") != NULL));
nats_clearLastError();
test("No concurrent call: ");
natsMutex_Lock(args.m);
args.sub = sub;
args.status = NATS_OK;
natsMutex_Unlock(args.m);
s = natsThread_Create(&t, _fetchRequest, (void*) &args);
if (s == NATS_OK)
{
nats_Sleep(250);
jsFetchRequest_Init(&fr);
fr.Batch = 1;
fr.Expires = NATS_SECONDS_TO_NANOS(1);
s = natsSubscription_FetchRequest(&list, sub, &fr);
}
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), jsErrConcurrentFetchNotAllowed) != NULL));
nats_clearLastError();
s = NATS_OK;
test("Populate: ");
for (i=0; (s == NATS_OK) && (i<10); i++)
s = js_PublishAsync(js, "bat", (const void*) "abcdefghij", 10, NULL);
testCond(s == NATS_OK);
test("Received ok: ");
natsThread_Join(t);
natsMutex_Lock(args.m);
s = args.status;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsThread_Destroy(t);
natsSubscription_Destroy(sub);
sub = NULL;
test("Create pull consumer: ");
s = js_PullSubscribe(&sub, js, "box", "feth-request", NULL, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
s = js_PublishAsync(js, "box", (const void*) "abcdefghij", 10, NULL);
testCond(s == NATS_OK);
test("Check expiration: ");
// Unlike with the simple fetch, asking for more than is avail will
// wait until expiration to return.
jsFetchRequest_Init(&fr);
fr.Batch = 10;
fr.Expires = NATS_MILLIS_TO_NANOS(500);
fr.Heartbeat = NATS_MILLIS_TO_NANOS(50);
start = nats_Now();
s = natsSubscription_FetchRequest(&list, sub, &fr);
dur = nats_Now() - start;
testCond((s == NATS_OK) && (list.Count == 1) && (list.Msgs != NULL)
&& (dur > 400) && (dur < 600));
natsMsgList_Destroy(&list);
#if _WIN32
nats_Sleep(1000);
#endif
test("Check invalid hb: ");
jsFetchRequest_Init(&fr);
fr.Batch = 10;
fr.Expires = NATS_SECONDS_TO_NANOS(1);
fr.Heartbeat = NATS_SECONDS_TO_NANOS(10);
s = natsSubscription_FetchRequest(&list, sub, &fr);
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "too large") != NULL));
nats_clearLastError();
test("Check idle hearbeat: ");
jsFetchRequest_Init(&fr);
fr.Batch = 10;
// Let's make it wait for 2 seconds
fr.Expires = NATS_SECONDS_TO_NANOS(2);
// And have HBs every 50ms
fr.Heartbeat = NATS_MILLIS_TO_NANOS(50);
// Set a message filter that will drop HB messages
natsConn_setFilter(nc, _dropIdleHBs);
start = nats_Now();
// We should be kicked out of the fetch request with an error indicating
// that we missed hearbeats.
s = natsSubscription_FetchRequest(&list, sub, &fr);
dur = nats_Now() - start;
testCond((s == NATS_MISSED_HEARTBEAT) && (dur < 500));
natsSubscription_Destroy(sub);
JS_TEARDOWN;
_destroyDefaultThreadArgs(&args);
}
static void
test_JetStreamSubscribeHeadersOnly(void)
{
natsStatus s;
natsSubscription *sub= NULL;
natsMsg *msg = NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
jsSubOptions so;
int i;
JS_SETUP(2, 6, 2);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "S";
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
for (i=0; (s == NATS_OK) && (i<10); i++)
{
s = natsMsg_Create(&msg, "S", NULL, "hello", 5);
IFOK(s, natsMsgHeader_Set(msg, "User-Header", "MyValue"));
IFOK(s, js_PublishMsg(NULL, js, msg, NULL, NULL));
natsMsg_Destroy(msg);
msg = NULL;
}
testCond(s == NATS_OK);
test("Create consumer with headers only: ");
jsSubOptions_Init(&so);
so.Config.HeadersOnly = true;
s = js_SubscribeSync(&sub, js, "S", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Verify only headers: ");
for (i=0; (s == NATS_OK) && (i<10); i++)
{
const char *val = NULL;
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, (natsMsg_GetDataLength(msg) == 0 ? NATS_OK : NATS_ERR));
IFOK(s, natsMsgHeader_Get(msg, "User-Header", &val));
IFOK(s, (strcmp(val, "MyValue") == 0 ? NATS_OK : NATS_ERR));
IFOK(s, natsMsgHeader_Get(msg, JSMsgSize, &val));
IFOK(s, (strcmp(val, "5") == 0 ? NATS_OK : NATS_ERR));
natsMsg_Destroy(msg);
msg = NULL;
}
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
jsCtx_Destroy(js);
natsConnection_Destroy(nc);
_stopServer(pid);
rmtree(datastore);
}
static void
_orderedConsCB(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
if (natsMsg_GetDataLength(msg) == 0)
{
args->done = true;
natsCondition_Signal(args->c);
}
else
{
natsBuf_Append(args->buf, natsMsg_GetData(msg), natsMsg_GetDataLength(msg));
args->sum++;
}
natsMutex_Unlock(args->m);
natsMsg_Destroy(msg);
}
static natsStatus
_testOrderedCons(jsCtx *js, jsStreamInfo *si, char *asset, int assetLen, struct threadArg *args, bool async)
{
natsStatus s = NATS_OK;
natsSubscription *sub = NULL;
int received = 0;
jsSubOptions so;
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(250);
s = natsBuf_Create(&args->buf, assetLen);
if ((s == NATS_OK) && async)
{
s = js_Subscribe(&sub, js, "a", _orderedConsCB, (void*) args, NULL, &so, NULL);
if (s == NATS_OK)
{
natsMutex_Lock(args->m);
while ((s != NATS_TIMEOUT) && !args->done)
s = natsCondition_TimedWait(args->c, args->m, 5000);
received = args->sum;
natsMutex_Unlock(args->m);
}
}
else if (s == NATS_OK)
{
s = js_SubscribeSync(&sub, js, "a", NULL, &so, NULL);
if (s == NATS_OK)
{
bool done = false;
int64_t start = 0;
natsMsg *msg = NULL;
start = nats_Now();
while ((s == NATS_OK) && !done)
{
s = natsSubscription_NextMsg(&msg, sub, 5000);
if (s == NATS_OK)
{
done = (natsMsg_GetDataLength(msg) == 0 ? true : false);
if (!done)
{
s = natsBuf_Append(args->buf, natsMsg_GetData(msg), natsMsg_GetDataLength(msg));
received++;
}
natsMsg_Destroy(msg);
msg = NULL;
}
if ((s == NATS_OK) && (nats_Now() - start > 5500))
s = NATS_TIMEOUT;
}
}
}
if ((s == NATS_OK) && (natsBuf_Len(args->buf) != assetLen))
{
fprintf(stderr, "\nAsset length (%d) does not match received data length (%d)\n",
assetLen, natsBuf_Len(args->buf));
s = NATS_MISMATCH;
}
else if (s == NATS_OK)
{
int i;
char *data = natsBuf_Data(args->buf);
for (i=0; i<assetLen; i++)
{
if (data[i] != asset[i])
{
fprintf(stderr, "\nAsset does not match received data:\nAsset=\n%s\nData=\n%s\n",
asset, data);
s = NATS_MISMATCH;
break;
}
}
}
else if (s == NATS_TIMEOUT)
fprintf(stderr, "\nReceived %d chunks out of %d\n", received, (int) (si->State.Msgs-1));
fflush(stderr);
natsSubscription_Destroy(sub);
natsMutex_Lock(args->m);
natsBuf_Destroy(args->buf);
args->buf = NULL;
args->sum = 0;
args->done = false;
natsMutex_Unlock(args->m);
return s;
}
static void
_singleLoss(natsConnection *nc, natsMsg **msg, void* closure)
{
const char *val = NULL;
int res = rand() % 100;
if ((res <= 10) && (natsMsgHeader_Get(*msg, "data", &val) == NATS_OK))
{
natsMsg_Destroy(*msg);
*msg = NULL;
natsConn_setFilter(nc, NULL);
}
}
static void
_multiLoss(natsConnection *nc, natsMsg **msg, void* closure)
{
const char *val = NULL;
int res = rand() % 100;
if ((res <= 10) && (natsMsgHeader_Get(*msg, "data", &val) == NATS_OK))
{
natsMsg_Destroy(*msg);
*msg = NULL;
}
}
static void
_firstOnlyLoss(natsConnection *nc, natsMsg **msg, void* closure)
{
jsMsgMetaData *meta = NULL;
if (natsMsg_GetMetaData(&meta, *msg) == NATS_OK)
{
if (meta->Sequence.Consumer == 1)
{
natsMsg_Destroy(*msg);
*msg = NULL;
natsConn_setFilter(nc, NULL);
}
jsMsgMetaData_Destroy(meta);
}
}
static void
_lastOnlyLoss(natsConnection *nc, natsMsg **msg, void* closure)
{
jsMsgMetaData *meta = NULL;
jsStreamInfo *si = (jsStreamInfo*) closure;
if (natsMsg_GetMetaData(&meta, *msg) == NATS_OK)
{
if (meta->Sequence.Stream == si->State.LastSeq-1)
{
natsMsg_Destroy(*msg);
*msg = NULL;
natsConn_setFilter(nc, NULL);
}
jsMsgMetaData_Destroy(meta);
}
}
static void
test_JetStreamOrderedConsumer(void)
{
natsStatus s;
natsSubscription *sub= NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
jsSubOptions so;
struct threadArg args;
int i;
char *asset = NULL;
int assetLen = 1024*1024;
const int chunkSize = 1024;
jsStreamInfo *si = NULL;
jsConsumerInfo *ci1 = NULL;
jsConsumerInfo *ci2 = NULL;
JS_SETUP(2, 3, 3);
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "OBJECT";
sc.Subjects = (const char*[1]){"a"};
sc.SubjectsLen = 1;
sc.Storage = js_MemoryStorage;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
// Create a sample asset.
asset = malloc(assetLen);
for (i=0; i<assetLen; i++)
asset[i] = (rand() % 26) + 'A';
test("Send chunks: ");
for (i=0; ((s == NATS_OK) && (i<assetLen)); i+=chunkSize)
{
char *chunk = asset+i;
int csize = (assetLen-i <= chunkSize ? assetLen-i : chunkSize);
natsMsg *msg = NULL;
s = natsMsg_Create(&msg, "a", NULL, (const void*) chunk, csize);
IFOK(s, natsMsgHeader_Set(msg, "data", "true"));
IFOK(s, js_PublishMsgAsync(js, &msg, NULL));
natsMsg_Destroy(msg);
msg = NULL;
}
IFOK(s, js_PublishAsync(js, "a", NULL, 0, NULL));
testCond(s == NATS_OK);
test("Wait for complete: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
// Do some tests on simple misconfigurations first.
// For ordered delivery a couple of things need to be set properly.
// Can't be durable or have ack policy that is not ack none or max deliver set.
test("Ordered consumer, no durable: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.Durable = "dlc";
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrOrderedConsNoDurable) != NULL));
nats_clearLastError();
test("Ordered consumer, ack explicit: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.AckPolicy = js_AckExplicit;
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrOrderedConsNoAckPolicy) != NULL));
nats_clearLastError();
test("Ordered consumer, max deliver: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.MaxDeliver = 10;
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrOrderedConsNoMaxDeliver) != NULL));
nats_clearLastError();
test("Ordered consumer, no deliver subject: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.DeliverSubject = "not.allowed";
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrOrderedConsNoDeliverSubject) != NULL));
nats_clearLastError();
test("Ordered consumer, no queue: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Queue = "queue";
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrOrderedConsNoQueue) != NULL));
nats_clearLastError();
test("Ordered consumer, no consumer: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Consumer = "consumer";
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
if (s == NATS_INVALID_ARG)
{
so.Stream = "OBJECT";
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
}
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrOrderedConsNoBind) != NULL));
nats_clearLastError();
test("Ordered consumer, no pull mode: ");
jsSubOptions_Init(&so);
so.Ordered = true;
s = js_PullSubscribe(&sub, js, "a", "dur", NULL, &so, &jerr);
testCond((s == NATS_INVALID_ARG) && (sub == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrOrderedConsNoPullMode) != NULL));
nats_clearLastError();
test("Get stream info: ");
s = js_GetStreamInfo(&si, js, "OBJECT", NULL, &jerr);
testCond((s == NATS_OK) && (si != NULL) && (jerr == 0));
test("Test async consumer: ");
s = _testOrderedCons(js, si, asset, assetLen, &args, true);
testCond(s == NATS_OK);
test("Test sync consumer: ");
s = _testOrderedCons(js, si, asset, assetLen, &args, false);
testCond(s == NATS_OK);
test("Test with single loss (async): ");
natsConn_setFilter(nc, _singleLoss);
s = _testOrderedCons(js, si, asset, assetLen, &args, true);
testCond(s == NATS_OK);
test("Test with single loss (sync): ");
natsConn_setFilter(nc, _singleLoss);
s = _testOrderedCons(js, si, asset, assetLen, &args, false);
testCond(s == NATS_OK);
test("Test with multi loss (async): ");
natsConn_setFilter(nc, _multiLoss);
s = _testOrderedCons(js, si, asset, assetLen, &args, true);
testCond(s == NATS_OK);
test("Test with multi loss (sync): ");
natsConn_setFilter(nc, _multiLoss);
s = _testOrderedCons(js, si, asset, assetLen, &args, false);
testCond(s == NATS_OK);
test("Test with first msg loss (async): ");
natsConn_setFilter(nc, _firstOnlyLoss);
s = _testOrderedCons(js, si, asset, assetLen, &args, true);
testCond(s == NATS_OK);
test("Test with first msg loss (sync): ");
natsConn_setFilter(nc, _firstOnlyLoss);
s = _testOrderedCons(js, si, asset, assetLen, &args, false);
testCond(s == NATS_OK);
test("Test with last msg loss (async): ");
natsConn_setFilterWithClosure(nc, _lastOnlyLoss, (void*) si);
s = _testOrderedCons(js, si, asset, assetLen, &args, true);
testCond(s == NATS_OK);
test("Test with last msg loss (sync): ");
natsConn_setFilterWithClosure(nc, _lastOnlyLoss, (void*) si);
s = _testOrderedCons(js, si, asset, assetLen, &args, false);
testCond(s == NATS_OK);
// A bug was causing the ordered consumer to be recreated at
// each hearbeat. Use a low hearbeat and check that name
// does not change after some HB intervals.
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TESTNAME";
sc.Subjects = (const char*[1]){"b"};
sc.SubjectsLen = 1;
sc.Storage = js_MemoryStorage;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Send 1 message: ");
s = js_Publish(NULL, js, "b", "hello", 5, NULL, NULL);
testCond(s == NATS_OK);
test("Create ordered cons with small HB: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(100);
s = js_SubscribeSync(&sub, js, "b", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Get name: ");
s = natsSubscription_GetConsumerInfo(&ci1, sub, NULL, NULL);
testCond((s == NATS_OK) && (ci1 != NULL));
nats_Sleep(300);
test("Send 1 message: ");
s = js_Publish(NULL, js, "b", "hello", 5, NULL, NULL);
testCond(s == NATS_OK);
nats_Sleep(300);
test("Check name does not change: ");
s = natsSubscription_GetConsumerInfo(&ci2, sub, NULL, NULL);
testCond((s == NATS_OK) && (ci2 != NULL)
&& (strcmp(ci1->Name, ci2->Name) == 0));
jsConsumerInfo_Destroy(ci1);
jsConsumerInfo_Destroy(ci2);
free(asset);
jsStreamInfo_Destroy(si);
natsSubscription_Destroy(sub);
_destroyDefaultThreadArgs(&args);
JS_TEARDOWN;
}
static void
_jsOrderedErrHandler(natsConnection *nc, natsSubscription *subscription, natsStatus err, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->status = err;
natsCondition_Signal(args->c);
natsMutex_Unlock(args->m);
}
static void
test_JetStreamOrderedConsumerWithErrors(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsOptions *opts = NULL;
natsSubscription *sub= NULL;
jsCtx *js = NULL;
natsPid pid = NATS_INVALID_PID;
jsErrCode jerr= 0;
char datastore[256] = {'\0'};
char cmdLine[1024] = {'\0'};
jsStreamConfig sc;
jsSubOptions so;
int i, iter;
char *asset = NULL;
int assetLen = 128*1024;
const int chunkSize = 256;
struct threadArg args;
ENSURE_JS_VERSION(2, 3, 3);
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s", datastore);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetErrorHandler(opts, _jsOrderedErrHandler, (void*) &args));
IFOK(s, natsConnection_Connect(&nc, opts));
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, NULL);
testCond(s == NATS_OK);
// Create a sample asset.
asset = malloc(assetLen);
for (i=0; i<assetLen; i++)
asset[i] = (rand() % 26) + 'A';
for (iter=0; iter<2; iter++)
{
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "OBJECT";
sc.Subjects = (const char*[1]){"a"};
sc.SubjectsLen = 1;
sc.Storage = js_MemoryStorage;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Send chunks: ");
for (i=0; ((s == NATS_OK) && (i<assetLen)); i+=chunkSize)
{
char *chunk = asset+i;
int csize = (assetLen-i <= chunkSize ? assetLen-i : chunkSize);
s = js_PublishAsync(js, "a", chunk, csize, NULL);
}
testCond(s == NATS_OK);
test("Wait for complete: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
test("Create ordered sub: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(200);
s = js_SubscribeSync(&sub, js, "a", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
// Since we are sync we will be paused here due to flow control.
nats_Sleep(100);
test("Delete asset: ");
if (iter == 0)
{
s = js_DeleteStream(js, "OBJECT", NULL, &jerr);
}
else
{
char *cons = NULL;
natsSub_Lock(sub);
cons = sub->jsi->consumer;
natsSub_Unlock(sub);
s = js_DeleteConsumer(js, "OBJECT", cons, NULL, &jerr);
}
testCond((s == NATS_OK) && (jerr == 0));
test("Check we get error: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.status != NATS_MISSED_HEARTBEAT))
s = natsCondition_TimedWait(args.c, args.m, 1000);
args.status = NATS_OK;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
sub = NULL;
}
free(asset);
JS_TEARDOWN;
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
}
static void
_dropMsgFive(natsConnection *nc, natsMsg **msg, void* closure)
{
const char *val = NULL;
struct threadArg *args = (struct threadArg*) closure;
if (natsMsgHeader_Get(*msg, "data", &val) == NATS_OK)
{
if (++(args->results[0]) == 5)
{
natsMsg_Destroy(*msg);
*msg = NULL;
natsConn_setFilter(nc, NULL);
}
}
}
static void
test_JetStreamOrderedConsumerWithAutoUnsub(void)
{
natsStatus s;
natsConnection *nc2= NULL;
natsSubscription *sub= NULL;
jsCtx *js2= NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
jsSubOptions so;
struct threadArg args;
int i;
natsStatistics stats;
uint64_t in1 = 0;
uint64_t in2 = 0;
JS_SETUP(2, 3, 3);
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "OBJECT";
sc.Subjects = (const char*[1]){"a"};
sc.SubjectsLen = 1;
sc.Storage = js_MemoryStorage;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Subscribe: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(250);
s = js_Subscribe(&sub, js, "a", _jsMsgHandler, (void*)&args, NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Auto-unsub: ");
s = natsSubscription_AutoUnsubscribe(sub, 10);
testCond(s == NATS_OK);
// Set a message filter that will drop 1 message
natsConn_setFilterWithClosure(nc, _dropMsgFive, (void*)&args);
// Now produce 20 messages
test("Produce: ");
for (i=0; (s == NATS_OK) && (i < 20); i++)
{
natsMsg *msg = NULL;
s = natsMsg_Create(&msg, "a", NULL, "hello", 5);
IFOK(s, natsMsgHeader_Set(msg, "data", "true"));
IFOK(s, js_PublishMsgAsync(js, &msg, NULL));
natsMsg_Destroy(msg);
msg = NULL;
}
testCond(s == NATS_OK);
test("Wait for all pubs done: ");
s = js_PublishAsyncComplete(js, NULL);
testCond(s == NATS_OK);
test("Wait for subscription to be invalid: ");
s = NATS_ERR;
for (i=0; i<10; i++)
{
if (!natsSubscription_IsValid(sub))
{
s = NATS_OK;
break;
}
nats_Sleep(100);
}
testCond(s == NATS_OK);
// Wait a bit to make sure we are not receiving more than expected,
// and give a chance for the server to process the auto-unsub
// protocol.
nats_Sleep(500);
test("Check count: ");
natsMutex_Lock(args.m);
s = (args.sum == 10 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
// Now capture the in msgs count for the connection
test("Get stats: ");
s = natsConnection_GetStats(nc, &stats);
IFOK(s, natsStatistics_GetCounts(&stats, &in1, NULL, NULL, NULL, NULL));
testCond(s == NATS_OK);
// Send one more message and this count should not increase if the
// server had properly processed the auto-unsub after the
// reset of the ordered consumer. Use a different connection
// to send.
test("Send one msg: ");
s = natsConnection_ConnectTo(&nc2, NATS_DEFAULT_URL);
IFOK(s, natsConnection_JetStream(&js2, nc2, NULL));
IFOK(s, js_Publish(NULL, js2, "a", "bad", 3, NULL, NULL));
testCond(s == NATS_OK);
test("Get stats 2: ");
s = natsConnection_GetStats(nc, &stats);
IFOK(s, natsStatistics_GetCounts(&stats, &in2, NULL, NULL, NULL, NULL));
testCond(s == NATS_OK);
test("Msg not received: ");
s = (in1 == in2 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
natsSubscription_Destroy(sub);
jsCtx_Destroy(js2);
natsConnection_Destroy(nc2);
_destroyDefaultThreadArgs(&args);
JS_TEARDOWN;
}
static void
test_JetStreamOrderedConsSrvRestart(void)
{
natsStatus s;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
natsOptions *opts = NULL;
jsConsumerInfo *ci = NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
jsSubOptions so;
struct threadArg args;
int i;
JS_SETUP(2, 9, 2);
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
// JS_SETUP creates a basic connection, we want to know when
// we reconnected, so create a new one.
test("Connect: ");
natsConnection_Destroy(nc);
nc = NULL;
jsCtx_Destroy(js);
js = NULL;
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetReconnectedCB(opts, _reconnectedCb, (void*) &args));
IFOK(s, natsOptions_SetReconnectWait(opts, 100));
IFOK(s, natsConnection_Connect(&nc, opts));
IFOK(s, natsConnection_JetStream(&js, nc, NULL));
testCond(s == NATS_OK);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "OCRESTART";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Subscribe: ");
jsSubOptions_Init(&so);
so.Ordered = true;
so.Config.Heartbeat = NATS_MILLIS_TO_NANOS(250);
so.Config.HeadersOnly = true;
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Send 1 message: ");
s = js_Publish(NULL, js, "foo", "hello", 5, NULL, NULL);
testCond(s == NATS_OK)
test("Consume: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Stopping server: ");
_stopServer(pid);
testCond(true);
// Wait more than the HB failure detection so that we check
// that resetting the ordered consumer works even while the
// server is still down.
test("Waiting before restarting: ");
nats_Sleep(1500);
testCond(true);
// Restart
test("Restarting server: ");
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Wait for reconnect: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.reconnected)
s = natsCondition_TimedWait(args.c, args.m, 2000);
testCond(s == NATS_OK);
test("Send 1 message: ");
s = js_Publish(NULL, js, "foo", "hello", 5, NULL, NULL);
testCond(s == NATS_OK)
test("Consume: ");
s = natsSubscription_NextMsg(&msg, sub, 5000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Check configuration is similar: ");
for (i=0; (s==NATS_OK) && (i<10); i++)
{
s = natsSubscription_GetConsumerInfo(&ci, sub, NULL, NULL);
if (s == NATS_OK)
break;
else if (s == NATS_NOT_FOUND)
{
s = NATS_OK;
nats_Sleep(100);
}
}
testCond((s == NATS_OK)
&& (ci != NULL) && (ci->Config != NULL)
&& (ci->Config->MemoryStorage)
&& (ci->Config->Replicas == 1)
&& (ci->Config->HeadersOnly));
jsConsumerInfo_Destroy(ci);
natsSubscription_Destroy(sub);
natsOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
JS_TEARDOWN;
}
static void
test_JetStreamSubscribeWithFWC(void)
{
natsStatus s;
natsSubscription *sub= NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
jsConsumerConfig cc;
jsSubOptions so;
JS_SETUP(2, 3, 5);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST_WC";
sc.Subjects = (const char*[1]){"fwc.>"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Create consumer: ");
jsConsumerConfig_Init(&cc);
cc.Durable = "dur";
cc.DeliverSubject = "bar";
cc.FilterSubject = "fwc.>";
s = js_AddConsumer(NULL, js, "TEST_WC", &cc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Subscribe: ");
jsSubOptions_Init(&so);
so.Config.Durable = "dur";
s = js_SubscribeSync(&sub, js, "fwc.>", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL));
natsSubscription_Destroy(sub);
JS_TEARDOWN;
}
static void
test_JetStreamStreamsSealAndRollup(void)
{
natsStatus s;
jsStreamInfo *si = NULL;
jsStreamConfig cfg;
jsErrCode jerr = 0;
natsMsg *msg = NULL;
natsSubscription *sub = NULL;
int i;
JS_SETUP(2, 6, 2);
test("Create sealed stream fails: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "SEAL_FAIL";
cfg.Sealed = true;
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_ERR) && (si == NULL) && (jerr == JSStreamInvalidConfig));
nats_clearLastError();
test("Create stream: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "SEAL";
s = js_AddStream(NULL, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Seal stream: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "SEAL";
cfg.Sealed = true;
s = js_UpdateStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL)
&& (si->Config != NULL) && si->Config->Sealed);
jsStreamInfo_Destroy(si);
si = NULL;
test("Can't send: ");
s = js_Publish(NULL, js, "SEAL", "a", 1, NULL, &jerr);
testCond((s == NATS_ERR) && (jerr == JSStreamSealedErr));
nats_clearLastError();
test("Create stream with deny purge/delete: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "AUDIT";
cfg.Storage = js_MemoryStorage;
cfg.DenyPurge = true;
cfg.DenyDelete = true;
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL)
&& (si->Config != NULL) && si->Config->DenyDelete
&& si->Config->DenyPurge);
jsStreamInfo_Destroy(si);
si = NULL;
test("Publish: ");
for (i=0; (s == NATS_OK) && (i < 10); i++)
s = js_Publish(NULL, js, "AUDIT", "ok", 2, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Can't delete: ");
s = js_DeleteMsg(js, "AUDIT", 1, NULL, &jerr);
testCond((s == NATS_ERR) && (jerr == JSStreamMsgDeleteFailed)
&& (strstr(nats_GetLastError(NULL), "message delete not permitted") != NULL));
nats_clearLastError();
test("Can't purge: ");
s = js_PurgeStream(js, "AUDIT", NULL, &jerr);
testCond((s == NATS_ERR) && (jerr == JSStreamPurgeFailedErr)
&& (strstr(nats_GetLastError(NULL), "stream purge not permitted") != NULL));
nats_clearLastError();
test("Try to remove deny clauses: ");
cfg.DenyPurge = false;
cfg.DenyDelete = false;
s = js_UpdateStream(NULL, js, &cfg, NULL, &jerr);
testCond((s == NATS_ERR) && (jerr == JSStreamInvalidConfig));
nats_clearLastError();
test("Create stream for rollup: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "ROLLUP";
cfg.Subjects = (const char*[1]){"rollup.*"};
cfg.SubjectsLen = 1;
cfg.AllowRollup = true;
s = js_AddStream(&si, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL)
&& (si->Config != NULL) && si->Config->AllowRollup);
jsStreamInfo_Destroy(si);
si = NULL;
test("Populate: ");
for (i=0; (s == NATS_OK) && (i<10); i++)
s = js_Publish(NULL, js, "rollup.a", "a", 1, NULL, &jerr);
for (i=0; (s == NATS_OK) && (i<10); i++)
s = js_Publish(NULL, js, "rollup.b", "b", 1, NULL, &jerr);
for (i=0; (s == NATS_OK) && (i<10); i++)
s = js_Publish(NULL, js, "rollup.c", "c", 1, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Check stream: ");
s = js_GetStreamInfo(&si, js, "ROLLUP", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL)
&& (si->State.Msgs == 30));
jsStreamInfo_Destroy(si);
si = NULL;
test("Rollup per subject: ");
s = natsMsg_Create(&msg, "rollup.b", NULL, "Rollup", 6);
IFOK(s, natsMsgHeader_Set(msg, JSMsgRollup, JSMsgRollupSubject));
IFOK(s, js_PublishMsg(NULL, js, msg, NULL, &jerr));
testCond((s == NATS_OK) && (jerr == 0));
natsMsg_Destroy(msg);
msg = NULL;
test("Create consumer: ");
s = js_SubscribeSync(&sub, js, "rollup.b", NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check content: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, (strcmp(natsMsg_GetData(msg), "Rollup") == 0 ? NATS_OK : NATS_ERR));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Make sure single msg: ");
s = natsSubscription_NextMsg(&msg, sub, 250);
testCond((s == NATS_TIMEOUT) && (msg == NULL));
nats_clearLastError();
natsSubscription_Destroy(sub);
sub = NULL;
test("Rollup for all: ");
s = natsMsg_Create(&msg, "rollup.c", NULL, "RollupAll", 9);
IFOK(s, natsMsgHeader_Set(msg, JSMsgRollup, JSMsgRollupAll));
IFOK(s, js_PublishMsg(NULL, js, msg, NULL, &jerr));
testCond((s == NATS_OK) && (jerr == 0));
natsMsg_Destroy(msg);
msg = NULL;
test("Create consumer: ");
s = js_SubscribeSync(&sub, js, "rollup.c", NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Check content: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
IFOK(s, (strcmp(natsMsg_GetData(msg), "RollupAll") == 0 ? NATS_OK : NATS_ERR));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Make sure single msg: ");
s = natsSubscription_NextMsg(&msg, sub, 250);
testCond((s == NATS_TIMEOUT) && (msg == NULL));
nats_clearLastError();
test("Check stream: ");
s = js_GetStreamInfo(&si, js, "ROLLUP", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL)
&& (si->State.Msgs == 1));
jsStreamInfo_Destroy(si);
si = NULL;
natsSubscription_Destroy(sub);
JS_TEARDOWN;
}
static void
test_JetStreamGetMsgAndLastMsg(void)
{
natsStatus s;
natsMsg *msg = NULL;
jsStreamConfig cfg;
jsErrCode jerr = 0;
const char *val = NULL;
JS_SETUP(2, 3, 1);
test("Create stream: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "GET_MSG";
cfg.Subjects = (const char*[1]){"foo.*"};
cfg.SubjectsLen = 1;
cfg.Storage = js_MemoryStorage;
s = js_AddStream(NULL, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
s = js_Publish(NULL, js, "foo.bar", "msg1", 4, NULL, NULL);
IFOK(s, js_Publish(NULL, js, "foo.bar", "msg2", 4, NULL, NULL));
IFOK(s, js_Publish(NULL, js, "foo.baz", "msg3", 4, NULL, NULL));
IFOK(s, natsMsg_Create(&msg, "foo.baz", NULL, "msg4", 4));
IFOK(s, natsMsgHeader_Set(msg, "Some-Header", "Some-Value"));
IFOK(s, js_PublishMsg(NULL, js, msg, NULL, NULL));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("GetMsg bad args: ");
s = js_GetMsg(NULL, js, "GET_MSG", 1, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetMsg(&msg, NULL, "GET_MSG", 1, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetMsg(&msg, js, "GET_MSG", 0, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0));
nats_clearLastError();
test("GetMsg stream name required: ");
s = js_GetMsg(&msg, js, NULL, 1, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetMsg(&msg, js, "", 1, NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("GetMsg stream not found: ");
s = js_GetMsg(&msg, js, "NOT_FOUND", 1, NULL, &jerr);
testCond((s == NATS_ERR) && (msg == NULL) && (jerr == JSStreamNotFoundErr)
&& (strstr(nats_GetLastError(NULL), "stream not found") != NULL));
nats_clearLastError();
test("GetMsg message not found: ");
s = js_GetMsg(&msg, js, "GET_MSG", 100, NULL, &jerr);
testCond((s == NATS_NOT_FOUND) && (msg == NULL) && (jerr == JSNoMessageFoundErr));
nats_clearLastError();
test("GetMsg message ok: ");
s = js_GetMsg(&msg, js, "GET_MSG", 1, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (msg != NULL)
&& (strcmp(natsMsg_GetSubject(msg), "foo.bar") == 0)
&& (strcmp(natsMsg_GetData(msg), "msg1") == 0)
&& (natsMsg_GetSequence(msg) == 1)
&& (natsMsg_GetTime(msg) != 0));
natsMsg_Destroy(msg);
msg = NULL;
// GetLasMsg tests now....
test("GetLastMsg bad args: ");
s = js_GetLastMsg(NULL, js, "GET_MSG", "foo.bar", NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetLastMsg(&msg, NULL, "GET_MSG", "foo.bar", NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetLastMsg(&msg, js, "GET_MSG", NULL, NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetLastMsg(&msg, js, "GET_MSG", "", NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0));
nats_clearLastError();
test("GetLastMsg stream name required: ");
s = js_GetLastMsg(&msg, js, NULL, "foo.bar", NULL, &jerr);
if (s == NATS_INVALID_ARG)
s = js_GetLastMsg(&msg, js, "", "foo.bar", NULL, &jerr);
testCond((s == NATS_INVALID_ARG) && (msg == NULL) && (jerr == 0)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("GetLastMsg stream not found: ");
s = js_GetLastMsg(&msg, js, "NOT_FOUND", "foo.bar", NULL, &jerr);
testCond((s == NATS_ERR) && (msg == NULL) && (jerr == JSStreamNotFoundErr)
&& (strstr(nats_GetLastError(NULL), "stream not found") != NULL));
nats_clearLastError();
test("GetLastMsg message not found: ");
s = js_GetLastMsg(&msg, js, "GET_MSG", "not.found", NULL, &jerr);
testCond((s == NATS_NOT_FOUND) && (msg == NULL) && (jerr == JSNoMessageFoundErr));
nats_clearLastError();
test("GetLastMsg message ok: ");
s = js_GetLastMsg(&msg, js, "GET_MSG", "foo.baz", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (msg != NULL) && (msg != NULL)
&& (strcmp(natsMsg_GetSubject(msg), "foo.baz") == 0)
&& (strcmp(natsMsg_GetData(msg), "msg4") == 0)
&& (natsMsgHeader_Get(msg, "Some-Header", &val) == NATS_OK)
&& (strcmp(val, "Some-Value") == 0)
&& (natsMsg_GetSequence(msg) == 4)
&& (natsMsg_GetTime(msg) != 0));
natsMsg_Destroy(msg);
JS_TEARDOWN;
}
static void
test_JetStreamConvertDirectMsg(void)
{
natsStatus s;
natsMsg *msg = NULL;
const char *val = NULL;
test("Bad request: ");
s = natsMsg_Create(&msg, "inbox", NULL, NULL, 0);
IFOK(s, natsMsgHeader_Set(msg, STATUS_HDR, REQ_TIMEOUT));
IFOK(s, natsMsgHeader_Set(msg, DESCRIPTION_HDR, "Bad Request"));
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "Bad Request") != NULL));
nats_clearLastError();
test("Not found: ");
s = natsMsgHeader_Set(msg, STATUS_HDR, NOT_FOUND_STATUS);
IFOK(s, natsMsgHeader_Set(msg, DESCRIPTION_HDR, "Message Not Found"));
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_NOT_FOUND) && (strstr(nats_GetLastError(NULL), natsStatus_GetText(NATS_NOT_FOUND)) != NULL));
nats_clearLastError();
natsMsg_Destroy(msg);
msg = NULL;
test("Msg has no header: ");
s = natsMsg_Create(&msg, "inbox", NULL, "1", 1);
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "should have headers") != NULL));
nats_clearLastError();
test("Missing stream: ");
s = natsMsgHeader_Set(msg, "some", "header");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing stream") != NULL));
nats_clearLastError();
test("Missing sequence: ");
s = natsMsgHeader_Set(msg, JSStream, "test");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "invalid sequence") != NULL));
nats_clearLastError();
test("Invalid sequence: ");
s = natsMsgHeader_Set(msg, JSSequence, "abc");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "invalid sequence 'abc'") != NULL));
nats_clearLastError();
test("Missing timestamp: ");
s = natsMsgHeader_Set(msg, JSSequence, "1");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid timestamp") != NULL));
nats_clearLastError();
test("Invalid timestamp: ");
s = natsMsgHeader_Set(msg, JSTimeStamp, "aaaaaaaaa bbbbbbbbbbbb cccccccccc ddddddddddd eeeeeeeeee ffffff");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid timestamp 'aaaaaaaaa bbbbbbbbbbbb cccccccccc ddddddddddd eeeeeeeeee ffffff'") != NULL));
nats_clearLastError();
test("Missing subject: ");
s = natsMsgHeader_Set(msg, JSTimeStamp, "2006-01-02T15:04:05Z");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid subject") != NULL));
nats_clearLastError();
test("Invalid subject: ");
s = natsMsgHeader_Set(msg, JSSubject, "");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "missing or invalid subject ''") != NULL));
nats_clearLastError();
test("Valid msg: ");
s = natsMsgHeader_Set(msg, JSSubject, "foo");
IFOK(s, js_directGetMsgToJSMsg("test", msg));
testCond((s == NATS_OK)
&& (strcmp(natsMsg_GetSubject(msg), "foo") == 0)
&& (natsMsg_GetSequence(msg) == 1)
&& (natsMsg_GetTime(msg) == 1136214245000000000L)
&& (natsMsgHeader_Get(msg, "some", &val) == NATS_OK)
&& (strcmp(val, "header") == 0));
natsMsg_Destroy(msg);
}
static natsStatus
_checkDirectGet(jsCtx *js, uint64_t seq, const char *nextBySubj, const char *lastBySubj,
const char *expectedSubj, uint64_t expectedSeq)
{
natsStatus s = NATS_OK;
natsMsg *msg = NULL;
jsDirectGetMsgOptions o;
jsDirectGetMsgOptions_Init(&o);
o.Sequence = seq;
o.NextBySubject = nextBySubj;
o.LastBySubject = lastBySubj;
s = js_DirectGetMsg(&msg, js, "DGM", NULL, &o);
if ((s != NATS_OK)
|| (msg == NULL)
|| (strcmp(natsMsg_GetSubject(msg), expectedSubj) != 0)
|| (natsMsg_GetSequence(msg) != expectedSeq)
|| (natsMsg_GetTime(msg) == 0))
{
s = NATS_ERR;
}
natsMsg_Destroy(msg);
return s;
}
static void
test_JetStreamDirectGetMsg(void)
{
natsStatus s;
natsMsg *msg = NULL;
jsStreamConfig cfg;
jsErrCode jerr = 0;
const char *val = NULL;
jsDirectGetMsgOptions dgo;
JS_SETUP(2, 9, 0);
test("Create stream: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "DGM";
cfg.Subjects = (const char*[2]){"foo", "bar"};
cfg.SubjectsLen = 2;
cfg.Storage = js_MemoryStorage;
cfg.AllowDirect = true;
s = js_AddStream(NULL, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Populate: ");
s = js_Publish(NULL, js, "foo", "a", 1, NULL, NULL);
IFOK(s, js_Publish(NULL, js, "foo", "b", 1, NULL, NULL));
IFOK(s, js_Publish(NULL, js, "foo", "c", 1, NULL, NULL));
IFOK(s, js_Publish(NULL, js, "bar", "d", 1, NULL, NULL));
IFOK(s, js_Publish(NULL, js, "foo", "e", 1, NULL, NULL));
testCond(s == NATS_OK);
test("DirecGetMsg bad args: ");
s = js_DirectGetMsg(NULL, js, "DGM", NULL, &dgo);
if (s == NATS_INVALID_ARG)
s = js_DirectGetMsg(&msg, NULL, "DGM", NULL, &dgo);
if (s == NATS_INVALID_ARG)
s = js_DirectGetMsg(&msg, js, "DGM", NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (msg == NULL));
nats_clearLastError();
test("GetMsg stream name required: ");
s = js_DirectGetMsg(&msg, js, NULL, NULL, &dgo);
if (s == NATS_INVALID_ARG)
s = js_DirectGetMsg(&msg, js, "", NULL, &dgo);
testCond((s == NATS_INVALID_ARG) && (msg == NULL)
&& (strstr(nats_GetLastError(NULL), jsErrStreamNameRequired) != NULL));
nats_clearLastError();
test("DirectGetMsg options init bad args: ");
s = jsDirectGetMsgOptions_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Seq==0 next_by_subj(bar): ");
testCond(_checkDirectGet(js, 0, "bar", NULL, "bar", 4) == NATS_OK);
test("Seq==0 last_by_subj: ");
testCond(_checkDirectGet(js, 0, NULL, "foo", "foo", 5) == NATS_OK);
test("Seq==0 next_by_subj(foo): ");
testCond(_checkDirectGet(js, 0, "foo", NULL, "foo", 1) == NATS_OK);
test("Seq==4 next_by_subj: ");
testCond(_checkDirectGet(js, 4, "foo", NULL, "foo", 5) == NATS_OK);
test("Seq==2 next_by_subj: ");
testCond(_checkDirectGet(js, 2, "foo", NULL, "foo", 2) == NATS_OK);
test("Publish msg with header: ");
s = natsMsg_Create(&msg, "foo", NULL, "f", 1);
IFOK(s, natsMsgHeader_Add(msg, "MyHeader", "MyValue"));
IFOK(s, js_PublishMsg(NULL, js, msg, NULL, NULL));
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Check headers preserved: ");
jsDirectGetMsgOptions_Init(&dgo);
dgo.Sequence = 6;
s = js_DirectGetMsg(&msg, js, "DGM", NULL, &dgo);
testCond((s == NATS_OK)
&& (natsMsgHeader_Get(msg, "MyHeader", &val) == NATS_OK)
&& (val != NULL)
&& (strcmp(val, "MyValue") == 0));
test("Change subject header: ");
s = natsMsgHeader_Set(msg, JSSubject, "xxx");
testCond(s == NATS_OK);
test("Msg subject not affected: ");
testCond(strcmp(natsMsg_GetSubject(msg), "foo") == 0);
natsMsg_Destroy(msg);
JS_TEARDOWN;
}
static void
test_JetStreamNakWithDelay(void)
{
natsStatus s;
natsSubscription *sub= NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
natsMsg *msg = NULL;
int64_t start=0;
int64_t dur = 0;
jsOptions o;
JS_SETUP(2, 7, 2);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Publish: ");
s = js_Publish(NULL, js, "foo", "ok", 2, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Create sub: ");
s = js_SubscribeSync(&sub, js, "foo", NULL, NULL, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Get message: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK) && (msg != NULL));
test("Nak with 500ms: ");
s = natsMsg_NakWithDelay(msg, 500, NULL);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Should not be redelivered yet: ");
s = natsSubscription_NextMsg(&msg, sub, 250);
testCond((s == NATS_TIMEOUT) && (msg == NULL));
nats_clearLastError();
test("Wait for redelivery: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK) && (msg != NULL));
test("NakWithDelay with 0: ");
s = natsMsg_NakWithDelay(msg, 0, NULL);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
test("Wait for redelivery: ");
s = natsSubscription_NextMsg(&msg, sub, 250);
testCond((s == NATS_OK) && (msg != NULL));
natsSub_retain(sub);
natsSubscription_Destroy(sub);
// Stop the server...
_stopServer(pid);
pid = NATS_INVALID_PID;
test("Check ack sync with options: ");
jsOptions_Init(&o);
o.Wait = 500;
start = nats_Now();
s = natsMsg_Ack(msg, &o);
dur = nats_Now()-start;
testCond((s == NATS_TIMEOUT) && (dur > 250) && (dur < 750));
natsMsg_Destroy(msg);
natsSub_release(sub);
JS_TEARDOWN;
}
static void
test_JetStreamBackOffRedeliveries(void)
{
natsStatus s;
natsSubscription *sub= NULL;
jsErrCode jerr= 0;
jsStreamConfig sc;
natsMsg *msg = NULL;
int64_t start =0;
int64_t dur = 0;
natsInbox *inbox = NULL;
jsConsumerInfo *ci = NULL;
jsSubOptions so;
int i;
JS_SETUP(2, 7, 2);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Create inbox: ");
s = natsInbox_Create(&inbox);
testCond(s == NATS_OK);
test("Wrong MaxDeliver: ");
jsSubOptions_Init(&so);
so.Stream = "TEST";
so.Config.Durable = "backoff";
so.Config.AckPolicy = js_AckExplicit;
so.Config.DeliverPolicy = js_DeliverAll;
so.Config.DeliverSubject = inbox;
so.Config.BackOff = (int64_t[]){NATS_MILLIS_TO_NANOS(50), NATS_MILLIS_TO_NANOS(250)};
so.Config.BackOffLen = 2;
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s != NATS_OK) && (sub == NULL) && (jerr == JSConsumerMaxDeliverBackoffErr)
&& (strstr(nats_GetLastError(NULL), "max deliver is required") != NULL));
nats_clearLastError();
test("Create ok: ");
so.Config.MaxDeliver = 4;
s = js_SubscribeSync(&sub, js, "foo", NULL, &so, &jerr);
testCond((s == NATS_OK) && (sub != NULL) && (jerr == 0));
test("Send: ");
s = js_Publish(NULL, js, "foo", "ok", 2, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Consume msg: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
msg = NULL;
// We should get a redelivery at around 50ms
test("Redelivered at 50ms: ");
start = nats_Now();
s = natsSubscription_NextMsg(&msg, sub, 1000);
dur = (nats_Now()-start);
testCond((s == NATS_OK) && (dur > 25) && (dur < 100));
natsMsg_Destroy(msg);
msg = NULL;
// Now it should be every 250ms or so
for (i=0; i<2; i++)
{
test("Redelivered at 250ms: ");
start = nats_Now();
s = natsSubscription_NextMsg(&msg, sub, 1000);
dur = (nats_Now()-start);
testCond((s == NATS_OK) && (dur > 200) && (dur < 300));
natsMsg_Destroy(msg);
msg = NULL;
}
// At this point, we should have go reach MaxDeliver
test("No more: ");
s = natsSubscription_NextMsg(&msg, sub, 300);
testCond(s == NATS_TIMEOUT);
test("Check consumer info: ");
s = js_GetConsumerInfo(&ci, js, "TEST", "backoff", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (ci != NULL) && (ci->Config != NULL)
&& (ci->Config->BackOffLen == 2)
&& (ci->Config->BackOff[0] == NATS_MILLIS_TO_NANOS(50))
&& (ci->Config->BackOff[1] == NATS_MILLIS_TO_NANOS(250)));
jsConsumerInfo_Destroy(ci);
natsInbox_Destroy(inbox);
natsSubscription_Destroy(sub);
JS_TEARDOWN;
}
static void
_subjectsInfoReq(natsConnection *nc, natsMsg **msg, void *closure)
{
natsMsg *newMsg = NULL;
int *count = (int*) closure;
const char *payload = NULL;
if (strstr((*msg)->data, "stream_info_response") == NULL)
return;
(*count)++;
if (*count == 1)
{
// Pretend that we have 5 subjects and a limit of 2
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_info_response\",\"total\":5,\"offset\":0,\"limit\":2,"\
"\"config\":{\"name\":\"TEST\",\"subjects\":[\"foo.>\"]},"\
"\"state\":{\"num_subjects\":5,\"subjects\":{\"foo.bar\":1,\"foo.baz\":2}}}";
}
else if (*count == 2)
{
// Continue with the pagination
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_info_response\",\"total\":5,\"offset\":2,\"limit\":2,"\
"\"config\":{\"name\":\"TEST\",\"subjects\":[\"foo.>\"]},"\
"\"state\":{\"num_subjects\":5,\"subjects\":{\"foo.bat\":3,\"foo.box\":4}}}";
}
else if (*count == 3)
{
// Pretend that the number went down in between page requests and the
// last request got nothing in return.
payload = "{\"type\":\"io.nats.jetstream.api.v1.stream_info_response\",\"total\":3,\"offset\":3,\"limit\":2,"\
"\"config\":{\"name\":\"TEST\",\"subjects\":[\"foo.>\"]},"\
"\"state\":{\"num_subjects\":3}}";
}
else
{
// Keep the original message
return;
}
if (natsMsg_create(&newMsg, (*msg)->subject, (int) strlen((*msg)->subject),
NULL, 0, payload, (int) strlen(payload), 0) == NATS_OK)
{
natsMsg_Destroy(*msg);
*msg = newMsg;
}
}
static void
test_JetStreamInfoWithSubjects(void)
{
natsStatus s;
jsStreamInfo *si = NULL;
jsStreamConfig cfg;
jsErrCode jerr = 0;
jsOptions o;
int count = 0;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
JS_SETUP(2, 9, 0);
test("Create stream: ");
jsStreamConfig_Init(&cfg);
cfg.Name = "TEST";
cfg.Subjects = (const char*[1]){"foo.>"};
cfg.SubjectsLen = 1;
s = js_AddStream(NULL, js, &cfg, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Send to different subjects: ");
s = js_Publish(NULL, js, "foo.bar", "m1", 2, NULL, &jerr);
IFOK(s, js_Publish(NULL, js, "foo.baz", "m1", 2, NULL, &jerr));
IFOK(s, js_Publish(NULL, js, "foo.baz", "m2", 2, NULL, &jerr));
IFOK(s, js_Publish(NULL, js, "foo.baz.bat", "m1", 2, NULL, &jerr));
IFOK(s, js_Publish(NULL, js, "foo.baz.bat", "m2", 2, NULL, &jerr));
IFOK(s, js_Publish(NULL, js, "foo.baz.bat", "m3", 2, NULL, &jerr));
testCond(s == NATS_OK);
test("Check number subjects: ");
s = js_GetStreamInfo(&si, js, "TEST", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3));
jsStreamInfo_Destroy(si);
si = NULL;
test("Get subjects list (no match): ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = "none";
s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3)
&& (si->State.Subjects == NULL));
jsStreamInfo_Destroy(si);
si = NULL;
test("Get subjects list (1 match): ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = "foo.bar";
s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3)
&& (si->State.Subjects != NULL)
&& (si->State.Subjects->Count == 1)
&& (strcmp(si->State.Subjects->List[0].Subject, "foo.bar") == 0)
&& (si->State.Subjects->List[0].Msgs == 1));
jsStreamInfo_Destroy(si);
si = NULL;
test("Get subjects list (all matches): ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = "foo.>";
s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr);
if ((s == NATS_OK) && ((si == NULL) || (si->State.Subjects == NULL) || (si->State.Subjects->Count != 3)))
s = NATS_ERR;
else
{
int i;
bool got1, got2, got3;
for (i=0; i<si->State.Subjects->Count; i++)
{
jsStreamStateSubject *subj = &(si->State.Subjects->List[i]);
if ((strcmp(subj->Subject, "foo.bar") == 0) && (subj->Msgs == 1))
got1 = true;
else if ((strcmp(subj->Subject, "foo.baz") == 0) && (subj->Msgs == 2))
got2 = true;
else if ((strcmp(subj->Subject, "foo.baz.bat") == 0) && (subj->Msgs == 3))
got3 = true;
}
s = (got1 && got2 && got3 ? NATS_OK : NATS_ERR);
}
testCond((s == NATS_OK) && (jerr == 0) && (si->State.NumSubjects == 3));
jsStreamInfo_Destroy(si);
si = NULL;
test("Create sub for pagination check: ");
s = natsConnection_SubscribeSync(&sub, nc, "$JS.API.STREAM.INFO.TEST");
testCond(s == NATS_OK);
natsConn_setFilterWithClosure(nc, _subjectsInfoReq, (void*) &count);
test("Get all subjects with pagination: ");
jsOptions_Init(&o);
o.Stream.Info.SubjectsFilter = ">";
s = js_GetStreamInfo(&si, js, "TEST", &o, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->State.NumSubjects == 3)
&& (si->State.Subjects != NULL)
&& (si->State.Subjects->List != NULL)
&& (si->State.Subjects->Count == 4));
jsStreamInfo_Destroy(si);
si = NULL;
natsConn_setFilter(nc, NULL);
test("Check 1st request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset") == NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 2nd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":2") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
test("Check 3rd request: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond((s == NATS_OK)
&& (strstr(natsMsg_GetData(msg), "offset\":4") != NULL));
natsMsg_Destroy(msg);
msg = NULL;
natsSubscription_Destroy(sub);
JS_TEARDOWN;
}
static natsStatus
_checkJSClusterReady(const char *url)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
jsCtx *js = NULL;
jsErrCode jerr= 0;
int i;
jsOptions jo;
jsOptions_Init(&jo);
jo.Wait = 1000;
s = natsConnection_ConnectTo(&nc, url);
IFOK(s, natsConnection_JetStream(&js, nc, &jo));
for (i=0; (s == NATS_OK) && (i<10); i++)
{
jsStreamInfo *si = NULL;
s = js_GetStreamInfo(&si, js, "CHECK_CLUSTER", &jo, &jerr);
if (jerr == JSStreamNotFoundErr)
{
nats_clearLastError();
s = NATS_OK;
break;
}
if ((s != NATS_OK) && (i < 9))
{
s = NATS_OK;
nats_Sleep(500);
}
}
jsCtx_Destroy(js);
natsConnection_Destroy(nc);
return s;
}
static void
test_JetStreamInfoAlternates(void)
{
char datastore1[256] = {'\0'};
char datastore2[256] = {'\0'};
char datastore3[256] = {'\0'};
char cmdLine[1024] = {'\0'};
natsPid pid1 = NATS_INVALID_PID;
natsPid pid2 = NATS_INVALID_PID;
natsPid pid3 = NATS_INVALID_PID;
natsConnection *nc = NULL;
jsCtx *js = NULL;
jsStreamInfo *si = NULL;
jsStreamConfig sc;
jsStreamSource ss;
natsStatus s;
ENSURE_JS_VERSION(2, 9, 0);
test("Start cluster: ");
_makeUniqueDir(datastore1, sizeof(datastore1), "datastore_");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -cluster_name abc -server_name A -cluster nats://127.0.0.1:6222 -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223,nats://127.0.0.1:6224 -p 4222", datastore1);
pid1 = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid1);
_makeUniqueDir(datastore2, sizeof(datastore2), "datastore_");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -cluster_name abc -server_name B -cluster nats://127.0.0.1:6223 -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223,nats://127.0.0.1:6224 -p 4223", datastore2);
pid2 = _startServer("nats://127.0.0.1:4223", cmdLine, true);
CHECK_SERVER_STARTED(pid1);
_makeUniqueDir(datastore3, sizeof(datastore3), "datastore_");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -cluster_name abc -server_name C -cluster nats://127.0.0.1:6224 -routes nats://127.0.0.1:6222,nats://127.0.0.1:6223,nats://127.0.0.1:6224 -p 4224", datastore3);
pid3 = _startServer("nats://127.0.0.1:4224", cmdLine, true);
CHECK_SERVER_STARTED(pid1);
testCond(true);
test("Check cluster: ");
s = _checkJSClusterReady("nats://127.0.0.1:4224");
testCond(s == NATS_OK);
test("Connect: ");
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, NULL);
testCond(s == NATS_OK);
test("Create stream: ");
jsStreamConfig_Init(&sc);
sc.Name = "TEST";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, NULL);
testCond(s == NATS_OK);
test("Create mirror: ");
jsStreamConfig_Init(&sc);
sc.Name = "MIRROR";
jsStreamSource_Init(&ss);
ss.Name = "TEST";
sc.Mirror = &ss;
s = js_AddStream(NULL, js, &sc, NULL, NULL);
testCond(s == NATS_OK);
test("Check for alternate: ");
s = js_GetStreamInfo(&si, js, "TEST", NULL, NULL);
testCond((s == NATS_OK) && (si != NULL) && (si->AlternatesLen == 2));
test("Check alternate content: ");
if ((strcmp(si->Alternates[0]->Cluster, "abc") != 0)
|| (strcmp(si->Alternates[1]->Cluster, "abc") != 0))
{
s = NATS_ERR;
}
else if (((strcmp(si->Alternates[0]->Name, "TEST") == 0) && (strcmp(si->Alternates[1]->Name, "MIRROR") != 0))
|| ((strcmp(si->Alternates[0]->Name, "MIRROR") == 0) && (strcmp(si->Alternates[1]->Name, "TEST") != 0)))
{
s = NATS_ERR;
}
testCond(s == NATS_OK);
jsStreamInfo_Destroy(si);
jsCtx_Destroy(js);
natsConnection_Destroy(nc);
_stopServer(pid3);
_stopServer(pid2);
_stopServer(pid1);
rmtree(datastore1);
rmtree(datastore2);
rmtree(datastore3);
}
static void
test_KeyValueManager(void)
{
natsStatus s;
kvStore *kv = NULL;
kvConfig kvc;
jsStreamConfig sc;
jsErrCode jerr = 0;
jsStreamInfo *si = NULL;
JS_SETUP(2, 7, 2);
test("kvConfig Init (bad args): ");
s = kvConfig_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Create KV - bad args: ");
kvConfig_Init(&kvc);
kvc.Bucket = "TEST";
s = js_CreateKeyValue(NULL, js, &kvc);
if (s == NATS_INVALID_ARG)
s = js_CreateKeyValue(&kv, NULL, &kvc);
if (s == NATS_INVALID_ARG)
s = js_CreateKeyValue(&kv, js, NULL);
testCond((s == NATS_INVALID_ARG) && (kv == NULL));
nats_clearLastError();
test("Create KV - bad bucket name: ");
kvConfig_Init(&kvc);
kvc.Bucket = "This.is.not.a.valid.name!";
s = js_CreateKeyValue(&kv, js, &kvc);
testCond((s == NATS_INVALID_ARG) && (kv == NULL)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidBucketName) != NULL));
nats_clearLastError();
test("Create KV - history too big: ");
kvConfig_Init(&kvc);
kvc.Bucket = "TEST";
kvc.History = kvMaxHistory + 10;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond((s == NATS_INVALID_ARG) && (kv == NULL)
&& (strstr(nats_GetLastError(NULL), kvErrHistoryTooLarge) != NULL));
nats_clearLastError();
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "TEST";
kvc.History = 3;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond((s == NATS_OK) && (kv != NULL));
test("Check discard policy: ");
s = js_GetStreamInfo(&si, js, "KV_TEST", NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0) && (si != NULL) && (si->Config != NULL)
&& (si->Config->Discard == js_DiscardNew));
jsStreamInfo_Destroy(si);
si = NULL;
test("Destroy kv store: ")
kvStore_Destroy(kv);
kv = NULL;
// Check that this is ok
kvStore_Destroy(NULL);
testCond(true);
test("Bind (bad args): ");
s = js_KeyValue(NULL, js, "TEST");
if (s == NATS_INVALID_ARG)
s = js_KeyValue(&kv, NULL, "TEST");
if (s == NATS_INVALID_ARG)
s = js_KeyValue(&kv, js, NULL);
if (s == NATS_INVALID_ARG)
s = js_KeyValue(&kv, NULL, "");
if (s == NATS_INVALID_ARG)
s = js_KeyValue(&kv, NULL, "bad.bucket.name");
testCond((s == NATS_INVALID_ARG) && (kv == NULL));
nats_clearLastError();
test("Bind (not found): ");
s = js_KeyValue(&kv, js, "NOT_FOUND");
testCond((s == NATS_NOT_FOUND) && (kv == NULL)
&& (nats_GetLastError(NULL) == NULL));
test("Bind to existing: ");
s = js_KeyValue(&kv, js, "TEST");
testCond((s == NATS_OK) && (kv != NULL));
test("Destroy kv store: ")
kvStore_Destroy(kv);
kv = NULL;
testCond(true);
test("Create non-kv stream: ");
jsStreamConfig_Init(&sc);
// Stream name has to start with "KV_" since this is how we
// form the stream name: KV_ + bucket name.
sc.Name = "KV_NON_KV_STREAM";
sc.Subjects = (const char*[1]){"foo"};
sc.SubjectsLen = 1;
s = js_AddStream(NULL, js, &sc, NULL, &jerr);
testCond((s == NATS_OK) && (jerr == 0));
test("Bind to non-kv stream: ");
s = js_KeyValue(&kv, js, "NON_KV_STREAM");
testCond((s == NATS_INVALID_ARG) && (kv == NULL)
&& (strstr(nats_GetLastError(NULL), kvErrBadBucket) != NULL));
nats_clearLastError();
test("Delete kv store (bad args): ");
s = js_DeleteKeyValue(NULL, "TEST");
if (s == NATS_INVALID_ARG)
s = js_DeleteKeyValue(js, NULL);
if (s == NATS_INVALID_ARG)
s = js_DeleteKeyValue(js, "");
if (s == NATS_INVALID_ARG)
s = js_DeleteKeyValue(js, "bad.bucket.name");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Delete kv store: ");
s = js_DeleteKeyValue(js, "TEST");
testCond(s == NATS_OK);
test("Check it is gone (bind should fail): ");
s = js_KeyValue(&kv, js, "TEST");
testCond((s == NATS_NOT_FOUND) && (kv == NULL));
JS_TEARDOWN;
}
static void
test_KeyValueBasics(void)
{
natsStatus s;
kvStore *kv = NULL;
kvEntry *e = NULL;
kvStatus *sts= NULL;
uint64_t rev = 0;
kvConfig kvc;
int iterMax = 1;
int i;
char bucketName[10];
JS_SETUP(2, 6, 2);
if (serverVersionAtLeast(2, 9, 0)) {
iterMax = 2;
}
for (i=0; i<iterMax; i++)
{
sprintf(bucketName, "TEST%d", i);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = bucketName;
kvc.History = 5;
kvc.TTL = NATS_SECONDS_TO_NANOS(3600);
kvc.Replicas = 1;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond((s == NATS_OK) && (kv != NULL));
if (i == 1)
{
// This means that we are running against 2.9.0+ server and
// so we want to try without "direct get", so we will
// artificially set the kv store to not use direct get API.
natsMutex_Lock(kv->mu);
kv->useDirect = false;
natsMutex_Unlock(kv->mu);
}
test("Check bucket: ");
s = (strcmp(kvStore_Bucket(kv), bucketName) == 0 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Check bucket (returns NULL): ");
s = (kvStore_Bucket(NULL) == NULL ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Check bytes is 0: ");
s = kvStore_Status(&sts, kv);
IFOK(s, (kvStatus_Bytes(sts) == 0 ? NATS_OK : NATS_ERR));
testCond(s == NATS_OK);
kvStatus_Destroy(sts);
sts = NULL;
test("Simple put (bad args): ");
rev = 1234;
s = kvStore_Put(&rev, NULL, "key", (const void*) "value", 5);
testCond((s == NATS_INVALID_ARG) && (rev == 0));
nats_clearLastError();
test("Simple put (bad key): ");
rev = 1234;
s = kvStore_Put(&rev, kv, NULL, (const void*) "value", 5);
if (s == NATS_INVALID_ARG)
s = kvStore_Put(&rev, kv, "", (const void*) "value", 5);
if (s == NATS_INVALID_ARG)
s = kvStore_Put(&rev, kv, ".bad.key", (const void*) "value", 5);
if (s == NATS_INVALID_ARG)
s = kvStore_Put(&rev, kv, "bad.key.", (const void*) "value", 5);
if (s == NATS_INVALID_ARG)
s = kvStore_Put(&rev, kv, "this is a bad key", (const void*) "value", 5);
testCond((s == NATS_INVALID_ARG) && (rev == 0)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL));
nats_clearLastError();
test("Simple put: ");
s = kvStore_Put(&rev, kv, "key", (const void*) "value", 5);
testCond((s == NATS_OK) && (rev == 1));
test("Get (bad args): ");
s = kvStore_Get(NULL, kv, "key");
if (s == NATS_INVALID_ARG)
s = kvStore_Get(&e, NULL, "key");
testCond((s == NATS_INVALID_ARG) && (e == NULL));
nats_clearLastError();
test("Get (bad key): ");
s = kvStore_Get(&e, kv, NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_Get(&e, kv, "");
if (s == NATS_INVALID_ARG)
s = kvStore_Get(&e, kv, ".bad.key");
if (s == NATS_INVALID_ARG)
s = kvStore_Get(&e, kv, "bad.key.");
if (s == NATS_INVALID_ARG)
s = kvStore_Get(&e, kv, "this is a bad key");
testCond((s == NATS_INVALID_ARG) && (e == NULL)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL));
nats_clearLastError();
test("Simple get: ");
s = kvStore_Get(&e, kv, "key");
testCond((s == NATS_OK) && (e != NULL)
&& (kvEntry_ValueLen(e) == 5)
&& (memcmp(kvEntry_Value(e), "value", 5) == 0)
&& (kvEntry_Revision(e) == 1));
test("Destroy entry: ");
kvEntry_Destroy(e);
e = NULL;
// Check that this is ok
kvEntry_Destroy(NULL);
testCond(true);
test("Get not found: ");
s = kvStore_Get(&e, kv, "not.found");
testCond((s == NATS_NOT_FOUND) && (e == NULL)
&& (nats_GetLastError(NULL) == NULL));
test("Simple put string: ");
s = kvStore_PutString(&rev, kv, "key", "value2");
testCond((s == NATS_OK) && (rev == 2));
test("Simple get string: ");
s = kvStore_Get(&e, kv, "key");
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_ValueString(e), "value2") == 0)
&& (kvEntry_Revision(e) == 2));
test("Destroy entry: ");
kvEntry_Destroy(e);
e = NULL;
testCond(true);
test("Get revision (bad args): ");
s = kvStore_GetRevision(&e, kv, "key", 0);
testCond((s == NATS_INVALID_ARG) && (e == NULL)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidRevision) != NULL));
nats_clearLastError();
test("Get revision: ");
s = kvStore_GetRevision(&e, kv, "key", 1);
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_ValueString(e), "value") == 0)
&& (kvEntry_Revision(e) == 1));
test("Destroy entry: ");
kvEntry_Destroy(e);
e = NULL;
testCond(true);
test("Delete key (bad args): ");
s = kvStore_Delete(NULL, "key");
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Delete key (bad key): ");
s = kvStore_Delete(kv, NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_Delete(kv, "");
if (s == NATS_INVALID_ARG)
s = kvStore_Delete(kv, ".bad.key");
if (s == NATS_INVALID_ARG)
s = kvStore_Delete(kv, "bad.key.");
if (s == NATS_INVALID_ARG)
s = kvStore_Delete(kv, "this is a bad key");
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL));
nats_clearLastError();
test("Delete key: ");
s = kvStore_Delete(kv, "key");
testCond(s == NATS_OK);
test("Check key gone: ");
s = kvStore_Get(&e, kv, "key");
testCond((s == NATS_NOT_FOUND) && (e == NULL)
&& (nats_GetLastError(NULL) == NULL));
test("Create (bad args): ");
s = kvStore_Create(&rev, NULL, "key", (const void*) "value3", 6);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Create (bad key): ");
s = kvStore_Create(&rev, kv, NULL, (const void*) "value3", 6);
if (s == NATS_INVALID_ARG)
s = kvStore_Create(&rev, kv, "", (const void*) "value3", 6);
if (s == NATS_INVALID_ARG)
s = kvStore_Create(&rev, kv, ".bad.key", (const void*) "value3", 6);
if (s == NATS_INVALID_ARG)
s = kvStore_Create(&rev, kv, "bad.key.", (const void*) "value3", 6);
if (s == NATS_INVALID_ARG)
s = kvStore_Create(&rev, kv, "this..is a bad key", (const void*) "value3", 6);
testCond((s == NATS_INVALID_ARG)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL));
nats_clearLastError();
test("Create: ");
s = kvStore_Create(&rev, kv, "key", (const void*) "value3", 6);
testCond((s == NATS_OK) && (rev == 4));
test("Create fail, since already exists: ");
s = kvStore_Create(&rev, kv, "key", (const void*) "value4", 6);
testCond((s == NATS_ERR) && (rev == 0));
nats_clearLastError();
test("Update (bad args): ");
s = kvStore_Update(&rev, NULL, "key", (const void*) "value4", 6, 4);
testCond((s == NATS_INVALID_ARG) && (rev == 0));
nats_clearLastError();
test("Update (bad key): ");
s = kvStore_Update(&rev, kv, NULL, (const void*) "value4", 6, 4);
if (s == NATS_INVALID_ARG)
s = kvStore_Update(&rev, kv, "", (const void*) "value4", 6, 4);
if (s == NATS_INVALID_ARG)
s = kvStore_Update(&rev, kv, ".bad.key", (const void*) "value4", 6, 4);
if (s == NATS_INVALID_ARG)
s = kvStore_Update(&rev, kv, "bad.key.", (const void*) "value4", 6, 4);
if (s == NATS_INVALID_ARG)
s = kvStore_Update(&rev, kv, "bad&key", (const void*) "value4", 6, 4);
testCond((s == NATS_INVALID_ARG) && (rev == 0)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidKey) != NULL));
nats_clearLastError();
test("Update: ");
s = kvStore_Update(&rev, kv, "key", (const void*) "value4", 6, 4);
testCond((s == NATS_OK) && (rev == 5));
test("Update fail because wrong rev: ");
s = kvStore_Update(NULL, kv, "key", (const void*) "value5", 6, 4);
testCond(s == NATS_ERR);
nats_clearLastError();
test("Update ok: ");
s = kvStore_Update(&rev, kv, "key", (const void*) "value5", 6, rev);
testCond((s == NATS_OK) && (rev == 6));
nats_clearLastError();
test("Create (string): ");
s = kvStore_CreateString(&rev, kv, "key2", "value1");
testCond((s == NATS_OK) && (rev == 7));
test("Update ok (string): ");
s = kvStore_UpdateString(&rev, kv, "key2", "value2", rev);
testCond((s == NATS_OK) && (rev == 8));
test("Status (bad args): ");
s = kvStore_Status(NULL, kv);
if (s == NATS_INVALID_ARG)
s = kvStore_Status(&sts, NULL);
testCond((s == NATS_INVALID_ARG) && (sts == NULL));
nats_clearLastError();
test("Status: ");
s = kvStore_Status(&sts, kv);
testCond((s == NATS_OK) && (sts != NULL));
test("Check history: ");
s = (kvStatus_History(sts) == 5 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Check bucket: ");
s = (strcmp(kvStatus_Bucket(sts), bucketName) == 0 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Check TTL: ");
s = (kvStatus_TTL(sts) == NATS_SECONDS_TO_NANOS(3600) ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Check values: ");
s = (kvStatus_Values(sts) == 7 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Check replicas: ");
s = (kvStatus_Replicas(sts) == 1 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
test("Check bytes: ");
{
jsStreamInfo *si = NULL;
if (i == 0)
s = js_GetStreamInfo(&si, js, "KV_TEST0", NULL, NULL);
else
s = js_GetStreamInfo(&si, js, "KV_TEST1", NULL, NULL);
IFOK(s, (kvStatus_Bytes(sts) == si->State.Bytes ? NATS_OK : NATS_ERR));
jsStreamInfo_Destroy(si);
}
testCond(s == NATS_OK);
test("Check status with NULL: ");
if ((kvStatus_History(NULL) != 0) || (kvStatus_Bucket(NULL) != NULL)
|| (kvStatus_TTL(NULL) != 0) || (kvStatus_Values(NULL) != 0)
|| (kvStatus_Bytes(NULL) != 0))
{
s = NATS_ERR;
}
testCond(s == NATS_OK);
test("Destroy status: ");
kvStatus_Destroy(sts);
sts = NULL;
// Check that this is ok
kvStatus_Destroy(NULL);
testCond(true);
test("Put for revision check: ");
s = kvStore_PutString(&rev, kv, "test.rev.one", "val1");
IFOK(s, kvStore_PutString(NULL, kv, "test.rev.two", "val2"));
testCond(s == NATS_OK);
test("Get revision (bad args): ");
s = kvStore_GetRevision(&e, kv, "test.rev.one", 0);
testCond((s == NATS_INVALID_ARG) && (e == NULL)
&& (strstr(nats_GetLastError(NULL), kvErrInvalidRevision) != NULL));
nats_clearLastError();
test("Get revision: ");
s = kvStore_GetRevision(&e, kv, "test.rev.one", rev);
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_ValueString(e), "val1") == 0)
&& (kvEntry_Revision(e) == rev));
kvEntry_Destroy(e);
e = NULL;
test("Get wrong revision for the key: ");
s = kvStore_GetRevision(&e, kv, "test.rev.two", rev);
testCond((s == NATS_NOT_FOUND) && (e == NULL));
test("Destroy kv store: ");
kvStore_Destroy(kv);
testCond(true);
kv = NULL;
}
JS_TEARDOWN;
}
static bool
_expectInitDone(kvWatcher *w)
{
natsStatus s;
kvEntry *e = NULL;
test("Check init done: ");
s = kvWatcher_Next(&e, w, 1000);
return ((s == NATS_OK) && (e == NULL));
}
static bool
_expectUpdate(kvWatcher *w, const char *key, const char *val, uint64_t rev)
{
natsStatus s;
kvEntry *e = NULL;
test("Check update: ");
s = kvWatcher_Next(&e, w, 1000);
if ((s != NATS_OK) || (e == NULL))
return false;
if ((strcmp(kvEntry_Bucket(e), "WATCH") != 0)
|| (strcmp(kvEntry_Key(e), key) != 0)
|| (strcmp(kvEntry_ValueString(e), val) != 0)
|| (kvEntry_Revision(e) != rev)
|| (kvEntry_Created(e) == 0))
{
return false;
}
kvEntry_Destroy(e);
return true;
}
static bool
_expectDelete(kvWatcher *w, const char *key, uint64_t rev)
{
natsStatus s;
kvEntry *e = NULL;
test("Check update: ");
s = kvWatcher_Next(&e, w, 1000);
if ((s != NATS_OK) || (e == NULL))
return false;
if ((kvEntry_Operation(e) != kvOp_Delete)
|| (kvEntry_Revision(e) != rev))
{
return false;
}
kvEntry_Destroy(e);
return true;
}
static void
_stopWatcher(void *closure)
{
kvWatcher *w = (kvWatcher*) closure;
nats_Sleep(100);
kvWatcher_Stop(w);
}
static void
test_KeyValueWatch(void)
{
natsStatus s;
kvStore *kv = NULL;
kvWatcher *w = NULL;
kvEntry *e = NULL;
natsThread *t = NULL;
int plc = 0;
int plb = 0;
kvConfig kvc;
int64_t start;
JS_SETUP(2, 6, 2);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "WATCH";
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Create watcher (bad args): ");
s = kvStore_Watch(NULL, kv, "foo", NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_Watch(&w, NULL, "foo", NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_Watch(&w, kv, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_Watch(&w, kv, "", NULL);
testCond((s == NATS_INVALID_ARG) && (w == NULL));
nats_clearLastError();
test("Create watcher: ");
s = kvStore_WatchAll(&w, kv, NULL);
testCond((s == NATS_OK) && (w != NULL));
testCond(_expectInitDone(w));
test("Create: ");
s = kvStore_CreateString(NULL, kv, "name", "derek");
testCond(s == NATS_OK);
testCond(_expectUpdate(w, "name", "derek", 1));
test("Put: ");
s = kvStore_PutString(NULL, kv, "name", "rip");
testCond(s == NATS_OK);
testCond(_expectUpdate(w, "name", "rip", 2));
test("Put: ");
s = kvStore_PutString(NULL, kv, "name", "ik");
testCond(s == NATS_OK);
testCond(_expectUpdate(w, "name", "ik", 3));
test("Put: ");
s = kvStore_PutString(NULL, kv, "age", "22");
testCond(s == NATS_OK);
testCond(_expectUpdate(w, "age", "22", 4));
test("Put: ");
s = kvStore_PutString(NULL, kv, "age", "33");
testCond(s == NATS_OK);
testCond(_expectUpdate(w, "age", "33", 5));
test("Delete: ");
s = kvStore_Delete(kv, "age");
testCond(s == NATS_OK);
testCond(_expectDelete(w, "age", 6));
test("Next (bad args): ");
s = kvWatcher_Next(NULL, w, 1000);
if (s == NATS_INVALID_ARG)
s = kvWatcher_Next(&e, NULL, 1000);
if (s == NATS_INVALID_ARG)
s = kvWatcher_Next(&e, w, 0);
if (s == NATS_INVALID_ARG)
s = kvWatcher_Next(&e, w, -1000);
testCond((s == NATS_INVALID_ARG) && (e == NULL));
nats_clearLastError();
test("Next (timeout): ");
s = kvWatcher_Next(&e, w, 1);
testCond((s == NATS_TIMEOUT) && (e == NULL));
nats_clearLastError();
test("Stop (bad args): ");
s = kvWatcher_Stop(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Stop: ");
s = kvWatcher_Stop(w);
testCond(s == NATS_OK);
test("Stop again: ");
s = kvWatcher_Stop(w);
testCond(s == NATS_OK);
test("Next fails: ");
s = kvWatcher_Next(&e, w, 1000);
testCond((s == NATS_ILLEGAL_STATE) && (e == NULL))
nats_clearLastError();
test("Destroy watcher: ");
kvWatcher_Destroy(w);
w = NULL;
// Check that this is ok
kvWatcher_Destroy(NULL);
testCond(true);
// Now try wildcard matching and make sure we only get last value when starting.
test("Put values in different keys: ");
s = kvStore_PutString(NULL, kv, "t.name", "derek");
IFOK(s, kvStore_PutString(NULL, kv, "t.name", "ik"));
IFOK(s, kvStore_PutString(NULL, kv, "t.age", "22"));
IFOK(s, kvStore_PutString(NULL, kv, "t.age", "49"));
testCond(s == NATS_OK);
test("Create watcher: ");
s = kvStore_Watch(&w, kv, "t.*", NULL);
testCond(s == NATS_OK);
test("Check pending limits: ");
natsMutex_Lock(w->mu);
s = natsSubscription_GetPendingLimits(w->sub, &plc, &plb);
natsMutex_Unlock(w->mu);
testCond((s == NATS_OK) && (plc == -1) && (plb == -1));
testCond(_expectUpdate(w, "t.name", "ik", 8));
testCond(_expectUpdate(w, "t.age", "49", 10));
testCond(_expectInitDone(w));
test("Block: ");
start = nats_Now();
s = natsThread_Create(&t, _stopWatcher, (void*) w);
IFOK(s, kvWatcher_Next(&e, w, 10000));
testCond((s == NATS_ILLEGAL_STATE) && (e == NULL)
&& ((nats_Now() - start) <= 1000));
nats_clearLastError();
natsThread_Join(t);
natsThread_Destroy(t);
kvWatcher_Destroy(w);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValueHistory(void)
{
natsStatus s;
kvStore *kv = NULL;
kvEntry *e = NULL;
kvEntryList l;
kvWatchOptions o;
kvConfig kvc;
int i;
JS_SETUP(2, 6, 2);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "WATCH";
kvc.History = 10;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Populate: ");
for (i=0; (s == NATS_OK) && (i<50); i++)
{
char tmp[16];
snprintf(tmp, sizeof(tmp), "%d", i+22);
s = kvStore_PutString(NULL, kv, "age", tmp);
}
testCond(s == NATS_OK);
test("Get history (bad args): ");
s = kvStore_History(NULL, kv, "age", NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_History(&l, NULL, "age", NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_History(&l, kv, NULL, NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_History(&l, kv, "", NULL);
testCond((s == NATS_INVALID_ARG) && (l.Entries == NULL) && (l.Count == 0));
nats_clearLastError();
test("Get history (timeout): ");
kvWatchOptions_Init(&o);
o.Timeout = 1;
s = kvStore_History(&l, kv, "age", &o);
testCond(((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 10))
|| ((s == NATS_TIMEOUT) && (l.Entries == NULL) && (l.Count == 0)));
nats_clearLastError();
kvEntryList_Destroy(&l);
test("Get History: ");
s = kvStore_History(&l, kv, "age", NULL);
testCond((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 10));
test("Check values: ");
for (i=0; (s == NATS_OK) && (i < 10); i++)
{
e = l.Entries[i];
if (e == NULL)
s = NATS_ERR;
if (strcmp(kvEntry_Key(e), "age") != 0)
s = NATS_ERR;
else if (kvEntry_Revision(e) != (uint64_t)(i+41))
s = NATS_ERR;
else
{
int val = (int) nats_ParseInt64(kvEntry_Value(e), kvEntry_ValueLen(e));
if (val != i+62)
s = NATS_ERR;
}
}
testCond(s == NATS_OK);
test("Destroy list: ")
kvEntryList_Destroy(&l);
testCond((l.Entries == NULL) && (l.Count == 0));
// Check that this is ok
kvEntryList_Destroy(NULL);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValueKeys(void)
{
natsStatus s;
kvStore *kv = NULL;
kvKeysList l;
bool nameOK = false;
bool ageOK = false;
bool countryOK = false;
char *k = NULL;
kvConfig kvc;
kvWatchOptions o;
int i;
JS_SETUP(2, 6, 2);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "KVS";
kvc.History = 2;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Populate: ");
s = kvStore_PutString(NULL, kv, "name", "derek");
IFOK(s, kvStore_PutString(NULL, kv, "age", "22"));
IFOK(s, kvStore_PutString(NULL, kv, "country", "US"));
IFOK(s, kvStore_PutString(NULL, kv, "name", "ivan"));
IFOK(s, kvStore_PutString(NULL, kv, "age", "33"));
IFOK(s, kvStore_PutString(NULL, kv, "country", "US"));
IFOK(s, kvStore_PutString(NULL, kv, "name", "rip"));
IFOK(s, kvStore_PutString(NULL, kv, "age", "44"));
IFOK(s, kvStore_PutString(NULL, kv, "country", "MT"));
testCond(s == NATS_OK);
test("Get keys (bad args): ");
s = kvStore_Keys(NULL, kv, NULL);
if (s == NATS_INVALID_ARG)
s = kvStore_Keys(&l, NULL, NULL);
testCond((s == NATS_INVALID_ARG) && (l.Keys == NULL) && (l.Count == 0));
nats_clearLastError();
test("Get keys (timeout): ");
kvWatchOptions_Init(&o);
o.Timeout = 1;
s = kvStore_Keys(&l, kv, &o);
testCond((((s == NATS_OK) && (l.Keys != NULL) && (l.Count == 3)))
|| ((s == NATS_TIMEOUT) && (l.Keys == NULL) && (l.Count == 0)));
nats_clearLastError();
kvKeysList_Destroy(&l);
test("Get keys: ");
s = kvStore_Keys(&l, kv, NULL);
testCond((s == NATS_OK) && (l.Keys != NULL) && (l.Count == 3));
test("Check keys: ");
for (i=0; (s == NATS_OK) && (i<3); i++)
{
char *k = l.Keys[i];
if (k == NULL)
s = NATS_ERR;
else
{
if (strcmp(k, "name") == 0)
nameOK = true;
else if (strcmp(k, "age") == 0)
ageOK = true;
else if (strcmp(k, "country") == 0)
countryOK = true;
}
}
testCond((s == NATS_OK) && nameOK && ageOK && countryOK);
test("Destroy list: ");
kvKeysList_Destroy(&l);
testCond((l.Keys == NULL) && (l.Count == 0));
// Check this is ok
kvKeysList_Destroy(NULL);
// Make sure delete and purge do the right thing and not return the keys.
test("Delete name: ");
s = kvStore_Delete(kv, "name");
testCond(s == NATS_OK);
test("Purge country: ");
s = kvStore_Purge(kv, "country", NULL);
testCond(s == NATS_OK);
test("Get keys: ");
s = kvStore_Keys(&l, kv, NULL);
testCond((s == NATS_OK) && (l.Keys != NULL) && (l.Count == 1));
test("Check key: ");
k = l.Keys[0];
s = ((k != NULL) && (strcmp(k, "age") == 0) ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
kvKeysList_Destroy(&l);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValueDeleteVsPurge(void)
{
natsStatus s;
kvStore *kv = NULL;
kvEntry *e = NULL;
kvEntryList l;
kvConfig kvc;
int i;
JS_SETUP(2, 6, 2);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "KVS";
kvc.History = 10;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Populate: ");
s = kvStore_PutString(NULL, kv, "name", "derek");
IFOK(s, kvStore_PutString(NULL, kv, "age", "22"));
IFOK(s, kvStore_PutString(NULL, kv, "name", "ivan"));
IFOK(s, kvStore_PutString(NULL, kv, "age", "33"));
IFOK(s, kvStore_PutString(NULL, kv, "name", "rip"));
IFOK(s, kvStore_PutString(NULL, kv, "age", "44"));
testCond(s == NATS_OK);
test("Delete age: ");
s = kvStore_Delete(kv, "age");
testCond(s == NATS_OK);
test("Get age history: ");
s = kvStore_History(&l, kv, "age", NULL);
testCond((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 4));
test("Check: ");
for (i=0;(s == NATS_OK) && (i<4); i++)
{
e = l.Entries[i];
if (e == NULL)
s = NATS_ERR;
else if ((int) kvEntry_Delta(e) != (3-i))
s = NATS_ERR;
}
testCond(s == NATS_OK);
kvEntryList_Destroy(&l);
test("Purge name: ");
s = kvStore_Purge(kv, "name", NULL);
testCond(s == NATS_OK);
test("Check marker: ");
s = kvStore_Get(&e, kv, "age");
testCond((s == NATS_NOT_FOUND) && (e == NULL));
test("Get history: ");
s = kvStore_History(&l, kv, "name", NULL);
testCond((s == NATS_OK) && (l.Entries != NULL) && (l.Count == 1));
test("Check: ");
e = l.Entries[0];
s = ((e != NULL) && (kvEntry_Operation(e) == kvOp_Purge) ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
kvEntryList_Destroy(&l);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValueDeleteTombstones(void)
{
natsStatus s;
kvStore *kv = NULL;
char *v = NULL;
jsStreamInfo *si = NULL;
kvConfig kvc;
kvPurgeOptions po;
int i;
JS_SETUP(2, 6, 2);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "KVS";
kvc.History = 10;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Create asset: ");
v = (char*) malloc(100);
if (v != NULL)
{
for (i=0; i<100; i++)
v[i] = 'A' + (char)(i % 26);
v[99] = '\0';
}
testCond(v != NULL);
test("Populate: ");
for (i=0; (s == NATS_OK) && (i<100); i++)
{
char tmp[64];
snprintf(tmp, sizeof(tmp), "key-%d", i);
s = kvStore_PutString(NULL, kv, tmp, v);
}
testCond(s == NATS_OK);
free(v);
test("Delete: ");
for (i=0; (s == NATS_OK) && (i<100); i++)
{
char tmp[64];
snprintf(tmp, sizeof(tmp), "key-%d", i);
s = kvStore_Delete(kv, tmp);
}
testCond(s == NATS_OK);
test("Purge deletes (bad args): ");
s = kvStore_PurgeDeletes(NULL, NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Purge deletes: ");
kvPurgeOptions_Init(&po);
po.DeleteMarkersOlderThan = -1;
s = kvStore_PurgeDeletes(kv, &po);
testCond(s == NATS_OK);
test("Check stream: ");
s = js_GetStreamInfo(&si, js, "KV_KVS", NULL, NULL);
testCond((s == NATS_OK) && (si != NULL) && (si->State.Msgs == 0));
jsStreamInfo_Destroy(si);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValuePurgeDeletesMarkerThreshold(void)
{
natsStatus s;
kvStore *kv = NULL;
kvConfig kvc;
kvPurgeOptions po;
kvEntryList list;
JS_SETUP(2, 7, 2);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "KVS";
kvc.History = 10;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Put keys: ")
s = kvStore_PutString(NULL, kv, "foo", "foo1");
IFOK(s, kvStore_PutString(NULL, kv, "bar", "bar1"));
IFOK(s, kvStore_PutString(NULL, kv, "foo", "foo2"));
testCond(s == NATS_OK);
test("Delete foo: ");
s = kvStore_Delete(kv, "foo");
testCond(s == NATS_OK);
nats_Sleep(500);
test("Delete bar: ");
s = kvStore_Delete(kv, "bar");
testCond(s == NATS_OK);
test("PurgeOptions init bad args: ");
s = kvPurgeOptions_Init(NULL);
testCond(s == NATS_INVALID_ARG);
nats_clearLastError();
test("Purge deletes: ");
kvPurgeOptions_Init(&po);
po.DeleteMarkersOlderThan = NATS_MILLIS_TO_NANOS(100);
s = kvStore_PurgeDeletes(kv, &po);
testCond(s == NATS_OK);
// The key foo should have been completely cleared of the data
// and the delete marker.
test("Check foo history: ")
s = kvStore_History(&list, kv, "foo", NULL);
testCond((s == NATS_NOT_FOUND) && (list.Entries == NULL) && (list.Count == 0));
test("Check bar history: ");
s = kvStore_History(&list, kv, "bar", NULL);
testCond((s == NATS_OK) && (list.Count == 1) && (list.Entries != NULL)
&& (kvEntry_Operation(list.Entries[0]) == kvOp_Delete));
kvEntryList_Destroy(&list);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValueCrossAccount(void)
{
natsStatus s;
natsOptions *opts= NULL;
natsConnection *nc1 = NULL;
natsConnection *nc2 = NULL;
jsCtx *js1 = NULL;
jsCtx *js2 = NULL;
natsPid pid = NATS_INVALID_PID;
kvStore *kv1 = NULL;
kvWatcher *w1 = NULL;
kvStore *kv2 = NULL;
kvWatcher *w2 = NULL;
kvEntry *e = NULL;
jsStreamInfo *si = NULL;
uint64_t rev = 0;
kvConfig kvc;
jsOptions o;
char datastore[256] = {'\0'};
char cmdLine[1024] = {'\0'};
char confFile[256] = {'\0'};
kvPurgeOptions po;
ENSURE_JS_VERSION(2, 6, 2);
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
_createConfFile(confFile, sizeof(confFile),
"jetstream: enabled\n"\
"accounts: {\n"\
" A: {\n"\
" users: [ {user: a, password: a} ]\n"\
" jetstream: enabled\n"\
" exports: [\n"\
" {service: '$JS.API.>' }\n"\
" {service: '$KV.>'}\n"\
" {stream: 'accI.>'}\n"\
" ]\n"\
" },\n"\
" I: {\n"\
" users: [ {user: i, password: i} ]\n"\
" imports: [\n"\
" {service: {account: A, subject: '$JS.API.>'}, to: 'fromA.>' }\n"\
" {service: {account: A, subject: '$KV.>'}, to: 'fromA.$KV.>' }\n"\
" {stream: {subject: 'accI.>', account: A}}\n"\
" ]\n"\
" }\n"\
"}");
test("Start JS server: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Create conn1: ");
s = natsConnection_ConnectTo(&nc1, "nats://a:a@127.0.0.1:4222");
testCond(s == NATS_OK);
test("Get context1: ");
s = natsConnection_JetStream(&js1, nc1, NULL);
testCond(s == NATS_OK);
test("Create KV1: ");
kvConfig_Init(&kvc);
kvc.Bucket = "Map";
s = js_CreateKeyValue(&kv1, js1, &kvc);
testCond(s == NATS_OK);
test("Create Watcher1: ");
s = kvStore_Watch(&w1, kv1, "map", NULL);
IFOK(s, kvWatcher_Next(&e, w1, 1000));
testCond((s == NATS_OK) && (e == NULL));
test("Create conn2: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetURL(opts, "nats://i:i@127.0.0.1:4222"));
IFOK(s, natsOptions_SetCustomInboxPrefix(opts, "accI"));
IFOK(s, natsConnection_Connect(&nc2, opts));
testCond(s == NATS_OK);
test("Get context2: ");
jsOptions_Init(&o);
o.Prefix = "fromA";
s = natsConnection_JetStream(&js2, nc2, &o);
testCond(s == NATS_OK);
test("Create KV2: ");
kvConfig_Init(&kvc);
kvc.Bucket = "Map";
s = js_CreateKeyValue(&kv2, js2, &kvc);
testCond(s == NATS_OK);
test("Create Watcher2: ");
s = kvStore_Watch(&w2, kv2, "map", NULL);
IFOK(s, kvWatcher_Next(&e, w2, 1000));
testCond((s == NATS_OK) && (e == NULL));
test("Put: ");
s = kvStore_PutString(&rev, kv2, "map", "value");
testCond(s == NATS_OK);
test("Get from kv1: ");
s = kvStore_Get(&e, kv1, "map");
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "value") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Get from kv2: ");
s = kvStore_Get(&e, kv2, "map");
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "value") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Watcher1 Next: ");
s = kvWatcher_Next(&e, w1, 1000);
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "value") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Watcher2 Next: ");
s = kvWatcher_Next(&e, w2, 1000);
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "value") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Update from kv2: ");
s = kvStore_UpdateString(NULL, kv2, "map", "updated", rev);
testCond(s == NATS_OK);
test("Get from kv1: ");
s = kvStore_Get(&e, kv1, "map");
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "updated") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Get from kv2: ");
s = kvStore_Get(&e, kv2, "map");
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "updated") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Watcher1 Next: ");
s = kvWatcher_Next(&e, w1, 1000);
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "updated") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Watcher2 Next: ");
s = kvWatcher_Next(&e, w2, 1000);
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "updated") == 0));
kvEntry_Destroy(e);
e = NULL;
test("Purge key from kv2: ");
s = kvStore_Purge(kv2, "map", NULL);
testCond(s == NATS_OK);
test("Check purge ok from w1: ");
s = kvWatcher_Next(&e, w1, 1000);
testCond((s == NATS_OK) && (e != NULL) && (kvEntry_Operation(e) == kvOp_Purge));
kvEntry_Destroy(e);
e = NULL;
test("Check purge ok from w2: ");
s = kvWatcher_Next(&e, w2, 1000);
testCond((s == NATS_OK) && (e != NULL) && (kvEntry_Operation(e) == kvOp_Purge));
kvEntry_Destroy(e);
e = NULL;
test("Delete purge records: ");
kvPurgeOptions_Init(&po);
po.DeleteMarkersOlderThan = -1;
s = kvStore_PurgeDeletes(kv2, &po);
testCond(s == NATS_OK);
test("All gone: ");
s = js_GetStreamInfo(&si, js1, "KV_Map", NULL, NULL);
testCond((s == NATS_OK) && (si != NULL) && (si->State.Msgs == 0));
jsStreamInfo_Destroy(si);
si = NULL;
test("Delete key from kv2: ");
s = kvStore_Delete(kv2, "map");
testCond(s == NATS_OK);
test("Check key gone: ");
s = kvStore_Get(&e, kv1, "map");
testCond((s == NATS_NOT_FOUND) && (e == NULL));
kvWatcher_Destroy(w2);
w2 = NULL;
kvStore_Destroy(kv2);
kv2 = NULL;
jsCtx_Destroy(js2);
js2 = NULL;
test("Get context2 (with trailing dot for prefix): ");
jsOptions_Init(&o);
o.Prefix = "fromA";
s = natsConnection_JetStream(&js2, nc2, &o);
testCond(s == NATS_OK);
test("Create KV2: ");
kvConfig_Init(&kvc);
kvc.Bucket = "Map";
s = js_CreateKeyValue(&kv2, js2, &kvc);
testCond(s == NATS_OK);
test("Put: ");
s = kvStore_PutString(NULL, kv2, "map", "value2");
testCond(s == NATS_OK);
test("Get from kv1: ");
s = kvStore_Get(&e, kv1, "map");
testCond((s == NATS_OK) && (e != NULL)
&& (strcmp(kvEntry_Key(e), "map") == 0)
&& (strcmp(kvEntry_ValueString(e), "value2") == 0));
kvEntry_Destroy(e);
e = NULL;
kvWatcher_Destroy(w1);
kvStore_Destroy(kv1);
jsCtx_Destroy(js1);
kvWatcher_Destroy(w2);
kvStore_Destroy(kv2);
jsCtx_Destroy(js2);
natsOptions_Destroy(opts);
natsConnection_Destroy(nc1);
natsConnection_Destroy(nc2);
_stopServer(pid);
rmtree(datastore);
remove(confFile);
}
static natsStatus
_checkDiscard(jsCtx *js, jsDiscardPolicy expected, kvStore **newKV)
{
kvStore *kv = NULL;
jsStreamInfo *si = NULL;
kvConfig kvc;
natsStatus s;
kvConfig_Init(&kvc);
kvc.Bucket = "TEST";
s = js_CreateKeyValue(&kv, js, &kvc);
IFOK(s, js_GetStreamInfo(&si, js, "KV_TEST", NULL, NULL));
IFOK(s, (si->Config->Discard == expected ? NATS_OK : NATS_ERR));
jsStreamInfo_Destroy(si);
*newKV = kv;
return s;
}
static void
test_KeyValueDiscardOldToNew(void)
{
kvStore *kv = NULL;
kvConfig kvc;
natsStatus s;
JS_SETUP(2, 7, 2);
// Change the server version in the connection to
// create as-if we were connecting to a v2.7.1 server.
natsConn_Lock(nc);
nc->srvVersion.ma = 2;
nc->srvVersion.mi = 7;
nc->srvVersion.up = 1;
natsConn_Unlock(nc);
test("Check discard (old): ");
s = _checkDiscard(js, js_DiscardOld, &kv);
testCond(s == NATS_OK);
kvStore_Destroy(kv);
kv = NULL;
// Now change version to 2.7.2
natsConn_Lock(nc);
nc->srvVersion.ma = 2;
nc->srvVersion.mi = 7;
nc->srvVersion.up = 2;
natsConn_Unlock(nc);
test("Check discard (old, no auto-update): ");
s = _checkDiscard(js, js_DiscardOld, &kv);
testCond((s == NATS_ERR) && (kv == NULL)
&& (strstr(nats_GetLastError(NULL), "different configuration") != NULL));
nats_clearLastError();
// Now delete the kv store and create against 2.7.2+
test("Delete KV: ");
s = js_DeleteStream(js, "KV_TEST", NULL, NULL);
testCond(s == NATS_OK);
test("Check discard (new): ");
s = _checkDiscard(js, js_DiscardNew, &kv);
testCond(s == NATS_OK);
kvStore_Destroy(kv);
kv = NULL;
test("Check that other changes are rejected: ");
kvConfig_Init(&kvc);
kvc.Bucket = "TEST";
kvc.MaxBytes = 1024*1024;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond((s == NATS_ERR)
&& (strstr(nats_GetLastError(NULL), "different configuration") != NULL));
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValueRePublish(void)
{
kvStore *kv = NULL;
jsStreamInfo *si = NULL;
natsSubscription *sub = NULL;
natsMsg *msg = NULL;
kvConfig kvc;
jsRePublish rp;
natsStatus s;
JS_SETUP(2, 9, 0);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "TEST_UPDATE";
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
kvStore_Destroy(kv);
kv = NULL;
test("Set RePublish should fail: ");
jsRePublish_Init(&rp);
rp.Source = ">";
rp.Destination = "bar.>";
kvc.RePublish =&rp;
s = js_CreateKeyValue(&kv, js, &kvc);
testCond((s == NATS_ERR) && (strstr(nats_GetLastError(NULL), "different configuration") != NULL));
nats_clearLastError();
test("Create with repub: ");
kvc.Bucket = "TEST";
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Check set: ");
s = js_GetStreamInfo(&si, js, "KV_TEST", NULL, NULL);
testCond((s == NATS_OK) && (si->Config != NULL) && (si->Config->RePublish != NULL));
jsStreamInfo_Destroy(si);
test("Sub: ");
s = natsConnection_SubscribeSync(&sub, nc, "bar.>");
testCond(s == NATS_OK);
test("Put: ");
s = kvStore_PutString(NULL, kv, "foo", "value");
testCond(s == NATS_OK);
test("Get msg: ");
s = natsSubscription_NextMsg(&msg, sub, 1000);
testCond(s == NATS_OK);
test("Check msg: ");
s = (strcmp(natsMsg_GetData(msg), "value") == 0 ? NATS_OK : NATS_ERR);
if (s == NATS_OK)
{
const char *subj = NULL;
s = natsMsgHeader_Get(msg, JSSubject, &subj);
if (s == NATS_OK)
s = (strcmp(subj, "$KV.TEST.foo") == 0 ? NATS_OK : NATS_ERR);
}
testCond(s == NATS_OK);
natsMsg_Destroy(msg);
natsSubscription_Destroy(sub);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static void
test_KeyValueMirrorDirectGet(void)
{
kvStore *kv = NULL;
kvConfig kvc;
jsStreamConfig sc;
jsStreamSource ss;
natsStatus s;
int i;
JS_SETUP(2, 9, 0);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "DIRECT_GET";
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Add mirror: ");
jsStreamConfig_Init(&sc);
sc.Name = "MIRROR";
jsStreamSource_Init(&ss);
ss.Name = "KV_DIRECT_GET";
sc.Mirror = &ss;
sc.MirrorDirect = true;
s = js_AddStream(NULL, js, &sc, NULL, NULL);
testCond(s == NATS_OK);
test("Populate: ");
for (i=0; (s==NATS_OK) && (i<100); i++)
{
char key[64];
snprintf(key, sizeof(key), "KEY.%d", i);
s = kvStore_PutString(NULL, kv, key, key);
}
testCond(s == NATS_OK);
test("Check get: ");
for (i=0; (s==NATS_OK) && (i<100); i++)
{
kvEntry *e = NULL;
s = kvStore_Get(&e, kv, "KEY.22");
if (s == NATS_OK)
{
s = (strcmp(kvEntry_ValueString(e), "KEY.22") == 0 ? NATS_OK : NATS_ERR);
kvEntry_Destroy(e);
}
}
testCond(s == NATS_OK);
kvStore_Destroy(kv);
JS_TEARDOWN;
}
static natsStatus
_connectToHubAndCheckLeaf(natsConnection **hub, natsConnection *lnc)
{
natsStatus s = NATS_OK;
natsConnection *nc = NULL;
natsSubscription *sub = NULL;
int i;
s = natsConnection_ConnectTo(&nc, NATS_DEFAULT_URL);
IFOK(s, natsConnection_SubscribeSync(&sub, nc, "check"));
IFOK(s, natsConnection_Flush(nc));
if (s == NATS_OK)
{
for (i=0; i<10; i++)
{
s = natsConnection_PublishString(lnc, "check", "hello");
if (s == NATS_OK)
{
natsMsg *msg = NULL;
s = natsSubscription_NextMsg(&msg, sub, 500);
natsMsg_Destroy(msg);
if (s == NATS_OK)
break;
}
}
}
natsSubscription_Destroy(sub);
if (s == NATS_OK)
*hub = nc;
else
natsConnection_Destroy(nc);
return s;
}
static void
test_KeyValueMirrorCrossDomains(void)
{
natsStatus s;
natsConnection *nc = NULL;
natsConnection *lnc= NULL;
jsCtx *js = NULL;
jsCtx *ljs= NULL;
jsCtx *rjs= NULL;
natsPid pid = NATS_INVALID_PID;
natsPid pid2= NATS_INVALID_PID;
jsOptions o;
jsErrCode jerr = 0;
char datastore[256] = {'\0'};
char datastore2[256] = {'\0'};
char cmdLine[1024] = {'\0'};
char confFile[256] = {'\0'};
char lconfFile[256] = {'\0'};
kvStore *kv = NULL;
kvStore *lkv = NULL;
kvStore *mkv = NULL;
kvStore *rkv = NULL;
kvEntry *e = NULL;
jsStreamInfo *si = NULL;
kvWatcher *w = NULL;
int ok = 0;
kvPurgeOptions po;
kvConfig kvc;
jsStreamSource src;
int i;
ENSURE_JS_VERSION(2, 9, 0);
_makeUniqueDir(datastore, sizeof(datastore), "datastore_");
_createConfFile(confFile, sizeof(confFile),
"server_name: HUB\n"\
"listen: 127.0.0.1:4222\n"\
"jetstream: { domain: HUB }\n"\
"leafnodes { listen: 127.0.0.1:7422 }\n");
test("Start hub: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
_makeUniqueDir(datastore2, sizeof(datastore2), "datastore_");
_createConfFile(lconfFile, sizeof(lconfFile),
"server_name: LEAF\n"\
"listen: 127.0.0.1:4223\n"\
"jetstream: { domain: LEAF }\n"\
"leafnodes {\n"\
" remotes = [ { url: leaf://127.0.0.1:7422 } ]\n"\
"}\n");
test("Start leaf: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, lconfFile);
pid2 = _startServer("nats://127.0.0.1:4223", cmdLine, true);
CHECK_SERVER_STARTED(pid2);
testCond(true);
test("Connect to leaf: ");
s = natsConnection_ConnectTo(&lnc, "nats://127.0.0.1:4223");
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&ljs, lnc, NULL);
testCond(s == NATS_OK);
test("Connect to hub and check connectivity through leaf: ");
s = _connectToHubAndCheckLeaf(&nc, lnc);
testCond(s == NATS_OK);
test("Get context: ");
s = natsConnection_JetStream(&js, nc, NULL);
testCond(s == NATS_OK);
test("Create KV value: ");
kvConfig_Init(&kvc);
kvc.Bucket = "TEST";
s = js_CreateKeyValue(&kv, js, &kvc);
testCond(s == NATS_OK);
test("Put keys: ");
s = kvStore_PutString(NULL, kv, "name", "derek");
IFOK(s, kvStore_PutString(NULL, kv, "age", "22"));
testCond(s == NATS_OK);
test("Create KV: ");
kvConfig_Init(&kvc);
kvc.Bucket = "MIRROR";
jsStreamSource_Init(&src);
src.Name = "TEST";
src.Domain = "HUB";
kvc.Mirror = &src;
s = js_CreateKeyValue(&lkv, ljs, &kvc);
testCond(s == NATS_OK);
test("Check config not changed: ");
testCond((strcmp(kvc.Bucket, "MIRROR") == 0)
&& (kvc.Mirror != NULL)
&& (strcmp(kvc.Mirror->Name, "TEST") == 0)
&& (strcmp(kvc.Mirror->Domain, "HUB") == 0)
&& (kvc.Mirror->External == NULL));
test("Get stream info: ");
s = js_GetStreamInfo(&si, ljs, "KV_MIRROR", NULL, &jerr);
testCond((s == NATS_OK) && (si != NULL) && (jerr == 0));
test("Check mirror direct: ");
testCond(si->Config->MirrorDirect);
jsStreamInfo_Destroy(si);
si = NULL;
test("Check mirror syncs: ");
for (i=0; i<10; i++)
{
s = js_GetStreamInfo(&si, ljs, "KV_MIRROR", NULL, NULL);
if (s != NATS_OK)
break;
if (si->State.Msgs != 2)
s = NATS_ERR;
jsStreamInfo_Destroy(si);
si = NULL;
if (s == NATS_OK)
break;
nats_Sleep(250);
}
testCond(s == NATS_OK);
// Bind locally from leafnode and make sure both get and put work.
test("Leaf KV: ");
s = js_KeyValue(&mkv, ljs, "MIRROR");
testCond(s == NATS_OK);
test("Put key: ");
s = kvStore_PutString(NULL, mkv, "name", "rip");
testCond(s == NATS_OK);
test("Get key: ");
s = kvStore_Get(&e, mkv, "name");
if ((s == NATS_OK) && (e != NULL))
s = (strcmp(kvEntry_ValueString(e), "rip") == 0 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
kvEntry_Destroy(e);
e = NULL;
test("Get context for HUB: ");
jsOptions_Init(&o);
o.Domain = "HUB";
s = natsConnection_JetStream(&rjs, lnc, &o);
testCond(s == NATS_OK);
test("Get KV: ");
s = js_KeyValue(&rkv, rjs, "TEST");
testCond(s == NATS_OK);
test("Put key: ");
s = kvStore_PutString(NULL, rkv, "name", "ivan");
testCond(s == NATS_OK);
test("Get key: ");
s = kvStore_Get(&e, rkv, "name");
if ((s == NATS_OK) && (e != NULL))
s = (strcmp(kvEntry_ValueString(e), "ivan") == 0 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
kvEntry_Destroy(e);
e = NULL;
test("Shutdown hub: ");
jsCtx_Destroy(js);
kvStore_Destroy(kv);
natsConnection_Destroy(nc);
nc = NULL;
_stopServer(pid);
pid = NATS_INVALID_PID;
testCond(true);
nats_Sleep(500);
test("Get key: ");
// Use mkv here, not rkv.
s = kvStore_Get(&e, mkv, "name");
if ((s == NATS_OK) && (e != NULL))
s = (strcmp(kvEntry_ValueString(e), "ivan") == 0 ? NATS_OK : NATS_ERR);
testCond(s == NATS_OK);
kvEntry_Destroy(e);
e = NULL;
test("Create watcher (name): ");
s = kvStore_Watch(&w, mkv, "name", NULL);
testCond(s == NATS_OK);
test("Check watcher: ");
s = kvWatcher_Next(&e, w, 1000);
if (s == NATS_OK)
{
if ((strcmp(kvEntry_Key(e), "name") != 0) || (strcmp(kvEntry_ValueString(e), "ivan") != 0))
s = NATS_ERR;
kvEntry_Destroy(e);
e = NULL;
}
IFOK(s, kvWatcher_Next(&e, w, 1000));
if (s == NATS_OK)
{
if ((kvEntry_Key(e) != NULL) || (kvEntry_ValueString(e) != NULL))
s = NATS_ERR;
kvEntry_Destroy(e);
e = NULL;
}
testCond(s == NATS_OK);
test("No more: ");
s = kvWatcher_Next(&e, w, 250);
testCond((s == NATS_TIMEOUT) && (e == NULL));
nats_clearLastError();
kvWatcher_Destroy(w);
w = NULL;
test("Create watcher (all): ")
s = kvStore_WatchAll(&w, mkv, NULL);
testCond((s == NATS_OK) && (w != NULL));
test("Check watcher: ");
s = kvWatcher_Next(&e, w, 1000);
if (s == NATS_OK)
{
if ((strcmp(kvEntry_Key(e), "age") != 0) || (strcmp(kvEntry_ValueString(e), "22") != 0))
s = NATS_ERR;
kvEntry_Destroy(e);
e = NULL;
}
IFOK(s, kvWatcher_Next(&e, w, 1000));
if (s == NATS_OK)
{
if ((strcmp(kvEntry_Key(e), "name") != 0) || (strcmp(kvEntry_ValueString(e), "ivan") != 0))
s = NATS_ERR;
kvEntry_Destroy(e);
e = NULL;
}
IFOK(s, kvWatcher_Next(&e, w, 1000));
if (s == NATS_OK)
{
if ((kvEntry_Key(e) != NULL) || (kvEntry_ValueString(e) != NULL))
s = NATS_ERR;
kvEntry_Destroy(e);
e = NULL;
}
testCond(s == NATS_OK);
test("No more: ");
s = kvWatcher_Next(&e, w, 250);
testCond((s == NATS_TIMEOUT) && (e == NULL));
nats_clearLastError();
test("Restart hub: ");
snprintf(cmdLine, sizeof(cmdLine), "-js -sd %s -c %s", datastore, confFile);
pid = _startServer("nats://127.0.0.1:4222", cmdLine, true);
CHECK_SERVER_STARTED(pid);
testCond(true);
test("Connect to hub and check connectivity through leaf: ");
s = _connectToHubAndCheckLeaf(&nc, lnc);
testCond(s == NATS_OK);
test("Delete keys: ");
s = kvStore_Delete(mkv, "age");
IFOK(s, kvStore_Delete(mkv, "name"));
testCond(s == NATS_OK);
test("Check mirror syncs: ");
for (i=0; (ok != 2) && (i < 10); i++)
{
if (kvWatcher_Next(&e, w, 1000) == NATS_OK)
{
if (((strcmp(kvEntry_Key(e), "age") == 0) || (strcmp(kvEntry_Key(e), "name") == 0))
&& (kvEntry_Operation(e) == kvOp_Delete))
{
ok++;
}
kvEntry_Destroy(e);
e = NULL;
}
}
testCond((s == NATS_OK) && (ok == 2));
test("Purge deletes: ");
kvPurgeOptions_Init(&po);
po.DeleteMarkersOlderThan = -1;
s = kvStore_PurgeDeletes(mkv, &po);
testCond(s == NATS_OK);
nats_clearLastError();
test("Check stream: ");
s = js_GetStreamInfo(&si, ljs, "KV_MIRROR", NULL, NULL);
testCond((s == NATS_OK) && (si != NULL) && (si->State.Msgs == 0));
jsStreamInfo_Destroy(si);
kvWatcher_Destroy(w);
natsConnection_Destroy(nc);
kvStore_Destroy(rkv);
kvStore_Destroy(mkv);
kvStore_Destroy(lkv);
jsCtx_Destroy(rjs);
jsCtx_Destroy(ljs);
natsConnection_Destroy(lnc);
_stopServer(pid2);
rmtree(datastore2);
_stopServer(pid);
rmtree(datastore);
remove(confFile);
remove(lconfFile);
}
#if defined(NATS_HAS_STREAMING)
static int
_roundUp(int val)
{
return ((val + (MEMALIGN-1))/MEMALIGN)*MEMALIGN;
}
static void
test_StanPBufAllocator(void)
{
natsPBufAllocator *a = NULL;
natsStatus s;
char *ptr1;
char *ptr2;
char *ptr3;
char *ptr4;
char *oldBuf;
int oldCap;
int expectedProtoSize;
int expectedOverhead;
int expectedUsed;
int expectedRemaining;
int expectedCap;
int prevUsed;
test("Create: ");
s = natsPBufAllocator_Create(&a, 10, 2);
expectedProtoSize = MEMALIGN + _roundUp(10);
expectedOverhead = (MEMALIGN * 2) + 2 + (2 * (MEMALIGN-1));
testCond((s == NATS_OK)
&& (a->protoSize == expectedProtoSize)
&& (a->overhead == expectedOverhead)
&& (a->base.alloc != NULL)
&& (a->base.free != NULL)
&& (a->base.allocator_data == a));
test("Prepare: ");
natsPBufAllocator_Prepare(a, 20);
expectedCap = expectedProtoSize + expectedOverhead + 20;
testCond((a->buf != NULL)
&& (a->cap == expectedCap)
&& (a->remaining == a->cap)
&& (a->used == 0));
test("Alloc 1: ");
ptr1 = (char*) a->base.alloc((void*)a, 10);
expectedUsed = MEMALIGN + _roundUp(10);
expectedRemaining = expectedCap - expectedUsed;
testCond((ptr1 != NULL)
&& ((ptr1-MEMALIGN) == a->buf)
&& ((ptr1-MEMALIGN)[0] == '0')
&& (a->used == expectedUsed)
&& (a->remaining == expectedRemaining));
test("Alloc 2: ");
ptr2 = (char*) a->base.alloc((void*)a, 5);
prevUsed = expectedUsed;
expectedUsed += MEMALIGN + _roundUp(5);
expectedRemaining = expectedCap - expectedUsed;
testCond((ptr2 != ptr1)
&& ((ptr2-MEMALIGN) == (a->buf + prevUsed))
&& ((ptr2-MEMALIGN)[0] == '0')
&& (a->used == expectedUsed)
&& (a->remaining == expectedRemaining));
test("Alloc 3: ");
ptr3 = (char*) a->base.alloc((void*)a, 3);
prevUsed = expectedUsed;
expectedUsed += MEMALIGN + _roundUp(3);
expectedRemaining = expectedCap - expectedUsed;
testCond((ptr3 != ptr2)
&& ((ptr3-MEMALIGN) == (a->buf + prevUsed))
&& ((ptr3-MEMALIGN)[0] == '0')
&& (a->used == expectedUsed)
&& (a->remaining == expectedRemaining));
test("Alloc 4: ");
ptr4 = (char*) a->base.alloc((void*)a, 50);
testCond((ptr4 != ptr3)
&& (((ptr4-MEMALIGN) < a->buf) || ((ptr4-MEMALIGN) > (a->buf+a->cap)))
&& ((ptr4-MEMALIGN)[0] == '1')
&& (a->used == expectedUsed)
&& (a->remaining == expectedRemaining));
// Free out of order, just make sure it does not crash
// and valgrind will make sure that we freed ptr4.
test("Free 2: ");
a->base.free((void*) a, (void*) ptr2);
testCond(1);
test("Free 1: ");
a->base.free((void*) a, (void*) ptr1);
testCond(1);
test("Free 4: ");
a->base.free((void*) a, (void*) ptr4);
testCond(1);
test("Free 3: ");
a->base.free((void*) a, (void*) ptr3);
testCond(1);
// Call prepare again with smaller buffer, buf should
// remain same, but used/remaining should be updated.
oldBuf = a->buf;
oldCap = a->cap;
test("Prepare with smaller buffer: ");
natsPBufAllocator_Prepare(a, 5);
testCond((a->buf == oldBuf)
&& (a->cap == oldCap)
&& (a->remaining == a->cap)
&& (a->used == 0));
test("Prepare requires expand: ");
natsPBufAllocator_Prepare(a, 100);
// Realloc may or may not make a->buf be different...
expectedCap = expectedProtoSize + expectedOverhead + 100;
testCond((a->buf != NULL)
&& (a->cap == expectedCap)
&& (a->remaining == a->cap)
&& (a->used == 0));
test("Destroy: ");
natsPBufAllocator_Destroy(a);
testCond(1);
}
static void
_stanConnLostCB(stanConnection *sc, const char *errorTxt, void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->closed = true;
arg->status = NATS_OK;
if ((arg->string != NULL) && (strcmp(errorTxt, arg->string) != 0))
arg->status = NATS_ERR;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
}
static void
test_StanConnOptions(void)
{
natsStatus s;
stanConnOptions *opts = NULL;
stanConnOptions *clone= NULL;
natsOptions *no = NULL;
test("Create option: ");
s = stanConnOptions_Create(&opts);
testCond(s == NATS_OK);
test("Has default values: ");
testCond(
(opts->connTimeout == STAN_CONN_OPTS_DEFAULT_CONN_TIMEOUT) &&
(opts->connectionLostCB == stanConn_defaultConnLostHandler) &&
(opts->connectionLostCBClosure == NULL) &&
(strcmp(opts->discoveryPrefix, STAN_CONN_OPTS_DEFAULT_DISCOVERY_PREFIX) == 0) &&
(opts->maxPubAcksInFlightPercentage == STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT_PERCENTAGE) &&
(opts->maxPubAcksInflight == STAN_CONN_OPTS_DEFAULT_MAX_PUB_ACKS_INFLIGHT) &&
(opts->ncOpts == NULL) &&
(opts->pingInterval == STAN_CONN_OPTS_DEFAULT_PING_INTERVAL) &&
(opts->pingMaxOut == STAN_CONN_OPTS_DEFAULT_PING_MAX_OUT) &&
(opts->pubAckTimeout == STAN_CONN_OPTS_DEFAULT_PUB_ACK_TIMEOUT) &&
(opts->url == NULL));
test("Check invalid connection wait: ");
s = stanConnOptions_SetConnectionWait(opts, -10);
if (s != NATS_OK)
s = stanConnOptions_SetConnectionWait(opts, 0);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid discovery prefix: ");
s = stanConnOptions_SetDiscoveryPrefix(opts, NULL);
if (s != NATS_OK)
s = stanConnOptions_SetDiscoveryPrefix(opts, "");
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid max pub acks: ");
s = stanConnOptions_SetMaxPubAcksInflight(opts, -1, 1);
if (s != NATS_OK)
s = stanConnOptions_SetMaxPubAcksInflight(opts, 0, 1);
if (s != NATS_OK)
s = stanConnOptions_SetMaxPubAcksInflight(opts, 10, -1);
if (s != NATS_OK)
s = stanConnOptions_SetMaxPubAcksInflight(opts, 10, 0);
if (s != NATS_OK)
s = stanConnOptions_SetMaxPubAcksInflight(opts, 10, 2);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid pings: ");
s = stanConnOptions_SetPings(opts, -1, 10);
if (s != NATS_OK)
s = stanConnOptions_SetPings(opts, 0, 10);
if (s != NATS_OK)
s = stanConnOptions_SetPings(opts, 1, -1);
if (s != NATS_OK)
s = stanConnOptions_SetPings(opts, 1, 0);
if (s != NATS_OK)
s = stanConnOptions_SetPings(opts, 1, 1);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid pub ack wait: ");
s = stanConnOptions_SetPubAckWait(opts, -1);
if (s != NATS_OK)
s = stanConnOptions_SetPubAckWait(opts, 0);
testCond(s != NATS_OK);
nats_clearLastError();
test("Set values: ");
s = stanConnOptions_SetConnectionWait(opts, 10000);
IFOK(s, stanConnOptions_SetDiscoveryPrefix(opts, "myPrefix"));
IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 10, (float) 0.8));
IFOK(s, stanConnOptions_SetPings(opts, 1, 10));
IFOK(s, stanConnOptions_SetPubAckWait(opts, 2000));
IFOK(s, stanConnOptions_SetURL(opts, "nats://me:1"));
IFOK(s, stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) 1));
testCond((s == NATS_OK) &&
(opts->connTimeout == 10000) &&
(strcmp(opts->discoveryPrefix, "myPrefix") == 0) &&
(opts->maxPubAcksInFlightPercentage == (float) 0.8) &&
(opts->maxPubAcksInflight == 10) &&
(opts->pingInterval == 1) &&
(opts->pingMaxOut == 10) &&
(opts->pubAckTimeout == 2000) &&
(strcmp(opts->url, "nats://me:1") == 0) &&
(opts->connectionLostCB == _stanConnLostCB) &&
(opts->connectionLostCBClosure == (void*) 1)
);
test("Set NATS options: ");
s = natsOptions_Create(&no);
IFOK(s, natsOptions_SetMaxPendingMsgs(no, 1000));
IFOK(s, stanConnOptions_SetNATSOptions(opts, no));
// change value from no after setting to stan opts
// check options were cloned.
IFOK(s, natsOptions_SetMaxPendingMsgs(no, 2000));
testCond((s == NATS_OK) &&
(opts->ncOpts != NULL) && // set
(opts->ncOpts != no) && // not a reference
(opts->ncOpts->maxPendingMsgs == 1000) // original value
);
test("Check clone: ");
s = stanConnOptions_clone(&clone, opts);
// Change values from original, check that clone
// keeps original values.
IFOK(s, stanConnOptions_SetConnectionWait(opts, 3000));
IFOK(s, stanConnOptions_SetDiscoveryPrefix(opts, "xxxxx"));
IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 100, (float) 0.2));
IFOK(s, stanConnOptions_SetPings(opts, 10, 20));
IFOK(s, stanConnOptions_SetPubAckWait(opts, 3000));
IFOK(s, stanConnOptions_SetURL(opts, "nats://metoo:1"));
IFOK(s, stanConnOptions_SetConnectionLostHandler(opts, NULL, NULL));
IFOK(s, stanConnOptions_SetNATSOptions(opts, NULL));
testCond((s == NATS_OK) &&
(clone != opts) &&
(clone->connTimeout == 10000) &&
(strcmp(clone->discoveryPrefix, "myPrefix") == 0) &&
(clone->maxPubAcksInFlightPercentage == (float) 0.8) &&
(clone->maxPubAcksInflight == 10) &&
(clone->pingInterval == 1) &&
(clone->pingMaxOut == 10) &&
(clone->pubAckTimeout == 2000) &&
(strcmp(clone->url, "nats://me:1") == 0) &&
(clone->connectionLostCB == _stanConnLostCB) &&
(clone->connectionLostCBClosure == (void*) 1) &&
(clone->ncOpts != NULL) &&
(clone->ncOpts != no) &&
(clone->ncOpts->maxPendingMsgs == 1000)
);
test("Check cb and NATS options can be set to NULL: ");
testCond(
(opts->ncOpts == NULL) &&
(opts->connectionLostCB == NULL) &&
(opts->connectionLostCBClosure == NULL));
test("Check URL can be set to NULL: ");
s = stanConnOptions_SetURL(opts, NULL);
testCond(s == NATS_OK);
test("Check clone ok after destroy original: ");
stanConnOptions_Destroy(opts);
testCond((s == NATS_OK) &&
(clone->connTimeout == 10000) &&
(strcmp(clone->discoveryPrefix, "myPrefix") == 0) &&
(clone->maxPubAcksInFlightPercentage == (float) 0.8) &&
(clone->maxPubAcksInflight == 10) &&
(clone->pingInterval == 1) &&
(clone->pingMaxOut == 10) &&
(clone->pubAckTimeout == 2000) &&
(strcmp(clone->url, "nats://me:1") == 0) &&
(clone->connectionLostCB == _stanConnLostCB) &&
(clone->connectionLostCBClosure == (void*) 1) &&
(clone->ncOpts != NULL) &&
(clone->ncOpts != no) &&
(clone->ncOpts->maxPendingMsgs == 1000)
);
natsOptions_Destroy(no);
stanConnOptions_Destroy(clone);
}
static void
test_StanSubOptions(void)
{
natsStatus s;
stanSubOptions *opts = NULL;
stanSubOptions *clone= NULL;
int64_t now = 0;
test("Create Options: ");
s = stanSubOptions_Create(&opts);
testCond(s == NATS_OK);
test("Default values: ");
testCond(
(opts->ackWait == STAN_SUB_OPTS_DEFAULT_ACK_WAIT) &&
(opts->durableName == NULL) &&
(opts->manualAcks == false) &&
(opts->maxInflight == STAN_SUB_OPTS_DEFAULT_MAX_INFLIGHT) &&
(opts->startAt == PB__START_POSITION__NewOnly) &&
(opts->startSequence == 0) &&
(opts->startTime == 0)
);
test("Check invalid ackwait: ");
s = stanSubOptions_SetAckWait(opts, -1);
if (s != NATS_OK)
s = stanSubOptions_SetAckWait(opts, 0);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid maxinflight: ");
s = stanSubOptions_SetMaxInflight(opts, -1);
if (s != NATS_OK)
s = stanSubOptions_SetMaxInflight(opts, 0);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid start seq: ");
s = stanSubOptions_StartAtSequence(opts, 0);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid start time: ");
s = stanSubOptions_StartAtTime(opts, -1);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check invalid start time: ");
s = stanSubOptions_StartAtTimeDelta(opts, -1);
testCond(s != NATS_OK);
nats_clearLastError();
test("Check set values: ");
s = stanSubOptions_SetAckWait(opts, 1000);
IFOK(s, stanSubOptions_SetDurableName(opts, "myDurable"));
IFOK(s, stanSubOptions_SetManualAckMode(opts, true));
IFOK(s, stanSubOptions_SetMaxInflight(opts, 200));
testCond((s == NATS_OK) &&
(opts->ackWait == 1000) &&
(strcmp(opts->durableName, "myDurable") == 0) &&
(opts->manualAcks == true) &&
(opts->maxInflight == 200)
);
now = nats_Now();
test("Check start at time delta: ");
s = stanSubOptions_StartAtTimeDelta(opts, 20000);
testCond((s == NATS_OK) &&
(opts->startAt == PB__START_POSITION__TimeDeltaStart) &&
((opts->startTime >= now-20200) &&
(opts->startTime <= now-19800))
);
test("Check start at time: ");
s = stanSubOptions_StartAtTime(opts, 1234567890);
testCond((s == NATS_OK) &&
(opts->startAt == PB__START_POSITION__TimeDeltaStart) &&
(opts->startTime == 1234567890)
);
test("Check start at seq: ");
s = stanSubOptions_StartAtSequence(opts, 100);
testCond((s == NATS_OK) &&
(opts->startAt == PB__START_POSITION__SequenceStart) &&
(opts->startSequence == 100)
);
test("Check deliver all avail: ");
s = stanSubOptions_DeliverAllAvailable(opts);
testCond((s == NATS_OK) && (opts->startAt == PB__START_POSITION__First));
test("Check clone: ");
s = stanSubOptions_clone(&clone, opts);
// Change values of opts to show that this does not affect
// the clone
IFOK(s, stanSubOptions_SetAckWait(opts, 20000));
IFOK(s, stanSubOptions_SetDurableName(opts, NULL));
IFOK(s, stanSubOptions_SetManualAckMode(opts, false));
IFOK(s, stanSubOptions_SetMaxInflight(opts, 4000));
IFOK(s, stanSubOptions_StartAtSequence(opts, 100));
testCond((s == NATS_OK) &&
(clone != opts) &&
(clone->ackWait == 1000) &&
(strcmp(clone->durableName, "myDurable") == 0) &&
(clone->manualAcks == true) &&
(clone->maxInflight == 200) &&
(clone->startAt == PB__START_POSITION__First)
);
test("Check clone ok after destroy original: ");
stanSubOptions_Destroy(opts);
testCond((s == NATS_OK) &&
(clone != opts) &&
(clone->ackWait == 1000) &&
(strcmp(clone->durableName, "myDurable") == 0) &&
(clone->manualAcks == true) &&
(clone->maxInflight == 200) &&
(clone->startAt == PB__START_POSITION__First)
);
stanSubOptions_Destroy(clone);
}
static void
test_StanMsg(void)
{
test("GetSequence with NULL msg: ");
testCond(stanMsg_GetSequence(NULL) == 0);
test("GetData with NULL msg: ");
testCond(stanMsg_GetData(NULL) == NULL);
test("GetDataLength with NULL msg: ");
testCond(stanMsg_GetDataLength(NULL) == 0);
test("GetTimestamp with NULL msg: ");
testCond(stanMsg_GetTimestamp(NULL) == 0);
test("IsRedelivered with NULL msg: ");
testCond(stanMsg_IsRedelivered(NULL) == false);
stanMsg_Destroy(NULL);
}
static void
test_StanServerNotReachable(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanConnOptions *opts = NULL;
natsPid serverPid = NATS_INVALID_PID;
int64_t now = 0;
int64_t elapsed = 0;
s = stanConnOptions_Create(&opts);
IFOK(s, stanConnOptions_SetURL(opts, "nats://127.0.0.1:4222"));
IFOK(s, stanConnOptions_SetConnectionWait(opts, 250));
if (s != NATS_OK)
FAIL("Unable to setup test");
serverPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(serverPid);
test("Connect fails if no streaming server running: ");
now = nats_Now();
s = stanConnection_Connect(&sc, clusterName, clientName, opts);
elapsed = nats_Now()-now;
if (serverVersionAtLeast(2, 2, 0))
{
testCond((s == NATS_NO_RESPONDERS) &&
(strstr(nats_GetLastError(NULL), STAN_ERR_CONNECT_REQUEST_NO_RESP) != NULL));
}
else
{
testCond((s == NATS_TIMEOUT) &&
(strstr(nats_GetLastError(NULL), STAN_ERR_CONNECT_REQUEST_TIMEOUT) != NULL) &&
(elapsed < 2000));
}
stanConnOptions_Destroy(opts);
_stopServer(serverPid);
}
static void
test_StanBasicConnect(void)
{
natsStatus s;
stanConnection *sc = NULL;
natsPid pid = NATS_INVALID_PID;
stanConnOptions *opts = NULL;
natsOptions *nopts = NULL;
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Basic connect: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
testCond(s == NATS_OK);
test("Connection close: ");
s = stanConnection_Close(sc);
testCond(s == NATS_OK);
test("Connection double close: ");
s = stanConnection_Close(sc);
testCond(s == NATS_OK);
stanConnection_Destroy(sc);
sc = NULL;
_stopServer(pid);
pid = NATS_INVALID_PID;
pid = _startStreamingServer("nats://127.0.0.1:4223", "-p 4223", true);
CHECK_SERVER_STARTED(pid);
test("Connect with non default stan URL: ");
s = stanConnOptions_Create(&opts);
IFOK(s, stanConnOptions_SetURL(opts, "nats://127.0.0.1:4223"));
IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts));
testCond(s == NATS_OK);
stanConnection_Destroy(sc);
sc = NULL;
test("stan URL takes precedence: ");
s = natsOptions_Create(&nopts);
IFOK(s, natsOptions_SetURL(nopts, "nats://127.0.0.1:4224")); // wrong URL
IFOK(s, stanConnOptions_SetNATSOptions(opts, nopts));
IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts));
// Should connect because it should use the one from stanConnOptions_SetURL.
testCond(s == NATS_OK);
stanConnection_Destroy(sc);
sc = NULL;
test("If no stan URL set, uses NATS URL: ");
s = stanConnOptions_SetURL(opts, NULL);
IFOK(s, natsOptions_SetURL(nopts, "nats://127.0.0.1:4223"));
IFOK(s, stanConnOptions_SetNATSOptions(opts, nopts));
IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts));
// Should connect because it should use the one from StanURL.
testCond(s == NATS_OK);
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
natsOptions_Destroy(nopts);
_stopServer(pid);
}
static void
test_StanConnectError(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanConnection *sc2 = NULL;
natsPid nPid = NATS_INVALID_PID;
natsPid sPid = NATS_INVALID_PID;
stanConnOptions *opts = NULL;
nPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(nPid);
sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true);
CHECK_SERVER_STARTED(sPid);
test("Check connect response error: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
IFOK(s, stanConnection_Connect(&sc2, clusterName, clientName, NULL));
testCond((s == NATS_ERR) &&
(strstr(nats_GetLastError(NULL), "clientID already registered") != NULL));
test("Check wrong discovery prefix: ");
s = stanConnOptions_Create(&opts);
IFOK(s, stanConnOptions_SetDiscoveryPrefix(opts, "wrongprefix"));
IFOK(s, stanConnOptions_SetConnectionWait(opts, 500));
IFOK(s, stanConnection_Connect(&sc2, clusterName, "newClient", opts));
testCond(serverVersionAtLeast(2, 2, 0) ? (s == NATS_NO_RESPONDERS) : (s == NATS_TIMEOUT));
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
_stopServer(sPid);
_stopServer(nPid);
}
static void
test_StanBasicPublish(void)
{
natsStatus s;
stanConnection *sc = NULL;
natsPid pid = NATS_INVALID_PID;
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Basic publish: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
IFOK(s, stanConnection_Publish(sc, "foo", (const void*) "hello", 5));
testCond(s == NATS_OK);
stanConnection_Destroy(sc);
_stopServer(pid);
}
static void
_stanPubAckHandler(const char *guid, const char *errTxt, void* closure)
{
struct threadArg *args= (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->status = NATS_OK;
if (errTxt != NULL)
{
if ((args->string == NULL) || (strstr(errTxt, args->string) == NULL))
args->status = NATS_ERR;
}
else if (args->string != NULL)
{
args->status = NATS_ERR;
}
args->msgReceived = true;
natsCondition_Signal(args->c);
natsMutex_Unlock(args->m);
}
static void
test_StanBasicPublishAsync(void)
{
natsStatus s;
stanConnection *sc = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Basic publish async: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
IFOK(s, stanConnection_PublishAsync(sc, "foo", (const void*) "hello", 5,
_stanPubAckHandler, (void*) &args));
testCond(s == NATS_OK);
test("PubAck callback report no error: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 2000);
IFOK(s, args.status);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
stanConnection_Destroy(sc);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
test_StanPublishTimeout(void)
{
natsStatus s;
stanConnection *sc = NULL;
struct threadArg args;
natsPid nPid = NATS_INVALID_PID;
natsPid sPid = NATS_INVALID_PID;
stanConnOptions *opts = NULL;
s = _createDefaultThreadArgsForCbTests(&args);
IFOK(s, stanConnOptions_Create(&opts));
IFOK(s, stanConnOptions_SetPubAckWait(opts, 50));
if (s != NATS_OK)
FAIL("Unable to setup test");
nPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(nPid);
sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true);
CHECK_SERVER_STARTED(sPid);
// First connect, then once that's done, shutdown streaming server
s = stanConnection_Connect(&sc, clusterName, clientName, opts);
_stopServer(sPid);
if (s != NATS_OK)
{
_stopServer(nPid);
FAIL("Not able to create connection for this test");
}
args.string = STAN_ERR_PUB_ACK_TIMEOUT;
test("Check publish async timeout");
s = stanConnection_PublishAsync(sc, "foo", (const void*) "hello", 5,
_stanPubAckHandler, (void*) &args);
testCond(s == NATS_OK);
test("PubAck callback report pub ack timeout error: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 2000);
IFOK(s, args.status);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
// Speed up test by closing stan's nc connection to avoid timing out on conn close
stanConnClose(sc, false);
stanConnOptions_Destroy(opts);
stanConnection_Destroy(sc);
_destroyDefaultThreadArgs(&args);
_stopServer(nPid);
}
static void
_stanPublishAsyncThread(void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
int i;
for (i = 0; i < 10; i++)
stanConnection_PublishAsync(args->sc, "foo", (const void*)"hello", 5, NULL, NULL);
}
static void
_stanPublishSyncThread(void *closure)
{
stanConnection *sc = (stanConnection*) closure;
stanConnection_Publish(sc, "foo", (const void*)"hello", 5);
}
static void
test_StanPublishMaxAcksInflight(void)
{
natsStatus s;
stanConnection *sc1 = NULL;
stanConnection *sc2 = NULL;
struct threadArg args;
natsPid nPid = NATS_INVALID_PID;
natsPid sPid = NATS_INVALID_PID;
stanConnOptions *opts = NULL;
natsThread *t = NULL;
natsThread *pts[10];
int i;
natsConnection *nc = NULL;
for (i=0;i<10;i++)
pts[i] = NULL;
s = _createDefaultThreadArgsForCbTests(&args);
IFOK(s, stanConnOptions_Create(&opts));
IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 5, 1.0));
if (s != NATS_OK)
FAIL("Unable to setup test");
nPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(nPid);
sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true);
CHECK_SERVER_STARTED(sPid);
// First connect, then once that's done, shutdown streaming server
s = stanConnection_Connect(&sc1, clusterName, clientName, opts);
IFOK(s, stanConnection_Connect(&sc2, clusterName, "otherClient", opts));
if (s != NATS_OK)
{
stanConnection_Destroy(sc1);
stanConnection_Destroy(sc2);
_stopServer(sPid);
_stopServer(nPid);
FAIL("Not able to create connection for this test");
}
_stopServer(sPid);
// grap nc first
natsMutex_Lock(sc1->mu);
nc = sc1->nc;
natsMutex_Unlock(sc1->mu);
test("Check max inflight: ");
args.sc = sc1;
// Retain the connection
stanConn_retain(sc1);
s = natsThread_Create(&t, _stanPublishAsyncThread, (void*) &args);
if (s == NATS_OK)
{
int i = 0;
// Check that pubAckMap size is never greater than 5.
for (i=0; (s == NATS_OK) && (i<10); i++)
{
nats_Sleep(100);
natsMutex_Lock(sc1->pubAckMu);
s = (natsStrHash_Count(sc1->pubAckMap) <= 5 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sc1->pubAckMu);
}
}
testCond(s == NATS_OK);
test("Close unblock: ");
natsConn_close(nc);
nc = NULL;
stanConnection_Destroy(sc1);
natsThread_Join(t);
natsThread_Destroy(t);
stanConn_release(sc1);
testCond(s == NATS_OK);
// Repeat test with sync publishers
// grap nc first
natsMutex_Lock(sc2->mu);
nc = sc2->nc;
natsMutex_Unlock(sc2->mu);
test("Check max inflight: ");
// Retain the connection
stanConn_retain(sc2);
for (i=0; (s == NATS_OK) && (i<10); i++)
s = natsThread_Create(&(pts[i]), _stanPublishSyncThread, (void*) sc2);
if (s == NATS_OK)
{
int i = 0;
// Check that pubAckMap size is never greater than 5.
for (i=0; (s == NATS_OK) && (i<10); i++)
{
nats_Sleep(100);
natsMutex_Lock(sc2->pubAckMu);
s = (natsStrHash_Count(sc2->pubAckMap) <= 5 ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sc2->pubAckMu);
}
}
testCond(s == NATS_OK);
test("Close unblock: ");
natsConn_close(nc);
nc = NULL;
stanConnection_Destroy(sc2);
for (i = 0; i<10; i++)
{
if (pts[i] != NULL)
{
natsThread_Join(pts[i]);
natsThread_Destroy(pts[i]);
}
}
stanConn_release(sc2);
testCond(s == NATS_OK);
stanConnOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(nPid);
}
static void
_dummyStanMsgHandler(stanConnection *sc, stanSubscription *sub, const char *channel,
stanMsg *msg, void* closure)
{
stanMsg_Destroy(msg);
}
static void
_stanMsgHandlerBumpSum(stanConnection *sc, stanSubscription *sub, const char *channel,
stanMsg *msg, void* closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
if (!stanMsg_IsRedelivered(msg))
args->sum++;
else
args->redelivered++;
natsCondition_Broadcast(args->c);
natsMutex_Unlock(args->m);
stanMsg_Destroy(msg);
}
static void
test_StanBasicSubscription(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanSubscription *sub = NULL;
stanSubscription *subf = NULL;
natsPid pid = NATS_INVALID_PID;
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
if (s != NATS_OK)
{
_stopServer(pid);
FAIL("Unable to create connection for this test");
}
test("Basic subscibe: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
testCond(s == NATS_OK);
test("Close connection: ")
s = stanConnection_Close(sc);
testCond(s == NATS_OK);
test("Subscribe should fail after conn closed: ");
s = stanConnection_Subscribe(&subf, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
testCond(s == NATS_CONNECTION_CLOSED);
test("Subscribe should fail after conn closed: ");
s = stanConnection_QueueSubscribe(&subf, sc, "foo", "bar", _dummyStanMsgHandler, NULL, NULL);
testCond(s == NATS_CONNECTION_CLOSED);
stanSubscription_Destroy(sub);
stanConnection_Destroy(sc);
_stopServer(pid);
}
static void
test_StanSubscriptionCloseAndUnsubscribe(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanSubscription *sub = NULL;
stanSubscription *sub2 = NULL;
natsPid pid = NATS_INVALID_PID;
natsPid spid = NATS_INVALID_PID;
char *cs = NULL;
stanConnOptions *opts = NULL;
s = stanConnOptions_Create(&opts);
IFOK(s, stanConnOptions_SetConnectionWait(opts, 250));
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
spid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true);
CHECK_SERVER_STARTED(spid);
s = stanConnection_Connect(&sc, clusterName, clientName, opts);
if (s != NATS_OK)
{
_stopServer(spid);
_stopServer(pid);
FAIL("Unable to create connection for this test");
}
test("Unsubscribe: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
IFOK(s, stanSubscription_Unsubscribe(sub));
testCond(s == NATS_OK);
stanSubscription_Destroy(sub);
sub = NULL;
test("Close: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
IFOK(s, stanSubscription_Close(sub));
testCond(s == NATS_OK);
stanSubscription_Destroy(sub);
sub = NULL;
test("Close not supported: ");
// Simulate that we connected to an older server
natsMutex_Lock(sc->mu);
cs = sc->subCloseRequests;
sc->subCloseRequests = NULL;
natsMutex_Unlock(sc->mu);
s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
IFOK(s, stanSubscription_Close(sub));
testCond((s == NATS_NO_SERVER_SUPPORT) &&
(strstr(nats_GetLastError(NULL), STAN_ERR_SUB_CLOSE_NOT_SUPPORTED) != NULL));
stanSubscription_Destroy(sub);
sub = NULL;
natsMutex_Lock(sc->mu);
sc->subCloseRequests = cs;
natsMutex_Unlock(sc->mu);
test("Close/Unsub timeout: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
IFOK(s, stanConnection_Subscribe(&sub2, sc, "foo", _dummyStanMsgHandler, NULL, NULL));
// Stop the serer
_stopServer(spid);
if (s == NATS_OK)
{
s = stanSubscription_Close(sub);
if (s != NATS_OK)
s = stanSubscription_Unsubscribe(sub2);
}
if (serverVersionAtLeast(2, 2, 0))
{
testCond((s == NATS_NO_RESPONDERS) &&
(strstr(nats_GetLastError(NULL), "no streaming server was listening") != NULL));
}
else
{
testCond((s == NATS_TIMEOUT) &&
(strstr(nats_GetLastError(NULL), "request timeout") != NULL));
}
stanSubscription_Destroy(sub);
stanSubscription_Destroy(sub2);
stanConnClose(sc, false);
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
_stopServer(pid);
}
static void
test_StanDurableSubscription(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanSubscription *dur = NULL;
stanSubOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg args;
int i;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Error setting up test");
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
if (s != NATS_OK)
{
_stopServer(pid);
FAIL("Unable to create connection for this test");
}
test("Send some messages: ");
for (i=0; (s == NATS_OK) && (i<3); i++)
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Basic durable subscibe: ");
s = stanSubOptions_Create(&opts);
IFOK(s, stanSubOptions_SetDurableName(opts, "dur"));
IFOK(s, stanSubOptions_DeliverAllAvailable(opts));
IFOK(s, stanConnection_Subscribe(&dur, sc, "foo", _stanMsgHandlerBumpSum, &args, opts));
testCond(s == NATS_OK);
test("Check 3 messages received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 3))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
// Wait a bit to give a chance for the server to process acks.
nats_Sleep(500);
test("Close connection: ");
s = stanConnection_Close(sc);
testCond(s == NATS_OK);
stanSubscription_Destroy(dur);
dur = NULL;
stanConnection_Destroy(sc);
sc = NULL;
test("Connect again: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
testCond(s == NATS_OK);
test("Send 2 more messages: ");
for (i=0; (s == NATS_OK) && (i<2); i++)
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Recreate durable with start seq 1: ");
s = stanSubOptions_StartAtSequence(opts, 1);
IFOK(s, stanConnection_Subscribe(&dur, sc, "foo", _stanMsgHandlerBumpSum, &args, opts));
testCond(s == NATS_OK);
test("Check 5 messages total are received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 5))
s = natsCondition_TimedWait(args.c, args.m, 2000);
testCond(s == NATS_OK);
test("Check no redelivered: ");
testCond((s == NATS_OK) && (args.redelivered == 0));
natsMutex_Unlock(args.m);
stanSubscription_Destroy(dur);
stanSubOptions_Destroy(opts);
stanConnection_Destroy(sc);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
test_StanBasicQueueSubscription(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanSubscription *qsub1 = NULL;
stanSubscription *qsub2 = NULL;
stanSubscription *qsub3 = NULL;
stanSubOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Error setting up test");
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
if (s != NATS_OK)
{
_stopServer(pid);
FAIL("Unable to create connection for this test");
}
test("Basic queue subscibe: ");
s = stanConnection_QueueSubscribe(&qsub1, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, NULL);
IFOK(s, stanConnection_QueueSubscribe(&qsub2, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, NULL));
testCond(s == NATS_OK);
// Test that durable and non durable queue subscribers with
// same name can coexist and they both receive the same message.
test("New durable queue sub with same queue name: ");
s = stanSubOptions_Create(&opts);
IFOK(s, stanSubOptions_SetDurableName(opts, "durable-queue-sub"));
IFOK(s, stanConnection_QueueSubscribe(&qsub3, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, opts));
testCond(s == NATS_OK);
test("Check published message ok: ");
s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5);
testCond(s == NATS_OK);
test("Check 1 message published is received once per group: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 2))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
stanSubscription_Destroy(qsub1);
stanSubscription_Destroy(qsub2);
stanSubscription_Destroy(qsub3);
stanSubOptions_Destroy(opts);
stanConnection_Destroy(sc);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
test_StanDurableQueueSubscription(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanSubscription *dur = NULL;
stanSubOptions *opts = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg args;
int i;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Error setting up test");
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
if (s != NATS_OK)
{
_stopServer(pid);
FAIL("Unable to create connection for this test");
}
test("Send some messages: ");
for (i=0; (s == NATS_OK) && (i<3); i++)
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Basic durable subscibe: ");
s = stanSubOptions_Create(&opts);
IFOK(s, stanSubOptions_SetDurableName(opts, "dur"));
IFOK(s, stanSubOptions_DeliverAllAvailable(opts));
IFOK(s, stanConnection_QueueSubscribe(&dur, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, opts));
testCond(s == NATS_OK);
test("Check 3 messages received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 3))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
// Give a chance for the server to process those acks
nats_Sleep(500);
test("Close connection: ");
s = stanConnection_Close(sc);
testCond(s == NATS_OK);
stanSubscription_Destroy(dur);
dur = NULL;
stanConnection_Destroy(sc);
sc = NULL;
test("Connect again: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
testCond(s == NATS_OK);
test("Send 2 more messages: ");
for (i=0; (s == NATS_OK) && (i<2); i++)
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Recreate durable with start seq 1: ");
s = stanSubOptions_StartAtSequence(opts, 1);
IFOK(s, stanConnection_QueueSubscribe(&dur, sc, "foo", "bar", _stanMsgHandlerBumpSum, &args, opts));
testCond(s == NATS_OK);
test("Check 5 messages total are received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 5))
s = natsCondition_TimedWait(args.c, args.m, 2000);
testCond(s == NATS_OK);
test("Check no redelivered: ");
testCond((s == NATS_OK) && (args.redelivered == 0));
natsMutex_Unlock(args.m);
stanSubscription_Destroy(dur);
stanSubOptions_Destroy(opts);
stanConnection_Destroy(sc);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
_stanCheckRecvStanMsg(stanConnection *sc, stanSubscription *sub, const char *channel,
stanMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
if (strcmp(channel, args->channel) != 0)
args->status = NATS_ERR;
if ((args->status == NATS_OK) && (strncmp(args->string, (char*) stanMsg_GetData(msg), strlen(args->string)) != 0))
args->status = NATS_ERR;
if ((args->status == NATS_OK) && (stanMsg_GetDataLength(msg) != 5))
args->status = NATS_ERR;
if ((args->status == NATS_OK) && (stanMsg_GetSequence(msg) == 0))
args->status = NATS_ERR;
if ((args->status == NATS_OK) && (stanMsg_GetTimestamp(msg) == 0))
args->status = NATS_ERR;
stanMsg_Destroy(msg);
args->done = true;
natsCondition_Signal(args->c);
natsMutex_Unlock(args->m);
}
static void
test_StanCheckReceivedvMsg(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanSubscription *sub = NULL;
natsPid pid = NATS_INVALID_PID;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Error setting up test");
args.channel = "foo";
args.string = "hello";
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
if (s != NATS_OK)
{
_stopServer(pid);
FAIL("Unable to create connection for this test");
}
test("Create sub: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _stanCheckRecvStanMsg, (void*) &args, NULL);
testCond(s == NATS_OK);
test("Send a message: ");
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Check message received: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.done)
s = natsCondition_TimedWait(args.c, args.m, 2000);
s = args.status;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
stanSubscription_Destroy(sub);
stanConnection_Destroy(sc);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
_stanManualAck(stanConnection *sc, stanSubscription *sub, const char *channel,
stanMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsStatus s;
natsMutex_Lock(args->m);
// control 1 means auto-ack, so expect ack to fail
s = stanSubscription_AckMsg(sub, msg);
args->status = NATS_OK;
if ((args->control == 1) &&
(s != NATS_ERR) &&
(strstr(nats_GetLastError(NULL), STAN_ERR_MANUAL_ACK) == NULL))
{
args->status = NATS_ERR;
}
else if ((args->control == 2) && (s != NATS_OK))
{
args->status = NATS_ERR;
}
args->sum++;
natsCondition_Signal(args->c);
stanMsg_Destroy(msg);
natsMutex_Unlock(args->m);
}
static void
_stanGetMsg(stanConnection *sc, stanSubscription *sub, const char *channel,
stanMsg *msg, void *closure)
{
struct threadArg *args = (struct threadArg*) closure;
natsMutex_Lock(args->m);
args->sMsg = msg;
args->msgReceived = true;
natsCondition_Signal(args->c);
natsMutex_Unlock(args->m);
}
static void
test_StanSubscriptionAckMsg(void)
{
natsStatus s;
stanConnection *sc = NULL;
stanSubscription *sub = NULL;
stanSubscription *sub2 = NULL;
natsPid pid = NATS_INVALID_PID;
stanSubOptions *opts = NULL;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
IFOK(s, stanSubOptions_Create(&opts));
IFOK(s, stanSubOptions_SetManualAckMode(opts, true));
if (s != NATS_OK)
FAIL("Error setting up test");
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
if (s != NATS_OK)
{
_stopServer(pid);
FAIL("Unable to create connection for this test");
}
test("Create sub with auto-ack: ");
args.control = 1;
s = stanConnection_Subscribe(&sub, sc, "foo", _stanManualAck, (void*) &args, NULL);
testCond(s == NATS_OK);
test("Publish message: ");
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Check manual ack fails: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 1))
s = natsCondition_TimedWait(args.c, args.m, 2000);
if (s == NATS_OK)
s = args.status;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
stanSubscription_Destroy(sub);
sub = NULL;
natsMutex_Lock(args.m);
args.control = 2;
args.sum = 0;
natsMutex_Unlock(args.m);
test("Create sub with manual ack: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _stanManualAck, (void*) &args, opts);
testCond(s == NATS_OK);
test("Publish message: ");
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Check manual ack ok: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 1))
s = natsCondition_TimedWait(args.c, args.m, 2000);
if (s == NATS_OK)
s = args.status;
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
stanSubscription_Destroy(sub);
sub = NULL;
test("Create sub and get message: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _stanGetMsg, (void*) &args, NULL);
IFOK(s, stanConnection_Publish(sc, "foo", (const void*) "hello", 5));
if (s == NATS_OK)
{
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && !args.msgReceived)
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
}
testCond(s == NATS_OK)
test("Create sub with manual ack: ");
s = stanConnection_Subscribe(&sub2, sc, "foo", _dummyStanMsgHandler, NULL, opts);
testCond(s == NATS_OK);
test("NULL Sub should fail: ");
s = stanSubscription_AckMsg(NULL, args.sMsg);
testCond(s == NATS_INVALID_ARG);
test("NULL Msg should fail: ");
s = stanSubscription_AckMsg(sub2, NULL);
testCond(s == NATS_INVALID_ARG);
test("Sub acking not own message fails: ");
s = stanSubscription_AckMsg(sub2, args.sMsg);
testCond((s == NATS_ILLEGAL_STATE)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), STAN_ERR_SUB_NOT_OWNER) != NULL));
natsMutex_Lock(args.m);
if (args.sMsg != NULL)
stanMsg_Destroy(args.sMsg);
natsMutex_Unlock(args.m);
stanSubscription_Destroy(sub);
stanSubscription_Destroy(sub2);
stanConnection_Destroy(sc);
stanSubOptions_Destroy(opts);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
test_StanPings(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
stanConnection *sc = NULL;
stanConnOptions *opts = NULL;
natsConnection *nc = NULL;
natsSubscription *psub = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
testAllowMillisecInPings = true;
s = stanConnOptions_Create(&opts);
}
IFOK(s, stanConnOptions_SetPings(opts, -50, 5));
if (s == NATS_OK)
{
arg.string = STAN_ERR_MAX_PINGS;
s = stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) &arg);
}
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
stanConnOptions_Destroy(opts);
testAllowMillisecInPings = false;
FAIL("Unable to setup test");
}
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
if (pid == NATS_INVALID_PID)
{
testAllowMillisecInPings = false;
FAIL("Unable to start server");
}
// Create NATS Subscription on pings subject and count the
// outgoing pings.
test("Pings are sent: ");
s = natsConnection_ConnectTo(&nc, "nats://127.0.0.1:4222");
// Use _recvTestString with control == 3 to increment sum up to 10
if (s == NATS_OK)
{
char psubject[256];
snprintf(psubject, sizeof(psubject), "%s.%s.pings", STAN_CONN_OPTS_DEFAULT_DISCOVERY_PREFIX, clusterName);
arg.control = 3;
s = natsConnection_Subscribe(&psub, nc, psubject, _recvTestString, (void*) &arg);
}
// Connect to Stan
IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, opts));
// We should start receiving PINGs
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && (arg.sum < 10))
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
if ((s == NATS_OK) && (arg.sum < 10))
s = NATS_ERR;
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
natsSubscription_Destroy(psub);
natsConnection_Destroy(nc);
test("Connection lost handler invoked: ");
_stopServer(pid);
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
IFOK(s, arg.status);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Connection closed: ");
natsMutex_Lock(sc->mu);
s = (sc->closed ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sc->mu);
testCond(s == NATS_OK);
stanConnClose(sc, false);
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
testAllowMillisecInPings = false;
_destroyDefaultThreadArgs(&arg);
}
static void
test_StanPingsNoResponder(void)
{
natsStatus s;
natsPid nPid = NATS_INVALID_PID;
natsPid sPid = NATS_INVALID_PID;
stanConnection *sc = NULL;
stanConnOptions *opts = NULL;
struct threadArg arg;
nPid = _startServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(nPid);
sPid = _startStreamingServer("nats://127.0.0.1:4222", "-ns nats://127.0.0.1:4222", true);
CHECK_SERVER_STARTED(sPid);
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
testAllowMillisecInPings = true;
s = stanConnOptions_Create(&opts);
}
IFOK(s, stanConnOptions_SetPings(opts, -50, 5));
IFOK(s, stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) &arg));
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
stanConnOptions_Destroy(opts);
testAllowMillisecInPings = false;
FAIL("Unable to setup test");
}
// Connect to Stan
test("Connects ok: ")
s = stanConnection_Connect(&sc, clusterName, clientName, opts);
testCond(s == NATS_OK);
// Shutdown the streaming server, but not NATS Server
_stopServer(sPid);
// We should get the connection lost callback invoked.
test("Connection lost: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
testAllowMillisecInPings = false;
_stopServer(nPid);
_destroyDefaultThreadArgs(&arg);
}
static void
test_StanConnectionLostHandlerNotSet(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
stanConnection *sc = NULL;
stanConnOptions *opts = NULL;
natsOptions *nOpts = NULL;
struct threadArg arg;
testAllowMillisecInPings = true;
s = _createDefaultThreadArgsForCbTests(&arg);
IFOK(s, natsOptions_Create(&nOpts));
IFOK(s, natsOptions_SetClosedCB(nOpts, _closedCb, (void*)&arg));
IFOK(s, stanConnOptions_Create(&opts));
IFOK(s, stanConnOptions_SetNATSOptions(opts, nOpts));
IFOK(s, stanConnOptions_SetPings(opts, -50, 5));
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
stanConnOptions_Destroy(opts);
testAllowMillisecInPings = false;
FAIL("Unable to setup test");
}
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
if (pid == NATS_INVALID_PID)
{
testAllowMillisecInPings = false;
FAIL("Unable to start server");
}
test("Connect: ");
s = stanConnection_Connect(&sc, clusterName, clientName, opts);
testCond(s == NATS_OK);
// Stop server and wait for closed notification
_stopServer(pid);
test("Connection closed: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Connection closed: ");
natsMutex_Lock(sc->mu);
s = (sc->closed ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sc->mu);
testCond(s == NATS_OK);
stanConnClose(sc, false);
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
natsOptions_Destroy(nOpts);
testAllowMillisecInPings = false;
_destroyDefaultThreadArgs(&arg);
}
static void
test_StanPingsUnblockPubCalls(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
stanConnection *sc = NULL;
stanConnOptions *opts = NULL;
natsThread *t = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s == NATS_OK)
{
testAllowMillisecInPings = true;
s = stanConnOptions_Create(&opts);
}
IFOK(s, stanConnOptions_SetMaxPubAcksInflight(opts, 1, 1.0));
IFOK(s, stanConnOptions_SetPings(opts, -100, 5));
if (s == NATS_OK)
{
arg.string = STAN_ERR_MAX_PINGS;
s = stanConnOptions_SetConnectionLostHandler(opts, _stanConnLostCB, (void*) &arg);
}
if (s != NATS_OK)
{
_destroyDefaultThreadArgs(&arg);
stanConnOptions_Destroy(opts);
testAllowMillisecInPings = false;
FAIL("Unable to setup test");
}
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
if (pid == NATS_INVALID_PID)
{
testAllowMillisecInPings = false;
FAIL("Unable to start server");
}
test("Connect: ");
s = stanConnection_Connect(&sc, clusterName, clientName, opts);
testCond(s == NATS_OK);
_stopServer(pid);
natsThread_Create(&t, _stanPublishAsyncThread, (void*) &arg);
test("Sync publish released: ");
s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5);
testCond(s != NATS_OK);
nats_clearLastError();
s = NATS_OK;
test("Async publish released: ");
if (t != NULL)
{
natsThread_Join(t);
natsThread_Destroy(t);
}
testCond(s == NATS_OK);
test("Connection lost handler invoked: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.closed)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
IFOK(s, arg.status);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Connection closed: ");
natsMutex_Lock(sc->mu);
s = (sc->closed ? NATS_OK : NATS_ERR);
natsMutex_Unlock(sc->mu);
testCond(s == NATS_OK);
stanConnClose(sc, false);
stanConnection_Destroy(sc);
stanConnOptions_Destroy(opts);
testAllowMillisecInPings = false;
_destroyDefaultThreadArgs(&arg);
}
static void
test_StanGetNATSConnection(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
stanConnection *sc = NULL;
stanSubscription *ssub = NULL;
natsConnection *nc = NULL;
natsConnection *nc2 = NULL;
natsSubscription *nsub = NULL;
struct threadArg args;
s = _createDefaultThreadArgsForCbTests(&args);
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Connect: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
testCond(s == NATS_OK);
test("Create sub: ");
s = stanConnection_Subscribe(&ssub, sc, "foo", _stanMsgHandlerBumpSum, (void*) &args, NULL);
testCond(s == NATS_OK);
test("Get NATS Connection: ");
s = stanConnection_GetNATSConnection(sc, &nc);
testCond((s == NATS_OK) && (nc != NULL));
test("Create sub & pub with NATS OK: ");
s = natsConnection_Subscribe(&nsub, nc, "foo", _dummyMsgHandler, NULL);
IFOK(s, natsConnection_PublishString(nc, "foo", "hello"));
testCond(s == NATS_OK);
test("Invalid to drain: ");
s = natsConnection_Drain(nc);
if (s == NATS_ILLEGAL_STATE)
s = natsConnection_DrainTimeout(nc, 1000);
if ((s == NATS_ILLEGAL_STATE)
&& (nats_GetLastError(NULL) != NULL)
&& (strstr(nats_GetLastError(NULL), "owned") != NULL))
{
s = NATS_OK;
nats_clearLastError();
}
testCond(s == NATS_OK);
test("Closing NATS Conn has no effect: ");
natsConnection_Close(nc);
s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5);
testCond(s == NATS_OK);
test("Destroying NATS Conn has no effect: ");
natsConnection_Destroy(nc);
s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5);
testCond(s == NATS_OK);
test("Calling release more than get does not crash: ");
stanConnection_ReleaseNATSConnection(sc);
stanConnection_ReleaseNATSConnection(sc);
s = stanConnection_Publish(sc, "foo", (const void*)"hello", 5);
testCond(s == NATS_OK);
test("Should have received 3 messages: ");
natsMutex_Lock(args.m);
while ((s != NATS_TIMEOUT) && (args.sum != 3))
s = natsCondition_TimedWait(args.c, args.m, 2000);
natsMutex_Unlock(args.m);
testCond(s == NATS_OK);
nc2 = NULL;
test("Invalid arg (sc == NULL): ");
s = stanConnection_GetNATSConnection(NULL, &nc2);
testCond((s == NATS_INVALID_ARG) && (nc2 == NULL));
nats_clearLastError();
test("Invalid arg (nc == NULL): ");
s = stanConnection_GetNATSConnection(sc, NULL);
testCond((s == NATS_INVALID_ARG) && (nc2 == NULL));
nats_clearLastError();
test("GetNATSConnection on closed connection fails: ");
s = stanConnection_Close(sc);
IFOK(s, stanConnection_GetNATSConnection(sc, &nc2));
testCond(s == NATS_CONNECTION_CLOSED);
nats_clearLastError();
natsSubscription_Destroy(nsub);
stanSubscription_Destroy(ssub);
// Valgrind will tell us if the NATS connection/STAN connection
// were properly released.
stanConnection_Destroy(sc);
_destroyDefaultThreadArgs(&args);
_stopServer(pid);
}
static void
test_StanNoRetryOnFailedConnect(void)
{
natsStatus s;
natsOptions *opts = NULL;
stanConnOptions *sopts = NULL;
stanConnection *sc = NULL;
test("RetryOnFailedConnect not supported: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_SetRetryOnFailedConnect(opts, true, _dummyConnHandler, (void*)1));
IFOK(s, stanConnOptions_Create(&sopts));
IFOK(s, stanConnOptions_SetNATSOptions(sopts, opts));
IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, sopts));
testCond(s == NATS_NO_SERVER);
natsOptions_Destroy(opts);
stanConnOptions_Destroy(sopts);
}
static bool
_subDlvThreadPooled(natsSubscription *sub)
{
bool pooled;
natsSub_Lock(sub);
pooled = (sub->libDlvWorker != NULL);
natsSub_Unlock(sub);
return pooled;
}
static void
test_StanInternalSubsNotPooled(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
natsOptions *opts = NULL;
stanConnOptions *sopts = NULL;
stanSubscription *sub = NULL;
stanConnection *sc = NULL;
natsSubscription *hbSub = NULL;
natsSubscription *ackSub = NULL;
natsSubscription *pingSub = NULL;
natsSubscription *inboxSub = NULL;
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Can connet: ");
s = natsOptions_Create(&opts);
IFOK(s, natsOptions_UseGlobalMessageDelivery(opts, true));
IFOK(s, stanConnOptions_Create(&sopts));
IFOK(s, stanConnOptions_SetNATSOptions(sopts, opts));
IFOK(s, stanConnection_Connect(&sc, clusterName, clientName, sopts));
testCond(s == NATS_OK);
test("Create sub: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
testCond(s == NATS_OK);
stanConn_Lock(sc);
hbSub = sc->hbSubscription;
ackSub = sc->ackSubscription;
pingSub = sc->pingSub;
stanConn_Unlock(sc);
stanSub_Lock(sub);
inboxSub = sub->inboxSub;
stanSub_Unlock(sub);
test("HBSub not pooled: ");
testCond((s == NATS_OK) && !_subDlvThreadPooled(hbSub));
test("AckSub not pooled: ");
testCond((s == NATS_OK) && !_subDlvThreadPooled(ackSub));
test("PingSub not pooled: ");
testCond((s == NATS_OK) && !_subDlvThreadPooled(pingSub));
test("InboxSub pooled: ");
testCond((s == NATS_OK) && _subDlvThreadPooled(inboxSub));
natsOptions_Destroy(opts);
stanConnOptions_Destroy(sopts);
stanSubscription_Destroy(sub);
stanConnection_Destroy(sc);
if (valgrind)
nats_Sleep(900);
_stopServer(pid);
}
static void
_stanSubOnComplete(void *closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
if (arg->control == 1)
{
arg->done = true;
natsCondition_Signal(arg->c);
}
natsMutex_Unlock(arg->m);
}
static void
_stanSubOnCompleteMsgCB(stanConnection *sc, stanSubscription *sub, const char *channel,
stanMsg *msg, void* closure)
{
struct threadArg *arg = (struct threadArg*) closure;
natsMutex_Lock(arg->m);
arg->msgReceived = true;
natsCondition_Signal(arg->c);
natsMutex_Unlock(arg->m);
// Sleep to simulate callbac doing some processing and let the
// main thread close the subscription.
nats_Sleep(500);
// Update a field that _stanSubOnComplete will ensure is set to
// prove that the onComplete is invoked after the msg callback
// has returned.
natsMutex_Lock(arg->m);
arg->control = 1;
natsMutex_Unlock(arg->m);
stanMsg_Destroy(msg);
}
static void
test_StanSubOnComplete(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
stanConnection *sc = NULL;
stanSubscription *sub = NULL;
struct threadArg arg;
s = _createDefaultThreadArgsForCbTests(&arg);
if (s != NATS_OK)
FAIL("Unable to setup test");
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Can connet: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
testCond(s == NATS_OK);
test("SetOnComplete error: ");
s = stanSubscription_SetOnCompleteCB(NULL, _stanSubOnComplete, NULL);
testCond(s == NATS_INVALID_ARG);
test("Create sub: ");
s = stanConnection_Subscribe(&sub, sc, "foo", _stanSubOnCompleteMsgCB, &arg, NULL);
testCond(s == NATS_OK);
test("SetOnComplete ok: ");
s = stanSubscription_SetOnCompleteCB(sub, _stanSubOnComplete, &arg);
testCond(s == NATS_OK);
test("Remove onComplete: ");
s = stanSubscription_SetOnCompleteCB(sub, NULL, NULL);
if (s == NATS_OK)
{
stanSub_Lock(sub);
if ((sub->onCompleteCB != NULL) || (sub->onCompleteCBClosure != NULL))
s = NATS_ERR;
stanSub_Unlock(sub);
}
testCond(s == NATS_OK);
test("SetOnComplete ok: ");
s = stanSubscription_SetOnCompleteCB(sub, _stanSubOnComplete, &arg);
testCond(s == NATS_OK);
test("Publish msg: ");
s = stanConnection_Publish(sc, "foo", (const void*) "hello", 5);
testCond(s == NATS_OK);
test("Wait for message to be received: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.msgReceived)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("Close subscription: ")
s = stanSubscription_Close(sub);
testCond(s == NATS_OK);
test("Ensure onComplete invoked after cb returned: ");
natsMutex_Lock(arg.m);
while ((s != NATS_TIMEOUT) && !arg.done)
s = natsCondition_TimedWait(arg.c, arg.m, 2000);
natsMutex_Unlock(arg.m);
testCond(s == NATS_OK);
test("SetOnComplete after close: ");
s = stanSubscription_SetOnCompleteCB(sub, _stanSubOnComplete, &arg);
testCond(s == NATS_INVALID_SUBSCRIPTION);
stanSubscription_Destroy(sub);
stanConnection_Destroy(sc);
_stopServer(pid);
_destroyDefaultThreadArgs(&arg);
}
static void
test_StanSubTimeout(void)
{
natsStatus s;
natsPid pid = NATS_INVALID_PID;
stanConnection *sc = NULL;
stanSubscription *sub = NULL;
natsConnection *nc = NULL;
natsSubscription *ncSub = NULL;
const char *closeSubj = NULL;
natsMsg *resp = NULL;
int i = 0;
Pb__SubscriptionRequest *r = NULL;
pid = _startStreamingServer("nats://127.0.0.1:4222", NULL, true);
CHECK_SERVER_STARTED(pid);
test("Can connect: ");
s = stanConnection_Connect(&sc, clusterName, clientName, NULL);
testCond(s == NATS_OK);
test("Get NATS subscription: ");
s = stanConnection_GetNATSConnection(sc, &nc);
testCond(s == NATS_OK);
test("Setup NATS sub: ");
natsMutex_Lock(sc->mu);
closeSubj = (const char*) sc->subCloseRequests;
natsMutex_Unlock(sc->mu);
s = natsConnection_SubscribeSync(&ncSub, nc, closeSubj);
testCond(s == NATS_OK);
// Artificially lower the timeout to make sure it fails.
natsMutex_Lock(sc->mu);
sc->opts->connTimeout=1;
natsMutex_Unlock(sc->mu);
test("Subscribe should timeout: ");
// Try to get the timeout...
for (i=0;i<50;i++)
{
s = stanConnection_Subscribe(&sub, sc, "foo", _dummyStanMsgHandler, NULL, NULL);
if (s == NATS_TIMEOUT)
break;
stanSubscription_Destroy(sub);
sub = NULL;
}
// We don't want to fail the thest if we did not get a timeout.
testCond((s == NATS_OK) || (s == NATS_TIMEOUT));
// However, proceed with the rest of the test only if it was a timeout.
if (s == NATS_TIMEOUT)
{
test("Check sub close request sent: ");
s = natsSubscription_NextMsg(&resp, ncSub, 1000);
testCond((s == NATS_OK) && (resp != NULL));
test("Check request: ");
r = pb__subscription_request__unpack(NULL,
(size_t) natsMsg_GetDataLength(resp),
(const uint8_t*) natsMsg_GetData(resp));
testCond((r != NULL)
&& (strcmp(r->clientid, clientName) == 0)
&& (strcmp(r->subject, "foo") == 0)
&& (r->inbox != NULL));
if (r != NULL)
pb__subscription_request__free_unpacked(r, NULL);
natsMsg_Destroy(resp);
}
natsSubscription_Destroy(ncSub);
stanSubscription_Destroy(sub);
stanConnection_ReleaseNATSConnection(sc);
stanConnection_Destroy(sc);
_stopServer(pid);
}
#endif
typedef void (*testFunc)(void);
typedef struct __testInfo
{
const char *name;
testFunc func;
} testInfo;
static testInfo allTests[] =
{
// Building blocks
{"Version", test_Version},
{"VersionMatchesTag", test_VersionMatchesTag},
{"OpenCloseAndWait", test_OpenCloseAndWait},
{"natsNowAndSleep", test_natsNowAndSleep},
{"natsAllocSprintf", test_natsAllocSprintf},
{"natsStrCaseStr", test_natsStrCaseStr},
{"natsSnprintf", test_natsSnprintf},
{"natsBuffer", test_natsBuffer},
{"natsParseInt64", test_natsParseInt64},
{"natsParseControl", test_natsParseControl},
{"natsNormalizeErr", test_natsNormalizeErr},
{"natsMutex", test_natsMutex},
{"natsThread", test_natsThread},
{"natsCondition", test_natsCondition},
{"natsTimer", test_natsTimer},
{"natsUrl", test_natsUrl},
{"natsCreateStringFromBuffer", test_natsCreateStringFromBuffer},
{"natsHash", test_natsHash},
{"natsHashing", test_natsHashing},
{"natsStrHash", test_natsStrHash},
{"natsInbox", test_natsInbox},
{"natsOptions", test_natsOptions},
{"natsSock_ConnectTcp", test_natsSock_ConnectTcp},
{"natsSock_ShuffleIPs", test_natsSock_ShuffleIPs},
{"natsSock_IPOrder", test_natsSock_IPOrder},
{"natsSock_ReadLine", test_natsSock_ReadLine},
{"natsJSON", test_natsJSON},
{"natsEncodeTimeUTC", test_natsEncodeTimeUTC},
{"natsErrWithLongText", test_natsErrWithLongText},
{"natsErrStackMoreThanMaxFrames", test_natsErrStackMoreThanMaxFrames},
{"natsMsg", test_natsMsg},
{"natsBase32", test_natsBase32Decode},
{"natsBase64", test_natsBase64Encode},
{"natsCRC16", test_natsCRC16},
{"natsKeys", test_natsKeys},
{"natsReadFile", test_natsReadFile},
{"natsGetJWTOrSeed", test_natsGetJWTOrSeed},
{"natsHostIsIP", test_natsHostIsIP},
{"natsWaitReady", test_natsWaitReady},
{"natsSign", test_natsSign},
{"HeadersLift", test_natsMsgHeadersLift},
{"HeadersAPIs", test_natsMsgHeaderAPIs},
{"MsgIsJSControl", test_natsMsgIsJSCtrl},
{"SrvVersionAtLeast", test_natsSrvVersionAtLeast},
// Package Level Tests
{"ReconnectServerStats", test_ReconnectServerStats},
{"ParseStateReconnectFunctionality",test_ParseStateReconnectFunctionality},
{"ServersRandomize", test_ServersRandomize},
{"SelectNextServer", test_SelectNextServer},
{"ParserPing", test_ParserPing},
{"ParserErr", test_ParserErr},
{"ParserOK", test_ParserOK},
{"ParseINFO", test_ParseINFO},
{"ParserShouldFail", test_ParserShouldFail},
{"ParserSplitMsg", test_ParserSplitMsg},
{"ProcessMsgArgs", test_ProcessMsgArgs},
{"LibMsgDelivery", test_LibMsgDelivery},
{"AsyncINFO", test_AsyncINFO},
{"RequestPool", test_RequestPool},
{"NoFlusherIfSendAsapOption", test_NoFlusherIfSendAsap},
{"HeadersAndSubPendingBytes", test_HeadersAndSubPendingBytes},
// Public API Tests
{"DefaultConnection", test_DefaultConnection},
{"SimplifiedURLs", test_SimplifiedURLs},
{"IPResolutionOrder", test_IPResolutionOrder},
{"UseDefaultURLIfNoServerSpecified",test_UseDefaultURLIfNoServerSpecified},
{"ConnectToWithMultipleURLs", test_ConnectToWithMultipleURLs},
{"ConnectionWithNULLOptions", test_ConnectionWithNullOptions},
{"ConnectionToWithNullURLs", test_ConnectionToWithNullURLs},
{"ConnectionStatus", test_ConnectionStatus},
{"ConnClosedCB", test_ConnClosedCB},
{"CloseDisconnectedCB", test_CloseDisconnectedCB},
{"ServerStopDisconnectedCB", test_ServerStopDisconnectedCB},
{"ClosedConnections", test_ClosedConnections},
{"ConnectVerboseOption", test_ConnectVerboseOption},
{"ReconnectThreadLeak", test_ReconnectThreadLeak},
{"ReconnectTotalTime", test_ReconnectTotalTime},
{"ReconnectDisallowedFlags", test_ReconnectDisallowedFlags},
{"ReconnectAllowedFlags", test_ReconnectAllowedFlags},
{"ConnCloseBreaksReconnectLoop", test_ConnCloseBreaksReconnectLoop},
{"BasicReconnectFunctionality", test_BasicReconnectFunctionality},
{"ExtendedReconnectFunctionality", test_ExtendedReconnectFunctionality},
{"QueueSubsOnReconnect", test_QueueSubsOnReconnect},
{"IsClosed", test_IsClosed},
{"IsReconnectingAndStatus", test_IsReconnectingAndStatus},
{"ReconnectBufSize", test_ReconnectBufSize},
{"RetryOnFailedConnect", test_RetryOnFailedConnect},
{"NoPartialOnReconnect", test_NoPartialOnReconnect},
{"ReconnectFailsPendingRequests", test_ReconnectFailsPendingRequest},
{"ErrOnConnectAndDeadlock", test_ErrOnConnectAndDeadlock},
{"ErrOnMaxPayloadLimit", test_ErrOnMaxPayloadLimit},
{"Auth", test_Auth},
{"AuthFailNoDisconnectCB", test_AuthFailNoDisconnectCB},
{"AuthToken", test_AuthToken},
{"AuthTokenHandler", test_AuthTokenHandler},
{"PermViolation", test_PermViolation},
{"AuthViolation", test_AuthViolation},
{"AuthenticationExpired", test_AuthenticationExpired},
{"AuthenticationExpiredReconnect", test_AuthenticationExpiredReconnect},
{"ConnectedServer", test_ConnectedServer},
{"MultipleClose", test_MultipleClose},
{"SimplePublish", test_SimplePublish},
{"SimplePublishNoData", test_SimplePublishNoData},
{"PublishMsg", test_PublishMsg},
{"InvalidSubsArgs", test_InvalidSubsArgs},
{"AsyncSubscribe", test_AsyncSubscribe},
{"AsyncSubscribeTimeout", test_AsyncSubscribeTimeout},
{"SyncSubscribe", test_SyncSubscribe},
{"PubSubWithReply", test_PubSubWithReply},
{"NoResponders", test_NoResponders},
{"Flush", test_Flush},
{"ConnCloseDoesFlush", test_ConnCloseDoesFlush},
{"QueueSubscriber", test_QueueSubscriber},
{"ReplyArg", test_ReplyArg},
{"SyncReplyArg", test_SyncReplyArg},
{"Unsubscribe", test_Unsubscribe},
{"DoubleUnsubscribe", test_DoubleUnsubscribe},
{"SubRemovedWhileProcessingMsg", test_SubRemovedWhileProcessingMsg},
{"RequestTimeout", test_RequestTimeout},
{"Request", test_Request},
{"RequestNoBody", test_RequestNoBody},
{"RequestMuxWithMappedSubject", test_RequestMuxWithMappedSubject},
{"OldRequest", test_OldRequest},
{"SimultaneousRequests", test_SimultaneousRequest},
{"RequestClose", test_RequestClose},
{"CustomInbox", test_CustomInbox},
{"MessagePadding", test_MessageBufferPadding},
{"FlushInCb", test_FlushInCb},
{"ReleaseFlush", test_ReleaseFlush},
{"FlushErrOnDisconnect", test_FlushErrOnDisconnect},
{"Inbox", test_Inbox},
{"Stats", test_Stats},
{"BadSubject", test_BadSubject},
{"SubBadSubjectAndQueueNames", test_SubBadSubjectAndQueueName},
{"ClientAsyncAutoUnsub", test_ClientAsyncAutoUnsub},
{"ClientSyncAutoUnsub", test_ClientSyncAutoUnsub},
{"ClientAutoUnsubAndReconnect", test_ClientAutoUnsubAndReconnect},
{"AutoUnsubNoUnsubOnDestroy", test_AutoUnsubNoUnsubOnDestroy},
{"NextMsgOnClosedSub", test_NextMsgOnClosedSub},
{"CloseSubRelease", test_CloseSubRelease},
{"IsValidSubscriber", test_IsValidSubscriber},
{"SlowSubscriber", test_SlowSubscriber},
{"SlowAsyncSubscriber", test_SlowAsyncSubscriber},
{"SlowConsumerCb", test_SlowConsumerCB},
{"PendingLimitsDeliveredAndDropped",test_PendingLimitsDeliveredAndDropped},
{"PendingLimitsWithSyncSub", test_PendingLimitsWithSyncSub},
{"AsyncSubscriptionPending", test_AsyncSubscriptionPending},
{"AsyncSubscriptionPendingDrain", test_AsyncSubscriptionPendingDrain},
{"SyncSubscriptionPending", test_SyncSubscriptionPending},
{"SyncSubscriptionPendingDrain", test_SyncSubscriptionPendingDrain},
{"AsyncErrHandler", test_AsyncErrHandler},
{"AsyncSubscriberStarvation", test_AsyncSubscriberStarvation},
{"AsyncSubscriberOnClose", test_AsyncSubscriberOnClose},
{"NextMsgCallOnAsyncSub", test_NextMsgCallOnAsyncSub},
{"SubOnComplete", test_SubOnComplete},
{"GetLastError", test_GetLastError},
{"StaleConnection", test_StaleConnection},
{"ServerErrorClosesConnection", test_ServerErrorClosesConnection},
{"NoEcho", test_NoEcho},
{"NoEchoOldServer", test_NoEchoOldServer},
{"DrainSub", test_DrainSub},
{"DrainSubStops", test_DrainSubStops},
{"DrainSubRaceOnAutoUnsub", test_DrainSubRaceOnAutoUnsub},
{"DrainSubNotResentOnReconnect", test_DrainSubNotResentOnReconnect},
{"DrainConn", test_DrainConn},
{"NoDoubleCloseCbOnDrain", test_NoDoubleConnClosedOnDrain},
{"GetClientID", test_GetClientID},
{"GetClientIP", test_GetClientIP},
{"GetRTT", test_GetRTT},
{"GetLocalIPAndPort", test_GetLocalIPAndPort},
{"UserCredsCallbacks", test_UserCredsCallbacks},
{"UserCredsFromFiles", test_UserCredsFromFiles},
{"UserCredsFromMemory", test_UserCredsFromMemory},
{"NKey", test_NKey},
{"NKeyFromSeed", test_NKeyFromSeed},
{"ConnSign", test_ConnSign},
{"WriteDeadline", test_WriteDeadline},
{"HeadersNotSupported", test_HeadersNotSupported},
{"HeadersBasic", test_HeadersBasic},
{"MsgsFilter", test_natsMsgsFilter},
{"EventLoop", test_EventLoop},
{"EventLoopRetryOnFailedConnect", test_EventLoopRetryOnFailedConnect},
{"EventLoopTLS", test_EventLoopTLS},
{"SSLBasic", test_SSLBasic},
{"SSLVerify", test_SSLVerify},
{"SSLCAFromMemory", test_SSLLoadCAFromMemory},
{"SSLCertAndKeyFromMemory", test_SSLCertAndKeyFromMemory},
{"SSLVerifyHostname", test_SSLVerifyHostname},
{"SSLSkipServerVerification", test_SSLSkipServerVerification},
{"SSLCiphers", test_SSLCiphers},
{"SSLMultithreads", test_SSLMultithreads},
{"SSLConnectVerboseOption", test_SSLConnectVerboseOption},
{"SSLSocketLeakEventLoop", test_SSLSocketLeakWithEventLoop},
{"SSLReconnectWithAuthError", test_SSLReconnectWithAuthError},
// Clusters Tests
{"ServersOption", test_ServersOption},
{"AuthServers", test_AuthServers},
{"AuthFailToReconnect", test_AuthFailToReconnect},
{"ReconnectWithTokenHandler", test_ReconnectWithTokenHandler},
{"BasicClusterReconnect", test_BasicClusterReconnect},
{"HotSpotReconnect", test_HotSpotReconnect},
{"ProperReconnectDelay", test_ProperReconnectDelay},
{"ProperFalloutAfterMaxAttempts", test_ProperFalloutAfterMaxAttempts},
{"StopReconnectAfterTwoAuthErr", test_StopReconnectAfterTwoAuthErr},
{"TimeoutOnNoServer", test_TimeoutOnNoServer},
{"PingReconnect", test_PingReconnect},
{"GetServers", test_GetServers},
{"GetDiscoveredServers", test_GetDiscoveredServers},
{"DiscoveredServersCb", test_DiscoveredServersCb},
{"IgnoreDiscoveredServers", test_IgnoreDiscoveredServers},
{"INFOAfterFirstPONGisProcessedOK", test_ReceiveINFORightAfterFirstPONG},
{"ServerPoolUpdatedOnClusterUpdate",test_ServerPoolUpdatedOnClusterUpdate},
{"ReconnectJitter", test_ReconnectJitter},
{"CustomReconnectDelay", test_CustomReconnectDelay},
{"LameDuckMode", test_LameDuckMode},
{"ReconnectImplicitUserInfo", test_ReconnectImplicitUserInfo},
{"JetStreamUnmarshalAccInfo", test_JetStreamUnmarshalAccountInfo},
{"JetStreamUnmarshalStreamState", test_JetStreamUnmarshalStreamState},
{"JetStreamUnmarshalStreamCfg", test_JetStreamUnmarshalStreamConfig},
{"JetStreamUnmarshalStreamInfo", test_JetStreamUnmarshalStreamInfo},
{"JetStreamMarshalStreamCfg", test_JetStreamMarshalStreamConfig},
{"JetStreamUnmarshalConsumerInfo", test_JetStreamUnmarshalConsumerInfo},
{"JetStreamContext", test_JetStreamContext},
{"JetStreamDomain", test_JetStreamContextDomain},
{"JetStreamMgtStreams", test_JetStreamMgtStreams},
{"JetStreamMgtConsumers", test_JetStreamMgtConsumers},
{"JetStreamPublish", test_JetStreamPublish},
{"JetStreamPublishAsync", test_JetStreamPublishAsync},
{"JetStreamPublishAckHandler", test_JetStreamPublishAckHandler},
{"JetStreamSubscribe", test_JetStreamSubscribe},
{"JetStreamSubscribeSync", test_JetStreamSubscribeSync},
{"JetStreamSubscribeConfigCheck", test_JetStreamSubscribeConfigCheck},
{"JetStreamSubscribeIdleHeartbeat", test_JetStreamSubscribeIdleHearbeat},
{"JetStreamSubscribeFlowControl", test_JetStreamSubscribeFlowControl},
{"JetStreamSubscribePull", test_JetStreamSubscribePull},
{"JetStreamSubscribeHeadersOnly", test_JetStreamSubscribeHeadersOnly},
{"JetStreamOrderedCons", test_JetStreamOrderedConsumer},
{"JetStreamOrderedConsWithErrors", test_JetStreamOrderedConsumerWithErrors},
{"JetStreamOrderedConsAutoUnsub", test_JetStreamOrderedConsumerWithAutoUnsub},
{"JetStreamOrderedConsSrvRestart", test_JetStreamOrderedConsSrvRestart},
{"JetStreamSubscribeWithFWC", test_JetStreamSubscribeWithFWC},
{"JetStreamStreamsSealAndRollup", test_JetStreamStreamsSealAndRollup},
{"JetStreamGetMsgAndLastMsg", test_JetStreamGetMsgAndLastMsg},
{"JetStreamConvertDirectMsg", test_JetStreamConvertDirectMsg},
{"JetStreamDirectGetMsg", test_JetStreamDirectGetMsg},
{"JetStreamNakWithDelay", test_JetStreamNakWithDelay},
{"JetStreamBackOffRedeliveries", test_JetStreamBackOffRedeliveries},
{"JetStreamInfoWithSubjects", test_JetStreamInfoWithSubjects},
{"JetStreamInfoAlternates", test_JetStreamInfoAlternates},
{"KeyValueManager", test_KeyValueManager},
{"KeyValueBasics", test_KeyValueBasics},
{"KeyValueWatch", test_KeyValueWatch},
{"KeyValueHistory", test_KeyValueHistory},
{"KeyValueKeys", test_KeyValueKeys},
{"KeyValueDeleteVsPurge", test_KeyValueDeleteVsPurge},
{"KeyValueDeleteTombstones", test_KeyValueDeleteTombstones},
{"KeyValueDeleteMarkerThreshold", test_KeyValuePurgeDeletesMarkerThreshold},
{"KeyValueCrossAccount", test_KeyValueCrossAccount},
{"KeyValueDiscardOldToNew", test_KeyValueDiscardOldToNew},
{"KeyValueRePublish", test_KeyValueRePublish},
{"KeyValueMirrorDirectGet", test_KeyValueMirrorDirectGet},
{"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains},
#if defined(NATS_HAS_STREAMING)
{"StanPBufAllocator", test_StanPBufAllocator},
{"StanConnOptions", test_StanConnOptions},
{"StanSubOptions", test_StanSubOptions},
{"StanMsg", test_StanMsg},
{"StanServerNotReachable", test_StanServerNotReachable},
{"StanBasicConnect", test_StanBasicConnect},
{"StanConnectError", test_StanConnectError},
{"StanBasicPublish", test_StanBasicPublish},
{"StanBasicPublishAsync", test_StanBasicPublishAsync},
{"StanPublishTimeout", test_StanPublishTimeout},
{"StanPublishMaxAcksInflight", test_StanPublishMaxAcksInflight},
{"StanBasicSubscription", test_StanBasicSubscription},
{"StanSubscriptionCloseAndUnsub", test_StanSubscriptionCloseAndUnsubscribe},
{"StanDurableSubscription", test_StanDurableSubscription},
{"StanBasicQueueSubscription", test_StanBasicQueueSubscription},
{"StanDurableQueueSubscription", test_StanDurableQueueSubscription},
{"StanCheckReceivedMsg", test_StanCheckReceivedvMsg},
{"StanSubscriptionAckMsg", test_StanSubscriptionAckMsg},
{"StanPings", test_StanPings},
{"StanPingsNoResponder", test_StanPingsNoResponder},
{"StanConnectionLostHandlerNotSet", test_StanConnectionLostHandlerNotSet},
{"StanPingsUnblockPublishCalls", test_StanPingsUnblockPubCalls},
{"StanGetNATSConnection", test_StanGetNATSConnection},
{"StanNoRetryOnFailedConnect", test_StanNoRetryOnFailedConnect},
{"StanInternalSubsNotPooled", test_StanInternalSubsNotPooled},
{"StanSubOnComplete", test_StanSubOnComplete},
{"StanSubTimeout", test_StanSubTimeout},
#endif
};
static int maxTests = (int) (sizeof(allTests)/sizeof(testInfo));
static void
generateList(void)
{
FILE *list = fopen("list.txt", "w");
int i;
if (list == NULL)
{
printf("@@ Unable to create file 'list.txt': %d\n", errno);
return;
}
printf("Number of tests: %d\n", maxTests);
for (i=0; i<maxTests; i++)
fprintf(list, "%s\n", allTests[i].name);
fflush(list);
fclose(list);
}
int main(int argc, char **argv)
{
const char *envStr;
int testStart = 0;
int testEnd = 0;
int i;
if (argc == 1)
{
generateList();
return 0;
}
if (argc == 3)
{
testStart = atoi(argv[1]);
testEnd = atoi(argv[2]);
}
if ((argc != 3)
|| (testStart < 0) || (testStart >= maxTests)
|| (testEnd < 0) || (testEnd >= maxTests)
|| (testStart > testEnd))
{
printf("@@ Usage: %s [start] [end] (0 .. %d)\n", argv[0], (maxTests - 1));
return 1;
}
envStr = getenv("NATS_TEST_TRAVIS");
if ((envStr != NULL) && (envStr[0] != '\0'))
runOnTravis = true;
envStr = getenv("NATS_TEST_VALGRIND");
if ((envStr != NULL) && (envStr[0] != '\0'))
{
valgrind = true;
printf("Test running in VALGRIND mode.\n");
}
envStr = getenv("NATS_TEST_KEEP_SERVER_OUTPUT");
if ((envStr != NULL) && (envStr[0] != '\0'))
{
keepServerOutput = true;
printf("Test prints server's output.\n");
}
envStr = getenv("NATS_TEST_SERVER_EXE");
if ((envStr != NULL) && (envStr[0] != '\0'))
{
natsServerExe = envStr;
printf("Test using server executable: %s\n", natsServerExe);
}
envStr = getenv("NATS_TEST_STREAMING_SERVER_EXE");
if ((envStr != NULL) && (envStr[0] != '\0'))
{
natsStreamingServerExe = envStr;
printf("Test using server executable: %s\n", natsStreamingServerExe);
}
envStr = getenv("NATS_TEST_SERVER_VERSION");
if ((envStr != NULL) && (envStr[0] != '\0'))
{
serverVersion = envStr;
printf("Test server version: %s\n", serverVersion);
}
if (nats_Open(-1) != NATS_OK)
{
printf("@@ Unable to run tests: unable to initialize the library!\n");
return 1;
}
if (natsMutex_Create(&slMu) != NATS_OK)
{
printf("@@ Unable to create lock for servers map\n");
return 1;
}
if (natsHash_Create(&slMap, 4) != NATS_OK)
{
natsMutex_Destroy(slMu);
printf("@@ Unable to create lock for servers map\n");
return 1;
}
// Execute tests
for (i=testStart; (i<=testEnd) && !failed; i++)
{
#ifdef _WIN32
printf("\n== %s ==\n", allTests[i].name);
#else
printf("\033[0;34m\n== %s ==\n\033[0;0m", allTests[i].name);
#endif
(*(allTests[i].func))();
}
#ifdef _WIN32
if (logHandle != NULL)
{
CloseHandle(logHandle);
DeleteFile(LOGFILE_NAME);
}
#else
remove(LOGFILE_NAME);
#endif
// Shutdown servers that are still running likely due to failed test
{
natsHash *pids = NULL;
natsHashIter iter;
int64_t key;
if (natsHash_Create(&pids, 16) == NATS_OK)
{
natsMutex_Lock(slMu);
natsHashIter_Init(&iter, slMap);
while (natsHashIter_Next(&iter, &key, NULL))
{
natsHash_Set(pids, key, NULL, NULL);
natsHashIter_RemoveCurrent(&iter);
}
natsHashIter_Done(&iter);
natsHash_Destroy(slMap);
slMap = NULL;
natsMutex_Unlock(slMu);
natsHashIter_Init(&iter, pids);
while (natsHashIter_Next(&iter, &key, NULL))
_stopServer((natsPid) key);
natsHash_Destroy(pids);
}
else
{
natsHash_Destroy(slMap);
}
natsMutex_Destroy(slMu);
}
// Makes valgrind happy
nats_CloseAndWait((failed ? 1 : 2000));
if (failed)
{
printf("*** TEST FAILED ***\n");
return 1;
}
printf("ALL PASSED\n");
return 0;
}