// 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 #include #include #ifdef _WIN32 #else #include #include #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) && (im); 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) && (iinboxes, 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 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; ifields != 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; ifields != 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; ips))); 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; isrvPool->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; isrvPool->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; isrvPool->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\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) && (kps->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; isrvPool->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, ¤tPool, ¤tPoolSize); 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; ips))); 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; imu); testCond(nc->respPoolSize == 1); natsMutex_Unlock(nc->mu); test("Pool max size: "); for (i=0; imu); 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>>> 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) && (imsgList.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", "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) && (im); 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) && (icount++; 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; ireconnects == 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 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; im); 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; idone = 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= 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) && (id_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; im); 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"); 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"); 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; iwsz; 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) && (isubject, (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; iState.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; iName, 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; ijsi->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; iState.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; imu); 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) || (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; }