diff --git a/.github/workflows/branch-snapshot.yml b/.github/workflows/branch-snapshot.yml index 90e24b384..e45b383b6 100644 --- a/.github/workflows/branch-snapshot.yml +++ b/.github/workflows/branch-snapshot.yml @@ -19,7 +19,7 @@ jobs: - name: Setup JDK uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '21' distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 diff --git a/.github/workflows/build-main.yml b/.github/workflows/build-main.yml index 80f6b542a..e8fca59f0 100644 --- a/.github/workflows/build-main.yml +++ b/.github/workflows/build-main.yml @@ -20,7 +20,7 @@ jobs: - name: Setup JDK uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '21' distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 94dc2bbb1..0afe2632f 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -16,9 +16,9 @@ jobs: SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} steps: - name: Setup JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: - java-version: '8' + java-version: '21' distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 @@ -40,7 +40,7 @@ jobs: - name: Check out code uses: actions/checkout@v4 - name: Build and Test - run: chmod +x gradlew && ./gradlew clean test jacocoTestReport + run: chmod +x gradlew && ./gradlew clean test jacocoTestReport --refresh-dependencies - name: Verify Javadoc run: ./gradlew javadoc - name: Send coverage to Coveralls diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 8acc3e576..6f25ef039 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -20,7 +20,7 @@ jobs: - name: Setup JDK uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '21' distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 diff --git a/build.gradle b/build.gradle index 39147bf22..ea9737d01 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,8 @@ plugins { id("java-library") id("maven-publish") id("jacoco") - id("biz.aQute.bnd.builder") version "5.1.2" +// id("biz.aQute.bnd.builder") version "5.1.2" + id("biz.aQute.bnd.builder") version "7.1.0" id("org.gradle.test-retry") version "1.6.4" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("signing") @@ -18,11 +19,26 @@ def isRelease = System.getenv("BUILD_EVENT") == "release" def brn = System.getenv("BRANCH_REF_NAME") def snap = brn == null || brn.equals("") ? "-SNAPSHOT" : "." + brn + "-SNAPSHOT" +def tc = System.getenv("TARGET_COMPATIBILITY"); +def targetCompat = tc == "21" ? JavaVersion.VERSION_21 : (tc == "17" ? JavaVersion.VERSION_17 : JavaVersion.VERSION_1_8) +def jarEnd = tc == "21" ? "-jdk21" : (tc == "17" ? "-jdk17" : "") +def jarAndArtifactName = "jnats" + jarEnd + version = isRelease ? jarVersion : jarVersion + snap // version is the variable the build actually uses. +def os = System.getProperty("os.name") +def isNotWindows = !os.contains("Windows") +def javaVersion = System.getProperty("java.version"); +def compilerIsJava8 = javaVersion.startsWith("1.8") + +System.out.println("OS: " + os) +System.out.println("Java: " + javaVersion) +System.out.println("Target Compatibility: " + targetCompat) +System.out.println("Output: " + group + ":" + jarAndArtifactName + ":" + version) + java { sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = targetCompat } repositories { @@ -37,8 +53,13 @@ dependencies { api 'org.jspecify:jspecify:1.0.0' testImplementation 'org.junit.jupiter:junit-jupiter:5.14.1' - testImplementation 'io.nats:jnats-server-runner:3.0.1' - testImplementation 'nl.jqno.equalsverifier:equalsverifier:3.12.3' + testImplementation 'io.nats:jnats-server-runner:3.1.0' + if (compilerIsJava8) { + testImplementation 'nl.jqno.equalsverifier:equalsverifier:3.12.3' + } + else { + testImplementation 'nl.jqno.equalsverifier:equalsverifier:4.2.2' + } testImplementation 'org.junit.platform:junit-platform-launcher:1.14.3' } @@ -84,6 +105,7 @@ jar { "Bundle-DocURL": "https://github.com/nats-io/nats.java" ) } + archiveBaseName = jarAndArtifactName } test { @@ -99,7 +121,13 @@ test { maxFailures = 4 maxRetries = 4 } - maxParallelForks = Runtime.runtime.availableProcessors() + int mpf = Math.max(1, (int)(Runtime.runtime.availableProcessors() * 3 / 4)) + if (compilerIsJava8 || isNotWindows) { + maxParallelForks = Math.min(6, mpf) + } + else { + maxParallelForks = Math.min(3, mpf); + } systemProperty 'junit.jupiter.execution.timeout.default', '3m' } @@ -209,10 +237,10 @@ publishing { artifact javadocJar artifact testsJar pom { - name = "jnats" + name = jarAndArtifactName packaging = "jar" groupId = group - artifactId = "jnats" + artifactId = jarAndArtifactName description = 'Client library for working with the NATS messaging system.' url = 'https://github.com/nats-io/nats.java' licenses { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..2cfe86acd --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,12 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +commons-math3 = "3.6.1" +guava = "33.4.5-jre" +junit-jupiter = "5.12.1" + +[libraries] +commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } diff --git a/settings.gradle b/settings.gradle index ea03d6ce4..0eaa8335e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,13 @@ pluginManagement { repositories { + gradlePluginPortal() mavenCentral() - maven { url "https://oss.sonatype.org/content/repositories/releases/" } - maven { url "https://plugins.gradle.org/m2/" } + maven { url="https://repo1.maven.org/maven2/" } + maven { url="https://central.sonatype.com/repository/maven-snapshots/" } + maven { url="https://plugins.gradle.org/m2/" } + } + plugins { + id("biz.aQute.bnd.builder") version "7.1.0" } } rootProject.name = 'java-nats' diff --git a/src/test/java/io/nats/client/AuthTests.java b/src/test/java/io/nats/client/AuthTests.java index ecf60bbb2..d9365f42a 100644 --- a/src/test/java/io/nats/client/AuthTests.java +++ b/src/test/java/io/nats/client/AuthTests.java @@ -13,16 +13,18 @@ package io.nats.client; +import io.nats.NatsRunnerUtils; +import io.nats.NatsServerRunner; import io.nats.client.Connection.Status; import io.nats.client.ConnectionListener.Events; -import io.nats.client.impl.ListenerForTesting; import io.nats.client.support.JwtUtils; +import io.nats.client.support.Listener; import io.nats.client.support.ssl.SslTestingHelper; import io.nats.client.utils.ResourceUtils; import io.nats.client.utils.TestBase; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.parallel.Isolated; import javax.net.ssl.SSLContext; import java.io.BufferedWriter; @@ -32,28 +34,62 @@ import java.nio.file.Files; import java.time.Duration; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static io.nats.client.support.Listener.LONG_VALIDATE_TIMEOUT; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.NOOP_EL; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ResourceUtils.jwtResource; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.condition.OS.WINDOWS; +@Isolated public class AuthTests extends TestBase { + private String userPassInUrl(String user, String pass, int port) { + return "nats://" + user + ":" + pass + "@" + NatsRunnerUtils.getDefaultLocalhostHost().host + ":" + port; + } + + private String tokenInUrl(String userOrToken, int port) { + return "nats://" + userOrToken + "@" + NatsRunnerUtils.getDefaultLocalhostHost().host + ":" + port; + } + + public static AuthHandler getUserCredsAuthHander() { + return Nats.credentials(jwtResource("user.creds")); + } + @Test public void testUserPass() throws Exception { - String[] customArgs = { "--user", "stephen", "--pass", "password" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .userInfo("stephen".toCharArray(), "password".toCharArray()).build(); - assertCanConnect(options); + String[] customArgs = { "--user", "uuu", "--pass", "ppp" }; + try (NatsTestServer ts = new NatsTestServer(customArgs)) { + // u/p in url + Options upInUrlOpts = optionsBuilder(userPassInUrl("uuu", "ppp", ts.getPort())).maxReconnects(0).build(); + assertCanConnect(upInUrlOpts); + + // u/p in options + Options upInOptionsOpts = optionsBuilder(ts).maxReconnects(0) + .userInfo("uuu".toCharArray(), "ppp".toCharArray()).build(); + assertCanConnect(upInOptionsOpts); + + Options badUserOpts = optionsBuilder(ts).maxReconnects(0).connectionTimeout(10000) + .userInfo("zzz".toCharArray(), "ppp".toCharArray()).build(); + assertThrows(AuthenticationException.class, () -> Nats.connect(badUserOpts)); + + Options badPassOpts = optionsBuilder(ts).maxReconnects(0).connectionTimeout(10000) + .userInfo("uuu".toCharArray(), "zzz".toCharArray()).build(); + assertThrows(AuthenticationException.class, () -> Nats.connect(badPassOpts)); + + Options missingUserPassOpts = optionsBuilder(ts).maxReconnects(0).connectionTimeout(10000).build(); + assertThrows(AuthenticationException.class, () -> Nats.connect(missingUserPassOpts)); } } @Test public void testEncodedPassword() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/encoded_pass.conf", false)) { + runInConfiguredServer("encoded_pass.conf", ts -> { int port = ts.getPort(); assertEncoded("space%20space", port); assertEncoded("colon%3Acolon", port); @@ -80,12 +116,12 @@ public void testEncodedPassword() throws Exception { // a plus sign in a user or pass is a plus sign, not a space assertThrows(AuthenticationException.class, () -> assertEncoded("space+space", port)); - } + }); } private void assertEncoded(String encoded, int port) throws IOException, InterruptedException { - String url = "nats://u" + encoded + ":p" + encoded + "@localhost:" + port; - Options options = new Options.Builder().server(url).build(); + String url = userPassInUrl("u" + encoded, "p" + encoded, port); + Options options = optionsBuilder(url).build(); Connection c = Nats.connect(options); c.getServerInfo(); c.close(); @@ -108,9 +144,10 @@ private static void assertNeedsJsonEncoding(String test) throws Exception { String user = "u" + test + "u"; String pass = "p" + test + "p"; String[] customArgs = {"--user", "\"" + user + "\"", "--pass", "\"" + pass + "\""}; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { + try (NatsTestServer ts = new NatsTestServer( + NatsServerRunner.builder().customArgs(customArgs))) { // See config file for user/pass - Options options = new Options.Builder().server("nats://localhost:" + ts.getPort()) + Options options = optionsBuilder(ts) .userInfo(user, pass) .maxReconnects(0).build(); assertCanConnect(options); @@ -119,158 +156,154 @@ private static void assertNeedsJsonEncoding(String test) throws Exception { @Test public void testUserPassOnReconnect() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); Connection nc; Subscription sub; - String[] customArgs = { "--user", "stephen", "--pass", "password" }; + String[] customArgs = { "--user", "uuu", "--pass", "ppp" }; int port; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { + try (NatsTestServer ts = new NatsTestServer(customArgs)) { port = ts.getPort(); // See config file for user/pass - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(-1) - .userInfo("stephen".toCharArray(), "password".toCharArray()).connectionListener(listener).build(); - nc = standardConnection(options); + Options options = optionsBuilder(ts).maxReconnects(-1) + .userInfo("uuu".toCharArray(), "ppp".toCharArray()).connectionListener(listener).build(); + nc = managedConnect(options); sub = nc.subscribe("test"); nc.publish("test", null); flushConnection(nc, MEDIUM_FLUSH_TIMEOUT_MS); Message msg = sub.nextMessage(Duration.ofSeconds(5)); assertNotNull(msg); - listener.prepForStatusChange(Events.DISCONNECTED); + + listener.queueConnectionEvent(Events.DISCONNECTED); } TestBase.flushConnection(nc); + listener.validate(); - listener.waitForStatusChange(5, TimeUnit.SECONDS); - assertTrue( - Connection.Status.RECONNECTING == nc.getStatus() || Connection.Status.DISCONNECTED == nc.getStatus(), "Reconnecting status"); - listener.prepForStatusChange(Events.RESUBSCRIBED); + listener.queueConnectionEvent(Events.RESUBSCRIBED); - try (NatsTestServer ignored = new NatsTestServer(customArgs, port, false)) { - listenerConnectionWait(nc, listener); + try (NatsTestServer ignored = new NatsTestServer(customArgs, port)) { + confirmConnected(nc); // wait for reconnect + listener.validate(); nc.publish("test", null); - flushConnection(nc, MEDIUM_FLUSH_TIMEOUT_MS); +// flushConnection(nc, MEDIUM_FLUSH_TIMEOUT_MS); + Message msg = sub.nextMessage(Duration.ofSeconds(5)); assertNotNull(msg); - standardCloseConnection(nc); + closeAndConfirm(nc); } } @Test public void testUserBCryptPass() throws Exception { /* - * go run mkpasswd.go -p password: password bcrypt hash: - * $2a$11$1oJy/wZYNTxr9jNwMNwS3eUGhBpHT3On8CL9o7ey89mpgo88VG6ba + * use a bcrypt hash generator on the cleartext pass + * ppp -> $2a$12$UjzncyjtsE6rJG4LSGk.JOweXV3P2umZ38gHuj4OMY0X/nQudiDgG */ - String[] customArgs = { "--user", "ginger", "--pass", - "$2a$11$1oJy/wZYNTxr9jNwMNwS3eUGhBpHT3On8CL9o7ey89mpgo88VG6ba" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { + String[] customArgs = { "--user", "uuu", "--pass", + "$2a$12$UjzncyjtsE6rJG4LSGk.JOweXV3P2umZ38gHuj4OMY0X/nQudiDgG" }; + try (NatsTestServer ts = new NatsTestServer(customArgs)) { // See config file for user/pass - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .userInfo("ginger".toCharArray(), "password".toCharArray()).build(); - assertCanConnect(options); - } - } - - @Test - public void testUserPassInURL() throws Exception { - String[] customArgs = { "--user", "stephen", "--pass", "password" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder().server("nats://stephen:password@localhost:" + ts.getPort()) - .maxReconnects(0).build(); + Options options = optionsBuilder(ts).maxReconnects(0) + .userInfo("uuu".toCharArray(), "ppp".toCharArray()).build(); assertCanConnect(options); } } @Test public void testUserPassInURLOnReconnect() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port; - Connection nc = null; - Subscription sub = null; - String[] customArgs = { "--user", "stephen", "--pass", "password" }; + Connection nc; + Subscription sub; + String[] customArgs = { "--user", "uuu", "--pass", "ppp" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { + try (NatsTestServer ts = new NatsTestServer(customArgs)) { port = ts.getPort(); // See config file for user/pass - Options options = new Options.Builder().server("nats://stephen:password@localhost:" + ts.getPort()) - .maxReconnects(-1).connectionListener(listener).build(); - nc = standardConnection(options); + Options options = new Options.Builder().server(userPassInUrl("uuu", "ppp", ts.getPort())) + .maxReconnects(-1).connectionListener(listener).errorListener(NOOP_EL).build(); + nc = managedConnect(options); sub = nc.subscribe("test"); nc.publish("test", null); flushConnection(nc, MEDIUM_FLUSH_TIMEOUT_MS); Message msg = sub.nextMessage(Duration.ofSeconds(5)); assertNotNull(msg); - listener.prepForStatusChange(Events.DISCONNECTED); + listener.queueConnectionEvent(Events.DISCONNECTED); } TestBase.flushConnection(nc); - listener.waitForStatusChange(5, TimeUnit.SECONDS); + listener.validate(); Status status = nc.getStatus(); assertTrue( Connection.Status.RECONNECTING == status || Connection.Status.DISCONNECTED == status, "Reconnecting status"); - listener.prepForStatusChange(Events.RESUBSCRIBED); + listener.queueConnectionEvent(Events.RESUBSCRIBED); - try (NatsTestServer ignored = new NatsTestServer(customArgs, port, false)) { - listenerConnectionWait(nc, listener); + try (NatsTestServer ignored = new NatsTestServer(customArgs, port)) { + confirmConnected(nc); // wait for reconnect nc.publish("test", null); flushConnection(nc, MEDIUM_FLUSH_TIMEOUT_MS); Message msg = sub.nextMessage(Duration.ofSeconds(5)); assertNotNull(msg); - standardCloseConnection(nc); + closeAndConfirm(nc); } } @Test public void testUserPassInURLClusteredWithDifferentUser() throws Exception { - String[] customArgs1 = { "--user", "stephen", "--pass", "password" }; - String[] customArgs2 = { "--user", "alberto", "--pass", "casadecampo" }; - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts1 = new NatsTestServer(customArgs1, false); - NatsTestServer ts2 = new NatsTestServer(customArgs2, false)) { - // See config file for user/pass - Options options = new Options.Builder().server("nats://stephen:password@localhost:" + ts1.getPort()) - .server("nats://alberto:casadecampo@localhost:" + ts2.getPort()).maxReconnects(4).noRandomize() - .connectionListener(listener).pingInterval(Duration.ofMillis(100)).build(); - Connection nc = standardConnection(options); - assertEquals(nc.getConnectedUrl(), "nats://stephen:password@localhost:" + ts1.getPort()); - - listener.prepForStatusChange(Events.RESUBSCRIBED); - ts1.close(); - listenerConnectionWait(nc, listener); - assertEquals(nc.getConnectedUrl(), "nats://alberto:casadecampo@localhost:" + ts2.getPort()); - standardCloseConnection(nc); + String[] customArgs1 = { "--user", "uuu", "--pass", "ppp" }; + String[] customArgs2 = { "--user", "uuu2", "--pass", "ppp2" }; + Listener listener = new Listener(); + try (NatsTestServer ts1 = new NatsTestServer(customArgs1); + NatsTestServer ts2 = new NatsTestServer(customArgs2)) { + String url1 = userPassInUrl("uuu", "ppp", ts1.getPort()); + String url2 = userPassInUrl("uuu2", "ppp2", ts2.getPort()); + Options options = optionsBuilder(url1, url2) + .maxReconnects(4) + .noRandomize() + .connectionListener(listener) + .pingInterval(Duration.ofMillis(100)) + .build(); + + try (Connection nc = managedConnect(options)) { + assertEquals(nc.getConnectedUrl(), url1); + listener.queueConnectionEvent(Events.RESUBSCRIBED); + ts1.close(); + confirmConnected(nc); // wait for reconnect + assertEquals(nc.getConnectedUrl(), url2); + } } } @Test public void testUserPassInURLWithFallback() throws Exception { - String[] customArgs1 = { "--user", "stephen", "--pass", "password" }; - String[] customArgs2 = { "--user", "alberto", "--pass", "casadecampo" }; - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts1 = new NatsTestServer(customArgs1, false); - NatsTestServer ts2 = new NatsTestServer(customArgs2, false)) { - // See config file for user/pass - Options options = new Options.Builder().server("nats://stephen:password@localhost:" + ts1.getPort()) - .server("nats://localhost:" + ts2.getPort()).noRandomize() - .userInfo("alberto".toCharArray(), "casadecampo".toCharArray()).maxReconnects(4).noRandomize() - .connectionListener(listener).pingInterval(Duration.ofMillis(100)).build(); - Connection nc = standardConnection(options); - assertEquals(nc.getConnectedUrl(), "nats://stephen:password@localhost:" + ts1.getPort()); - - listener.prepForStatusChange(Events.RESUBSCRIBED); - ts1.close(); - listener.waitForStatusChange(10, TimeUnit.SECONDS); - assertConnected(nc); - assertEquals(nc.getConnectedUrl(), "nats://localhost:" + ts2.getPort()); - standardCloseConnection(nc); + String[] customArgs1 = { "--user", "uuu", "--pass", "ppp" }; + String[] customArgs2 = { "--user", "uuu2", "--pass", "ppp2" }; + Listener listener = new Listener(); + try (NatsTestServer ts1 = new NatsTestServer(customArgs1); + NatsTestServer ts2 = new NatsTestServer(customArgs2)) { + String url1 = userPassInUrl("uuu", "ppp", ts1.getPort()); + Options options = optionsBuilder(url1, ts2.getNatsLocalhostUri()) + .userInfo("uuu2".toCharArray(), "ppp2".toCharArray()) + .maxReconnects(4) + .noRandomize() + .connectionListener(listener) + .pingInterval(Duration.ofMillis(100)).build(); + try (Connection nc = managedConnect(options)) { + assertEquals(nc.getConnectedUrl(), url1); + + listener.queueConnectionEvent(Events.RESUBSCRIBED, LONG_VALIDATE_TIMEOUT); + ts1.close(); + listener.validate(); + assertConnected(nc); + assertEquals(ts2.getServerUri(), nc.getConnectedUrl()); + } } } @@ -278,23 +311,27 @@ public void testUserPassInURLWithFallback() throws Exception { public void testTokenInURLClusteredWithDifferentUser() throws Exception { String[] customArgs1 = { "--auth", "token_one" }; String[] customArgs2 = { "--auth", "token_two" }; - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts1 = new NatsTestServer(customArgs1, false); - NatsTestServer ts2 = new NatsTestServer(customArgs2, false)) { - // See config file for user/pass - Options options = new Options.Builder().server("nats://token_one@localhost:" + ts1.getPort()) - .server("nats://token_two@localhost:" + ts2.getPort()).maxReconnects(4).noRandomize() - .connectionListener(listener).pingInterval(Duration.ofMillis(100)).build(); - Connection nc = standardConnection(options); - assertEquals(nc.getConnectedUrl(), "nats://token_one@localhost:" + ts1.getPort()); - - listener.prepForStatusChange(Events.RESUBSCRIBED); - ts1.close(); - listener.waitForStatusChange(2, TimeUnit.SECONDS); - - standardConnectionWait(nc); - assertEquals(nc.getConnectedUrl(), "nats://token_two@localhost:" + ts2.getPort()); - standardCloseConnection(nc); + Listener listener = new Listener(); + try (NatsTestServer ts1 = new NatsTestServer(customArgs1); + NatsTestServer ts2 = new NatsTestServer(customArgs2)) { + String url1 = tokenInUrl("token_one", ts1.getPort()); + String url2 = tokenInUrl("token_two", ts2.getPort()); + Options options = optionsBuilder(url1, url2) + .maxReconnects(4) + .noRandomize() + .connectionListener(listener) + .pingInterval(Duration.ofMillis(100)) + .build(); + try (Connection nc = managedConnect(options)) { + assertEquals(nc.getConnectedUrl(), url1); + + listener.queueConnectionEvent(Events.RESUBSCRIBED); + ts1.close(); + listener.validate(); + + confirmConnected(nc); // wait for reconnect + assertEquals(nc.getConnectedUrl(), url2); + } } } @@ -302,101 +339,60 @@ public void testTokenInURLClusteredWithDifferentUser() throws Exception { public void testTokenInURLWithFallback() throws Exception { String[] customArgs1 = { "--auth", "token_one" }; String[] customArgs2 = { "--auth", "token_two" }; - ListenerForTesting listener = new ListenerForTesting(); - Connection nc = null; - try (NatsTestServer ts1 = new NatsTestServer(customArgs1, false); - NatsTestServer ts2 = new NatsTestServer(customArgs2, false)) { - // See config file for user/pass - Options options = new Options.Builder().server("nats://token_one@localhost:" + ts1.getPort()) - .server("nats://localhost:" + ts2.getPort()).token("token_two".toCharArray()).maxReconnects(4) - .noRandomize().connectionListener(listener).pingInterval(Duration.ofMillis(100)).build(); - nc = standardConnection(options); - assertEquals(nc.getConnectedUrl(), "nats://token_one@localhost:" + ts1.getPort()); - - listener.prepForStatusChange(Events.RESUBSCRIBED); - ts1.close(); - - listener.waitForStatusChange(STANDARD_CONNECTION_WAIT_MS, TimeUnit.MILLISECONDS); - listenerConnectionWait(nc, listener); - assertEquals(nc.getConnectedUrl(), "nats://localhost:" + ts2.getPort()); - standardCloseConnection(nc); + Listener listener = new Listener(); + try (NatsTestServer ts1 = new NatsTestServer(customArgs1); + NatsTestServer ts2 = new NatsTestServer(customArgs2)) { + String url1 = tokenInUrl("token_one", ts1.getPort()); + Options options = optionsBuilder(url1, ts2.getNatsLocalhostUri()) + .token("token_two".toCharArray()) + .maxReconnects(4) + .noRandomize() + .connectionListener(listener) + .pingInterval(Duration.ofMillis(100)) + .build(); + + try (Connection nc = managedConnect(options)) { + assertEquals(nc.getConnectedUrl(), url1); + + listener.queueConnectionEvent(Events.RESUBSCRIBED); + ts1.close(); + listener.validate(); + + confirmConnected(nc); // wait for reconnect + assertEquals(ts2.getServerUri(), nc.getConnectedUrl()); + } } } @Test public void testToken() throws Exception { - String[] customArgs = { "--auth", "derek" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0).token("derek".toCharArray()) - .build(); + String[] customArgs = { "--auth", "token" }; + try (NatsTestServer ts = new NatsTestServer(customArgs)) { + // token in options + Options options = optionsBuilder(ts) + .maxReconnects(0) + .token("token".toCharArray()) + .build(); assertCanConnect(options); - } - } - @Test - public void testTokenInURL() throws Exception { - String[] customArgs = { "--auth", "alberto" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder().server("nats://alberto@localhost:" + ts.getPort()).maxReconnects(0) - .build(); + // token in url + options = optionsBuilder(tokenInUrl("token", ts.getPort())) + .maxReconnects(0).build(); assertCanConnect(options); - } - } - - @Test - public void testBadUserBadPass() { - assertThrows(AuthenticationException.class, () -> { - String[] customArgs = { "--user", "stephen", "--pass", "password" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .userInfo("sam".toCharArray(), "notthepassword".toCharArray()).build(); - Nats.connect(options); // expected to fail - } - }); - } - - @Test - public void testMissingUserPass() { - assertThrows(AuthenticationException.class, () -> { - String[] customArgs = { "--user", "wally", "--pass", "password" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0).build(); - Nats.connect(options); // expected to fail - } - }); - } - @Test - public void testBadToken() { - assertThrows(AuthenticationException.class, () -> { - String[] customArgs = { "--auth", "colin" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder() - .server(ts.getURI()) - .maxReconnects(0) - .errorListener(new ErrorListener() {}) - .token("notthetoken".toCharArray()) - .build(); - Nats.connect(options); // expected to fail - } - }); - } + // incorrect token + Options incorrectToken = optionsBuilder(ts) + .maxReconnects(0) + .token("incorrectToken".toCharArray()) + .build(); + assertThrows(AuthenticationException.class, () -> Nats.connect(incorrectToken)); - @Test - public void testMissingToken() { - assertThrows(AuthenticationException.class, () -> { - String[] customArgs = { "--auth", "ivan" }; - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - // See config file for user/pass - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0).build(); - Nats.connect(options); // expected to fail - } - }); + // incorrect token + Options missingToken = optionsBuilder(ts) + .maxReconnects(0) + .build(); + assertThrows(AuthenticationException.class, () -> Nats.connect(missingToken)); + } } String createNKeyConfigFile(char[] nkey) throws Exception { @@ -426,94 +422,83 @@ String createNKeyConfigFile(char[] nkey) throws Exception { public void testNKeyAuth() throws Exception { NKey theKey = NKey.createUser(null); assertNotNull(theKey); + String configFilePath = createNKeyConfigFile(theKey.getPublicKey()); - String configFile = createNKeyConfigFile(theKey.getPublicKey()); - - try (NatsTestServer ts = new NatsTestServer(configFile, false)) { - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .authHandler(new AuthHandlerForTesting(theKey)).build(); - assertCanConnect(options); - } - } + NatsServerRunner.Builder b = NatsServerRunner.builder().configFilePath(configFilePath); + try (NatsTestServer ts = new NatsTestServer(b)) { - @Test - public void testStaticNKeyAuth() throws Exception { - NKey theKey = NKey.createUser(null); - assertNotNull(theKey); + Options authHandlerOptions = optionsBuilder(ts).maxReconnects(0) + .authHandler(new AuthHandlerForTesting(theKey)).build(); + assertCanConnect(authHandlerOptions); - String configFile = createNKeyConfigFile(theKey.getPublicKey()); + Options staticOptions = optionsBuilder(ts).maxReconnects(0) + .authHandler(Nats.staticCredentials(null, theKey.getSeed())).build(); + assertCanConnect(staticOptions); - try (NatsTestServer ts = new NatsTestServer(configFile, false)) { - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .authHandler(Nats.staticCredentials(null, theKey.getSeed())).build(); - assertCanConnect(options); - } + // direct through Nats.connect + Connection nc = Nats.connect(ts.getServerUri(), Nats.staticCredentials(null, theKey.getSeed())); + confirmConnectedThenClosed(nc); - //test Nats.connect method - try (NatsTestServer ts = new NatsTestServer(configFile, false)) { - Connection nc = Nats.connect(ts.getURI(), Nats.staticCredentials(null, theKey.getSeed())); - standardConnectionWait(nc); - standardCloseConnection(nc); + // fails with no nkey + Options noNkey = optionsBuilder(ts).maxReconnects(0) + .authHandler(new AuthHandlerForTesting(null)).build(); + assertThrows(IOException.class, () -> Nats.connect(noNkey)); } } @Test public void testJWTAuthWithCredsFile() throws Exception { // manual auth handler or credential path - try (NatsTestServer ts = new NatsTestServer("src/test/resources/operator.conf", false)) { - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds")) + runInSharedConfiguredServer("operator.conf", ts -> { + Options options = optionsBuilder(ts).maxReconnects(0) + .authHandler(getUserCredsAuthHander()) .build(); assertCanConnect(options); - options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .credentialPath("src/test/resources/jwt_nkey/user.creds") + options = optionsBuilder(ts).maxReconnects(0) + .credentialPath(jwtResource("user.creds")) .build(); assertCanConnect(options); - } - //test Nats.connect method - try (NatsTestServer ts = new NatsTestServer("src/test/resources/operator.conf", false)) { - Connection nc = Nats.connect(ts.getURI(), Nats.credentials("src/test/resources/jwt_nkey/user.creds")); - standardConnectionWait(nc); - standardCloseConnection(nc); - } + //test Nats.connect method + Connection nc = Nats.connect(ts.getServerUri(), getUserCredsAuthHander()); + confirmConnectedThenClosed(nc); + }); + } + @Test + public void testJWTAuthWithCredsFileAlso() throws Exception { //test Nats.connect method - try (NatsTestServer ts = new NatsTestServer("src/test/resources/operatorJnatsTest.conf", false)) { - Connection nc = Nats.connect(ts.getURI(), Nats.credentials("src/test/resources/jwt_nkey/userJnatsTest.creds")); - standardConnectionWait(nc); - standardCloseConnection(nc); - } + runInConfiguredServer("operatorJnatsTest.conf", ts -> { + Connection nc = Nats.connect(ts.getServerUri(), Nats.credentials(jwtResource("userJnatsTest.creds"))); + confirmConnectedThenClosed(nc); + }); } @Test public void testWsJWTAuthWithCredsFile() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/ws_operator.conf", false)) { - String uri = ts.getLocalhostUri("ws"); - Options options = new Options.Builder().server(uri).maxReconnects(0) - .authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds")).build(); + runInConfiguredServer("ws_operator.conf", ts -> { + String uri = ts.getLocalhostUri(WS); + // in options + Options options = optionsBuilder(uri).maxReconnects(0) + .authHandler(getUserCredsAuthHander()).build(); assertCanConnect(options); - } - //test Nats.connect method - try (NatsTestServer ts = new NatsTestServer("src/test/resources/ws_operator.conf", false)) { - String uri = ts.getLocalhostUri("ws"); - Connection nc = Nats.connect(uri, Nats.credentials("src/test/resources/jwt_nkey/user.creds")); - standardConnectionWait(nc); - standardCloseConnection(nc); - } + // directly Nats.connect + Connection nc = Nats.connect(uri, getUserCredsAuthHander()); + confirmConnectedThenClosed(nc); + }); } @Test public void testWssJWTAuthWithCredsFile() throws Exception { SSLContext ctx = SslTestingHelper.createTestSSLContext(); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss_operator.conf", false)) { + runInConfiguredServer("wss_operator.conf", ts -> { String uri = ts.getLocalhostUri("wss"); - Options options = new Options.Builder().server(uri).maxReconnects(0).sslContext(ctx) - .authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds")).build(); + Options options = optionsBuilder(uri).maxReconnects(0).sslContext(ctx) + .authHandler(getUserCredsAuthHander()).build(); assertCanConnect(options); - } + }); } @Test @@ -522,126 +507,99 @@ public void testStaticJWTAuth() throws Exception { String jwt = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiI3UE1GTkc0R1c1WkE1NEg3N09TUUZKNkJNQURaSUQ2NTRTVk1XMkRFQVZINVIyUVU0MkhBIiwiaWF0IjoxNTY1ODg5ODk4LCJpc3MiOiJBQUhWU0k1NVlQTkJRWjVQN0Y2NzZDRkFPNFBIQlREWUZRSUVHVEtMUVRJUEVZUEZEVEpOSEhPNCIsIm5hbWUiOiJkZW1vIiwic3ViIjoiVUMzT01MSlhUWVBZN0ZUTVVZNUNaNExHRVdRSTNZUzZKVFZXU0VGRURBMk9MTEpZSVlaVFo3WTMiLCJ0eXBlIjoidXNlciIsIm5hdHMiOnsicHViIjp7fSwic3ViIjp7fX19.ROSJ7D9ETt9c8ZVHxsM4_FU2dBRLh5cNfb56MxPQth74HAxxtGMl0nn-9VVmWjXgFQn4JiIbwrGfFDBRMzxsAA"; String nkey = "SUAFYHVVQVOIDOOQ4MTOCTLGNZCJ5PZ4HPV5WAPROGTEIOF672D3R7GBY4"; - try (NatsTestServer ts = new NatsTestServer("src/test/resources/operator.conf", false)) { - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .authHandler(Nats.staticCredentials(jwt.toCharArray(), nkey.toCharArray())).build(); + runInSharedConfiguredServer("operator.conf", ts -> { + Options options = optionsBuilder(ts).maxReconnects(0) + .authHandler(Nats.staticCredentials(jwt.toCharArray(), nkey.toCharArray())).build(); assertCanConnect(options); - } - } - - @Test - public void testBadAuthHandler() { - assertThrows(IOException.class, () -> { - NKey theKey = NKey.createUser(null); - assertNotNull(theKey); - - String configFile = createNKeyConfigFile(theKey.getPublicKey()); - - try (NatsTestServer ts = new NatsTestServer(configFile, false)) { - Options options = new Options.Builder().server(ts.getURI()).maxReconnects(0) - .authHandler(new AuthHandlerForTesting(null)). // No nkey - build(); - Connection nc = Nats.connect(options); - assertNotConnected(nc); - } }); } @Test public void testReconnectWithAuth() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - + Listener listener = new Listener(); // Connect should fail on ts2 - try (NatsTestServer ts = new NatsTestServer("src/test/resources/operator.conf", false); - NatsTestServer ts2 = new NatsTestServer("src/test/resources/operator.conf", false)) { - Options options = new Options.Builder().servers(new String[]{ts.getURI(), ts2.getURI()}) - .noRandomize().maxReconnects(-1).authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds")).build(); - Connection nc = standardConnection(options); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - - listener.prepForStatusChange(Events.RECONNECTED); - - ts.close(); - - // Reconnect will fail because ts has the same auth error - listener.waitForStatusChange(5, TimeUnit.SECONDS); - assertConnected(nc); - assertEquals(ts2.getURI(), nc.getConnectedUrl()); - standardCloseConnection(nc); - } - } - - @Test - public void testCloseOnReconnectWithSameError() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - - // Connect should fail on ts1 - try (NatsTestServer ts = new NatsTestServer("src/test/resources/operator_noacct.conf", false); - NatsTestServer ts2 = new NatsTestServer("src/test/resources/operator.conf", false)) { - Options options = new Options.Builder().servers(new String[]{ts.getURI(), ts2.getURI()}) - .maxReconnects(-1).connectionTimeout(Duration.ofSeconds(2)).noRandomize() - .authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds")).build(); - Connection nc = standardConnection(options); - assertEquals(ts2.getURI(), nc.getConnectedUrl()); + runInConfiguredServer("operator.conf", ts1 -> { // closed as part of test, so cannot be shared + runInSharedConfiguredServer("operator.conf", ts2 -> { + Options options = optionsBuilder(ts1.getServerUri(), ts2.getServerUri()) + .noRandomize() + .maxReconnects(-1) + .authHandler(getUserCredsAuthHander()) + .connectionListener(listener) + .errorListener(listener) + .build(); + Connection nc = managedConnect(options); + assertEquals(ts1.getServerUri(), nc.getConnectedUrl()); - listener.prepForStatusChange(Events.CLOSED); + listener.queueConnectionEvent(Events.RECONNECTED); - ts2.close(); + ts1.close(); - // Reconnect will fail because ts has the same auth error - listener.waitForStatusChange(6, TimeUnit.SECONDS); - standardCloseConnection(nc); - } + // Reconnect will fail because ts has the same auth error + listener.validate(); + assertConnected(nc); + assertEquals(ts2.getServerUri(), nc.getConnectedUrl()); + closeAndConfirm(nc); + }); + }); } @Test - public void testThatAuthErrorIsCleared() throws Exception { + public void testCloseOnReconnectWithSameError() throws Exception { // Connect should fail on ts1 - try (NatsTestServer ts1 = new NatsTestServer("src/test/resources/operator_noacct.conf", false); - NatsTestServer ts2 = new NatsTestServer("src/test/resources/operator.conf", false)) { - - Options options = new Options.Builder() - .servers(new String[]{ts1.getURI(), ts2.getURI()}).noRandomize() - .maxReconnects(-1) - .connectionTimeout(Duration.ofSeconds(5)) - .reconnectWait(Duration.ofSeconds(1)) // wait a tad to allow restarts - .authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds")) - .errorListener(new ListenerForTesting()) - .build(); - Connection nc = standardConnection(options); - assertEquals(ts2.getURI(), nc.getConnectedUrl()); - - String tsURI = ts1.getURI(); - int port1 = ts1.getPort(); - int port2 = ts2.getPort(); - - ts1.close(); - - // ts3 will be at the same port that ts was - try (NatsTestServer ts3 = new NatsTestServer("src/test/resources/operator.conf", port1, false)) { - ListenerForTesting listener = new ListenerForTesting(); - listener.prepForStatusChange(Events.RECONNECTED); - - ts2.close(); + runInConfiguredServer("operator.conf", ts2 -> { // closed in test, so cannot be shared + runInSharedConfiguredServer("operator_noacct.conf", ts1 -> { + Listener listener = new Listener(); + Options options = optionsBuilder(ts1.getServerUri(), ts2.getServerUri()) + .maxReconnects(-1) + .connectionTimeout(Duration.ofSeconds(2)) + .connectionListener(listener) + .noRandomize() + .authHandler(getUserCredsAuthHander()) + .build(); - // reconnect should work because we are now running with the good config - listenerConnectionWait(nc, listener, 10000); + try (Connection nc = managedConnect(options)) { + assertEquals(ts2.getServerUri(), nc.getConnectedUrl()); - assertEquals(ts3.getURI(), nc.getConnectedUrl()); - assertEquals(tsURI, ts3.getURI()); + listener.queueConnectionEvent(Events.CLOSED, LONG_VALIDATE_TIMEOUT); - // Close this and go back to the bad config on that port, should be ok 1x - listener.prepForStatusChange(Events.RECONNECTED); - ts3.close(); + ts2.close(); - try (NatsTestServer ignored = new NatsTestServer("src/test/resources/operator_noacct.conf", port1, false); - NatsTestServer ts5 = new NatsTestServer("src/test/resources/operator.conf", port2, false)) { - listenerConnectionWait(nc, listener, 10000); - assertEquals(ts5.getURI(), nc.getConnectedUrl()); + // Reconnect will fail because ts has the same auth error + listener.validate(); } - } - standardCloseConnection(nc); - } + }); + }); + } + + @Test + public void testThatAuthErrorIsCleared() throws Exception { + AtomicReference ncRef = new AtomicReference<>(); + AtomicReference server2Ref = new AtomicReference<>(); + AtomicInteger port2Ref = new AtomicInteger(); + + runInConfiguredServer("operator_noacct.conf", ts1 -> { + runInConfiguredServer("operator.conf", ts2 -> { + String server1 = ts1.getServerUri(); + String server2 = ts2.getServerUri(); + server2Ref.set(server2); + port2Ref.set(ts2.getPort()); + Options options = optionsBuilder(server1, server2) + .noRandomize() + .maxReconnects(-1) + .connectionTimeout(Duration.ofSeconds(5)) + .reconnectWait(Duration.ofSeconds(1)) // wait a tad to allow restarts + .authHandler(getUserCredsAuthHander()) + .build(); + Connection nc = managedConnect(options); + ncRef.set(nc); + assertEquals(server2, nc.getConnectedUrl()); + }); + + runInConfiguredServer("operator.conf", port2Ref.get(), ts2 -> { + confirmConnected(ncRef.get()); + assertEquals(server2Ref.get(), ncRef.get().getConnectedUrl()); + }); + }); } @Test @@ -665,12 +623,11 @@ public void testReconnectAfterAccountAuthenticationExpired() throws Exception { } private static void _testReconnectAfter(String errText) throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - - CompletableFuture f = new CompletableFuture(); + Listener listener = new Listener(); + CompletableFuture fMock = new CompletableFuture<>(); NatsServerProtocolMock.Customizer timeoutCustomizer = (ts, r, w) -> { - f.join(); // wait until we are ready + fMock.join(); // wait until we are ready w.write("-ERR " + errText + "\r\n"); // Drop the line feed w.flush(); }; @@ -678,61 +635,35 @@ private static void _testReconnectAfter(String errText) throws Exception { int port = NatsTestServer.nextPort(); // Connect should fail on ts1 - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(timeoutCustomizer, port, true); - NatsTestServer ts2 = new NatsTestServer("src/test/resources/operator.conf", false)) { - Options options = new Options.Builder() - .servers(new String[]{ts.getURI(), ts2.getURI()}) - .maxReconnects(-1) - .noRandomize() - .authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds")) - .errorListener(new ErrorListener() {}) - .build(); - - Connection nc = standardConnection(options); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - - listener.prepForStatusChange(Events.RECONNECTED); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(timeoutCustomizer, port, true)) { + runInSharedConfiguredServer("operator.conf", ts2 -> { + Options options = optionsBuilder(mockTs, ts2) + .maxReconnects(-1) + .noRandomize() + .authHandler(getUserCredsAuthHander()) + .errorListener(listener) + .connectionListener(listener) + .build(); - f.complete(true); + listener.queueConnectionEvent(Events.RECONNECTED); - listenerConnectionWait(nc, listener); - assertEquals(ts2.getURI(), nc.getConnectedUrl()); + try (Connection nc = standardConnect(options)) { + assertEquals(mockTs.getServerUri(), nc.getConnectedUrl()); + fMock.complete(true); + listener.validate(); + assertEquals(ts2.getServerUri(), nc.getConnectedUrl()); - String err = nc.getLastError(); - assertNotNull(err); - assertTrue(err.toLowerCase().contains(errText)); - standardCloseConnection(nc); + String err = nc.getLastError(); + assertNotNull(err); + assertTrue(err.toLowerCase().contains(errText)); + } + }); } } - @Disabled("This test flaps on CI, it must be that environment") @Test public void testRealUserAuthenticationExpired() throws Exception { - CountDownLatch cdlConnected = new CountDownLatch(1); - CountDownLatch cdlDisconnected = new CountDownLatch(1); - CountDownLatch cdlReconnected = new CountDownLatch(1); - CountDownLatch elUserAuthenticationExpired = new CountDownLatch(1); - CountDownLatch elAuthorizationViolation = new CountDownLatch(1); - - ConnectionListener cl = (conn, type) -> { - switch (type) { - case CONNECTED: cdlConnected.countDown(); break; - case DISCONNECTED: cdlDisconnected.countDown(); break; - case RECONNECTED: cdlReconnected.countDown(); break; - } - }; - - ErrorListener el = new ErrorListener() { - @Override - public void errorOccurred(Connection conn, String error) { - if (error.equalsIgnoreCase("user authentication expired")) { - elUserAuthenticationExpired.countDown(); - } - else if (error.equalsIgnoreCase("authorization violation")) { - elAuthorizationViolation.countDown(); - } - } - }; + Listener listener = new Listener(); String accountSeed = "SAAPXJRFMUYDUH3NOZKE7BS2ZDO2P4ND7G6W743MTNA3KCSFPX3HNN6AX4"; String accountId = "ACPWDUYSZRRF7XAEZKUAGPUH6RPICWEHSTFELYKTOWUVZ4R2XMP4QJJX"; @@ -743,39 +674,52 @@ else if (error.equalsIgnoreCase("authorization violation")) { NKey nKeyUser = NKey.fromSeed(userSeed.toCharArray()); String publicUserKey = new String(nKeyUser.getPublicKey()); - long expires = 2500; - long wait = 5000; + long expires = 1000; Duration expiration = Duration.ofMillis(expires); String jwt = JwtUtils.issueUserJWT(nKeyAccount, accountId, publicUserKey, "jnatsTestUser", expiration); String creds = String.format(JwtUtils.NATS_USER_JWT_FORMAT, jwt, new String(nKeyUser.getSeed())); - String credsFile = null; - try { - credsFile = ResourceUtils.createTempFile("nats_java_test", ".creds", creds.split("\\Q\\n\\E")); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/operatorJnatsTest.conf", false)) { - + runInConfiguredServer("operatorJnatsTest.conf", ts -> { + String credsFile = null; + try { + credsFile = ResourceUtils.createTempFile("nats_java_test", ".creds", creds.split("\\Q\\n\\E")); Options options = Options.builder() - .server(ts.getURI()) + .server(ts.getServerUri()) .credentialPath(credsFile) - .connectionListener(cl) - .errorListener(el) + .connectionListener(listener) + .errorListener(listener) .maxReconnects(5) .build(); - Connection nc = Nats.connect(options); - assertTrue(cdlConnected.await(wait, TimeUnit.MILLISECONDS)); - assertTrue(cdlDisconnected.await(wait, TimeUnit.MILLISECONDS)); - assertTrue(cdlReconnected.await(wait, TimeUnit.MILLISECONDS)); - assertTrue(elUserAuthenticationExpired.await(wait, TimeUnit.MILLISECONDS)); - assertTrue(elAuthorizationViolation.await(wait, TimeUnit.MILLISECONDS)); + listener.queueError("User Authentication Expired"); - nc.close(); + // so these shenanigans to test this... + // 1. sometimes it connects and the expiration comes while connected + // 2. sometimes the connect exceptions right away + // 3. sometimes the connect happens but still exceptions + // this is all simply the speed and timing of the machine/server/connection + try (Connection ignored = Nats.connect(options)) { + sleep(2500); // 1. connected, so the validate() at the end verifies this + } + catch (AuthenticationException ignore) { + return; // 2. sometimes the connect exceptions right away + } + catch (RuntimeException e) { + // 3. sometimes the connect happens but still exceptions + Throwable t = e; + while (t != null) { + if (t instanceof AuthenticationException) { + return; + } + t = t.getCause(); + } + fail(e); + } + listener.validate(); // 1. finish } - catch (Exception ignore) { + finally { + ResourceUtils.deleteFileOrFolder(credsFile); } - } - finally { - ResourceUtils.deleteFileOrFolder(credsFile); - } + }); } } diff --git a/src/test/java/io/nats/client/BadHandler.java b/src/test/java/io/nats/client/BadHandler.java index faf2dbb4d..1fd9b9351 100644 --- a/src/test/java/io/nats/client/BadHandler.java +++ b/src/test/java/io/nats/client/BadHandler.java @@ -15,18 +15,18 @@ public class BadHandler implements ErrorListener, ConnectionListener { public void exceptionOccurred(Connection conn, Exception exp) { - throw new IllegalStateException("Its good to be bad"); + throw new IllegalStateException("Intentional"); } public void errorOccurred(Connection conn, String type) { - throw new IllegalStateException("Its good to be bad"); + throw new IllegalStateException("Intentional"); } public void connectionEvent(Connection conn, Events type) { - throw new IllegalStateException("Its good to be bad"); + throw new IllegalStateException("Intentional"); } public void slowConsumerDetected(Connection conn, Consumer consumer) { - throw new IllegalStateException("Its good to be bad"); + throw new IllegalStateException("Intentional"); } } \ No newline at end of file diff --git a/src/test/java/io/nats/client/ConnectTests.java b/src/test/java/io/nats/client/ConnectTests.java index f19c4deba..83a11ac9f 100644 --- a/src/test/java/io/nats/client/ConnectTests.java +++ b/src/test/java/io/nats/client/ConnectTests.java @@ -16,10 +16,10 @@ import io.nats.client.ConnectionListener.Events; import io.nats.client.NatsServerProtocolMock.ExitAt; import io.nats.client.api.ServerInfo; -import io.nats.client.impl.ListenerForTesting; import io.nats.client.impl.SimulateSocketDataPortException; -import io.nats.client.utils.TestBase; +import io.nats.client.support.Listener; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.io.IOException; import java.net.InetAddress; @@ -32,303 +32,252 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static io.nats.client.utils.TestBase.*; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; +@Isolated public class ConnectTests { @Test - public void testDefaultConnection() throws Exception { - try (NatsTestServer ignored = new NatsTestServer(Options.DEFAULT_PORT, false)) { - Connection nc = standardConnection(); - assertEquals(Options.DEFAULT_PORT, nc.getServerInfo().getPort()); - standardCloseConnection(nc); - } + public void testConnectWithConfig() throws Exception { + runInConfiguredServer("simple.conf", ts -> assertCanConnect(optionsBuilder(ts).build())); } @Test - public void testConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Connection nc = standardConnection(ts.getURI()); - assertEquals(ts.getPort(), nc.getServerInfo().getPort()); - // coverage for getClientAddress - InetAddress inetAddress = nc.getClientInetAddress(); - assertTrue(inetAddress.equals(InetAddress.getLoopbackAddress()) - || inetAddress.equals(InetAddress.getLocalHost())); - standardCloseConnection(nc); - } - } + public void testConnectVariants() throws Exception { + try (NatsTestServer ts1 = new NatsTestServer()) { + try (NatsTestServer ts2 = new NatsTestServer()) { + // commas in one server url + Options options = optionsBuilder().server(ts1.getServerUri() + "," + ts2.getServerUri()).build(); + try (Connection nc = managedConnect(options)) { + // coverage for getClientAddress + InetAddress inetAddress = nc.getClientInetAddress(); + assertNotNull(inetAddress); + assertTrue(inetAddress.equals(InetAddress.getLoopbackAddress()) + || inetAddress.equals(InetAddress.getLocalHost())); + } - @Test - public void testConnectionWithOptions() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).build(); - assertCanConnect(options); + // Randomize + boolean needOne = true; + boolean needTwo = true; + int tries = 20; + options = options(ts1, ts2); + while (tries-- > 0 && (needOne || needTwo)) { + try (Connection nc = managedConnect(options)) { + Collection servers = nc.getServers(); + assertTrue(servers.contains(ts1.getServerUri())); + assertTrue(servers.contains(ts2.getServerUri())); + if (ts1.getServerUri().equals(nc.getConnectedUrl())) { + needOne = false; + } + else { + needTwo = false; + } + } + } + assertFalse(needOne); + assertFalse(needTwo); + + // noRandomize + tries = 3; + int gotOne = 0; + int gotTwo = 0; + + // should never get a two + options = optionsBuilder(ts1.getServerUri(), ts2.getServerUri()).noRandomize().build(); + for (int i = 0; i < tries; i++) { + try (Connection nc = managedConnect(options)) { + Collection servers = nc.getServers(); + assertTrue(servers.contains(ts1.getServerUri())); + assertTrue(servers.contains(ts2.getServerUri())); + if (ts1.getServerUri().equals(nc.getConnectedUrl())) { + gotOne++; + } + else { + gotTwo++; + } + } + } + + assertEquals(tries, gotOne, "should always ge one"); + assertEquals(0, gotTwo, "should never get two"); + } } } @Test public void testFullFakeConnect() throws Exception { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { - assertCanConnect(ts.getURI()); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { + assertCanConnect(mockTs); } } @Test public void testFullFakeConnectWithTabs() throws Exception { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { - ts.useTabs(); - assertCanConnect(ts.getURI()); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { + mockTs.useTabs(); + assertCanConnect(mockTs); } } @Test - public void testConnectExitBeforeInfo() { - assertThrows(IOException.class, () -> { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.EXIT_BEFORE_INFO)) { - Options options = new Options.Builder().server(ts.getURI()).noReconnect().build(); - assertCanConnect(options); - } - }); + public void testConnectExitBeforeInfo() throws IOException { + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.EXIT_BEFORE_INFO)) { + Options options = optionsBuilder(mockTs).noReconnect().build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + } } @Test - public void testConnectExitAfterInfo() { - assertThrows(IOException.class, () -> { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_INFO)) { - Options options = new Options.Builder().server(ts.getURI()).noReconnect().build(); - assertCanConnect(options); - } - }); + public void testConnectExitAfterInfo() throws IOException { + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_INFO)) { + Options options = optionsBuilder(mockTs).noReconnect().build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + } } @Test - public void testConnectExitAfterConnect() { - assertThrows(IOException.class, () -> { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_CONNECT)) { - Options options = new Options.Builder().server(ts.getURI()).noReconnect().build(); - assertCanConnect(options); - } - }); + public void testConnectExitAfterConnect() throws IOException { + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_CONNECT)) { + Options options = optionsBuilder(mockTs).noReconnect().build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + } } @Test - public void testConnectExitAfterPing() { - assertThrows(IOException.class, () -> { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_PING)) { - Options options = new Options.Builder().server(ts.getURI()).noReconnect().build(); - assertCanConnect(options); - } - }); + public void testConnectExitAfterPing() throws IOException { + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_PING)) { + Options options = optionsBuilder(mockTs).noReconnect().build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + } } @Test public void testConnectionFailureWithFallback() throws Exception { - - try (NatsTestServer ts = new NatsTestServer(false)) { - try (NatsServerProtocolMock fake = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_PING)) { - Options options = new Options.Builder().connectionTimeout(Duration.ofSeconds(5)).server(fake.getURI()) - .server(ts.getURI()).build(); + try (NatsTestServer ts = new NatsTestServer()) { + try (NatsServerProtocolMock mock = new NatsServerProtocolMock(ExitAt.EXIT_AFTER_PING)) { + Options options = optionsBuilder(mock, ts) + .noRandomize() + .build(); assertCanConnect(options); } } } @Test - public void testConnectWithConfig() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/simple.conf", false)) { - assertCanConnect(ts.getURI()); + public void testFailWithMissingLineFeedAfterInfo() throws Exception { + String badInfo = "{\"server_id\":\"test\", \"version\":\"9.9.99\"}\rmore stuff"; + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(null, badInfo)) { + Options options = optionsBuilder(mockTs).reconnectWait(Duration.ofDays(1)).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); } } @Test - public void testConnectWithCommas() throws Exception { - try (NatsTestServer ts1 = new NatsTestServer(false)) { - try (NatsTestServer ts2 = new NatsTestServer(false)) { - assertCanConnect(ts1.getURI() + "," + ts2.getURI()); - } + public void testFailWithStuffAfterInitialInfo() throws Exception { + String badInfo = "{\"server_id\":\"test\", \"version\":\"9.9.99\"}\r\nmore stuff"; + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(null, badInfo)) { + Options options = optionsBuilder(mockTs).reconnectWait(Duration.ofDays(1)).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); } } @Test - public void testConnectRandomize() throws Exception { - try (NatsTestServer ts1 = new NatsTestServer(false)) { - try (NatsTestServer ts2 = new NatsTestServer(false)) { - boolean needOne = true; - boolean needTwo = true; - int count = 0; - int maxTries = 100; - while (count++ < maxTries && (needOne || needTwo)) { - Connection nc = standardConnection(ts1.getURI() + "," + ts2.getURI()); - if (nc.getConnectedUrl().equals(ts1.getURI())) { - needOne = false; - } else { - needTwo = false; - } - Collection servers = nc.getServers(); - assertTrue(servers.contains(ts1.getURI())); - assertTrue(servers.contains(ts2.getURI())); - standardCloseConnection(nc); - } - assertFalse(needOne); - assertFalse(needTwo); - } + public void testFailWrongInitialInfoOP() throws Exception { + String badInfo = "PING {\"server_id\":\"test\", \"version\":\"9.9.99\"}\r\n"; // wrong op code + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(null, badInfo)) { + mockTs.useCustomInfoAsFullInfo(); + Options options = optionsBuilder(mockTs).reconnectWait(Duration.ofDays(1)).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); } } @Test - public void testConnectNoRandomize() throws Exception { - try (NatsTestServer ts1 = new NatsTestServer(false)) { - try (NatsTestServer ts2 = new NatsTestServer(false)) { - int one = 0; - int two = 0; - - // should get at least 1 for each - for (int i = 0; i < 10; i++) { - String[] servers = { ts1.getURI(), ts2.getURI() }; - Options options = new Options.Builder().noRandomize().servers(servers).build(); - Connection nc = standardConnection(options); - if (nc.getConnectedUrl().equals(ts1.getURI())) { - one++; - } else { - two++; - } - standardCloseConnection(nc); - } - - assertEquals(one, 10, "always got one"); - assertEquals(two, 0, "never got two"); - } + public void testIncompleteInitialInfo() throws Exception { + String badInfo = "{\"server_id\"\r\n"; + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(null, badInfo)) { + Options options = optionsBuilder(mockTs).reconnectWait(Duration.ofDays(1)).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); } } - @Test - public void testFailWithMissingLineFeedAfterInfo() { - assertThrows(IOException.class, () -> { - String badInfo = "{\"server_id\":\"test\", \"version\":\"9.9.99\"}\rmore stuff"; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(null, badInfo)) { - Options options = new Options.Builder().server(ts.getURI()).reconnectWait(Duration.ofDays(1)).build(); - Nats.connect(options); - } - }); - } - - @Test - public void testFailWithStuffAfterInitialInfo() { - assertThrows(IOException.class, () -> { - String badInfo = "{\"server_id\":\"test\", \"version\":\"9.9.99\"}\r\nmore stuff"; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(null, badInfo)) { - Options options = new Options.Builder().server(ts.getURI()).reconnectWait(Duration.ofDays(1)).build(); - Nats.connect(options); - } - }); - } - - @Test - public void testFailWrongInitialInfoOP() { - assertThrows(IOException.class, () -> { - String badInfo = "PING {\"server_id\":\"test\", \"version\":\"9.9.99\"}\r\n"; // wrong op code - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(null, badInfo)) { - ts.useCustomInfoAsFullInfo(); - Options options = new Options.Builder().server(ts.getURI()).reconnectWait(Duration.ofDays(1)).build(); - Nats.connect(options); - } - }); - } - - @Test - public void testIncompleteInitialInfo() { - assertThrows(IOException.class, () -> { - String badInfo = "{\"server_id\"\r\n"; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(null, badInfo)) { - Options options = new Options.Builder().server(ts.getURI()).reconnectWait(Duration.ofDays(1)).build(); - Nats.connect(options); - } - }); - } - @Test public void testAsyncConnection() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - Connection nc = null; - - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).connectionListener(listener).build(); - listener.prepForStatusChange(Events.CONNECTED); - + Listener listener = new Listener(); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts).connectionListener(listener).build(); + listener.queueConnectionEvent(Events.CONNECTED); Nats.connectAsynchronously(options, false); + listener.validate(); - listener.waitForStatusChange(1, TimeUnit.SECONDS); - - nc = listener.getLastEventConnection(); + Connection nc = listener.getLastConnectionEventConnection(); assertNotNull(nc); assertConnected(nc); - standardCloseConnection(nc); + closeAndConfirm(nc); } } @Test public void testAsyncConnectionWithReconnect() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port = NatsTestServer.nextPort(); - Options options = new Options.Builder().server("nats://localhost:" + port).maxReconnects(-1) + Options options = optionsBuilder(port).maxReconnects(-1) .reconnectWait(Duration.ofMillis(100)).connectionListener(listener).build(); Nats.connectAsynchronously(options, true); sleep(5000); // No server at this point, let it fail and try to start over - Connection nc = listener.getLastEventConnection(); // will be disconnected, but should be there + Connection nc = listener.getLastConnectionEventConnection(); // will be disconnected, but should be there assertNotNull(nc); - listener.prepForStatusChange(Events.RECONNECTED); - try (NatsTestServer ignored = new NatsTestServer(port, false)) { - listenerConnectionWait(nc, listener); - standardCloseConnection(nc); + listener.queueConnectionEvent(Events.RECONNECTED); + try (NatsTestServer ignored = new NatsTestServer(port)) { + confirmConnectedThenClosed(nc); } } @Test - public void testThrowOnAsyncWithoutListener() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).build(); - Nats.connectAsynchronously(options, false); - } - }); + public void testThrowOnAsyncWithoutListener() throws Exception { + Options options = optionsBuilder(NatsTestServer.nextPort()).build(); + assertThrows(IllegalArgumentException.class, () -> Nats.connectAsynchronously(options, false)); } @Test public void testErrorOnAsync() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - Options options = new Options.Builder().server("nats://localhost:" + NatsTestServer.nextPort()) - .connectionListener(listener).errorListener(listener).noReconnect().build(); - listener.prepForStatusChange(Events.CLOSED); + Listener listener = new Listener(); + Options options = optionsBuilder(NatsTestServer.nextPort()) + .connectionListener(listener) + .errorListener(listener) + .noReconnect() + .build(); + listener.queueConnectionEvent(Events.CLOSED); Nats.connectAsynchronously(options, false); - listener.waitForStatusChange(10, TimeUnit.SECONDS); - + listener.validate(); assertTrue(listener.getExceptionCount() > 0); - assertTrue(listener.getEventCount(Events.CLOSED) > 0); } @Test - public void testConnectionTimeout() { - assertThrows(IOException.class, () -> { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.SLEEP_BEFORE_INFO)) { // will sleep for 3 - Options options = new Options.Builder().server(ts.getURI()).noReconnect().traceConnection() - .connectionTimeout(Duration.ofSeconds(2)). // 2 is also the default but explicit for test - build(); - Connection nc = Nats.connect(options); - assertNotSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - } - }); + public void testConnectionTimeout() throws Exception { + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.SLEEP_BEFORE_INFO)) { // will sleep for 3 + Options options = optionsBuilder(mockTs) + .noReconnect() + .connectionTimeout(Duration.ofSeconds(2)) // 2 is also the default but explicit for test + .build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + } } @Test public void testSlowConnectionNoTimeout() throws Exception { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.SLEEP_BEFORE_INFO)) { - Options options = new Options.Builder().server(ts.getURI()).noReconnect() - .connectionTimeout(Duration.ofSeconds(6)). // longer than the sleep - build(); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.SLEEP_BEFORE_INFO)) { + Options options = optionsBuilder(mockTs) + .noReconnect() + .connectionTimeout(Duration.ofSeconds(6)) // longer than the sleep + .build(); assertCanConnect(options); } } @@ -338,11 +287,11 @@ public void testTimeCheckCoverage() throws Exception { List traces = new ArrayList<>(); TimeTraceLogger l = (f, a) -> traces.add(String.format(f, a)); - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).traceConnection().build(); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts).traceConnection().build(); assertCanConnect(options); - options = new Options.Builder().server(ts.getURI()).timeTraceLogger(l).build(); + options = optionsBuilder(ts).timeTraceLogger(l).build(); assertCanConnect(options); } @@ -376,9 +325,8 @@ public void testReconnectLogging() throws Exception { List traces = new ArrayList<>(); TimeTraceLogger l = (f, a) -> traces.add(String.format(f, a)); - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder() - .server(ts.getURI()) + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) .traceConnection() .timeTraceLogger(l) .reconnectWait(Duration.ofSeconds(1)) @@ -386,7 +334,7 @@ public void testReconnectLogging() throws Exception { .connectionTimeout(Duration.ofSeconds(2)) .build(); - try (Connection nc = Nats.connect(options)) { + try (Connection nc = managedConnect(options)) { assertConnected(nc); ts.close(); Thread.sleep(3000); @@ -400,7 +348,7 @@ public void testReconnectLogging() throws Exception { @Test public void testConnectExceptionHasURLS() { try { - Nats.connect("nats://testserver.notnats:4222, nats://testserver.alsonotnats:4223"); + Nats.connect(options("nats://testserver.notnats:4222, nats://testserver.alsonotnats:4223")); } catch (Exception e) { assertTrue(e.getMessage().contains("testserver.notnats:4222")); assertTrue(e.getMessage().contains("testserver.alsonotnats:4223")); @@ -409,8 +357,8 @@ public void testConnectExceptionHasURLS() { @Test public void testFlushBuffer() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Connection nc = standardConnection(ts.getURI()); + try (NatsTestServer ts = new NatsTestServer()) { + Connection nc = managedConnect(options(ts)); // test connected nc.flushBuffer(); @@ -422,7 +370,7 @@ public void testFlushBuffer() throws Exception { // test while reconnecting assertThrows(IllegalStateException.class, nc::flushBuffer); - standardCloseConnection(nc); + closeAndConfirm(nc); // test when closed. assertThrows(IllegalStateException.class, nc::flushBuffer); @@ -431,8 +379,8 @@ public void testFlushBuffer() throws Exception { @Test public void testFlushBufferThreadSafety() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Connection nc = standardConnection(ts.getURI()); + try (NatsTestServer ts = new NatsTestServer()) { + Connection nc = managedConnect(options(ts)); // use two latches to sync the threads as close as // possible. @@ -450,8 +398,9 @@ public void run() { } catch (Exception e) { // NOOP } + String subject = random(); for (int i = 1; i <= 50000; i++) { - nc.publish("foo", payload); + nc.publish(subject, payload); if (i % 2000 == 0) { try { nc.flushBuffer(); @@ -469,7 +418,8 @@ public void run() { // sync up the current thread and the publish thread // to get the most out of the test. try { - pubLatch.await(2, TimeUnit.SECONDS); + //noinspection ResultOfMethodCallIgnored + pubLatch.await(2, TimeUnit.SECONDS); } catch (Exception e) { // NOOP } @@ -482,13 +432,13 @@ public void run() { nc.flushBuffer(); } - // cleanup and doublecheck the thread is done. + // cleanup and double-check the thread is done. t.join(2000); // make sure the publisher actually completed. assertTrue(completedLatch.await(10, TimeUnit.SECONDS)); - standardCloseConnection(nc); + closeAndConfirm(nc); } } @@ -498,7 +448,7 @@ public void testSocketLevelException() throws Exception { int port = NatsTestServer.nextPort(); AtomicBoolean simExReceived = new AtomicBoolean(); - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); ErrorListener el = new ErrorListener() { @Override public void exceptionOccurred(Connection conn, Exception exp) { @@ -508,8 +458,7 @@ public void exceptionOccurred(Connection conn, Exception exp) { } }; - Options options = new Options.Builder() - .server(NatsTestServer.getNatsLocalhostUri(port)) + Options options = optionsBuilder(port) .dataPortType("io.nats.client.impl.SimulateSocketDataPortException") .connectionListener(listener) .errorListener(el) @@ -519,7 +468,7 @@ public void exceptionOccurred(Connection conn, Exception exp) { Connection connection = null; // 1. DO NOT RECONNECT ON CONNECT - try (NatsTestServer ts = new NatsTestServer(port, false)) { + try (NatsTestServer ts = new NatsTestServer(port)) { try { SimulateSocketDataPortException.THROW_ON_CONNECT.set(true); connection = Nats.connect(options); @@ -534,41 +483,36 @@ public void exceptionOccurred(Connection conn, Exception exp) { simExReceived.set(false); // 2. RECONNECT ON CONNECT - try (NatsTestServer ts = new NatsTestServer(port, false)) { + try (NatsTestServer ts = new NatsTestServer(port)) { try { SimulateSocketDataPortException.THROW_ON_CONNECT.set(true); - listener.prepForStatusChange(Events.RECONNECTED); + listener.queueConnectionEvent(Events.RECONNECTED); connection = Nats.connectReconnectOnConnect(options); - assertTrue(listener.waitForStatusChange(5, TimeUnit.SECONDS)); - listener.prepForStatusChange(Events.DISCONNECTED); + listener.validate(); + listener.queueConnectionEvent(Events.DISCONNECTED); } catch (Exception e) { fail("should have connected " + e); } } - assertTrue(listener.waitForStatusChange(5, TimeUnit.SECONDS)); + listener.validate(); assertTrue(simExReceived.get()); simExReceived.set(false); // 2. NORMAL RECONNECT - listener.prepForStatusChange(Events.RECONNECTED); - try (NatsTestServer ts = new NatsTestServer(port, false)) { + listener.queueConnectionEvent(Events.RECONNECTED); + try (NatsTestServer ts = new NatsTestServer(port)) { SimulateSocketDataPortException.THROW_ON_CONNECT.set(true); - try { - assertTrue(listener.waitForStatusChange(5, TimeUnit.SECONDS)); - } - catch (Exception e) { - fail("should have reconnected " + e); - } + listener.validate(); } } @Test public void testRunInJsCluster() throws Exception { - ListenerForTesting[] listeners = new ListenerForTesting[3]; - listeners[0] = new ListenerForTesting(); - listeners[1] = new ListenerForTesting(); - listeners[2] = new ListenerForTesting(); + Listener[] listeners = new Listener[3]; + listeners[0] = new Listener(); + listeners[1] = new Listener(); + listeners[2] = new Listener(); ThreeServerTestOptions tstOpts = new ThreeServerTestOptions() { @Override @@ -585,39 +529,43 @@ public boolean configureAccount() { public boolean includeAllServers() { return true; } - }; - runInJsCluster(ConnectTests::validateRunInJsCluster); - - listeners[0] = new ListenerForTesting(); - listeners[1] = new ListenerForTesting(); - listeners[2] = new ListenerForTesting(); - - runInJsCluster(tstOpts, ConnectTests::validateRunInJsCluster); - } + @Override + public boolean jetStream() { + return true; + } + }; - private static void validateRunInJsCluster(Connection nc1, Connection nc2, Connection nc3) throws InterruptedException { - Thread.sleep(200); - ServerInfo si1 = nc1.getServerInfo(); - ServerInfo si2 = nc2.getServerInfo(); - ServerInfo si3 = nc3.getServerInfo(); - assertEquals(si1.getCluster(), si2.getCluster()); - assertEquals(si1.getCluster(), si3.getCluster()); - String port1 = "" + si1.getPort(); - String port2 = "" + si2.getPort(); - String port3 = "" + si3.getPort(); - String urls1 = String.join(",", si1.getConnectURLs()); - String urls2 = String.join(",", si2.getConnectURLs()); - String urls3 = String.join(",", si3.getConnectURLs()); - assertTrue(urls1.contains(port1)); - assertTrue(urls1.contains(port2)); - assertTrue(urls1.contains(port3)); - assertTrue(urls2.contains(port1)); - assertTrue(urls2.contains(port2)); - assertTrue(urls2.contains(port3)); - assertTrue(urls3.contains(port1)); - assertTrue(urls3.contains(port2)); - assertTrue(urls3.contains(port3)); + listeners[0] = new Listener(); + listeners[1] = new Listener(); + listeners[2] = new Listener(); + + runInCluster(tstOpts, (nc1, nc2, nc3) -> { + Thread.sleep(200); + ServerInfo si1 = nc1.getServerInfo(); + ServerInfo si2 = nc2.getServerInfo(); + ServerInfo si3 = nc3.getServerInfo(); + assertTrue(si1.isJetStreamAvailable()); + assertTrue(si2.isJetStreamAvailable()); + assertTrue(si3.isJetStreamAvailable()); + assertEquals(si1.getCluster(), si2.getCluster()); + assertEquals(si1.getCluster(), si3.getCluster()); + String port1 = "" + si1.getPort(); + String port2 = "" + si2.getPort(); + String port3 = "" + si3.getPort(); + String urls1 = String.join(",", si1.getConnectURLs()); + String urls2 = String.join(",", si2.getConnectURLs()); + String urls3 = String.join(",", si3.getConnectURLs()); + assertTrue(urls1.contains(port1)); + assertTrue(urls1.contains(port2)); + assertTrue(urls1.contains(port3)); + assertTrue(urls2.contains(port1)); + assertTrue(urls2.contains(port2)); + assertTrue(urls2.contains(port3)); + assertTrue(urls3.contains(port1)); + assertTrue(urls3.contains(port2)); + assertTrue(urls3.contains(port3)); + }); } // https://github.com/nats-io/nats.java/issues/1201 @@ -626,11 +574,7 @@ void testLowConnectionTimeoutResultsInIOException() { Options options = Options.builder() .connectionTimeout(Duration.ZERO) .build(); - - assertThrows(IOException.class, () -> { - Connection nc = Nats.connect(options); - nc.close(); - }); + assertThrows(IOException.class, () -> Nats.connect(options)); } @Test @@ -638,14 +582,14 @@ void testConnectWithHappyEyeballsShortCircuitCoverage() throws Exception { Options options = Options.builder().server("demo.nats.io") .hostnameResolveMode(Options.HostnameResolveMode.HappyEyeballs) .build(); - try (Connection nc = standardConnection(options)) { + try (Connection nc = Nats.connect(options)) { assertConnected(nc); } } @Test void testConnectPendingCountCoverage() throws Exception { - TestBase.runInJsServer(nc -> { + runInOwnServer(nc -> { AtomicLong outgoingPendingMessageCount = new AtomicLong(); AtomicLong outgoingPendingBytes = new AtomicLong(); @@ -654,17 +598,12 @@ void testConnectPendingCountCoverage() throws Exception { while (tKeepGoing.get()) { outgoingPendingMessageCount.set(Math.max(outgoingPendingMessageCount.get(), nc.outgoingPendingMessageCount())); outgoingPendingBytes.set(Math.max(outgoingPendingBytes.get(), nc.outgoingPendingBytes())); - try { - Thread.sleep(10); - } - catch (InterruptedException e) { - throw new RuntimeException(e); - } + sleep(10); } }); t.start(); - String subject = subject(); + String subject = random(); byte[] data = new byte[8 * 1024]; for (int x = 0; x < 5000; x++) { nc.publish(subject, data); diff --git a/src/test/java/io/nats/client/EchoTests.java b/src/test/java/io/nats/client/EchoTests.java index 6429ed843..b6c489645 100644 --- a/src/test/java/io/nats/client/EchoTests.java +++ b/src/test/java/io/nats/client/EchoTests.java @@ -13,68 +13,50 @@ package io.nats.client; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import io.nats.client.NatsServerProtocolMock.ExitAt; +import io.nats.client.impl.SharedServer; +import io.nats.client.utils.ConnectionUtils; +import io.nats.client.utils.TestBase; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.time.Duration; -import org.junit.jupiter.api.Test; - -import io.nats.client.NatsServerProtocolMock.ExitAt; +import static io.nats.client.utils.ConnectionUtils.assertCanConnect; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static org.junit.jupiter.api.Assertions.*; -public class EchoTests { +public class EchoTests extends TestBase { @Test - public void testFailWithBadServerProtocol() { - assertThrows(IOException.class, () -> { - Connection nc = null; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { - Options opt = new Options.Builder().server(ts.getURI()).noEcho().noReconnect().build(); - try { - nc = Nats.connect(opt); // Should fail - } finally { - if (nc != null) { - nc.close(); - assertTrue(Connection.Status.CLOSED == nc.getStatus(), "Closed Status"); - } - } - } - }); + public void testFailWithBadServerProtocol() throws Exception { + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { + Options options = optionsBuilder(mockTs).noEcho().noReconnect().build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + } } @Test public void testConnectToOldServerWithEcho() throws Exception { - Connection nc = null; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { - Options opt = new Options.Builder().server(ts.getURI()).noReconnect().build(); - try { - nc = Nats.connect(opt); - } finally { - if (nc != null) { - nc.close(); - assertTrue(Connection.Status.CLOSED == nc.getStatus(), "Closed Status"); - } - } + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { + Options options = optionsBuilder(mockTs).noReconnect().build(); + assertCanConnect(options); } } @Test public void testWithEcho() throws Exception { - try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder().server(ts.getURI()).noReconnect().build(); - try (Connection nc1 = Nats.connect(options); - Connection nc2 = Nats.connect(options);) { - + runInShared(nc1 -> { + try (Connection nc2 = ConnectionUtils.managedConnect(options(nc1))) { // Echo is on so both sub should get messages from both pub - Subscription sub1 = nc1.subscribe("test"); + String subject = random(); + Subscription sub1 = nc1.subscribe(subject); nc1.flush(Duration.ofSeconds(1)); - Subscription sub2 = nc2.subscribe("test"); + Subscription sub2 = nc2.subscribe(subject); nc2.flush(Duration.ofSeconds(1)); // Pub from connect 1 - nc1.publish("test", null); + nc1.publish(subject, null); nc1.flush(Duration.ofSeconds(1)); Message msg = sub1.nextMessage(Duration.ofSeconds(1)); assertNotNull(msg); @@ -82,45 +64,42 @@ public void testWithEcho() throws Exception { assertNotNull(msg); // Pub from connect 2 - nc2.publish("test", null); + nc2.publish(subject, null); nc2.flush(Duration.ofSeconds(1)); msg = sub1.nextMessage(Duration.ofSeconds(1)); assertNotNull(msg); msg = sub2.nextMessage(Duration.ofSeconds(1)); assertNotNull(msg); } - } + }); } - + @Test public void testWithNoEcho() throws Exception { - try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder().server(ts.getURI()).noEcho().noReconnect().build(); - try (Connection nc1 = Nats.connect(options); - Connection nc2 = Nats.connect(options);) { + runInSharedOwnNc(optionsBuilder().noEcho().noReconnect(), nc1 -> { + Connection nc2 = SharedServer.sharedConnectionForSameServer(nc1); - // Echo is on so both sub should get messages from both pub - Subscription sub1 = nc1.subscribe("test"); - nc1.flush(Duration.ofSeconds(1)); - Subscription sub2 = nc2.subscribe("test"); - nc2.flush(Duration.ofSeconds(1)); + String subject = random(); + Subscription sub1 = nc1.subscribe(subject); + nc1.flush(Duration.ofSeconds(1)); + Subscription sub2 = nc2.subscribe(subject); + nc2.flush(Duration.ofSeconds(1)); - // Pub from connect 1 - nc1.publish("test", null); - nc1.flush(Duration.ofSeconds(1)); - Message msg = sub1.nextMessage(Duration.ofSeconds(1)); - assertNull(msg); // no message for sub1 from pub 1 - msg = sub2.nextMessage(Duration.ofSeconds(1)); - assertNotNull(msg); + // Pub from connect 1 + nc1.publish(subject, null); + nc1.flush(Duration.ofSeconds(1)); + Message msg = sub1.nextMessage(Duration.ofSeconds(1)); + assertNull(msg); // no message for sub1 from pub 1 + msg = sub2.nextMessage(Duration.ofSeconds(1)); + assertNotNull(msg); - // Pub from connect 2 - nc2.publish("test", null); - nc2.flush(Duration.ofSeconds(1)); - msg = sub1.nextMessage(Duration.ofSeconds(1)); - assertNotNull(msg); - msg = sub2.nextMessage(Duration.ofSeconds(1)); - assertNull(msg); // no message for sub2 from pub 2 - } - } + // Pub from connect 2 + nc2.publish(subject, null); + nc2.flush(Duration.ofSeconds(1)); + msg = sub1.nextMessage(Duration.ofSeconds(1)); + assertNotNull(msg); + msg = sub2.nextMessage(Duration.ofSeconds(1)); + assertNotNull(msg); // nc2 is not no echo + }); } } \ No newline at end of file diff --git a/src/test/java/io/nats/client/HttpRequestTests.java b/src/test/java/io/nats/client/HttpRequestTests.java index 45afa5a28..34f05c3dd 100644 --- a/src/test/java/io/nats/client/HttpRequestTests.java +++ b/src/test/java/io/nats/client/HttpRequestTests.java @@ -57,14 +57,8 @@ public void testSetters() { @Test public void testNulls() { HttpRequest request = new HttpRequest(); - assertThrows(IllegalArgumentException.class, () -> { - request.method(null); - }); - assertThrows(IllegalArgumentException.class, () -> { - request.uri(null); - }); - assertThrows(IllegalArgumentException.class, () -> { - request.version(null); - }); + assertThrows(IllegalArgumentException.class, () -> request.method(null)); + assertThrows(IllegalArgumentException.class, () -> request.uri(null)); + assertThrows(IllegalArgumentException.class, () -> request.version(null)); } } diff --git a/src/test/java/io/nats/client/NKeyTests.java b/src/test/java/io/nats/client/NKeyTests.java index 66f9fb774..fc16d588a 100644 --- a/src/test/java/io/nats/client/NKeyTests.java +++ b/src/test/java/io/nats/client/NKeyTests.java @@ -78,7 +78,7 @@ public void testBase32() { char[] encoded = base32Encode(bytes); byte[] decoded = base32Decode(encoded); String test = new String(decoded, StandardCharsets.UTF_8); - assertEquals(test, expected); + assertEquals(expected, test); } // bad input for coverage @@ -97,8 +97,8 @@ public void testEncodeDecodeSeed() throws Exception { char[] encoded = NKey.encodeSeed(NKey.Type.ACCOUNT, bytes); DecodedSeed decoded = NKey.decodeSeed(encoded); - assertEquals(NKey.Type.fromPrefix(decoded.prefix), NKey.Type.ACCOUNT); - assertTrue(Arrays.equals(bytes, decoded.bytes)); + assertEquals(NKey.Type.ACCOUNT, NKey.Type.fromPrefix(decoded.prefix)); + assertArrayEquals(bytes, decoded.bytes); } @Test @@ -109,42 +109,37 @@ public void testEncodeDecode() throws Exception { char[] encoded = NKey.encode(NKey.Type.ACCOUNT, bytes); byte[] decoded = NKey.decode(NKey.Type.ACCOUNT, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); + assertArrayEquals(bytes, decoded); encoded = NKey.encode(NKey.Type.USER, bytes); decoded = NKey.decode(NKey.Type.USER, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); + assertArrayEquals(bytes, decoded); encoded = NKey.encode(NKey.Type.SERVER, bytes); decoded = NKey.decode(NKey.Type.SERVER, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); + assertArrayEquals(bytes, decoded); encoded = NKey.encode(NKey.Type.CLUSTER, bytes); decoded = NKey.decode(NKey.Type.CLUSTER, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); + assertArrayEquals(bytes, decoded); } @Test - public void testDecodeWrongType() { - assertThrows(IllegalArgumentException.class, () -> { - byte[] bytes = new byte[32]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - - char[] encoded = NKey.encode(NKey.Type.ACCOUNT, bytes); - NKey.decode(NKey.Type.USER, encoded, false); - }); + public void testDecodeWrongType() throws IOException { + byte[] bytes = new byte[32]; + SecureRandom random = new SecureRandom(); + random.nextBytes(bytes); + + char[] encoded = NKey.encode(NKey.Type.ACCOUNT, bytes); + assertThrows(IllegalArgumentException.class, () -> NKey.decode(NKey.Type.USER, encoded, false)); } @Test public void testEncodeSeedSize() { - assertThrows(IllegalArgumentException.class, () -> { - byte[] bytes = new byte[48]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - - NKey.encodeSeed(NKey.Type.ACCOUNT, bytes); - }); + byte[] bytes = new byte[48]; + SecureRandom random = new SecureRandom(); + random.nextBytes(bytes); + assertThrows(IllegalArgumentException.class, () -> NKey.encodeSeed(NKey.Type.ACCOUNT, bytes)); } @Test @@ -196,15 +191,15 @@ public void testAccount() throws Exception { assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'A'); + assertEquals('A', publicKey[0]); char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); + assertEquals('P', privateKey[0]); byte[] data = "Synadia".getBytes(StandardCharsets.UTF_8); byte[] sig = theKey.sign(data); - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); + assertEquals(ED25519_SIGNATURE_SIZE, sig.length); assertTrue(theKey.verify(data, sig)); @@ -230,15 +225,15 @@ public void testUser() throws Exception { assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'U'); + assertEquals('U', publicKey[0]); char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); + assertEquals('P', privateKey[0]); byte[] data = "Mister Zero".getBytes(StandardCharsets.UTF_8); byte[] sig = theKey.sign(data); - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); + assertEquals(ED25519_SIGNATURE_SIZE, sig.length); assertTrue(theKey.verify(data, sig)); @@ -264,15 +259,15 @@ public void testCluster() throws Exception { assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'C'); + assertEquals('C', publicKey[0]); char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); + assertEquals('P', privateKey[0]); byte[] data = "Connect Everything".getBytes(StandardCharsets.UTF_8); byte[] sig = theKey.sign(data); - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); + assertEquals(ED25519_SIGNATURE_SIZE, sig.length); assertTrue(theKey.verify(data, sig)); @@ -298,15 +293,15 @@ public void testOperator() throws Exception { assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'O'); + assertEquals('O', publicKey[0]); char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); + assertEquals('P', privateKey[0]); byte[] data = "Connect Everything".getBytes(StandardCharsets.UTF_8); byte[] sig = theKey.sign(data); - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); + assertEquals(ED25519_SIGNATURE_SIZE, sig.length); assertTrue(theKey.verify(data, sig)); @@ -332,15 +327,15 @@ public void testServer() throws Exception { assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'N'); + assertEquals('N', publicKey[0]); char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); + assertEquals('P', privateKey[0]); byte[] data = "Polaris and Pluto".getBytes(StandardCharsets.UTF_8); byte[] sig = theKey.sign(data); - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); + assertEquals(ED25519_SIGNATURE_SIZE, sig.length); assertTrue(theKey.verify(data, sig)); @@ -379,54 +374,44 @@ public void testPublicOnly() throws Exception { assertNotEquals(otherKey, theKey); assertNotEquals(otherKey, pubOnly); - assertNotEquals(pubOnly.getPublicKey()[0], '\0'); + assertNotEquals('\0', pubOnly.getPublicKey()[0]); pubOnly.clear(); - assertEquals(pubOnly.getPublicKey()[0], '\0'); + assertEquals('\0', pubOnly.getPublicKey()[0]); } @Test - public void testPublicOnlyCantSign() { - assertThrows(IllegalStateException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); - - byte[] data = "Public and Private".getBytes(StandardCharsets.UTF_8); - pubOnly.sign(data); - }); + public void testPublicOnlyCantSign() throws Exception { + NKey theKey = NKey.createUser(null); + NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); + + byte[] data = "Public and Private".getBytes(StandardCharsets.UTF_8); + assertThrows(IllegalStateException.class, () -> pubOnly.sign(data)); } @Test - public void testPublicOnlyCantProvideSeed() { - assertThrows(IllegalStateException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); - pubOnly.getSeed(); - }); + public void testPublicOnlyCantProvideSeed() throws Exception { + NKey theKey = NKey.createUser(null); + NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); + assertThrows(IllegalStateException.class, pubOnly::getSeed); } @Test - public void testPublicOnlyCantProvidePrivate() { - assertThrows(IllegalStateException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); - pubOnly.getPrivateKey(); - }); + public void testPublicOnlyCantProvidePrivate() throws Exception { + NKey theKey = NKey.createUser(null); + NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); + assertThrows(IllegalStateException.class, pubOnly::getPrivateKey); } @Test - public void testPublicFromSeedShouldFail() { - assertThrows(IllegalArgumentException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey.fromPublicKey(theKey.getSeed()); - }); + public void testPublicFromSeedShouldFail() throws Exception { + NKey theKey = NKey.createUser(null); + assertThrows(IllegalArgumentException.class, () -> NKey.fromPublicKey(theKey.getSeed())); } @Test - public void testSeedFromPublicShouldFail() { - assertThrows(IllegalArgumentException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey.fromSeed(theKey.getPublicKey()); - }); + public void testSeedFromPublicShouldFail() throws Exception { + NKey theKey = NKey.createUser(null); + assertThrows(IllegalArgumentException.class, () -> NKey.fromSeed(theKey.getPublicKey())); } @Test @@ -473,7 +458,7 @@ public void testBigSignVerify() throws Exception { byte[] data = Files.readAllBytes(Paths.get("src/test/resources/keystore.jks")); byte[] sig = theKey.sign(data); - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); + assertEquals(ED25519_SIGNATURE_SIZE, sig.length); assertTrue(theKey.verify(data, sig)); char[] publicKey = theKey.getPublicKey(); @@ -517,7 +502,7 @@ public void testInterop() throws Exception { NKey fromSeed = NKey.fromSeed(seed); NKey fromPublicKey = NKey.fromPublicKey(publicKey); - assertEquals(fromSeed.getType(), NKey.Type.USER); + assertEquals(NKey.Type.USER, fromSeed.getType()); byte[] nonceData = base64UrlDecode(nonce); byte[] nonceSig = base64UrlDecode(nonceEncodedSig); @@ -562,7 +547,7 @@ public void testTypeEnum() { assertEquals(NKey.Type.OPERATOR, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_OPERATOR)); assertEquals(NKey.Type.CLUSTER, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_CLUSTER)); assertEquals(NKey.Type.ACCOUNT, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_PRIVATE)); - assertThrows(IllegalArgumentException.class, () -> { NKey.Type ignored = NKey.Type.fromPrefix(9999); }); + assertThrows(IllegalArgumentException.class, () -> NKey.Type.fromPrefix(9999)); } @Test @@ -583,19 +568,16 @@ public void testEquals() throws Exception { NKey key = NKey.createServer(null); assertEquals(key, key); assertEquals(key, NKey.fromSeed(key.getSeed())); - assertNotEquals(key, new Object()); + assertNotEquals(new Object(), key); assertNotEquals(key, NKey.createServer(null)); assertNotEquals(key, NKey.createAccount(null)); } @Test - public void testClear() { - assertThrows(IllegalArgumentException.class, () -> { - NKey key = NKey.createServer(null); - key.clear(); - key.getPrivateKey(); - - }, "Invalid encoding"); + public void testClear() throws Exception { + NKey key = NKey.createServer(null); + key.clear(); + assertThrows(IllegalArgumentException.class, key::getPrivateKey); } @Test diff --git a/src/test/java/io/nats/client/NUIDTests.java b/src/test/java/io/nats/client/NUIDTests.java index 4bed73d2f..e4e5d3cef 100644 --- a/src/test/java/io/nats/client/NUIDTests.java +++ b/src/test/java/io/nats/client/NUIDTests.java @@ -27,7 +27,7 @@ public class NUIDTests { @Test public void testDigits() { - assertEquals(NUID.digits.length, NUID.base, "digits length does not match base modulo"); + assertEquals(NUID.base, NUID.digits.length, "digits length does not match base modulo"); } @Test @@ -104,6 +104,7 @@ public void whenRandomizePrefixCalledDirectlyThenPrefixIsRandomized() { public void whenMultipleThreadsIncrementSequenceThenNoCollisionsOccur() throws InterruptedException { NUID nuid = NUID.getInstance(); Set sequences = ConcurrentHashMap.newKeySet(); + //noinspection resource ExecutorService service = Executors.newFixedThreadPool(10); // This test checks the thread-safety of nextSequence by generating sequences from multiple threads. @@ -116,6 +117,7 @@ public void whenMultipleThreadsIncrementSequenceThenNoCollisionsOccur() throws I } service.shutdown(); + //noinspection ResultOfMethodCallIgnored service.awaitTermination(1, TimeUnit.MINUTES); // All sequences should be unique, verifying no sequence collision occurs when accessed by multiple threads. diff --git a/src/test/java/io/nats/client/NatsServerProtocolMock.java b/src/test/java/io/nats/client/NatsServerProtocolMock.java index 3ed9465fd..9092a3c6d 100644 --- a/src/test/java/io/nats/client/NatsServerProtocolMock.java +++ b/src/test/java/io/nats/client/NatsServerProtocolMock.java @@ -24,7 +24,7 @@ * Handles the begining of the connect sequence, all hard coded, but * is configurable to fail at specific points to allow client testing. */ -public class NatsServerProtocolMock implements Closeable{ +public class NatsServerProtocolMock implements Closeable, TestServer { // Default is to exit after pong public enum ExitAt { @@ -49,11 +49,11 @@ public enum Progress { } public interface Customizer { - public void customizeTest(NatsServerProtocolMock ts, BufferedReader reader, PrintWriter writer); + void customizeTest(NatsServerProtocolMock mockTs, BufferedReader reader, PrintWriter writer); } - private int port; - private ExitAt exitAt; + private final int port; + private final ExitAt exitAt; private Progress progress; private boolean protocolFailure; private CompletableFuture waitForIt; @@ -105,7 +105,7 @@ public NatsServerProtocolMock(Customizer custom, String customInfo) throws IOExc private void start() { this.progress = Progress.NO_CLIENT; this.waitForIt = new CompletableFuture<>(); - Thread t = new Thread(() -> {accept();}); + Thread t = new Thread(this::accept); t.start(); try { Thread.sleep(100); @@ -122,12 +122,14 @@ public void useCustomInfoAsFullInfo() { customInfoIsFullInfo = true; } + @Override public int getPort() { return port; } - public String getURI() { - return "nats://localhost:" + this.getPort(); + @Override + public String getServerUri() { + return "nats://0.0.0.0:" + port; } public Progress getProgress() { @@ -239,7 +241,7 @@ public void accept() { } catch (IOException io) { protocolFailure = true; // System.out.println("\n*** Mock Server @" + this.port + " got exception "+io.getMessage()); - io.printStackTrace(); + // io.printStackTrace(); } catch (Exception ex) { // System.out.println("\n*** Mock Server @" + this.port + " got exception "+ex.getMessage()); diff --git a/src/test/java/io/nats/client/NatsTestServer.java b/src/test/java/io/nats/client/NatsTestServer.java index 0a4ae36ed..4f331fa18 100644 --- a/src/test/java/io/nats/client/NatsTestServer.java +++ b/src/test/java/io/nats/client/NatsTestServer.java @@ -20,69 +20,54 @@ import java.io.IOException; import java.util.logging.Level; -public class NatsTestServer extends NatsServerRunner { +import static io.nats.client.utils.ResourceUtils.configResource; + +public class NatsTestServer extends NatsServerRunner implements TestServer { + static { NatsTestServer.quiet(); - NatsServerRunner.setDefaultOutputSupplier(ConsoleOutput::new); - NatsServerRunner.setDefaultValidateTries(5); - NatsServerRunner.setDefaultInitialValidateDelay(100); - NatsServerRunner.setDefaultSubsequentValidateDelay(50); - } + NatsRunnerUtils.setDefaultConnectValidateTries(10); + NatsRunnerUtils.setDefaultConnectValidateTimeout(200); + NatsRunnerUtils.setDefaultOutputSupplier(ConsoleOutput::new); } public static void quiet() { - NatsServerRunner.setDefaultOutputLevel(Level.WARNING); + NatsRunnerUtils.setDefaultOutputLevel(Level.WARNING); } public static void verbose() { - NatsServerRunner.setDefaultOutputLevel(Level.ALL); - } - - public NatsTestServer() throws IOException { - super(); - } - - public NatsTestServer(boolean debug) throws IOException { - super(debug); + NatsRunnerUtils.setDefaultOutputLevel(Level.ALL); } - public NatsTestServer(boolean debug, boolean jetstream) throws IOException { - super(debug, jetstream); + public static Builder configFileBuilder(String configFilePath) { + return NatsServerRunner.builder().configFilePath(configResource(configFilePath)); } - public NatsTestServer(int port, boolean debug) throws IOException { - super(port, debug); - } - - public NatsTestServer(int port, boolean debug, boolean jetstream) throws IOException { - super(port, debug, jetstream); - } - - public NatsTestServer(String configFilePath, boolean debug) throws IOException { - super(configFilePath, debug); + public NatsTestServer() throws IOException { + this(builder()); } - public NatsTestServer(String configFilePath, boolean debug, boolean jetstream) throws IOException { - super(configFilePath, debug, jetstream); + public NatsTestServer(int port) throws IOException { + this(builder().port(port)); } - public NatsTestServer(String configFilePath, String[] configInserts, int port, boolean debug) throws IOException { - super(configFilePath, configInserts, port, debug); + public NatsTestServer(int port, boolean jetstream) throws IOException { + this(builder().port(port).jetstream(jetstream)); } - public NatsTestServer(String configFilePath, int port, boolean debug) throws IOException { - super(configFilePath, port, debug); + public NatsTestServer(String configFilePath, String[] configInserts, int port) throws IOException { + this(builder().configFilePath(configResource(configFilePath)).configInserts(configInserts).port(port)); } - public NatsTestServer(String[] customArgs, boolean debug) throws IOException { - super(customArgs, debug); + public NatsTestServer(String[] customArgs) throws IOException { + this(builder().customArgs(customArgs)); } - public NatsTestServer(String[] customArgs, int port, boolean debug) throws IOException { - super(customArgs, port, debug); + public NatsTestServer(String[] customArgs, int port) throws IOException { + this(builder().customArgs(customArgs).port(port)); } - public NatsTestServer(int port, boolean debug, boolean jetstream, String configFilePath, String[] configInserts, String[] customArgs) throws IOException { - super(port, debug, jetstream, configFilePath, configInserts, customArgs); + public NatsTestServer(int port, boolean jetstream, String configFilePath, String[] configInserts, String[] customArgs) throws IOException { + this(builder().port(port).jetstream(jetstream).configFilePath(configResource(configFilePath)).configInserts(configInserts).customArgs(customArgs)); } public NatsTestServer(Builder b) throws IOException { @@ -97,15 +82,24 @@ public String getLocalhostUri(String schema) { return NatsRunnerUtils.getLocalhostUri(schema, getPort()); } - public String getNatsLocalhostUri() { + @Override + public String getServerUri() { return NatsRunnerUtils.getNatsLocalhostUri(getPort()); } - public static String getNatsLocalhostUri(int port) { + public static String getLocalhostUri(int port) { return NatsRunnerUtils.getNatsLocalhostUri(port); } public static String getLocalhostUri(String schema, int port) { return NatsRunnerUtils.getLocalhostUri(schema, port); } + + public static String[] getLocalhostUris(String schema, NatsTestServer... servers) { + String[] results = new String[servers.length]; + for (int x = 0; x < servers.length; x++) { + results[x] = NatsRunnerUtils.getLocalhostUri(schema, servers[x].getPort()); + } + return results; + } } diff --git a/src/test/java/io/nats/client/OptionsTests.java b/src/test/java/io/nats/client/OptionsTests.java index 3b6151c92..caa3b76b8 100644 --- a/src/test/java/io/nats/client/OptionsTests.java +++ b/src/test/java/io/nats/client/OptionsTests.java @@ -16,6 +16,7 @@ import io.nats.client.ConnectionListener.Events; import io.nats.client.impl.*; import io.nats.client.support.HttpRequest; +import io.nats.client.support.Listener; import io.nats.client.support.NatsUri; import io.nats.client.support.ssl.SslTestingHelper; import io.nats.client.utils.CloseOnUpgradeAttempt; @@ -40,6 +41,7 @@ import static io.nats.client.Options.*; import static io.nats.client.support.Encoding.base64UrlEncodeToString; import static io.nats.client.support.NatsConstants.DEFAULT_PORT; +import static io.nats.client.utils.ResourceUtils.jwtResource; import static org.junit.jupiter.api.Assertions.*; public class OptionsTests { @@ -227,12 +229,8 @@ private static void _testChainedDurationOptions(Options o) { @Test public void testHttpRequestInterceptors() { - java.util.function.Consumer interceptor1 = req -> { - req.getHeaders().add("Test1", "Header"); - }; - java.util.function.Consumer interceptor2 = req -> { - req.getHeaders().add("Test2", "Header"); - }; + java.util.function.Consumer interceptor1 = req -> req.getHeaders().add("Test1", "Header"); + java.util.function.Consumer interceptor2 = req -> req.getHeaders().add("Test2", "Header"); Options o = new Options.Builder() .httpRequestInterceptor(interceptor1) .httpRequestInterceptor(interceptor2) @@ -497,7 +495,7 @@ public void testProperties() throws Exception { props.setProperty(Options.PROP_CONNECTION_NAME, "name"); // stringProperty builds an auth handler - props.setProperty(Options.PROP_CREDENTIAL_PATH, "src/test/resources/jwt_nkey/test.creds"); + props.setProperty(Options.PROP_CREDENTIAL_PATH, jwtResource("test.creds")); // charArrayProperty props.setProperty(Options.PROP_USERNAME, "user"); @@ -737,31 +735,38 @@ public void testPropertiesSubjectValidationType() { @Test public void testPropertyErrorListener() { Properties props = new Properties(); - props.setProperty(Options.PROP_ERROR_LISTENER, ListenerForTesting.class.getCanonicalName()); + props.setProperty(Options.PROP_ERROR_LISTENER, Listener.class.getCanonicalName()); Options o = new Options.Builder(props).build(); assertFalse(o.isVerbose(), "default verbose"); // One from a different type assertNotNull(o.getErrorListener(), "property error listener"); o.getErrorListener().errorOccurred(null, "bad subject"); - assertEquals(1, ((ListenerForTesting) o.getErrorListener()).getCount(), "property error listener class"); + assertEquals(0, ((Listener) o.getErrorListener()).getExceptionCount(), "property error listener class"); } @SuppressWarnings("deprecation") @Test public void testPropertyConnectionListeners() { Properties props = new Properties(); - props.setProperty(Options.PROP_CONNECTION_CB, ListenerForTesting.class.getCanonicalName()); + props.setProperty(Options.PROP_CONNECTION_CB, Listener.class.getCanonicalName()); Options o = new Options.Builder(props).build(); assertFalse(o.isVerbose(), "default verbose"); // One from a different type assertNotNull(o.getConnectionListener(), "property connection listener"); + Listener listener = ((Listener) o.getConnectionListener()); + listener.queueConnectionEvent(Events.DISCONNECTED); o.getConnectionListener().connectionEvent(null, Events.DISCONNECTED); + listener.validate(); + + listener.queueConnectionEvent(Events.RECONNECTED); o.getConnectionListener().connectionEvent(null, Events.RECONNECTED); - o.getConnectionListener().connectionEvent(null, Events.CLOSED); + listener.validate(); - assertEquals(3, ((ListenerForTesting) o.getConnectionListener()).getCount(), "property connect listener class"); + listener.queueConnectionEvent(Events.CLOSED); + o.getConnectionListener().connectionEvent(null, Events.CLOSED); + listener.validate(); } @Test @@ -1114,20 +1119,16 @@ private void assertServersAndUnprocessed(boolean two, Options o) { @Test public void testBadClassInPropertyConnectionListeners() { - assertThrows(IllegalArgumentException.class, () -> { - Properties props = new Properties(); - props.setProperty(Options.PROP_CONNECTION_CB, "foo"); - new Options.Builder(props); - }); + Properties props = new Properties(); + props.setProperty(Options.PROP_CONNECTION_CB, "foo"); + assertThrows(IllegalArgumentException.class, () -> new Options.Builder(props)); } @Test public void testBadClassInPropertyStatisticsCollector() { - assertThrows(IllegalArgumentException.class, () -> { - Properties props = new Properties(); - props.setProperty(Options.PROP_STATISTICS_COLLECTOR, "foo"); - new Options.Builder(props); - }); + Properties props = new Properties(); + props.setProperty(Options.PROP_STATISTICS_COLLECTOR, "foo"); + assertThrows(IllegalArgumentException.class, () -> new Options.Builder(props)); } @Test @@ -1144,10 +1145,8 @@ public void testThrowOnBadServerURI() { @Test public void testThrowOnBadServersURI() { - assertThrows(IllegalArgumentException.class, () -> { - String[] serverUrls = {URL_PROTO_HOST_PORT_8080, "foo:/bar\\:blammer"}; - new Options.Builder().servers(serverUrls).build(); - }); + String[] serverUrls = {URL_PROTO_HOST_PORT_8080, "foo:/bar\\:blammer"}; + assertThrows(IllegalArgumentException.class, () -> new Builder().servers(serverUrls).build()); } @Test @@ -1176,9 +1175,8 @@ public void testCallbackExecutor() throws ExecutionException, InterruptedExcepti Options options = new Options.Builder() .callbackThreadFactory(threadFactory) .build(); - Future callbackFuture = options.getCallbackExecutor().submit(() -> { - assertEquals("test", Thread.currentThread().getName()); - }); + Future callbackFuture = options.getCallbackExecutor().submit( + () -> assertEquals("test", Thread.currentThread().getName())); callbackFuture.get(5, TimeUnit.SECONDS); } @@ -1188,9 +1186,8 @@ public void testConnectExecutor() throws ExecutionException, InterruptedExceptio Options options = new Options.Builder() .connectThreadFactory(threadFactory) .build(); - Future connectFuture = options.getConnectExecutor().submit(() -> { - assertEquals("test", Thread.currentThread().getName()); - }); + Future connectFuture = options.getConnectExecutor().submit( + () -> assertEquals("test", Thread.currentThread().getName())); connectFuture.get(5, TimeUnit.SECONDS); } diff --git a/src/test/java/io/nats/client/PublishOptionsTests.java b/src/test/java/io/nats/client/PublishOptionsTests.java index a11053772..93867a424 100644 --- a/src/test/java/io/nats/client/PublishOptionsTests.java +++ b/src/test/java/io/nats/client/PublishOptionsTests.java @@ -97,12 +97,13 @@ public void testBuilder() { @Test public void testProperties() { + String stream = random(); Properties p = new Properties(); p.setProperty(PublishOptions.PROP_PUBLISH_TIMEOUT, "PT20M"); - p.setProperty(PublishOptions.PROP_STREAM_NAME, STREAM); + p.setProperty(PublishOptions.PROP_STREAM_NAME, stream); PublishOptions po = new PublishOptions.Builder(p).build(); //noinspection deprecation - assertEquals(STREAM, po.getStream(), "stream foo"); + assertEquals(stream, po.getStream(), "stream foo"); assertEquals(Duration.ofMinutes(20), po.getStreamTimeout(), "20M timeout"); p = new Properties(); diff --git a/src/test/java/io/nats/client/PublishTests.java b/src/test/java/io/nats/client/PublishTests.java index 120c44c72..4deb14a2e 100644 --- a/src/test/java/io/nats/client/PublishTests.java +++ b/src/test/java/io/nats/client/PublishTests.java @@ -13,122 +13,93 @@ package io.nats.client; -import io.nats.client.api.StreamConfiguration; +import io.nats.client.ConnectionListener.Events; import io.nats.client.impl.Headers; +import io.nats.client.impl.JetStreamTestingContext; import io.nats.client.impl.NatsMessage; +import io.nats.client.support.Listener; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.net.SocketException; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import static io.nats.client.support.NatsConstants.*; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static io.nats.client.utils.ResourceUtils.dataAsLines; -import static io.nats.client.utils.TestBase.*; import static org.junit.jupiter.api.Assertions.*; -public class PublishTests { +public class PublishTests extends TestBase { @Test - public void throwsIfClosedOnPublish() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.close(); - nc.publish("subject", "replyto", null); - fail(); - } - }); - } + public void throwsIfClosed() throws Exception { + runInSharedOwnNc(nc -> { + nc.close(); + // can't publish after close + assertThrows(IllegalStateException.class, () -> nc.publish(random(), random(), null)); - @Test - public void throwsIfClosedOnFlush() { - assertThrows(TimeoutException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.close(); - nc.flush(null); - fail(); - } + // flush after close always times out + assertThrows(TimeoutException.class, () -> nc.flush(null)); + + // a normal api call after close + assertThrows(IOException.class, nc::RTT); }); } @Test - public void testThrowsWithoutSubject() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.publish(null, null); - fail(); - } + public void testThrowsWithoutSubject() throws Exception { + runInShared(nc -> { + //noinspection DataFlowIssue + assertThrows(IllegalArgumentException.class, () -> nc.publish(null, null)); }); } - @Test public void testThrowsIfTooBig() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/max_payload.conf", false, false)) - { - Connection nc = Nats.connect(ts.getURI()); - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - byte[] body1001 = new byte[1001]; - assertThrows(IllegalArgumentException.class, () -> nc.publish("subject", null, null, body1001)); + byte[] body1001 = new byte[1024]; // 1024 is > than max_payload.conf max_payload: 1000 - byte[] body977 = new byte[977]; - Headers h = new Headers(); - h.put("abcd", "12345"); // NATS/1.0\r\nabcd:12345\r\n\r\n 24 characters 971 + 24 = 1001 - assertThrows(IllegalArgumentException.class, () -> nc.publish("subject", null, h, body977)); + byte[] body977 = new byte[977]; + Headers h = new Headers(); + h.put("abcd", "12345"); // NATS/1.0\r\nabcd:12345\r\n\r\n 24 characters 971 + 24 = 1001 - nc.close(); - - AtomicBoolean mpv = new AtomicBoolean(false); - AtomicBoolean se = new AtomicBoolean(false); - ErrorListener el = new ErrorListener() { - @Override - public void errorOccurred(Connection conn, String error) { - mpv.set(error.contains("Maximum Payload Violation")); - } + runInConfiguredServer("max_payload.conf", ts -> { + try (Connection nc = managedConnect(options(ts))) { + assertThrows(IllegalArgumentException.class, () -> nc.publish(random(), null, null, body1001)); + assertThrows(IllegalArgumentException.class, () -> nc.publish("subject", null, h, body977)); + } - @Override - public void exceptionOccurred(Connection conn, Exception exp) { - se.set(exp instanceof SocketException); - } - }; - Options options = Options.builder() - .server(ts.getURI()) + Listener listener = new Listener(); + Options options = optionsBuilder(ts) .clientSideLimitChecks(false) - .errorListener(el) + .errorListener(listener) .build(); - Connection nc2 = Nats.connect(options); - assertSame(Connection.Status.CONNECTED, nc2.getStatus(), "Connected Status"); - nc2.publish("subject", null, null, body1001); - - sleep(250); - assertTrue(mpv.get()); - assertTrue(se.get()); - } + try (Connection nc = managedConnect(options)) { + listener.queueError("Maximum Payload Violation"); + listener.queueException(SocketException.class); + nc.publish(random(), null, null, body1001); + listener.validateForAny(); // sometimes the exception comes in before the error and the error never comes, so validate for either. + } + }); } @Test - public void testThrowsIfheadersNotSupported() { - assertThrows(IllegalArgumentException.class, () -> { - String customInfo = "{\"server_id\":\"test\", \"version\":\"9.9.99\"}"; - - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(null, customInfo); - Connection nc = Nats.connect(ts.getURI())) { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - nc.publish(NatsMessage.builder() + public void testThrowsIfHeadersNotSupported() throws Exception { + String customInfo = "{\"server_id\":\"test\", \"version\":\"9.9.99\"}"; + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(null, customInfo)) { + try (Connection nc = standardConnect(mockTs)) { + assertThrows(IllegalArgumentException.class, + () -> nc.publish(NatsMessage.builder() .subject("testThrowsIfheadersNotSupported") .headers(new Headers().add("key", "value")) - .build()); - fail(); + .build())); } - }); + } } @Test @@ -168,7 +139,7 @@ private void runSimplePublishTest(String subject, String replyTo, Headers header StringBuilder headerLine; String bodyLine; - System.out.println("*** Mock Server @" + ts.getPort() + " waiting for " + proto + " ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " waiting for " + proto + " ..."); try { pubLine = r.readLine(); if (hPub) { @@ -188,7 +159,7 @@ private void runSimplePublishTest(String subject, String replyTo, Headers header } if (pubLine.startsWith(proto)) { - System.out.println("*** Mock Server @" + ts.getPort() + " got " + proto + " ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " got " + proto + " ..."); protocol.set(pubLine); hdrProto.set(headerLine.toString()); body.set(bodyLine); @@ -196,84 +167,82 @@ private void runSimplePublishTest(String subject, String replyTo, Headers header } }; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(receiveMessageCustomizer); - Connection nc = standardConnection(ts.getURI())) { - - byte[] bodyBytes; - if (bodyString == null || bodyString.isEmpty()) { - bodyBytes = EMPTY_BODY; - bodyString = ""; - } - else { - bodyBytes = bodyString.getBytes(StandardCharsets.UTF_8); - } + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(receiveMessageCustomizer)) { + try (Connection nc = standardConnect(mockTs)) { + byte[] bodyBytes; + if (bodyString == null || bodyString.isEmpty()) { + bodyBytes = EMPTY_BODY; + bodyString = ""; + } + else { + bodyBytes = bodyString.getBytes(StandardCharsets.UTF_8); + } - nc.publish(NatsMessage.builder().subject(subject).replyTo(replyTo).headers(headers).data(bodyBytes).build()); + nc.publish(NatsMessage.builder().subject(subject).replyTo(replyTo).headers(headers).data(bodyBytes).build()); - assertTrue(gotPub.get(), "Got " + proto + "."); //wait for receipt to close up - standardCloseConnection(nc); + assertTrue(gotPub.get(), "Got " + proto + "."); //wait for receipt to close up + closeAndConfirm(nc); - if (proto.equals(OP_PUB)) { - String expectedProtocol; - if (replyTo == null) { - expectedProtocol = proto + " " + subject + " " + bodyBytes.length; - } else { - expectedProtocol = proto + " " + subject + " " + replyTo + " " + bodyBytes.length; + if (proto.equals(OP_PUB)) { + String expectedProtocol; + if (replyTo == null) { + expectedProtocol = proto + " " + subject + " " + bodyBytes.length; + } + else { + expectedProtocol = proto + " " + subject + " " + replyTo + " " + bodyBytes.length; + } + assertEquals(expectedProtocol, protocol.get(), "Protocol matches"); + assertEquals(bodyString, body.get(), "Body matches"); } - assertEquals(expectedProtocol, protocol.get(), "Protocol matches"); - assertEquals(bodyString, body.get(), "Body matches"); - } - else { - String expectedProtocol; - int hdrLen = headers.serializedLength(); - int totLen = hdrLen + bodyBytes.length; - if (replyTo == null) { - expectedProtocol = proto + " " + subject + " " + hdrLen + " " + totLen; - } else { - expectedProtocol = proto + " " + subject + " " + replyTo + " " + hdrLen + " " + totLen; + else { + String expectedProtocol; + int hdrLen = headers.serializedLength(); + int totLen = hdrLen + bodyBytes.length; + if (replyTo == null) { + expectedProtocol = proto + " " + subject + " " + hdrLen + " " + totLen; + } + else { + expectedProtocol = proto + " " + subject + " " + replyTo + " " + hdrLen + " " + totLen; + } + assertEquals(expectedProtocol, protocol.get(), "Protocol matches"); + assertEquals(bodyString, body.get(), "Body matches"); + assertEquals(new String(headers.getSerialized()), hdrProto.get()); } - assertEquals(expectedProtocol, protocol.get(), "Protocol matches"); - assertEquals(bodyString, body.get(), "Body matches"); - assertEquals(new String(headers.getSerialized()), hdrProto.get()); } } } @Test public void testMaxPayload() throws Exception { - runInServer(standardOptionsBuilder().noReconnect(), nc -> { + runInSharedOwnNc(optionsBuilder().noReconnect(), nc -> { int maxPayload = (int)nc.getServerInfo().getMaxPayload(); - nc.publish("mptest", new byte[maxPayload-1]); - nc.publish("mptest", new byte[maxPayload]); + nc.publish(random(), new byte[maxPayload - 1]); + assertThrows(IllegalArgumentException.class, () -> nc.publish(random(), new byte[maxPayload + 1])); }); + } - try { - runInServer(standardOptionsBuilder().noReconnect().clientSideLimitChecks(false), nc -> { - int maxPayload = (int)nc.getServerInfo().getMaxPayload(); - for (int x = 1; x < 1000; x++) { - nc.publish("mptest", new byte[maxPayload + x]); - } - }); - fail("Expecting IllegalStateException"); - } - catch (IllegalStateException ignore) {} - - try { - runInServer(standardOptionsBuilder().noReconnect(), nc -> { - int maxPayload = (int)nc.getServerInfo().getMaxPayload(); - for (int x = 1; x < 1000; x++) { - nc.publish("mptest", new byte[maxPayload + x]); - } - }); - fail("Expecting IllegalArgumentException"); - } - catch (IllegalArgumentException ignore) {} + @Test + public void testMaxPayloadNoClientSideLimitChecks() throws Exception { + Listener listener = new Listener(); + Options.Builder builder = optionsBuilder() + .noReconnect() + .clientSideLimitChecks(false) + .errorListener(listener) + .connectionListener(listener); + + runInSharedOwnNc(builder, nc -> { + listener.queueError("Maximum Payload Violation"); + listener.queueConnectionEvent(Events.DISCONNECTED); + int maxPayload = (int)nc.getServerInfo().getMaxPayload(); + nc.publish(random(), new byte[maxPayload + 1]); + listener.validateAll(); + }); } @Test public void testUtf8Subjects() throws Exception { - String subject = dataAsLines("utf8-test-strings.txt").get(0); - String jsSubject = variant() + "-" + subject; // just to have a different; + String utfSubject = dataAsLines("utf8-test-strings.txt").get(0); + String jsSubject = random() + "-" + utfSubject; // just to have a different; AtomicReference coreReceivedSubjectNotSupported = new AtomicReference<>(); AtomicReference coreReceivedSubjectWhenSupported = new AtomicReference<>(); @@ -284,59 +253,52 @@ public void testUtf8Subjects() throws Exception { CountDownLatch jsReceivedLatchNotSupported = new CountDownLatch(1); CountDownLatch jsReceivedLatchWhenSupported = new CountDownLatch(1); - try (NatsTestServer ts = new NatsTestServer(false, true); - Connection ncNotSupported = standardConnection(standardOptionsBuilder(ts.getURI()).build()); - Connection ncSupported = standardConnection(standardOptionsBuilder(ts.getURI()).supportUTF8Subjects().build())) - { - try { - ncNotSupported.jetStreamManagement().addStream( - StreamConfiguration.builder() - .name(stream()) - .subjects(jsSubject) - .build()); - JetStream jsNotSupported = ncNotSupported.jetStream(); - JetStream jsSupported = ncNotSupported.jetStream(); - - Dispatcher dNotSupported = ncNotSupported.createDispatcher(); - Dispatcher dSupported = ncSupported.createDispatcher(); - - dNotSupported.subscribe(subject, m -> { - coreReceivedSubjectNotSupported.set(m.getSubject()); - coreReceivedLatchNotSupported.countDown(); - }); - - dSupported.subscribe(subject, m -> { - coreReceivedSubjectWhenSupported.set(m.getSubject()); - coreReceivedLatchWhenSupported.countDown(); - }); - - jsNotSupported.subscribe(jsSubject, dNotSupported, m -> { - jsReceivedSubjectNotSupported.set(m.getSubject()); - jsReceivedLatchNotSupported.countDown(); - }, false); - - jsSupported.subscribe(jsSubject, dSupported, m -> { - jsReceivedSubjectWhenSupported.set(m.getSubject()); - jsReceivedLatchWhenSupported.countDown(); - }, false); - - ncNotSupported.publish(subject, null); // demonstrates that publishing always does utf8 - jsSupported.publish(jsSubject, null); - - assertTrue(coreReceivedLatchNotSupported.await(1, TimeUnit.SECONDS)); - assertTrue(coreReceivedLatchWhenSupported.await(1, TimeUnit.SECONDS)); - assertTrue(jsReceivedLatchNotSupported.await(1, TimeUnit.SECONDS)); - assertTrue(jsReceivedLatchWhenSupported.await(1, TimeUnit.SECONDS)); - - assertNotEquals(subject, coreReceivedSubjectNotSupported.get()); - assertEquals(subject, coreReceivedSubjectWhenSupported.get()); - assertNotEquals(jsSubject, jsReceivedSubjectNotSupported.get()); - assertEquals(jsSubject, jsReceivedSubjectWhenSupported.get()); - - } - finally { - cleanupJs(ncSupported); + Options.Builder ncNotSupportedOptionsBuilder = optionsBuilder().noReconnect().clientSideLimitChecks(false); + runInSharedOwnNc(ncNotSupportedOptionsBuilder, ncNotSupported -> { + Options ncSupportedOptions = optionsBuilder(ncNotSupported).supportUTF8Subjects().build(); + try (Connection ncSupported = managedConnect(ncSupportedOptions)) { + try (JetStreamTestingContext ctxNotSupported = new JetStreamTestingContext(ncNotSupported, 0)) { + ctxNotSupported.createOrReplaceStream(jsSubject); + JetStream jsNotSupported = ncNotSupported.jetStream(); + JetStream jsSupported = ncNotSupported.jetStream(); + + Dispatcher dNotSupported = ncNotSupported.createDispatcher(); + Dispatcher dSupported = ncSupported.createDispatcher(); + + dNotSupported.subscribe(utfSubject, m -> { + coreReceivedSubjectNotSupported.set(m.getSubject()); + coreReceivedLatchNotSupported.countDown(); + }); + + dSupported.subscribe(utfSubject, m -> { + coreReceivedSubjectWhenSupported.set(m.getSubject()); + coreReceivedLatchWhenSupported.countDown(); + }); + + jsNotSupported.subscribe(jsSubject, dNotSupported, m -> { + jsReceivedSubjectNotSupported.set(m.getSubject()); + jsReceivedLatchNotSupported.countDown(); + }, false); + + jsSupported.subscribe(jsSubject, dSupported, m -> { + jsReceivedSubjectWhenSupported.set(m.getSubject()); + jsReceivedLatchWhenSupported.countDown(); + }, false); + + ncNotSupported.publish(utfSubject, null); // demonstrates that publishing always does utf8 + jsSupported.publish(jsSubject, null); + + assertTrue(coreReceivedLatchNotSupported.await(1, TimeUnit.SECONDS)); + assertTrue(coreReceivedLatchWhenSupported.await(1, TimeUnit.SECONDS)); + assertTrue(jsReceivedLatchNotSupported.await(1, TimeUnit.SECONDS)); + assertTrue(jsReceivedLatchWhenSupported.await(1, TimeUnit.SECONDS)); + + assertNotEquals(utfSubject, coreReceivedSubjectNotSupported.get()); + assertEquals(utfSubject, coreReceivedSubjectWhenSupported.get()); + assertNotEquals(jsSubject, jsReceivedSubjectNotSupported.get()); + assertEquals(jsSubject, jsReceivedSubjectWhenSupported.get()); + } } - } + }); } } diff --git a/src/test/java/io/nats/client/ReconnectCheck.java b/src/test/java/io/nats/client/ReconnectCheck.java deleted file mode 100644 index 7f979b8e6..000000000 --- a/src/test/java/io/nats/client/ReconnectCheck.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.nats.client; - -import java.io.IOException; -import java.time.Duration; - -/* Program to reproduce #231 */ -public class ReconnectCheck { - private static long received; - private static long published; - - private static long lastReceivedId; - - public static void main(String []args) throws IOException, InterruptedException { - Connection natsIn = Nats.connect(buildOptions("IN")); - Connection natsOut = Nats.connect(buildOptions("OUT")); - - Dispatcher natsDispatcher = natsIn.createDispatcher(m -> { - long receivedId = Long.parseLong(new String(m.getData())); - - if (receivedId < lastReceivedId) { - System.out.println(String.format("##### Tid: %d, Received stale data: got %d, last received %d", Thread.currentThread().getId(), receivedId, lastReceivedId)); - } - lastReceivedId = receivedId; - - if (received++ % 1_000_000 == 0) { - System.out.println(String.format("Tid: %d, Received %d messages", Thread.currentThread().getId(), received)); - } - }); - - natsDispatcher.subscribe("foo"); - - long id = 0; - - while (true) { - for (int i = 0; i < 100_000; i++) { - natsOut.publish("foo", ("" + id++).getBytes()); - if (published++ % 1_000_000 == 0) { - System.out.println(String.format("Tid: %d, Published %d messages.", Thread.currentThread().getId(), published)); - } - } - Thread.sleep(1); - } - } - - private static Options buildOptions(String name) { - Options.Builder natsOptions = new Options.Builder().servers(new String[]{"nats://127.0.0.1:4222"}) - .connectionName(name); - natsOptions.maxReconnects(-1).reconnectWait(Duration.ofSeconds(1)) - .connectionTimeout(Duration.ofSeconds(5)); - natsOptions.pingInterval(Duration.ofMillis(100)); - natsOptions.connectionListener((conn, e) -> - System.out.println(String.format("Tid: %d, %s, NATS: connection event - %s, connected url: %s. servers: %s ", Thread.currentThread().getId(), name, e, conn.getConnectedUrl(), conn.getServers()) - )); - natsOptions.errorListener(new ErrorListener() { - @Override - public void slowConsumerDetected(Connection conn, Consumer consumer) { - System.out.println(String.format("Tid: %d, %s, %s: Slow Consumer", Thread.currentThread().getId(), name, conn.getConnectedUrl())); - } - - @Override - public void exceptionOccurred(Connection conn, Exception exp) { - System.out.println(String.format("Tid: %d, %s, Nats '%s' exception: %s", Thread.currentThread().getId(), name, conn.getConnectedUrl(), exp.toString())); - } - - @Override - public void errorOccurred(Connection conn, String error) { - System.out.println(String.format("Tid: %d, %s, Nats '%s': Error %s", Thread.currentThread().getId(), name, conn.getConnectedUrl(), error.toString())); - } - }); - - // Do not cache any messages when Nats connection is down. - natsOptions.reconnectBufferSize(-1); - return natsOptions.build(); - } -} diff --git a/src/test/java/io/nats/client/RequestTests.java b/src/test/java/io/nats/client/RequestTests.java deleted file mode 100644 index 639e0b2a3..000000000 --- a/src/test/java/io/nats/client/RequestTests.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2020 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client; - -import io.nats.client.api.StorageType; -import io.nats.client.api.StreamConfiguration; -import io.nats.client.impl.ListenerForTesting; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; - -import static io.nats.client.utils.TestBase.standardConnection; -import static io.nats.client.utils.TestBase.subject; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class RequestTests { - - @Test - public void testRequestNoResponder() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false, true)) { - Options optCancel = Options.builder().server(ts.getURI()).errorListener(new ListenerForTesting()).build(); - Options optReport = Options.builder().server(ts.getURI()).reportNoResponders().errorListener(new ListenerForTesting()).build(); - try (Connection ncCancel = standardConnection(optCancel); - Connection ncReport = standardConnection(optReport); - ) - { - assertThrows(CancellationException.class, () -> ncCancel.request(subject(999), null).get()); - ExecutionException ee = assertThrows(ExecutionException.class, () -> ncReport.request(subject(999), null).get()); - assertTrue(ee.getCause() instanceof JetStreamStatusException); - assertTrue(ee.getMessage().contains("503 No Responders Available For Request")); - - ncCancel.jetStreamManagement().addStream( - StreamConfiguration.builder() - .name("testRequestNoResponder").subjects("trnrExists").storageType(StorageType.Memory) - .build()); - - JetStream jsCancel = ncCancel.jetStream(); - JetStream jsReport = ncReport.jetStream(); - - IOException ioe = assertThrows(IOException.class, () -> jsCancel.publish("not-exist", null)); - assertTrue(ioe.getMessage().contains("503")); - ioe = assertThrows(IOException.class, () -> jsReport.publish("trnrNotExist", null)); - assertTrue(ioe.getMessage().contains("503")); - } - } - } -} \ No newline at end of file diff --git a/src/test/java/io/nats/client/SubscribeOptionsTests.java b/src/test/java/io/nats/client/SubscribeOptionsTests.java index 3ca4b61a8..7ba9261f7 100644 --- a/src/test/java/io/nats/client/SubscribeOptionsTests.java +++ b/src/test/java/io/nats/client/SubscribeOptionsTests.java @@ -36,13 +36,16 @@ public void testPushAffirmative() { assertNull(so.getName()); assertNull(so.getDeliverSubject()); + String stream = random(); + String durable = random(); + String deliver = random(); so = PushSubscribeOptions.builder() - .stream(STREAM).durable(DURABLE).deliverSubject(DELIVER).build(); + .stream(stream).durable(durable).deliverSubject(deliver).build(); - assertEquals(STREAM, so.getStream()); - assertEquals(DURABLE, so.getDurable()); - assertEquals(DURABLE, so.getName()); - assertEquals(DELIVER, so.getDeliverSubject()); + assertEquals(stream, so.getStream()); + assertEquals(durable, so.getDurable()); + assertEquals(durable, so.getName()); + assertEquals(deliver, so.getDeliverSubject()); // demonstrate that you can clear the builder so = PushSubscribeOptions.builder() @@ -187,14 +190,16 @@ public void testDeliverSubjectValidation() { @Test public void testPullAffirmative() { + String stream = random(); + String durable = random(); PullSubscribeOptions.Builder builder = PullSubscribeOptions.builder() - .stream(STREAM) - .durable(DURABLE); + .stream(stream) + .durable(durable); PullSubscribeOptions so = builder.build(); - assertEquals(STREAM, so.getStream()); - assertEquals(DURABLE, so.getDurable()); - assertEquals(DURABLE, so.getName()); + assertEquals(stream, so.getStream()); + assertEquals(durable, so.getDurable()); + assertEquals(durable, so.getName()); assertTrue(so.isPull()); assertNotNull(so.toString()); // COVERAGE @@ -211,29 +216,31 @@ public void testPushFieldValidation() { assertThrows(IllegalArgumentException.class, () -> ConsumerConfiguration.builder().name(bad).build()); } - assertClientError(JsConsumerNameDurableMismatch, () -> PushSubscribeOptions.builder().name(NAME).durable(DURABLE).build()); + String name = random(); + String durable = random(); + assertClientError(JsConsumerNameDurableMismatch, () -> PushSubscribeOptions.builder().name(name).durable(durable).build()); // durable directly - PushSubscribeOptions uso = PushSubscribeOptions.builder().durable(DURABLE).build(); - assertEquals(DURABLE, uso.getDurable()); - assertEquals(DURABLE, uso.getName()); - uso = PushSubscribeOptions.builder().name(NAME).build(); + PushSubscribeOptions uso = PushSubscribeOptions.builder().durable(durable).build(); + assertEquals(durable, uso.getDurable()); + assertEquals(durable, uso.getName()); + uso = PushSubscribeOptions.builder().name(name).build(); assertNull(uso.getDurable()); - assertEquals(NAME, uso.getName()); + assertEquals(name, uso.getName()); // in configuration - ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(DURABLE).build(); + ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(durable).build(); uso = PushSubscribeOptions.builder().configuration(cc).build(); - assertEquals(DURABLE, uso.getDurable()); - assertEquals(DURABLE, uso.getName()); - cc = ConsumerConfiguration.builder().name(NAME).build(); + assertEquals(durable, uso.getDurable()); + assertEquals(durable, uso.getName()); + cc = ConsumerConfiguration.builder().name(name).build(); uso = PushSubscribeOptions.builder().configuration(cc).build(); assertNull(uso.getDurable()); - assertEquals(NAME, uso.getName()); + assertEquals(name, uso.getName()); // new helper - ConsumerConfiguration.builder().durable(DURABLE).buildPushSubscribeOptions(); - ConsumerConfiguration.builder().name(NAME).buildPushSubscribeOptions(); + ConsumerConfiguration.builder().durable(durable).buildPushSubscribeOptions(); + ConsumerConfiguration.builder().name(name).buildPushSubscribeOptions(); } @Test @@ -247,87 +254,94 @@ public void testPullFieldValidation() { assertThrows(IllegalArgumentException.class, () -> ConsumerConfiguration.builder().name(bad).build()); } - assertClientError(JsConsumerNameDurableMismatch, () -> PullSubscribeOptions.builder().name(NAME).durable(DURABLE).build()); + String durable = random(); + assertClientError(JsConsumerNameDurableMismatch, () -> PullSubscribeOptions.builder().name(random()).durable(durable).build()); // durable directly - PullSubscribeOptions lso = PullSubscribeOptions.builder().durable(DURABLE).build(); - assertEquals(DURABLE, lso.getDurable()); - assertEquals(DURABLE, lso.getName()); + PullSubscribeOptions lso = PullSubscribeOptions.builder().durable(durable).build(); + assertEquals(durable, lso.getDurable()); + assertEquals(durable, lso.getName()); // in configuration - ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(DURABLE).build(); + ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(durable).build(); lso = PullSubscribeOptions.builder().configuration(cc).build(); - assertEquals(DURABLE, lso.getDurable()); - assertEquals(DURABLE, lso.getName()); + assertEquals(durable, lso.getDurable()); + assertEquals(durable, lso.getName()); // new helper - lso = ConsumerConfiguration.builder().durable(DURABLE).buildPullSubscribeOptions(); - assertEquals(DURABLE, lso.getDurable()); - assertEquals(DURABLE, lso.getName()); + lso = ConsumerConfiguration.builder().durable(durable).buildPullSubscribeOptions(); + assertEquals(durable, lso.getDurable()); + assertEquals(durable, lso.getName()); } @Test public void testCreationErrors() { - ConsumerConfiguration cc1 = ConsumerConfiguration.builder().durable(durable((1))).build(); + String durable1 = random(); + String durable2 = random(); + ConsumerConfiguration cc1 = ConsumerConfiguration.builder().durable(durable1).build(); assertClientError(JsSoDurableMismatch, - () -> PushSubscribeOptions.builder().configuration(cc1).durable(durable(2)).build()); + () -> PushSubscribeOptions.builder().configuration(cc1).durable(durable2).build()); - ConsumerConfiguration cc2 = ConsumerConfiguration.builder().deliverGroup(deliver((1))).build(); + String deliver1 = random(); + String deliver2 = random(); + ConsumerConfiguration cc2 = ConsumerConfiguration.builder().deliverGroup(deliver1).build(); assertClientError(JsSoDeliverGroupMismatch, - () -> PushSubscribeOptions.builder().configuration(cc2).deliverGroup(deliver(2)).build()); + () -> PushSubscribeOptions.builder().configuration(cc2).deliverGroup(deliver2).build()); - ConsumerConfiguration cc3 = ConsumerConfiguration.builder().name(name((1))).build(); + String name1 = random(); + String name2 = random(); + ConsumerConfiguration cc3 = ConsumerConfiguration.builder().name(name1).build(); assertClientError(JsSoNameMismatch, - () -> PushSubscribeOptions.builder().configuration(cc3).name(name(2)).build()); + () -> PushSubscribeOptions.builder().configuration(cc3).name(name2).build()); } @Test public void testBindCreationErrors() { - String durOrName = name(); + String random = random(); // bind - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(null, durOrName)); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(EMPTY, durOrName)); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(STREAM, null)); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(STREAM, EMPTY)); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(STREAM).bind(true).build()); - - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(EMPTY).durable(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().durable(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(STREAM).durable(EMPTY).bind(true).build()); - - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(EMPTY).name(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().name(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(STREAM).name(EMPTY).bind(true).build()); - - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(null, durOrName)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(EMPTY, durOrName)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(STREAM, null)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(STREAM, EMPTY)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(STREAM).bind(true).build()); - - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).durable(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().durable(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(STREAM).durable(EMPTY).bind(true).build()); - - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).name(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().name(durOrName).bind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(STREAM).name(EMPTY).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(null, random)); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(EMPTY, random)); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(random, null)); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.bind(random, EMPTY)); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(random).bind(true).build()); + + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(EMPTY).durable(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().durable(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(random).durable(EMPTY).bind(true).build()); + + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(EMPTY).name(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().name(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(random).name(EMPTY).bind(true).build()); + + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(null, random)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(EMPTY, random)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(random, null)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.bind(random, EMPTY)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(random).bind(true).build()); + + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).durable(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().durable(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(random).durable(EMPTY).bind(true).build()); + + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).name(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().name(random).bind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(random).name(EMPTY).bind(true).build()); // fast bind - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(null, durOrName)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(EMPTY, durOrName)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(STREAM, null)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(STREAM, EMPTY)); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(STREAM).fastBind(true).build()); - - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).durable(durOrName).fastBind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().durable(durOrName).fastBind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(STREAM).durable(EMPTY).fastBind(true).build()); - - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).name(durOrName).fastBind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().name(durOrName).fastBind(true).build()); - assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(STREAM).name(EMPTY).fastBind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(null, random)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(EMPTY, random)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(random, null)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.fastBind(random, EMPTY)); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(random).fastBind(true).build()); + + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).durable(random).fastBind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().durable(random).fastBind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(random).durable(EMPTY).fastBind(true).build()); + + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(EMPTY).name(random).fastBind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().name(random).fastBind(true).build()); + assertThrows(IllegalArgumentException.class, () -> PullSubscribeOptions.builder().stream(random).name(EMPTY).fastBind(true).build()); } @Test @@ -335,17 +349,18 @@ public void testOrderedCreation() { ConsumerConfiguration ccEmpty = ConsumerConfiguration.builder().build(); PushSubscribeOptions.builder().configuration(ccEmpty).ordered(true).build(); + String random = random(); assertClientError(JsSoOrderedNotAllowedWithBind, - () -> PushSubscribeOptions.builder().stream(STREAM).bind(true).ordered(true).build()); + () -> PushSubscribeOptions.builder().stream(random).bind(true).ordered(true).build()); assertClientError(JsSoOrderedNotAllowedWithDeliverGroup, - () -> PushSubscribeOptions.builder().deliverGroup(DELIVER).ordered(true).build()); + () -> PushSubscribeOptions.builder().deliverGroup(random).ordered(true).build()); assertClientError(JsSoOrderedNotAllowedWithDurable, - () -> PushSubscribeOptions.builder().durable(DURABLE).ordered(true).build()); + () -> PushSubscribeOptions.builder().durable(random).ordered(true).build()); assertClientError(JsSoOrderedNotAllowedWithDeliverSubject, - () -> PushSubscribeOptions.builder().deliverSubject(DELIVER).ordered(true).build()); + () -> PushSubscribeOptions.builder().deliverSubject(random).ordered(true).build()); ConsumerConfiguration ccAckNotNone1 = ConsumerConfiguration.builder().ackPolicy(AckPolicy.All).build(); assertClientError(JsSoOrderedRequiresAckPolicyNone, @@ -364,10 +379,12 @@ public void testOrderedCreation() { ConsumerConfiguration ccHb = ConsumerConfiguration.builder().idleHeartbeat(100).build(); PushSubscribeOptions pso = PushSubscribeOptions.builder().configuration(ccHb).ordered(true).build(); + assertNotNull(pso.getConsumerConfiguration().getIdleHeartbeat()); assertEquals(100, pso.getConsumerConfiguration().getIdleHeartbeat().toMillis()); ccHb = ConsumerConfiguration.builder().idleHeartbeat(DEFAULT_ORDERED_HEARTBEAT + 1).build(); pso = PushSubscribeOptions.builder().configuration(ccHb).ordered(true).build(); + assertNotNull(pso.getConsumerConfiguration().getIdleHeartbeat()); assertEquals(DEFAULT_ORDERED_HEARTBEAT + 1, pso.getConsumerConfiguration().getIdleHeartbeat().toMillis()); // okay if you set it to true diff --git a/src/test/java/io/nats/client/SubscriberTests.java b/src/test/java/io/nats/client/SubscriberTests.java index da184c887..fbe0e9093 100644 --- a/src/test/java/io/nats/client/SubscriberTests.java +++ b/src/test/java/io/nats/client/SubscriberTests.java @@ -18,8 +18,8 @@ import java.time.Duration; import java.util.HashSet; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; +import static io.nats.client.utils.ConnectionUtils.standardConnect; import static io.nats.client.utils.TestBase.*; import static org.junit.jupiter.api.Assertions.*; @@ -27,56 +27,49 @@ public class SubscriberTests { @Test public void testCreateInbox() throws Exception { - HashSet check = new HashSet<>(); - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - for (int i=0; i < 10_000; i++) { + runInShared(nc -> { + HashSet check = new HashSet<>(); + for (int i=0; i < 100; i++) { String inbox = nc.createInbox(); assertFalse(check.contains(inbox)); check.add(inbox); } - } + }); } @Test public void testSingleMessage() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject"); - nc.publish("subject", new byte[16]); + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + nc.publish(subject, new byte[16]); Message msg = sub.nextMessage(Duration.ofMillis(500)); assertTrue(sub.isActive()); - assertEquals("subject", msg.getSubject()); + assertEquals(subject, msg.getSubject()); assertEquals(sub, msg.getSubscription()); assertNull(msg.getReplyTo()); assertEquals(16, msg.getData().length); - } + }); } @Test public void testMessageFromSubscriptionContainsConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject"); - nc.publish("subject", new byte[16]); + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + nc.publish(subject, new byte[16]); Message msg = sub.nextMessage(Duration.ofMillis(500)); assertTrue(sub.isActive()); - assertEquals("subject", msg.getSubject()); + assertEquals(subject, msg.getSubject()); assertEquals(sub, msg.getSubscription()); assertNull(msg.getReplyTo()); assertEquals(16, msg.getData().length); assertSame(msg.getConnection(), nc); - } + }); } @Test @@ -85,7 +78,7 @@ public void testTabInProtocolLine() throws Exception { CompletableFuture sendMsg = new CompletableFuture<>(); NatsServerProtocolMock.Customizer receiveMessageCustomizer = (ts, r,w) -> { - String subLine = ""; + String subLine; // System.out.println("*** Mock Server @" + ts.getPort() + " waiting for SUB ..."); try { @@ -113,42 +106,38 @@ public void testTabInProtocolLine() throws Exception { w.flush(); }; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(receiveMessageCustomizer); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(receiveMessageCustomizer)) { + try (Connection nc = standardConnect(mockTs)) { + String subject = random(); + Subscription sub = nc.subscribe(subject); - Subscription sub = nc.subscribe("subject"); + gotSub.get(); + sendMsg.complete(Boolean.TRUE); - gotSub.get(); - sendMsg.complete(Boolean.TRUE); + Message msg = sub.nextMessage(Duration.ZERO); - Message msg = sub.nextMessage(Duration.ZERO);//Duration.ofMillis(1000)); - - assertTrue(sub.isActive()); - assertNotNull(msg); - assertEquals("subject", msg.getSubject()); - assertEquals(sub, msg.getSubscription()); - assertNull(msg.getReplyTo()); - assertEquals(0, msg.getData().length); - - standardCloseConnection(nc); + assertTrue(sub.isActive()); + assertNotNull(msg); + assertEquals(subject, msg.getSubject()); + assertEquals(sub, msg.getSubscription()); + assertNull(msg.getReplyTo()); + assertEquals(0, msg.getData().length); + } } } @Test public void testMultiMessage() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject"); - nc.publish("subject", new byte[16]); - nc.publish("subject", new byte[16]); - nc.publish("subject", new byte[16]); + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + nc.publish(subject, new byte[16]); + nc.publish(subject, new byte[16]); + nc.publish(subject, new byte[16]); Message msg = sub.nextMessage(Duration.ofMillis(500)); - assertEquals("subject", msg.getSubject()); + assertEquals(subject, msg.getSubject()); assertEquals(sub, msg.getSubscription()); assertNull(msg.getReplyTo()); assertEquals(16, msg.getData().length); @@ -158,26 +147,25 @@ public void testMultiMessage() throws Exception { assertNotNull(msg); msg = sub.nextMessage(100); // coverage for nextMessage(millis) assertNull(msg); - } + }); } @Test - public void testQueueSubscribers() throws Exception, TimeoutException { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - + public void testQueueSubscribers() throws Exception { + runInShared(nc -> { int msgs = 100; int received = 0; int sub1Count = 0; int sub2Count = 0; Message msg; - Subscription sub1 = nc.subscribe("subject", "queue"); - Subscription sub2 = nc.subscribe("subject", "queue"); + String subject = random(); + String queue = random(); + Subscription sub1 = nc.subscribe(subject, queue); + Subscription sub2 = nc.subscribe(subject, queue); for (int i = 0; i < msgs; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } nc.flush(Duration.ofMillis(200));// Get them all to the server @@ -186,7 +174,7 @@ public void testQueueSubscribers() throws Exception, TimeoutException { msg = sub1.nextMessage(null); if (msg != null) { - assertEquals("subject", msg.getSubject()); + assertEquals(subject, msg.getSubject()); assertNull(msg.getReplyTo()); assertEquals(16, msg.getData().length); received++; @@ -198,7 +186,7 @@ public void testQueueSubscribers() throws Exception, TimeoutException { msg = sub2.nextMessage(null); if (msg != null) { - assertEquals("subject", msg.getSubject()); + assertEquals(subject, msg.getSubject()); assertNull(msg.getReplyTo()); assertEquals(16, msg.getData().length); received++; @@ -208,299 +196,180 @@ public void testQueueSubscribers() throws Exception, TimeoutException { assertEquals(msgs, received); assertEquals(msgs, sub1Count + sub2Count); - - // System.out.println("### Sub 1 " + sub1Count); - // System.out.println("### Sub 2 " + sub2Count); - } - } - - @Test - public void testUnsubscribe() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject"); - nc.publish("subject", new byte[16]); - - Message msg = sub.nextMessage(Duration.ofMillis(500)); - assertNotNull(msg); - - sub.unsubscribe(); - assertFalse(sub.isActive()); - sub.nextMessage(Duration.ofMillis(500)); // Will throw an exception - } }); } @Test - public void testAutoUnsubscribe() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject").unsubscribe(1); - nc.publish("subject", new byte[16]); + public void testUnsubscribe() throws Exception { + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + nc.publish(subject, new byte[16]); - Message msg = sub.nextMessage(Duration.ofMillis(500)); // should get 1 - assertNotNull(msg); + Message msg = sub.nextMessage(Duration.ofMillis(500)); + assertNotNull(msg); - sub.nextMessage(Duration.ofMillis(500)); // Will throw an exception - } + sub.unsubscribe(); + assertFalse(sub.isActive()); + assertThrows(IllegalStateException.class, () -> sub.nextMessage(Duration.ofMillis(500))); }); } @Test - public void testMultiAutoUnsubscribe() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - int msgCount = 10; - Subscription sub = nc.subscribe("subject").unsubscribe(msgCount); - - for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); - } + public void testAutoUnsubscribe() throws Exception { + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject).unsubscribe(1); + nc.publish(subject, new byte[16]); - Message msg; - for (int i = 0; i < msgCount; i++) { - msg = sub.nextMessage(Duration.ofMillis(500)); // should get 1 - assertNotNull(msg); - } + Message msg = sub.nextMessage(Duration.ofMillis(500)); // should get 1 + assertNotNull(msg); - sub.nextMessage(Duration.ofMillis(500)); // Will throw an exception - } + assertThrows(IllegalStateException.class, () -> sub.nextMessage(Duration.ofMillis(500))); }); } @Test - public void testOnlyOneUnsubscribe() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject"); - - sub.unsubscribe(); - sub.unsubscribe(); // Will throw an exception + public void testMultiAutoUnsubscribe() throws Exception { + runInShared(nc -> { + String subject = random(); + int msgCount = 10; + Subscription sub = nc.subscribe(subject).unsubscribe(msgCount); + + for (int i = 0; i < msgCount; i++) { + nc.publish(subject, new byte[16]); } - }); - } - - @Test - public void testOnlyOneAutoUnsubscribe() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - Subscription sub = nc.subscribe("subject").unsubscribe(1); - nc.publish("subject", new byte[16]); - - Message msg = sub.nextMessage(Duration.ofMillis(500)); // should get 1 + Message msg; + for (int i = 0; i < msgCount; i++) { + msg = sub.nextMessage(Duration.ofMillis(500)); // should get 1 assertNotNull(msg); - - sub.unsubscribe(); // Will throw an exception } + + assertThrows(IllegalStateException.class, () -> sub.nextMessage(Duration.ofMillis(500))); }); } @Test - public void testUnsubscribeInAnotherThread() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject"); - - new Thread(sub::unsubscribe).start(); - - sub.nextMessage(Duration.ofMillis(5000)); // throw - } + public void testOnlyOneUnsubscribe() throws Exception { + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + sub.unsubscribe(); + assertThrows(IllegalStateException.class, sub::unsubscribe); }); } @Test - public void testAutoUnsubAfterMaxIsReached() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - - Subscription sub = nc.subscribe("subject"); - - int msgCount = 10; - for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); - } - - nc.flush(Duration.ofMillis(1000)); // Slow things down so we have time to unsub + public void testOnlyOneAutoUnsubscribe() throws Exception { + runInShared(nc -> { + String subject = random(); - for (int i = 0; i < msgCount; i++) { - sub.nextMessage(null); - } + Subscription sub = nc.subscribe(subject).unsubscribe(1); + nc.publish(subject, new byte[16]); - sub.unsubscribe(msgCount); // we already have that many + Message msg = sub.nextMessage(Duration.ofMillis(500)); // should get 1 + assertNotNull(msg); - sub.nextMessage(Duration.ofMillis(500)); // Will throw an exception - } + assertThrows(IllegalStateException.class, sub::unsubscribe); }); } @Test - public void testThrowOnNullSubject() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.subscribe(null); - } + public void testUnsubscribeInAnotherThread() throws Exception { + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + new Thread(sub::unsubscribe).start(); + assertThrows(IllegalStateException.class, () -> sub.nextMessage(Duration.ofMillis(5000))); }); } @Test - public void testThrowOnEmptySubject() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.subscribe(""); + public void testAutoUnsubAfterMaxIsReached() throws Exception { + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + + int msgCount = 10; + for (int i = 0; i < msgCount; i++) { + nc.publish(subject, new byte[16]); } - }); - } - @Test - public void testThrowOnNullQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.subscribe("subject", null); - } - }); - } + nc.flush(Duration.ofMillis(1000)); // Slow things down so we have time to unsub - @Test - public void testThrowOnEmptyQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.subscribe("subject", ""); + for (int i = 0; i < msgCount; i++) { + sub.nextMessage(null); } - }); - } - @Test - public void testThrowOnNullSubjectWithQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.subscribe(null, "quque"); - } - }); - } + sub.unsubscribe(msgCount); // we already have that many - @Test - public void testThrowOnEmptySubjectWithQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.subscribe("", "quque"); - } + assertThrows(IllegalStateException.class, () -> sub.nextMessage(Duration.ofMillis(5000))); }); } @Test - public void throwsOnSubscribeIfClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.close(); - nc.subscribe("subject"); - } - }); - } + public void testSubscribesThatException() throws Exception { + // own nc b/c we will close + runInShared(nc -> { - @Test - public void throwsOnUnsubscribeIfClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Subscription sub = nc.subscribe("subject"); - nc.close(); - sub.unsubscribe(); - } - }); - } + // null subject + //noinspection DataFlowIssue + assertThrows(IllegalArgumentException.class, () -> nc.subscribe(null)); - @Test - public void throwsOnAutoUnsubscribeIfClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - Subscription sub = nc.subscribe("subject"); - nc.close(); - sub.unsubscribe(1); - } - }); - } + // empty subject + assertThrows(IllegalArgumentException.class, () -> nc.subscribe("")); - @Test - public void testUnsubscribeWhileWaiting() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); + // null queue + //noinspection DataFlowIssue + assertThrows(IllegalArgumentException.class, () -> nc.subscribe(random(), null)); - Subscription sub = nc.subscribe("subject"); - nc.flush(Duration.ofMillis(1000)); + // empty queue + assertThrows(IllegalArgumentException.class, () -> nc.subscribe(random(), "")); - new Thread(()->{ - try { Thread.sleep(100); } catch(Exception e) { /* ignored */ } - sub.unsubscribe(); - }).start(); + // null subject with queue + //noinspection DataFlowIssue + assertThrows(IllegalArgumentException.class, () -> nc.subscribe(null, random())); - sub.nextMessage(Duration.ofMillis(5000)); // Should throw - } + // empty subject with queue + assertThrows(IllegalArgumentException.class, () -> nc.subscribe("", random())); }); } @Test - public void testInvalidSubjectsAndQueueNames() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - Dispatcher d = nc.createDispatcher(m -> {}); - for (String bad : BAD_SUBJECTS_OR_QUEUES) { - assertThrows(IllegalArgumentException.class, () -> nc.subscribe(bad)); - assertThrows(IllegalArgumentException.class, () -> d.subscribe(bad)); - assertThrows(IllegalArgumentException.class, () -> d.subscribe(bad, m -> {})); - assertThrows(IllegalArgumentException.class, () -> d.subscribe(bad, "q")); - assertThrows(IllegalArgumentException.class, () -> d.subscribe(bad, "q", m -> {})); - assertThrows(IllegalArgumentException.class, () -> nc.subscribe("s", bad)); - assertThrows(IllegalArgumentException.class, () -> d.subscribe("s", bad)); - assertThrows(IllegalArgumentException.class, () -> d.subscribe("s", bad, m -> {})); - } - } - } + public void testSubscribesThatExceptionAfterClose() throws Exception { + // own nc b/c we will close + runInSharedOwnNc(nc -> { + Subscription sub = nc.subscribe(random()); + nc.close(); + + /// can't subscribe when closed + assertThrows(IllegalStateException.class, () -> nc.subscribe(random())); + + /// can't unsubscribe when closed + assertThrows(IllegalStateException.class, sub::unsubscribe); - private static int getDataId(Message m) { - return Integer.parseInt(new String(m.getData())); + /// can't auto unsubscribe when closed + assertThrows(IllegalStateException.class, () -> sub.unsubscribe(1)); + }); } @Test - public void testDispatcherDefaultSubscribeWhenNoDefaultHandler() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - String subject = subject(); - - Dispatcher d = nc.createDispatcher(); - assertThrows(IllegalStateException.class, () -> d.subscribe(subject)); - } + public void testUnsubscribeWhileWaiting() throws Exception { + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); + nc.flush(Duration.ofMillis(1000)); + + new Thread(() -> { + try { + Thread.sleep(100); + } + catch (Exception e) { /* ignored */ } + sub.unsubscribe(); + }).start(); + + assertThrows(IllegalStateException.class, () -> sub.nextMessage(Duration.ofMillis(5000))); + }); } } diff --git a/src/test/java/io/nats/client/TestServer.java b/src/test/java/io/nats/client/TestServer.java new file mode 100644 index 000000000..e7a162924 --- /dev/null +++ b/src/test/java/io/nats/client/TestServer.java @@ -0,0 +1,19 @@ +// 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. + +package io.nats.client; + +public interface TestServer { + int getPort(); + String getServerUri(); +} diff --git a/src/test/java/io/nats/client/api/AccountStatisticsTests.java b/src/test/java/io/nats/client/api/AccountStatisticsTests.java index 47ac8c661..76253541e 100644 --- a/src/test/java/io/nats/client/api/AccountStatisticsTests.java +++ b/src/test/java/io/nats/client/api/AccountStatisticsTests.java @@ -39,7 +39,9 @@ public void testAccountStatsImpl() { assertEquals("ngs", as.getDomain()); ApiStats api = as.getApi(); + //noinspection deprecation assertEquals(301, api.getTotal()); // COVERAGE + //noinspection deprecation assertEquals(302, api.getErrors()); // COVERAGE assertEquals(301, api.getTotalApiRequests()); assertEquals(302, api.getErrorCount()); @@ -70,7 +72,9 @@ public void testAccountStatsImpl() { api = as.getApi(); assertNotNull(api); + //noinspection deprecation assertEquals(0, api.getTotal()); // COVERAGE + //noinspection deprecation assertEquals(0, api.getErrors()); // COVERAGE assertEquals(0, api.getTotalApiRequests()); assertEquals(0, api.getErrorCount()); @@ -80,8 +84,10 @@ public void testAccountStatsImpl() { private void validateTier(AccountTier tier, int tierBase, int limitsIdBase) { assertNotNull(tier); + //noinspection deprecation assertEquals(tierBase + 1, tier.getMemory()); // COVERAGE assertEquals(tierBase + 1, tier.getMemoryBytes()); + //noinspection deprecation assertEquals(tierBase + 2, tier.getStorage()); // COVERAGE assertEquals(tierBase + 2, tier.getStorageBytes()); assertEquals(tierBase + 3, tier.getStreams()); diff --git a/src/test/java/io/nats/client/api/ConsumerConfigurationTests.java b/src/test/java/io/nats/client/api/ConsumerConfigurationTests.java index 33b2abd11..39ef34b41 100644 --- a/src/test/java/io/nats/client/api/ConsumerConfigurationTests.java +++ b/src/test/java/io/nats/client/api/ConsumerConfigurationTests.java @@ -48,8 +48,8 @@ public void testBuilder() throws Exception { .ackWait(Duration.ofSeconds(99)) // duration .deliverPolicy(DeliverPolicy.ByStartSequence) .description("blah") - .name(NAME) - .durable(NAME) + .name("name") + .durable("name") .filterSubject("fs") .maxDeliver(5555) .maxAckPending(6666) @@ -58,7 +58,7 @@ public void testBuilder() throws Exception { .sampleFrequency("10s") .startSequence(2001) .startTime(zdt) - .deliverSubject(DELIVER) + .deliverSubject("deliver") .flowControl(66000) // duration .maxPullWaiting(73) .maxBatch(55) @@ -82,9 +82,10 @@ public void testBuilder() throws Exception { assertNotNull(c.toString()); // COVERAGE assertAsBuilt(c, zdt); - ConsumerCreateRequest ccr = new ConsumerCreateRequest(STREAM, c); + String stream = random(); + ConsumerCreateRequest ccr = new ConsumerCreateRequest(stream, c); assertNotNull(ccr.toString()); // COVERAGE - assertEquals(STREAM, ccr.getStreamName()); + assertEquals(stream, ccr.getStreamName()); assertNotNull(ccr.getConfig()); assertNotNull(ccr.getAction()); assertSame(ConsumerCreateRequest.Action.CreateOrUpdate, ccr.getAction()); @@ -207,7 +208,7 @@ public void testBuilder() throws Exception { assertThrows(IllegalArgumentException.class, () -> ConsumerConfiguration.builder().backoff(DURATION_MIN_LONG - 1).build()); - assertClientError(JsConsumerNameDurableMismatch, () -> ConsumerConfiguration.builder().name(NAME).durable(DURABLE).build()); + assertClientError(JsConsumerNameDurableMismatch, () -> ConsumerConfiguration.builder().name(random()).durable(random()).build()); // filter subjects vs filter subject builder.filterSubjects("subject-0", "subject-1"); @@ -259,8 +260,8 @@ private void assertAsBuilt(ConsumerConfiguration c, ZonedDateTime zdt) { assertEquals(Duration.ofSeconds(99), c.getAckWait()); assertEquals(DeliverPolicy.ByStartSequence, c.getDeliverPolicy()); assertEquals("blah", c.getDescription()); - assertEquals(NAME, c.getDurable()); - assertEquals(NAME, c.getName()); + assertEquals("name", c.getDurable()); + assertEquals("name", c.getName()); assertEquals("fs", c.getFilterSubject()); assertEquals(5555, c.getMaxDeliver()); assertEquals(6666, c.getMaxAckPending()); @@ -269,7 +270,7 @@ private void assertAsBuilt(ConsumerConfiguration c, ZonedDateTime zdt) { assertEquals("10s", c.getSampleFrequency()); assertEquals(2001, c.getStartSequence()); assertEquals(zdt, c.getStartTime()); - assertEquals(DELIVER, c.getDeliverSubject()); + assertEquals("deliver", c.getDeliverSubject()); assertTrue(c.isFlowControl()); assertEquals(Duration.ofSeconds(66), c.getIdleHeartbeat()); assertEquals(73, c.getMaxPullWaiting()); diff --git a/src/test/java/io/nats/client/api/ObjectStoreApiTests.java b/src/test/java/io/nats/client/api/ObjectStoreApiTests.java index 75d0f6239..77e9d5b7f 100644 --- a/src/test/java/io/nats/client/api/ObjectStoreApiTests.java +++ b/src/test/java/io/nats/client/api/ObjectStoreApiTests.java @@ -94,7 +94,7 @@ public void testObjectInfoConstruction() { } private void validateObjectInfo(ObjectInfo oi, ZonedDateTime modified) { - assertEquals(BUCKET, oi.getBucket()); + assertEquals("bucket", oi.getBucket()); assertEquals("object-name", oi.getObjectName()); assertEquals("object-desc", oi.getDescription()); assertEquals(344000, oi.getSize()); @@ -107,11 +107,11 @@ private void validateObjectInfo(ObjectInfo oi, ZonedDateTime modified) { assertEquals(8196, oi.getObjectMeta().getObjectMetaOptions().getChunkSize()); assertNotNull(oi.getHeaders()); assertEquals(2, oi.getHeaders().size()); - List list = oi.getHeaders().get(key(1)); + List list = oi.getHeaders().get("key-1"); assertNotNull(list); assertEquals(1, list.size()); - assertEquals(data(1), oi.getHeaders().getFirst(key(1))); - list = oi.getHeaders().get(key(2)); + assertEquals(data(1), oi.getHeaders().getFirst("key-1")); + list = oi.getHeaders().get("key-2"); assertNotNull(list); assertEquals(2, list.size()); assertTrue(list.contains(data(21))); @@ -126,11 +126,12 @@ private void validateObjectInfo(ObjectInfo oi, ZonedDateTime modified) { @Test public void testObjectInfoCoverage() { - ObjectLink link1a = ObjectLink.object(BUCKET, "name"); - ObjectLink link1b = ObjectLink.object(BUCKET, "name"); - ObjectLink link2 = ObjectLink.object(BUCKET, "name2"); - ObjectLink blink1a = ObjectLink.bucket(BUCKET); - ObjectLink blink1b = ObjectLink.bucket(BUCKET); + String bucket = random(); + ObjectLink link1a = ObjectLink.object(bucket, "name"); + ObjectLink link1b = ObjectLink.object(bucket, "name"); + ObjectLink link2 = ObjectLink.object(bucket, "name2"); + ObjectLink blink1a = ObjectLink.bucket(bucket); + ObjectLink blink1b = ObjectLink.bucket(bucket); ObjectLink blink2 = ObjectLink.bucket("bucket2"); ObjectMetaOptions metaOptions = ObjectMetaOptions.builder().link(link1a).chunkSize(1024).build(); @@ -159,11 +160,9 @@ private void linkCoverage(ObjectLink link1a, ObjectLink link1b, ObjectLink link2 assertFalse(blink1a.isObjectLink()); assertTrue(blink1a.isBucketLink()); - //noinspection EqualsWithItself assertEquals(link1a, link1a); assertEquals(link1a, link1b); assertEquals(link1a, ObjectLink.object(link1a.getBucket(), link1a.getObjectName())); - //noinspection EqualsWithItself assertEquals(blink1a, blink1a); assertEquals(blink1a, blink1b); assertFalse(link1a.equals(new Object())); @@ -191,7 +190,6 @@ private void metaOptionsCoverage(ObjectMetaOptions metaOptions, ObjectMetaOption assertTrue(metaOptionsC.hasData()); assertFalse(ObjectMetaOptions.builder().build().hasData()); - //noinspection EqualsWithItself assertEquals(metaOptions, metaOptions); assertFalse(metaOptions.equals(new Object())); assertFalse(metaOptions.equals(null)); @@ -242,7 +240,6 @@ private void metaCoverage(ObjectLink link, ObjectLink link2) { ObjectMeta metaH = ObjectMeta.builder("meta").headers(new Headers().put("key", "data")).headers(null).build(); assertEquals(0, metaH.getHeaders().size()); - //noinspection EqualsWithItself assertEquals(meta1a, meta1a); assertEquals(meta1a, meta1b); assertNotEquals(meta1a, meta1c); @@ -252,19 +249,16 @@ private void metaCoverage(ObjectLink link, ObjectLink link2) { assertNotEquals(meta1a, meta3a); assertNotEquals(meta1a, meta4a); - //noinspection EqualsWithItself assertEquals(meta2a, meta2a); assertEquals(meta2a, meta2b); assertNotEquals(meta2a, meta2c); assertNotEquals(meta2a, meta1a); - //noinspection EqualsWithItself assertEquals(meta3a, meta3a); assertEquals(meta3a, meta3b); assertNotEquals(meta3a, meta3c); assertNotEquals(meta3a, meta1a); - //noinspection EqualsWithItself assertEquals(meta4a, meta4a); assertEquals(meta4a, meta4b); assertNotEquals(meta4a, meta4c); @@ -345,7 +339,6 @@ private void infoCoverage(ObjectLink link1, ObjectLink link2) { assertEquals(info1a, info1b); assertNotEquals(info1a, info1c); - //noinspection EqualsWithItself assertEquals(info1a, info1a); assertFalse(info1a.equals(null)); assertFalse(info1a.equals(new Object())); @@ -357,43 +350,36 @@ private void infoCoverage(ObjectLink link1, ObjectLink link2) { assertNotEquals(info1a, info7a); assertNotEquals(info1a, info8a); - //noinspection EqualsWithItself assertEquals(info2a, info2a); assertEquals(info2a, info2b); assertNotEquals(info2a, info2c); assertNotEquals(info2a, info1a); - //noinspection EqualsWithItself assertEquals(info3a, info3a); assertEquals(info3a, info3b); assertNotEquals(info3a, info3c); assertNotEquals(info3a, info1a); - //noinspection EqualsWithItself assertEquals(info4a, info4a); assertEquals(info4a, info4b); assertNotEquals(info4a, info4c); assertNotEquals(info4a, info1a); - //noinspection EqualsWithItself assertEquals(info5a, info5a); assertEquals(info5a, info5b); assertNotEquals(info5a, info5c); assertNotEquals(info5a, info1a); - //noinspection EqualsWithItself assertEquals(info6a, info6a); assertEquals(info6a, info6b); assertNotEquals(info6a, info6c); assertNotEquals(info6a, info1a); - //noinspection EqualsWithItself assertEquals(info7a, info7a); assertEquals(info7a, info7b); assertNotEquals(info7a, info7c); assertNotEquals(info7a, info1a); - //noinspection EqualsWithItself assertEquals(info8a, info8a); assertEquals(info8a, info8b); assertNotEquals(info8a, info8c); diff --git a/src/test/java/io/nats/client/api/PublishAckTests.java b/src/test/java/io/nats/client/api/PublishAckTests.java index e184bbbe2..64407b0e7 100644 --- a/src/test/java/io/nats/client/api/PublishAckTests.java +++ b/src/test/java/io/nats/client/api/PublishAckTests.java @@ -69,9 +69,7 @@ public void testRequiredFieldsSet() { @Test public void testThrowsOnGarbage() { - assertThrows(JetStreamApiException.class, () -> { - new PublishAck(getDataMessage("notjson")); - }); + assertThrows(JetStreamApiException.class, () -> new PublishAck(getDataMessage("notjson"))); } @Test diff --git a/src/test/java/io/nats/client/api/StreamConfigurationTests.java b/src/test/java/io/nats/client/api/StreamConfigurationTests.java index 7298b504b..ff3af0340 100644 --- a/src/test/java/io/nats/client/api/StreamConfigurationTests.java +++ b/src/test/java/io/nats/client/api/StreamConfigurationTests.java @@ -13,7 +13,6 @@ package io.nats.client.api; -import io.nats.client.JetStreamManagement; import io.nats.client.impl.JetStreamTestBase; import io.nats.client.support.DateTimeUtils; import io.nats.client.support.JsonParseException; @@ -31,6 +30,7 @@ import static io.nats.client.api.CompressionOption.S2; import static io.nats.client.api.ConsumerConfiguration.*; import static io.nats.client.support.ApiConstants.*; +import static io.nats.client.utils.VersionUtils.atLeast2_10; import static org.junit.jupiter.api.Assertions.*; public class StreamConfigurationTests extends JetStreamTestBase { @@ -54,11 +54,10 @@ private StreamConfiguration getTestConfiguration() { @Test public void testRoundTrip() throws Exception { - runInJsServer(si -> si.isNewerVersionThan("2.8.4"), nc -> { - CompressionOption compressionOption = atLeast2_10(ensureRunServerInfo()) ? S2 : None; - String stream = stream(); + runInSharedCustom((nc, ctx) -> { + CompressionOption compressionOption = atLeast2_10() ? S2 : None; StreamConfiguration sc = StreamConfiguration.builder(getTestConfiguration()) - .name(stream) + .name(ctx.stream) .mirror(null) .sources() .replicas(1) @@ -71,8 +70,7 @@ public void testRoundTrip() throws Exception { .allowMessageCounter(false) .persistMode(null) .build(); - JetStreamManagement jsm = nc.jetStreamManagement(); - validateTestStreamConfiguration(jsm.addStream(sc).getConfiguration(), true, stream); + validateTestStreamConfiguration(ctx.createOrReplaceStream(sc).getConfiguration(), true, ctx.stream); }); } @@ -148,7 +146,7 @@ public void testMissingJsonFields() throws Exception{ public void testInvalidNameInJson() throws Exception{ String originalJson = getStreamConfigurationJson(); JsonValue originalParsedJson = JsonParser.parse(originalJson); - originalParsedJson.map.put(NAME, new JsonValue("Inavlid*Name")); + originalParsedJson.map.put("name", new JsonValue("Invalid*Name")); assertThrows(IllegalArgumentException.class, () -> StreamConfiguration.instance(originalParsedJson.toJson())); } @@ -268,11 +266,9 @@ public void testConstruction() { for (String l1 : lines) { if (l1.startsWith("{")) { Mirror m1 = new Mirror(JsonParser.parseUnchecked(l1)); - //noinspection EqualsWithItself assertEquals(m1, m1); assertEquals(m1, Mirror.builder(m1).build()); Source s1 = new Source(JsonParser.parseUnchecked(l1)); - //noinspection EqualsWithItself assertEquals(s1, s1); assertEquals(s1, Source.builder(s1).build()); //this provides testing coverage @@ -300,7 +296,6 @@ public void testConstruction() { lines = ResourceUtils.dataAsLines("ExternalJson.txt"); for (String l1 : lines) { External e1 = new External(JsonParser.parseUnchecked(l1)); - //noinspection EqualsWithItself assertEquals(e1, e1); //noinspection MisorderedAssertEqualsArguments assertNotEquals(e1, null); @@ -319,7 +314,7 @@ public void testConstruction() { // coverage for null StreamConfiguration, millis maxAge, millis duplicateWindow StreamConfiguration scCov = StreamConfiguration.builder(null) - .name(name()) + .name(random()) .maxAge(1111) .duplicateWindow(2222) .build(); @@ -455,63 +450,74 @@ private void assertNotEqualsEqualsHashcode(Source s, Mirror m, Source.Builder sb @Test public void testSubjects() { - StreamConfiguration.Builder builder = StreamConfiguration.builder().name(name()); + StreamConfiguration.Builder builder = StreamConfiguration.builder().name(random()); + String subject = random(); // subjects(...) replaces - builder.subjects(subject(0)); - assertSubjects(builder.build(), 0); + builder.subjects(subject); + assertSubjects(builder.build(), subject); // subjects(...) replaces builder.subjects(); assertSubjects(builder.build()); // subjects(...) replaces - builder.subjects(subject(1)); - assertSubjects(builder.build(), 1); + subject = random(); + builder.subjects(subject); + assertSubjects(builder.build(), subject); // subjects(...) replaces builder.subjects((String)null); assertSubjects(builder.build()); // subjects(...) replaces - builder.subjects(subject(2), subject(3)); - assertSubjects(builder.build(), 2, 3); + String subjectA = random(); + String subjectB = random(); + builder.subjects(subjectA, subjectB); + assertSubjects(builder.build(), subjectA, subjectB); // subjects(...) replaces - builder.subjects(subject(101), null, subject(102)); - assertSubjects(builder.build(), 101, 102); + subjectA = random(); + subjectB = random(); + builder.subjects(subjectA, null, subjectB); + assertSubjects(builder.build(), subjectA, subjectB); // subjects(...) replaces - builder.subjects(Arrays.asList(subject(4), subject(5))); - assertSubjects(builder.build(), 4, 5); + subjectA = random(); + subjectB = random(); + builder.subjects(Arrays.asList(subjectA, subjectB)); + assertSubjects(builder.build(), subjectA, subjectB); // addSubjects(...) adds unique - builder.addSubjects(subject(5), subject(6)); - assertSubjects(builder.build(), 4, 5, 6); + String subjectC = random(); + builder.addSubjects(subjectB, subjectC); + assertSubjects(builder.build(), subjectA, subjectB, subjectC); // addSubjects(...) adds unique - builder.addSubjects(Arrays.asList(subject(6), subject(7), subject(8))); - assertSubjects(builder.build(), 4, 5, 6, 7, 8); + String subjectD = random(); + String subjectE = random(); + builder.addSubjects(Arrays.asList(subjectC, subjectD, subjectE)); + assertSubjects(builder.build(), subjectA, subjectB, subjectC, subjectD, subjectE); // addSubjects(...) null check builder.addSubjects((String[]) null); - assertSubjects(builder.build(), 4, 5, 6, 7, 8); + assertSubjects(builder.build(), subjectA, subjectB, subjectC, subjectD, subjectE); // addSubjects(...) null check builder.addSubjects((Collection) null); - assertSubjects(builder.build(), 4, 5, 6, 7, 8); + assertSubjects(builder.build(), subjectA, subjectB, subjectC, subjectD, subjectE); } - private void assertSubjects(StreamConfiguration sc, int... subIds) { - assertEquals(subIds.length, sc.getSubjects().size()); - for (int subId : subIds) { - assertTrue(sc.getSubjects().contains(subject(subId))); + private void assertSubjects(StreamConfiguration sc, String... subjects) { + assertEquals(subjects.length, sc.getSubjects().size()); + for (String s : subjects) { + assertTrue(sc.getSubjects().contains(s)); } } @Test public void testRetentionPolicy() { - StreamConfiguration.Builder builder = StreamConfiguration.builder().name(name()); + StreamConfiguration.Builder builder = StreamConfiguration.builder().name(random()); assertEquals(RetentionPolicy.Limits, builder.build().getRetentionPolicy()); builder.retentionPolicy(RetentionPolicy.Limits); @@ -529,7 +535,7 @@ public void testRetentionPolicy() { @Test public void testCompressionOption() { - StreamConfiguration.Builder builder = StreamConfiguration.builder().name(name()); + StreamConfiguration.Builder builder = StreamConfiguration.builder().name(random()); assertEquals(None, builder.build().getCompressionOption()); builder.compressionOption(None); @@ -546,7 +552,7 @@ public void testCompressionOption() { @Test public void testStorageType() { - StreamConfiguration.Builder builder = StreamConfiguration.builder().name(name()); + StreamConfiguration.Builder builder = StreamConfiguration.builder().name(random()); assertEquals(StorageType.File, builder.build().getStorageType()); builder.storageType(StorageType.Memory); @@ -558,7 +564,7 @@ public void testStorageType() { @Test public void testDiscardPolicy() { - StreamConfiguration.Builder builder = StreamConfiguration.builder().name(name()); + StreamConfiguration.Builder builder = StreamConfiguration.builder().name(random()); assertEquals(DiscardPolicy.Old, builder.build().getDiscardPolicy()); builder.discardPolicy(DiscardPolicy.New); diff --git a/src/test/java/io/nats/client/impl/AuthAndConnectTests.java b/src/test/java/io/nats/client/impl/AuthAndConnectTests.java index 2e5dd750e..d86ce49e9 100644 --- a/src/test/java/io/nats/client/impl/AuthAndConnectTests.java +++ b/src/test/java/io/nats/client/impl/AuthAndConnectTests.java @@ -17,45 +17,41 @@ import io.nats.client.ErrorListener; import io.nats.client.NatsTestServer; import io.nats.client.Options; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicBoolean; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; -import static io.nats.client.utils.TestBase.*; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class AuthAndConnectTests { - @Test - public void testIsAuthError() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Connection nc = standardConnection(ts.getURI()); - NatsConnection nats = (NatsConnection)nc; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; - assertTrue(nats.isAuthenticationError("user authentication expired")); - assertTrue(nats.isAuthenticationError("authorization violation")); - assertTrue(nats.isAuthenticationError("Authorization Violation")); - assertFalse(nats.isAuthenticationError("test")); - assertFalse(nats.isAuthenticationError("")); - assertFalse(nats.isAuthenticationError(null)); +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.ThreadUtils.sleep; +import static org.junit.jupiter.api.Assertions.*; - standardCloseConnection(nc); - } +public class AuthAndConnectTests extends TestBase { + @Test + public void testIsAuthError() { + //noinspection resource + NatsConnection nats = new NatsConnection(options()); + assertTrue(nats.isAuthenticationError("user authentication expired")); + assertTrue(nats.isAuthenticationError("authorization violation")); + assertTrue(nats.isAuthenticationError("Authorization Violation")); + assertFalse(nats.isAuthenticationError("test")); + assertFalse(nats.isAuthenticationError("")); + assertFalse(nats.isAuthenticationError(null)); } @Test() public void testConnectWhenClosed() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - NatsConnection nc = (NatsConnection)standardConnection(ts.getURI()); - standardCloseConnection(nc); + runInSharedOwnNc(c -> { + NatsConnection nc = (NatsConnection)c; + closeAndConfirm(nc); nc.connect(false); // should do nothing assertClosed(nc); nc.reconnect(); // should do nothing assertClosed(nc); - } + }); } /** @@ -72,35 +68,36 @@ public void errorOccurred(Connection conn, String error) { } }; - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { Options options = Options.builder() - .server(ts.getURI()) + .server(ts.getServerUri()) .maxReconnects(-1) .reconnectWait(Duration.ZERO) .errorListener(noopErrorListener) .build(); - NatsConnection nc = (NatsConnection) standardConnection(options); + try (NatsConnection nc = (NatsConnection) managedConnect(options)) { - // After we've connected, shut down, so we can attempt reconnecting. - ts.shutdown(true); + // After we've connected, shut down, so we can attempt reconnecting. + ts.shutdown(true); - final AtomicBoolean running = new AtomicBoolean(true); - Thread parallelCommunicationIssues = new Thread(() -> { - while (running.get()) { - nc.handleCommunicationIssue(new Exception()); + final AtomicBoolean running = new AtomicBoolean(true); + Thread parallelCommunicationIssues = new Thread(() -> { + while (running.get()) { + nc.handleCommunicationIssue(new Exception()); - // Shortly sleep, to not spam at full speed. - sleep(1); - } - }); - parallelCommunicationIssues.start(); + // Shortly sleep, to not spam at full speed. + sleep(1); + } + }); + parallelCommunicationIssues.start(); - // Wait for some time to allow for reconnection logic to run. - Thread.sleep(2000); - running.set(false); + // Wait for some time to allow for reconnection logic to run. + Thread.sleep(2000); + running.set(false); - assertNotEquals(Connection.Status.CLOSED, nc.getStatus()); + assertNotEquals(Connection.Status.CLOSED, nc.getStatus()); + } } } } diff --git a/src/test/java/io/nats/client/impl/AuthHandlerTests.java b/src/test/java/io/nats/client/impl/AuthHandlerTests.java index 3762fbf3e..2d73d488a 100644 --- a/src/test/java/io/nats/client/impl/AuthHandlerTests.java +++ b/src/test/java/io/nats/client/impl/AuthHandlerTests.java @@ -20,9 +20,10 @@ import java.nio.charset.StandardCharsets; +import static io.nats.client.utils.ResourceUtils.jwtResource; import static io.nats.client.utils.ResourceUtils.resourceAsString; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; public class AuthHandlerTests { @@ -31,8 +32,8 @@ public class AuthHandlerTests { @Test public void testCredsFile() throws Exception { - AuthHandler auth = Nats.credentials("src/test/resources/jwt_nkey/test.creds"); - assertTrue(auth instanceof FileAuthHandler); + AuthHandler auth = Nats.credentials(jwtResource("test.creds")); + assertInstanceOf(FileAuthHandler.class, auth); NKey key = NKey.fromSeed(SEED.toCharArray()); byte[] test = "hello world".getBytes(StandardCharsets.UTF_8); @@ -47,7 +48,7 @@ public void testMemoryAuth() throws Exception { String creds = resourceAsString("jwt_nkey/test.creds"); AuthHandler auth = Nats.staticCredentials(creds.getBytes(StandardCharsets.UTF_8)); - assertTrue(auth instanceof MemoryAuthHandler); + assertInstanceOf(MemoryAuthHandler.class, auth); NKey key = NKey.fromSeed(SEED.toCharArray()); byte[] test = "hello world".getBytes(StandardCharsets.UTF_8); @@ -59,7 +60,7 @@ public void testMemoryAuth() throws Exception { @Test public void testSeparateWrappedFiles() throws Exception { - AuthHandler auth = Nats.credentials("src/test/resources/jwt_nkey/test_wrapped.jwt", "src/test/resources/jwt_nkey/test_wrapped.nk"); + AuthHandler auth = Nats.credentials(jwtResource("test_wrapped.jwt"), jwtResource("test_wrapped.nk")); NKey key = NKey.fromSeed(SEED.toCharArray()); byte[] test = "hello world again".getBytes(StandardCharsets.UTF_8); @@ -71,7 +72,7 @@ public void testSeparateWrappedFiles() throws Exception { @Test public void testSeparateNKeyWrappedFile() throws Exception { - AuthHandler auth = Nats.credentials(null, "src/test/resources/jwt_nkey/test_wrapped.nk"); + AuthHandler auth = Nats.credentials(null, jwtResource("test_wrapped.nk")); NKey key = NKey.fromSeed(SEED.toCharArray()); byte[] test = "hello world again".getBytes(StandardCharsets.UTF_8); @@ -83,7 +84,7 @@ public void testSeparateNKeyWrappedFile() throws Exception { @Test public void testSeparateBareFiles() throws Exception { - AuthHandler auth = Nats.credentials("src/test/resources/jwt_nkey/test.jwt", "src/test/resources/jwt_nkey/test.nk"); + AuthHandler auth = Nats.credentials(jwtResource("test.jwt"), jwtResource("test.nk")); NKey key = NKey.fromSeed(SEED.toCharArray()); byte[] test = "hello world and again".getBytes(StandardCharsets.UTF_8); diff --git a/src/test/java/io/nats/client/impl/AuthViolationDuringReconnectOnFlushTimeoutTest.java b/src/test/java/io/nats/client/impl/AuthViolationDuringReconnectOnFlushTimeoutTest.java index dd3cb2fb8..3c7b938a2 100644 --- a/src/test/java/io/nats/client/impl/AuthViolationDuringReconnectOnFlushTimeoutTest.java +++ b/src/test/java/io/nats/client/impl/AuthViolationDuringReconnectOnFlushTimeoutTest.java @@ -1,28 +1,27 @@ package io.nats.client.impl; import io.nats.client.*; -import io.nats.client.support.NatsUri; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.io.IOException; import java.time.Duration; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /* Test to reproduce #1426 */ +@Isolated public class AuthViolationDuringReconnectOnFlushTimeoutTest { private static final int NUMBER_OF_SUBS = 1_000_000; private static void startServer(Context ctx) throws IOException { - ctx.server.set(new NatsTestServer(new String[]{"--auth", "1234"}, ctx.port, false)); + ctx.server.set(new NatsTestServer(new String[]{"--auth", "1234"}, ctx.port)); } private static void restartServer(Context ctx) { @@ -44,18 +43,15 @@ public void testAuthViolationDuringReconnect() throws Exception { ctx.port = NatsTestServer.nextPort(); startServer(ctx); - Options options = new Options.Builder() - .servers(new String[]{"nats://" + "127.0.0.1:" + ctx.port}) + Options options = optionsBuilder(ctx.port) .noRandomize() .token(new char[]{'1', '2', '3', '4'}) - .maxMessagesInOutgoingQueue(NUMBER_OF_SUBS ) .reconnectBufferSize(NUMBER_OF_SUBS * 100) .connectionTimeout(Duration.ofMillis(10)) .reconnectWait(Duration.ofMillis(2000)) - .connectionListener((conn, e) -> - System.out.println(String.format("Tid: %d, NATS: connection event - %s, connected url: %s. servers: %s ", Thread.currentThread().getId(), e, conn.getConnectedUrl(), conn.getServers()) - )) +// .connectionListener((conn, e) -> +// System.out.printf("Tid: %d, NATS: connection event - %s, connected url: %s. servers: %s %n", Thread.currentThread().getId(), e, conn.getConnectedUrl(), conn.getServers())) .errorListener(ctx.errorListener) .build(); @@ -76,8 +72,8 @@ public void testAuthViolationDuringReconnect() throws Exception { TimeUnit.SECONDS.sleep(20); // give time to restore all subscriptions synchronized(ctx.nc.getStatus()) { - while (ctx.nc.getStatus() != Connection.Status.CONNECTED && ctx.nc.getStatus() != Connection.Status.CLOSED) { - } + //noinspection StatementWithEmptyBody + while (ctx.nc.getStatus() != Connection.Status.CONNECTED && ctx.nc.getStatus() != Connection.Status.CLOSED) {} } assertFalse(ctx.violated.get()); @@ -98,21 +94,20 @@ static class Context implements AutoCloseable { AtomicBoolean violated = new AtomicBoolean(false); CountDownLatch restartsLeft = new CountDownLatch(1); ErrorListener errorListener = new ErrorListener() { - @Override - public void slowConsumerDetected(Connection conn, Consumer consumer) { - System.out.println(String.format("Tid: %d, %s: Slow Consumer", Thread.currentThread().getId(), conn.getConnectedUrl())); - } - - @Override - public void exceptionOccurred(Connection conn, Exception exp) { - exp.printStackTrace(); - System.out.println(String.format("Tid: %d, Nats '%s' exception: %s", Thread.currentThread().getId(), conn.getConnectedUrl(), exp.toString())); - } +// @Override +// public void slowConsumerDetected(Connection conn, Consumer consumer) { +// System.out.printf("Tid: %d, %s: Slow Consumer%n", Thread.currentThread().getId(), conn.getConnectedUrl()); +// } +// +// @Override +// public void exceptionOccurred(Connection conn, Exception exp) { +// exp.printStackTrace(); +// System.out.printf("Tid: %d, Nats '%s' exception: %s%n", Thread.currentThread().getId(), conn.getConnectedUrl(), exp); +// } @Override public void errorOccurred(Connection conn, String error) { - System.out.println(String.format("Tid: %d, Nats '%s': Error %s", Thread.currentThread().getId(), conn.getConnectedUrl(), error.toString())); - +// System.out.printf("Tid: %d, Nats '%s': Error %s%n", Thread.currentThread().getId(), conn.getConnectedUrl(), error); if (error.contains("Authorization Violation")) { violated.set(true); } diff --git a/src/test/java/io/nats/client/impl/ConnectionListenerTests.java b/src/test/java/io/nats/client/impl/ConnectionListenerTests.java index 67b7d5b59..fbb06a6f8 100644 --- a/src/test/java/io/nats/client/impl/ConnectionListenerTests.java +++ b/src/test/java/io/nats/client/impl/ConnectionListenerTests.java @@ -15,6 +15,8 @@ import io.nats.client.*; import io.nats.client.ConnectionListener.Events; +import io.nats.client.support.Listener; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -22,12 +24,14 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; -import static io.nats.client.utils.TestBase.*; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; -public class ConnectionListenerTests { +public class ConnectionListenerTests extends TestBase { @Test public void testToString() { @@ -35,99 +39,85 @@ public void testToString() { } @Test - public void testCloseCount() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - ListenerForTesting listener = new ListenerForTesting(); - Options options = new Options.Builder(). - server(ts.getURI()). - connectionListener(listener). - build(); - Connection nc = standardConnection(options); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - standardCloseConnection(nc); + public void testCloseEvent() throws Exception { + Listener listener = new Listener(); + listener.queueConnectionEvent(Events.CLOSED); + Options.Builder builder = optionsBuilder().connectionListener(listener); + runInSharedOwnNc(builder, nc -> { + closeAndConfirm(nc); assertNull(nc.getConnectedUrl()); - assertEquals(1, listener.getEventCount(Events.CLOSED)); - } + }); + listener.validate(); } @Test public void testDiscoveredServersCountAndListenerInOptions() throws Exception { - - try (NatsTestServer ts = new NatsTestServer()) { - String customInfo = "{\"server_id\":\"myid\", \"version\":\"9.9.99\",\"connect_urls\": [\""+ts.getURI()+"\"]}"; - try (NatsServerProtocolMock ts2 = new NatsServerProtocolMock(null, customInfo)) { - ListenerForTesting listener = new ListenerForTesting(); - Options options = new Options.Builder(). - server(ts2.getURI()). - maxReconnects(0). - connectionListener(listener). - build(); - - listener.prepForStatusChange(Events.CONNECTED); - standardCloseConnection( listenerConnectionWait(options, listener) ); - assertEquals(1, listener.getEventCount(Events.DISCOVERED_SERVERS)); + runInSharedServer(ts -> { + String customInfo = "{\"server_id\":\"myid\", \"version\":\"9.9.99\",\"connect_urls\": [\""+ts.getServerUri()+"\"]}"; + try (NatsServerProtocolMock mockTs2 = new NatsServerProtocolMock(null, customInfo)) { + Listener listener = new Listener(); + Options options = optionsBuilder(mockTs2) + .maxReconnects(0) + .connectionListener(listener) + .build(); + listener.queueConnectionEvent(Events.DISCOVERED_SERVERS); + try (Connection ignore = standardConnect(options)) { + listener.validate(); + } } - } + }); } @Test public void testDisconnectReconnectCount() throws Exception { int port; Connection nc; - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder(). - server(ts.getURI()). - reconnectWait(Duration.ofMillis(100)). - maxReconnects(-1). - connectionListener(listener). - build(); + Listener listener = new Listener(); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) + .reconnectWait(Duration.ofMillis(100)) + .maxReconnects(-1) + .connectionListener(listener) + .build(); port = ts.getPort(); - nc = standardConnection(options); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - listener.prepForStatusChange(Events.DISCONNECTED); + nc = managedConnect(options); + assertEquals(ts.getServerUri(), nc.getConnectedUrl()); + listener.queueConnectionEvent(Events.DISCONNECTED); } try { nc.flush(Duration.ofMillis(250)); } catch (Exception exp) { /* ignored */ } - listener.waitForStatusChange(1000, TimeUnit.MILLISECONDS); - assertTrue(listener.getEventCount(Events.DISCONNECTED) >= 1); + listener.validate(); assertNull(nc.getConnectedUrl()); - try (NatsTestServer ts = new NatsTestServer(port, false)) { - standardConnectionWait(nc); - assertEquals(1, listener.getEventCount(Events.RECONNECTED)); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - standardCloseConnection(nc); + listener.queueConnectionEvent(Events.RECONNECTED); + try (NatsTestServer ts = new NatsTestServer(port)) { + confirmConnected(nc); // wait for reconnect + listener.validate(); + assertEquals(ts.getServerUri(), nc.getConnectedUrl()); + closeAndConfirm(nc); } } @Test public void testExceptionInConnectionListener() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - BadHandler listener = new BadHandler(); - Options options = new Options.Builder(). - server(ts.getURI()). - connectionListener(listener). - build(); - Connection nc = standardConnection(options); - standardCloseConnection(nc); - assertTrue(((NatsConnection)nc).getStatisticsCollector().getExceptions() > 0); - } + BadHandler badHandler = new BadHandler(); + Options.Builder builder = optionsBuilder().connectionListener(badHandler); + AtomicReference stats = new AtomicReference<>(); + runInSharedOwnNc(builder, nc -> stats.set(nc.getStatistics())); + sleep(100); // it needs time here + assertTrue(stats.get().getExceptions() > 0); } @Test public void testMultipleConnectionListeners() throws Exception { Set capturedEvents = ConcurrentHashMap.newKeySet(); - - try (NatsTestServer ts = new NatsTestServer(false)) { - ListenerForTesting listener = new ListenerForTesting(); - Options options = new Options.Builder(). - server(ts.getURI()). - connectionListener(listener). - build(); - Connection nc = standardConnection(options); - assertEquals(ts.getURI(), nc.getConnectedUrl()); + Listener listener = new Listener(); + listener.queueConnectionEvent(Events.CLOSED); + AtomicReference stats = new AtomicReference<>(); + Options.Builder builder = optionsBuilder().connectionListener(listener); + runInSharedOwnNc(builder, nc -> { + stats.set(nc.getStatistics()); //noinspection DataFlowIssue // addConnectionListener parameter is annotated as @NonNull assertThrows(NullPointerException.class, () -> nc.addConnectionListener(null)); @@ -143,18 +133,18 @@ public void testMultipleConnectionListeners() throws Exception { nc.addConnectionListener((conn, event) -> capturedEvents.add("CL4-" + event.name())); nc.removeConnectionListener(removedConnectionListener); - standardCloseConnection(nc); + closeAndConfirm(nc); assertNull(nc.getConnectedUrl()); - assertEquals(1, listener.getEventCount(Events.CLOSED)); - assertTrue(((NatsConnection)nc).getStatisticsCollector().getExceptions() > 0); - } + }); + + assertTrue(stats.get().getExceptions() > 0); + listener.validate(); Set expectedEvents = new HashSet<>(Arrays.asList( "CL1-CLOSED", "CL2-CLOSED", "CL3-CLOSED", "CL4-CLOSED")); - assertEquals(expectedEvents, capturedEvents); } diff --git a/src/test/java/io/nats/client/impl/DispatcherTests.java b/src/test/java/io/nats/client/impl/DispatcherTests.java index dc69bc526..64b0478ec 100644 --- a/src/test/java/io/nats/client/impl/DispatcherTests.java +++ b/src/test/java/io/nats/client/impl/DispatcherTests.java @@ -14,9 +14,9 @@ package io.nats.client.impl; import io.nats.client.*; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; @@ -25,23 +25,123 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import static io.nats.client.utils.TestBase.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; - // Some tests are a bit tricky, and depend on the fact that the dispatcher -// uses a single queue, so the "subject" messages go through before +// uses a single queue, so the subject messages go through before // the done message (or should) - wanted to note that somewhere -public class DispatcherTests { +public class DispatcherTests extends TestBase { + + @Test + public void testDispatcherSubscribingExceptions() throws Exception { + // InvalidSubjectsAndQueueNames + runInShared(nc -> { + + Dispatcher dx = nc.createDispatcher(m -> { + }); + for (String bad : BAD_SUBJECTS_OR_QUEUES) { + assertThrows(IllegalArgumentException.class, () -> nc.subscribe(bad)); + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(bad)); + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(bad, m -> { + })); + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(bad, "q")); + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(bad, "q", m -> { + })); + assertThrows(IllegalArgumentException.class, () -> nc.subscribe("s", bad)); + assertThrows(IllegalArgumentException.class, () -> dx.subscribe("s", bad)); + assertThrows(IllegalArgumentException.class, () -> dx.subscribe("s", bad, m -> { + })); + } + + String subject = random(); + dx.subscribe(subject); + + // can't subscribe to empty subject -> subject, handler + assertThrows(IllegalArgumentException.class, () -> dx.subscribe("", msg -> { + })); + + // can't subscribe to null handler -> subject, handler + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(random(), (MessageHandler) null)); + + // can't subscribe null subject -> subject, queue, handler + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(null, random(), msg -> { + })); + + // can't subscribe empty subject -> subject, queue, handler + assertThrows(IllegalArgumentException.class, () -> dx.subscribe("", random(), msg -> { + })); + + // can't subscribe with null queue -> subject, queue, handler + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(random(), null, msg -> { + })); + + // can't subscribe with empty queue -> subject, queue, handler + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(random(), "", msg -> { + })); + + // can't subscribe with null handler -> subject, queue, handler + assertThrows(IllegalArgumentException.class, () -> dx.subscribe(random(), random(), null)); + + // can't unsubscribe with null subject + assertThrows(IllegalArgumentException.class, () -> dx.unsubscribe((String) null)); + + // can't unsubscribe with empty subject + assertThrows(IllegalArgumentException.class, () -> dx.unsubscribe("")); + + nc.closeDispatcher(dx); + + // can't close if already closed + assertThrows(IllegalArgumentException.class, () -> nc.closeDispatcher(dx)); + + // can't unsubscribe if dispatcher is closed + assertThrows(IllegalStateException.class, () -> dx.unsubscribe(subject)); + + // can't subscribe if dispatcher is closed + assertThrows(IllegalStateException.class, () -> dx.subscribe(random())); + + // If dispatcher was made without a default handler, + // you must subscribe with a specific handler + Dispatcher dNoHandler = nc.createDispatcher(); + dNoHandler.subscribe(random(), m -> { + }); // This is fine + IllegalStateException ise = assertThrows(IllegalStateException.class, () -> dNoHandler.subscribe(random())); + assertTrue(ise.getMessage().contains("Dispatcher was made without a default handler.")); + + nc.closeDispatcher(dNoHandler); + }); + } + @Test - public void testDispatcherMultipleSubscriptionsBySubject() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - String subject1 = subject(); - String subject2 = subject(); + public void throwsOnCreateIfConnectionClosed() throws Exception { + runInSharedOwnNc(nc -> { + Dispatcher d = nc.createDispatcher(msg -> {}); + // close connection + nc.close(); + + // can't create if connection is closed + assertThrows(IllegalStateException.class, () -> nc.createDispatcher(msg -> {})); + + // can't subscribe if connection is closed + assertThrows(IllegalStateException.class, () -> d.subscribe(random())); + + // can't subscribe if connection is closed + assertThrows(IllegalStateException.class, () -> d.subscribe(random())); + + // can't close dispatcher if connection is closed + assertThrows(IllegalStateException.class, () -> nc.closeDispatcher(d)); + }); + } + + @Test + public void testProperlyUnsubscribeBySubject() throws Exception { + runInShared(nc -> { + // MultipleSubscriptionsBySubject + String subject1 = random(); + String subject2 = random(); List dflt = Collections.synchronizedList(new ArrayList<>()); List sub21 = Collections.synchronizedList(new ArrayList<>()); @@ -74,7 +174,7 @@ public void testDispatcherMultipleSubscriptionsBySubject() throws Exception { assertFalse(sub22.contains(2)); assertTrue(sub31.contains(2)); assertTrue(sub32.contains(2)); - } + }); } private static int getDataId(Message m) { @@ -83,113 +183,113 @@ private static int getDataId(Message m) { @Test public void testSingleMessage() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); - + runInShared(nc -> { final CompletableFuture msgFuture = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - msgFuture.complete(msg); - }); + Dispatcher d = nc.createDispatcher(msgFuture::complete); - d.subscribe("subject"); + String subject = random(); + d.subscribe(subject); nc.flush(Duration.ofMillis(500));// Get them all to the server - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); Message msg = msgFuture.get(500, TimeUnit.MILLISECONDS); assertTrue(d.isActive()); - assertEquals("subject", msg.getSubject()); + assertEquals(subject, msg.getSubject()); assertNotNull(msg.getSubscription()); assertNull(msg.getReplyTo()); assertEquals(16, msg.getData().length); - } + + nc.closeDispatcher(d); + }); } @Test public void testDispatcherMessageContainsConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); - + runInShared(nc -> { final CompletableFuture msgFuture = new CompletableFuture<>(); final CompletableFuture connFuture = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { msgFuture.complete(msg); connFuture.complete(msg.getConnection()); }); - d.subscribe("subject"); + String subject = random(); + d.subscribe(subject); nc.flush(Duration.ofMillis(5000));// Get them all to the server - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); Message msg = msgFuture.get(5000, TimeUnit.MILLISECONDS); Connection conn = connFuture.get(5000, TimeUnit.MILLISECONDS); assertTrue(d.isActive()); - assertEquals("subject", msg.getSubject()); + assertEquals(subject, msg.getSubject()); assertNotNull(msg.getSubscription()); assertNull(msg.getReplyTo()); assertEquals(16, msg.getData().length); - assertTrue(conn == nc); - } + assertSame(conn, nc); + + nc.closeDispatcher(d); + }); } @Test public void testMultiSubject() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); - + runInShared(nc -> { final CompletableFuture one = new CompletableFuture<>(); final CompletableFuture two = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("one")) { + String subject1 = random(); + String subject2 = random(); + Dispatcher d = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(subject1)) { one.complete(msg); - } else if (msg.getSubject().equals("two")) { + } + else if (msg.getSubject().equals(subject2)) { two.complete(msg); } }); - d.subscribe("one"); - d.subscribe("two"); + d.subscribe(subject1); + d.subscribe(subject2); nc.flush(Duration.ofMillis(500));// Get them all to the server - nc.publish("one", new byte[16]); - nc.publish("two", new byte[16]); + nc.publish(subject1, new byte[16]); + nc.publish(subject2, new byte[16]); Message msg = one.get(500, TimeUnit.MILLISECONDS); - assertEquals("one", msg.getSubject()); + assertEquals(subject1, msg.getSubject()); msg = two.get(500, TimeUnit.MILLISECONDS); - assertEquals("two", msg.getSubject()); - } + assertEquals(subject2, msg.getSubject()); + + nc.closeDispatcher(d); + }); } @Test public void testMultiMessage() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { + runInShared(nc -> { final CompletableFuture done = new CompletableFuture<>(); int msgCount = 100; - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { if (msg.getSubject().equals("done")) { done.complete(Boolean.TRUE); - } else { + } + else { q.add(msg); } }); - d.subscribe("subject"); + String subject = random(); + d.subscribe(subject); d.subscribe("done"); nc.flush(Duration.ofMillis(1000)); // wait for them to go through for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } nc.publish("done", new byte[16]); nc.flush(Duration.ofMillis(1000)); // wait for them to go through @@ -197,60 +297,59 @@ public void testMultiMessage() throws Exception { done.get(500, TimeUnit.MILLISECONDS); assertEquals(msgCount, q.size()); - } + }); } @Test - public void testClose() { - assertThrows(TimeoutException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture phase1 = new CompletableFuture<>(); - final CompletableFuture phase2 = new CompletableFuture<>(); - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); - - final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("phase1")) { - phase1.complete(Boolean.TRUE); - } else if (msg.getSubject().equals("phase1")) { - phase2.complete(Boolean.TRUE); - } else { - q.add(msg); - } - }); + public void testClosedDispatcherBehavior() throws Exception { + runInShared(nc -> { + final CompletableFuture fPhase1 = new CompletableFuture<>(); + final CompletableFuture fPhase2 = new CompletableFuture<>(); - d.subscribe("subject"); - d.subscribe("phase1"); - d.subscribe("phase2"); - nc.flush(Duration.ofMillis(500));// Get them all to the server + final ConcurrentLinkedQueue received = new ConcurrentLinkedQueue<>(); + String subject = random(); + String phase1 = random(); + String phase2 = random(); + Dispatcher d = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(phase1)) { + fPhase1.complete(Boolean.TRUE); + } + else if (msg.getSubject().equals(phase2)) { + fPhase2.complete(Boolean.TRUE); + } + else { + received.add(msg); + } + }); + + d.subscribe(subject); + d.subscribe(phase1); + d.subscribe(phase2); + nc.flush(Duration.ofMillis(500));// Get them all to the server - nc.publish("subject", new byte[16]); - nc.publish("phase1", null); + nc.publish(subject, new byte[16]); + nc.publish(phase1, null); - nc.flush(Duration.ofMillis(1000)); // wait for them to go through - phase1.get(200, TimeUnit.MILLISECONDS); + nc.flush(Duration.ofMillis(1000)); // wait for them to go through + fPhase1.get(200, TimeUnit.MILLISECONDS); - assertEquals(1, q.size()); + assertEquals(1, received.size()); - nc.closeDispatcher(d); + nc.closeDispatcher(d); - assertFalse(d.isActive()); + assertFalse(d.isActive()); - // This won't arrive - nc.publish("phase2", new byte[16]); + // This won't arrive + nc.publish(phase2, new byte[16]); - nc.flush(Duration.ofMillis(1000)); // wait for them to go through - phase2.get(200, TimeUnit.MILLISECONDS); - } + nc.flush(Duration.ofMillis(1000)); // wait for them to go through + assertThrows(TimeoutException.class, () -> fPhase2.get(200, TimeUnit.MILLISECONDS)); }); } - @Test public void testQueueSubscribers() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { + runInShared(nc -> { int msgs = 100; AtomicInteger received = new AtomicInteger(); AtomicInteger sub1Count = new AtomicInteger(); @@ -259,37 +358,41 @@ public void testQueueSubscribers() throws Exception { final CompletableFuture done1 = new CompletableFuture<>(); final CompletableFuture done2 = new CompletableFuture<>(); - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + String subject = random(); + String done = random(); + String queue = random(); - Dispatcher d1 = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("done")) { + Dispatcher d1 = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(done)) { done1.complete(Boolean.TRUE); - } else { + } + else { sub1Count.incrementAndGet(); received.incrementAndGet(); } }); - Dispatcher d2 = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("done")) { + Dispatcher d2 = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(done)) { done2.complete(Boolean.TRUE); - } else { + } + else { sub2Count.incrementAndGet(); received.incrementAndGet(); } }); - d1.subscribe("subject", "queue"); - d2.subscribe("subject", "queue"); - d1.subscribe("done"); - d2.subscribe("done"); + d1.subscribe(subject, queue); + d2.subscribe(subject, queue); + d1.subscribe(done); + d2.subscribe(done); nc.flush(Duration.ofMillis(500)); for (int i = 0; i < msgs; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("done", null); + nc.publish(done, null); nc.flush(Duration.ofMillis(500)); done1.get(500, TimeUnit.MILLISECONDS); @@ -298,692 +401,479 @@ public void testQueueSubscribers() throws Exception { assertEquals(msgs, received.get()); assertEquals(msgs, sub1Count.get() + sub2Count.get()); - // They won't be equal but print to make sure they are close (human testing) - System.out.println("### Sub 1 " + sub1Count.get()); - System.out.println("### Sub 2 " + sub2Count.get()); - } + nc.closeDispatcher(d1); + nc.closeDispatcher(d2); + }); } @Test - public void testCantUnsubSubFromDispatcher() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) - { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + public void testCantUnsubSubFromDispatcher() throws Exception { + runInShared(nc -> { + final CompletableFuture msgFuture = new CompletableFuture<>(); + Dispatcher d = nc.createDispatcher(msgFuture::complete); - final CompletableFuture msgFuture = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - msgFuture.complete(msg); - }); + String subject = random(); + d.subscribe(subject); + nc.flush(Duration.ofMillis(500));// Get them all to the server - d.subscribe("subject"); - nc.flush(Duration.ofMillis(500));// Get them all to the server + nc.publish(subject, new byte[16]); - nc.publish("subject", new byte[16]); + Message msg = msgFuture.get(500, TimeUnit.MILLISECONDS); - Message msg = msgFuture.get(500, TimeUnit.MILLISECONDS); + assertThrows(IllegalStateException.class, () -> msg.getSubscription().unsubscribe()); - msg.getSubscription().unsubscribe(); // Should throw - assertFalse(true); - } + nc.closeDispatcher(d); }); } @Test - public void testCantAutoUnsubSubFromDispatcher() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) - { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + public void testCantAutoUnsubSubFromDispatcher() throws Exception { + runInShared(nc -> { + final CompletableFuture msgFuture = new CompletableFuture<>(); + Dispatcher d = nc.createDispatcher(msgFuture::complete); - final CompletableFuture msgFuture = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - msgFuture.complete(msg); - }); + String subject = random(); + d.subscribe(subject); + nc.flush(Duration.ofMillis(500));// Get them all to the server - d.subscribe("subject"); - nc.flush(Duration.ofMillis(500));// Get them all to the server + nc.publish(subject, new byte[16]); - nc.publish("subject", new byte[16]); + Message msg = msgFuture.get(500, TimeUnit.MILLISECONDS); - Message msg = msgFuture.get(500, TimeUnit.MILLISECONDS); + assertThrows(IllegalStateException.class, () -> msg.getSubscription().unsubscribe(1)); - msg.getSubscription().unsubscribe(1); // Should throw - assertFalse(true); - } + nc.closeDispatcher(d); }); } @Test - public void testPublishAndFlushFromCallback() - throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + public void testPublishAndFlushFromCallback() throws Exception { + runInShared(nc -> { + String subject = random(); + + long startCount = nc.getStatistics().getFlushCounter(); final CompletableFuture msgFuture = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { try { nc.flush(Duration.ofMillis(1000)); - } catch (Exception ex) { + } + catch (Exception ex) { ex.printStackTrace(); } msgFuture.complete(msg); }); - d.subscribe("subject"); + d.subscribe(subject); nc.flush(Duration.ofMillis(500));// Get them all to the server - nc.publish("subject", new byte[16]); // publish one to kick it off + nc.publish(subject, new byte[16]); // publish one to kick it off Message msg = msgFuture.get(500, TimeUnit.MILLISECONDS); assertNotNull(msg); - assertEquals(2, ((NatsStatistics)(nc.getStatistics())).getFlushCounter()); - } + long diffCount = nc.getStatistics().getFlushCounter() - startCount; + assertEquals(2, diffCount); + nc.closeDispatcher(d); + }); } @Test public void testUnsub() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture phase1 = new CompletableFuture<>(); - final CompletableFuture phase2 = new CompletableFuture<>(); + runInShared(nc -> { + final CompletableFuture fPhase1 = new CompletableFuture<>(); + final CompletableFuture fPhase2 = new CompletableFuture<>(); int msgCount = 10; - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + + String subject = random(); + String phase1 = random(); + String phase2 = random(); final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("phase1")) { - phase1.complete(Boolean.TRUE); - } else if (msg.getSubject().equals("phase2")) { - phase2.complete(Boolean.TRUE); - } else { + Dispatcher d = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(phase1)) { + fPhase1.complete(Boolean.TRUE); + } + else if (msg.getSubject().equals(phase2)) { + fPhase2.complete(Boolean.TRUE); + } + else { q.add(msg); } }); - d.subscribe("subject"); - d.subscribe("phase1"); - d.subscribe("phase2"); + d.subscribe(subject); + d.subscribe(phase1); + d.subscribe(phase2); nc.flush(Duration.ofMillis(1000));// Get them all to the server for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("phase1", new byte[16]); + nc.publish(phase1, new byte[16]); nc.flush(Duration.ofMillis(1000)); // wait for them to go through - phase1.get(5000, TimeUnit.MILLISECONDS); + fPhase1.get(5000, TimeUnit.MILLISECONDS); - d.unsubscribe("subject"); + d.unsubscribe(subject); nc.flush(Duration.ofMillis(1000));// Get them all to the server for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("phase2", new byte[16]); + nc.publish(phase2, new byte[16]); nc.flush(Duration.ofMillis(1000)); // wait for them to go through - phase2.get(1000, TimeUnit.MILLISECONDS); // make sure we got them + fPhase2.get(1000, TimeUnit.MILLISECONDS); // make sure we got them assertEquals(msgCount, q.size()); - } + + nc.closeDispatcher(d); + }); } @Test public void testAutoUnsub() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture phase1 = new CompletableFuture<>(); - final CompletableFuture phase2 = new CompletableFuture<>(); + runInShared(nc -> { + final CompletableFuture fPhase1 = new CompletableFuture<>(); + final CompletableFuture fPhase2 = new CompletableFuture<>(); int msgCount = 100; - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + + String subject = random(); + String phase1 = random(); + String phase2 = random(); final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - NatsDispatcher d = (NatsDispatcher) nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("phase1")) { - phase1.complete(Boolean.TRUE); - }else if (msg.getSubject().equals("phase2")) { - phase2.complete(Boolean.TRUE); - } else { + NatsDispatcher d = (NatsDispatcher) nc.createDispatcher(msg -> { + if (msg.getSubject().equals(phase1)) { + fPhase1.complete(Boolean.TRUE); + } + else if (msg.getSubject().equals(phase2)) { + fPhase2.complete(Boolean.TRUE); + } + else { q.add(msg); } }); - d.subscribe("subject"); - d.subscribe("phase1"); - d.subscribe("phase2"); + d.subscribe(subject); + d.subscribe(phase1); + d.subscribe(phase2); nc.flush(Duration.ofMillis(500));// Get them all to the server for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("phase1", new byte[16]); + nc.publish(phase1, new byte[16]); nc.flush(Duration.ofMillis(1000)); // wait for them to go through - phase1.get(1000, TimeUnit.MILLISECONDS); // make sure we got them + fPhase1.get(1000, TimeUnit.MILLISECONDS); // make sure we got them assertEquals(msgCount, q.size()); - d.unsubscribe("subject", msgCount + 1); + d.unsubscribe(subject, msgCount + 1); for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("phase2", new byte[16]); + nc.publish(phase2, new byte[16]); nc.flush(Duration.ofMillis(1000)); // Wait for it all to get processed - phase2.get(1000, TimeUnit.MILLISECONDS); // make sure we got them + fPhase2.get(1000, TimeUnit.MILLISECONDS); // make sure we got them assertEquals(msgCount + 1, q.size()); - } + + nc.closeDispatcher(d); + }); } @Test public void testUnsubFromCallback() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture done = new CompletableFuture<>(); - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + runInShared(nc -> { + final CompletableFuture fDone = new CompletableFuture<>(); + String subject = random(); + String done = random(); final AtomicReference dispatcher = new AtomicReference<>(); final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - final Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("done")) { - done.complete(Boolean.TRUE); - } else { + final Dispatcher d = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(done)) { + fDone.complete(Boolean.TRUE); + } + else { q.add(msg); - dispatcher.get().unsubscribe("subject"); + dispatcher.get().unsubscribe(subject); } }); dispatcher.set(d); - d.subscribe("subject"); - d.subscribe("done"); + d.subscribe(subject); + d.subscribe(done); nc.flush(Duration.ofMillis(500));// Get them all to the server - nc.publish("subject", new byte[16]); - nc.publish("subject", new byte[16]); - nc.publish("done", new byte[16]); // when we get this we know the others are dispatched + nc.publish(subject, new byte[16]); + nc.publish(subject, new byte[16]); + nc.publish(done, new byte[16]); // when we get this we know the others are dispatched nc.flush(Duration.ofMillis(1000)); // Wait for the publish, or we will get multiples for sure - done.get(200, TimeUnit.MILLISECONDS); // make sure we got them + fDone.get(200, TimeUnit.MILLISECONDS); // make sure we got them assertEquals(1, q.size()); - } + + nc.closeDispatcher(d); + }); } @Test - public void testAutoUnsubFromCallback() - throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture done = new CompletableFuture<>(); - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + public void testAutoUnsubFromCallback() throws Exception { + runInShared(nc -> { + final CompletableFuture fDone = new CompletableFuture<>(); + String subject = random(); + String done = random(); final AtomicReference dispatcher = new AtomicReference<>(); final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - final Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("done")) { - done.complete(Boolean.TRUE); - } else { + final Dispatcher d = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(done)) { + fDone.complete(Boolean.TRUE); + } + else { q.add(msg); - dispatcher.get().unsubscribe("subject", 2); // get 1 more, for a total of 2 + dispatcher.get().unsubscribe(subject, 2); // get 1 more, for a total of 2 } }); dispatcher.set(d); - d.subscribe("subject"); - d.subscribe("done"); + d.subscribe(subject); + d.subscribe(done); nc.flush(Duration.ofMillis(1000));// Get them all to the server - nc.publish("subject", new byte[16]); - nc.publish("subject", new byte[16]); - nc.publish("subject", new byte[16]); - nc.publish("done", new byte[16]); // when we get this we know the others are dispatched + nc.publish(subject, new byte[16]); + nc.publish(subject, new byte[16]); + nc.publish(subject, new byte[16]); + nc.publish(done, new byte[16]); // when we get this we know the others are dispatched nc.flush(Duration.ofMillis(1000)); // Wait for the publish - done.get(200, TimeUnit.MILLISECONDS); // make sure we got them + fDone.get(200, TimeUnit.MILLISECONDS); // make sure we got them assertEquals(2, q.size()); - } + + nc.closeDispatcher(d); + }); } @Test public void testCloseFromCallback() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture done = new CompletableFuture<>(); - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + // custom connection since we must close it. + runInSharedOwnNc(nc -> { + final CompletableFuture fDone = new CompletableFuture<>(); - final Dispatcher d = nc.createDispatcher((msg) -> { + String subject = random(); + final Dispatcher d = nc.createDispatcher(msg -> { try { - if (msg.getSubject().equals("done")) { + if (msg.getSubject().equals(subject)) { nc.close(); - done.complete(Boolean.TRUE); + fDone.complete(Boolean.TRUE); } - } catch (InterruptedException e) { + } + catch (InterruptedException e) { e.printStackTrace(); } }); - d.subscribe("done"); + d.subscribe(subject); sleep(500); // Making sure the "subscribe" has been registered on the server - nc.publish("done", new byte[16]); + nc.publish(subject, new byte[16]); - done.get(5000, TimeUnit.MILLISECONDS); - - waitUntilStatus(nc, 5000, Connection.Status.CLOSED); - } + fDone.get(5000, TimeUnit.MILLISECONDS); + }); } @Test public void testDispatchHandlesExceptionInHandler() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture done = new CompletableFuture<>(); + runInShared(nc -> { + final CompletableFuture fDone = new CompletableFuture<>(); int msgCount = 100; - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + String subject = random(); + String done = random(); final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("done")) { - done.complete(Boolean.TRUE); - } else { + Dispatcher d = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(done)) { + fDone.complete(Boolean.TRUE); + } + else { q.add(msg); throw new NumberFormatException(); } }); - d.subscribe("subject"); - d.subscribe("done"); + d.subscribe(subject); + d.subscribe(done); nc.flush(Duration.ofMillis(500));// Get them all to the server for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("done", new byte[16]); + nc.publish(done, new byte[16]); nc.flush(Duration.ofMillis(1000)); // wait for them to go through - done.get(200, TimeUnit.MILLISECONDS); + fDone.get(200, TimeUnit.MILLISECONDS); assertEquals(msgCount, q.size()); - } - } - - @Test - public void testThrowOnNullSubject() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe(null); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnEmptySubject() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - - d.subscribe(""); - assertFalse(true); - } - }); - } - @Test - public void testThrowOnEmptyQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("subject", ""); - assertFalse(true); - } + nc.closeDispatcher(d); }); } @Test - public void testThrowOnNullSubjectWithQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe(null, "quque"); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnEmptySubjectWithQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("", "quque"); - assertFalse(true); - } - }); - } - - @Test - public void throwsOnCreateIfClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.close(); - nc.createDispatcher((msg) -> {}); - assertFalse(true); - } - }); - } - - @Test - public void throwsOnSubscribeIfClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - nc.close(); - d.subscribe("subject"); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnSubscribeWhenClosed() throws IOException, InterruptedException, TimeoutException { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - nc.closeDispatcher(d); - d.subscribe("foo"); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnUnsubscribeWhenClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("foo"); - nc.closeDispatcher(d); - d.unsubscribe("foo"); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnDoubleClose() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - nc.closeDispatcher(d); - nc.closeDispatcher(d); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnConnClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - nc.close(); - nc.closeDispatcher(d); - assertFalse(true); - } + public void testThrowOnBadInput() throws Exception { + runInShared(nc -> { + Dispatcher d = nc.createDispatcher(msg -> { + }); + // Null Subject + assertThrows(IllegalArgumentException.class, () -> d.subscribe(null)); + // Empty Subject + assertThrows(IllegalArgumentException.class, () -> d.subscribe("")); + // Empty Subject + assertThrows(IllegalArgumentException.class, () -> d.subscribe("")); + // Null Subject With Queue + assertThrows(IllegalArgumentException.class, () -> d.subscribe(null, random())); + // Empty Subject With Queue + assertThrows(IllegalArgumentException.class, () -> d.subscribe("", random())); + nc.closeDispatcher(d); }); } @Test public void testDoubleSubscribe() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture done = new CompletableFuture<>(); + runInShared(nc -> { + final CompletableFuture fDone = new CompletableFuture<>(); int msgCount = 100; - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + + String subject = random(); + String done = random(); final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue<>(); - Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("done")) { - done.complete(Boolean.TRUE); - } else { + Dispatcher d = nc.createDispatcher(msg -> { + if (msg.getSubject().equals(done)) { + fDone.complete(Boolean.TRUE); + } + else { q.add(msg); } }); - d.subscribe("subject").subscribe("subject").subscribe("subject").subscribe("done"); + d.subscribe(subject).subscribe(subject).subscribe(subject).subscribe(done); nc.flush(Duration.ofSeconds(5)); // wait for them to go through for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("done", new byte[16]); + nc.publish(done, new byte[16]); nc.flush(Duration.ofSeconds(5)); // wait for them to go through - done.get(5, TimeUnit.SECONDS); + fDone.get(5, TimeUnit.SECONDS); + + assertEquals(msgCount, q.size()); // Should only get one since all the extra subs do nothing?? - assertEquals(msgCount, q.size()); // Shoudl only get one since all the extra subs do nothing?? - } + nc.closeDispatcher(d); + }); } @Test public void testDoubleSubscribeWithCustomHandler() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture done = new CompletableFuture<>(); + runInShared(nc -> { + final CompletableFuture fDone = new CompletableFuture<>(); int msgCount = 100; - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); final AtomicInteger count = new AtomicInteger(0); - Dispatcher d = nc.createDispatcher((msg) -> {}); + Dispatcher d = nc.createDispatcher(msg -> { + }); - d.subscribe("subject", (msg) -> { count.incrementAndGet(); }); - d.subscribe("subject", "queue", (msg) -> { count.incrementAndGet(); }); - d.subscribe("done", (msg) -> { done.complete(Boolean.TRUE); }); + String subject = random(); + String done = random(); + String queue = random(); + d.subscribe(subject, msg -> count.incrementAndGet()); + d.subscribe(subject, queue, msg -> count.incrementAndGet()); + d.subscribe(done, msg -> fDone.complete(Boolean.TRUE)); nc.flush(Duration.ofSeconds(5)); // wait for them to go through for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("done", new byte[16]); + nc.publish(done, new byte[16]); nc.flush(Duration.ofSeconds(5)); // wait for them to go through - done.get(5, TimeUnit.SECONDS); + fDone.get(5, TimeUnit.SECONDS); assertEquals(msgCount * 2, count.get()); // We should get 2x the messages because we subscribed 2 times. - } + + nc.closeDispatcher(d); + }); } @Test public void testDoubleSubscribeWithUnsubscribeAfterWithCustomHandler() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - final CompletableFuture done1 = new CompletableFuture<>(); - final CompletableFuture done2 = new CompletableFuture<>(); + runInShared(nc -> { + final CompletableFuture fDone1 = new CompletableFuture<>(); + final CompletableFuture fDone2 = new CompletableFuture<>(); int msgCount = 100; - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + String subject = random(); + String done = random(); final AtomicInteger count = new AtomicInteger(0); - Dispatcher d = nc.createDispatcher((msg) -> {}); - Subscription s1 = d.subscribe("subject", (msg) -> { count.incrementAndGet(); }); - Subscription doneSub = d.subscribe("done", (msg) -> { done1.complete(Boolean.TRUE); }); - d.subscribe("subject", (msg) -> { count.incrementAndGet(); }); + Dispatcher d = nc.createDispatcher(msg -> { + }); + Subscription s1 = d.subscribe(subject, msg -> count.incrementAndGet()); + Subscription doneSub = d.subscribe(done, msg -> fDone1.complete(Boolean.TRUE)); + d.subscribe(subject, msg -> count.incrementAndGet()); nc.flush(Duration.ofSeconds(5)); // wait for the subs to go through for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("done", new byte[16]); + nc.publish(done, new byte[16]); nc.flush(Duration.ofSeconds(5)); // wait for the messages to go through - done1.get(5, TimeUnit.SECONDS); + fDone1.get(5, TimeUnit.SECONDS); assertEquals(msgCount * 2, count.get()); // We should get 2x the messages because we subscribed 2 times. count.set(0); d.unsubscribe(s1); d.unsubscribe(doneSub); - d.subscribe("done", (msg) -> { done2.complete(Boolean.TRUE); }); + d.subscribe(done, msg -> fDone2.complete(Boolean.TRUE)); nc.flush(Duration.ofSeconds(5)); // wait for the unsub to go through for (int i = 0; i < msgCount; i++) { - nc.publish("subject", new byte[16]); + nc.publish(subject, new byte[16]); } - nc.publish("done", new byte[16]); + nc.publish(done, new byte[16]); nc.flush(Duration.ofSeconds(5)); // wait for the messages to go through - done2.get(5, TimeUnit.SECONDS); + fDone2.get(5, TimeUnit.SECONDS); assertEquals(msgCount, count.get()); // We only have 1 active subscription, so we should only get msgCount. - } - } - - @Test - public void testThrowOnEmptySubjectWithMessageHandler() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("", (msg) -> {}); - assertFalse(true); - } - }); - } - @Test - public void testThrowOnNullHandler() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("test", (MessageHandler)null); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnNullHandlerWithQueue() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("test", "queue", (MessageHandler)null); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnEmptyQueueWithMessageHandler() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("subject", "", (msg) -> {}); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnNullSubjectWithQueueWithMessageHandler() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe(null, "quque", (msg) -> {}); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnEmptySubjectWithQueueWithMessageHandler() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.subscribe("", "quque", (msg) -> {}); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnEmptySubjectInUnsub() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - d.unsubscribe(""); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnUnsubWhenClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - Subscription sub = d.subscribe("subject", (msg) -> {}); - nc.closeDispatcher(d); - d.unsubscribe(sub); - assertFalse(true); - } - }); - } - - @Test - public void testThrowOnWrongSubscription() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - Dispatcher d = nc.createDispatcher((msg) -> {}); - Subscription sub2 = nc.subscribe("test"); - d.unsubscribe(sub2); - assertFalse(true); - } + nc.closeDispatcher(d); }); } @Test public void testDispatcherFactoryCoverage() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(Options.builder().server(ts.getURI()).useDispatcherWithExecutor().build())) - { + runInSharedOwnNc(optionsBuilder().useDispatcherWithExecutor(), nc -> { CountDownLatch latch = new CountDownLatch(1); - Dispatcher d = nc.createDispatcher((msg) -> latch.countDown()); + Dispatcher d = nc.createDispatcher(msg -> latch.countDown()); assertInstanceOf(NatsDispatcherWithExecutor.class, d); String subject = NUID.nextGlobalSequence(); d.subscribe(subject); nc.publish(subject, null); assertTrue(latch.await(1, TimeUnit.SECONDS)); - } + }); } } diff --git a/src/test/java/io/nats/client/impl/DrainTests.java b/src/test/java/io/nats/client/impl/DrainTests.java index 7f3d18d24..452a671ad 100644 --- a/src/test/java/io/nats/client/impl/DrainTests.java +++ b/src/test/java/io/nats/client/impl/DrainTests.java @@ -15,6 +15,7 @@ import io.nats.client.*; import io.nats.client.ConnectionListener.Events; +import io.nats.client.support.Listener; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -25,39 +26,39 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static io.nats.client.utils.TestBase.*; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class DrainTests { - @SuppressWarnings("resource") @Test public void testCloseOnDrainFailure() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - final Connection nc = standardConnection(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); + try (NatsTestServer ts = new NatsTestServer()) { + final Connection nc = managedConnect(optionsBuilder(ts).maxReconnects(0).build()); - nc.subscribe("draintest"); + nc.subscribe(random()); nc.flush(Duration.ofSeconds(1)); // Get the sub to the server, so drain has things to do ts.shutdown(); // shut down the server to fail drain and subsequent close assertThrows(Exception.class, () -> nc.drain(Duration.ofSeconds(1))); + + closeAndConfirm(nc); } } @Test public void testSimpleSubDrain() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - - Subscription sub = subCon.subscribe("draintest"); + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { + String subject = random(); + Subscription sub = subCon.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); // publish 2 + pubCon.publish(subject, null); + pubCon.publish(subject, null); // publish 2 pubCon.flush(Duration.ofSeconds(1)); Message msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 @@ -71,29 +72,26 @@ public void testSimpleSubDrain() throws Exception { assertTrue(tracker.get(1, TimeUnit.SECONDS)); assertFalse(sub.isActive()); - assertEquals(((NatsConnection) subCon).getConsumerCount(), 0); - } + assertEquals(0, ((NatsConnection) subCon).getConsumerCount()); + }); } @Test public void testSimpleDispatchDrain() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { AtomicInteger count = new AtomicInteger(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { count.incrementAndGet(); sleep(2000); // go slow so the main app can drain us }); - d.subscribe("draintest"); - d.subscribe("draintest", (msg) -> { count.incrementAndGet(); }); + + String subject = random(); + d.subscribe(subject); + d.subscribe(subject, msg -> count.incrementAndGet()); subCon.flush(Duration.ofSeconds(5)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); @@ -102,32 +100,28 @@ public void testSimpleDispatchDrain() throws Exception { CompletableFuture tracker = d.drain(Duration.ofSeconds(8)); assertTrue(tracker.get(10, TimeUnit.SECONDS)); // wait for the drain to complete - assertEquals(count.get(), 4); // Should get both, two times. + assertEquals(4, count.get()); // Should get both, two times. assertFalse(d.isActive()); - assertEquals(((NatsConnection) subCon).getConsumerCount(), 0); - } + assertEquals(0, ((NatsConnection) subCon).getConsumerCount()); + }); } @Test public void testSimpleConnectionDrain() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { AtomicInteger count = new AtomicInteger(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { count.incrementAndGet(); sleep(500); // go slow so the main app can drain us }); - d.subscribe("draintest"); + String subject = random(); + d.subscribe(subject); - Subscription sub = subCon.subscribe("draintest"); + Subscription sub = subCon.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); @@ -142,31 +136,27 @@ public void testSimpleConnectionDrain() throws Exception { assertTrue(tracker.get(2, TimeUnit.SECONDS)); assertTrue(((NatsConnection) subCon).isDrained()); - assertEquals(count.get(), 2); // Should get both - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertEquals(2, count.get()); // Should get both + assertClosed(subCon); + }); } @Test public void testConnectionDrainWithZeroTimeout() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { AtomicInteger count = new AtomicInteger(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { count.incrementAndGet(); sleep(500); // go slow so the main app can drain us }); - d.subscribe("draintest"); + String subject = random(); + d.subscribe(subject); - Subscription sub = subCon.subscribe("draintest"); + Subscription sub = subCon.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); @@ -181,24 +171,20 @@ public void testConnectionDrainWithZeroTimeout() throws Exception { assertTrue(tracker.get(2, TimeUnit.SECONDS)); assertTrue(((NatsConnection) subCon).isDrained()); - assertEquals(count.get(), 2); // Should get both - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertEquals(2, count.get()); // Should get both + assertClosed(subCon); + }); } @Test public void testDrainWithZeroTimeout() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - - Subscription sub = subCon.subscribe("draintest"); + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { + String subject = random(); + Subscription sub = subCon.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); // publish 2 + pubCon.publish(subject, null); + pubCon.publish(subject, null); // publish 2 pubCon.flush(Duration.ofSeconds(1)); Message msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 @@ -212,83 +198,63 @@ public void testDrainWithZeroTimeout() throws Exception { assertTrue(tracker.get(1, TimeUnit.SECONDS)); assertFalse(sub.isActive()); - } + }); } @Test - public void testSubDuringDrainThrows() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - - subCon.subscribe("draintest"); - subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); - pubCon.flush(Duration.ofSeconds(1)); + public void testSubDuringDrainThrows() throws Exception { + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { + String subject = random(); + subCon.subscribe(subject); + subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - subCon.flush(Duration.ofSeconds(1)); + pubCon.publish(subject, null); + pubCon.publish(subject, null); + pubCon.flush(Duration.ofSeconds(1)); + subCon.flush(Duration.ofSeconds(1)); - CompletableFuture tracker = subCon.drain(Duration.ofSeconds(500)); + subCon.drain(Duration.ofSeconds(500)); - // Try to subscribe while we are draining the sub - subCon.subscribe("another"); // Should throw - assertTrue(tracker.get(1000, TimeUnit.SECONDS)); - } + // Try to subscribe while we are draining the sub + assertThrows(IllegalStateException.class, () -> subCon.subscribe(random())); }); } @Test - public void testCreateDispatcherDuringDrainThrows() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - - subCon.subscribe("draintest"); - subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server + public void testCreateDispatcherDuringDrainThrows() throws Exception { + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { + String subject = random(); + subCon.subscribe(subject); + subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); - pubCon.flush(Duration.ofSeconds(1)); + pubCon.publish(subject, null); + pubCon.publish(subject, null); + pubCon.flush(Duration.ofSeconds(1)); - subCon.flush(Duration.ofSeconds(1)); + subCon.flush(Duration.ofSeconds(1)); - CompletableFuture tracker = subCon.drain(Duration.ofSeconds(500)); + subCon.drain(Duration.ofSeconds(500)); - subCon.createDispatcher((msg) -> { - }); - assertTrue(tracker.get(1000, TimeUnit.SECONDS)); - } + assertThrows(IllegalStateException.class, () -> subCon.createDispatcher(msg -> {})); }); } @Test public void testUnsubDuringDrainIsNoop() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { AtomicInteger count = new AtomicInteger(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { count.incrementAndGet(); sleep(1000); // go slow so the main app can drain us }); - d.subscribe("draintest"); + String subject = random(); + d.subscribe(subject); - Subscription sub = subCon.subscribe("draintest"); + Subscription sub = subCon.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); @@ -299,7 +265,7 @@ public void testUnsubDuringDrainIsNoop() throws Exception { sleep(1000); // give the drain time to get started sub.unsubscribe(); - d.unsubscribe("draintest"); + d.unsubscribe(subject); Message msg = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(msg); @@ -307,64 +273,56 @@ public void testUnsubDuringDrainIsNoop() throws Exception { assertNotNull(msg); assertTrue(tracker.get(2, TimeUnit.SECONDS)); - assertEquals(count.get(), 2); // Should get both - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertEquals(2, count.get()); // Should get both + assertClosed(subCon); + }); } @Test public void testDrainInMessageHandler() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { AtomicInteger count = new AtomicInteger(); AtomicReference dispatcher = new AtomicReference<>(); AtomicReference> tracker = new AtomicReference<>(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { count.incrementAndGet(); tracker.set(dispatcher.get().drain(Duration.ofSeconds(1))); }); - d.subscribe("draintest"); + String subject = random(); + d.subscribe(subject); dispatcher.set(d); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); sleep(500); // give the msgs time to get to subCon assertTrue(tracker.get().get(5, TimeUnit.SECONDS)); // wait for the drain to complete - assertEquals(count.get(), 2); // Should get both + assertEquals(2, count.get()); // Should get both assertFalse(d.isActive()); - assertEquals(((NatsConnection) subCon).getConsumerCount(), 0); - } + assertEquals(0, ((NatsConnection) subCon).getConsumerCount()); + }); } @Test public void testDrainFutureMatches() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { AtomicInteger count = new AtomicInteger(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { count.incrementAndGet(); sleep(500); // go slow so the main app can drain us }); - d.subscribe("draintest"); + String subject = random(); + d.subscribe(subject); - Subscription sub = subCon.subscribe("draintest"); + Subscription sub = subCon.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); @@ -385,125 +343,91 @@ public void testDrainFutureMatches() throws Exception { assertNotNull(msg); assertTrue(tracker.get(2, TimeUnit.SECONDS)); - assertEquals(count.get(), 2); // Should get both - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertEquals(2, count.get()); // Should get both + assertClosed(subCon); + }); } @Test - public void testFirstTimeRequestReplyDuringDrain() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - - Subscription sub = subCon.subscribe("draintest"); - subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - - Dispatcher d = pubCon.createDispatcher((msg) -> { - pubCon.publish(msg.getReplyTo(), null); - }); - d.subscribe("reply"); - pubCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); - pubCon.flush(Duration.ofSeconds(1)); + public void testFirstTimeRequestReplyDuringDrain() throws Exception { + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { + String subject = random(); + Subscription sub = subCon.subscribe(subject); + subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - CompletableFuture tracker = subCon.drain(Duration.ofSeconds(500)); + Dispatcher d = pubCon.createDispatcher(msg -> pubCon.publish(msg.getReplyTo(), null)); + String reply = random(); + d.subscribe(reply); + pubCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - Message msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 - assertNotNull(msg); + pubCon.publish(subject, null); + pubCon.publish(subject, null); + pubCon.flush(Duration.ofSeconds(1)); - CompletableFuture response = subCon.request("reply", null); - subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - assertNotNull(response.get(200, TimeUnit.SECONDS)); + subCon.drain(Duration.ofSeconds(500)); - msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 - assertNotNull(msg); + Message msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 + assertNotNull(msg); - assertTrue(tracker.get(500, TimeUnit.SECONDS)); // wait for the drain to complete - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertThrows(IllegalStateException.class, () -> subCon.request(reply, null)); }); } @Test - public void testRequestReplyDuringDrain() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - - Subscription sub = subCon.subscribe("draintest"); - subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - - Dispatcher d = pubCon.createDispatcher((msg) -> { - pubCon.publish(msg.getReplyTo(), null); - }); - d.subscribe("reply"); - pubCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); - pubCon.flush(Duration.ofSeconds(1)); + public void testRequestReplyDuringDrain() throws Exception { + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { + String subject = random(); + Subscription sub = subCon.subscribe(subject); + subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - CompletableFuture response = subCon.request("reply", null); - subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - assertNotNull(response.get(1, TimeUnit.SECONDS)); + Dispatcher d = pubCon.createDispatcher(msg -> pubCon.publish(msg.getReplyTo(), null)); + String reply = random(); + d.subscribe(reply); + pubCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - CompletableFuture tracker = subCon.drain(Duration.ofSeconds(1)); + pubCon.publish(subject, null); + pubCon.publish(subject, null); + pubCon.flush(Duration.ofSeconds(1)); - Message msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 - assertNotNull(msg); + CompletableFuture response = subCon.request(reply, null); + subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server + assertNotNull(response.get(1, TimeUnit.SECONDS)); - response = subCon.request("reply", null); - subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - assertNotNull(response.get(200, TimeUnit.SECONDS)); + subCon.drain(Duration.ofSeconds(1)); - msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 - assertNotNull(msg); + Message msg = sub.nextMessage(Duration.ofSeconds(1)); // read 1 + assertNotNull(msg); - assertTrue(tracker.get(500, TimeUnit.SECONDS)); // wait for the drain to complete - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertThrows(IllegalStateException.class, () -> subCon.request(reply, null)); }); } @Test public void testQueueHandoffWithDrain() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNc(optionsBuilder().maxReconnects(0), pubCon -> { final int total = 5_000; - final Duration sleepBetweenDrains = Duration.ofMillis(250); - final Duration sleepBetweenMessages = Duration.ofMillis(1); - final Duration testTimeout = Duration.ofMillis(5 * total * (sleepBetweenDrains.toMillis() + sleepBetweenMessages.toMillis())); + final long sleepBetweenDrains = 250; + final long sleepBetweenMessages = 5; + final Duration testTimeout = Duration.ofMillis(5 * total * (sleepBetweenDrains + sleepBetweenMessages)); final Duration waitTimeout = testTimeout.plusSeconds(1); AtomicInteger count = new AtomicInteger(); Instant start = Instant.now(); Instant now = start; - Connection working = null; - NatsDispatcher workingD = null; - NatsDispatcher drainingD = null; + Connection working; + NatsDispatcher workingD; + NatsDispatcher drainingD; - Connection draining = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - assertSame(Connection.Status.CONNECTED, draining.getStatus(), "Connected Status"); + Connection draining = SharedServer.connectionForSameServer(pubCon, optionsBuilder().maxReconnects(0)); - drainingD = (NatsDispatcher) draining.createDispatcher((msg) -> { - count.incrementAndGet(); - }).subscribe("draintest", "queue"); + String subject = random(); + String queue = random(); + drainingD = (NatsDispatcher) draining.createDispatcher(msg -> count.incrementAndGet()).subscribe(subject, queue); draining.flush(Duration.ofSeconds(5)); Thread pubThread = new Thread(() -> { for (int i = 0; i < total; i++) { - pubCon.publish("draintest", null); - park(sleepBetweenMessages); + pubCon.publish(subject, null); + sleep(sleepBetweenMessages); } flushConnection(pubCon, Duration.ofSeconds(5)); }); @@ -511,15 +435,12 @@ public void testQueueHandoffWithDrain() throws Exception { pubThread.start(); while (count.get() < total && Duration.between(start, now).compareTo(testTimeout) < 0) { - - working = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - assertSame(Connection.Status.CONNECTED, working.getStatus(), "Connected Status"); - workingD = (NatsDispatcher) working.createDispatcher((msg) -> { - count.incrementAndGet(); - }).subscribe("draintest", "queue"); + working = SharedServer.connectionForSameServer(pubCon, optionsBuilder().maxReconnects(0)); + assertConnected(working); + workingD = (NatsDispatcher) working.createDispatcher(msg -> count.incrementAndGet()).subscribe(subject, queue); working.flush(Duration.ofSeconds(5)); - park(sleepBetweenDrains); + sleep(sleepBetweenDrains); CompletableFuture tracker = draining.drain(testTimeout); @@ -537,26 +458,22 @@ public void testQueueHandoffWithDrain() throws Exception { pubThread.join(); assertEquals(total, count.get()); - } + }); } @Test public void testDrainWithLotsOfMessages() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { int total = 1000; - Subscription sub = subCon.subscribe("draintest"); + String subject = random(); + Subscription sub = subCon.subscribe(subject); sub.setPendingLimits(5 * total, -1); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server // Sub should cache them in the pending queue for (int i = 0; i < total; i++) { - pubCon.publish("draintest", null); + pubCon.publish(subject, null); sleep(1); // use a nice stead pace to avoid slow consumers } try { @@ -576,20 +493,15 @@ public void testDrainWithLotsOfMessages() throws Exception { assertTrue(tracker.get(5, TimeUnit.SECONDS)); assertFalse(sub.isActive()); - assertEquals(((NatsConnection) subCon).getConsumerCount(), 0); - } + assertEquals(0, ((NatsConnection) subCon).getConsumerCount()); + }); } @Test public void testSlowAsyncDuringDrainCanFinishIfTime() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + runInSharedOwnNcs(optionsBuilder().maxReconnects(0), (subCon, pubCon) -> { AtomicInteger count = new AtomicInteger(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { try { Thread.sleep(1500); // go slow so the main app can drain us } catch (Exception e) { @@ -600,11 +512,12 @@ public void testSlowAsyncDuringDrainCanFinishIfTime() throws Exception { count.incrementAndGet(); } }); - d.subscribe("draintest"); + String subject = random(); + d.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); @@ -614,22 +527,18 @@ public void testSlowAsyncDuringDrainCanFinishIfTime() throws Exception { assertTrue(tracker.get(10, TimeUnit.SECONDS)); assertTrue(((NatsConnection) subCon).isDrained()); - assertEquals(count.get(), 2); // Should get both - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertEquals(2, count.get()); // Should get both + assertClosed(subCon); + }); } @Test public void testSlowAsyncDuringDrainCanBeInterrupted() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).errorListener(listener).maxReconnects(0).build()); - Connection pubCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - assertSame(Connection.Status.CONNECTED, pubCon.getStatus(), "Connected Status"); - + Listener listener = new Listener(); + runInSharedOwnNc(optionsBuilder().errorListener(listener).maxReconnects(0), subCon -> { + Connection pubCon = SharedServer.sharedConnectionForSameServer(subCon); AtomicInteger count = new AtomicInteger(); - Dispatcher d = subCon.createDispatcher((msg) -> { + Dispatcher d = subCon.createDispatcher(msg -> { try { Thread.sleep(3000); // go slow so the main app can drain us } catch (Exception e) { @@ -640,11 +549,12 @@ public void testSlowAsyncDuringDrainCanBeInterrupted() throws Exception { count.incrementAndGet(); } }); - d.subscribe("draintest"); + String subject = random(); + d.subscribe(subject); subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - pubCon.publish("draintest", null); - pubCon.publish("draintest", null); + pubCon.publish(subject, null); + pubCon.publish(subject, null); pubCon.flush(Duration.ofSeconds(1)); subCon.flush(Duration.ofSeconds(1)); @@ -656,37 +566,32 @@ public void testSlowAsyncDuringDrainCanBeInterrupted() throws Exception { assertFalse(tracker.get(10, TimeUnit.SECONDS)); assertFalse(((NatsConnection) subCon).isDrained()); assertEquals(0, listener.getExceptionCount()); // Don't throw during drain from reader - assertSame(Connection.Status.CLOSED, subCon.getStatus()); - } + assertClosed(subCon); + }); } @Test - public void testThrowIfCantFlush() { - assertThrows(TimeoutException.class, () -> { - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().connectionListener(listener).server(ts.getURI()).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); + public void testThrowIfCantFlush() throws Exception { + Listener listener = new Listener(); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts).connectionListener(listener).build(); + try (Connection subCon = managedConnect(options)) { subCon.flush(Duration.ofSeconds(1)); // Get the sub to the server - listener.prepForStatusChange(Events.DISCONNECTED); + listener.queueConnectionEvent(Events.DISCONNECTED); ts.close(); // make the drain flush fail - listener.waitForStatusChange(2, TimeUnit.SECONDS); // make sure the connection is down - subCon.drain(Duration.ofSeconds(1)); //should throw + listener.validate(); + + assertThrows(TimeoutException.class, () -> subCon.drain(Duration.ofSeconds(1))); } - }); + } } @Test - public void testThrowIfClosing() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection subCon = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertSame(Connection.Status.CONNECTED, subCon.getStatus(), "Connected Status"); - - subCon.close(); - subCon.drain(Duration.ofSeconds(1)); - } + public void testThrowIfClosing() throws Exception { + runInSharedOwnNc(optionsBuilder().maxReconnects(0), subCon -> { + subCon.close(); + assertThrows(IllegalStateException.class, () -> subCon.drain(Duration.ofSeconds(1))); }); } } diff --git a/src/test/java/io/nats/client/impl/ErrorListenerTests.java b/src/test/java/io/nats/client/impl/ErrorListenerTests.java index 6bd7351a4..457827e06 100644 --- a/src/test/java/io/nats/client/impl/ErrorListenerTests.java +++ b/src/test/java/io/nats/client/impl/ErrorListenerTests.java @@ -15,7 +15,9 @@ import io.nats.client.*; import io.nats.client.ConnectionListener.Events; +import io.nats.client.support.Listener; import io.nats.client.support.Status; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -26,33 +28,34 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import static io.nats.client.utils.TestBase.*; +import static io.nats.client.support.Listener.LONG_VALIDATE_TIMEOUT; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.*; -public class ErrorListenerTests { +public class ErrorListenerTests extends TestBase { @Test - public void testLastError() throws Exception { - NatsConnection nc = null; - ListenerForTesting listener = new ListenerForTesting(); + public void testLastError_ClearError_AuthViolation() throws Exception { + NatsConnection nc; + Listener listener = new Listener(true); String[] customArgs = {"--user", "stephen", "--pass", "password"}; try (NatsTestServer ts = new NatsTestServer(); - NatsTestServer ts2 = new NatsTestServer(customArgs, false); //ts2 requires auth + NatsTestServer ts2 = new NatsTestServer(customArgs); //ts2 requires auth NatsTestServer ts3 = new NatsTestServer()) { - Options options = new Options.Builder() - .server(ts.getURI()) - .server(ts2.getURI()) - .server(ts3.getURI()) + Options options = optionsBuilder(ts.getServerUri(), ts2.getServerUri(), ts3.getServerUri()) .noRandomize() .connectionListener(listener) .errorListener(listener) .maxReconnects(-1) .build(); nc = (NatsConnection) Nats.connect(options); - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - listener.prepForStatusChange(Events.DISCONNECTED); + assertConnected(nc); + assertEquals(ts.getServerUri(), nc.getConnectedUrl()); + listener.queueConnectionEvent(Events.DISCONNECTED); + listener.queueConnectionEvent(Events.RECONNECTED); + listener.queueError("Authorization Violation"); ts.close(); @@ -63,126 +66,40 @@ public void testLastError() throws Exception { // this usually fails } - listener.waitForStatusChange(5, TimeUnit.SECONDS); + listener.validateAll(); - listener.prepForStatusChange(Events.RECONNECTED); - listener.waitForStatusChange(5, TimeUnit.SECONDS); - - assertTrue(listener.errorsEventually("Authorization Violation", 2000)); - - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - assertEquals(ts3.getURI(), nc.getConnectedUrl()); - } finally { - standardCloseConnection(nc); - } - } - - @Test - public void testClearLastError() throws Exception { - NatsConnection nc = null; - ListenerForTesting listener = new ListenerForTesting(); - String[] customArgs = {"--user", "stephen", "--pass", "password"}; - - try (NatsTestServer ts = new NatsTestServer(); - NatsTestServer ts2 = new NatsTestServer(customArgs, false); //ts2 requires auth - NatsTestServer ts3 = new NatsTestServer()) { - Options options = new Options.Builder() - .server(ts.getURI()) - .server(ts2.getURI()) - .server(ts3.getURI()) - .noRandomize() - .connectionListener(listener) - .errorListener(listener) - .maxReconnects(-1) - .build(); - nc = (NatsConnection) Nats.connect(options); - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - listener.prepForStatusChange(Events.DISCONNECTED); - - ts.close(); - - try { - nc.flush(Duration.ofSeconds(1)); - } - catch (Exception exp) { - // this usually fails - } - - listener.waitForStatusChange(5, TimeUnit.SECONDS); - - listener.prepForStatusChange(Events.RECONNECTED); - listener.waitForStatusChange(5, TimeUnit.SECONDS); - - assertTrue(listener.errorsEventually("Authorization Violation", 2000)); - - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - assertEquals(ts3.getURI(), nc.getConnectedUrl()); + confirmConnected(nc); // wait for reconnect + assertEquals(ts3.getServerUri(), nc.getConnectedUrl()); nc.clearLastError(); assertNull(nc.getLastError()); } - finally { - standardCloseConnection(nc); - } - } - - @Test - public void testErrorOnNoAuth() throws Exception { - String[] customArgs = {"--user", "stephen", "--pass", "password"}; - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { - sleep(1000); // give the server time to get ready, otherwise sometimes this test flaps - // See config file for user/pass - // no or wrong u/p in the options is an error - Options options = new Options.Builder(). - server(ts.getURI()) - .maxReconnects(0) - .errorListener(listener) - .build(); - try { - Nats.connect(options); - fail(); - } - catch (AuthenticationException ae) { - if (ae.getMessage().contains("Authorization Violation")) { - return; - } - fail(); - } - assertTrue(listener.errorsEventually("Authorization Violation", 10000)); - } } @Test public void testExceptionOnBadDispatcher() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - errorListener(listener). - build(); - Connection nc = Nats.connect(options); - try { - Dispatcher d = nc.createDispatcher((msg) -> { + Options options = optionsBuilder(ts) + .maxReconnects(0) + .errorListener(listener) + .build(); + try (Connection nc = Nats.connect(options)) { + Dispatcher d = nc.createDispatcher(msg -> { throw new ArithmeticException(); }); - d.subscribe("subject"); - Future incoming = nc.request("subject", null); - - Message msg; + String subject = random(); + d.subscribe(subject); + Future incoming = nc.request(subject, null); try { - msg = incoming.get(200, TimeUnit.MILLISECONDS); - } catch (TimeoutException te) { - msg = null; + incoming.get(200, TimeUnit.MILLISECONDS); + fail(); } - - assertNull(msg); - assertEquals(1, listener.getCount()); - } finally { - standardCloseConnection(nc); + catch (TimeoutException te) { + // expected + } + assertEquals(1, listener.getExceptionCount()); } } } @@ -191,14 +108,13 @@ public void testExceptionOnBadDispatcher() throws Exception { public void testExceptionInErrorHandler() throws Exception { String[] customArgs = {"--user", "stephen", "--pass", "password"}; BadHandler listener = new BadHandler(); - try (NatsTestServer ts = new NatsTestServer(customArgs, false)) { + try (NatsTestServer ts = new NatsTestServer(customArgs)) { // See config file for user/pass - Options options = new Options.Builder() - .server(ts.getURI()) - .maxReconnects(0) - .errorListener(listener) - // skip this so we get an error userInfo("stephen", "password"). - .build(); + // don't put u/p in options + Options options = optionsBuilder(ts) + .maxReconnects(0) + .errorListener(listener) + .build(); assertThrows(IOException.class, () -> Nats.connect(options)); } } @@ -206,19 +122,17 @@ public void testExceptionInErrorHandler() throws Exception { @Test public void testExceptionInSlowConsumerHandler() throws Exception { BadHandler listener = new BadHandler(); - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(new Options.Builder(). - server(ts.getURI()). - errorListener(listener). - build())) { + try (NatsTestServer ts = new NatsTestServer(); + Connection nc = Nats.connect(optionsBuilder(ts).errorListener(listener).build())) { - Subscription sub = nc.subscribe("subject"); + String subject = random(); + Subscription sub = nc.subscribe(subject); sub.setPendingLimits(1, -1); - nc.publish("subject", null); - nc.publish("subject", null); - nc.publish("subject", null); - nc.publish("subject", null); + nc.publish(subject, null); + nc.publish(subject, null); + nc.publish(subject, null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); @@ -226,7 +140,7 @@ public void testExceptionInSlowConsumerHandler() throws Exception { nc.close(); // should force the exception listener through - assertTrue(nc.getStatisticsCollector().getExceptions() > 0); + assertTrue(nc.getStatistics().getExceptions() > 0); } } @@ -234,18 +148,15 @@ public void testExceptionInSlowConsumerHandler() throws Exception { public void testExceptionInExceptionHandler() throws Exception { BadHandler listener = new BadHandler(); try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - errorListener(listener). - build(); + Options options = optionsBuilder(ts).maxReconnects(0).errorListener(listener).build(); Connection nc = Nats.connect(options); try { - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { throw new ArithmeticException(); }); - d.subscribe("subject"); - Future incoming = nc.request("subject", null); + String subject = random(); + d.subscribe(subject); + Future incoming = nc.request(subject, null); Message msg; @@ -256,82 +167,78 @@ public void testExceptionInExceptionHandler() throws Exception { } assertNull(msg); - assertEquals(((NatsConnection) nc).getStatisticsCollector().getExceptions(), 2); // 1 for the dispatcher, 1 for the handlers + assertEquals(2, nc.getStatistics().getExceptions()); // 1 for the dispatcher, 1 for the handlers } finally { - standardCloseConnection(nc); + closeAndConfirm(nc); } } } @Test public void testDiscardedMessageFastProducer() throws Exception { + String subject = random(); int maxMessages = 10; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxMessagesInOutgoingQueue(maxMessages). - discardMessagesWhenOutgoingQueueFull(). - errorListener(listener). - pingInterval(Duration.ofSeconds(100)). // make this long so we don't ping during test - build(); + Options options = optionsBuilder(ts) + .maxMessagesInOutgoingQueue(maxMessages) + .discardMessagesWhenOutgoingQueueFull() + .errorListener(listener) + .pingInterval(Duration.ofSeconds(100)) // make this long so we don't ping during test + .build(); NatsConnection nc = (NatsConnection) Nats.connect(options); try { nc.flush(Duration.ofSeconds(2)); - nc.getWriter().stop().get(2, TimeUnit.SECONDS); for (int i = 0; i < maxMessages + 1; i++) { - nc.publish("subject" + i, ("message" + i).getBytes()); + nc.publish(subject + i, ("message" + i).getBytes()); } nc.getWriter().start(nc.getDataPortFuture()); nc.flush(Duration.ofSeconds(2)); } finally { - standardCloseConnection(nc); + closeAndConfirm(nc); } } List discardedMessages = listener.getDiscardedMessages(); assertFalse(discardedMessages.isEmpty(), "expected discardedMessages > 0, got " + discardedMessages.size()); int offset = maxMessages + 1 - discardedMessages.size(); - assertEquals("subject" + offset, discardedMessages.get(0).getSubject()); + assertEquals(subject + offset, discardedMessages.get(0).getSubject()); assertEquals("message" + offset, new String(discardedMessages.get(0).getData())); } @Test public void testDiscardedMessageServerClosed() throws Exception { + String subject = random(); int maxMessages = 10; - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxMessagesInOutgoingQueue(maxMessages). - discardMessagesWhenOutgoingQueueFull(). - pingInterval(Duration.ofSeconds(100)). // make this long so we don't ping during test - connectionListener(listener). - errorListener(listener). - build(); - Connection nc = standardConnection(options); - - try { - nc.flush(Duration.ofSeconds(1)); // Get the sub to the server - - listener.prepForStatusChange(Events.DISCONNECTED); + Listener listener = new Listener(); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) + .maxMessagesInOutgoingQueue(maxMessages) + .discardMessagesWhenOutgoingQueueFull() + .connectionListener(listener) + .errorListener(listener) + .pingInterval(Duration.ofSeconds(100)) // make this long so we don't ping during test + .build(); + listener.queueConnectionEvent(Events.CONNECTED, LONG_VALIDATE_TIMEOUT); + listener.queueConnectionEvent(Events.DISCONNECTED, LONG_VALIDATE_TIMEOUT); + try (Connection nc = managedConnect(options)) { + nc.flush(Duration.ofSeconds(1)); + listener.validate(); ts.close(); - listener.waitForStatusChange(2, TimeUnit.SECONDS); // make sure the connection is down + listener.validate(); for (int i = 0; i < maxMessages + 1; i++) { - nc.publish("subject" + i, ("message" + i).getBytes()); + nc.publish(subject + i, ("message" + i).getBytes()); } - } finally { - standardCloseConnection(nc); } } List discardedMessages = listener.getDiscardedMessages(); assertFalse(discardedMessages.isEmpty(), "At least one message discarded"); - assertTrue(discardedMessages.get(0).getSubject().startsWith("subject"), "Message subject"); + assertTrue(discardedMessages.get(0).getSubject().startsWith(subject), "Message subject"); assertTrue(new String(discardedMessages.get(0).getData()).startsWith("message"), "Message data"); } diff --git a/src/test/java/io/nats/client/impl/HeadersTests.java b/src/test/java/io/nats/client/impl/HeadersTests.java index 5af80e1ed..971e8aa66 100644 --- a/src/test/java/io/nats/client/impl/HeadersTests.java +++ b/src/test/java/io/nats/client/impl/HeadersTests.java @@ -557,36 +557,36 @@ public void constructStatusWithValidBytesAndCoverage() { assertValidStatus("NATS/1.0 923 Unknown Message And Code\r\n", 923, "Unknown Message And Code"); // additional coverage for status extraction comparing status text to known values - assertValidStatus(999, EXCEEDED_MAX_WAITING); - assertValidStatus(999, EXCEEDED_MAX_REQUEST_BATCH); - assertValidStatus(999, EXCEEDED_MAX_REQUEST_MAX_BYTES); - assertValidStatus(999, EXCEEDED_MAX_REQUEST_EXPIRES); - assertValidStatus(999, EOB_TEXT); - - assertValidStatus(999, BATCH_COMPLETED); - assertValidStatus(999, BAD_REQUEST); - - assertValidStatus(999, NO_RESPONDERS_TEXT); - assertValidStatus(999, NO_MESSAGES); - - assertValidStatus(999, FLOW_CONTROL_TEXT); - assertValidStatus(999, HEARTBEAT_TEXT); - assertValidStatus(999, MESSAGE_SIZE_EXCEEDS_MAX_BYTES); - assertValidStatus(999, LEADERSHIP_CHANGE); - assertValidStatus(999, SERVER_SHUTDOWN); - assertValidStatus(999, CONSUMER_DELETED); - assertValidStatus(999, CONSUMER_IS_PUSH_BASED); + assertValidStatus(EXCEEDED_MAX_WAITING); + assertValidStatus(EXCEEDED_MAX_REQUEST_BATCH); + assertValidStatus(EXCEEDED_MAX_REQUEST_MAX_BYTES); + assertValidStatus(EXCEEDED_MAX_REQUEST_EXPIRES); + assertValidStatus(EOB_TEXT); + + assertValidStatus(BATCH_COMPLETED); + assertValidStatus(BAD_REQUEST); + + assertValidStatus(NO_RESPONDERS_TEXT); + assertValidStatus(NO_MESSAGES); + + assertValidStatus(FLOW_CONTROL_TEXT); + assertValidStatus(HEARTBEAT_TEXT); + assertValidStatus(MESSAGE_SIZE_EXCEEDS_MAX_BYTES); + assertValidStatus(LEADERSHIP_CHANGE); + assertValidStatus(SERVER_SHUTDOWN); + assertValidStatus(CONSUMER_DELETED); + assertValidStatus(CONSUMER_IS_PUSH_BASED); // coverage - assertValidStatus(999, "E Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "B Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "N Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "F Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "I Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "M Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "L Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "S Test Starts With Known Letter But Not Known"); - assertValidStatus(999, "C Test Starts With Known Letter But Not Known"); + assertValidStatus("E Test Starts With Known Letter But Not Known"); + assertValidStatus("B Test Starts With Known Letter But Not Known"); + assertValidStatus("N Test Starts With Known Letter But Not Known"); + assertValidStatus("F Test Starts With Known Letter But Not Known"); + assertValidStatus("I Test Starts With Known Letter But Not Known"); + assertValidStatus("M Test Starts With Known Letter But Not Known"); + assertValidStatus("L Test Starts With Known Letter But Not Known"); + assertValidStatus("S Test Starts With Known Letter But Not Known"); + assertValidStatus("C Test Starts With Known Letter But Not Known"); } @Test @@ -686,10 +686,10 @@ private void assertValidHeader(IncomingHeadersProcessor ihp, String key, String } } - private void assertValidStatus(int code, String text) { - String test = "NATS/1.0 " + code + " " + text + "\r\n"; + private void assertValidStatus(String text) { + String test = "NATS/1.0 999 " + text + "\r\n"; IncomingHeadersProcessor ihp = new IncomingHeadersProcessor(test.getBytes()); - assertValidStatus(ihp, code, text); + assertValidStatus(ihp, 999, text); } private IncomingHeadersProcessor assertValidStatus(String test, int code, String msg) { @@ -815,7 +815,6 @@ public void equalsHash() { Headers h2 = new Headers(); //noinspection MisorderedAssertEqualsArguments assertNotEquals(h1, null); - //noinspection EqualsWithItself assertEquals(h1, h1); assertEquals(h1, h2); assertEquals(h1.hashCode(), h1.hashCode()); diff --git a/src/test/java/io/nats/client/impl/InfoHandlerTests.java b/src/test/java/io/nats/client/impl/InfoHandlerTests.java index e641545ee..cd9ba10bf 100644 --- a/src/test/java/io/nats/client/impl/InfoHandlerTests.java +++ b/src/test/java/io/nats/client/impl/InfoHandlerTests.java @@ -13,7 +13,10 @@ package io.nats.client.impl; -import io.nats.client.*; +import io.nats.client.Connection; +import io.nats.client.ConnectionListener; +import io.nats.client.NatsServerProtocolMock; +import io.nats.client.Options; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -22,6 +25,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static io.nats.client.utils.ConnectionUtils.standardConnect; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -29,21 +34,13 @@ public class InfoHandlerTests { @Test public void testInitialInfo() throws IOException, InterruptedException { String customInfo = "{\"server_id\":\"myid\", \"version\":\"9.9.99\"}"; - - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(null, customInfo)) { - Connection nc = Nats.connect(ts.getURI()); - try { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(null, customInfo)) { + try (Connection nc = standardConnect(mockTs)) { assertEquals("myid", nc.getServerInfo().getServerId(), "got custom info"); - } finally { - nc.close(); - assertTrue(Connection.Status.CLOSED == nc.getStatus(), "Closed Status"); } } } - - @Test public void testUnsolicitedInfo() throws IOException, InterruptedException, ExecutionException { String customInfo = "{\"server_id\":\"myid\", \"version\":\"9.9.99\"}"; @@ -61,17 +58,16 @@ public void testUnsolicitedInfo() throws IOException, InterruptedException, Exec return; } - System.out.println("*** Mock Server @" + ts.getPort() + " sending INFO ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " sending INFO ..."); w.write("INFO {\"server_id\":\"replacement\", \"version\":\"9.9.99\"}\r\n"); w.flush(); - System.out.println("*** Mock Server @" + ts.getPort() + " sending PING ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " sending PING ..."); w.write("PING\r\n"); w.flush(); - String pong = ""; - - System.out.println("*** Mock Server @" + ts.getPort() + " waiting for PONG ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " waiting for PONG ..."); + String pong; try { pong = r.readLine(); } catch (Exception e) { @@ -80,32 +76,25 @@ public void testUnsolicitedInfo() throws IOException, InterruptedException, Exec } if (pong != null && pong.startsWith("PONG")) { - System.out.println("*** Mock Server @" + ts.getPort() + " got PONG ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " got PONG ..."); gotPong.complete(Boolean.TRUE); } else { - System.out.println("*** Mock Server @" + ts.getPort() + " got something else... " + pong); + // System.out.println("*** Mock Server @" + ts.getPort() + " got something else... " + pong); gotPong.complete(Boolean.FALSE); } }; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(infoCustomizer, customInfo)) { - Connection nc = Nats.connect(ts.getURI()); - try { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(infoCustomizer, customInfo)) { + try (Connection nc = standardConnect(mockTs)) { assertEquals("myid", nc.getServerInfo().getServerId(), "got custom info"); sendInfo.complete(Boolean.TRUE); - assertTrue(gotPong.get().booleanValue(), "Got pong."); // Server round tripped so we should have new info + assertTrue(gotPong.get(), "Got pong."); // Server round tripped so we should have new info assertEquals("replacement", nc.getServerInfo().getServerId(), "got replacement info"); - } finally { - nc.close(); - assertTrue(Connection.Status.CLOSED == nc.getStatus(), "Closed Status"); } } } - - @Test public void testLDM() throws IOException, InterruptedException, ExecutionException, TimeoutException { String customInfo = "{\"server_id\":\"myid\", \"version\":\"9.9.99\", \"ldm\":true}"; @@ -114,7 +103,6 @@ public void testLDM() throws IOException, InterruptedException, ExecutionExcepti CompletableFuture connectLDM = new CompletableFuture<>(); NatsServerProtocolMock.Customizer infoCustomizer = (ts, r, w) -> { - // Wait for client to be ready. try { sendInfo.get(); @@ -124,17 +112,16 @@ public void testLDM() throws IOException, InterruptedException, ExecutionExcepti return; } - System.out.println("*** Mock Server @" + ts.getPort() + " sending INFO ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " sending INFO ..."); w.write("INFO {\"server_id\":\"replacement\"}\r\n"); w.flush(); - System.out.println("*** Mock Server @" + ts.getPort() + " sending PING ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " sending PING ..."); w.write("PING\r\n"); w.flush(); - String pong = ""; - - System.out.println("*** Mock Server @" + ts.getPort() + " waiting for PONG ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " waiting for PONG ..."); + String pong; try { pong = r.readLine(); } catch (Exception e) { @@ -143,39 +130,33 @@ public void testLDM() throws IOException, InterruptedException, ExecutionExcepti } if (pong != null && pong.startsWith("PONG")) { - System.out.println("*** Mock Server @" + ts.getPort() + " got PONG ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " got PONG ..."); gotPong.complete(Boolean.TRUE); } else { - System.out.println("*** Mock Server @" + ts.getPort() + " got something else... " + pong); + // System.out.println("*** Mock Server @" + ts.getPort() + " got something else... " + pong); gotPong.complete(Boolean.FALSE); } }; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(infoCustomizer, customInfo)) { + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(infoCustomizer, customInfo)) { - Options options = new Options.Builder().server(ts.getURI()).connectionListener(new ConnectionListener() { - @Override - public void connectionEvent(Connection conn, Events type) { - if (type.equals(Events.LAME_DUCK)) connectLDM.complete(type); - } - }).build(); + ConnectionListener cl = (conn, type) -> { + if (type.equals(ConnectionListener.Events.LAME_DUCK)) connectLDM.complete(type); + }; - Connection nc = Nats.connect(options); - try { - assertTrue(Connection.Status.CONNECTED == nc.getStatus(), "Connected Status"); + Options options = optionsBuilder(mockTs).connectionListener(cl).build(); + + try (Connection nc = standardConnect(options)) { assertEquals("myid", nc.getServerInfo().getServerId(), "got custom info"); sendInfo.complete(Boolean.TRUE); assertTrue(gotPong.get(), "Got pong."); // Server round tripped so we should have new info assertEquals("replacement", nc.getServerInfo().getServerId(), "got replacement info"); - } finally { - nc.close(); - assertTrue(Connection.Status.CLOSED == nc.getStatus(), "Closed Status"); } } ConnectionListener.Events event = connectLDM.get(5, TimeUnit.SECONDS); - assertEquals(event, ConnectionListener.Events.LAME_DUCK); - System.out.println(event); + assertEquals(ConnectionListener.Events.LAME_DUCK, event); + // System.out.println(event); } } \ No newline at end of file diff --git a/src/test/java/io/nats/client/impl/JetStreamConsumerTests.java b/src/test/java/io/nats/client/impl/JetStreamConsumerTests.java index cb49fd537..89b286a2d 100644 --- a/src/test/java/io/nats/client/impl/JetStreamConsumerTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamConsumerTests.java @@ -15,18 +15,19 @@ import io.nats.client.*; import io.nats.client.api.ConsumerConfiguration; -import io.nats.client.utils.TestBase; +import io.nats.client.support.Listener; +import io.nats.client.utils.VersionUtils; import org.junit.jupiter.api.Test; import java.io.IOException; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import static io.nats.client.support.NatsJetStreamClientError.JsSubOrderedNotAllowOnQueues; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class JetStreamConsumerTests extends JetStreamTestBase { @@ -59,35 +60,28 @@ protected Boolean beforeQueueProcessorImpl(NatsMessage msg) { @Test public void testOrderedConsumerSync() throws Exception { - jsServer.run(nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - + runInShared((nc, ctx) -> { // Get this in place before any subscriptions are made - ((NatsJetStream)js)._pushOrderedMessageManagerFactory = OrderedTestDropSimulator::new; + ctx.js._pushOrderedMessageManagerFactory = OrderedTestDropSimulator::new; // Test queue exception IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, - () -> js.subscribe(tsc.subject(), QUEUE, PushSubscribeOptions.builder().ordered(true).build())); + () -> ctx.js.subscribe(ctx.subject(), random(), PushSubscribeOptions.builder().ordered(true).build())); assertTrue(iae.getMessage().contains(JsSubOrderedNotAllowOnQueues.id())); // Setup sync subscription - _testOrderedConsumerSync(js, tsc, null, PushSubscribeOptions.builder().ordered(true).build()); + _testOrderedConsumerSync(ctx, null, PushSubscribeOptions.builder().ordered(true).build()); - String consumerName = prefix(); - _testOrderedConsumerSync(js, tsc, consumerName, PushSubscribeOptions.builder().name(consumerName).ordered(true).build()); + _testOrderedConsumerSync(ctx, ctx.consumerName(), PushSubscribeOptions.builder().name(ctx.consumerName()).ordered(true).build()); }); } - private static void _testOrderedConsumerSync(JetStream js, TestingStreamContainer tsc, String consumerNamePrefix, PushSubscribeOptions pso) throws IOException, JetStreamApiException, TimeoutException, InterruptedException { - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); + private static void _testOrderedConsumerSync(JetStreamTestingContext ctx, String consumerNamePrefix, PushSubscribeOptions pso) throws IOException, JetStreamApiException, InterruptedException { + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), pso); String firstConsumerName = validateOrderedConsumerNamePrefix(sub, consumerNamePrefix); // Published messages will be intercepted by the OrderedTestDropSimulator - jsPublish(js, tsc.subject(), 101, 6); + jsPublish(ctx.js, ctx.subject(), 101, 6); // Loop through the messages to make sure I get stream sequence 1 to 6 int expectedStreamSeq = 1; @@ -122,28 +116,32 @@ private static void reValidateOrderedConsumerNamePrefix(JetStreamSubscription su } @Test - public void testOrderedConsumerAsync() throws Exception { - jsServer.run(nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - _testOrderedConsumerAsync(nc, jsm, js, null, PushSubscribeOptions.builder().ordered(true).build()); - String customName = variant(); - _testOrderedConsumerAsync(nc, jsm, js, customName, PushSubscribeOptions.builder().name(customName).ordered(true).build()); + public void testOrderedConsumerAsyncNoName() throws Exception { + runInShared((nc, ctx) -> { + // without name (prefix) + _testOrderedConsumerAsync(nc, ctx, null, + PushSubscribeOptions.builder().ordered(true).build()); }); } - private static void _testOrderedConsumerAsync(Connection nc, JetStreamManagement jsm, JetStream js, String consumerNamePrefix, PushSubscribeOptions pso) throws JetStreamApiException, IOException, TimeoutException, InterruptedException { - TestingStreamContainer tsc = new TestingStreamContainer(jsm); + @Test + public void testOrderedConsumerAsyncWithName() throws Exception { + runInShared((nc, ctx) -> { + // with name (prefix) + _testOrderedConsumerAsync(nc, ctx, ctx.consumerName(), + PushSubscribeOptions.builder().name(ctx.consumerName()).ordered(true).build()); + }); + } + private static void _testOrderedConsumerAsync(Connection nc, JetStreamTestingContext ctx, String consumerNamePrefix, PushSubscribeOptions pso) throws JetStreamApiException, IOException, InterruptedException { // Get this in place before any subscriptions are made - ((NatsJetStream) js)._pushOrderedMessageManagerFactory = OrderedTestDropSimulator::new; + ctx.js._pushOrderedMessageManagerFactory = OrderedTestDropSimulator::new; // We'll need a dispatcher Dispatcher d = nc.createDispatcher(); // Test queue exception - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), QUEUE, d, m -> {}, false, pso)); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), random(), d, m -> {}, false, pso)); assertTrue(iae.getMessage().contains(JsSubOrderedNotAllowOnQueues.id())); // Set up an async subscription @@ -158,11 +156,11 @@ private static void _testOrderedConsumerAsync(Connection nc, JetStreamManagement msgLatch.countDown(); }; - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, handler, false, pso); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, handler, false, pso); String firstConsumerName = validateOrderedConsumerNamePrefix(sub, consumerNamePrefix); // publish after sub b/c interceptor is set during sub, so before messages come in - jsPublish(js, tsc.subject(), 201, 6); + jsPublish(ctx.js, ctx.subject(), 201, 6); // wait for the messages awaitAndAssert(msgLatch); @@ -257,41 +255,39 @@ protected Boolean beforeQueueProcessorImpl(NatsMessage msg) { @Test public void testHeartbeatError() throws Exception { - ListenerForTesting listenerForTesting = new ListenerForTesting(); - runInJsServer(listenerForTesting, nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - JetStream js = nc.jetStream(); - + Listener listener = new Listener(); + runInSharedOwnNc(listener, (nc, ctx) -> { Dispatcher d = nc.createDispatcher(); ConsumerConfiguration cc = ConsumerConfiguration.builder().idleHeartbeat(100).build(); - + JetStream js = ctx.js; PushSubscribeOptions pso = PushSubscribeOptions.builder().configuration(cc).build(); SimulatorState state = setupFactory(js); - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); - validate(sub, listenerForTesting, state, null); + JetStreamSubscription sub = js.subscribe(ctx.subject(), pso); + validate(sub, listener, state, null); state = setupFactory(js); - sub = js.subscribe(tsc.subject(), d, m -> {}, false, pso); - validate(sub, listenerForTesting, state, d); + sub = js.subscribe(ctx.subject(), d, m -> {}, false, pso); + validate(sub, listener, state, d); pso = PushSubscribeOptions.builder().ordered(true).configuration(cc).build(); state = setupOrderedFactory(js); - sub = js.subscribe(tsc.subject(), pso); - validate(sub, listenerForTesting, state, null); + sub = js.subscribe(ctx.subject(), pso); + validate(sub, listener, state, null); state = setupOrderedFactory(js); - sub = js.subscribe(tsc.subject(), d, m -> {}, false, pso); - validate(sub, listenerForTesting, state, d); + sub = js.subscribe(ctx.subject(), d, m -> {}, false, pso); + validate(sub, listener, state, d); state = setupPullFactory(js); - sub = js.subscribe(tsc.subject(), PullSubscribeOptions.DEFAULT_PULL_OPTS); + sub = js.subscribe(ctx.subject(), PullSubscribeOptions.DEFAULT_PULL_OPTS); sub.pull(PullRequestOptions.builder(1).idleHeartbeat(100).expiresIn(2000).build()); - validate(sub, listenerForTesting, state, null); + validate(sub, listener, state, null); }); } - private static void validate(JetStreamSubscription sub, ListenerForTesting listener, SimulatorState state, Dispatcher d) throws InterruptedException { + private static void validate(JetStreamSubscription sub, Listener listener, SimulatorState state, Dispatcher d) throws InterruptedException { + listener.reset(); + //noinspection ResultOfMethodCallIgnored state.latch.await(2, TimeUnit.SECONDS); if (d == null) { @@ -304,14 +300,13 @@ private static void validate(JetStreamSubscription sub, ListenerForTesting liste assertTrue(state.hbCounter.get() > 0); boolean gotHbAlarm = false; for (int x = 0; x < 50; x++) { - gotHbAlarm = !listener.getHeartbeatAlarms().isEmpty(); + gotHbAlarm = listener.getHeartbeatAlarmCount() > 0; if (gotHbAlarm) { break; } sleep(10); } assertTrue(gotHbAlarm); - listener.reset(); } private static SimulatorState setupFactory(JetStream js) { @@ -332,8 +327,6 @@ private static SimulatorState setupOrderedFactory(JetStream js) { private static SimulatorState setupPullFactory(JetStream js) { SimulatorState state = new SimulatorState(); - // the expected latch count is 1 b/c pull is dead once there is a hb error - CountDownLatch latch = new CountDownLatch(1); ((NatsJetStream)js)._pullMessageManagerFactory = (conn, lJs, stream, so, serverCC, qmode, dispatcher) -> new PullHeartbeatErrorSimulator(conn, false, state); @@ -342,53 +335,48 @@ private static SimulatorState setupPullFactory(JetStream js) { @Test public void testMultipleSubjectFilters() throws Exception { - jsServer.run(TestBase::atLeast2_10, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(nc, 2); - - jsPublish(js, tsc.subject(0), 10); - jsPublish(js, tsc.subject(1), 5); + runInSharedCustom(VersionUtils::atLeast2_10, (nc, ctx) -> { + ctx.createOrReplaceStream(2); + jsPublish(ctx.js, ctx.subject(0), 10); + jsPublish(ctx.js, ctx.subject(1), 5); // push ephemeral - ConsumerConfiguration cc = ConsumerConfiguration.builder().filterSubjects(tsc.subject(0), tsc.subject(1)).build(); - JetStreamSubscription sub = js.subscribe(null, PushSubscribeOptions.builder().configuration(cc).build()); - validateMultipleSubjectFilterSub(sub, tsc.subject(0)); + ConsumerConfiguration cc = ConsumerConfiguration.builder().filterSubjects(ctx.subject(0), ctx.subject(1)).build(); + JetStreamSubscription sub = ctx.js.subscribe(null, PushSubscribeOptions.builder().configuration(cc).build()); + validateMultipleSubjectFilterSub(sub, ctx.subject(0)); // pull ephemeral - sub = js.subscribe(null, PullSubscribeOptions.builder().configuration(cc).build()); + sub = ctx.js.subscribe(null, PullSubscribeOptions.builder().configuration(cc).build()); sub.pullExpiresIn(15, 1000); - validateMultipleSubjectFilterSub(sub, tsc.subject(0)); + validateMultipleSubjectFilterSub(sub, ctx.subject(0)); // push named - String name = name(); - cc = ConsumerConfiguration.builder().filterSubjects(tsc.subject(0), tsc.subject(1)).name(name).deliverSubject(deliver()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); - sub = js.subscribe(null, PushSubscribeOptions.builder().configuration(cc).build()); - validateMultipleSubjectFilterSub(sub, tsc.subject(0)); - - name = name(); - cc = ConsumerConfiguration.builder().filterSubjects(tsc.subject(0), tsc.subject(1)).name(name).deliverSubject(deliver()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); - sub = js.subscribe(null, PushSubscribeOptions.bind(tsc.stream, name)); - validateMultipleSubjectFilterSub(sub, tsc.subject(0)); + String name = random(); + cc = ConsumerConfiguration.builder().filterSubjects(ctx.subject(0), ctx.subject(1)).name(name).deliverSubject(random()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); + sub = ctx.js.subscribe(null, PushSubscribeOptions.builder().configuration(cc).build()); + validateMultipleSubjectFilterSub(sub, ctx.subject(0)); + + name = random(); + cc = ConsumerConfiguration.builder().filterSubjects(ctx.subject(0), ctx.subject(1)).name(name).deliverSubject(random()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); + sub = ctx.js.subscribe(null, PushSubscribeOptions.bind(ctx.stream, name)); + validateMultipleSubjectFilterSub(sub, ctx.subject(0)); // pull named - name = name(); - cc = ConsumerConfiguration.builder().filterSubjects(tsc.subject(0), tsc.subject(1)).name(name).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); - sub = js.subscribe(null, PullSubscribeOptions.builder().configuration(cc).build()); + name = random(); + cc = ConsumerConfiguration.builder().filterSubjects(ctx.subject(0), ctx.subject(1)).name(name).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); + sub = ctx.js.subscribe(null, PullSubscribeOptions.builder().configuration(cc).build()); sub.pullExpiresIn(15, 1000); - validateMultipleSubjectFilterSub(sub, tsc.subject(0)); + validateMultipleSubjectFilterSub(sub, ctx.subject(0)); - name = name(); - cc = ConsumerConfiguration.builder().filterSubjects(tsc.subject(0), tsc.subject(1)).name(name).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); - sub = js.subscribe(null, PullSubscribeOptions.bind(tsc.stream, name)); + name = random(); + cc = ConsumerConfiguration.builder().filterSubjects(ctx.subject(0), ctx.subject(1)).name(name).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); + sub = ctx.js.subscribe(null, PullSubscribeOptions.bind(ctx.stream, name)); sub.pullExpiresIn(15, 1000); - validateMultipleSubjectFilterSub(sub, tsc.subject(0)); + validateMultipleSubjectFilterSub(sub, ctx.subject(0)); }); } @@ -412,15 +400,13 @@ private static void validateMultipleSubjectFilterSub(JetStreamSubscription sub, @Test public void testRaiseStatusWarnings1194() throws Exception { - ListenerForTesting listenerForTesting = new ListenerForTesting(false, false); - runInJsServer(listenerForTesting, nc -> { + Listener listener = new Listener(false, false); + runInSharedOwnNc(listener, (nc, ctx) -> { // Setup - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - StreamContext streamContext = nc.getStreamContext(tsc.stream); + StreamContext streamContext = nc.getStreamContext(ctx.stream); // Setting maxBatch=1, so we shouldn't allow fetching more messages at once. - ConsumerConfiguration consumerConfig = ConsumerConfiguration.builder().filterSubject(tsc.subject()).maxBatch(1).build(); + ConsumerConfiguration consumerConfig = ConsumerConfiguration.builder().filterSubject(ctx.subject()).maxBatch(1).build(); ConsumerContext consumerContext = streamContext.createOrUpdateConsumer(consumerConfig); int count = 0; @@ -439,7 +425,7 @@ public void testRaiseStatusWarnings1194() throws Exception { } } assertEquals(0, count); - assertEquals(0, listenerForTesting.getPullStatusWarnings().size()); + assertEquals(0, listener.getPullStatusWarningsCount()); fco = FetchConsumeOptions.builder() .maxMessages(100) @@ -454,7 +440,7 @@ public void testRaiseStatusWarnings1194() throws Exception { } } assertEquals(0, count); - assertEquals(1, listenerForTesting.getPullStatusWarnings().size()); + assertEquals(1, listener.getPullStatusWarningsCount()); }); } } diff --git a/src/test/java/io/nats/client/impl/JetStreamGeneralTests.java b/src/test/java/io/nats/client/impl/JetStreamGeneralTests.java index eb5bb4fcb..eede22c37 100644 --- a/src/test/java/io/nats/client/impl/JetStreamGeneralTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamGeneralTests.java @@ -16,7 +16,7 @@ import io.nats.client.*; import io.nats.client.api.*; import io.nats.client.support.NatsJetStreamUtil; -import io.nats.client.support.RandomUtils; +import io.nats.client.utils.ConnectionUtils; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -25,30 +25,34 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import static io.nats.client.api.ConsumerConfiguration.*; import static io.nats.client.support.NatsConstants.EMPTY; import static io.nats.client.support.NatsJetStreamClientError.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.VersionUtils.atLeast2_10; +import static io.nats.client.utils.VersionUtils.before2_11; import static org.junit.jupiter.api.Assertions.*; public class JetStreamGeneralTests extends JetStreamTestBase { @Test public void testJetStreamContextCreate() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); // tries management functions - nc.jetStreamManagement().getAccountStatistics(); // another management - nc.jetStream().publish(tsc.subject(), dataBytes(1)); + runInShared((nc, ctx) -> { + ctx.jsm.getAccountStatistics(); // another management + ctx.js.publish(ctx.subject(), dataBytes(1)); }); } @Test public void testJetNotEnabled() throws Exception { - runInServer(nc -> { + runInOwnServer(nc -> { // get normal context, try to do an operation JetStream js = nc.jetStream(); - assertThrows(IOException.class, () -> js.subscribe(SUBJECT)); + assertThrows(IOException.class, () -> js.subscribe(random())); // get management context, try to do an operation JetStreamManagement jsm = nc.jetStreamManagement(); @@ -56,42 +60,24 @@ public void testJetNotEnabled() throws Exception { }); } - @Test - public void testJetEnabledGoodAccount() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/js_authorization.conf", false, true)) { - Options options = new Options.Builder().server(ts.getURI()) - .userInfo("serviceup".toCharArray(), "uppass".toCharArray()).build(); - Connection nc = standardConnection(options); - nc.jetStreamManagement(); - nc.jetStream(); - } - } - @Test public void testJetStreamPublishDefaultOptions() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - JetStream js = nc.jetStream(); - PublishAck ack = jsPublish(js, tsc.subject()); + runInShared((nc, ctx) -> { + PublishAck ack = jsPublish(ctx.js, ctx.subject()); assertEquals(1, ack.getSeqno()); }); } @Test - public void testConnectionClosing() throws Exception { - runInJsServer(nc -> { - nc.close(); - assertThrows(IOException.class, nc::jetStream); - assertThrows(IOException.class, nc::jetStreamManagement); - }); - } - - @Test - public void testCreateWithOptionsForCoverage() throws Exception { - jsServer.run(nc -> { + public void testExceptionsAndCoverage() throws Exception { + runInSharedOwnNc(nc -> { JetStreamOptions jso = JetStreamOptions.builder().build(); nc.jetStream(jso); nc.jetStreamManagement(jso); + + nc.close(); + assertThrows(IOException.class, nc::jetStream); + assertThrows(IOException.class, nc::jetStreamManagement); }); } @@ -107,237 +93,245 @@ public void testMiscMetaDataCoverage() { @Test public void testJetStreamSubscribe() throws Exception { - jsServer.run(nc -> { - - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - jsPublish(js, tsc.subject()); + runInShared((nc, ctx) -> { + jsPublish(ctx.js, ctx.subject()); // default ephemeral subscription. - Subscription s = js.subscribe(tsc.subject()); + Subscription s = ctx.js.subscribe(ctx.subject()); Message m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); assertEquals(DATA, new String(m.getData())); - List names = jsm.getConsumerNames(tsc.stream); + List names = ctx.jsm.getConsumerNames(ctx.stream); assertEquals(1, names.size()); // default subscribe options // ephemeral subscription. - s = js.subscribe(tsc.subject(), PushSubscribeOptions.builder().build()); + s = ctx.js.subscribe(ctx.subject(), PushSubscribeOptions.builder().build()); m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); assertEquals(DATA, new String(m.getData())); - names = jsm.getConsumerNames(tsc.stream); + names = ctx.jsm.getConsumerNames(ctx.stream); assertEquals(2, names.size()); // set the stream - PushSubscribeOptions pso = PushSubscribeOptions.builder().stream(tsc.stream).durable(DURABLE).build(); - s = js.subscribe(tsc.subject(), pso); + String durable = random(); + PushSubscribeOptions pso = PushSubscribeOptions.builder().stream(ctx.stream).durable(durable).build(); + s = ctx.js.subscribe(ctx.subject(), pso); m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); assertEquals(DATA, new String(m.getData())); - names = jsm.getConsumerNames(tsc.stream); + names = ctx.jsm.getConsumerNames(ctx.stream); assertEquals(3, names.size()); // coverage Dispatcher dispatcher = nc.createDispatcher(); - js.subscribe(tsc.subject()); - js.subscribe(tsc.subject(), (PushSubscribeOptions)null); - js.subscribe(tsc.subject(), QUEUE, null); - js.subscribe(tsc.subject(), dispatcher, mh -> {}, false); - js.subscribe(tsc.subject(), dispatcher, mh -> {}, false, null); - js.subscribe(tsc.subject(), QUEUE, dispatcher, mh -> {}, false, null); + ctx.js.subscribe(ctx.subject()); + ctx.js.subscribe(ctx.subject(), (PushSubscribeOptions) null); + ctx.js.subscribe(ctx.subject(), random(), null); + ctx.js.subscribe(ctx.subject(), dispatcher, mh -> { + }, false); + ctx.js.subscribe(ctx.subject(), dispatcher, mh -> { + }, false, null); + ctx.js.subscribe(ctx.subject(), random(), dispatcher, mh -> { + }, false, null); // bind with w/o subject - jsm.addOrUpdateConsumer(tsc.stream, + durable = random(); + String deliver = random(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder() - .durable(durable(101)) - .deliverSubject(deliver(101)) + .durable(durable) + .deliverSubject(deliver) .build()); - PushSubscribeOptions psoBind = PushSubscribeOptions.bind(tsc.stream, durable(101)); - unsubscribeEnsureNotBound(js.subscribe(null, psoBind)); - unsubscribeEnsureNotBound(js.subscribe("", psoBind)); - JetStreamSubscription sub = js.subscribe(null, dispatcher, mh -> {}, false, psoBind); + PushSubscribeOptions psoBind = PushSubscribeOptions.bind(ctx.stream, durable); + unsubscribeEnsureNotBound(ctx.js.subscribe(null, psoBind)); + unsubscribeEnsureNotBound(ctx.js.subscribe("", psoBind)); + JetStreamSubscription sub = ctx.js.subscribe(null, dispatcher, mh -> { + }, false, psoBind); unsubscribeEnsureNotBound(dispatcher, sub); - js.subscribe("", dispatcher, mh -> {}, false, psoBind); + ctx.js.subscribe("", dispatcher, mh -> { + }, false, psoBind); - jsm.addOrUpdateConsumer(tsc.stream, + durable = random(); + deliver = random(); + String queue = random(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder() - .durable(durable(102)) - .deliverSubject(deliver(102)) - .deliverGroup(queue(102)) + .durable(durable) + .deliverSubject(deliver) + .deliverGroup(queue) .build()); - psoBind = PushSubscribeOptions.bind(tsc.stream, durable(102)); - unsubscribeEnsureNotBound(js.subscribe(null, queue(102), psoBind)); - unsubscribeEnsureNotBound(js.subscribe("", queue(102), psoBind)); - sub = js.subscribe(null, queue(102), dispatcher, mh -> {}, false, psoBind); + psoBind = PushSubscribeOptions.bind(ctx.stream, durable); + unsubscribeEnsureNotBound(ctx.js.subscribe(null, queue, psoBind)); + unsubscribeEnsureNotBound(ctx.js.subscribe("", queue, psoBind)); + sub = ctx.js.subscribe(null, queue, dispatcher, mh -> { + }, false, psoBind); unsubscribeEnsureNotBound(dispatcher, sub); - js.subscribe("", queue(102), dispatcher, mh -> {}, false, psoBind); - - if (atLeast2_9_0(nc)) { - ConsumerConfiguration cc = builder().name(name(1)).build(); - pso = PushSubscribeOptions.builder().configuration(cc).build(); - sub = js.subscribe(tsc.subject(), pso); - m = sub.nextMessage(DEFAULT_TIMEOUT); - assertNotNull(m); - assertEquals(DATA, new String(m.getData())); - ConsumerInfo ci = sub.getConsumerInfo(); - assertEquals(name(1), ci.getName()); - assertEquals(name(1), ci.getConsumerConfiguration().getName()); - assertNull(ci.getConsumerConfiguration().getDurable()); - - cc = builder().durable(durable(1)).build(); - pso = PushSubscribeOptions.builder().configuration(cc).build(); - sub = js.subscribe(tsc.subject(), pso); - m = sub.nextMessage(DEFAULT_TIMEOUT); - assertNotNull(m); - assertEquals(DATA, new String(m.getData())); - ci = sub.getConsumerInfo(); - assertEquals(durable(1), ci.getName()); - assertEquals(durable(1), ci.getConsumerConfiguration().getName()); - assertEquals(durable(1), ci.getConsumerConfiguration().getDurable()); - - cc = builder().durable(name(2)).name(name(2)).build(); - pso = PushSubscribeOptions.builder().configuration(cc).build(); - sub = js.subscribe(tsc.subject(), pso); - m = sub.nextMessage(DEFAULT_TIMEOUT); - assertNotNull(m); - assertEquals(DATA, new String(m.getData())); - ci = sub.getConsumerInfo(); - assertEquals(name(2), ci.getName()); - assertEquals(name(2), ci.getConsumerConfiguration().getName()); - assertEquals(name(2), ci.getConsumerConfiguration().getDurable()); - - // test opt out - JetStreamOptions jso = JetStreamOptions.builder().optOut290ConsumerCreate(true).build(); - JetStream jsOptOut = nc.jetStream(jso); - ConsumerConfiguration ccOptOut = builder().name(name(99)).build(); - PushSubscribeOptions psoOptOut = PushSubscribeOptions.builder().configuration(ccOptOut).build(); - assertClientError(JsConsumerCreate290NotAvailable, () -> jsOptOut.subscribe(tsc.subject(), psoOptOut)); - } + ctx.js.subscribe("", queue, dispatcher, mh -> { + }, false, psoBind); + + String name = random(); + ConsumerConfiguration cc = builder().name(name).build(); + pso = PushSubscribeOptions.builder().configuration(cc).build(); + sub = ctx.js.subscribe(ctx.subject(), pso); + m = sub.nextMessage(DEFAULT_TIMEOUT); + assertNotNull(m); + assertEquals(DATA, new String(m.getData())); + ConsumerInfo ci = sub.getConsumerInfo(); + assertEquals(name, ci.getName()); + assertEquals(name, ci.getConsumerConfiguration().getName()); + assertNull(ci.getConsumerConfiguration().getDurable()); + + durable = random(); + cc = builder().durable(durable).build(); + pso = PushSubscribeOptions.builder().configuration(cc).build(); + sub = ctx.js.subscribe(ctx.subject(), pso); + m = sub.nextMessage(DEFAULT_TIMEOUT); + assertNotNull(m); + assertEquals(DATA, new String(m.getData())); + ci = sub.getConsumerInfo(); + assertEquals(durable, ci.getName()); + assertEquals(durable, ci.getConsumerConfiguration().getName()); + assertEquals(durable, ci.getConsumerConfiguration().getDurable()); + + String durName = random(); + cc = builder().durable(durName).name(durName).build(); + pso = PushSubscribeOptions.builder().configuration(cc).build(); + sub = ctx.js.subscribe(ctx.subject(), pso); + m = sub.nextMessage(DEFAULT_TIMEOUT); + assertNotNull(m); + assertEquals(DATA, new String(m.getData())); + ci = sub.getConsumerInfo(); + assertEquals(durName, ci.getName()); + assertEquals(durName, ci.getConsumerConfiguration().getName()); + assertEquals(durName, ci.getConsumerConfiguration().getDurable()); + + // test opt out + JetStreamOptions jso = JetStreamOptions.builder().optOut290ConsumerCreate(true).build(); + JetStream jsOptOut = nc.jetStream(jso); + ConsumerConfiguration ccOptOut = builder().name(random()).build(); + PushSubscribeOptions psoOptOut = PushSubscribeOptions.builder().configuration(ccOptOut).build(); + assertClientError(JsConsumerCreate290NotAvailable, () -> jsOptOut.subscribe(ctx.subject(), psoOptOut)); }); } @Test public void testJetStreamSubscribeLenientSubject() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - JetStream js = nc.jetStream(); + runInShared((nc, ctx) -> { Dispatcher d = nc.createDispatcher(); - js.subscribe(tsc.subject(), (PushSubscribeOptions)null); - js.subscribe(tsc.subject(), null, (PushSubscribeOptions)null); // queue name is not required, just a weird way to call this api - js.subscribe(tsc.subject(), d, m -> {}, false, (PushSubscribeOptions)null); - js.subscribe(tsc.subject(), null, d, m -> {}, false, (PushSubscribeOptions)null); // queue name is not required, just a weird way to call this api + ctx.js.subscribe(ctx.subject(), (PushSubscribeOptions)null); + ctx.js.subscribe(ctx.subject(), null, (PushSubscribeOptions)null); // queue name is not required, just a weird way to call this api + ctx.js.subscribe(ctx.subject(), d, m -> {}, false, (PushSubscribeOptions)null); + ctx.js.subscribe(ctx.subject(), null, d, m -> {}, false, (PushSubscribeOptions)null); // queue name is not required, just a weird way to call this api - PushSubscribeOptions pso = ConsumerConfiguration.builder().filterSubject(tsc.subject()).buildPushSubscribeOptions(); - js.subscribe(null, pso); - js.subscribe(null, null, pso); - js.subscribe(null, d, m -> {}, false, pso); - js.subscribe(null, null, d, m -> {}, false, pso); + PushSubscribeOptions pso = ConsumerConfiguration.builder().filterSubject(ctx.subject()).buildPushSubscribeOptions(); + ctx.js.subscribe(null, pso); + ctx.js.subscribe(null, null, pso); + ctx.js.subscribe(null, d, m -> {}, false, pso); + ctx.js.subscribe(null, null, d, m -> {}, false, pso); PushSubscribeOptions psoF = ConsumerConfiguration.builder().buildPushSubscribeOptions(); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, psoF)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, psoF)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, null, psoF)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, d, m -> {}, false, psoF)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, null, d, m -> {}, false, psoF)); - - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, (PushSubscribeOptions)null)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, (PushSubscribeOptions)null)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, null, (PushSubscribeOptions)null)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, d, m -> {}, false, (PushSubscribeOptions)null)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, null, d, m -> {}, false, (PushSubscribeOptions)null)); - - PullSubscribeOptions lso = ConsumerConfiguration.builder().filterSubject(tsc.subject()).buildPullSubscribeOptions(); - js.subscribe(null, lso); - js.subscribe(null, d, m -> {}, lso); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, psoF)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, psoF)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, null, psoF)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, d, m -> {}, false, psoF)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, null, d, m -> {}, false, psoF)); + + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, (PushSubscribeOptions)null)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, (PushSubscribeOptions)null)); + //noinspection RedundantCast + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, null, (PushSubscribeOptions)null)); + //noinspection RedundantCast + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, d, m -> {}, false, (PushSubscribeOptions)null)); + //noinspection RedundantCast + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, null, d, m -> {}, false, (PushSubscribeOptions)null)); + + PullSubscribeOptions lso = ConsumerConfiguration.builder().filterSubject(ctx.subject()).buildPullSubscribeOptions(); + ctx.js.subscribe(null, lso); + ctx.js.subscribe(null, d, m -> {}, lso); PullSubscribeOptions lsoF = ConsumerConfiguration.builder().buildPullSubscribeOptions(); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, lsoF)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, d, m -> {}, lsoF)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, lsoF)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, d, m -> {}, lsoF)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, (PullSubscribeOptions)null)); - assertThrows(IllegalArgumentException.class, () -> js.subscribe(null, d, m -> {}, (PullSubscribeOptions)null)); + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, (PullSubscribeOptions)null)); + //noinspection RedundantCast + assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(null, d, m -> {}, (PullSubscribeOptions)null)); }); } @Test public void testJetStreamSubscribeErrors() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { + String stream = random(); // stream not found - PushSubscribeOptions psoInvalidStream = PushSubscribeOptions.builder().stream(STREAM).build(); - assertThrows(JetStreamApiException.class, () -> js.subscribe(SUBJECT, psoInvalidStream)); + PushSubscribeOptions psoInvalidStream = PushSubscribeOptions.builder().stream(stream).build(); + assertThrows(JetStreamApiException.class, () -> ctx.js.subscribe(random(), psoInvalidStream)); Dispatcher d = nc.createDispatcher(); for (String bad : BAD_SUBJECTS_OR_QUEUES) { if (bad == null || bad.isEmpty()) { // subject - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(bad)); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(bad)); assertTrue(iae.getMessage().startsWith("Subject")); - assertClientError(JsSubSubjectNeededToLookupStream, () -> js.subscribe(bad, (PushSubscribeOptions)null)); + assertClientError(JsSubSubjectNeededToLookupStream, () -> ctx.js.subscribe(bad, (PushSubscribeOptions)null)); } else { // subject - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(bad)); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(bad)); assertTrue(iae.getMessage().startsWith("Subject")); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(bad, (PushSubscribeOptions)null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(bad, (PushSubscribeOptions)null)); assertTrue(iae.getMessage().startsWith("Subject")); // queue - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, bad, null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), bad, null)); assertTrue(iae.getMessage().startsWith("Queue")); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, bad, d, m -> {}, false, null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), bad, d, m -> {}, false, null)); assertTrue(iae.getMessage().startsWith("Queue")); } } // dispatcher - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, null, null, false)); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), null, null, false)); assertTrue(iae.getMessage().startsWith("Dispatcher")); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, null, null, false, null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), null, null, false, null)); assertTrue(iae.getMessage().startsWith("Dispatcher")); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, QUEUE, null, null, false, null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), random(), null, null, false, null)); assertTrue(iae.getMessage().startsWith("Dispatcher")); // handler Dispatcher dispatcher = nc.createDispatcher(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, dispatcher, null, false)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), dispatcher, null, false)); assertTrue(iae.getMessage().startsWith("Handler")); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, dispatcher, null, false, null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), dispatcher, null, false, null)); assertTrue(iae.getMessage().startsWith("Handler")); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(SUBJECT, QUEUE, dispatcher, null, false, null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(random(), random(), dispatcher, null, false, null)); assertTrue(iae.getMessage().startsWith("Handler")); }); } @Test public void testFilterSubjectEphemeral() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - String subjectWild = SUBJECT + ".*"; - String subjectA = SUBJECT + ".A"; - String subjectB = SUBJECT + ".B"; - TestingStreamContainer tsc = new TestingStreamContainer(nc, subjectWild); - - jsPublish(js, subjectA, 1); - jsPublish(js, subjectB, 1); - jsPublish(js, subjectA, 1); - jsPublish(js, subjectB, 1); + runInSharedCustom((nc, ctx) -> { + String subject = random(); + String subjectWild = subject + ".*"; + String subjectA = subject + ".A"; + String subjectB = subject + ".B"; + ctx.createOrReplaceStream(subjectWild); + + jsPublish(ctx.js, subjectA, 1); + jsPublish(ctx.js, subjectB, 1); + jsPublish(ctx.js, subjectA, 1); + jsPublish(ctx.js, subjectB, 1); // subscribe to the wildcard ConsumerConfiguration cc = builder().ackPolicy(AckPolicy.None).build(); PushSubscribeOptions pso = PushSubscribeOptions.builder().configuration(cc).build(); - JetStreamSubscription sub = js.subscribe(subjectWild, pso); + JetStreamSubscription sub = ctx.js.subscribe(subjectWild, pso); nc.flush(Duration.ofSeconds(1)); Message m = sub.nextMessage(Duration.ofSeconds(1)); @@ -356,7 +350,7 @@ public void testFilterSubjectEphemeral() throws Exception { // subscribe to A cc = builder().filterSubject(subjectA).ackPolicy(AckPolicy.None).build(); pso = PushSubscribeOptions.builder().configuration(cc).build(); - sub = js.subscribe(subjectA, pso); + sub = ctx.js.subscribe(subjectA, pso); nc.flush(Duration.ofSeconds(1)); m = sub.nextMessage(Duration.ofSeconds(1)); @@ -371,7 +365,7 @@ public void testFilterSubjectEphemeral() throws Exception { // subscribe to B cc = builder().filterSubject(subjectB).ackPolicy(AckPolicy.None).build(); pso = PushSubscribeOptions.builder().configuration(cc).build(); - sub = js.subscribe(subjectB, pso); + sub = ctx.js.subscribe(subjectB, pso); nc.flush(Duration.ofSeconds(1)); m = sub.nextMessage(Duration.ofSeconds(1)); @@ -392,16 +386,15 @@ public void testPrefix() throws Exception { String streamMadeByTar = "stream-made-by-tar"; String subjectMadeBySrc = "sub-made-by.src"; String subjectMadeByTar = "sub-made-by.tar"; + runInConfiguredServer("js_prefix.conf", ts -> { + Options optionsSrc = optionsBuilder(ts) + .userInfo("src".toCharArray(), "spass".toCharArray()).build(); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/js_prefix.conf", false)) { - Options optionsSrc = new Options.Builder().server(ts.getURI()) - .userInfo("src".toCharArray(), "spass".toCharArray()).build(); - - Options optionsTar = new Options.Builder().server(ts.getURI()) - .userInfo("tar".toCharArray(), "tpass".toCharArray()).build(); + Options optionsTar = optionsBuilder(ts) + .userInfo("tar".toCharArray(), "tpass".toCharArray()).build(); - try (Connection ncSrc = Nats.connect(optionsSrc); - Connection ncTar = Nats.connect(optionsTar) + try (Connection ncSrc = ConnectionUtils.managedConnect(optionsSrc); + Connection ncTar = ConnectionUtils.managedConnect(optionsTar) ) { // Setup JetStreamOptions. SOURCE does not need prefix JetStreamOptions jsoSrc = JetStreamOptions.builder().build(); @@ -413,17 +406,17 @@ public void testPrefix() throws Exception { // add streams with both account StreamConfiguration scSrc = StreamConfiguration.builder() - .name(streamMadeBySrc) - .storageType(StorageType.Memory) - .subjects(subjectMadeBySrc) - .build(); + .name(streamMadeBySrc) + .storageType(StorageType.Memory) + .subjects(subjectMadeBySrc) + .build(); jsmSrc.addStream(scSrc); StreamConfiguration scTar = StreamConfiguration.builder() - .name(streamMadeByTar) - .storageType(StorageType.Memory) - .subjects(subjectMadeByTar) - .build(); + .name(streamMadeByTar) + .storageType(StorageType.Memory) + .subjects(subjectMadeByTar) + .build(); jsmTar.addStream(scTar); JetStream jsSrc = ncSrc.jetStream(jsoSrc); @@ -440,7 +433,7 @@ public void testPrefix() throws Exception { readPrefixMessages(ncTar, jsTar, subjectMadeBySrc, "src"); readPrefixMessages(ncTar, jsTar, subjectMadeByTar, "tar"); } - } + }); } private void readPrefixMessages(Connection nc, JetStream js, String subject, String dest) throws InterruptedException, IOException, JetStreamApiException, TimeoutException { @@ -457,67 +450,61 @@ private void readPrefixMessages(Connection nc, JetStream js, String subject, Str @Test public void testBindPush() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - JetStream js = nc.jetStream(); - - jsPublish(js, tsc.subject(), 1, 1); + runInShared((nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 1, 1); PushSubscribeOptions pso = PushSubscribeOptions.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .build(); - JetStreamSubscription s = js.subscribe(tsc.subject(), pso); + JetStreamSubscription s = ctx.js.subscribe(ctx.subject(), pso); Message m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); assertEquals(data(1), new String(m.getData())); m.ack(); unsubscribeEnsureNotBound(s); - jsPublish(js, tsc.subject(), 2, 1); + jsPublish(ctx.js, ctx.subject(), 2, 1); pso = PushSubscribeOptions.builder() - .stream(tsc.stream) - .durable(tsc.consumerName()) + .stream(ctx.stream) + .durable(ctx.consumerName()) .bind(true) .build(); - s = js.subscribe(tsc.subject(), pso); + s = ctx.js.subscribe(ctx.subject(), pso); m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); assertEquals(data(2), new String(m.getData())); m.ack(); unsubscribeEnsureNotBound(s); - jsPublish(js, tsc.subject(), 3, 1); - pso = PushSubscribeOptions.bind(tsc.stream, tsc.consumerName()); - s = js.subscribe(tsc.subject(), pso); + jsPublish(ctx.js, ctx.subject(), 3, 1); + pso = PushSubscribeOptions.bind(ctx.stream, ctx.consumerName()); + s = ctx.js.subscribe(ctx.subject(), pso); m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); assertEquals(data(3), new String(m.getData())); assertThrows(IllegalArgumentException.class, - () -> PushSubscribeOptions.builder().stream(tsc.stream).bind(true).build()); + () -> PushSubscribeOptions.builder().stream(ctx.stream).bind(true).build()); assertThrows(IllegalArgumentException.class, - () -> PushSubscribeOptions.builder().durable(tsc.consumerName()).bind(true).build()); + () -> PushSubscribeOptions.builder().durable(ctx.consumerName()).bind(true).build()); assertThrows(IllegalArgumentException.class, () -> PushSubscribeOptions.builder().stream(EMPTY).bind(true).build()); assertThrows(IllegalArgumentException.class, - () -> PushSubscribeOptions.builder().stream(tsc.stream).durable(EMPTY).bind(true).build()); + () -> PushSubscribeOptions.builder().stream(ctx.stream).durable(EMPTY).bind(true).build()); }); } @Test public void testBindPull() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - JetStream js = nc.jetStream(); - - jsPublish(js, tsc.subject(), 1, 1); + runInShared((nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 1, 1); PullSubscribeOptions pso = PullSubscribeOptions.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .build(); - JetStreamSubscription s = js.subscribe(tsc.subject(), pso); + JetStreamSubscription s = ctx.js.subscribe(ctx.subject(), pso); s.pull(1); Message m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); @@ -525,13 +512,13 @@ public void testBindPull() throws Exception { m.ack(); unsubscribeEnsureNotBound(s); - jsPublish(js, tsc.subject(), 2, 1); + jsPublish(ctx.js, ctx.subject(), 2, 1); pso = PullSubscribeOptions.builder() - .stream(tsc.stream) - .durable(tsc.consumerName()) + .stream(ctx.stream) + .durable(ctx.consumerName()) .bind(true) .build(); - s = js.subscribe(tsc.subject(), pso); + s = ctx.js.subscribe(ctx.subject(), pso); s.pull(1); m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); @@ -539,9 +526,9 @@ public void testBindPull() throws Exception { m.ack(); unsubscribeEnsureNotBound(s); - jsPublish(js, tsc.subject(), 3, 1); - pso = PullSubscribeOptions.bind(tsc.stream, tsc.consumerName()); - s = js.subscribe(tsc.subject(), pso); + jsPublish(ctx.js, ctx.subject(), 3, 1); + pso = PullSubscribeOptions.bind(ctx.stream, ctx.consumerName()); + s = ctx.js.subscribe(ctx.subject(), pso); s.pull(1); m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); @@ -551,329 +538,323 @@ public void testBindPull() throws Exception { @Test public void testBindErrors() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // bind errors - PushSubscribeOptions pushbinderr = PushSubscribeOptions.bind(tsc.stream, "binddur"); - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), pushbinderr)); + PushSubscribeOptions pushbinderr = PushSubscribeOptions.bind(ctx.stream, "binddur"); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), pushbinderr)); assertTrue(iae.getMessage().contains(JsSubConsumerNotFoundRequiredInBind.id())); - PullSubscribeOptions pullbinderr = PullSubscribeOptions.bind(tsc.stream, "binddur"); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), pullbinderr)); + PullSubscribeOptions pullbinderr = PullSubscribeOptions.bind(ctx.stream, "binddur"); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), pullbinderr)); assertTrue(iae.getMessage().contains(JsSubConsumerNotFoundRequiredInBind.id())); }); } @Test public void testFilterMismatchErrors() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); + runInOwnJsServer((nc, jsm, js) -> { + String stream = random(); + String subject = random(); - // single subject - TestingStreamContainer tsc = new TestingStreamContainer(nc); + createMemoryStream(jsm, stream, subject); // will work as SubscribeSubject equals Filter Subject - filterMatchSubscribeOk(js, jsm, tsc.stream, tsc.subject(), tsc.subject()); - filterMatchSubscribeOk(js, jsm, tsc.stream, ">", ">"); - filterMatchSubscribeOk(js, jsm, tsc.stream, "*", "*"); + filterMatchSubscribeOk(js, jsm, stream, subject, subject); + filterMatchSubscribeOk(js, jsm, stream, ">", ">"); + filterMatchSubscribeOk(js, jsm, stream, "*", "*"); // will not work - filterMatchSubscribeEx(js, jsm, tsc.stream, tsc.subject(), ""); - filterMatchSubscribeEx(js, jsm, tsc.stream, tsc.subject(), ">"); - filterMatchSubscribeEx(js, jsm, tsc.stream, tsc.subject(), "*"); + filterMatchSubscribeEx(js, jsm, stream, subject, ""); + filterMatchSubscribeEx(js, jsm, stream, subject, ">"); + filterMatchSubscribeEx(js, jsm, stream, subject, "*"); // multiple subjects no wildcards - jsm.deleteStream(tsc.stream); - createMemoryStream(jsm, tsc.stream, tsc.subject(), subject(2)); + jsm.deleteStream(stream); + createMemoryStream(jsm, stream, subject, subject(2)); // will work as SubscribeSubject equals Filter Subject - filterMatchSubscribeOk(js, jsm, tsc.stream, tsc.subject(), tsc.subject()); - filterMatchSubscribeOk(js, jsm, tsc.stream, ">", ">"); - filterMatchSubscribeOk(js, jsm, tsc.stream, "*", "*"); + filterMatchSubscribeOk(js, jsm, stream, subject, subject); + filterMatchSubscribeOk(js, jsm, stream, ">", ">"); + filterMatchSubscribeOk(js, jsm, stream, "*", "*"); // will not work because stream has more than 1 subject - filterMatchSubscribeEx(js, jsm, tsc.stream, tsc.subject(), ""); - filterMatchSubscribeEx(js, jsm, tsc.stream, tsc.subject(), ">"); - filterMatchSubscribeEx(js, jsm, tsc.stream, tsc.subject(), "*"); + filterMatchSubscribeEx(js, jsm, stream, subject, ""); + filterMatchSubscribeEx(js, jsm, stream, subject, ">"); + filterMatchSubscribeEx(js, jsm, stream, subject, "*"); - String subjectGt = tsc.subject() + ".>"; - String subjectStar = tsc.subject() + ".*"; - String subjectDot = tsc.subject() + "." + name(); + String subjectGt = subject + ".>"; + String subjectStar = subject + ".*"; + String subjectDot = subject + "." + random(); // multiple subjects via '>' - jsm.deleteStream(tsc.stream); - createMemoryStream(jsm, tsc.stream, subjectGt); + jsm.deleteStream(stream); + createMemoryStream(jsm, stream, subjectGt); // will work, exact matches - filterMatchSubscribeOk(js, jsm, tsc.stream, subjectDot, subjectDot); - filterMatchSubscribeOk(js, jsm, tsc.stream, ">", ">"); + filterMatchSubscribeOk(js, jsm, stream, subjectDot, subjectDot); + filterMatchSubscribeOk(js, jsm, stream, ">", ">"); // will not work because mismatch / stream has more than 1 subject - filterMatchSubscribeEx(js, jsm, tsc.stream, subjectDot, ""); - filterMatchSubscribeEx(js, jsm, tsc.stream, subjectDot, ">"); - filterMatchSubscribeEx(js, jsm, tsc.stream, subjectDot, subjectGt); + filterMatchSubscribeEx(js, jsm, stream, subjectDot, ""); + filterMatchSubscribeEx(js, jsm, stream, subjectDot, ">"); + filterMatchSubscribeEx(js, jsm, stream, subjectDot, subjectGt); // multiple subjects via '*' - jsm.deleteStream(tsc.stream); - createMemoryStream(jsm, tsc.stream, subjectStar); + jsm.deleteStream(stream); + createMemoryStream(jsm, stream, subjectStar); // will work, exact matches - filterMatchSubscribeOk(js, jsm, tsc.stream, subjectDot, subjectDot); - filterMatchSubscribeOk(js, jsm, tsc.stream, ">", ">"); + filterMatchSubscribeOk(js, jsm, stream, subjectDot, subjectDot); + filterMatchSubscribeOk(js, jsm, stream, ">", ">"); // will not work because mismatch / stream has more than 1 subject - filterMatchSubscribeEx(js, jsm, tsc.stream, subjectDot, ""); - filterMatchSubscribeEx(js, jsm, tsc.stream, subjectDot, ">"); - filterMatchSubscribeEx(js, jsm, tsc.stream, subjectDot, subjectStar); + filterMatchSubscribeEx(js, jsm, stream, subjectDot, ""); + filterMatchSubscribeEx(js, jsm, stream, subjectDot, ">"); + filterMatchSubscribeEx(js, jsm, stream, subjectDot, subjectStar); }); } private void filterMatchSubscribeOk(JetStream js, JetStreamManagement jsm, String stream, String subscribeSubject, String... filterSubjects) throws IOException, JetStreamApiException { - int i = RandomUtils.PRAND.nextInt(); // just want a unique number - filterMatchSetupConsumer(jsm, i, stream, filterSubjects); - unsubscribeEnsureNotBound(js.subscribe(subscribeSubject, builder().durable(durable(i)).buildPushSubscribeOptions())); + String deliver = random(); + String dur = random(); + filterMatchSetupConsumer(jsm, deliver, dur, stream, filterSubjects); + unsubscribeEnsureNotBound(js.subscribe(subscribeSubject, builder().durable(dur).buildPushSubscribeOptions())); } private void filterMatchSubscribeEx(JetStream js, JetStreamManagement jsm, String stream, String subscribeSubject, String... filterSubjects) throws IOException, JetStreamApiException { - int i = RandomUtils.PRAND.nextInt(); // just want a unique number - filterMatchSetupConsumer(jsm, i, stream, filterSubjects); + String deliver = random(); + String dur = random(); + filterMatchSetupConsumer(jsm, deliver, dur, stream, filterSubjects); IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, - () -> js.subscribe(subscribeSubject, builder().durable(durable(i)).buildPushSubscribeOptions())); + () -> js.subscribe(subscribeSubject, builder().durable(dur).buildPushSubscribeOptions())); assertTrue(iae.getMessage().contains(JsSubSubjectDoesNotMatchFilter.id())); } - private void filterMatchSetupConsumer(JetStreamManagement jsm, int i, String stream, String... fs) throws IOException, JetStreamApiException { + private void filterMatchSetupConsumer(JetStreamManagement jsm, String deliver, String dur, String stream, String... fs) throws IOException, JetStreamApiException { jsm.addOrUpdateConsumer(stream, - builder().deliverSubject(deliver(i)).durable(durable(i)).filterSubjects(fs).build()); + builder().deliverSubject(deliver).durable(dur).filterSubjects(fs).build()); } @Test public void testBindDurableDeliverSubject() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - + runInShared((nc, ctx) -> { // create a durable push subscriber - has a deliver subject + String dur1 = random(); + String dur2 = random(); + String deliver = random(); ConsumerConfiguration ccDurPush = builder() - .durable(durable(1)) - .deliverSubject(deliver(1)) - .filterSubject(tsc.subject()) + .durable(dur1) + .deliverSubject(deliver) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, ccDurPush); + ctx.jsm.addOrUpdateConsumer(ctx.stream, ccDurPush); // create a durable pull subscriber - notice no deliver subject ConsumerConfiguration ccDurPull = builder() - .durable(durable(2)) - .filterSubject(tsc.subject()) + .durable(dur2) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, ccDurPull); + ctx.jsm.addOrUpdateConsumer(ctx.stream, ccDurPull); // try to pull subscribe against a push durable IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, - () -> js.subscribe(tsc.subject(), PullSubscribeOptions.builder().durable(durable(1)).build()) + () -> ctx.js.subscribe(ctx.subject(), PullSubscribeOptions.builder().durable(dur1).build()) ); assertTrue(iae.getMessage().contains(JsSubConsumerAlreadyConfiguredAsPush.id())); // try to pull bind against a push durable iae = assertThrows(IllegalArgumentException.class, - () -> js.subscribe(tsc.subject(), PullSubscribeOptions.bind(tsc.stream, durable(1))) + () -> ctx.js.subscribe(ctx.subject(), PullSubscribeOptions.bind(ctx.stream, dur1)) ); assertTrue(iae.getMessage().contains(JsSubConsumerAlreadyConfiguredAsPush.id())); // try to push subscribe against a pull durable iae = assertThrows(IllegalArgumentException.class, - () -> js.subscribe(tsc.subject(), PushSubscribeOptions.builder().durable(durable(2)).build()) + () -> ctx.js.subscribe(ctx.subject(), PushSubscribeOptions.builder().durable(dur2).build()) ); assertTrue(iae.getMessage().contains(JsSubConsumerAlreadyConfiguredAsPull.id())); // try to push bind against a pull durable iae = assertThrows(IllegalArgumentException.class, - () -> js.subscribe(tsc.subject(), PushSubscribeOptions.bind(tsc.stream, durable(2))) + () -> ctx.js.subscribe(ctx.subject(), PushSubscribeOptions.bind(ctx.stream, dur2)) ); assertTrue(iae.getMessage().contains(JsSubConsumerAlreadyConfiguredAsPull.id())); // this one is okay - js.subscribe(tsc.subject(), PushSubscribeOptions.builder().durable(durable(1)).build()); + ctx.js.subscribe(ctx.subject(), PushSubscribeOptions.builder().durable(dur1).build()); }); } @Test public void testConsumerIsNotModified() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - + runInShared((nc, ctx) -> { // test with config in issue 105 + String dur = random(); + String q = random(); ConsumerConfiguration cc = builder() .description("desc") .ackPolicy(AckPolicy.Explicit) .deliverPolicy(DeliverPolicy.All) - .deliverSubject(deliver(1)) - .deliverGroup(queue(1)) - .durable(durable(1)) + .deliverSubject(random()) + .deliverGroup(q) + .durable(dur) .maxAckPending(65000) .maxDeliver(5) .maxBatch(10) .maxBytes(11) .replayPolicy(ReplayPolicy.Instant) - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - PushSubscribeOptions pushOpts = PushSubscribeOptions.bind(tsc.stream, durable(1)); - js.subscribe(tsc.subject(), queue(1), pushOpts); // should not throw an error + PushSubscribeOptions pushOpts = PushSubscribeOptions.bind(ctx.stream, dur); + ctx.js.subscribe(ctx.subject(), q, pushOpts); // should not throw an error // testing numerics + dur = random(); cc = builder() .deliverPolicy(DeliverPolicy.ByStartSequence) - .deliverSubject(deliver(21)) - .durable(durable(21)) + .deliverSubject(random()) + .durable(dur) .startSequence(42) .maxDeliver(43) .maxBatch(47) .maxBytes(48) .rateLimit(44) .maxAckPending(45) - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - pushOpts = PushSubscribeOptions.bind(tsc.stream, durable(21)); - js.subscribe(tsc.subject(), pushOpts); // should not throw an error + pushOpts = PushSubscribeOptions.bind(ctx.stream, dur); + ctx.js.subscribe(ctx.subject(), pushOpts); // should not throw an error + dur = random(); cc = builder() - .durable(durable(22)) + .durable(dur) .maxPullWaiting(46) - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - PullSubscribeOptions pullOpts = PullSubscribeOptions.bind(tsc.stream, durable(22)); - js.subscribe(tsc.subject(), pullOpts); // should not throw an error + PullSubscribeOptions pullOpts = PullSubscribeOptions.bind(ctx.stream, dur); + ctx.js.subscribe(ctx.subject(), pullOpts); // should not throw an error // testing DateTime + dur = random(); cc = builder() .deliverPolicy(DeliverPolicy.ByStartTime) - .deliverSubject(deliver(3)) - .durable(durable(3)) + .deliverSubject(random()) + .durable(dur) .startTime(ZonedDateTime.now().plusHours(1)) - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - pushOpts = PushSubscribeOptions.bind(tsc.stream, durable(3)); - js.subscribe(tsc.subject(), pushOpts); // should not throw an error + pushOpts = PushSubscribeOptions.bind(ctx.stream, dur); + ctx.js.subscribe(ctx.subject(), pushOpts); // should not throw an error // testing boolean and duration + dur = random(); cc = builder() - .deliverSubject(deliver(4)) - .durable(durable(4)) + .deliverSubject(random()) + .durable(dur) .flowControl(1000) .headersOnly(true) .maxExpires(30000) .ackWait(2000) - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - pushOpts = PushSubscribeOptions.bind(tsc.stream, durable(4)); - js.subscribe(tsc.subject(), pushOpts); // should not throw an error + pushOpts = PushSubscribeOptions.bind(ctx.stream, dur); + ctx.js.subscribe(ctx.subject(), pushOpts); // should not throw an error // testing enums + dur = random(); cc = builder() - .deliverSubject(deliver(5)) - .durable(durable(5)) + .deliverSubject(random()) + .durable(dur) .deliverPolicy(DeliverPolicy.Last) .ackPolicy(AckPolicy.None) .replayPolicy(ReplayPolicy.Original) - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - pushOpts = PushSubscribeOptions.bind(tsc.stream, durable(5)); - js.subscribe(tsc.subject(), pushOpts); // should not throw an error + pushOpts = PushSubscribeOptions.bind(ctx.stream, dur); + ctx.js.subscribe(ctx.subject(), pushOpts); // should not throw an error }); } @Test public void testSubscribeDurableConsumerMustMatch() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - - String stream = stream(); - String subject = subject(); - createMemoryStream(nc, stream, subject); + runInShared((nc, ctx) -> { + String stream = ctx.stream; + String subject = ctx.subject(); // push - String uname = durable(); - String deliver = deliver(); + String uname = random(); + String deliver = random(); nc.jetStreamManagement().addOrUpdateConsumer(stream, pushDurableBuilder(subject, uname, deliver).build()); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).deliverPolicy(DeliverPolicy.Last), "deliverPolicy"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).deliverPolicy(DeliverPolicy.New), "deliverPolicy"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).ackPolicy(AckPolicy.None), "ackPolicy"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).ackPolicy(AckPolicy.All), "ackPolicy"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).replayPolicy(ReplayPolicy.Original), "replayPolicy"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).deliverPolicy(DeliverPolicy.Last), "deliverPolicy"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).deliverPolicy(DeliverPolicy.New), "deliverPolicy"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).ackPolicy(AckPolicy.None), "ackPolicy"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).ackPolicy(AckPolicy.All), "ackPolicy"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).replayPolicy(ReplayPolicy.Original), "replayPolicy"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).flowControl(10000), "flowControl"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).headersOnly(true), "headersOnly"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).flowControl(10000), "flowControl"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).headersOnly(true), "headersOnly"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).startTime(ZonedDateTime.now()), "startTime"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(Duration.ofMillis(1)), "ackWait"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).description("x"), "description"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).sampleFrequency("x"), "sampleFrequency"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).idleHeartbeat(Duration.ofMillis(1000)), "idleHeartbeat"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).maxExpires(Duration.ofMillis(1000)), "maxExpires"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).inactiveThreshold(Duration.ofMillis(1000)), "inactiveThreshold"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).startTime(ZonedDateTime.now()), "startTime"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(Duration.ofMillis(1)), "ackWait"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).description("x"), "description"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).sampleFrequency("x"), "sampleFrequency"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).idleHeartbeat(Duration.ofMillis(1000)), "idleHeartbeat"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).maxExpires(Duration.ofMillis(1000)), "maxExpires"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).inactiveThreshold(Duration.ofMillis(1000)), "inactiveThreshold"); // value - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).maxDeliver(MAX_DELIVER_MIN), "maxDeliver"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).maxAckPending(0), "maxAckPending"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(0), "ackWait"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).maxDeliver(MAX_DELIVER_MIN), "maxDeliver"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).maxAckPending(0), "maxAckPending"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(0), "ackWait"); // value unsigned - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).startSequence(1), "startSequence"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).rateLimit(1), "rateLimit"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).startSequence(1), "startSequence"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).rateLimit(1), "rateLimit"); // unset doesn't fail because the server provides a value equal to the unset - changeOkPush(js, subject, pushDurableBuilder(subject, uname, deliver).maxDeliver(INTEGER_UNSET)); + changeOkPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).maxDeliver(INTEGER_UNSET)); // unset doesn't fail because the server does not provide a value // negatives are considered the unset - changeOkPush(js, subject, pushDurableBuilder(subject, uname, deliver).startSequence(ULONG_UNSET)); - changeOkPush(js, subject, pushDurableBuilder(subject, uname, deliver).startSequence(-1)); - changeOkPush(js, subject, pushDurableBuilder(subject, uname, deliver).rateLimit(ULONG_UNSET)); - changeOkPush(js, subject, pushDurableBuilder(subject, uname, deliver).rateLimit(-1)); + changeOkPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).startSequence(ULONG_UNSET)); + changeOkPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).startSequence(-1)); + changeOkPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).rateLimit(ULONG_UNSET)); + changeOkPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).rateLimit(-1)); // unset fail b/c the server does set a value that is not equal to the unset or the minimum - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).maxAckPending(LONG_UNSET), "maxAckPending"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).maxAckPending(0), "maxAckPending"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(LONG_UNSET), "ackWait"); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(0), "ackWait"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).maxAckPending(LONG_UNSET), "maxAckPending"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).maxAckPending(0), "maxAckPending"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(LONG_UNSET), "ackWait"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).ackWait(0), "ackWait"); // pull - String lname = durable(); + String lname = random(); nc.jetStreamManagement().addOrUpdateConsumer(stream, pullDurableBuilder(subject, lname).build()); // value - changeExPull(js, subject, pullDurableBuilder(subject, lname).maxPullWaiting(0), "maxPullWaiting"); - changeExPull(js, subject, pullDurableBuilder(subject, lname).maxBatch(0), "maxBatch"); - changeExPull(js, subject, pullDurableBuilder(subject, lname).maxBytes(0), "maxBytes"); + changeExPull(ctx.js, subject, pullDurableBuilder(subject, lname).maxPullWaiting(0), "maxPullWaiting"); + changeExPull(ctx.js, subject, pullDurableBuilder(subject, lname).maxBatch(0), "maxBatch"); + changeExPull(ctx.js, subject, pullDurableBuilder(subject, lname).maxBytes(0), "maxBytes"); // unsets fail b/c the server does set a value - changeExPull(js, subject, pullDurableBuilder(subject, lname).maxPullWaiting(-1), "maxPullWaiting"); + changeExPull(ctx.js, subject, pullDurableBuilder(subject, lname).maxPullWaiting(-1), "maxPullWaiting"); // unset - changeOkPull(js, subject, pullDurableBuilder(subject, lname).maxBatch(-1)); - changeOkPull(js, subject, pullDurableBuilder(subject, lname).maxBytes(-1)); + changeOkPull(ctx.js, subject, pullDurableBuilder(subject, lname).maxBatch(-1)); + changeOkPull(ctx.js, subject, pullDurableBuilder(subject, lname).maxBytes(-1)); // metadata Map metadataA = new HashMap<>(); metadataA.put("a", "A"); @@ -882,18 +863,18 @@ public void testSubscribeDurableConsumerMustMatch() throws Exception { if (atLeast2_10()) { // metadata server null versus new not null nc.jetStreamManagement().addOrUpdateConsumer(stream, pushDurableBuilder(subject, uname, deliver).build()); - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).metadata(metadataA), "metadata"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).metadata(metadataA), "metadata"); // metadata server not null versus new null nc.jetStreamManagement().addOrUpdateConsumer(stream, pushDurableBuilder(subject, uname, deliver).metadata(metadataA).build()); - changeOkPush(js, subject, pushDurableBuilder(subject, uname, deliver)); + changeOkPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver)); // metadata server not null versus new not null but different - changeExPush(js, subject, pushDurableBuilder(subject, uname, deliver).metadata(metadataB), "metadata"); + changeExPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).metadata(metadataB), "metadata"); if (before2_11()) { // metadata server not null versus new not null and same - changeOkPush(js, subject, pushDurableBuilder(subject, uname, deliver).metadata(metadataA)); + changeOkPush(ctx.js, subject, pushDurableBuilder(subject, uname, deliver).metadata(metadataA)); } } }); @@ -935,46 +916,35 @@ private Builder pullDurableBuilder(String subject, String durable) { @Test public void testGetConsumerInfoFromSubscription() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - JetStreamSubscription sub = js.subscribe(tsc.subject()); + runInShared((nc, ctx) -> { + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject()); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server ConsumerInfo ci = sub.getConsumerInfo(); - assertEquals(tsc.stream, ci.getStreamName()); + assertEquals(ctx.stream, ci.getStreamName()); }); } @Test public void testInternalLookupConsumerInfoCoverage() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // - consumer not found // - stream does not exist - JetStreamSubscription sub = js.subscribe(tsc.subject()); - assertNull(((NatsJetStream)js).lookupConsumerInfo(tsc.stream, tsc.consumerName())); + ctx.js.subscribe(ctx.subject()); + assertNull(ctx.js.lookupConsumerInfo(ctx.stream, random())); assertThrows(JetStreamApiException.class, - () -> ((NatsJetStream)js).lookupConsumerInfo(stream(999), tsc.consumerName())); + () -> ctx.js.lookupConsumerInfo(random(), random())); }); } @Test public void testGetJetStreamValidatedConnectionCoverage() { NatsJetStreamMessage njsm = new NatsJetStreamMessage(null); - IllegalStateException ise = assertThrows(IllegalStateException.class, njsm::getJetStreamValidatedConnection); assertTrue(ise.getMessage().contains("subscription")); // make a dummy connection so we can make a subscription + // notice we never nc.connect(); Options options = Options.builder().build(); NatsConnection nc = new NatsConnection(options); njsm.subscription = new NatsSubscription("sid", "sub", "q", nc, null); @@ -999,6 +969,7 @@ public void testNatsConnectionTimeCheckLogic() { Options options = Options.builder() .timeTraceLogger(l) .build(); + //noinspection resource NatsConnection nc = new NatsConnection(options); nc.traceTimeCheck("zero", 0); @@ -1025,88 +996,93 @@ public void testNatsConnectionTimeCheckLogic() { @Test public void testMoreCreateSubscriptionErrors() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - - IllegalStateException ise = assertThrows(IllegalStateException.class, () -> js.subscribe(SUBJECT)); + runInShared((nc, ctx) -> { + IllegalStateException ise = assertThrows(IllegalStateException.class, () -> ctx.js.subscribe(random())); assertTrue(ise.getMessage().contains(JsSubNoMatchingStreamForSubject.id())); - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - // general pull push validation - ConsumerConfiguration ccCantHave = builder().durable("pulldur").deliverGroup("cantHave").build(); + + String pulldur = random(); + String dgCantHave = random(); + ConsumerConfiguration ccCantHave = builder().durable(pulldur).deliverGroup(dgCantHave).build(); PullSubscribeOptions pullCantHaveDlvrGrp = PullSubscribeOptions.builder().configuration(ccCantHave).build(); - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), pullCantHaveDlvrGrp)); + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), pullCantHaveDlvrGrp)); assertTrue(iae.getMessage().contains(JsSubPullCantHaveDeliverGroup.id())); - ccCantHave = builder().durable("pulldur").deliverSubject("cantHave").build(); + ccCantHave = builder().durable(pulldur).deliverSubject(dgCantHave).build(); PullSubscribeOptions pullCantHaveDlvrSub = PullSubscribeOptions.builder().configuration(ccCantHave).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), pullCantHaveDlvrSub)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), pullCantHaveDlvrSub)); assertTrue(iae.getMessage().contains(JsSubPullCantHaveDeliverSubject.id())); ccCantHave = builder().maxPullWaiting(1L).build(); PushSubscribeOptions pushCantHaveMpw = PushSubscribeOptions.builder().configuration(ccCantHave).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), pushCantHaveMpw)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), pushCantHaveMpw)); assertTrue(iae.getMessage().contains(JsSubPushCantHaveMaxPullWaiting.id())); ccCantHave = builder().maxBatch(1L).build(); PushSubscribeOptions pushCantHaveMb = PushSubscribeOptions.builder().configuration(ccCantHave).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), pushCantHaveMb)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), pushCantHaveMb)); assertTrue(iae.getMessage().contains(JsSubPushCantHaveMaxBatch.id())); ccCantHave = builder().maxBytes(1L).build(); PushSubscribeOptions pushCantHaveMby = PushSubscribeOptions.builder().configuration(ccCantHave).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), pushCantHaveMby)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), pushCantHaveMby)); assertTrue(iae.getMessage().contains(JsSubPushCantHaveMaxBytes.id())); // create some consumers - PushSubscribeOptions psoDurNoQ = PushSubscribeOptions.builder().durable("durNoQ").build(); - js.subscribe(tsc.subject(), psoDurNoQ); + String durNoQ = random(); + String durYesQ = random(); + + PushSubscribeOptions psoDurNoQ = PushSubscribeOptions.builder().durable(durNoQ).build(); + ctx.js.subscribe(ctx.subject(), psoDurNoQ); - PushSubscribeOptions psoDurYesQ = PushSubscribeOptions.builder().durable("durYesQ").build(); - js.subscribe(tsc.subject(), "yesQ", psoDurYesQ); + PushSubscribeOptions psoDurYesQ = PushSubscribeOptions.builder().durable(durYesQ).build(); + ctx.js.subscribe(ctx.subject(), "yesQ", psoDurYesQ); // already bound - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), psoDurNoQ)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), psoDurNoQ)); assertTrue(iae.getMessage().contains(JsSubConsumerAlreadyBound.id())); // queue match - PushSubscribeOptions qmatch = PushSubscribeOptions.builder() - .durable("qmatchdur").deliverGroup("qmatchq").build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), "qnotmatch", qmatch)); + String qmatchdur = random(); + String qmatchq = random(); + PushSubscribeOptions qmatch = PushSubscribeOptions.builder().durable(qmatchdur).deliverGroup(qmatchq).build(); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), "qnotmatch", qmatch)); assertTrue(iae.getMessage().contains(JsSubQueueDeliverGroupMismatch.id())); // queue vs config - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), "notConfigured", psoDurNoQ)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), "notConfigured", psoDurNoQ)); assertTrue(iae.getMessage().contains(JsSubExistingConsumerNotQueue.id())); - PushSubscribeOptions psoNoVsYes = PushSubscribeOptions.builder().durable("durYesQ").build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), psoNoVsYes)); + PushSubscribeOptions psoNoVsYes = PushSubscribeOptions.builder().durable(durYesQ).build(); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), psoNoVsYes)); assertTrue(iae.getMessage().contains(JsSubExistingConsumerIsQueue.id())); - PushSubscribeOptions psoYesVsNo = PushSubscribeOptions.builder().durable("durYesQ").build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), "qnotmatch", psoYesVsNo)); + PushSubscribeOptions psoYesVsNo = PushSubscribeOptions.builder().durable(durYesQ).build(); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), "qnotmatch", psoYesVsNo)); assertTrue(iae.getMessage().contains(JsSubExistingQueueDoesNotMatchRequestedQueue.id())); // flow control heartbeat push / pull - ConsumerConfiguration ccFc = builder().durable("ccFcDur").flowControl(1000).build(); - ConsumerConfiguration ccHb = builder().durable("ccHbDur").idleHeartbeat(1000).build(); + String ccFcDur = random(); + String ccHbDur = random(); + ConsumerConfiguration ccFc = builder().durable(ccFcDur).flowControl(1000).build(); + ConsumerConfiguration ccHb = builder().durable(ccHbDur).idleHeartbeat(1000).build(); PullSubscribeOptions psoPullCcFc = PullSubscribeOptions.builder().configuration(ccFc).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), psoPullCcFc)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), psoPullCcFc)); assertTrue(iae.getMessage().contains(JsSubFcHbNotValidPull.id())); PullSubscribeOptions psoPullCcHb = PullSubscribeOptions.builder().configuration(ccHb).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), psoPullCcHb)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), psoPullCcHb)); assertTrue(iae.getMessage().contains(JsSubFcHbNotValidPull.id())); PushSubscribeOptions psoPushCcFc = PushSubscribeOptions.builder().configuration(ccFc).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), "cantHaveQ", psoPushCcFc)); + String cantHaveQ = random(); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), cantHaveQ, psoPushCcFc)); assertTrue(iae.getMessage().contains(JsSubFcHbNotValidQueue.id())); PushSubscribeOptions psoPushCcHb = PushSubscribeOptions.builder().configuration(ccHb).build(); - iae = assertThrows(IllegalArgumentException.class, () -> js.subscribe(tsc.subject(), "cantHaveQ", psoPushCcHb)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.js.subscribe(ctx.subject(), cantHaveQ, psoPushCcHb)); assertTrue(iae.getMessage().contains(JsSubFcHbNotValidQueue.id())); }); } @@ -1118,4 +1094,25 @@ public void testNatsJetStreamUtil() { assertNotNull(gen); assertTrue(gen.startsWith("prefix-")); } + + @Test + public void testRequestNoResponder() throws Exception { + runInSharedCustom((ncCancel, ctx) -> { + Options optReport = optionsBuilder(ncCancel).reportNoResponders().build(); + try (Connection ncReport = ConnectionUtils.managedConnect(optReport)) { + assertThrows(CancellationException.class, () -> ncCancel.request(random(), null).get()); + ExecutionException ee = assertThrows(ExecutionException.class, () -> ncReport.request(random(), null).get()); + assertInstanceOf(JetStreamStatusException.class, ee.getCause()); + assertTrue(ee.getMessage().contains("503 No Responders Available For Request")); + + JetStream jsCancel = ncCancel.jetStream(); + JetStream jsReport = ncReport.jetStream(); + + IOException ioe = assertThrows(IOException.class, () -> jsCancel.publish("not-exist", null)); + assertTrue(ioe.getMessage().contains("503")); + ioe = assertThrows(IOException.class, () -> jsReport.publish("trnrNotExist", null)); + assertTrue(ioe.getMessage().contains("503")); + } + }); + } } \ No newline at end of file diff --git a/src/test/java/io/nats/client/impl/JetStreamManagementTests.java b/src/test/java/io/nats/client/impl/JetStreamManagementTests.java index 2c9a9ca22..c27347e60 100644 --- a/src/test/java/io/nats/client/impl/JetStreamManagementTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamManagementTests.java @@ -16,9 +16,9 @@ import io.nats.client.*; import io.nats.client.api.*; import io.nats.client.support.DateTimeUtils; -import io.nats.client.utils.TestBase; +import io.nats.client.support.Listener; +import io.nats.client.utils.VersionUtils; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -34,35 +34,33 @@ import static io.nats.client.support.DateTimeUtils.ZONE_ID_GMT; import static io.nats.client.support.NatsJetStreamConstants.*; import static io.nats.client.utils.ResourceUtils.dataAsString; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class JetStreamManagementTests extends JetStreamTestBase { @Test public void testStreamCreate() throws Exception { - runInJsServer(nc -> { - long now = ZonedDateTime.now().toEpochSecond(); + long now = ZonedDateTime.now().toEpochSecond(); - JetStreamManagement jsm = nc.jetStreamManagement(); + runInSharedCustom((nc, ctx) -> { + StreamConfiguration sc = ctx.scBuilder(2).build(); + String subject0 = ctx.subject(0); + String subject1 = ctx.subject(1); - StreamConfiguration sc = StreamConfiguration.builder() - .name(STREAM) - .storageType(StorageType.Memory) - .subjects(subject(0), subject(1)) - .build(); + StreamInfo si = ctx.createOrReplaceStream(sc); - StreamInfo si = jsm.addStream(sc); assertNotNull(si.getStreamState().toString()); // coverage assertTrue(now <= si.getCreateTime().toEpochSecond()); assertNotNull(si.getConfiguration()); sc = si.getConfiguration(); - assertEquals(STREAM, sc.getName()); + assertEquals(ctx.stream, sc.getName()); assertEquals(2, sc.getSubjects().size()); - assertEquals(subject(0), sc.getSubjects().get(0)); - assertEquals(subject(1), sc.getSubjects().get(1)); - assertTrue(subject(0).compareTo(subject(1)) != 0); // coverage + assertEquals(subject0, sc.getSubjects().get(0)); + assertEquals(subject1, sc.getSubjects().get(1)); + assertTrue(subject0.compareTo(subject1) != 0); // coverage assertEquals(RetentionPolicy.Limits, sc.getRetentionPolicy()); assertEquals(DiscardPolicy.Old, sc.getDiscardPolicy()); @@ -72,6 +70,7 @@ public void testStreamCreate() throws Exception { assertEquals(-1, sc.getMaxConsumers()); assertEquals(-1, sc.getMaxMsgs()); assertEquals(-1, sc.getMaxBytes()); + //noinspection deprecation assertEquals(-1, sc.getMaxMsgSize()); // COVERAGE for deprecated assertEquals(-1, sc.getMaximumMessageSize()); assertEquals(1, sc.getReplicas()); @@ -87,38 +86,28 @@ public void testStreamCreate() throws Exception { assertEquals(0, ss.getFirstSequence()); assertEquals(0, ss.getLastSequence()); assertEquals(0, ss.getConsumerCount()); + }); + } - if (nc.getServerInfo().isSameOrNewerThanVersion("2.10")) { - JetStream js = jsm.jetStream(); - String stream = stream(); - sc = StreamConfiguration.builder() - .name(stream) - .storageType(StorageType.Memory) - .firstSequence(42) - .subjects("test-first-seq").build(); - si = jsm.addStream(sc); - assertNotNull(si.getTimestamp()); - assertEquals(42, si.getConfiguration().getFirstSequence()); - PublishAck pa = js.publish("test-first-seq", null); - assertEquals(42, pa.getSeqno()); - } + @Test + public void testStreamCreate210() throws Exception { + runInSharedCustom(VersionUtils::atLeast2_10, (nc, ctx) -> { + StreamConfiguration sc = ctx.scBuilder(1) + .firstSequence(42).build(); + StreamInfo si = ctx.createOrReplaceStream(sc); + assertNotNull(si.getTimestamp()); + assertEquals(42, si.getConfiguration().getFirstSequence()); + PublishAck pa = ctx.js.publish(ctx.subject(), null); + assertEquals(42, pa.getSeqno()); }); } @Test public void testStreamMetadata() throws Exception { - jsServer.run(TestBase::atLeast2_9_0, nc -> { + runInSharedCustom((nc, ctx) -> { Map metaData = new HashMap<>(); metaData.put(META_KEY, META_VALUE); - JetStreamManagement jsm = nc.jetStreamManagement(); - - StreamConfiguration sc = StreamConfiguration.builder() - .name(stream()) - .storageType(StorageType.Memory) - .subjects(subject()) - .metadata(metaData) - .build(); - - StreamInfo si = jsm.addStream(sc); + StreamConfiguration sc = ctx.scBuilder(1).metadata(metaData).build(); + StreamInfo si = ctx.createOrReplaceStream(sc); assertNotNull(si.getConfiguration()); assertMetaData(si.getConfiguration().getMetadata()); }); @@ -126,25 +115,17 @@ public void testStreamMetadata() throws Exception { @Test public void testStreamCreateWithNoSubject() throws Exception { - jsServer.run(nc -> { - long now = ZonedDateTime.now().toEpochSecond(); - - JetStreamManagement jsm = nc.jetStreamManagement(); - - String stream = stream(); - StreamConfiguration sc = StreamConfiguration.builder() - .name(stream) - .storageType(StorageType.Memory) - .build(); - - StreamInfo si = jsm.addStream(sc); + long now = ZonedDateTime.now().toEpochSecond(); + runInSharedCustom((nc, ctx) -> { + StreamConfiguration sc = ctx.scBuilder().subjects().build(); + StreamInfo si = ctx.addStream(sc); assertTrue(now <= si.getCreateTime().toEpochSecond()); sc = si.getConfiguration(); - assertEquals(stream, sc.getName()); + assertEquals(ctx.stream, sc.getName()); assertEquals(1, sc.getSubjects().size()); - assertEquals(stream, sc.getSubjects().get(0)); + assertEquals(ctx.stream, sc.getSubjects().get(0)); assertEquals(RetentionPolicy.Limits, sc.getRetentionPolicy()); assertEquals(DiscardPolicy.Old, sc.getDiscardPolicy()); @@ -174,16 +155,18 @@ public void testStreamCreateWithNoSubject() throws Exception { @Test public void testUpdateStream() throws Exception { - runInJsServer(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - StreamInfo si = addTestStream(jsm); - StreamConfiguration sc = si.getConfiguration(); + runInSharedCustom((nc, ctx) -> { + ctx.createOrReplaceStream(2); + String subject0 = ctx.subject(0); + String subject1 = ctx.subject(1); + + StreamConfiguration sc = ctx.si.getConfiguration(); assertNotNull(sc); - assertEquals(STREAM, sc.getName()); + assertEquals(ctx.stream, sc.getName()); assertNotNull(sc.getSubjects()); assertEquals(2, sc.getSubjects().size()); - assertEquals(subject(0), sc.getSubjects().get(0)); - assertEquals(subject(1), sc.getSubjects().get(1)); + assertEquals(subject0, sc.getSubjects().get(0)); + assertEquals(subject1, sc.getSubjects().get(1)); assertEquals(-1, sc.getMaxMsgs()); assertEquals(-1, sc.getMaxBytes()); assertEquals(-1, sc.getMaximumMessageSize()); @@ -195,10 +178,7 @@ public void testUpdateStream() throws Exception { assertEquals(Duration.ofMinutes(2), sc.getDuplicateWindow()); assertNull(sc.getTemplateOwner()); - sc = StreamConfiguration.builder() - .name(STREAM) - .storageType(StorageType.Memory) // File is default, this ensures it's not a change - .subjects(subject(0), subject(1), subject(2)) + sc = ctx.scBuilder(3) .maxMessages(42) .maxBytes(43) .maximumMessageSize(44) @@ -208,17 +188,18 @@ public void testUpdateStream() throws Exception { .duplicateWindow(Duration.ofMinutes(3)) .maxMessagesPerSubject(45) .build(); - si = jsm.updateStream(sc); + StreamInfo si = ctx.jsm.updateStream(sc); assertNotNull(si); + String subject2 = ctx.subject(2); sc = si.getConfiguration(); assertNotNull(sc); - assertEquals(STREAM, sc.getName()); + assertEquals(ctx.stream, sc.getName()); assertNotNull(sc.getSubjects()); assertEquals(3, sc.getSubjects().size()); - assertEquals(subject(0), sc.getSubjects().get(0)); - assertEquals(subject(1), sc.getSubjects().get(1)); - assertEquals(subject(2), sc.getSubjects().get(2)); + assertEquals(subject0, sc.getSubjects().get(0)); + assertEquals(subject1, sc.getSubjects().get(1)); + assertEquals(subject2, sc.getSubjects().get(2)); assertEquals(42, sc.getMaxMsgs()); assertEquals(43, sc.getMaxBytes()); assertEquals(44, sc.getMaximumMessageSize()); @@ -232,165 +213,112 @@ public void testUpdateStream() throws Exception { assertNull(sc.getTemplateOwner()); // allowed to change Allow Direct - jsm.deleteStream(STREAM); - jsm.addStream(getTestStreamConfigurationBuilder().allowDirect(false).build()); - jsm.updateStream(getTestStreamConfigurationBuilder().allowDirect(true).build()); - jsm.updateStream(getTestStreamConfigurationBuilder().allowDirect(false).build()); + ctx.jsm.updateStream(StreamConfiguration.builder(sc).allowDirect(true).build()); + ctx.jsm.updateStream(StreamConfiguration.builder(sc).allowDirect(false).build()); // allowed to change Mirror Direct - jsm.deleteStream(STREAM); - jsm.addStream(getTestStreamConfigurationBuilder().mirrorDirect(false).build()); - jsm.updateStream(getTestStreamConfigurationBuilder().mirrorDirect(true).build()); - jsm.updateStream(getTestStreamConfigurationBuilder().mirrorDirect(false).build()); + ctx.createOrReplaceStream(StreamConfiguration.builder(sc).mirrorDirect(false).build()); + ctx.jsm.updateStream(StreamConfiguration.builder(sc).mirrorDirect(true).build()); + ctx.jsm.updateStream(StreamConfiguration.builder(sc).mirrorDirect(false).build()); }); } @Test - public void testAddStreamInvalids() throws Exception { - jsServer.run(TestBase::atLeast2_10, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); + public void testStreamExceptions() throws Exception { + runInSharedCustom((nc, ctx) -> { + assertThrows(IllegalArgumentException.class, () -> ctx.jsm.addStream(null)); - assertThrows(IllegalArgumentException.class, () -> jsm.addStream(null)); + // stream isn't even created yet + assertStatus(10059, assertThrows(JetStreamApiException.class, () -> ctx.jsm.getMessage(ctx.stream, 1))); - String stream = stream(); - - StreamConfiguration sc = StreamConfiguration.builder() - .name(stream) - .description(variant()) - .storageType(StorageType.Memory) - .subjects(subject()) + StreamConfiguration sc = ctx.scBuilder(1) + .description(random()) .build(); - jsm.addStream(sc); - - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).subjects(subject()).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).description(variant()).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).retentionPolicy(RetentionPolicy.Interest).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).retentionPolicy(RetentionPolicy.WorkQueue).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).compressionOption(CompressionOption.S2).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).maxConsumers(1).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).maxMessages(1).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).maxMessagesPerSubject(1).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).maxAge(Duration.ofSeconds(1L)).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).maxMsgSize(1).build())); // COVERAGE for deprecated - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).maximumMessageSize(1).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).storageType(StorageType.File).build())); - - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).noAck(true).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).discardPolicy(DiscardPolicy.New).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).duplicateWindow(Duration.ofSeconds(1L)).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).allowRollup(true).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).allowDirect(true).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).denyDelete(true).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).denyPurge(true).build())); - assert10058(() -> jsm.addStream(StreamConfiguration.builder(sc).firstSequence(100).build())); + ctx.createOrReplaceStream(sc); + + // no messages yet + assertStatus(10037, assertThrows(JetStreamApiException.class, () -> ctx.jsm.getMessage(ctx.stream, 1))); + + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).subjects(random()).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).description(random()).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).retentionPolicy(RetentionPolicy.Interest).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).retentionPolicy(RetentionPolicy.WorkQueue).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).compressionOption(CompressionOption.S2).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).maxConsumers(1).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).maxMessages(1).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).maxMessagesPerSubject(1).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).maxAge(Duration.ofSeconds(1L)).build()))); + //noinspection deprecation + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).maxMsgSize(1).build()))); // COVERAGE for deprecated + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).maximumMessageSize(1).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).storageType(StorageType.File).build()))); + + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).noAck(true).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).discardPolicy(DiscardPolicy.New).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).duplicateWindow(Duration.ofSeconds(1L)).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).allowRollup(true).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).allowDirect(true).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).denyDelete(true).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).denyPurge(true).build()))); + assertStatus(10058, assertThrows(JetStreamApiException.class, () -> ctx.jsm.addStream(StreamConfiguration.builder(sc).firstSequence(100).build()))); }); } - // io.nats.client.JetStreamApiException: stream name already in use with a different configuration [10058] - private void assert10058(Executable executable) { - assertEquals(10058, assertThrows(JetStreamApiException.class, executable).getApiErrorCode()); - } - @Test public void testUpdateStreamInvalids() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - assertThrows(IllegalArgumentException.class, () -> jsm.updateStream(null)); - - String stream = stream(); - String[] subjects = new String[]{subject(), subject()}; + runInSharedCustom((nc, ctx) -> { + assertThrows(IllegalArgumentException.class, () -> ctx.jsm.updateStream(null)); + StreamConfiguration sc = ctx.scBuilder(2).build(); // cannot update non existent stream - StreamConfiguration sc = getTestStreamConfiguration(stream, subjects); - // stream not added yet - assertThrows(JetStreamApiException.class, () -> jsm.updateStream(sc)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.updateStream(sc)); // add the stream - jsm.addStream(sc); + StreamInfo si = ctx.createOrReplaceStream(sc); // cannot change storage type - StreamConfiguration scMemToFile = getTestStreamConfigurationBuilder(stream, subjects) + StreamConfiguration scMemToFile = ctx.scBuilder(2) .storageType(StorageType.File) .build(); - assertThrows(JetStreamApiException.class, () -> jsm.updateStream(scMemToFile)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.updateStream(scMemToFile)); - // cannot change MaxConsumers - if (nc.getServerInfo().isOlderThanVersion("2.12.5")) { - StreamConfiguration scMaxCon = getTestStreamConfigurationBuilder(stream, subjects) + if (nc.getServerInfo().isOlderThanVersion("2.14")) { + // cannot change MaxConsumers + StreamConfiguration scMaxCon = ctx.scBuilder(2) .maxConsumers(2) .build(); - assertThrows(JetStreamApiException.class, () -> jsm.updateStream(scMaxCon)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.updateStream(scMaxCon)); } - StreamConfiguration scReten = getTestStreamConfigurationBuilder(stream, subjects) + StreamConfiguration scReten = ctx.scBuilder(2) .retentionPolicy(RetentionPolicy.Interest) .build(); if (nc.getServerInfo().isOlderThanVersion("2.10")) { // cannot change RetentionPolicy - assertThrows(JetStreamApiException.class, () -> jsm.updateStream(scReten)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.updateStream(scReten)); } else { - jsm.updateStream(scReten); + ctx.jsm.updateStream(scReten); } - - jsm.deleteStream(stream); - - jsm.addStream(getTestStreamConfigurationBuilder(stream, subjects).storageType(StorageType.File).build()); - assertThrows(JetStreamApiException.class, () -> jsm.updateStream(getTestStreamConfiguration(stream, subjects))); }); } - private static StreamInfo addTestStream(JetStreamManagement jsm) throws IOException, JetStreamApiException { - StreamInfo si = jsm.addStream(getTestStreamConfiguration()); - assertNotNull(si); - return si; - } - - private static StreamConfiguration getTestStreamConfiguration() { - return getTestStreamConfigurationBuilder().build(); - } - - private static StreamConfiguration getTestStreamConfiguration(String stream, String... subjects) { - return getTestStreamConfigurationBuilder(stream, subjects).build(); - } - - private static StreamConfiguration.Builder getTestStreamConfigurationBuilder() { - return getTestStreamConfigurationBuilder(STREAM); - } - - private static StreamConfiguration.Builder getTestStreamConfigurationBuilder(String stream, String... subjects) { - if (subjects == null || subjects.length == 0) { - subjects = new String[]{subject(0), subject(1)}; - } - - return StreamConfiguration.builder() - .name(stream) - .storageType(StorageType.Memory) - .subjects(subjects); - } - @Test public void testGetStreamInfo() throws Exception { - jsServer.run(nc -> { - String stream = stream(); - - JetStreamManagement jsm = nc.jetStreamManagement(); - assertThrows(JetStreamApiException.class, () -> jsm.getStreamInfo(stream)); - - JetStream js = nc.jetStream(); + runInSharedCustom((nc, ctx) -> { + assertThrows(JetStreamApiException.class, () -> ctx.jsm.getStreamInfo(ctx.stream)); String[] subjects = new String[6]; - String subjectIx5 = subject(); + String subjectIx5 = random(); for (int x = 0; x < 5; x++) { - subjects[x] = subject() + x + 1; + subjects[x] = random() + x + 1; } subjects[5] = subjectIx5 + ".>"; - createMemoryStream(jsm, stream, subjects); + ctx.createOrReplaceStream(subjects); - StreamInfo si = jsm.getStreamInfo(stream); - assertEquals(stream, si.getConfiguration().getName()); + StreamInfo si = ctx.jsm.getStreamInfo(ctx.stream); + assertEquals(ctx.stream, si.getConfiguration().getName()); assertEquals(0, si.getStreamState().getSubjectCount()); assertEquals(0, si.getStreamState().getSubjects().size()); assertEquals(0, si.getStreamState().getDeletedCount()); @@ -407,23 +335,23 @@ public void testGetStreamInfo() throws Exception { List packs = new ArrayList<>(); for (int x = 0; x < 5; x++) { - jsPublish(js, subjects[x], x + 1); - PublishAck pa = jsPublish(js, subjects[x], data(x + 2)); + jsPublish(ctx.js, subjects[x], x + 1); + PublishAck pa = jsPublish(ctx.js, subjects[x], data(x + 2)); packs.add(pa); - jsm.deleteMessage(stream, pa.getSeqno()); + ctx.jsm.deleteMessage(ctx.stream, pa.getSeqno()); } - jsPublish(js, subjectIx5 + ".bar", 6); + jsPublish(ctx.js, subjectIx5 + ".bar", 6); - si = jsm.getStreamInfo(stream); - assertEquals(stream, si.getConfiguration().getName()); + si = ctx.jsm.getStreamInfo(ctx.stream); + assertEquals(ctx.stream, si.getConfiguration().getName()); assertEquals(6, si.getStreamState().getSubjectCount()); assertEquals(0, si.getStreamState().getSubjects().size()); assertEquals(5, si.getStreamState().getDeletedCount()); assertEquals(0, si.getStreamState().getDeleted().size()); assertTrue(si.getStreamState().getSubjectMap().isEmpty()); - si = jsm.getStreamInfo(stream, StreamInfoOptions.builder().allSubjects().deletedDetails().build()); - assertEquals(stream, si.getConfiguration().getName()); + si = ctx.jsm.getStreamInfo(ctx.stream, StreamInfoOptions.builder().allSubjects().deletedDetails().build()); + assertEquals(ctx.stream, si.getConfiguration().getName()); assertEquals(6, si.getStreamState().getSubjectCount()); List list = si.getStreamState().getSubjects(); assertNotNull(list); @@ -448,10 +376,10 @@ public void testGetStreamInfo() throws Exception { assertTrue(si.getStreamState().getDeleted().contains(pa.getSeqno())); } - jsPublish(js, subjectIx5 + ".baz", 2); + jsPublish(ctx.js, subjectIx5 + ".baz", 2); sleep(100); - si = jsm.getStreamInfo(stream, StreamInfoOptions.builder().filterSubjects(subjectIx5 + ".>").deletedDetails().build()); + si = ctx.jsm.getStreamInfo(ctx.stream, StreamInfoOptions.builder().filterSubjects(subjectIx5 + ".>").deletedDetails().build()); assertEquals(7, si.getStreamState().getSubjectCount()); list = si.getStreamState().getSubjects(); assertNotNull(list); @@ -467,7 +395,7 @@ public void testGetStreamInfo() throws Exception { assertNotNull(s); assertEquals(2, s.getCount()); - si = jsm.getStreamInfo(stream, StreamInfoOptions.builder().filterSubjects(subjects[4]).build()); + si = ctx.jsm.getStreamInfo(ctx.stream, StreamInfoOptions.builder().filterSubjects(subjects[4]).build()); list = si.getStreamState().getSubjects(); assertNotNull(list); assertEquals(1, list.size()); @@ -477,105 +405,13 @@ public void testGetStreamInfo() throws Exception { }); } - @Test - public void testGetStreamInfoSubjectPagination() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/pagination.conf", false, true)) { - try (Connection nc = standardConnection(ts.getURI())) { - if (nc.getServerInfo().isNewerVersionThan("2.8.4")) { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - long rounds = 101; - long size = 1000; - long count = rounds * size; - jsm.addStream(StreamConfiguration.builder() - .name(stream(1)) - .storageType(StorageType.Memory) - .subjects("s.*.*") - .build()); - - jsm.addStream(StreamConfiguration.builder() - .name(stream(2)) - .storageType(StorageType.Memory) - .subjects("t.*.*") - .build()); - - for (int x = 1; x <= rounds; x++) { - for (int y = 1; y <= size; y++) { - js.publish("s." + x + "." + y, null); - } - } - - for (int y = 1; y <= size; y++) { - js.publish("t.7." + y, null); - } - - StreamInfo si = jsm.getStreamInfo(stream(1)); - validateStreamInfo(si.getStreamState(), 0, 0, count); - - si = jsm.getStreamInfo(stream(1), StreamInfoOptions.allSubjects()); - validateStreamInfo(si.getStreamState(), count, count, count); - - si = jsm.getStreamInfo(stream(1), StreamInfoOptions.filterSubjects("s.7.*")); - validateStreamInfo(si.getStreamState(), size, size, count); - - si = jsm.getStreamInfo(stream(1), StreamInfoOptions.filterSubjects("s.7.1")); - validateStreamInfo(si.getStreamState(), 1L, 1, count); - - si = jsm.getStreamInfo(stream(2), StreamInfoOptions.filterSubjects("t.7.*")); - validateStreamInfo(si.getStreamState(), size, size, size); - - si = jsm.getStreamInfo(stream(2), StreamInfoOptions.filterSubjects("t.7.1")); - validateStreamInfo(si.getStreamState(), 1L, 1, size); - - List infos = jsm.getStreams(); - assertEquals(2, infos.size()); - si = infos.get(0); - if (si.getConfiguration().getSubjects().get(0).equals("s.*.*")) { - validateStreamInfo(si.getStreamState(), 0, 0, count); - validateStreamInfo(infos.get(1).getStreamState(), 0, 0, size); - } - else { - validateStreamInfo(si.getStreamState(), 0, 0, size); - validateStreamInfo(infos.get(1).getStreamState(), 0, 0, count); - } - - infos = jsm.getStreams(">"); - assertEquals(2, infos.size()); - - infos = jsm.getStreams("*.7.*"); - assertEquals(2, infos.size()); - - infos = jsm.getStreams("*.7.1"); - assertEquals(2, infos.size()); - - infos = jsm.getStreams("s.7.*"); - assertEquals(1, infos.size()); - assertEquals("s.*.*", infos.get(0).getConfiguration().getSubjects().get(0)); - - infos = jsm.getStreams("t.7.1"); - assertEquals(1, infos.size()); - assertEquals("t.*.*", infos.get(0).getConfiguration().getSubjects().get(0)); - } - } - } - } - - private void validateStreamInfo(StreamState streamState, long subjectsList, long filteredCount, long subjectCount) { - assertEquals(subjectsList, streamState.getSubjects().size()); - assertEquals(filteredCount, streamState.getSubjects().size()); - assertEquals(subjectCount, streamState.getSubjectCount()); - } - @Test public void testGetStreamInfoOrNamesPaginationFilter() throws Exception { - runInJsServer(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - + runInOwnJsServer((nc, jsm, js) -> { // getStreams pages at 256 // getStreamNames pages at 1024 - - addStreams(jsm, 300, 0, "x256"); + String prefix = random(); + addStreams(jsm, prefix, 300, 0, "x256"); List list = jsm.getStreams(); assertEquals(300, list.size()); @@ -583,7 +419,7 @@ public void testGetStreamInfoOrNamesPaginationFilter() throws Exception { List names = jsm.getStreamNames(); assertEquals(300, names.size()); - addStreams(jsm, 1100, 300, "x1024"); + addStreams(jsm, prefix, 1100, 300, "x1024"); list = jsm.getStreams(); assertEquals(1400, list.size()); @@ -605,209 +441,221 @@ public void testGetStreamInfoOrNamesPaginationFilter() throws Exception { }); } - private void addStreams(JetStreamManagement jsm, int count, int adj, String div) throws IOException, JetStreamApiException { + private void addStreams(JetStreamManagement jsm, String prefix, int count, int adj, String div) throws IOException, JetStreamApiException { for (int x = 0; x < count; x++) { - createMemoryStream(jsm, "stream-" + (x + adj), "sub" + (x + adj) + "." + div + ".*"); + createMemoryStream(jsm, prefix + "-" + (x + adj), "sub" + (x + adj) + "." + div + ".*"); } } @Test public void testGetStreamNamesBySubjectFilter() throws Exception { - runInJsServer(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - createMemoryStream(jsm, stream(1), "foo"); - createMemoryStream(jsm, stream(2), "bar"); - createMemoryStream(jsm, stream(3), "a.a"); - createMemoryStream(jsm, stream(4), "a.b"); + runInOwnJsServer((nc, jsm, js) -> { + String stream1 = random(); + String stream2 = random(); + String stream3 = random(); + String stream4 = random(); + createMemoryStream(jsm, stream1, "foo"); + createMemoryStream(jsm, stream2, "bar"); + createMemoryStream(jsm, stream3, "a.a"); + createMemoryStream(jsm, stream4, "a.b"); List list = jsm.getStreamNames("*"); - assertStreamNameList(list, 1, 2); + assertStreamNameList(list, stream1, stream2); list = jsm.getStreamNames(">"); - assertStreamNameList(list, 1, 2, 3, 4); + assertStreamNameList(list, stream1, stream2, stream3, stream4); list = jsm.getStreamNames("*.*"); - assertStreamNameList(list, 3, 4); + assertStreamNameList(list, stream3, stream4); list = jsm.getStreamNames("a.>"); - assertStreamNameList(list, 3, 4); + assertStreamNameList(list, stream3, stream4); list = jsm.getStreamNames("a.*"); - assertStreamNameList(list, 3, 4); + assertStreamNameList(list, stream3, stream4); list = jsm.getStreamNames("foo"); - assertStreamNameList(list, 1); + assertStreamNameList(list, stream1); list = jsm.getStreamNames("a.a"); - assertStreamNameList(list, 3); + assertStreamNameList(list, stream3); list = jsm.getStreamNames("nomatch"); assertStreamNameList(list); }); } - private void assertStreamNameList(List list, int... ids) { + private void assertStreamNameList(List list, String... streams) { assertNotNull(list); - assertEquals(ids.length, list.size()); - for (int id : ids) { - assertTrue(list.contains(stream(id))); + assertEquals(streams.length, list.size()); + for (String s : streams) { + assertTrue(list.contains(s)); } } @Test public void testDeleteStream() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); + runInShared((nc, ctx) -> { JetStreamApiException jsapiEx = - assertThrows(JetStreamApiException.class, () -> jsm.deleteStream(stream())); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.deleteStream(random())); assertEquals(10059, jsapiEx.getApiErrorCode()); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - assertNotNull(jsm.getStreamInfo(tsc.stream)); - assertTrue(jsm.deleteStream(tsc.stream)); + assertNotNull(ctx.jsm.getStreamInfo(ctx.stream)); + assertTrue(ctx.deleteStream()); - jsapiEx = assertThrows(JetStreamApiException.class, () -> jsm.getStreamInfo(tsc.stream)); + jsapiEx = assertThrows(JetStreamApiException.class, () -> ctx.jsm.getStreamInfo(ctx.stream)); assertEquals(10059, jsapiEx.getApiErrorCode()); - jsapiEx = assertThrows(JetStreamApiException.class, () -> jsm.deleteStream(tsc.stream)); + jsapiEx = assertThrows(JetStreamApiException.class, ctx::deleteStream); assertEquals(10059, jsapiEx.getApiErrorCode()); }); } @Test public void testPurgeStreamAndOptions() throws Exception { - jsServer.run(nc -> { + runInSharedCustom((nc, ctx) -> { // invalid to have both keep and seq assertThrows(IllegalArgumentException.class, () -> PurgeOptions.builder().keep(1).sequence(1).build()); - JetStreamManagement jsm = nc.jetStreamManagement(); - // error to purge a stream that does not exist - assertThrows(JetStreamApiException.class, () -> jsm.purgeStream(stream())); - - TestingStreamContainer tsc = new TestingStreamContainer(nc, 2); - createMemoryStream(jsm, tsc.stream, tsc.subject(0), tsc.subject(1)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.purgeStream(random())); - StreamInfo si = jsm.getStreamInfo(tsc.stream); + ctx.createOrReplaceStream(2); + StreamInfo si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(0, si.getStreamState().getMsgCount()); - jsPublish(nc, tsc.subject(0), 10); - si = jsm.getStreamInfo(tsc.stream); + jsPublish(nc, ctx.subject(0), 10); + si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(10, si.getStreamState().getMsgCount()); PurgeOptions options = PurgeOptions.builder().keep(7).build(); - PurgeResponse pr = jsm.purgeStream(tsc.stream, options); + PurgeResponse pr = ctx.jsm.purgeStream(ctx.stream, options); assertTrue(pr.isSuccess()); assertEquals(3, pr.getPurged()); options = PurgeOptions.builder().sequence(9).build(); - pr = jsm.purgeStream(tsc.stream, options); + pr = ctx.jsm.purgeStream(ctx.stream, options); assertTrue(pr.isSuccess()); assertEquals(5, pr.getPurged()); - si = jsm.getStreamInfo(tsc.stream); + si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(2, si.getStreamState().getMsgCount()); - pr = jsm.purgeStream(tsc.stream); + pr = ctx.jsm.purgeStream(ctx.stream); assertTrue(pr.isSuccess()); assertEquals(2, pr.getPurged()); - si = jsm.getStreamInfo(tsc.stream); + si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(0, si.getStreamState().getMsgCount()); - jsPublish(nc, tsc.subject(0), 10); - jsPublish(nc, tsc.subject(1), 10); - si = jsm.getStreamInfo(tsc.stream); + jsPublish(nc, ctx.subject(0), 10); + jsPublish(nc, ctx.subject(1), 10); + si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(20, si.getStreamState().getMsgCount()); - jsm.purgeStream(tsc.stream, PurgeOptions.subject(tsc.subject(0))); - si = jsm.getStreamInfo(tsc.stream); + ctx.jsm.purgeStream(ctx.stream, PurgeOptions.subject(ctx.subject(0))); + si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(10, si.getStreamState().getMsgCount()); - options = PurgeOptions.builder().subject(tsc.subject(0)).sequence(1).build(); - assertEquals(tsc.subject(0), options.getSubject()); + options = PurgeOptions.builder().subject(ctx.subject(0)).sequence(1).build(); + assertEquals(ctx.subject(0), options.getSubject()); assertEquals(1, options.getSequence()); - options = PurgeOptions.builder().subject(tsc.subject(0)).keep(2).build(); + options = PurgeOptions.builder().subject(ctx.subject(0)).keep(2).build(); assertEquals(2, options.getKeep()); }); } @Test - public void testAddDeleteConsumer() throws Exception { - runInJsServer(nc -> { - boolean atLeast2dot9 = nc.getServerInfo().isSameOrNewerThanVersion("2.9"); + public void testAddDeleteConsumerPart1() throws Exception { + runInSharedCustom((nc, ctx) -> { + String subject = random(); + ctx.createOrReplaceStream(subjectGt(subject)); - JetStreamManagement jsm = nc.jetStreamManagement(); - - createMemoryStream(jsm, STREAM, subjectDot(">")); - - List list = jsm.getConsumers(STREAM); + List list = ctx.jsm.getConsumers(ctx.stream); assertEquals(0, list.size()); final ConsumerConfiguration cc = ConsumerConfiguration.builder().build(); IllegalArgumentException iae = - assertThrows(IllegalArgumentException.class, () -> jsm.addOrUpdateConsumer(null, cc)); + assertThrows(IllegalArgumentException.class, () -> ctx.jsm.addOrUpdateConsumer(null, cc)); assertTrue(iae.getMessage().contains("Stream cannot be null or empty")); - iae = assertThrows(IllegalArgumentException.class, () -> jsm.addOrUpdateConsumer(STREAM, null)); + iae = assertThrows(IllegalArgumentException.class, () -> ctx.jsm.addOrUpdateConsumer(ctx.stream, null)); assertTrue(iae.getMessage().contains("Config cannot be null")); // durable and name can both be null - ConsumerInfo ci = jsm.addOrUpdateConsumer(STREAM, cc); + ConsumerInfo ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); assertNotNull(ci.getName()); // threshold can be set for durable - final ConsumerConfiguration cc2 = ConsumerConfiguration.builder().durable(DURABLE).inactiveThreshold(10000).build(); - ci = jsm.addOrUpdateConsumer(STREAM, cc2); - assertEquals(10000, ci.getConsumerConfiguration().getInactiveThreshold().toMillis()); + final ConsumerConfiguration cc2 = ConsumerConfiguration.builder().durable(random()).inactiveThreshold(10000).build(); + ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc2); + assertNotNull(ci.getName()); + ConsumerConfiguration cc2cc = ci.getConsumerConfiguration(); + assertNotNull(cc2cc); + Duration duration = ci.getConsumerConfiguration().getInactiveThreshold(); + assertNotNull(duration); + assertEquals(10000, duration.toMillis()); + }); + } - // prep for next part of test - jsm.deleteStream(STREAM); - createMemoryStream(jsm, STREAM, subjectDot(">")); + @Test + public void testAddDeleteConsumerPart2() throws Exception { + runInSharedCustom((nc, ctx) -> { + boolean atLeast2dot9 = nc.getServerInfo().isSameOrNewerThanVersion("2.9"); + String subject = random(); + ctx.createOrReplaceStream(subjectGt(subject)); // with and w/o deliver subject for push/pull - addConsumer(jsm, atLeast2dot9, 1, false, null, ConsumerConfiguration.builder() - .durable(durable(1)) + String dur0 = random(); + addConsumer(ctx.jsm, ctx.stream, atLeast2dot9, dur0, null, null, ConsumerConfiguration.builder() + .durable(dur0) .build()); - addConsumer(jsm, atLeast2dot9, 2, true, null, ConsumerConfiguration.builder() - .durable(durable(2)) - .deliverSubject(deliver(2)) + String dur = random(); + String deliver = random(); + addConsumer(ctx.jsm, ctx.stream, atLeast2dot9, dur, deliver, null, ConsumerConfiguration.builder() + .durable(dur) + .deliverSubject(deliver) .build()); // test delete here - List consumers = jsm.getConsumerNames(STREAM); + List consumers = ctx.jsm.getConsumerNames(ctx.stream); assertEquals(2, consumers.size()); - assertTrue(jsm.deleteConsumer(STREAM, durable(1))); - consumers = jsm.getConsumerNames(STREAM); + assertTrue(ctx.jsm.deleteConsumer(ctx.stream, dur0)); + consumers = ctx.jsm.getConsumerNames(ctx.stream); assertEquals(1, consumers.size()); - assertThrows(JetStreamApiException.class, () -> jsm.deleteConsumer(STREAM, durable(1))); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.deleteConsumer(ctx.stream, dur0)); // some testing of new name if (atLeast2dot9) { - addConsumer(jsm, true, 3, false, null, ConsumerConfiguration.builder() - .durable(durable(3)) - .name(durable(3)) + dur = random(); + addConsumer(ctx.jsm, ctx.stream, true, dur, null, null, ConsumerConfiguration.builder() + .durable(dur) + .name(dur) .build()); - addConsumer(jsm, true, 4, true, null, ConsumerConfiguration.builder() - .durable(durable(4)) - .name(durable(4)) - .deliverSubject(deliver(4)) + dur = random(); + deliver = random(); + addConsumer(ctx.jsm, ctx.stream, true, dur, deliver, null, ConsumerConfiguration.builder() + .durable(dur) + .name(dur) + .deliverSubject(deliver) .build()); - addConsumer(jsm, true, 5, false, ">", ConsumerConfiguration.builder() - .durable(durable(5)) + dur = random(); + addConsumer(ctx.jsm, ctx.stream, true, dur, null, ">", ConsumerConfiguration.builder() + .durable(dur) .filterSubject(">") .build()); - addConsumer(jsm, true, 6, false, subjectDot(">"), ConsumerConfiguration.builder() - .durable(durable(6)) - .filterSubject(subjectDot(">")) + dur = random(); + addConsumer(ctx.jsm, ctx.stream, true, dur, null, subjectGt(subject), ConsumerConfiguration.builder() + .durable(dur) + .filterSubject(subjectGt(subject)) .build()); - addConsumer(jsm, true, 7, false, subjectDot("foo"), ConsumerConfiguration.builder() - .durable(durable(7)) - .filterSubject(subjectDot("foo")) + dur = random(); + addConsumer(ctx.jsm, ctx.stream, true, dur, null, subjectDot(subject, "foo"), ConsumerConfiguration.builder() + .durable(dur) + .filterSubject(subjectDot(subject, "foo")) .build()); } }); @@ -815,22 +663,20 @@ public void testAddDeleteConsumer() throws Exception { @Test public void testAddPausedConsumer() throws Exception { - jsServer.run(TestBase::atLeast2_11, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - - List list = jsm.getConsumers(tsc.stream); + runInShared(VersionUtils::atLeast2_11, (nc, ctx) -> { + List list = ctx.jsm.getConsumers(ctx.stream); assertEquals(0, list.size()); ZonedDateTime pauseUntil = ZonedDateTime.now(ZONE_ID_GMT).plusMinutes(2); ConsumerConfiguration cc = ConsumerConfiguration.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .pauseUntil(pauseUntil) .build(); // Consumer should be paused on creation. - ConsumerInfo ci = jsm.addOrUpdateConsumer(tsc.stream, cc); + ConsumerInfo ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); assertTrue(ci.getPaused()); + assertNotNull(ci.getPauseRemaining()); assertTrue(ci.getPauseRemaining().toMillis() > 60_000); assertEquals(pauseUntil, ci.getConsumerConfiguration().getPauseUntil()); }); @@ -838,154 +684,147 @@ public void testAddPausedConsumer() throws Exception { @Test public void testPauseResumeConsumer() throws Exception { - jsServer.run(TestBase::atLeast2_11, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - - List list = jsm.getConsumers(tsc.stream); + runInShared(VersionUtils::atLeast2_11, (nc, ctx) -> { + List list = ctx.jsm.getConsumers(ctx.stream); assertEquals(0, list.size()); ConsumerConfiguration cc = ConsumerConfiguration.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .build(); // durable and name can both be null - ConsumerInfo ci = jsm.addOrUpdateConsumer(tsc.stream, cc); + ConsumerInfo ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); assertNotNull(ci.getName()); // pause consumer ZonedDateTime pauseUntil = ZonedDateTime.now(ZONE_ID_GMT).plusMinutes(2); - ConsumerPauseResponse pauseResponse = jsm.pauseConsumer(tsc.stream, ci.getName(), pauseUntil); + ConsumerPauseResponse pauseResponse = ctx.jsm.pauseConsumer(ctx.stream, ci.getName(), pauseUntil); assertTrue(pauseResponse.isPaused()); assertEquals(pauseUntil, pauseResponse.getPauseUntil()); - ci = jsm.getConsumerInfo(tsc.stream, ci.getName()); + ci = ctx.jsm.getConsumerInfo(ctx.stream, ci.getName()); assertTrue(ci.getPaused()); + assertNotNull(ci.getPauseRemaining()); assertTrue(ci.getPauseRemaining().toMillis() > 60_000); // resume consumer - boolean isResumed = jsm.resumeConsumer(tsc.stream, ci.getName()); + boolean isResumed = ctx.jsm.resumeConsumer(ctx.stream, ci.getName()); assertTrue(isResumed); - ci = jsm.getConsumerInfo(tsc.stream, ci.getName()); + ci = ctx.jsm.getConsumerInfo(ctx.stream, ci.getName()); assertFalse(ci.getPaused()); // pause again - pauseResponse = jsm.pauseConsumer(tsc.stream, ci.getName(), pauseUntil); + pauseResponse = ctx.jsm.pauseConsumer(ctx.stream, ci.getName(), pauseUntil); assertTrue(pauseResponse.isPaused()); assertEquals(pauseUntil, pauseResponse.getPauseUntil()); // resume via pause with no date - pauseResponse = jsm.pauseConsumer(tsc.stream, ci.getName(), null); + pauseResponse = ctx.jsm.pauseConsumer(ctx.stream, ci.getName(), null); assertFalse(pauseResponse.isPaused()); assertEquals(DEFAULT_TIME, pauseResponse.getPauseUntil()); - ci = jsm.getConsumerInfo(tsc.stream, ci.getName()); + ci = ctx.jsm.getConsumerInfo(ctx.stream, ci.getName()); assertFalse(ci.getPaused()); - assertThrows(JetStreamApiException.class, () -> jsm.pauseConsumer(stream(), tsc.consumerName(), pauseUntil)); - assertThrows(JetStreamApiException.class, () -> jsm.pauseConsumer(tsc.stream, name(), pauseUntil)); - assertThrows(JetStreamApiException.class, () -> jsm.resumeConsumer(stream(), tsc.consumerName())); - assertThrows(JetStreamApiException.class, () -> jsm.resumeConsumer(tsc.stream, name())); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.pauseConsumer(random(), ctx.consumerName(), pauseUntil)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.pauseConsumer(ctx.stream, random(), pauseUntil)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.resumeConsumer(random(), ctx.consumerName())); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.resumeConsumer(ctx.stream, random())); }); } - private static void addConsumer(JetStreamManagement jsm, boolean atLeast2dot9, int id, boolean deliver, String fs, ConsumerConfiguration cc) throws IOException, JetStreamApiException { - ConsumerInfo ci = jsm.addOrUpdateConsumer(STREAM, cc); - assertEquals(durable(id), ci.getName()); + private static void addConsumer(JetStreamManagement jsm, String stream, boolean atLeast2dot9, String name, String deliver, String fs, ConsumerConfiguration cc) throws IOException, JetStreamApiException { + ConsumerInfo ci = jsm.addOrUpdateConsumer(stream, cc); + assertEquals(name, ci.getName()); if (atLeast2dot9) { - assertEquals(durable(id), ci.getConsumerConfiguration().getName()); + assertEquals(name, ci.getConsumerConfiguration().getName()); } - assertEquals(durable(id), ci.getConsumerConfiguration().getDurable()); + assertEquals(name, ci.getConsumerConfiguration().getDurable()); if (fs == null) { assertNull(ci.getConsumerConfiguration().getFilterSubject()); } - if (deliver) { - assertEquals(deliver(id), ci.getConsumerConfiguration().getDeliverSubject()); + if (deliver != null) { + assertEquals(deliver, ci.getConsumerConfiguration().getDeliverSubject()); } } @Test public void testValidConsumerUpdates() throws Exception { - runInJsServer(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - createMemoryStream(jsm, STREAM, SUBJECT_GT); + runInSharedCustom((nc, ctx) -> { + String subject = random(); + String subjectGt = subjectGt(subject); + ctx.createOrReplaceStream(subjectGt); - ConsumerConfiguration cc = prepForUpdateTest(jsm); - cc = ConsumerConfiguration.builder(cc).deliverSubject(deliver(2)).build(); - assertValidAddOrUpdate(jsm, cc); + ConsumerConfiguration cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, null); + cc = ConsumerConfiguration.builder(cc).deliverSubject(random()).build(); + assertValidAddOrUpdate(ctx.jsm, cc, ctx.stream); - cc = prepForUpdateTest(jsm); + cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, cc.getDurable()); cc = ConsumerConfiguration.builder(cc).ackWait(Duration.ofSeconds(5)).build(); - assertValidAddOrUpdate(jsm, cc); + assertValidAddOrUpdate(ctx.jsm, cc, ctx.stream); - cc = prepForUpdateTest(jsm); + cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, cc.getDurable()); cc = ConsumerConfiguration.builder(cc).rateLimit(100).build(); - assertValidAddOrUpdate(jsm, cc); + assertValidAddOrUpdate(ctx.jsm, cc, ctx.stream); - cc = prepForUpdateTest(jsm); + cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, cc.getDurable()); cc = ConsumerConfiguration.builder(cc).maxAckPending(100).build(); - assertValidAddOrUpdate(jsm, cc); + assertValidAddOrUpdate(ctx.jsm, cc, ctx.stream); - cc = prepForUpdateTest(jsm); + cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, cc.getDurable()); cc = ConsumerConfiguration.builder(cc).maxDeliver(4).build(); - assertValidAddOrUpdate(jsm, cc); + assertValidAddOrUpdate(ctx.jsm, cc, ctx.stream); - if (nc.getServerInfo().isNewerVersionThan("2.8.4")) { - cc = prepForUpdateTest(jsm); - cc = ConsumerConfiguration.builder(cc).filterSubject(SUBJECT_STAR).build(); - assertValidAddOrUpdate(jsm, cc); - } + cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, cc.getDurable()); + cc = ConsumerConfiguration.builder(cc).filterSubject(subjectStar(subject)).build(); + assertValidAddOrUpdate(ctx.jsm, cc, ctx.stream); }); } @Test public void testInvalidConsumerUpdates() throws Exception { - runInJsServer(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - createMemoryStream(jsm, STREAM, SUBJECT_GT); + runInSharedCustom((nc, ctx) -> { + String subject = random(); + String subjectGt = subjectGt(subject); + ctx.createOrReplaceStream(subjectGt); - ConsumerConfiguration cc = prepForUpdateTest(jsm); + ConsumerConfiguration cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, null); cc = ConsumerConfiguration.builder(cc).deliverPolicy(DeliverPolicy.New).build(); - assertInvalidConsumerUpdate(jsm, cc); - - if (nc.getServerInfo().isSameOrOlderThanVersion("2.8.4")) { - cc = prepForUpdateTest(jsm); - cc = ConsumerConfiguration.builder(cc).filterSubject(SUBJECT_STAR).build(); - assertInvalidConsumerUpdate(jsm, cc); - } + assertInvalidConsumerUpdate(ctx.jsm, cc, ctx.stream); - cc = prepForUpdateTest(jsm); + cc = prepForUpdateTest(ctx.jsm, ctx.stream, subjectGt, cc.getDurable()); cc = ConsumerConfiguration.builder(cc).idleHeartbeat(Duration.ofMillis(111)).build(); - assertInvalidConsumerUpdate(jsm, cc); + assertInvalidConsumerUpdate(ctx.jsm, cc, ctx.stream); }); } - private ConsumerConfiguration prepForUpdateTest(JetStreamManagement jsm) throws IOException, JetStreamApiException { + private ConsumerConfiguration prepForUpdateTest(JetStreamManagement jsm, String stream, String subjectGt, String durableToDelete) throws IOException, JetStreamApiException { try { - jsm.deleteConsumer(STREAM, durable(1)); + if (durableToDelete != null) { + jsm.deleteConsumer(stream, durableToDelete); + } } catch (Exception e) { /* ignore */ } - ConsumerConfiguration cc = ConsumerConfiguration.builder() - .durable(durable(1)) + .durable(random()) .ackPolicy(AckPolicy.Explicit) - .deliverSubject(deliver(1)) + .deliverSubject(random()) .maxDeliver(3) - .filterSubject(SUBJECT_GT) + .filterSubject(subjectGt) .build(); - assertValidAddOrUpdate(jsm, cc); + assertValidAddOrUpdate(jsm, cc, stream); return cc; } - private void assertInvalidConsumerUpdate(JetStreamManagement jsm, ConsumerConfiguration cc) { - JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> jsm.addOrUpdateConsumer(STREAM, cc)); + private void assertInvalidConsumerUpdate(JetStreamManagement jsm, ConsumerConfiguration cc, String stream) { + JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> jsm.addOrUpdateConsumer(stream, cc)); assertEquals(10012, e.getApiErrorCode()); assertEquals(500, e.getErrorCode()); } - private void assertValidAddOrUpdate(JetStreamManagement jsm, ConsumerConfiguration cc) throws IOException, JetStreamApiException { - ConsumerInfo ci = jsm.addOrUpdateConsumer(STREAM, cc); + private void assertValidAddOrUpdate(JetStreamManagement jsm, ConsumerConfiguration cc, String stream) throws IOException, JetStreamApiException { + ConsumerInfo ci = jsm.addOrUpdateConsumer(stream, cc); ConsumerConfiguration cicc = ci.getConsumerConfiguration(); assertEquals(cc.getDurable(), ci.getName()); assertEquals(cc.getDurable(), cicc.getDurable()); @@ -993,84 +832,77 @@ private void assertValidAddOrUpdate(JetStreamManagement jsm, ConsumerConfigurati assertEquals(cc.getMaxDeliver(), cicc.getMaxDeliver()); assertEquals(cc.getDeliverPolicy(), cicc.getDeliverPolicy()); - List consumers = jsm.getConsumerNames(STREAM); + List consumers = jsm.getConsumerNames(stream); assertEquals(1, consumers.size()); assertEquals(cc.getDurable(), consumers.get(0)); } @Test public void testConsumerMetadata() throws Exception { - jsServer.run(nc -> { + runInShared((nc, ctx) -> { Map metaData = new HashMap<>(); metaData.put(META_KEY, META_VALUE); - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); ConsumerConfiguration cc = ConsumerConfiguration.builder() - .durable(tsc.consumerName()) + .durable(random()) .metadata(metaData) .build(); - ConsumerInfo ci = jsm.addOrUpdateConsumer(tsc.stream, cc); + ConsumerInfo ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); assertMetaData(ci.getConsumerConfiguration().getMetadata()); }); } @Test public void testCreateConsumersWithFilters() throws Exception { - runInJsServer(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - createDefaultTestStream(jsm); + runInShared((nc, ctx) -> { + String subject = ctx.subject(); // plain subject - ConsumerConfiguration.Builder builder = ConsumerConfiguration.builder().durable(DURABLE); - jsm.addOrUpdateConsumer(STREAM, builder.filterSubject(SUBJECT).build()); - List cis = jsm.getConsumers(STREAM); - assertEquals(SUBJECT, cis.get(0).getConsumerConfiguration().getFilterSubject()); + ConsumerConfiguration.Builder builder = ConsumerConfiguration.builder().durable(random()); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder.filterSubject(subject).build()); + List cis = ctx.jsm.getConsumers(ctx.stream); + assertEquals(subject, cis.get(0).getConsumerConfiguration().getFilterSubject()); if (nc.getServerInfo().isSameOrNewerThanVersion("2.10")) { // 2.10 and later you can set the filter to something that does not match - jsm.addOrUpdateConsumer(STREAM, builder.filterSubject(subjectDot("two-ten-allows-not-matching")).build()); - cis = jsm.getConsumers(STREAM); - assertEquals(subjectDot("two-ten-allows-not-matching"), cis.get(0).getConsumerConfiguration().getFilterSubject()); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder.filterSubject(subjectDot(subject, "two-ten-allows-not-matching")).build()); + cis = ctx.jsm.getConsumers(ctx.stream); + assertEquals(subjectDot(subject, "two-ten-allows-not-matching"), cis.get(0).getConsumerConfiguration().getFilterSubject()); } else { assertThrows(JetStreamApiException.class, - () -> jsm.addOrUpdateConsumer(STREAM, builder.filterSubject(subjectDot("not-match")).build())); + () -> ctx.jsm.addOrUpdateConsumer(ctx.stream, builder.filterSubject(subjectDot(subject, "not-match")).build())); } // wildcard subject - jsm.deleteStream(STREAM); - createMemoryStream(jsm, STREAM, SUBJECT_STAR); + ctx.createOrReplaceStream(subjectStar(subject)); - jsm.addOrUpdateConsumer(STREAM, builder.filterSubject(subjectDot("A")).build()); - cis = jsm.getConsumers(STREAM); - assertEquals(subjectDot("A"), cis.get(0).getConsumerConfiguration().getFilterSubject()); + String subjectA = subjectDot(subject, "A"); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder.filterSubject(subjectA).build()); + cis = ctx.jsm.getConsumers(ctx.stream); + assertEquals(subjectA, cis.get(0).getConsumerConfiguration().getFilterSubject()); // gt subject - jsm.deleteStream(STREAM); - createMemoryStream(jsm, STREAM, SUBJECT_GT); + ctx.createOrReplaceStream(subjectGt(subject)); - jsm.addOrUpdateConsumer(STREAM, builder.filterSubject(subjectDot("A")).build()); - cis = jsm.getConsumers(STREAM); - assertEquals(subjectDot("A"), cis.get(0).getConsumerConfiguration().getFilterSubject()); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder.filterSubject(subjectA).build()); + cis = ctx.jsm.getConsumers(ctx.stream); + assertEquals(subjectA, cis.get(0).getConsumerConfiguration().getFilterSubject()); }); } @Test public void testGetConsumerInfo() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - assertThrows(JetStreamApiException.class, () -> jsm.getConsumerInfo(tsc.stream, tsc.consumerName())); - ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(tsc.consumerName()).build(); - ConsumerInfo ci = jsm.addOrUpdateConsumer(tsc.stream, cc); - assertEquals(tsc.stream, ci.getStreamName()); - assertEquals(tsc.consumerName(), ci.getName()); - ci = jsm.getConsumerInfo(tsc.stream, tsc.consumerName()); - assertEquals(tsc.stream, ci.getStreamName()); - assertEquals(tsc.consumerName(), ci.getName()); - assertThrows(JetStreamApiException.class, () -> jsm.getConsumerInfo(tsc.stream, durable(999))); + runInShared((nc, ctx) -> { + assertThrows(JetStreamApiException.class, () -> ctx.jsm.getConsumerInfo(ctx.stream, ctx.consumerName())); + ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(ctx.consumerName()).build(); + ConsumerInfo ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); + assertEquals(ctx.stream, ci.getStreamName()); + assertEquals(ctx.consumerName(), ci.getName()); + ci = ctx.jsm.getConsumerInfo(ctx.stream, ctx.consumerName()); + assertEquals(ctx.stream, ci.getStreamName()); + assertEquals(ctx.consumerName(), ci.getName()); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.getConsumerInfo(ctx.stream, random())); if (nc.getServerInfo().isSameOrNewerThanVersion("2.10")) { assertNotNull(ci.getTimestamp()); } @@ -1082,24 +914,22 @@ public void testGetConsumerInfo() throws Exception { @Test public void testGetConsumers() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - - addConsumers(jsm, tsc.stream, 600, "A"); // getConsumers pages at 256 + runInShared((nc, ctx) -> { + addConsumers(ctx.jsm, ctx.stream, 600); // getConsumers pages at 256 - List list = jsm.getConsumers(tsc.stream); + List list = ctx.jsm.getConsumers(ctx.stream); assertEquals(600, list.size()); - addConsumers(jsm, tsc.stream, 500, "B"); // getConsumerNames pages at 1024 - List names = jsm.getConsumerNames(tsc.stream); + addConsumers(ctx.jsm, ctx.stream, 500); // getConsumerNames pages at 1024 + List names = ctx.jsm.getConsumerNames(ctx.stream); assertEquals(1100, names.size()); }); } - private void addConsumers(JetStreamManagement jsm, String stream, int count, String durableVary) throws IOException, JetStreamApiException { + private void addConsumers(JetStreamManagement jsm, String stream, int count) throws IOException, JetStreamApiException { + String base = random() ; for (int x = 1; x <= count; x++) { - String dur = durable(durableVary, x); + String dur = base + "-" + x; ConsumerConfiguration cc = ConsumerConfiguration.builder() .durable(dur) .build(); @@ -1124,88 +954,59 @@ public void testDeleteMessage() throws Exception { assertFalse(mdr.isErase()); assertTrue(mdr.isNoErase()); - runInJsServer(nc -> { - createDefaultTestStream(nc); - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { Headers h = new Headers(); h.add("foo", "bar"); ZonedDateTime timeBeforeCreated = ZonedDateTime.now(); - js.publish(NatsMessage.builder().subject(SUBJECT).headers(h).data(dataBytes(1)).build()); - js.publish(NatsMessage.builder().subject(SUBJECT).build()); + ctx.js.publish(NatsMessage.builder().subject(ctx.subject()).headers(h).data(dataBytes(1)).build()); + ctx.js.publish(NatsMessage.builder().subject(ctx.subject()).build()); - JetStreamManagement jsm = nc.jetStreamManagement(); - - MessageInfo mi = jsm.getMessage(STREAM, 1); + MessageInfo mi = ctx.jsm.getMessage(ctx.stream, 1); assertNotNull(mi.toString()); - assertEquals(SUBJECT, mi.getSubject()); + assertEquals(ctx.subject(), mi.getSubject()); + assertNotNull(mi.getData()); assertEquals(data(1), new String(mi.getData())); assertEquals(1, mi.getSeq()); + assertNotNull(mi.getTime()); assertTrue(mi.getTime().toEpochSecond() >= timeBeforeCreated.toEpochSecond()); assertNotNull(mi.getHeaders()); - assertEquals("bar", mi.getHeaders().get("foo").get(0)); + List foos = mi.getHeaders().get("foo"); + assertNotNull(foos); + assertEquals("bar", foos.get(0)); - mi = jsm.getMessage(STREAM, 2); + mi = ctx.jsm.getMessage(ctx.stream, 2); assertNotNull(mi.toString()); - assertEquals(SUBJECT, mi.getSubject()); + assertEquals(ctx.subject(), mi.getSubject()); assertNull(mi.getData()); assertEquals(2, mi.getSeq()); + assertNotNull(mi.getTime()); assertTrue(mi.getTime().toEpochSecond() >= timeBeforeCreated.toEpochSecond()); assertTrue(mi.getHeaders() == null || mi.getHeaders().isEmpty()); - assertTrue(jsm.deleteMessage(STREAM, 1, false)); // added coverage for use of erase (no_erase) flag. - assertThrows(JetStreamApiException.class, () -> jsm.deleteMessage(STREAM, 1)); - assertThrows(JetStreamApiException.class, () -> jsm.getMessage(STREAM, 1)); - assertThrows(JetStreamApiException.class, () -> jsm.getMessage(STREAM, 3)); - assertThrows(JetStreamApiException.class, () -> jsm.deleteMessage(stream(999), 1)); - assertThrows(JetStreamApiException.class, () -> jsm.getMessage(stream(999), 1)); + assertTrue(ctx.jsm.deleteMessage(ctx.stream, 1, false)); // added coverage for use of erase (no_erase) flag. + assertThrows(JetStreamApiException.class, () -> ctx.jsm.deleteMessage(ctx.stream, 1)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.getMessage(ctx.stream, 1)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.getMessage(ctx.stream, 3)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.deleteMessage(random(), 1)); + assertThrows(JetStreamApiException.class, () -> ctx.jsm.getMessage(random(), 1)); }); } - @Test - public void testAuthCreateUpdateStream() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/js_authorization.conf", false)) { - Options optionsSrc = new Options.Builder().server(ts.getURI()) - .userInfo("serviceup".toCharArray(), "uppass".toCharArray()).build(); - - try (Connection nc = Nats.connect(optionsSrc)) { - JetStreamManagement jsm = nc.jetStreamManagement(); - - // add streams with both account - StreamConfiguration sc = StreamConfiguration.builder() - .name(STREAM) - .storageType(StorageType.Memory) - .subjects(subject(1)) - .build(); - StreamInfo si = jsm.addStream(sc); - - sc = StreamConfiguration.builder(si.getConfiguration()) - .addSubjects(subject(2)) - .build(); - - jsm.updateStream(sc); - } - } - } - @Test public void testSealed() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); + runInShared((nc, ctx) -> { + assertFalse(ctx.si.getConfiguration().getSealed()); - TestingStreamContainer tsc = new TestingStreamContainer(nc); - assertFalse(tsc.si.getConfiguration().getSealed()); + ctx.js.publish(ctx.subject(), "data1".getBytes()); - JetStream js = nc.jetStream(); - js.publish(tsc.subject(), "data1".getBytes()); - - StreamConfiguration sc = new StreamConfiguration.Builder(tsc.si.getConfiguration()) - .seal().build(); - StreamInfo si = jsm.updateStream(sc); + StreamConfiguration sc = new StreamConfiguration.Builder(ctx.si.getConfiguration()) + .seal() + .build(); + StreamInfo si = ctx.jsm.updateStream(sc); assertTrue(si.getConfiguration().getSealed()); - assertThrows(JetStreamApiException.class, () -> js.publish(tsc.subject(), "data2".getBytes())); + assertThrows(JetStreamApiException.class, () -> ctx.js.publish(ctx.subject(), "data2".getBytes())); }); } @@ -1220,113 +1021,106 @@ public void testStorageTypeCoverage() { @Test public void testConsumerReplica() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { final ConsumerConfiguration cc0 = ConsumerConfiguration.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .build(); - ConsumerInfo ci = jsm.addOrUpdateConsumer(tsc.stream, cc0); + ConsumerInfo ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc0); // server returns 0 when value is not set assertEquals(0, ci.getConsumerConfiguration().getNumReplicas()); final ConsumerConfiguration cc1 = ConsumerConfiguration.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .numReplicas(1) .build(); - ci = jsm.addOrUpdateConsumer(tsc.stream, cc1); + ci = ctx.jsm.addOrUpdateConsumer(ctx.stream, cc1); assertEquals(1, ci.getConsumerConfiguration().getNumReplicas()); }); } @Test public void testGetMessage() throws Exception { - jsServer.run(nc -> { - if (nc.getServerInfo().isNewerVersionThan("2.8.4")) { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - TestingStreamContainer tsc = new TestingStreamContainer(nc, 2); - assertFalse(tsc.si.getConfiguration().getAllowDirect()); - - ZonedDateTime timeBeforeCreated = ZonedDateTime.now(); - sleep(100); - js.publish(buildTestGetMessage(tsc, 0, 1)); - js.publish(buildTestGetMessage(tsc, 1, 2)); - js.publish(buildTestGetMessage(tsc, 0, 3)); - js.publish(buildTestGetMessage(tsc, 1, 4)); - js.publish(buildTestGetMessage(tsc, 0, 5)); - js.publish(buildTestGetMessage(tsc, 1, 6)); - - validateGetMessage(jsm, tsc, timeBeforeCreated); - - StreamConfiguration sc = StreamConfiguration.builder(tsc.si.getConfiguration()).allowDirect(true).build(); - StreamInfo si = jsm.updateStream(sc); - assertTrue(si.getConfiguration().getAllowDirect()); - validateGetMessage(jsm, tsc, timeBeforeCreated); - - // error case stream doesn't exist - assertThrows(JetStreamApiException.class, () -> jsm.getMessage(stream(999), 1)); - } + runInSharedCustom((nc, ctx) -> { + ctx.createOrReplaceStream(2); + assertFalse(ctx.si.getConfiguration().getAllowDirect()); + + ZonedDateTime timeBeforeCreated = ZonedDateTime.now(); + sleep(100); + ctx.js.publish(buildTestGetMessage(ctx, 0, 1)); + ctx.js.publish(buildTestGetMessage(ctx, 1, 2)); + ctx.js.publish(buildTestGetMessage(ctx, 0, 3)); + ctx.js.publish(buildTestGetMessage(ctx, 1, 4)); + ctx.js.publish(buildTestGetMessage(ctx, 0, 5)); + ctx.js.publish(buildTestGetMessage(ctx, 1, 6)); + + validateGetMessage(ctx.jsm, ctx, timeBeforeCreated); + + StreamConfiguration sc = StreamConfiguration.builder(ctx.si.getConfiguration()).allowDirect(true).build(); + StreamInfo si = ctx.jsm.updateStream(sc); + assertTrue(si.getConfiguration().getAllowDirect()); + validateGetMessage(ctx.jsm, ctx, timeBeforeCreated); + + // error case stream doesn't exist + assertThrows(JetStreamApiException.class, () -> ctx.jsm.getMessage(random(), 1)); }); } - private static NatsMessage buildTestGetMessage(TestingStreamContainer tsc, int s, int q) { - String data = "s" + s + "-q" + q; + private static NatsMessage buildTestGetMessage(JetStreamTestingContext ctx, int n, int q) { + String data = "s" + n + "-q" + q; return NatsMessage.builder() - .subject(tsc.subject(s)) + .subject(ctx.subject(n)) .data("d-" + data) .headers(new Headers().put("h", "h-" + data)) .build(); } - private void validateGetMessage(JetStreamManagement jsm, TestingStreamContainer tsc, ZonedDateTime timeBeforeCreated) throws IOException, JetStreamApiException { - - assertMessageInfo(tsc, 0, 1, jsm.getMessage(tsc.stream, 1), timeBeforeCreated); - assertMessageInfo(tsc, 0, 5, jsm.getLastMessage(tsc.stream, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 6, jsm.getLastMessage(tsc.stream, tsc.subject(1)), timeBeforeCreated); - - assertMessageInfo(tsc, 0, 1, jsm.getNextMessage(tsc.stream, -1, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 2, jsm.getNextMessage(tsc.stream, -1, tsc.subject(1)), timeBeforeCreated); - assertMessageInfo(tsc, 0, 1, jsm.getNextMessage(tsc.stream, 0, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 2, jsm.getNextMessage(tsc.stream, 0, tsc.subject(1)), timeBeforeCreated); - assertMessageInfo(tsc, 0, 1, jsm.getFirstMessage(tsc.stream, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 2, jsm.getFirstMessage(tsc.stream, tsc.subject(1)), timeBeforeCreated); - assertMessageInfo(tsc, 0, 1, jsm.getFirstMessage(tsc.stream, timeBeforeCreated), timeBeforeCreated); - assertMessageInfo(tsc, 0, 1, jsm.getFirstMessage(tsc.stream, timeBeforeCreated, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 2, jsm.getFirstMessage(tsc.stream, timeBeforeCreated, tsc.subject(1)), timeBeforeCreated); - - assertMessageInfo(tsc, 0, 1, jsm.getNextMessage(tsc.stream, 1, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 2, jsm.getNextMessage(tsc.stream, 1, tsc.subject(1)), timeBeforeCreated); - - assertMessageInfo(tsc, 0, 3, jsm.getNextMessage(tsc.stream, 2, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 2, jsm.getNextMessage(tsc.stream, 2, tsc.subject(1)), timeBeforeCreated); - - assertMessageInfo(tsc, 0, 5, jsm.getNextMessage(tsc.stream, 5, tsc.subject(0)), timeBeforeCreated); - assertMessageInfo(tsc, 1, 6, jsm.getNextMessage(tsc.stream, 5, tsc.subject(1)), timeBeforeCreated); - - assertStatus(10003, assertThrows(JetStreamApiException.class, () -> jsm.getMessage(tsc.stream, -1))); - assertStatus(10003, assertThrows(JetStreamApiException.class, () -> jsm.getMessage(tsc.stream, 0))); - assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getMessage(tsc.stream, 9))); - assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getLastMessage(tsc.stream, "not-a-subject"))); - assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getFirstMessage(tsc.stream, "not-a-subject"))); - assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getNextMessage(tsc.stream, 9, tsc.subject(0)))); - assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getNextMessage(tsc.stream, 1, "not-a-subject"))); + private void validateGetMessage(JetStreamManagement jsm, JetStreamTestingContext ctx, ZonedDateTime timeBeforeCreated) throws IOException, JetStreamApiException { + assertMessageInfo(ctx, 0, 1, jsm.getMessage(ctx.stream, 1), timeBeforeCreated); + assertMessageInfo(ctx, 0, 5, jsm.getLastMessage(ctx.stream, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 6, jsm.getLastMessage(ctx.stream, ctx.subject(1)), timeBeforeCreated); + + assertMessageInfo(ctx, 0, 1, jsm.getNextMessage(ctx.stream, -1, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 2, jsm.getNextMessage(ctx.stream, -1, ctx.subject(1)), timeBeforeCreated); + assertMessageInfo(ctx, 0, 1, jsm.getNextMessage(ctx.stream, 0, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 2, jsm.getNextMessage(ctx.stream, 0, ctx.subject(1)), timeBeforeCreated); + assertMessageInfo(ctx, 0, 1, jsm.getFirstMessage(ctx.stream, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 2, jsm.getFirstMessage(ctx.stream, ctx.subject(1)), timeBeforeCreated); + assertMessageInfo(ctx, 0, 1, jsm.getFirstMessage(ctx.stream, timeBeforeCreated), timeBeforeCreated); + assertMessageInfo(ctx, 0, 1, jsm.getFirstMessage(ctx.stream, timeBeforeCreated, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 2, jsm.getFirstMessage(ctx.stream, timeBeforeCreated, ctx.subject(1)), timeBeforeCreated); + + assertMessageInfo(ctx, 0, 1, jsm.getNextMessage(ctx.stream, 1, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 2, jsm.getNextMessage(ctx.stream, 1, ctx.subject(1)), timeBeforeCreated); + + assertMessageInfo(ctx, 0, 3, jsm.getNextMessage(ctx.stream, 2, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 2, jsm.getNextMessage(ctx.stream, 2, ctx.subject(1)), timeBeforeCreated); + + assertMessageInfo(ctx, 0, 5, jsm.getNextMessage(ctx.stream, 5, ctx.subject(0)), timeBeforeCreated); + assertMessageInfo(ctx, 1, 6, jsm.getNextMessage(ctx.stream, 5, ctx.subject(1)), timeBeforeCreated); + + assertStatus(10003, assertThrows(JetStreamApiException.class, () -> jsm.getMessage(ctx.stream, -1))); + assertStatus(10003, assertThrows(JetStreamApiException.class, () -> jsm.getMessage(ctx.stream, 0))); + assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getMessage(ctx.stream, 9))); + assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getLastMessage(ctx.stream, "not-a-subject"))); + assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getFirstMessage(ctx.stream, "not-a-subject"))); + assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getNextMessage(ctx.stream, 9, ctx.subject(0)))); + assertStatus(10037, assertThrows(JetStreamApiException.class, () -> jsm.getNextMessage(ctx.stream, 1, "not-a-subject"))); } private void assertStatus(int apiErrorCode, JetStreamApiException jsae) { assertEquals(apiErrorCode, jsae.getApiErrorCode()); } - private void assertMessageInfo(TestingStreamContainer tsc, int subj, long seq, MessageInfo mi, ZonedDateTime timeBeforeCreated) { - assertEquals(tsc.stream, mi.getStream()); - assertEquals(tsc.subject(subj), mi.getSubject()); + private void assertMessageInfo(JetStreamTestingContext ctx, int subj, long seq, MessageInfo mi, ZonedDateTime timeBeforeCreated) { + assertEquals(ctx.stream, mi.getStream()); + assertEquals(ctx.subject(subj), mi.getSubject()); assertEquals(seq, mi.getSeq()); assertNotNull(mi.getTime()); assertTrue(mi.getTime().toEpochSecond() >= timeBeforeCreated.toEpochSecond()); String expectedData = "s" + subj + "-q" + seq; + assertNotNull(mi.getData()); assertEquals("d-" + expectedData, new String(mi.getData())); + assertNotNull(mi.getHeaders()); assertEquals("h-" + expectedData, mi.getHeaders().getFirst("h")); assertNull(mi.getHeaders().getFirst(NATS_SUBJECT)); assertNull(mi.getHeaders().getFirst(NATS_SEQUENCE)); @@ -1372,14 +1166,19 @@ private void validateMessageGetRequestObject( @Test public void testMessageGetRequestObjectDeprecatedMethods() { // coverage for deprecated methods + //noinspection deprecation MessageGetRequest.seqBytes(1); - MessageGetRequest.lastBySubjectBytes(SUBJECT); + //noinspection deprecation + MessageGetRequest.lastBySubjectBytes(random()); + //noinspection deprecation new MessageGetRequest(1); - new MessageGetRequest(SUBJECT); + //noinspection deprecation + new MessageGetRequest(random()); // coverage for MessageInfo, has error String json = dataAsString("GenericErrorResponse.json"); NatsMessage m = new NatsMessage("sub", null, json.getBytes(StandardCharsets.US_ASCII)); + //noinspection deprecation MessageInfo mi = new MessageInfo(m); assertTrue(mi.hasError()); assertEquals(-1, mi.getLastSeq()); @@ -1388,44 +1187,42 @@ public void testMessageGetRequestObjectDeprecatedMethods() { @Test public void testDirectMessageRepublishedSubject() throws Exception { - jsServer.run(TestBase::atLeast2_9_0, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - String streamBucketName = "sb-" + variant(null); - String subject = subject(); + runInSharedCustom((nc, ctx) -> { + String bucketName = random(); + String subject = random(); String streamSubject = subject + ".>"; String publishSubject1 = subject + ".one"; String publishSubject2 = subject + ".two"; String publishSubject3 = subject + ".three"; - String republishDest = "$KV." + streamBucketName + ".>"; + String republishDest = "$KV." + bucketName + ".>"; - StreamConfiguration sc = StreamConfiguration.builder() - .name(streamBucketName) - .storageType(StorageType.Memory) - .subjects(streamSubject) - .republish(Republish.builder().source(">").destination(republishDest).build()) + StreamConfiguration sc = ctx.scBuilder(streamSubject) + .republish(Republish.builder() + .source(">") + .destination(republishDest) + .build()) .build(); - jsm.addStream(sc); + ctx.createOrReplaceStream(sc); - KeyValueConfiguration kvc = KeyValueConfiguration.builder().name(streamBucketName).build(); - nc.keyValueManagement().create(kvc); - KeyValue kv = nc.keyValue(streamBucketName); + ctx.kvCreate(bucketName); + KeyValue kv = nc.keyValue(bucketName); nc.publish(publishSubject1, "uno".getBytes()); nc.jetStream().publish(publishSubject2, "dos".getBytes()); kv.put(publishSubject3, "tres"); KeyValueEntry kve1 = kv.get(publishSubject1); - assertEquals(streamBucketName, kve1.getBucket()); + assertEquals(bucketName, kve1.getBucket()); assertEquals(publishSubject1, kve1.getKey()); assertEquals("uno", kve1.getValueAsString()); KeyValueEntry kve2 = kv.get(publishSubject2); - assertEquals(streamBucketName, kve2.getBucket()); + assertEquals(bucketName, kve2.getBucket()); assertEquals(publishSubject2, kve2.getKey()); assertEquals("dos", kve2.getValueAsString()); KeyValueEntry kve3 = kv.get(publishSubject3); - assertEquals(streamBucketName, kve3.getBucket()); + assertEquals(bucketName, kve3.getBucket()); assertEquals(publishSubject3, kve3.getKey()); assertEquals("tres", kve3.getValueAsString()); }); @@ -1433,8 +1230,8 @@ public void testDirectMessageRepublishedSubject() throws Exception { @Test public void testCreateConsumerUpdateConsumer() throws Exception { - jsServer.run(TestBase::atLeast2_9_0, nc -> { - String streamPrefix = variant(); + runInOwnJsServer((nc, jsm, js) -> { + String streamPrefix = random(); JetStreamManagement jsmNew = nc.jetStreamManagement(); JetStreamManagement jsmPre290 = nc.jetStreamManagement(JetStreamOptions.builder().optOut290ConsumerCreate(true).build()); @@ -1442,8 +1239,8 @@ public void testCreateConsumerUpdateConsumer() throws Exception { // New without filter // -------------------------------------------------------- String stream1 = streamPrefix + "-new"; - String name = name(); - String subject = name(); + String name = random(); + String subject = random(); createMemoryStream(jsmNew, stream1, subject + ".*"); ConsumerConfiguration cc11 = ConsumerConfiguration.builder().name(name).build(); @@ -1462,7 +1259,7 @@ public void testCreateConsumerUpdateConsumer() throws Exception { assertEquals(10148, e.getApiErrorCode()); // update ok when exists - ConsumerConfiguration cc12 = ConsumerConfiguration.builder().name(name).description(variant()).build(); + ConsumerConfiguration cc12 = ConsumerConfiguration.builder().name(name).description(random()).build(); ci = jsmNew.updateConsumer(stream1, cc12); assertEquals(name, ci.getName()); assertNull(ci.getConsumerConfiguration().getFilterSubject()); @@ -1471,8 +1268,8 @@ public void testCreateConsumerUpdateConsumer() throws Exception { // New with filter subject // -------------------------------------------------------- String stream2 = streamPrefix + "-new-fs"; - name = name(); - subject = name(); + name = random(); + subject = random(); String fs1 = subject + ".A"; String fs2 = subject + ".B"; createMemoryStream(jsmNew, stream2, subject + ".*"); @@ -1502,8 +1299,8 @@ public void testCreateConsumerUpdateConsumer() throws Exception { // Pre 290 durable pathway // -------------------------------------------------------- String stream3 = streamPrefix + "-old-durable"; - name = name(); - subject = name(); + name = random(); + subject = random(); fs1 = subject + ".A"; fs2 = subject + ".B"; String fs3 = subject + ".C"; @@ -1537,7 +1334,7 @@ public void testCreateConsumerUpdateConsumer() throws Exception { // -------------------------------------------------------- // Pre 290 ephemeral pathway // -------------------------------------------------------- - subject = name(); + subject = random(); String stream4 = streamPrefix + "-old-ephemeral"; fs1 = subject + ".A"; @@ -1557,61 +1354,52 @@ public void testCreateConsumerUpdateConsumer() throws Exception { @Test public void testNoRespondersWhenConsumerDeleted() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - jsServer.run(new Options.Builder().errorListener(listener), TestBase::atLeast2_10_26, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - String stream = stream(); - String subject = subject(); - - assertThrows(JetStreamApiException.class, () -> jsm.getMessage(stream, 1)); - - createMemoryStream(jsm, stream, subject); - + Listener listener = new Listener(); + runInSharedOwnNc(listener, VersionUtils::atLeast2_10_26, (nc, ctx) -> { + String subject = ctx.subject(); for (int x = 0; x < 5; x++) { - js.publish(subject, null); + ctx.js.publish(subject, null); } - String consumer = create1026Consumer(jsm, stream, subject); - PullSubscribeOptions so = PullSubscribeOptions.fastBind(stream, consumer); - JetStreamSubscription sub = js.subscribe(null, so); - jsm.deleteConsumer(stream, consumer); + String consumer = create1026Consumer(ctx.jsm, ctx.stream, subject); + PullSubscribeOptions so = PullSubscribeOptions.fastBind(ctx.stream, consumer); + JetStreamSubscription sub = ctx.js.subscribe(null, so); + ctx.jsm.deleteConsumer(ctx.stream, consumer); sub.pull(5); validate1026(sub.nextMessage(500), listener, false); - ConsumerContext context = setupFor1026Simplification(nc, jsm, listener, stream, subject); + ConsumerContext context = setupFor1026Simplification(nc, ctx.jsm, listener, ctx.stream, subject); validate1026(context.next(1000), listener, true); // simplification next never raises warnings, so empty = true - context = setupFor1026Simplification(nc, jsm, listener, stream, subject); + context = setupFor1026Simplification(nc, ctx.jsm, listener, ctx.stream, subject); //noinspection resource FetchConsumer fc = context.fetch(FetchConsumeOptions.builder().maxMessages(1).raiseStatusWarnings(false).build()); validate1026(fc.nextMessage(), listener, true); // we said not to raise status warnings in the FetchConsumeOptions - context = setupFor1026Simplification(nc, jsm, listener, stream, subject); + context = setupFor1026Simplification(nc, ctx.jsm, listener, ctx.stream, subject); //noinspection resource fc = context.fetch(FetchConsumeOptions.builder().maxMessages(1).raiseStatusWarnings().build()); validate1026(fc.nextMessage(), listener, false); // we said raise status warnings in the FetchConsumeOptions - context = setupFor1026Simplification(nc, jsm, listener, stream, subject); + context = setupFor1026Simplification(nc, ctx.jsm, listener, ctx.stream, subject); IterableConsumer ic = context.iterate(ConsumeOptions.builder().raiseStatusWarnings(false).build()); validate1026(ic.nextMessage(1000), listener, true); // we said not to raise status warnings in the ConsumeOptions - context = setupFor1026Simplification(nc, jsm, listener, stream, subject); + context = setupFor1026Simplification(nc, ctx.jsm, listener, ctx.stream, subject); ic = context.iterate(ConsumeOptions.builder().raiseStatusWarnings().build()); validate1026(ic.nextMessage(1000), listener, false); // we said raise status warnings in the ConsumeOptions AtomicInteger count = new AtomicInteger(); MessageHandler handler = m -> count.incrementAndGet(); - context = setupFor1026Simplification(nc, jsm, listener, stream, subject); + context = setupFor1026Simplification(nc, ctx.jsm, listener, ctx.stream, subject); //noinspection resource context.consume(ConsumeOptions.builder().raiseStatusWarnings(false).build(), handler); Thread.sleep(100); // give time to get a message assertEquals(0, count.get()); validate1026(null, listener, true); - context = setupFor1026Simplification(nc, jsm, listener, stream, subject); + context = setupFor1026Simplification(nc, ctx.jsm, listener, ctx.stream, subject); //noinspection resource context.consume(ConsumeOptions.builder().raiseStatusWarnings().build(), handler); Thread.sleep(100); // give time to get a message @@ -1620,13 +1408,18 @@ public void testNoRespondersWhenConsumerDeleted() throws Exception { }); } - private static void validate1026(Message m, ListenerForTesting listener, boolean empty) { + private static void validate1026(Message m, Listener listener, boolean empty) { assertNull(m); - sleep(100); // give time for the message to get there - assertEquals(empty, listener.getPullStatusWarnings().isEmpty()); + sleep(250); // give time for the message to get there + if (empty) { + assertEquals(0, listener.getPullStatusWarningsCount()); + } + else { + assertTrue(listener.getPullStatusWarningsCount() > 0); + } } - private static ConsumerContext setupFor1026Simplification(Connection nc, JetStreamManagement jsm, ListenerForTesting listener, String stream, String subject) throws IOException, JetStreamApiException { + private static ConsumerContext setupFor1026Simplification(Connection nc, JetStreamManagement jsm, Listener listener, String stream, String subject) throws IOException, JetStreamApiException { listener.reset(); String consumer = create1026Consumer(jsm, stream, subject); ConsumerContext cCtx = nc.getConsumerContext(stream, consumer); @@ -1635,7 +1428,7 @@ private static ConsumerContext setupFor1026Simplification(Connection nc, JetStre } private static String create1026Consumer(JetStreamManagement jsm, String stream, String subject) throws IOException, JetStreamApiException { - String consumer = name(); + String consumer = random(); jsm.addOrUpdateConsumer(stream, ConsumerConfiguration.builder() .durable(consumer) .filterSubject(subject) @@ -1690,22 +1483,20 @@ public void testMessageDeleteRequest() { @Test public void testStreamPersistMode() throws Exception { - jsServer.run(TestBase::atLeast2_12, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - + runInOwnJsServer(VersionUtils::atLeast2_12, (nc, jsm, js) -> { StreamConfiguration sc = StreamConfiguration.builder() - .name(stream()) + .name(random()) .storageType(StorageType.File) - .subjects(subject()) + .subjects(random()) .build(); StreamInfo si = jsm.addStream(sc); assertTrue(si.getConfiguration().getPersistMode() == null || si.getConfiguration().getPersistMode() == PersistMode.Default); sc = StreamConfiguration.builder() - .name(stream()) + .name(random()) .storageType(StorageType.File) - .subjects(subject()) + .subjects(random()) .persistMode(PersistMode.Default) .build(); @@ -1713,9 +1504,9 @@ public void testStreamPersistMode() throws Exception { assertTrue(si.getConfiguration().getPersistMode() == null || si.getConfiguration().getPersistMode() == PersistMode.Default); sc = StreamConfiguration.builder() - .name(stream()) + .name(random()) .storageType(StorageType.File) - .subjects(subject()) + .subjects(random()) .persistMode(PersistMode.Async) .build(); diff --git a/src/test/java/io/nats/client/impl/JetStreamManagementWithConfTests.java b/src/test/java/io/nats/client/impl/JetStreamManagementWithConfTests.java new file mode 100644 index 000000000..b9ffadeed --- /dev/null +++ b/src/test/java/io/nats/client/impl/JetStreamManagementWithConfTests.java @@ -0,0 +1,145 @@ +// Copyright 2020-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.impl; + +import io.nats.client.Connection; +import io.nats.client.JetStream; +import io.nats.client.JetStreamManagement; +import io.nats.client.api.*; +import io.nats.client.utils.ConnectionUtils; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JetStreamManagementWithConfTests extends JetStreamTestBase { + + @Test + public void testGetStreamInfoSubjectPagination() throws Exception { + runInConfiguredServer("pagination.conf", ts -> { + try (Connection nc = ConnectionUtils.managedConnect(options(ts))) { + JetStreamManagement jsm = nc.jetStreamManagement(); + JetStream js = jsm.jetStream(); + + String stream1 = random(); + String stream2 = random(); + long rounds = 101; + long size = 1000; + long count = rounds * size; + jsm.addStream(StreamConfiguration.builder() + .name(stream1) + .storageType(StorageType.Memory) + .subjects("s.*.*") + .build()); + + jsm.addStream(StreamConfiguration.builder() + .name(stream2) + .storageType(StorageType.Memory) + .subjects("t.*.*") + .build()); + + for (int x = 1; x <= rounds; x++) { + for (int y = 1; y <= size; y++) { + js.publish("s." + x + "." + y, null); + } + } + + for (int y = 1; y <= size; y++) { + js.publish("t.7." + y, null); + } + + StreamInfo si = jsm.getStreamInfo(stream1); + validateStreamInfo(si.getStreamState(), 0, 0, count); + + si = jsm.getStreamInfo(stream1, StreamInfoOptions.allSubjects()); + validateStreamInfo(si.getStreamState(), count, count, count); + + si = jsm.getStreamInfo(stream1, StreamInfoOptions.filterSubjects("s.7.*")); + validateStreamInfo(si.getStreamState(), size, size, count); + + si = jsm.getStreamInfo(stream1, StreamInfoOptions.filterSubjects("s.7.1")); + validateStreamInfo(si.getStreamState(), 1L, 1, count); + + si = jsm.getStreamInfo(stream2, StreamInfoOptions.filterSubjects("t.7.*")); + validateStreamInfo(si.getStreamState(), size, size, size); + + si = jsm.getStreamInfo(stream2, StreamInfoOptions.filterSubjects("t.7.1")); + validateStreamInfo(si.getStreamState(), 1L, 1, size); + + List infos = jsm.getStreams(); + assertEquals(2, infos.size()); + si = infos.get(0); + if (si.getConfiguration().getSubjects().get(0).equals("s.*.*")) { + validateStreamInfo(si.getStreamState(), 0, 0, count); + validateStreamInfo(infos.get(1).getStreamState(), 0, 0, size); + } + else { + validateStreamInfo(si.getStreamState(), 0, 0, size); + validateStreamInfo(infos.get(1).getStreamState(), 0, 0, count); + } + + infos = jsm.getStreams(">"); + assertEquals(2, infos.size()); + + infos = jsm.getStreams("*.7.*"); + assertEquals(2, infos.size()); + + infos = jsm.getStreams("*.7.1"); + assertEquals(2, infos.size()); + + infos = jsm.getStreams("s.7.*"); + assertEquals(1, infos.size()); + assertEquals("s.*.*", infos.get(0).getConfiguration().getSubjects().get(0)); + + infos = jsm.getStreams("t.7.1"); + assertEquals(1, infos.size()); + assertEquals("t.*.*", infos.get(0).getConfiguration().getSubjects().get(0)); + } + }); + } + + private void validateStreamInfo(StreamState streamState, long subjectsList, long filteredCount, long subjectCount) { + assertEquals(subjectsList, streamState.getSubjects().size()); + assertEquals(filteredCount, streamState.getSubjects().size()); + assertEquals(subjectCount, streamState.getSubjectCount()); + } + + @Test + public void testGoodAuthAccount() throws Exception { + runInConfiguredServer("js_authorization.conf", ts -> { + try (Connection nc = ConnectionUtils.managedConnect(optionsBuilder(ts).userInfo("serviceup", "uppass").build())) { + JetStreamManagement jsm = nc.jetStreamManagement(); + // add streams with both account + String stream = random(); + String subject1 = random(); + String subject2 = random(); + StreamConfiguration sc = StreamConfiguration.builder() + .name(stream) + .storageType(StorageType.Memory) + .subjects(subject1) + .build(); + StreamInfo si = jsm.addStream(sc); + + sc = StreamConfiguration.builder(si.getConfiguration()) + .addSubjects(subject2) + .build(); + + jsm.updateStream(sc); + } + }); + } +} diff --git a/src/test/java/io/nats/client/impl/JetStreamMirrorAndSourcesTests.java b/src/test/java/io/nats/client/impl/JetStreamMirrorAndSourcesTests.java index 3f8c0cb31..d1ca38b9a 100644 --- a/src/test/java/io/nats/client/impl/JetStreamMirrorAndSourcesTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamMirrorAndSourcesTests.java @@ -13,10 +13,13 @@ package io.nats.client.impl; -import io.nats.client.*; +import io.nats.client.JetStreamApiException; +import io.nats.client.JetStreamSubscription; +import io.nats.client.Message; +import io.nats.client.PushSubscribeOptions; import io.nats.client.api.*; import io.nats.client.support.DateTimeUtils; -import org.junit.jupiter.api.Disabled; +import io.nats.client.utils.VersionUtils; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -29,105 +32,96 @@ public class JetStreamMirrorAndSourcesTests extends JetStreamTestBase { @Test public void testMirrorBasics() throws Exception { - String S1 = stream(); - String U1 = subject(); - String U2 = subject(); - String U3 = subject(); - String M1 = mirror(); - - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - + String S1 = random(); + String S2 = random(); + String S3 = random(); + String S4 = random(); + String U1 = random(); + String U2 = random(); + String U3 = random(); + String M1 = random(); + + runInSharedCustom((nc, ctx) -> { Mirror mirror = Mirror.builder().sourceName(S1).build(); // Create source stream - StreamConfiguration sc = StreamConfiguration.builder() - .name(S1) - .storageType(StorageType.Memory) - .subjects(U1, U2, U3) - .build(); - StreamInfo si = jsm.addStream(sc); + StreamConfiguration sc = ctx.scBuilder(U1, U2, U3).name(S1).build(); + StreamInfo si = ctx.addStream(sc); sc = si.getConfiguration(); assertNotNull(sc); assertEquals(S1, sc.getName()); // Now create our mirror stream. - sc = StreamConfiguration.builder() - .name(M1) - .storageType(StorageType.Memory) - .mirror(mirror) - .build(); - jsm.addStream(sc); - assertMirror(jsm, M1, S1, null, null); + sc = ctx.scBuilder() + .name(M1) + .subjects() // scBuilder added subjects + .mirror(mirror) + .build(); + ctx.addStream(sc); + assertMirror(ctx.jsm, M1, S1, null, null); // Send 100 messages. - jsPublish(js, U2, 100); + jsPublish(ctx.js, U2, 100); // Check the state - assertMirror(jsm, M1, S1, 100L, null); + assertMirror(ctx.jsm, M1, S1, 100L, null); // Purge the source stream. - jsm.purgeStream(S1); + ctx.jsm.purgeStream(S1); - jsPublish(js, U2, 50); + jsPublish(ctx.js, U2, 50); // Create second mirror - sc = StreamConfiguration.builder() - .name(mirror(2)) - .storageType(StorageType.Memory) - .mirror(mirror) - .build(); - jsm.addStream(sc); + sc = ctx.scBuilder() + .name(S2) + .subjects() // scBuilder added subjects + .mirror(mirror) + .build(); + ctx.createOrReplaceStream(sc); // Check the state - assertMirror(jsm, mirror(2), S1, 50L, 101L); + assertMirror(ctx.jsm, S2, S1, 50L, 101L); - jsPublish(js, U3, 100); + jsPublish(ctx.js, U3, 100); // third mirror checks start seq - sc = StreamConfiguration.builder() - .name(mirror(3)) - .storageType(StorageType.Memory) - .mirror(Mirror.builder().sourceName(S1).startSeq(150).build()) - .build(); - jsm.addStream(sc); + sc = ctx.scBuilder() + .name(S3) + .subjects() // scBuilder added subjects + .mirror(Mirror.builder().sourceName(S1).startSeq(150).build()) + .build(); + ctx.createOrReplaceStream(sc); // Check the state - assertMirror(jsm, mirror(3), S1, 101L, 150L); + assertMirror(ctx.jsm, S3, S1, 101L, 150L); // third mirror checks start seq ZonedDateTime zdt = DateTimeUtils.fromNow(Duration.ofHours(-2)); - sc = StreamConfiguration.builder() - .name(mirror(4)) - .storageType(StorageType.Memory) - .mirror(Mirror.builder().sourceName(S1).startTime(zdt).build()) - .build(); - jsm.addStream(sc); + sc = ctx.scBuilder() + .name(S4) + .subjects() // scBuilder added subjects + .mirror(Mirror.builder().sourceName(S1).startTime(zdt).build()) + .build(); + ctx.createOrReplaceStream(sc); // Check the state - assertMirror(jsm, mirror(4), S1, 150L, 101L); + assertMirror(ctx.jsm, S4, S1, 150L, 101L); }); } @Test public void testMirrorReading() throws Exception { - String S1 = stream(); - String U1 = subject(); - String U2 = subject(); - String M1 = mirror(); - - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); + String S1 = random(); + String U1 = random(); + String U2 = random(); + String M1 = random(); + runInSharedCustom((nc, ctx) -> { // Create source stream - StreamConfiguration sc = StreamConfiguration.builder() + StreamConfiguration sc = ctx.scBuilder(U1, U2) .name(S1) - .storageType(StorageType.Memory) - .subjects(U1, U2) .build(); - StreamInfo si = jsm.addStream(sc); + StreamInfo si = ctx.createOrReplaceStream(sc); sc = si.getConfiguration(); assertNotNull(sc); assertEquals(S1, sc.getName()); @@ -135,28 +129,28 @@ public void testMirrorReading() throws Exception { Mirror mirror = Mirror.builder().sourceName(S1).build(); // Now create our mirror stream. - sc = StreamConfiguration.builder() - .name(M1) - .storageType(StorageType.Memory) - .mirror(mirror) - .build(); - jsm.addStream(sc); - assertMirror(jsm, M1, S1, null, null); + sc = ctx.scBuilder() + .name(M1) + .subjects() // scBuilder added subjects + .mirror(mirror) + .build(); + ctx.addStream(sc); + assertMirror(ctx.jsm, M1, S1, null, null); // Send messages. - jsPublish(js, U1, 10); - jsPublish(js, U2, 20); + jsPublish(ctx.js, U1, 10); + jsPublish(ctx.js, U2, 20); - assertMirror(jsm, M1, S1, 30L, null); + assertMirror(ctx.jsm, M1, S1, 30L, null); - JetStreamSubscription sub = js.subscribe(U1); + JetStreamSubscription sub = ctx.js.subscribe(U1); List list = readMessagesAck(sub); assertEquals(10, list.size()); for (Message m : list) { assertEquals(S1, m.metaData().getStream()); } - sub = js.subscribe(U2); + sub = ctx.js.subscribe(U2); list = readMessagesAck(sub); assertEquals(20, list.size()); for (Message m : list) { @@ -166,14 +160,14 @@ public void testMirrorReading() throws Exception { //noinspection deprecation PushSubscribeOptions.bind(M1); // coverage for deprecated PushSubscribeOptions pso = PushSubscribeOptions.stream(M1); - sub = js.subscribe(U1, pso); + sub = ctx.js.subscribe(U1, pso); list = readMessagesAck(sub); assertEquals(10, list.size()); for (Message m : list) { assertEquals(M1, m.metaData().getStream()); } - sub = js.subscribe(U2, pso); + sub = ctx.js.subscribe(U2, pso); list = readMessagesAck(sub); assertEquals(20, list.size()); for (Message m : list) { @@ -184,58 +178,52 @@ public void testMirrorReading() throws Exception { @Test public void testMirrorExceptions() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - Mirror mirror = Mirror.builder().sourceName(STREAM).build(); - + runInSharedCustom((nc, ctx) -> { + Mirror mirror = Mirror.builder().sourceName(random()).build(); StreamConfiguration scEx = StreamConfiguration.builder() - .name(mirror()) - .subjects(subject()) + .name(random()) + .subjects(random()) .mirror(mirror) .build(); - assertThrows(JetStreamApiException.class, () -> jsm.addStream(scEx)); + assertThrows(JetStreamApiException.class, () -> ctx.createOrReplaceStream(scEx)); }); } @Test public void testSourceBasics() throws Exception { - String S1 = stream(); - String S2 = stream(); - String S3 = stream(); - String S4 = stream(); - String S5 = stream(); - String S99 = stream(); - String R1 = source(); - String R2 = source(); - - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - + String S1 = random(); + String S2 = random(); + String S3 = random(); + String S4 = random(); + String S5 = random(); + String S99 = random(); + String R1 = random(); + String R2 = random(); + + runInSharedCustom((nc, ctx) -> { // Create streams - StreamInfo si = jsm.addStream(StreamConfiguration.builder() + StreamInfo si = ctx.addStream(StreamConfiguration.builder() .name(S1).storageType(StorageType.Memory).build()); StreamConfiguration sc = si.getConfiguration(); assertNotNull(sc); assertEquals(S1, sc.getName()); - si = jsm.addStream(StreamConfiguration.builder() + si = ctx.addStream(StreamConfiguration.builder() .name(S2).storageType(StorageType.Memory).build()); sc = si.getConfiguration(); assertNotNull(sc); assertEquals(S2, sc.getName()); - si = jsm.addStream(StreamConfiguration.builder() + si = ctx.addStream(StreamConfiguration.builder() .name(S3).storageType(StorageType.Memory).build()); sc = si.getConfiguration(); assertNotNull(sc); assertEquals(S3, sc.getName()); // Populate each one. - jsPublish(js, S1, 10); - jsPublish(js, S2, 15); - jsPublish(js, S3, 25); + jsPublish(ctx.js, S1, 10); + jsPublish(ctx.js, S2, 15); + jsPublish(ctx.js, S3, 25); sc = StreamConfiguration.builder() .name(R1) @@ -245,9 +233,9 @@ public void testSourceBasics() throws Exception { Source.builder().sourceName(S3).build()) .build(); - jsm.addStream(sc); + ctx.addStream(sc); - assertSource(jsm, R1, 50L, null); + assertSource(ctx.jsm, R1, 50L, null); sc = StreamConfiguration.builder() .name(R1) @@ -257,91 +245,88 @@ public void testSourceBasics() throws Exception { Source.builder().sourceName(S4).build()) .build(); - jsm.updateStream(sc); + ctx.jsm.updateStream(sc); sc = StreamConfiguration.builder() .name(S99) .storageType(StorageType.Memory) .subjects(S4, S5) .build(); - jsm.addStream(sc); + ctx.addStream(sc); - jsPublish(js, S4, 20); - jsPublish(js, S5, 20); - jsPublish(js, S4, 10); + jsPublish(ctx.js, S4, 20); + jsPublish(ctx.js, S5, 20); + jsPublish(ctx.js, S4, 10); sc = StreamConfiguration.builder() .name(R2) .storageType(StorageType.Memory) .sources(Source.builder().sourceName(S99).startSeq(26).build()) .build(); - jsm.addStream(sc); - assertSource(jsm, R2, 25L, null); + ctx.addStream(sc); + assertSource(ctx.jsm, R2, 25L, null); - MessageInfo info = jsm.getMessage(R2, 1); + MessageInfo info = ctx.jsm.getMessage(R2, 1); assertStreamSource(info, S99, 26); + String source3 = random(); sc = StreamConfiguration.builder() - .name(source(3)) + .name(source3) .storageType(StorageType.Memory) .sources(Source.builder().sourceName(S99).startSeq(11).filterSubject(S4).build()) .build(); - jsm.addStream(sc); - assertSource(jsm, source(3), 20L, null); + ctx.addStream(sc); + assertSource(ctx.jsm, source3, 20L, null); - info = jsm.getMessage(source(3), 1); + info = ctx.jsm.getMessage(source3, 1); assertStreamSource(info, S99, 11); }); } @Test - @Disabled("This used to work.") public void testSourceAndTransformsRoundTrips() throws Exception { - jsServer.run(si -> atLeast2_10(), nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - StreamConfiguration scSource = StreamConfigurationTests.getStreamConfigurationFromJson( + runInOwnJsServer(VersionUtils::atLeast2_10, (nc, jsm, js) -> { + StreamConfiguration sc = StreamConfigurationTests.getStreamConfigurationFromJson( "StreamConfigurationSourcedSubjectTransform.json"); - StreamInfo si = jsm.addStream(scSource); - assertNull(scSource.getMirror()); - assertNull(si.getMirrorInfo()); + assertNotNull(sc.getSources()); + assertNull(sc.getMirror()); - assertNotNull(scSource.getSources()); - assertNotNull(si.getSourceInfos()); - Source source = scSource.getSources().get(0); - SourceInfo info = si.getSourceInfos().get(0); - assertNotNull(info); - assertNotNull(info.getSubjectTransforms()); - assertEquals(1, info.getSubjectTransforms().size()); - - assertEquals(source.getName(), info.getName()); - assertNotNull(source.getSubjectTransforms()); - assertEquals(1, source.getSubjectTransforms().size()); - - SubjectTransform st = source.getSubjectTransforms().get(0); - SubjectTransform infoSt = info.getSubjectTransforms().get(0); - assertEquals(st.getSource(), infoSt.getSource()); - assertEquals(st.getDestination(), infoSt.getDestination()); - - source = scSource.getSources().get(1); - info = si.getSourceInfos().get(1); - assertNotNull(scSource.getSources()); + Source sourceFoo = sc.getSources().get(0); + Source sourceBar = sc.getSources().get(1); + + StreamInfo si = jsm.addStream(sc); assertNotNull(si.getSourceInfos()); - assertEquals(source.getName(), info.getName()); - assertNotNull(info.getSubjectTransforms()); - assertEquals(1, info.getSubjectTransforms().size()); - assertNotNull(source.getSubjectTransforms()); - st = source.getSubjectTransforms().get(0); - infoSt = info.getSubjectTransforms().get(0); - assertEquals(st.getSource(), infoSt.getSource()); - assertEquals(st.getDestination(), infoSt.getDestination()); + assertNull(si.getMirrorInfo()); + + for (SourceInfo info : si.getSourceInfos()) { + assertNotNull(info); + assertNotNull(info.getSubjectTransforms()); + assertEquals(1, info.getSubjectTransforms().size()); + + String which = info.getName().substring(7); // sourcedfoo --> foo sourcebar --> bar + Source source; + if (which.equals("foo")) { + source = sourceFoo; + } + else { + source = sourceBar; + } + assertEquals(source.getName(), info.getName()); + assertNotNull(source.getSubjectTransforms()); + assertEquals(1, source.getSubjectTransforms().size()); + + SubjectTransform st = source.getSubjectTransforms().get(0); + SubjectTransform infoSt = info.getSubjectTransforms().get(0); + assertEquals(st.getSource(), infoSt.getSource()); + assertEquals(st.getDestination(), infoSt.getDestination()); + } }); } @Test public void testMirror() throws Exception { - jsServer.run(si -> atLeast2_10(), nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); + runInOwnJsServer(VersionUtils::atLeast2_10, (nc, jsm, js) -> { StreamConfiguration scMirror = StreamConfigurationTests.getStreamConfigurationFromJson( "StreamConfigurationMirrorSubjectTransform.json"); diff --git a/src/test/java/io/nats/client/impl/JetStreamPubTests.java b/src/test/java/io/nats/client/impl/JetStreamPubTests.java index 52f0a89e7..c42078735 100644 --- a/src/test/java/io/nats/client/impl/JetStreamPubTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamPubTests.java @@ -28,55 +28,54 @@ import static io.nats.client.support.NatsJetStreamConstants.MSG_TTL_HDR; import static io.nats.client.support.NatsJetStreamConstants.NATS_MARKER_REASON_HDR; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; +import static io.nats.client.utils.VersionUtils.atLeast2_12; import static org.junit.jupiter.api.Assertions.*; public class JetStreamPubTests extends JetStreamTestBase { @Test public void testPublishVarieties() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); + runInShared((nc, ctx) -> { + PublishAck pa = ctx.js.publish(ctx.subject(), dataBytes(1)); + assertPublishAck(pa, ctx.stream, 1); - JetStream js = nc.jetStream(); - - PublishAck pa = js.publish(tsc.subject(), dataBytes(1)); - assertPublishAck(pa, tsc.stream, 1); - - Message msg = NatsMessage.builder().subject(tsc.subject()).data(dataBytes(2)).build(); - pa = js.publish(msg); - assertPublishAck(pa, tsc.stream, 2); + Message msg = NatsMessage.builder().subject(ctx.subject()).data(dataBytes(2)).build(); + pa = ctx.js.publish(msg); + assertPublishAck(pa, ctx.stream, 2); PublishOptions po = PublishOptions.builder().build(); - pa = js.publish(tsc.subject(), dataBytes(3), po); - assertPublishAck(pa, tsc.stream, 3); + pa = ctx.js.publish(ctx.subject(), dataBytes(3), po); + assertPublishAck(pa, ctx.stream, 3); - msg = NatsMessage.builder().subject(tsc.subject()).data(dataBytes(4)).build(); - pa = js.publish(msg, po); - assertPublishAck(pa, tsc.stream, 4); + msg = NatsMessage.builder().subject(ctx.subject()).data(dataBytes(4)).build(); + pa = ctx.js.publish(msg, po); + assertPublishAck(pa, ctx.stream, 4); - pa = js.publish(tsc.subject(), null); - assertPublishAck(pa, tsc.stream, 5); + pa = ctx.js.publish(ctx.subject(), null); + assertPublishAck(pa, ctx.stream, 5); - msg = NatsMessage.builder().subject(tsc.subject()).build(); - pa = js.publish(msg); - assertPublishAck(pa, tsc.stream, 6); + msg = NatsMessage.builder().subject(ctx.subject()).build(); + pa = ctx.js.publish(msg); + assertPublishAck(pa, ctx.stream, 6); - pa = js.publish(tsc.subject(), null, po); - assertPublishAck(pa, tsc.stream, 7); + pa = ctx.js.publish(ctx.subject(), null, po); + assertPublishAck(pa, ctx.stream, 7); - msg = NatsMessage.builder().subject(tsc.subject()).build(); - pa = js.publish(msg, po); - assertPublishAck(pa, tsc.stream, 8); + msg = NatsMessage.builder().subject(ctx.subject()).build(); + pa = ctx.js.publish(msg, po); + assertPublishAck(pa, ctx.stream, 8); Headers h = new Headers().put("foo", "bar9"); - pa = js.publish(tsc.subject(), h, dataBytes(9)); - assertPublishAck(pa, tsc.stream, 9); + pa = ctx.js.publish(ctx.subject(), h, dataBytes(9)); + assertPublishAck(pa, ctx.stream, 9); h = new Headers().put("foo", "bar10"); - pa = js.publish(tsc.subject(), h, dataBytes(10), po); - assertPublishAck(pa, tsc.stream, 10); + pa = ctx.js.publish(ctx.subject(), h, dataBytes(10), po); + assertPublishAck(pa, ctx.stream, 10); - Subscription s = js.subscribe(tsc.subject()); + Subscription s = ctx.js.subscribe(ctx.subject()); assertNextMessage(s, data(1), null); assertNextMessage(s, data(2), null); assertNextMessage(s, data(3), null); @@ -89,7 +88,7 @@ public void testPublishVarieties() throws Exception { assertNextMessage(s, data(10), "bar10"); // 503 - assertThrows(IOException.class, () -> js.publish(subject(999), null)); + assertThrows(IOException.class, () -> ctx.js.publish(random(), null)); }); } @@ -119,40 +118,37 @@ private void assertPublishAck(PublishAck pa, String stream, long seqno) { @Test public void testPublishAsyncVarieties() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { List> futures = new ArrayList<>(); - futures.add(js.publishAsync(tsc.subject(), dataBytes(1))); + futures.add(ctx.js.publishAsync(ctx.subject(), dataBytes(1))); - Message msg = NatsMessage.builder().subject(tsc.subject()).data(dataBytes(2)).build(); - futures.add(js.publishAsync(msg)); + Message msg = NatsMessage.builder().subject(ctx.subject()).data(dataBytes(2)).build(); + futures.add(ctx.js.publishAsync(msg)); PublishOptions po = PublishOptions.builder().build(); - futures.add(js.publishAsync(tsc.subject(), dataBytes(3), po)); + futures.add(ctx.js.publishAsync(ctx.subject(), dataBytes(3), po)); - msg = NatsMessage.builder().subject(tsc.subject()).data(dataBytes(4)).build(); - futures.add(js.publishAsync(msg, po)); + msg = NatsMessage.builder().subject(ctx.subject()).data(dataBytes(4)).build(); + futures.add(ctx.js.publishAsync(msg, po)); Headers h = new Headers().put("foo", "bar5"); - futures.add(js.publishAsync(tsc.subject(), h, dataBytes(5))); + futures.add(ctx.js.publishAsync(ctx.subject(), h, dataBytes(5))); h = new Headers().put("foo", "bar6"); - futures.add(js.publishAsync(tsc.subject(), h, dataBytes(6), po)); + futures.add(ctx.js.publishAsync(ctx.subject(), h, dataBytes(6), po)); sleep(100); // just make sure all the publish complete for (int i = 1; i <= 6; i++) { CompletableFuture future = futures.get(i-1); PublishAck pa = future.get(); - assertEquals(tsc.stream, pa.getStream()); + assertEquals(ctx.stream, pa.getStream()); assertFalse(pa.isDuplicate()); assertEquals(i, pa.getSeqno()); } - Subscription s = js.subscribe(tsc.subject()); + Subscription s = ctx.js.subscribe(ctx.subject()); for (int x = 1; x <= 6; x++) { Message m = s.nextMessage(DEFAULT_TIMEOUT); assertNotNull(m); @@ -164,50 +160,50 @@ public void testPublishAsyncVarieties() throws Exception { } } - assertFutureIOException(js.publishAsync(subject(999), null)); + assertFutureIOException(ctx.js.publishAsync(random(), null)); - msg = NatsMessage.builder().subject(subject(999)).build(); - assertFutureIOException(js.publishAsync(msg)); + msg = NatsMessage.builder().subject(random()).build(); + assertFutureIOException(ctx.js.publishAsync(msg)); PublishOptions pox1 = PublishOptions.builder().build(); - assertFutureIOException(js.publishAsync(subject(999), null, pox1)); + assertFutureIOException(ctx.js.publishAsync(random(), null, pox1)); - msg = NatsMessage.builder().subject(subject(999)).build(); - assertFutureIOException(js.publishAsync(msg, pox1)); + msg = NatsMessage.builder().subject(random()).build(); + assertFutureIOException(ctx.js.publishAsync(msg, pox1)); - PublishOptions pox2 = PublishOptions.builder().expectedLastMsgId(messageId(999)).build(); + PublishOptions pox2 = PublishOptions.builder().expectedLastMsgId(random()).build(); - assertFutureJetStreamApiException(js.publishAsync(tsc.subject(), null, pox2)); + assertFutureJetStreamApiException(ctx.js.publishAsync(ctx.subject(), null, pox2)); - msg = NatsMessage.builder().subject(tsc.subject()).build(); - assertFutureJetStreamApiException(js.publishAsync(msg, pox2)); + msg = NatsMessage.builder().subject(ctx.subject()).build(); + assertFutureJetStreamApiException(ctx.js.publishAsync(msg, pox2)); }); } @Test public void testMultithreadedPublishAsync() throws Exception { + //noinspection resource final ExecutorService executorService = Executors.newFixedThreadPool(3); try { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); + runInShared((nc, ctx) -> { final int messagesToPublish = 6; // create a new connection that does not have the inbox dispatcher set try (NatsConnection nc2 = new NatsConnection(nc.getOptions())){ nc2.connect(true); - JetStream js = nc2.jetStream(); + JetStream js2 = nc2.jetStream(); List>> futures = new ArrayList<>(); for (int i = 0; i < messagesToPublish; i++) { final Future> submitFuture = executorService.submit(() -> - js.publishAsync(tsc.subject(), dataBytes(1))); + js2.publishAsync(ctx.subject(), dataBytes(1))); futures.add(submitFuture); } // verify all messages were published for (int i = 0; i < messagesToPublish; i++) { CompletableFuture future = futures.get(i).get(200, TimeUnit.MILLISECONDS); PublishAck pa = future.get(200, TimeUnit.MILLISECONDS); - assertEquals(tsc.stream, pa.getStream()); + assertEquals(ctx.stream, pa.getStream()); assertFalse(pa.isDuplicate()); } } @@ -231,201 +227,200 @@ private void assertFutureJetStreamApiException(CompletableFuture fut @Test public void testPublishExpectations() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - String subjectPrefix = variant(); - String streamSubject = subjectPrefix + ".>"; - String sub1 = subjectPrefix + ".foo.1"; - String sub2 = subjectPrefix + ".foo.2"; - String sub3 = subjectPrefix + ".bar.3"; - - TestingStreamContainer tsc = new TestingStreamContainer(nc, streamSubject); - String stream1 = tsc.stream; - createMemoryStream(jsm, stream1, streamSubject); - - PublishOptions po = PublishOptions.builder() - .expectedStream(tsc.stream) - .messageId(messageId(1)) - .build(); - PublishAck pa = js.publish(sub1, dataBytes(1), po); - assertPublishAck(pa, tsc.stream, 1); - - po = PublishOptions.builder() - .expectedLastMsgId(messageId(1)) - .messageId(messageId(2)) - .build(); - pa = js.publish(sub1, dataBytes(2), po); - assertPublishAck(pa, tsc.stream, 2); - - po = PublishOptions.builder() - .expectedLastSequence(2) - .messageId(messageId(3)) - .build(); - pa = js.publish(sub1, dataBytes(3), po); - assertPublishAck(pa, tsc.stream, 3); - - po = PublishOptions.builder() - .expectedLastSequence(3) - .messageId(messageId(4)) - .build(); - pa = js.publish(sub2, dataBytes(4), po); - assertPublishAck(pa, tsc.stream, 4); - - po = PublishOptions.builder() - .expectedLastSubjectSequence(3) - .messageId(messageId(5)) - .build(); - pa = js.publish(sub1, dataBytes(5), po); - assertPublishAck(pa, tsc.stream, 5); - - po = PublishOptions.builder() - .expectedLastSubjectSequence(4) - .messageId(messageId(6)) - .build(); - pa = js.publish(sub2, dataBytes(6), po); - assertPublishAck(pa, tsc.stream, 6); - - PublishOptions po1 = PublishOptions.builder().expectedStream(stream(999)).build(); - JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> js.publish(sub1, dataBytes(999), po1)); - assertEquals(10060, e.getApiErrorCode()); - - PublishOptions po2 = PublishOptions.builder().expectedLastMsgId(messageId(999)).build(); - e = assertThrows(JetStreamApiException.class, () -> js.publish(sub1, dataBytes(999), po2)); - assertEquals(10070, e.getApiErrorCode()); - - PublishOptions po3 = PublishOptions.builder().expectedLastSequence(999).build(); - e = assertThrows(JetStreamApiException.class, () -> js.publish(sub1, dataBytes(999), po3)); - assertEquals(10071, e.getApiErrorCode()); - - PublishOptions po4 = PublishOptions.builder().expectedLastSubjectSequence(999).build(); - e = assertThrows(JetStreamApiException.class, () -> js.publish(sub1, dataBytes(999), po4)); - assertEquals(10071, e.getApiErrorCode()); - - // 0 has meaning to expectedLastSubjectSequence - tsc = new TestingStreamContainer(nc); - createMemoryStream(jsm, tsc.stream, tsc.subject()); - PublishOptions poLss = PublishOptions.builder().expectedLastSubjectSequence(0).build(); - pa = js.publish(tsc.subject(), dataBytes(22), poLss); - assertPublishAck(pa, tsc.stream, 1); - - final String fSubject = tsc.subject(); - e = assertThrows(JetStreamApiException.class, () -> js.publish(fSubject, dataBytes(999), poLss)); - assertEquals(10071, e.getApiErrorCode()); - - // 0 has meaning - tsc = new TestingStreamContainer(nc); - PublishOptions poLs = PublishOptions.builder().expectedLastSequence(0).build(); - pa = js.publish(tsc.subject(), dataBytes(331), poLs); - assertPublishAck(pa, tsc.stream, 1); - - tsc = new TestingStreamContainer(nc); - poLs = PublishOptions.builder().expectedLastSubjectSequence(0).build(); - pa = js.publish(tsc.subject(), dataBytes(441), poLs); - assertPublishAck(pa, tsc.stream, 1); - - // expectedLastSubjectSequenceSubject - - pa = js.publish(sub3, dataBytes(500)); - assertPublishAck(pa, stream1, 7); - - PublishOptions poLsss = PublishOptions.builder() - .expectedLastSubjectSequence(5) - .build(); - pa = js.publish(sub1, dataBytes(501), poLsss); - assertPublishAck(pa, stream1, 8); - - poLsss = PublishOptions.builder() - .expectedLastSubjectSequence(6) - .build(); - pa = js.publish(sub2, dataBytes(502), poLsss); - assertPublishAck(pa, stream1, 9); - - poLsss = PublishOptions.builder() - .expectedLastSubjectSequence(9) - .expectedLastSubjectSequenceSubject(streamSubject) - .build(); - pa = js.publish(sub2, dataBytes(503), poLsss); - assertPublishAck(pa, stream1, 10); - - poLsss = PublishOptions.builder() - .expectedLastSubjectSequence(10) - .expectedLastSubjectSequenceSubject(subjectPrefix + ".foo.*") - .build(); - pa = js.publish(sub2, dataBytes(504), poLsss); - assertPublishAck(pa, stream1, 11); - - PublishOptions final1 = poLsss; - assertThrows(JetStreamApiException.class, () -> js.publish(sub2, dataBytes(505), final1)); - - poLsss = PublishOptions.builder() - .expectedLastSubjectSequence(7) - .expectedLastSubjectSequenceSubject(subjectPrefix + ".bar.*") - .build(); - pa = js.publish(sub3, dataBytes(506), poLsss); - assertPublishAck(pa, stream1, 12); - - poLsss = PublishOptions.builder() - .expectedLastSubjectSequence(12) - .expectedLastSubjectSequenceSubject(streamSubject) - .build(); - pa = js.publish(sub3, dataBytes(507), poLsss); - assertPublishAck(pa, stream1, 13); + runInSharedCustom((nc, jstc1) -> { + try (JetStreamTestingContext ctx2 = new JetStreamTestingContext(nc, 1); + JetStreamTestingContext ctx3 = new JetStreamTestingContext(nc, 1); + JetStreamTestingContext ctx4 = new JetStreamTestingContext(nc, 1) + ) { + String stream1 = jstc1.stream; + String subjectPrefix = random(); + String streamSubject = subjectPrefix + ".>"; + String sub1 = subjectPrefix + ".foo.1"; + String sub2 = subjectPrefix + ".foo.2"; + String sub3 = subjectPrefix + ".bar.3"; + jstc1.createOrReplaceStream(streamSubject); + + String mid = random(); + PublishOptions po = PublishOptions.builder() + .expectedStream(stream1) + .messageId(mid) + .build(); + PublishAck pa = jstc1.js.publish(sub1, dataBytes(1), po); + assertPublishAck(pa, stream1, 1); + + String lastId = mid; + mid = random(); + po = PublishOptions.builder() + .expectedLastMsgId(lastId) + .messageId(mid) + .build(); + pa = jstc1.js.publish(sub1, dataBytes(2), po); + assertPublishAck(pa, stream1, 2); + + mid = random(); + po = PublishOptions.builder() + .expectedLastSequence(2) + .messageId(mid) + .build(); + pa = jstc1.js.publish(sub1, dataBytes(3), po); + assertPublishAck(pa, stream1, 3); + + mid = random(); + po = PublishOptions.builder() + .expectedLastSequence(3) + .messageId(mid) + .build(); + pa = jstc1.js.publish(sub2, dataBytes(4), po); + assertPublishAck(pa, stream1, 4); + + mid = random(); + po = PublishOptions.builder() + .expectedLastSubjectSequence(3) + .messageId(mid) + .build(); + pa = jstc1.js.publish(sub1, dataBytes(5), po); + assertPublishAck(pa, stream1, 5); + + mid = random(); + po = PublishOptions.builder() + .expectedLastSubjectSequence(4) + .messageId(mid) + .build(); + pa = jstc1.js.publish(sub2, dataBytes(6), po); + assertPublishAck(pa, stream1, 6); + + PublishOptions po1 = PublishOptions.builder().expectedStream(random()).build(); + JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> jstc1.js.publish(sub1, dataBytes(), po1)); + assertEquals(10060, e.getApiErrorCode()); + + PublishOptions po2 = PublishOptions.builder().expectedLastMsgId(random()).build(); + e = assertThrows(JetStreamApiException.class, () -> jstc1.js.publish(sub1, dataBytes(), po2)); + assertEquals(10070, e.getApiErrorCode()); + + PublishOptions po3 = PublishOptions.builder().expectedLastSequence(999).build(); + e = assertThrows(JetStreamApiException.class, () -> jstc1.js.publish(sub1, dataBytes(), po3)); + assertEquals(10071, e.getApiErrorCode()); + + PublishOptions po4 = PublishOptions.builder().expectedLastSubjectSequence(999).build(); + e = assertThrows(JetStreamApiException.class, () -> jstc1.js.publish(sub1, dataBytes(), po4)); + assertEquals(10071, e.getApiErrorCode()); + + // 0 has meaning to expectedLastSubjectSequence + PublishOptions poLss = PublishOptions.builder().expectedLastSubjectSequence(0).build(); + pa = ctx2.js.publish(ctx2.subject(), dataBytes(22), poLss); + assertPublishAck(pa, ctx2.stream, 1); + + final String fSubject = ctx2.subject(); + e = assertThrows(JetStreamApiException.class, () -> ctx2.js.publish(fSubject, dataBytes(), poLss)); + assertEquals(10071, e.getApiErrorCode()); + + // 0 has meaning + PublishOptions poLs = PublishOptions.builder().expectedLastSequence(0).build(); + pa = ctx3.js.publish(ctx3.subject(), dataBytes(331), poLs); + assertPublishAck(pa, ctx3.stream, 1); + + poLs = PublishOptions.builder().expectedLastSubjectSequence(0).build(); + pa = ctx4.js.publish(ctx4.subject(), dataBytes(441), poLs); + assertPublishAck(pa, ctx4.stream, 1); + + // expectedLastSubjectSequenceSubject + pa = ctx4.js.publish(sub3, dataBytes(500)); + assertPublishAck(pa, stream1, 7); + + PublishOptions poLsss = PublishOptions.builder() + .expectedLastSubjectSequence(5) + .build(); + pa = ctx4.js.publish(sub1, dataBytes(501), poLsss); + assertPublishAck(pa, stream1, 8); + + poLsss = PublishOptions.builder() + .expectedLastSubjectSequence(6) + .build(); + pa = ctx4.js.publish(sub2, dataBytes(502), poLsss); + assertPublishAck(pa, stream1, 9); + + poLsss = PublishOptions.builder() + .expectedLastSubjectSequence(9) + .expectedLastSubjectSequenceSubject(streamSubject) + .build(); + pa = ctx4.js.publish(sub2, dataBytes(503), poLsss); + assertPublishAck(pa, stream1, 10); + + poLsss = PublishOptions.builder() + .expectedLastSubjectSequence(10) + .expectedLastSubjectSequenceSubject(subjectPrefix + ".foo.*") + .build(); + pa = ctx4.js.publish(sub2, dataBytes(504), poLsss); + assertPublishAck(pa, stream1, 11); + + PublishOptions final1 = poLsss; + assertThrows(JetStreamApiException.class, () -> ctx4.js.publish(sub2, dataBytes(505), final1)); + + poLsss = PublishOptions.builder() + .expectedLastSubjectSequence(7) + .expectedLastSubjectSequenceSubject(subjectPrefix + ".bar.*") + .build(); + pa = ctx4.js.publish(sub3, dataBytes(506), poLsss); + assertPublishAck(pa, stream1, 12); + + poLsss = PublishOptions.builder() + .expectedLastSubjectSequence(12) + .expectedLastSubjectSequenceSubject(streamSubject) + .build(); + pa = ctx4.js.publish(sub3, dataBytes(507), poLsss); + assertPublishAck(pa, stream1, 13); + + poLsss = PublishOptions.builder() + .expectedLastSubjectSequenceSubject("not-even-a-subject") + .build(); + if (atLeast2_12()) { + PublishOptions fpoLsss = poLsss; + assertThrows(JetStreamApiException.class, () -> ctx4.js.publish(sub3, dataBytes(508), fpoLsss)); + } + else { + pa = ctx4.js.publish(sub3, dataBytes(508), poLsss); + assertPublishAck(pa, stream1, 14); + } - poLsss = PublishOptions.builder() - .expectedLastSubjectSequenceSubject("not-even-a-subject") - .build(); - if (atLeast2_12()) { - PublishOptions fpoLsss = poLsss; - assertThrows(JetStreamApiException.class, () -> js.publish(sub3, dataBytes(508), fpoLsss)); - } - else { - pa = js.publish(sub3, dataBytes(508), poLsss); - assertPublishAck(pa, stream1, 14); - } + poLsss = PublishOptions.builder() + .expectedLastSequence(14) + .expectedLastSubjectSequenceSubject("not-even-a-subject") + .build(); + if (atLeast2_12()) { + PublishOptions fpoLsss = poLsss; + assertThrows(JetStreamApiException.class, () -> ctx4.js.publish(sub3, dataBytes(509), fpoLsss)); + } + else { + pa = ctx4.js.publish(sub3, dataBytes(509), poLsss); + assertPublishAck(pa, stream1, 15); + } - poLsss = PublishOptions.builder() - .expectedLastSequence(14) - .expectedLastSubjectSequenceSubject("not-even-a-subject") - .build(); - if (atLeast2_12()) { - PublishOptions fpoLsss = poLsss; - assertThrows(JetStreamApiException.class, () -> js.publish(sub3, dataBytes(509), fpoLsss)); + poLsss = PublishOptions.builder() + .expectedLastSubjectSequence(15) + .expectedLastSubjectSequenceSubject("not-even-a-subject") + .build(); + PublishOptions final2 = poLsss; + // JetStreamApiException: wrong last sequence: 0 [10071] + assertThrows(JetStreamApiException.class, () -> ctx4.js.publish(sub3, dataBytes(510), final2)); } - else { - pa = js.publish(sub3, dataBytes(509), poLsss); - assertPublishAck(pa, stream1, 15); - } - - poLsss = PublishOptions.builder() - .expectedLastSubjectSequence(15) - .expectedLastSubjectSequenceSubject("not-even-a-subject") - .build(); - PublishOptions final2 = poLsss; - // JetStreamApiException: wrong last sequence: 0 [10071] - assertThrows(JetStreamApiException.class, () -> js.publish(sub3, dataBytes(510), final2)); }); } @Test public void testPublishMiscExceptions() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { // stream supplied and matches //noinspection deprecation - PublishOptions po = PublishOptions.builder().stream(tsc.stream).build(); - js.publish(tsc.subject(), dataBytes(9), po); + PublishOptions po = PublishOptions.builder().stream(ctx.stream).build(); + ctx.js.publish(ctx.subject(), dataBytes(9), po); // mismatch stream to PO stream //noinspection deprecation - PublishOptions pox = PublishOptions.builder().stream(stream()).build(); - assertThrows(IOException.class, () -> js.publish(tsc.subject(), dataBytes(99), pox)); + PublishOptions pox = PublishOptions.builder().stream(random()).build(); + assertThrows(IOException.class, () -> ctx.js.publish(ctx.subject(), dataBytes(), pox)); // invalid subject - assertThrows(IOException.class, () -> js.publish(subject(), dataBytes(999))); + assertThrows(IOException.class, () -> ctx.js.publish(random(), dataBytes())); }); } @@ -441,22 +436,20 @@ public void testPublishAckJson() throws IOException, JetStreamApiException { @Test public void testPublishNoAck() throws Exception { - jsServer.run(nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { JetStreamOptions jso = JetStreamOptions.builder().publishNoAck(true).build(); - JetStream js = nc.jetStream(jso); + JetStream customJs = nc.jetStream(jso); String data1 = "noackdata1"; String data2 = "noackdata2"; - PublishAck pa = js.publish(tsc.subject(), data1.getBytes()); + PublishAck pa = customJs.publish(ctx.subject(), data1.getBytes()); assertNull(pa); - CompletableFuture f = js.publishAsync(tsc.subject(), data2.getBytes()); + CompletableFuture f = customJs.publishAsync(ctx.subject(), data2.getBytes()); assertNull(f); - JetStreamSubscription sub = js.subscribe(tsc.subject()); + JetStreamSubscription sub = customJs.subscribe(ctx.subject()); Message m = sub.nextMessage(Duration.ofSeconds(2)); assertNotNull(m); assertEquals(data1, new String(m.getData())); @@ -468,140 +461,111 @@ public void testPublishNoAck() throws Exception { @Test public void testMaxPayloadJs() throws Exception { - String streamName = "stream-max-payload-test"; - String subject1 = "mptest1"; - String subject2 = "mptest2"; - - try (NatsTestServer ts = new NatsTestServer(false, true)) - { - Options options = standardOptionsBuilder().noReconnect().server(ts.getURI()).build(); + runInSharedCustom(optionsBuilder().noReconnect(), (nc, ctx) -> { long expectedSeq = 0; - try (Connection nc = standardConnection(options)){ - JetStreamManagement jsm = nc.jetStreamManagement(); - try { jsm.deleteStream(streamName); } catch (JetStreamApiException ignore) {} - jsm.addStream(StreamConfiguration.builder() - .name(streamName) - .storageType(StorageType.Memory) - .subjects(subject1, subject2) - .maximumMessageSize(1000) - .build() - ); - - JetStream js = nc.jetStream(); - for (int x = 1; x <= 3; x++) + StreamConfiguration.Builder builder = ctx.scBuilder().maximumMessageSize(1000); + ctx.createOrReplaceStream(builder); + String subject0 = ctx.subject(0); + + for (int x = 1; x <= 3; x++) { + int size = 1000 + x - 2; + if (size > 1000) { + JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> ctx.js.publish(subject0, new byte[size])); + assertEquals(10054, e.getApiErrorCode()); + } + else { - int size = 1000 + x - 2; - if (size > 1000) - { - JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> js.publish(subject1, new byte[size])); - assertEquals(10054, e.getApiErrorCode()); - } - else - { - PublishAck pa = js.publish(subject1, new byte[size]); - assertEquals(++expectedSeq, pa.getSeqno()); - } + PublishAck pa = ctx.js.publish(subject0, new byte[size]); + assertEquals(++expectedSeq, pa.getSeqno()); } } - try (Connection nc = standardConnection(options)){ - JetStream js = nc.jetStream(); - for (int x = 1; x <= 3; x++) + for (int x = 1; x <= 3; x++) { + int size = 1000 + x - 2; + CompletableFuture paFuture = ctx.js.publishAsync(subject0, new byte[size]); + if (size > 1000) { - int size = 1000 + x - 2; - CompletableFuture paFuture = js.publishAsync(subject1, new byte[size]); - if (size > 1000) - { - ExecutionException e = assertThrows(ExecutionException.class, () -> paFuture.get(1000, TimeUnit.MILLISECONDS)); - JetStreamApiException j = (JetStreamApiException)e.getCause().getCause(); - assertEquals(10054, j.getApiErrorCode()); - } - else - { - PublishAck pa = paFuture.get(1000, TimeUnit.MILLISECONDS); - assertEquals(++expectedSeq, pa.getSeqno()); - } + ExecutionException e = assertThrows(ExecutionException.class, () -> paFuture.get(1000, TimeUnit.MILLISECONDS)); + JetStreamApiException j = (JetStreamApiException)e.getCause().getCause(); + assertEquals(10054, j.getApiErrorCode()); + } + else + { + PublishAck pa = paFuture.get(1000, TimeUnit.MILLISECONDS); + assertEquals(++expectedSeq, pa.getSeqno()); } } - } + }); } @Test public void testPublishWithTTL() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - String stream = stream(); - String subject = subject(); - StreamConfiguration sc = StreamConfiguration.builder() - .name(stream) - .storageType(StorageType.Memory) - .allowMessageTtl() - .subjects(subject).build(); + runInShared((nc, ctx) -> { + StreamConfiguration.Builder builder = ctx.scBuilder().allowMessageTtl(); + ctx.createOrReplaceStream(builder); - jsm.addStream(sc); + String stream = ctx.stream; + String subject = ctx.subject(); PublishOptions opts = PublishOptions.builder().messageTtlSeconds(1).build(); - PublishAck pa1 = js.publish(subject, null, opts); + PublishAck pa1 = ctx.js.publish(subject, null, opts); assertNotNull(pa1); opts = PublishOptions.builder().messageTtlNever().build(); - PublishAck paNever = js.publish(subject, null, opts); + PublishAck paNever = ctx.js.publish(subject, null, opts); assertNotNull(paNever); - MessageInfo mi1 = jsm.getMessage(stream, pa1.getSeqno()); - assertEquals("1s", mi1.getHeaders().getFirst(MSG_TTL_HDR)); + MessageInfo mi1 = ctx.jsm.getMessage(stream, pa1.getSeqno()); + Headers h = mi1.getHeaders(); + assertNotNull(h); + assertEquals("1s",h.getFirst(MSG_TTL_HDR)); - MessageInfo miNever = jsm.getMessage(stream, paNever.getSeqno()); - assertEquals("never", miNever.getHeaders().getFirst(MSG_TTL_HDR)); + MessageInfo miNever = ctx.jsm.getMessage(stream, paNever.getSeqno()); + h = miNever.getHeaders(); + assertNotNull(h); + assertEquals("never",h.getFirst(MSG_TTL_HDR)); sleep(1200); - JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> jsm.getMessage(stream, pa1.getSeqno())); + JetStreamApiException e = assertThrows(JetStreamApiException.class, () -> ctx.jsm.getMessage(stream, pa1.getSeqno())); assertEquals(10037, e.getApiErrorCode()); - assertNotNull((jsm.getMessage(stream, paNever.getSeqno()))); + assertNotNull((ctx.jsm.getMessage(stream, paNever.getSeqno()))); }); } @Test public void testMsgDeleteMarkerMaxAge() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - String stream = stream(); - String subject = subject(); - StreamConfiguration sc = StreamConfiguration.builder() - .name(stream) - .storageType(StorageType.Memory) + runInSharedCustom((nc, ctx) -> { + StreamConfiguration sc = ctx.scBuilder(1) .allowMessageTtl() .subjectDeleteMarkerTtl(Duration.ofSeconds(50)) .maxAge(1000) - .subjects(subject).build(); - - jsm.addStream(sc); + .build(); + ctx.createOrReplaceStream(sc); + String subject = ctx.subject(); PublishOptions opts = PublishOptions.builder().messageTtlSeconds(1).build(); - PublishAck pa = js.publish(subject, null, opts); + PublishAck pa = ctx.js.publish(subject, null, opts); assertNotNull(pa); sleep(1200); - MessageInfo mi = jsm.getLastMessage(stream, subject); - assertEquals("MaxAge", mi.getHeaders().getFirst(NATS_MARKER_REASON_HDR)); - assertEquals("50s", mi.getHeaders().getFirst(MSG_TTL_HDR)); + MessageInfo mi = ctx.jsm.getLastMessage(ctx.stream, subject); + Headers h = mi.getHeaders(); + assertNotNull(h); + assertEquals("MaxAge", h.getFirst(NATS_MARKER_REASON_HDR)); + assertEquals("50s", h.getFirst(MSG_TTL_HDR)); assertThrows(IllegalArgumentException.class, () -> StreamConfiguration.builder() - .name(stream) + .name(ctx.stream) .storageType(StorageType.Memory) .allowMessageTtl() .subjectDeleteMarkerTtl(Duration.ofMillis(999)) .subjects(subject).build()); assertThrows(IllegalArgumentException.class, () -> StreamConfiguration.builder() - .name(stream) + .name(ctx.stream) .storageType(StorageType.Memory) .allowMessageTtl() .subjectDeleteMarkerTtl(999) diff --git a/src/test/java/io/nats/client/impl/JetStreamPullTests.java b/src/test/java/io/nats/client/impl/JetStreamPullTests.java index 1a0d4ff84..0ee080e96 100644 --- a/src/test/java/io/nats/client/impl/JetStreamPullTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamPullTests.java @@ -18,11 +18,14 @@ import io.nats.client.api.ConsumerConfiguration; import io.nats.client.api.PriorityPolicy; import io.nats.client.support.JsonUtils; -import io.nats.client.support.Status; -import io.nats.client.utils.TestBase; +import io.nats.client.support.Listener; +import io.nats.client.support.ListenerStatusType; +import io.nats.client.utils.ConnectionUtils; +import io.nats.client.utils.VersionUtils; import org.jspecify.annotations.NonNull; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.io.IOException; import java.time.Duration; @@ -39,30 +42,36 @@ import static io.nats.client.api.ConsumerConfiguration.builder; import static io.nats.client.support.ApiConstants.*; +import static io.nats.client.support.ListenerStatusType.PullError; +import static io.nats.client.support.ListenerStatusType.PullWarning; import static io.nats.client.support.NatsJetStreamConstants.NATS_PIN_ID_HDR; import static io.nats.client.support.Status.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; +@Isolated public class JetStreamPullTests extends JetStreamTestBase { - static class ErrorListenerPullImpl extends ErrorListenerLoggerImpl { - @Override - public void pullStatusWarning(Connection conn, JetStreamSubscription sub, Status status) {} - } + static Connection conflictNc; + static Listener conflictListener; - private Options.Builder noPullWarnings() { - return Options.builder().errorListener(new ErrorListenerPullImpl()); + @AfterAll + public static void afterAll() { + testBaseAfterAll(); + if (conflictNc != null) { + try { + conflictNc.close(); + } + catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + } + } } @Test public void testFetch() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { long fetchMs = 3000; Duration fetchDur = Duration.ofMillis(fetchMs); Duration ackWaitDur = Duration.ofMillis(fetchMs * 2); @@ -72,12 +81,12 @@ public void testFetch() throws Exception { .build(); PullSubscribeOptions options = PullSubscribeOptions.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .configuration(cc) .build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), options); - assertSubscription(sub, tsc.stream, tsc.consumerName(), null, true); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), options); + assertSubscription(sub, ctx.stream, ctx.consumerName(), null, true); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server List messages = sub.fetch(10, fetchDur); @@ -85,12 +94,12 @@ public void testFetch() throws Exception { messages.forEach(Message::ack); sleep(ackWaitDur.toMillis()); // let the pull expire - jsPublish(js, tsc.subject(), "A", 10); + jsPublish(ctx.js, ctx.subject(), "A", 10); messages = sub.fetch(10, fetchDur); validateRead(10, messages.size()); messages.forEach(Message::ack); - jsPublish(js, tsc.subject(), "B", 20); + jsPublish(ctx.js, ctx.subject(), "B", 20); messages = sub.fetch(10, fetchDur); validateRead(10, messages.size()); messages.forEach(Message::ack); @@ -99,13 +108,13 @@ public void testFetch() throws Exception { validateRead(10, messages.size()); messages.forEach(Message::ack); - jsPublish(js, tsc.subject(), "C", 5); + jsPublish(ctx.js, ctx.subject(), "C", 5); messages = sub.fetch(10, fetchDur); validateRead(5, messages.size()); messages.forEach(Message::ack); sleep(fetchMs); // let the pull expire - jsPublish(js, tsc.subject(), "D", 15); + jsPublish(ctx.js, ctx.subject(), "D", 15); messages = sub.fetch(10, fetchDur); validateRead(10, messages.size()); messages.forEach(Message::ack); @@ -114,7 +123,7 @@ public void testFetch() throws Exception { validateRead(5, messages.size()); messages.forEach(Message::ack); - jsPublish(js, tsc.subject(), "E", 10); + jsPublish(ctx.js, ctx.subject(), "E", 10); messages = sub.fetch(10, fetchDur); validateRead(10, messages.size()); sleep(ackWaitDur.toMillis()); // let the acks wait expire, pull will also expire it's shorter @@ -131,13 +140,7 @@ public void testFetch() throws Exception { @Test public void testIterate() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { long fetchMs = 5000; Duration fetchDur = Duration.ofMillis(fetchMs); Duration ackWaitDur = Duration.ofMillis(fetchMs * 2); @@ -147,12 +150,12 @@ public void testIterate() throws Exception { .build(); PullSubscribeOptions options = PullSubscribeOptions.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .configuration(cc) .build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), options); - assertSubscription(sub, tsc.stream, tsc.consumerName(), null, true); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), options); + assertSubscription(sub, ctx.stream, ctx.consumerName(), null, true); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server Iterator iterator = sub.iterate(10, fetchDur); @@ -160,13 +163,13 @@ public void testIterate() throws Exception { validateRead(0, messages.size()); messages.forEach(Message::ack); - jsPublish(js, tsc.subject(), "A", 10); + jsPublish(ctx.js, ctx.subject(), "A", 10); iterator = sub.iterate(10, fetchDur); messages = readMessages(iterator); validateRead(10, messages.size()); messages.forEach(Message::ack); - jsPublish(js, tsc.subject(), "B", 20); + jsPublish(ctx.js, ctx.subject(), "B", 20); iterator = sub.iterate(10, fetchDur); messages = readMessages(iterator); validateRead(10, messages.size()); @@ -177,14 +180,14 @@ public void testIterate() throws Exception { validateRead(10, messages.size()); messages.forEach(Message::ack); - jsPublish(js, tsc.subject(), "C", 5); + jsPublish(ctx.js, ctx.subject(), "C", 5); iterator = sub.iterate(10, fetchDur); messages = readMessages(iterator); validateRead(5, messages.size()); messages.forEach(Message::ack); sleep(fetchMs); // give time for the pull to expire - jsPublish(js, tsc.subject(), "D", 15); + jsPublish(ctx.js, ctx.subject(), "D", 15); iterator = sub.iterate(10, fetchDur); messages = readMessages(iterator); validateRead(10, messages.size()); @@ -196,7 +199,7 @@ public void testIterate() throws Exception { messages.forEach(Message::ack); sleep(fetchMs); // give time for the pull to expire - jsPublish(js, tsc.subject(), "E", 10); + jsPublish(ctx.js, ctx.subject(), "E", 10); iterator = sub.iterate(10, fetchDur); messages = readMessages(iterator); validateRead(10, messages.size()); @@ -207,7 +210,7 @@ public void testIterate() throws Exception { validateRead(10, messages.size()); messages.forEach(Message::ack); - jsPublish(js, tsc.subject(), "F", 1); + jsPublish(ctx.js, ctx.subject(), "F", 1); iterator = sub.iterate(1, fetchDur); //noinspection ResultOfMethodCallIgnored iterator.hasNext(); // calling hasNext twice in a row is for coverage @@ -218,23 +221,17 @@ public void testIterate() throws Exception { @Test public void testBasic() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // Build our subscription options. - PullSubscribeOptions options = PullSubscribeOptions.builder().durable(tsc.consumerName()).build(); + PullSubscribeOptions options = PullSubscribeOptions.builder().durable(ctx.consumerName()).build(); // Subscribe synchronously. - JetStreamSubscription sub = js.subscribe(tsc.subject(), options); - assertSubscription(sub, tsc.stream, tsc.consumerName(), null, true); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), options); + assertSubscription(sub, ctx.stream, ctx.consumerName(), null, true); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // publish some amount of messages, but not entire pull size - jsPublish(js, tsc.subject(), "A", 4); + jsPublish(ctx.js, ctx.subject(), "A", 4); // start the pull sub.pull(10); @@ -245,7 +242,7 @@ public void testBasic() throws Exception { validateRedAndTotal(4, messages.size(), 4, total); // publish some more covering our initial pull and more - jsPublish(js, tsc.subject(), "B", 10); + jsPublish(ctx.js, ctx.subject(), "B", 10); // read what is available, expect 6 more messages = readMessagesAck(sub); @@ -266,7 +263,7 @@ public void testBasic() throws Exception { validateRedAndTotal(4, messages.size(), 14, total); // publish some more - jsPublish(js, tsc.subject(), "C", 10); + jsPublish(ctx.js, ctx.subject(), "C", 10); // read what is available, should be 6 since we didn't finish the last batch messages = readMessagesAck(sub); @@ -298,16 +295,16 @@ public void testBasic() throws Exception { validateRedAndTotal(0, messages.size(), 24, total); // publish some more to test null timeout - jsPublish(js, tsc.subject(), "D", 10); - sub = js.subscribe(tsc.subject(), PullSubscribeOptions.builder().durable(durable(2)).build()); + jsPublish(ctx.js, ctx.subject(), "D", 10); + sub = ctx.js.subscribe(ctx.subject(), PullSubscribeOptions.builder().durable(random()).build()); sub.pull(10); sleep(500); messages = readMessagesAck(sub, null); validateRedAndTotal(10, messages.size(), 10, messages.size()); // publish some more to test never timeout - jsPublish(js, tsc.subject(), "E", 10); - sub = js.subscribe(tsc.subject(), PullSubscribeOptions.builder().durable(durable(2)).build()); + jsPublish(ctx.js, ctx.subject(), "E", 10); + sub = ctx.js.subscribe(ctx.subject(), PullSubscribeOptions.builder().durable(random()).build()); sub.pull(10); sleep(500); messages = readMessagesAck(sub, Duration.ZERO, 10); @@ -317,24 +314,18 @@ public void testBasic() throws Exception { @Test public void testNoWait() throws Exception { - runInJsServer(noPullWarnings(), nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // Build our subscription options. - PullSubscribeOptions options = PullSubscribeOptions.builder().durable(tsc.consumerName()).build(); + PullSubscribeOptions options = PullSubscribeOptions.builder().durable(ctx.consumerName()).build(); // Subscribe synchronously. - JetStreamSubscription sub = js.subscribe(tsc.subject(), options); - assertSubscription(sub, tsc.stream, tsc.consumerName(), null, true); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), options); + assertSubscription(sub, ctx.stream, ctx.consumerName(), null, true); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // publish 10 messages // no wait, batch size 10, there are 10 messages, we will read them all and not trip nowait - jsPublish(js, tsc.subject(), "A", 10); + jsPublish(ctx.js, ctx.subject(), "A", 10); sub.pullNoWait(10); List messages = readMessagesAck(sub); assertEquals(10, messages.size()); @@ -342,7 +333,7 @@ public void testNoWait() throws Exception { // publish 20 messages // no wait, batch size 10, there are 20 messages, we will read 10 - jsPublish(js, tsc.subject(), "B", 20); + jsPublish(ctx.js, ctx.subject(), "B", 20); sub.pullNoWait(10); messages = readMessagesAck(sub); assertEquals(10, messages.size()); @@ -355,14 +346,14 @@ public void testNoWait() throws Exception { // publish 5 messages // no wait, batch size 10, there are 5 messages, we WILL trip nowait - jsPublish(js, tsc.subject(), "C", 5); + jsPublish(ctx.js, ctx.subject(), "C", 5); sub.pullNoWait(10); messages = readMessagesAck(sub); assertEquals(5, messages.size()); // publish 12 messages // no wait, batch size 10, there are more than batch messages we will read 10 - jsPublish(js, tsc.subject(), "D", 12); + jsPublish(ctx.js, ctx.subject(), "D", 12); sub.pullNoWait(10); messages = readMessagesAck(sub); assertEquals(10, messages.size()); @@ -376,7 +367,7 @@ public void testNoWait() throws Exception { // this is just coverage of the pullNoWait api + expires, not really validating server functionality // publish 12 messages // no wait, batch size 10, there are more than batch messages we will read 10 - jsPublish(js, tsc.subject(), "E", 12); + jsPublish(ctx.js, ctx.subject(), "E", 12); sub.pullNoWait(10, 10000); messages = readMessagesAck(sub); assertEquals(10, messages.size()); @@ -391,81 +382,75 @@ public void testNoWait() throws Exception { @Test public void testPullExpires() throws Exception { - runInJsServer(noPullWarnings(), nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // Build our subscription options. - PullSubscribeOptions options = PullSubscribeOptions.builder().durable(tsc.consumerName()).build(); + PullSubscribeOptions options = PullSubscribeOptions.builder().durable(ctx.consumerName()).build(); // Subscribe synchronously. - JetStreamSubscription sub = js.subscribe(tsc.subject(), options); - assertSubscription(sub, tsc.stream, tsc.consumerName(), null, true); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), options); + assertSubscription(sub, ctx.stream, ctx.consumerName(), null, true); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server long expires = 500; // millis // publish 10 messages - jsPublish(js, tsc.subject(), "A", 5); + jsPublish(ctx.js, ctx.subject(), "A", 5); sub.pullExpiresIn(10, Duration.ofMillis(expires)); // using Duration version here List messages = readMessagesAck(sub); assertEquals(5, messages.size()); assertAllJetStream(messages); sleep(expires); // make sure the pull actually expires - jsPublish(js, tsc.subject(), "B", 10); + jsPublish(ctx.js, ctx.subject(), "B", 10); sub.pullExpiresIn(10, Duration.ofMillis(expires)); // using Duration version here messages = readMessagesAck(sub); assertEquals(10, messages.size()); sleep(expires); // make sure the pull actually expires - jsPublish(js, tsc.subject(), "C", 5); + jsPublish(ctx.js, ctx.subject(), "C", 5); sub.pullExpiresIn(10, Duration.ofMillis(expires)); // using Duration version here messages = readMessagesAck(sub); assertEquals(5, messages.size()); assertAllJetStream(messages); sleep(expires); // make sure the pull actually expires - jsPublish(js, tsc.subject(), "D", 10); + jsPublish(ctx.js, ctx.subject(), "D", 10); sub.pull(10); messages = readMessagesAck(sub); assertEquals(10, messages.size()); - jsPublish(js, tsc.subject(), "E", 5); + jsPublish(ctx.js, ctx.subject(), "E", 5); sub.pullExpiresIn(10, expires); // using millis version here messages = readMessagesAck(sub); assertEquals(5, messages.size()); assertAllJetStream(messages); sleep(expires); // make sure the pull actually expires - jsPublish(js, tsc.subject(), "F", 10); + jsPublish(ctx.js, ctx.subject(), "F", 10); sub.pullNoWait(10); messages = readMessagesAck(sub); assertEquals(10, messages.size()); - jsPublish(js, tsc.subject(), "G", 5); + jsPublish(ctx.js, ctx.subject(), "G", 5); sub.pullExpiresIn(10, expires); // using millis version here messages = readMessagesAck(sub); assertEquals(5, messages.size()); assertAllJetStream(messages); sleep(expires); // make sure the pull actually expires - jsPublish(js, tsc.subject(), "H", 10); + jsPublish(ctx.js, ctx.subject(), "H", 10); messages = sub.fetch(10, expires); assertEquals(10, messages.size()); assertAllJetStream(messages); - jsPublish(js, tsc.subject(), "I", 5); + jsPublish(ctx.js, ctx.subject(), "I", 5); sub.pullExpiresIn(10, expires); messages = readMessagesAck(sub); assertEquals(5, messages.size()); assertAllJetStream(messages); sleep(expires); // make sure the pull actually expires - jsPublish(js, tsc.subject(), "J", 10); + jsPublish(ctx.js, ctx.subject(), "J", 10); Iterator i = sub.iterate(10, expires); int count = 0; while (i.hasNext()) { @@ -482,19 +467,13 @@ public void testPullExpires() throws Exception { @Test public void testAckNak() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - PullSubscribeOptions pso = PullSubscribeOptions.builder().durable(DURABLE).build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); + runInShared((nc, ctx) -> { + PullSubscribeOptions pso = PullSubscribeOptions.builder().durable(random()).build(); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), pso); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // NAK - jsPublish(js, tsc.subject(), "NAK", 1); + jsPublish(ctx.js, ctx.subject(), "NAK", 1); sub.pull(1); @@ -518,19 +497,13 @@ public void testAckNak() throws Exception { @Test public void testAckTerm() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - PullSubscribeOptions pso = PullSubscribeOptions.builder().durable(DURABLE).build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); + runInShared((nc, ctx) -> { + PullSubscribeOptions pso = PullSubscribeOptions.builder().durable(random()).build(); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), pso); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // TERM - jsPublish(js, tsc.subject(), "TERM", 1); + jsPublish(ctx.js, ctx.subject(), "TERM", 1); sub.pull(1); Message message = sub.nextMessage(Duration.ofSeconds(1)); @@ -546,51 +519,39 @@ public void testAckTerm() throws Exception { @Test public void testAckReplySyncCoverage() throws Exception { - jsServer.run(nc -> { - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - // Create our JetStream context. - JetStream js = nc.jetStream(); - - JetStreamSubscription sub = js.subscribe(tsc.subject()); + runInShared((nc, ctx) -> { + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject()); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server - jsPublish(js, tsc.subject(), "COVERAGE", 1); + jsPublish(ctx.js, ctx.subject(), "COVERAGE", 1); Message message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); - NatsJetStreamMessage njsm = (NatsJetStreamMessage)message; + NatsJetStreamMessage njsMsg = (NatsJetStreamMessage)message; - njsm.replyTo = "$JS.ACK.stream.LS0k4eeN.1.1.1.1627472530542070600.0"; + njsMsg.replyTo = "$tsc.js.ACK.stream.LS0k4eeN.1.1.1.1627472530542070600.0"; - assertThrows(TimeoutException.class, () -> njsm.ackSync(Duration.ofSeconds(1))); + assertThrows(TimeoutException.class, () -> njsMsg.ackSync(Duration.ofSeconds(1))); }); } @Test public void testAckWaitTimeout() throws Exception { - jsServer.run(nc -> { - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - // Create our JetStream context. - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { ConsumerConfiguration cc = ConsumerConfiguration.builder() .ackWait(1500) .build(); PullSubscribeOptions pso = PullSubscribeOptions.builder() - .durable(tsc.consumerName()) + .durable(ctx.consumerName()) .configuration(cc) .build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), pso); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // Ack Wait timeout - jsPublish(js, tsc.subject(), "WAIT", 2); + jsPublish(ctx.js, ctx.subject(), "WAIT", 2); sub.pull(2); Message m = sub.nextMessage(1000); @@ -622,81 +583,72 @@ public void testAckWaitTimeout() throws Exception { @Test public void testDurable() throws Exception { - runInJsServer(noPullWarnings(), nc -> { - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - String durable = durable(); - - // Create our JetStream context. - JetStream js = nc.jetStream(); + runInShared((nc, ctx) -> { + String durable = random(); // Build our subscription options normally PullSubscribeOptions options1 = PullSubscribeOptions.builder().durable(durable).build(); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(tsc.subject(), options1)); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(ctx.subject(), options1)); // bind long form PullSubscribeOptions options2 = PullSubscribeOptions.builder() - .stream(tsc.stream) + .stream(ctx.stream) .durable(durable) .bind(true) .build(); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options2)); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options2)); // fast bind long form PullSubscribeOptions options3 = PullSubscribeOptions.builder() - .stream(tsc.stream) + .stream(ctx.stream) .durable(durable) .fastBind(true) .build(); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options3)); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options3)); // bind short form - PullSubscribeOptions options4 = PullSubscribeOptions.bind(tsc.stream, durable); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options4)); + PullSubscribeOptions options4 = PullSubscribeOptions.bind(ctx.stream, durable); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options4)); // fast bind short form - PullSubscribeOptions options5 = PullSubscribeOptions.fastBind(tsc.stream, durable); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options5)); + PullSubscribeOptions options5 = PullSubscribeOptions.fastBind(ctx.stream, durable); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options5)); }); } @Test public void testNamed() throws Exception { - runInJsServer(noPullWarnings(), TestBase::atLeast2_9_0, nc -> { - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - String name = name(); + runInShared((nc, ctx) -> { + String name = random(); - jsm.addOrUpdateConsumer(tsc.stream, ConsumerConfiguration.builder() + ctx.jsm.addOrUpdateConsumer(ctx.stream, ConsumerConfiguration.builder() .name(name) .inactiveThreshold(10_000) .build()); // bind long form PullSubscribeOptions options2 = PullSubscribeOptions.builder() - .stream(tsc.stream) + .stream(ctx.stream) .name(name) .bind(true) .build(); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options2)); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options2)); // fast bind long form PullSubscribeOptions options3 = PullSubscribeOptions.builder() - .stream(tsc.stream) + .stream(ctx.stream) .name(name) .fastBind(true) .build(); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options3)); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options3)); // bind short form - PullSubscribeOptions options4 = PullSubscribeOptions.bind(tsc.stream, name); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options4)); + PullSubscribeOptions options4 = PullSubscribeOptions.bind(ctx.stream, name); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options4)); // fast bind short form - PullSubscribeOptions options5 = PullSubscribeOptions.fastBind(tsc.stream, name); - _testDurableOrNamed(js, tsc.subject(), () -> js.subscribe(null, options5)); + PullSubscribeOptions options5 = PullSubscribeOptions.fastBind(ctx.stream, name); + _testDurableOrNamed(ctx.js, ctx.subject(), () -> ctx.js.subscribe(null, options5)); }); } @@ -777,11 +729,7 @@ public void testPullRequestOptionsBuilder() { } interface ConflictSetup { - JetStreamSubscription setup(Connection nc, JetStreamManagement jsm, JetStream js, TestingStreamContainer tsc, ListenerForTesting listener) throws Exception; - } - - private boolean versionIsBefore(Connection nc, String targetVersion) { - return targetVersion != null && nc.getServerInfo().isOlderThanVersion(targetVersion); + JetStreamSubscription setup(Connection nc, JetStreamTestingContext ctx) throws Exception; } interface BuilderCustomizer { @@ -792,349 +740,365 @@ private PullSubscribeOptions makePso(BuilderCustomizer c) { return c.customize(ConsumerConfiguration.builder().ackPolicy(AckPolicy.None)).inactiveThreshold(INACTIVE_THRESHOLD).buildPullSubscribeOptions(); } - static final long NEXT_MESSAGE = 2500; - static final long WAIT_FOR_MESSAGES = 10_000; + static final long NEXT_MESSAGE_TIMEOUT = 5000; static final long INACTIVE_THRESHOLD = 30_000; - static final int TYPE_ERROR = 1; - static final int TYPE_WARNING = 2; - static final int TYPE_NONE = 0; - private void testConflictStatus(int statusCode, String statusText, int type, String targetVersion, ConflictSetup setup) throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - AtomicBoolean skip = new AtomicBoolean(false); - runInJsServer(listener, nc -> { - skip.set(versionIsBefore(nc, targetVersion)); - if (skip.get()) { - return; + private void _testConflictStatuses(int statusCode, String statusText, ListenerStatusType statusType, ConflictSetup setup) throws Exception { + runInSharedNamed("conflict", ts -> { + if (conflictNc == null) { + conflictListener = new Listener(); + conflictNc = ConnectionUtils.managedConnect( + optionsBuilder(ts).errorListener(conflictListener).connectionListener(conflictListener).build()); } - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStreamSubscription sub = setup.setup(nc, jsm, js, tsc, listener); - if (sub.getDispatcher() == null) { - if (type == TYPE_ERROR) { - JetStreamStatusException jsse = assertThrows(JetStreamStatusException.class, () -> sub.nextMessage(NEXT_MESSAGE)); - assertEquals(statusCode, jsse.getStatus().getCode()); - assertEquals(sub.hashCode(), jsse.getSubscription().hashCode()); - //noinspection deprecation - assertTrue(jsse.getDescription().contains(statusText)); // coverage + else { + conflictListener.reset(); + } + try (JetStreamTestingContext tcsCtx = new JetStreamTestingContext(conflictNc, 1)) { + if (statusType != null) { + conflictListener.queueStatus(statusType, statusCode, Listener.LONG_VALIDATE_TIMEOUT); } - else { - sub.nextMessage(NEXT_MESSAGE); + JetStreamSubscription sub = setup.setup(conflictNc, tcsCtx); + if (sub.getDispatcher() == null) { + if (statusType == PullError) { + JetStreamStatusException jsse = assertThrows(JetStreamStatusException.class, () -> sub.nextMessage(NEXT_MESSAGE_TIMEOUT)); + assertEquals(statusCode, jsse.getStatus().getCode()); + assertEquals(sub.hashCode(), jsse.getSubscription().hashCode()); + //noinspection deprecation + assertTrue(jsse.getDescription().contains(statusText)); // coverage + } + else { + sub.nextMessage(NEXT_MESSAGE_TIMEOUT); + } + } + if (statusType != null) { + conflictListener.validate(); } } - checkHandler(statusText, type, listener, WAIT_FOR_MESSAGES); }); } - private void checkHandler(String statusText, int type, ListenerForTesting listener, long timeout) { - if (type == TYPE_ERROR) { - assertTrue(listener.pullStatusErrorOrWait(statusText, timeout)); - } - else if (type == TYPE_WARNING) { - assertTrue(listener.pullStatusWarningEventually(statusText, timeout)); - } - } - @Test - public void testExceedsMaxWaitingSyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_WAITING, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - PullSubscribeOptions so = makePso(b -> b.maxPullWaiting(1)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); - sub.pull(1); - sub.pull(1); - return sub; - }); + public void testExceededMaxWaitingSync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_WAITING, PullWarning, + (nc, ctx) -> { + PullSubscribeOptions so = makePso(b -> b.maxPullWaiting(1)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); + sub.pull(1); + sub.pull(1); + return sub; + }); } + @Test - public void testExceedsMaxWaitingAsyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_WAITING, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = makePso(b -> b.maxPullWaiting(1)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, m -> {}, so); - sub.pull(1); - sub.pull(1); - return sub; - }); + public void testExceededMaxWaitingAsync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_WAITING, PullWarning, + (nc, ctx) -> { + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = makePso(b -> b.maxPullWaiting(1)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, m -> {}, so); + sub.pull(1); + sub.pull(1); + return sub; + }); } @Test - public void testExceedsMaxRequestBatchSyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_REQUEST_BATCH, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - PullSubscribeOptions so = makePso(b -> b.maxBatch(1)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); - sub.pull(2); - return sub; - }); + public void testExceedsMaxRequestBatchSync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_REQUEST_BATCH, PullWarning, + (nc, ctx) -> { + PullSubscribeOptions so = makePso(b -> b.maxBatch(1)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); + sub.pull(2); + return sub; + }); } + @Test - public void testExceedsMaxRequestBatchAsyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_REQUEST_BATCH, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = makePso(b -> b.maxBatch(1)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, m -> {}, so); - sub.pull(2); - return sub; - }); + public void testExceedsMaxRequestBatchAsync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_REQUEST_BATCH, PullWarning, + (nc, ctx) -> { + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = makePso(b -> b.maxBatch(1)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, m -> {}, so); + sub.pull(2); + return sub; + } + ); } @Test - public void testMessageSizeExceedsMaxBytesSyncSub() throws Exception { - testConflictStatus(409, MESSAGE_SIZE_EXCEEDS_MAX_BYTES, TYPE_NONE, "2.9.0", (nc, jsm, js, tsc, handler) -> { - PullSubscribeOptions so = makePso(b -> b); - js.publish(tsc.subject(), new byte[1000]); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); - sub.pull(PullRequestOptions.builder(1).maxBytes(100).build()); - return sub; - }); + public void testMessageSizeExceedsMaxBytesSync() throws Exception { + _testConflictStatuses(409, MESSAGE_SIZE_EXCEEDS_MAX_BYTES, PullWarning, + (nc, ctx) -> { + PullSubscribeOptions so = makePso(b -> b); + ctx.js.publish(ctx.subject(), new byte[1000]); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); + sub.pull(PullRequestOptions.builder(1).maxBytes(100).build()); + return sub; + }); } + @Test - public void testMessageSizeExceedsMaxBytesAsyncSub() throws Exception { - testConflictStatus(409, MESSAGE_SIZE_EXCEEDS_MAX_BYTES, TYPE_NONE, "2.9.0", (nc, jsm, js, tsc, handler) -> { - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = makePso(b -> b); - js.publish(tsc.subject(), new byte[1000]); - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, m -> {}, so); - sub.pull(PullRequestOptions.builder(1).maxBytes(100).build()); - return sub; - }); + public void testMessageSizeExceedsMaxBytesAsync() throws Exception { + _testConflictStatuses(409, MESSAGE_SIZE_EXCEEDS_MAX_BYTES, PullWarning, + (nc, ctx) -> { + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = makePso(b -> b); + ctx.js.publish(ctx.subject(), new byte[1000]); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, m -> {}, so); + sub.pull(PullRequestOptions.builder(1).maxBytes(100).build()); + return sub; + } + ); } @Test - public void testExceedsMaxRequestExpiresSyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_REQUEST_EXPIRES, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - PullSubscribeOptions so = makePso(b -> b.maxExpires(1000).name("foo")); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); - sub.pullExpiresIn(1, 2000); - return sub; - }); + public void testExceedsMaxRequestExpiresSync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_REQUEST_EXPIRES, PullWarning, + (nc, ctx) -> { + PullSubscribeOptions so = makePso(b -> b.maxExpires(1000)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); + sub.pullExpiresIn(1, 2000); + return sub; + }); } + @Test - public void testExceedsMaxRequestExpiresAsyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_REQUEST_EXPIRES, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = makePso(b -> b.maxExpires(1000)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, m -> {}, so); - sub.pullExpiresIn(1, 2000); - return sub; - }); + public void testExceedsMaxRequestExpiresAsync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_REQUEST_EXPIRES, PullWarning, + (nc, ctx) -> { + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = makePso(b -> b.maxExpires(1000)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, m -> {}, so); + sub.pullExpiresIn(1, 2000); + return sub; + } + ); } @Test - public void testConsumerIsPushBasedSyncSub() throws Exception { - testConflictStatus(409, CONSUMER_IS_PUSH_BASED, TYPE_ERROR, "2.9.0", (nc, jsm, js, tsc, handler) -> { - jsm.addOrUpdateConsumer(tsc.stream, builder().durable(durable(1)).ackPolicy(AckPolicy.None).build()); - PullSubscribeOptions so = PullSubscribeOptions.bind(tsc.stream, durable(1)); - JetStreamSubscription sub = js.subscribe(null, so); - jsm.deleteConsumer(tsc.stream, durable(1)); - // consumer with same name but is push now - jsm.addOrUpdateConsumer(tsc.stream, builder().durable(durable(1)).deliverSubject(deliver(1)).build()); - sub.pull(1); - return sub; - }); + public void testConsumerIsPushBasedSync() throws Exception { + _testConflictStatuses(409, CONSUMER_IS_PUSH_BASED, PullError, + (nc, ctx) -> { + String dur = random(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(dur).ackPolicy(AckPolicy.None).build()); + PullSubscribeOptions so = PullSubscribeOptions.bind(ctx.stream, dur); + JetStreamSubscription sub = ctx.js.subscribe(null, so); + ctx.jsm.deleteConsumer(ctx.stream, dur); + // consumer with same name but is push now + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(dur).deliverSubject(dur).build()); + sub.pull(1); + return sub; + }); } @Test - public void testConsumerIsPushBasedAsyncSub() throws Exception { - testConflictStatus(409, CONSUMER_IS_PUSH_BASED, TYPE_ERROR, "2.9.0", (nc, jsm, js, tsc, handler) -> { - jsm.addOrUpdateConsumer(tsc.stream, builder().durable(durable(1)).ackPolicy(AckPolicy.None).build()); - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = PullSubscribeOptions.bind(tsc.stream, durable(1)); - JetStreamSubscription sub = js.subscribe(null, d, m -> {}, so); - jsm.deleteConsumer(tsc.stream, durable(1)); - // consumer with same name but is push now - jsm.addOrUpdateConsumer(tsc.stream, builder().durable(durable(1)).deliverSubject(deliver(1)).build()); - sub.pull(1); - return sub; - }); + public void testConsumerIsPushBasedAsync() throws Exception { + _testConflictStatuses(409, CONSUMER_IS_PUSH_BASED, PullError, + (nc, ctx) -> { + String dur = random(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(dur).ackPolicy(AckPolicy.None).build()); + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = PullSubscribeOptions.bind(ctx.stream, dur); + JetStreamSubscription sub = ctx.js.subscribe(null, d, m -> {}, so); + ctx.jsm.deleteConsumer(ctx.stream, dur); + // consumer with same name but is push now + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(dur).deliverSubject(dur).build()); + sub.pull(1); + return sub; + } + ); } - // This just flaps. It's a timing thing? Already spent too much time, IWOMM and it should work as is. @Test - @Disabled public void testConsumerDeletedSyncSub() throws Exception { - testConflictStatus(409, CONSUMER_DELETED, TYPE_ERROR, "2.9.6", (nc, jsm, js, tsc, handler) -> { - jsm.addOrUpdateConsumer(tsc.stream, builder().durable(durable(1)).ackPolicy(AckPolicy.None).build()); - PullSubscribeOptions so = PullSubscribeOptions.bind(tsc.stream, durable(1)); - JetStreamSubscription sub = js.subscribe(null, so); - sub.pullExpiresIn(1, 30000); - jsm.deleteConsumer(tsc.stream, durable(1)); - js.publish(tsc.subject(), null); - return sub; - }); + _testConflictStatuses(409, CONSUMER_DELETED, PullError, + (nc, ctx) -> { + String dur = random(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(dur).ackPolicy(AckPolicy.None).build()); + PullSubscribeOptions so = PullSubscribeOptions.bind(ctx.stream, dur); + JetStreamSubscription sub = ctx.js.subscribe(null, so); + sub.pullExpiresIn(1, 30000); + nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server + ctx.jsm.deleteConsumer(ctx.stream, dur); + ctx.js.publish(ctx.subject(), null); + return sub; + }); } - // This just flaps. It's a timing thing? Already spent too much time, IWOMM and it should work as is. @Test - @Disabled public void testConsumerDeletedAsyncSub() throws Exception { - testConflictStatus(409, CONSUMER_DELETED, TYPE_ERROR, "2.9.6", (nc, jsm, js, tsc, handler) -> { - jsm.addOrUpdateConsumer(tsc.stream, builder().durable(durable(1)).ackPolicy(AckPolicy.None).build()); - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = PullSubscribeOptions.bind(tsc.stream, durable(1)); - JetStreamSubscription sub = js.subscribe(null, d, m -> {}, so); - sub.pullExpiresIn(1, 30000); - nc.flush(Duration.ofSeconds(1)); - jsm.deleteConsumer(tsc.stream, durable(1)); - js.publish(tsc.subject(), null); - return sub; - }); + _testConflictStatuses(409, CONSUMER_DELETED, PullError, + // Async + (nc, ctx) -> { + String dur = random(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(dur).ackPolicy(AckPolicy.None).build()); + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = PullSubscribeOptions.bind(ctx.stream, dur); + JetStreamSubscription sub = ctx.js.subscribe(null, d, m -> {}, so); + sub.pullExpiresIn(1, 30000); + nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server + ctx.jsm.deleteConsumer(ctx.stream, dur); + ctx.js.publish(ctx.subject(), null); + return sub; + } + ); } - static class BadPullRequestOptions extends PullRequestOptions { - public BadPullRequestOptions() { - super(PullRequestOptions.builder(1)); - } - - @Override - @NonNull - public String toJson() { - StringBuilder sb = JsonUtils.beginJson(); - JsonUtils.addField(sb, BATCH, 1); - JsonUtils.addFldWhenTrue(sb, NO_WAIT, true); - JsonUtils.addFieldAsNanos(sb, IDLE_HEARTBEAT, Duration.ofMillis(1)); - return JsonUtils.endJson(sb).toString(); - } + @Test + public void testBadRequestSync() throws Exception { + _testConflictStatuses(400, BAD_REQUEST, PullError, + (nc, ctx) -> { + PullSubscribeOptions so = makePso(b -> b); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); + sub.pull(new BadPullRequestOptions()); + return sub; + }); } @Test - public void testBadRequestSyncSub() throws Exception { - testConflictStatus(400, BAD_REQUEST, TYPE_ERROR, "2.9.0", (nc, jsm, js, tsc, handler) -> { - PullSubscribeOptions so = makePso(b -> b); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); - sub.pull(new BadPullRequestOptions()); - return sub; - }); + public void testBadRequestAsync() throws Exception { + _testConflictStatuses(400, BAD_REQUEST, PullError, + (nc, ctx) -> { + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = makePso(b -> b); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, m -> {}, so); + sub.pull(new BadPullRequestOptions()); + return sub; + }); } @Test - public void testBadRequestAsyncSub() throws Exception { - testConflictStatus(400, BAD_REQUEST, TYPE_ERROR, "2.9.0", (nc, jsm, js, tsc, handler) -> { - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = makePso(b -> b); - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, m -> {}, so); - sub.pull(new BadPullRequestOptions()); - return sub; - }); + public void testNotFoundSync() throws Exception { + _testConflictStatuses(404, NO_MESSAGES, PullWarning, + (nc, ctx) -> { + PullSubscribeOptions so = makePso(b -> b); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); + sub.pullNoWait(1); + return sub; + }); } + @Test - public void testNotFoundSyncSub() throws Exception { - testConflictStatus(404, NO_MESSAGES, TYPE_NONE, "2.9.0", (nc, jsm, js, tsc, handler) -> { - PullSubscribeOptions so = makePso(b -> b); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); - sub.pullNoWait(1); - return sub; - }); + public void testNotFoundAsync() throws Exception { + _testConflictStatuses(404, NO_MESSAGES, PullWarning, + (nc, ctx) -> { + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = makePso(b -> b); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, m -> {}, so); + sub.pullNoWait(1); + return sub; + }); } @Test - public void testNotFoundAsyncSub() throws Exception { - testConflictStatus(404, NO_MESSAGES, TYPE_NONE, "2.9.0", (nc, jsm, js, tsc, handler) -> { - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = makePso(b -> b); - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, m -> {}, so); - sub.pullNoWait(1); - return sub; - }); + public void testExceedsMaxRequestBytes1stMessageSync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_REQUEST_MAX_BYTES, PullWarning, + (nc, ctx) -> { + PullSubscribeOptions so = makePso(b -> b.maxBytes(1)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); + sub.pull(PullRequestOptions.builder(1).maxBytes(2).build()); + return sub; + }); } @Test - public void testExceedsMaxRequestBytes1stMessageSyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_REQUEST_MAX_BYTES, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - PullSubscribeOptions so = makePso(b -> b.maxBytes(1)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); - sub.pull(PullRequestOptions.builder(1).maxBytes(2).build()); - return sub; - }); + public void testExceedsMaxRequestBytes1stMessageAsync() throws Exception { + _testConflictStatuses(409, EXCEEDED_MAX_REQUEST_MAX_BYTES, PullWarning, + (nc, ctx) -> { + Dispatcher d = nc.createDispatcher(); + PullSubscribeOptions so = makePso(b -> b.maxBytes(1)); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), d, m -> {}, so); + sub.pull(PullRequestOptions.builder(1).maxBytes(2).build()); + return sub; + }); } - @Test - public void testExceedsMaxRequestBytes1stMessageAsyncSub() throws Exception { - testConflictStatus(409, EXCEEDED_MAX_REQUEST_MAX_BYTES, TYPE_WARNING, "2.9.0", (nc, jsm, js, tsc, handler) -> { - Dispatcher d = nc.createDispatcher(); - PullSubscribeOptions so = makePso(b -> b.maxBytes(1)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), d, m -> {}, so); - sub.pull(PullRequestOptions.builder(1).maxBytes(2).build()); - return sub; - }); + static class BadPullRequestOptions extends PullRequestOptions { + public BadPullRequestOptions() { + super(PullRequestOptions.builder(1)); + } + + @Override + @NonNull + public String toJson() { + StringBuilder sb = JsonUtils.beginJson(); + JsonUtils.addField(sb, BATCH, 1); + JsonUtils.addFldWhenTrue(sb, NO_WAIT, true); + JsonUtils.addFieldAsNanos(sb, IDLE_HEARTBEAT, Duration.ofMillis(1)); + return JsonUtils.endJson(sb).toString(); + } } @Test public void testExceedsMaxRequestBytesNthMessageSyncSub() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - runInJsServer(TestBase::atLeast2_9_1, listener, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - - jsm.addOrUpdateConsumer(tsc.stream, builder().durable(durable(1)).ackPolicy(AckPolicy.None).filterSubjects(tsc.subject()).build()); - PullSubscribeOptions so = PullSubscribeOptions.bind(tsc.stream, durable(1)); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); + Listener listener = new Listener(); + runInSharedOwnNc(listener, (nc, ctx) -> { + listener.queueStatus(PullWarning, CONFLICT_CODE); + String dur = random(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(dur).ackPolicy(AckPolicy.None).filterSubjects(ctx.subject()).build()); + PullSubscribeOptions so = PullSubscribeOptions.bind(ctx.stream, dur); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); // subject 7 + reply 52 + bytes 100 = 159 // subject 7 + reply 52 + bytes 100 + headers 21 = 180 - js.publish(tsc.subject(), new byte[100]); - js.publish(tsc.subject(), new Headers().add("foo", "bar"), new byte[100]); + ctx.js.publish(ctx.subject(), new byte[100]); + ctx.js.publish(ctx.subject(), new Headers().add("foo", "bar"), new byte[100]); // 1000 - 159 - 180 = 661 // subject 7 + reply 52 + bytes 610 = 669 > 661 - js.publish(tsc.subject(), new byte[610]); + ctx.js.publish(ctx.subject(), new byte[610]); sub.pull(PullRequestOptions.builder(10).maxBytes(1000).expiresIn(1000).build()); assertNotNull(sub.nextMessage(500)); assertNotNull(sub.nextMessage(500)); assertNull(sub.nextMessage(500)); - checkHandler(MESSAGE_SIZE_EXCEEDS_MAX_BYTES, TYPE_NONE, listener, 2500); + listener.validate(); }); } @Test - public void testExceedsMaxRequestBytesExactBytes() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - runInJsServer(TestBase::atLeast2_9_1, listener, nc -> { - String stream = "sixsix"; // six letters so I can count - String subject = "seven"; // seven letters so I can count - String durable = durable(0); // short keeps under max bytes - createMemoryStream(nc, stream, subject); - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - jsm.addOrUpdateConsumer(stream, builder().durable(durable).ackPolicy(AckPolicy.None).filterSubjects(subject).build()); - PullSubscribeOptions so = PullSubscribeOptions.bind(stream, durable); - JetStreamSubscription sub = js.subscribe(subject, so); - - // 159 + 180 + 661 = 1000 + public void testDoesNotExceedMaxRequestBytesExactBytes() throws Exception { + Listener listener = new Listener(); + runInSharedOwnNc(listener, (nc, ctx) -> { + listener.queueStatus(PullWarning, CONFLICT_CODE); + ctx.stream = random(6); // six letters so I can count + String subject = random(5); // five letters so I can count + String durable = random(); // default random is 10 chars so is short enough to keeps under max bytes + ctx.createOrReplaceStream(subject); + ctx.jsm.addOrUpdateConsumer(ctx.stream, builder().durable(durable).ackPolicy(AckPolicy.None).filterSubjects(subject).build()); + PullSubscribeOptions so = PullSubscribeOptions.bind(ctx.stream, durable); + JetStreamSubscription sub = ctx.js.subscribe(subject, so); + + // 159 + 180 + 661 = 1000 // subject includes crlf // subject 7 + reply 52 + bytes 100 = 159 // subject 7 + reply 52 + bytes 100 + headers 21 = 180 // subject 7 + reply 52 + bytes 602 = 661 - js.publish(subject, new byte[100]); - js.publish(subject, new Headers().add("foo", "bar"), new byte[100]); - js.publish(subject, new byte[602]); + ctx.js.publish(subject, new byte[100]); + ctx.js.publish(subject, new Headers().add("foo", "bar"), new byte[100]); + ctx.js.publish(subject, new byte[602]); sub.pull(PullRequestOptions.builder(10).maxBytes(1000).expiresIn(1000).build()); assertNotNull(sub.nextMessage(500)); assertNotNull(sub.nextMessage(500)); assertNotNull(sub.nextMessage(500)); assertNull(sub.nextMessage(500)); // there are no more messages - checkHandler(MESSAGE_SIZE_EXCEEDS_MAX_BYTES, TYPE_NONE, listener, 2500); + listener.validateNotReceived(); }); } @Test public void testReader() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { // Pre define a consumer - ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(tsc.consumerName()).filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(ctx.consumerName()).filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - PullSubscribeOptions so = PullSubscribeOptions.bind(tsc.stream, tsc.consumerName()); - JetStreamSubscription sub = js.subscribe(tsc.subject(), so); + PullSubscribeOptions so = PullSubscribeOptions.bind(ctx.stream, ctx.consumerName()); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), so); JetStreamReader reader = sub.reader(500, 125); int stopCount = 500; @@ -1143,7 +1107,7 @@ public void testReader() throws Exception { AtomicInteger count = new AtomicInteger(); Thread readerThread = getReaderThread(count, stopCount, reader); - Publisher publisher = new Publisher(js, tsc.subject(), 25); + Publisher publisher = new Publisher(ctx.js, ctx.subject(), 25); Thread pubThread = new Thread(publisher); pubThread.start(); @@ -1186,35 +1150,30 @@ private static Thread getReaderThread(AtomicInteger count, int stopCount, JetStr @Test public void testOverflow() throws Exception { - ListenerForTesting l = new ListenerForTesting(); - Options.Builder b = Options.builder().errorListener(l); - jsServer.run(b, TestBase::atLeast2_11, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - jsPublish(js, tsc.subject(), 100); + runInShared(VersionUtils::atLeast2_11, (nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 100); // Setting PriorityPolicy requires at least one PriorityGroup to be set ConsumerConfiguration ccNoGroup = ConsumerConfiguration.builder() .priorityPolicy(PriorityPolicy.Overflow) .build(); JetStreamApiException jsae = assertThrows(JetStreamApiException.class, - () -> jsm.addOrUpdateConsumer(tsc.stream, ccNoGroup)); + () -> ctx.jsm.addOrUpdateConsumer(ctx.stream, ccNoGroup)); assertEquals(10159, jsae.getApiErrorCode()); // Testing errors - String group = variant(); - String consumer = variant(); + String group = random(); + String consumer = random(); ConsumerConfiguration cc = ConsumerConfiguration.builder() .name(consumer) .priorityPolicy(PriorityPolicy.Overflow) .priorityGroups(group) - .filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + .filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - PullSubscribeOptions so = PullSubscribeOptions.fastBind(tsc.stream, consumer); - JetStreamSubscription sub = js.subscribe(null, so); + PullSubscribeOptions so = PullSubscribeOptions.fastBind(ctx.stream, consumer); + JetStreamSubscription sub = ctx.js.subscribe(null, so); // 400 Bad Request - Priority Group missing sub.pull(1); @@ -1225,20 +1184,20 @@ public void testOverflow() throws Exception { assertThrows(JetStreamStatusException.class, () -> sub.nextMessage(1000)); // Testing min ack pending - group = variant(); - consumer = variant(); + group = random(); + consumer = random(); cc = ConsumerConfiguration.builder() .name(consumer) .priorityPolicy(PriorityPolicy.Overflow) .priorityGroups(group) .ackWait(60_000) - .filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + .filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - so = PullSubscribeOptions.fastBind(tsc.stream, consumer); - JetStreamSubscription subPrime = js.subscribe(null, so); - JetStreamSubscription subOver = js.subscribe(null, so); + so = PullSubscribeOptions.fastBind(ctx.stream, consumer); + JetStreamSubscription subPrime = ctx.js.subscribe(null, so); + JetStreamSubscription subOver = ctx.js.subscribe(null, so); PullRequestOptions proNoMin = PullRequestOptions.builder(5) .group(group) @@ -1262,19 +1221,19 @@ public void testOverflow() throws Exception { _overflowCheck(subOver, proOverB, true, 0); // Testing min pending - group = variant(); - consumer = variant(); + group = random(); + consumer = random(); cc = ConsumerConfiguration.builder() .name(consumer) .priorityPolicy(PriorityPolicy.Overflow) .priorityGroups(group) - .filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + .filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - so = PullSubscribeOptions.fastBind(tsc.stream, consumer); - subPrime = js.subscribe(null, so); - subOver = js.subscribe(null, so); + so = PullSubscribeOptions.fastBind(ctx.stream, consumer); + subPrime = ctx.js.subscribe(null, so); + subOver = ctx.js.subscribe(null, so); proNoMin = PullRequestOptions.builder(5) .group(group) @@ -1296,7 +1255,7 @@ public void testOverflow() throws Exception { }); } - private static void _overflowCheck(JetStreamSubscription sub, PullRequestOptions pro, boolean ack, int expected) throws InterruptedException, JetStreamApiException, IOException { + private static void _overflowCheck(JetStreamSubscription sub, PullRequestOptions pro, boolean ack, int expected) throws InterruptedException { sub.pull(pro); int count = 0; Message m = sub.nextMessage(1000); @@ -1316,24 +1275,18 @@ public void testPrioritized() throws Exception { // start a priority 1 (#1) and a priority 2 (#2) consumer, #1 should get messages, #2 should get none // close the #1, #2 should get messages // start another priority 1 (#3), #2 should stop getting messages #3 should get messages - ListenerForTesting l = new ListenerForTesting(); - Options.Builder b = Options.builder().errorListener(l); - jsServer.run(b, TestBase::atLeast2_12, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - - String consumer = name(); - String group = variant(); + runInShared(VersionUtils::atLeast2_12, (nc, ctx) -> { + String consumer = random(); + String group = random(); ConsumerConfiguration cc = ConsumerConfiguration.builder() - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .name(consumer) .priorityGroups(group) .priorityPolicy(PriorityPolicy.Prioritized) .build(); - StreamContext streamContext = nc.getStreamContext(tsc.stream); + StreamContext streamContext = nc.getStreamContext(ctx.stream); ConsumerContext consumerContext1 = streamContext.createOrUpdateConsumer(cc); ConsumerContext consumerContext2 = streamContext.getConsumerContext(consumer); @@ -1373,13 +1326,13 @@ public void testPrioritized() throws Exception { MessageConsumer mc1 = consumerContext1.consume(coP1, handler1); MessageConsumer mc2 = consumerContext2.consume(coP2, handler2); - AtomicBoolean keepPublishing = new AtomicBoolean(true); + AtomicBoolean pub = new AtomicBoolean(true); Thread t = new Thread(() -> { int count = 0; - while (keepPublishing.get()) { + while (pub.get()) { ++count; try { - js.publish(tsc.subject(), ("x" + count).getBytes()); + ctx.js.publish(ctx.subject(), ("x" + count).getBytes()); sleep(20); } catch (Exception e) { @@ -1402,7 +1355,7 @@ public void testPrioritized() throws Exception { MessageConsumer mc3 = consumerContext2.consume(coP1, handler3); Thread.sleep(200); - keepPublishing.set(false); + pub.set(false); t.join(); mc2.close(); mc3.close(); @@ -1418,24 +1371,18 @@ public void testPinnedClient() throws Exception { // have 3 consumers in the same group all PriorityPolicy.PinnedClient // start consuming, tracking pin ids and counts // unpin 10 times and make sure that new pins are made - ListenerForTesting l = new ListenerForTesting(); - Options.Builder b = Options.builder().errorListener(l); - jsServer.run(b, TestBase::atLeast2_12, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - - String consumer = name(); - String group = variant(); + runInShared(VersionUtils::atLeast2_12, (nc, ctx) -> { + String consumer = random(); + String group = random(); ConsumerConfiguration cc = ConsumerConfiguration.builder() - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .name(consumer) .priorityGroups(group) .priorityPolicy(PriorityPolicy.PinnedClient) .build(); - StreamContext streamContext = nc.getStreamContext(tsc.stream); + StreamContext streamContext = nc.getStreamContext(ctx.stream); ConsumerContext consumerContext1 = streamContext.createOrUpdateConsumer(cc); ConsumerContext consumerContext2 = streamContext.getConsumerContext(consumer); ConsumerContext consumerContext3 = streamContext.getConsumerContext(consumer); @@ -1488,7 +1435,7 @@ public void testPinnedClient() throws Exception { while (pub.get()) { ++count; try { - js.publish(tsc.subject(), ("x" + count).getBytes()); + ctx.js.publish(ctx.subject(), ("x" + count).getBytes()); sleep(20); } catch (Exception e) { @@ -1513,7 +1460,7 @@ public void testPinnedClient() throws Exception { assertTrue(consumerContext3.unpin(group)); break; case 3: - assertTrue(jsm.unpinConsumer(tsc.stream, consumer, group)); + assertTrue(ctx.jsm.unpinConsumer(ctx.stream, consumer, group)); break; } assertTrue(consumerContext1.unpin(group)); diff --git a/src/test/java/io/nats/client/impl/JetStreamPushAsyncTests.java b/src/test/java/io/nats/client/impl/JetStreamPushAsyncTests.java index b49510f1c..ac35269b8 100644 --- a/src/test/java/io/nats/client/impl/JetStreamPushAsyncTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamPushAsyncTests.java @@ -15,6 +15,7 @@ import io.nats.client.*; import io.nats.client.api.*; +import io.nats.client.support.Listener; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -25,21 +26,16 @@ import static io.nats.client.support.NatsJetStreamConstants.*; import static io.nats.client.support.NatsKeyValueUtil.KV_OPERATION_HEADER_KEY; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class JetStreamPushAsyncTests extends JetStreamTestBase { @Test public void testHandlerSub() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // publish some messages - jsPublish(js, tsc.subject(), 10); + jsPublish(ctx.js, ctx.subject(), 10); // create a dispatcher without a default handler. Dispatcher dispatcher = nc.createDispatcher(); @@ -55,7 +51,7 @@ public void testHandlerSub() throws Exception { }; // Subscribe using the handler - js.subscribe(tsc.subject(), dispatcher, handler, false); + ctx.js.subscribe(ctx.subject(), dispatcher, handler, false); // Wait for messages to arrive using the countdown latch. // make sure we don't wait forever @@ -67,15 +63,9 @@ public void testHandlerSub() throws Exception { @Test public void testHandlerAutoAck() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // publish some messages - jsPublish(js, tsc.subject(), 10); + jsPublish(ctx.js, ctx.subject(), 10); // create a dispatcher without a default handler. Dispatcher dispatcher = nc.createDispatcher(); @@ -91,8 +81,8 @@ public void testHandlerAutoAck() throws Exception { }; // subscribe using the handler, auto ack true - PushSubscribeOptions pso1 = PushSubscribeOptions.builder().durable(durable(1)).build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), dispatcher, handler1, true, pso1); + PushSubscribeOptions pso1 = PushSubscribeOptions.builder().durable(random()).build(); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), dispatcher, handler1, true, pso1); // Wait for messages to arrive using the countdown latch. // make sure we don't wait forever @@ -102,7 +92,7 @@ public void testHandlerAutoAck() throws Exception { // check that all the messages were read by the durable dispatcher.unsubscribe(sub); - sub = js.subscribe(tsc.subject(), pso1); + sub = ctx.js.subscribe(ctx.subject(), pso1); assertNull(sub.nextMessage(Duration.ofSeconds(1))); // 2. auto ack false @@ -117,8 +107,8 @@ public void testHandlerAutoAck() throws Exception { // subscribe using the handler, auto ack false ConsumerConfiguration cc = ConsumerConfiguration.builder().ackWait(Duration.ofMillis(500)).build(); - PushSubscribeOptions pso2 = PushSubscribeOptions.builder().durable(durable(2)).configuration(cc).build(); - sub = js.subscribe(tsc.subject(), dispatcher, handler2, false, pso2); + PushSubscribeOptions pso2 = PushSubscribeOptions.builder().durable(random()).configuration(cc).build(); + sub = ctx.js.subscribe(ctx.subject(), dispatcher, handler2, false, pso2); // Wait for messages to arrive using the countdown latch. // make sure we don't wait forever @@ -131,7 +121,7 @@ public void testHandlerAutoAck() throws Exception { sleep(1000); // just give it time for the server to realize the messages are not ack'ed dispatcher.unsubscribe(sub); - sub = js.subscribe(tsc.subject(), pso2); + sub = ctx.js.subscribe(ctx.subject(), pso2); List list = readMessagesAck(sub, false); assertEquals(10, list.size()); }); @@ -139,14 +129,8 @@ public void testHandlerAutoAck() throws Exception { @Test public void testCantNextMessageOnAsyncPushSub() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - JetStreamSubscription sub = js.subscribe(tsc.subject(), nc.createDispatcher(), msg -> {}, false); + runInShared((nc, ctx) -> { + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), nc.createDispatcher(), msg -> {}, false); // this should exception, can't next message on an async push sub assertThrows(IllegalStateException.class, () -> sub.nextMessage(Duration.ofMillis(1000))); @@ -156,16 +140,8 @@ public void testCantNextMessageOnAsyncPushSub() throws Exception { @Test public void testPushAsyncFlowControl() throws Exception { - ListenerForTesting listenerForTesting = new ListenerForTesting(); - Options.Builder ob = new Options.Builder().errorListener(listenerForTesting); - - runInJsServer(ob, nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + Listener listener = new Listener(); + runInSharedOwnNc(listener, (nc, ctx) -> { byte[] data = new byte[8192]; int MSG_COUNT = 1000; @@ -174,7 +150,7 @@ public void testPushAsyncFlowControl() throws Exception { for (int x = 100_000; x < MSG_COUNT + 100_000; x++) { byte[] fill = (""+ x).getBytes(); System.arraycopy(fill, 0, data, 0, 6); - js.publish(NatsMessage.builder().subject(tsc.subject()).data(data).build()); + ctx.js.publish(NatsMessage.builder().subject(ctx.subject()).data(data).build()); } // create a dispatcher without a default handler. @@ -197,34 +173,29 @@ public void testPushAsyncFlowControl() throws Exception { ConsumerConfiguration cc = ConsumerConfiguration.builder().flowControl(1000).build(); PushSubscribeOptions pso = PushSubscribeOptions.builder().configuration(cc).build(); - js.subscribe(tsc.subject(), dispatcher, handler, false, pso); + ctx.js.subscribe(ctx.subject(), dispatcher, handler, false, pso); // Wait for messages to arrive using the countdown latch. // make sure we don't wait forever awaitAndAssert(msgLatch); assertEquals(MSG_COUNT, count.get()); - assertFalse(listenerForTesting.getFlowControlProcessedEvents().isEmpty()); + assertTrue(listener.getFlowControlCount() > 0); }); } @Test - public void testDontAutoAckSituations() throws Exception { - String mockAckReply = "mock-ack-reply."; - - jsServer.run(nc -> { - // create the stream. - String stream = stream(); - String subject = subject(); - createMemoryStream(nc, stream, subject, mockAckReply + "*"); + public void testDoNotAutoAckSituations() throws Exception { + String mockAckReply = random(); // "mock-ack-reply."; - // Create our JetStream context. - JetStream js = nc.jetStream(); + runInSharedCustom((nc, ctx) -> { + String subject = ctx.subject(); + ctx.createOrReplaceStream(subject, subjectStar(mockAckReply)); int pubCount = 5; // publish a message - jsPublish(js, subject, pubCount); + jsPublish(ctx.js, subject, pubCount); // create a dispatcher without a default handler. Dispatcher dispatcher = nc.createDispatcher(); @@ -243,29 +214,29 @@ public void testDontAutoAckSituations() throws Exception { int f = count.incrementAndGet(); if (f == 1) { - m.replyTo = mockAckReply + "ack"; + m.replyTo = subjectDot(mockAckReply, "ack"); m.ack(); } else if (f == 2) { - m.replyTo = mockAckReply + "nak"; + m.replyTo = subjectDot(mockAckReply, "nak"); m.nak(); } else if (f == 3) { - m.replyTo = mockAckReply + "term"; + m.replyTo = subjectDot(mockAckReply, "term"); m.term(); } else if (f == 4) { - m.replyTo = mockAckReply + "progress"; + m.replyTo = subjectDot(mockAckReply, "progress"); m.inProgress(); } else { - m.replyTo = mockAckReply + "auto"; + m.replyTo = subjectDot(mockAckReply, "auto"); } msgLatchRef.get().countDown(); }; // subscribe using the handler, auto ack true - JetStreamSubscription async = js.subscribe(subject, dispatcher, handler, true); + JetStreamSubscription async = ctx.js.subscribe(subject, dispatcher, handler, true); // Wait for messages to arrive using the countdown latch. // make sure we don't wait forever @@ -273,36 +244,36 @@ else if (f == 4) { assertEquals(0, msgLatchRef.get().getCount()); dispatcher.unsubscribe(async); - JetStreamSubscription mockAckReplySub = js.subscribe(mockAckReply + "*"); + JetStreamSubscription mockAckReplySub = ctx.js.subscribe(subjectStar(mockAckReply)); Message msg = mockAckReplySub.nextMessage(2000); - assertEquals(mockAckReply + "ack", msg.getSubject()); + assertEquals(subjectDot(mockAckReply, "ack"), msg.getSubject()); assertEquals("+ACK", new String(msg.getData())); msg = mockAckReplySub.nextMessage(500); - assertEquals(mockAckReply + "nak", msg.getSubject()); + assertEquals(subjectDot(mockAckReply, "nak"), msg.getSubject()); assertEquals("-NAK", new String(msg.getData())); msg = mockAckReplySub.nextMessage(500); - assertEquals(mockAckReply + "term", msg.getSubject()); + assertEquals(subjectDot(mockAckReply, "term"), msg.getSubject()); assertEquals("+TERM", new String(msg.getData())); msg = mockAckReplySub.nextMessage(500); - assertEquals(mockAckReply + "progress", msg.getSubject()); + assertEquals(subjectDot(mockAckReply, "progress"), msg.getSubject()); assertEquals("+WPI", new String(msg.getData())); // because it was in progress which is not a terminal ack, the auto ack acks msg = mockAckReplySub.nextMessage(500); - assertEquals(mockAckReply + "progress", msg.getSubject()); + assertEquals(subjectDot(mockAckReply, "progress"), msg.getSubject()); assertEquals("+ACK", new String(msg.getData())); msg = mockAckReplySub.nextMessage(500); - assertEquals(mockAckReply + "auto", msg.getSubject()); + assertEquals(subjectDot(mockAckReply, "auto"), msg.getSubject()); assertEquals("+ACK", new String(msg.getData())); // coverage explicit no ack flag msgLatchRef.set(new CountDownLatch(pubCount)); PushSubscribeOptions pso = ConsumerConfiguration.builder().ackWait(Duration.ofMinutes(2)).buildPushSubscribeOptions(); - async = js.subscribe(subject, dispatcher, handler, false, pso); + async = ctx.js.subscribe(subject, dispatcher, handler, false, pso); awaitAndAssert(msgLatchRef.get()); assertEquals(0, msgLatchRef.get().getCount()); dispatcher.unsubscribe(async); @@ -313,7 +284,7 @@ else if (f == 4) { // coverage explicit AckPolicyNone msgLatchRef.set(new CountDownLatch(pubCount)); pso = ConsumerConfiguration.builder().ackPolicy(AckPolicy.None).buildPushSubscribeOptions(); - async = js.subscribe(subject, dispatcher, handler, true, pso); + async = ctx.js.subscribe(subject, dispatcher, handler, true, pso); awaitAndAssert(msgLatchRef.get()); assertEquals(0, msgLatchRef.get().getCount()); dispatcher.unsubscribe(async); @@ -334,10 +305,10 @@ public void onMessage(Message msg) throws InterruptedException { @Test public void testMemoryStorageServerBugPR2719() throws Exception { - String stream = stream(); - String sub = "msbsub.>"; - String key1 = "msbsub.key1"; - String key2 = "msbsub.key2"; + String subBase = random(); + String sub = subjectGt(subBase); + String key1 = subjectDot(subBase, "key1"); + String key2 = subjectDot(subBase, "key2"); Headers deleteHeaders = new Headers() .put(KV_OPERATION_HEADER_KEY, KeyValueOperation.DELETE.name()); @@ -345,18 +316,13 @@ public void testMemoryStorageServerBugPR2719() throws Exception { .put(KV_OPERATION_HEADER_KEY, KeyValueOperation.PURGE.name()) .put(ROLLUP_HDR, ROLLUP_HDR_SUBJECT); - jsServer.run(nc -> { - StreamConfiguration sc = StreamConfiguration.builder() - .name(stream) - .storageType(StorageType.Memory) - .subjects(sub) + runInSharedCustom((nc, ctx) -> { + StreamConfiguration sc = ctx.scBuilder(sub) .allowRollup(true) .denyDelete(true) .build(); - nc.jetStreamManagement().addStream(sc); - - JetStream js = nc.jetStream(); + ctx.createOrReplaceStream(sc); MemStorBugHandler fullHandler = new MemStorBugHandler(); MemStorBugHandler onlyHandler = new MemStorBugHandler(); @@ -378,8 +344,8 @@ public void testMemoryStorageServerBugPR2719() throws Exception { .buildPushSubscribeOptions(); Dispatcher d = nc.createDispatcher(); - js.subscribe(sub, d, fullHandler, false, psoFull); - js.subscribe(sub, d, onlyHandler, false, psoOnly); + ctx.js.subscribe(sub, d, fullHandler, false, psoFull); + ctx.js.subscribe(sub, d, onlyHandler, false, psoOnly); Object[] expecteds = new Object[] { "a", "aa", "z", "zz", @@ -389,22 +355,22 @@ public void testMemoryStorageServerBugPR2719() throws Exception { KeyValueOperation.PURGE, KeyValueOperation.PURGE, }; - js.publish(NatsMessage.builder().subject(key1).data((String)expecteds[0]).build()); - js.publish(NatsMessage.builder().subject(key1).data((String)expecteds[1]).build()); - js.publish(NatsMessage.builder().subject(key2).data((String)expecteds[2]).build()); - js.publish(NatsMessage.builder().subject(key2).data((String)expecteds[3]).build()); + ctx.js.publish(NatsMessage.builder().subject(key1).data((String)expecteds[0]).build()); + ctx.js.publish(NatsMessage.builder().subject(key1).data((String)expecteds[1]).build()); + ctx.js.publish(NatsMessage.builder().subject(key2).data((String)expecteds[2]).build()); + ctx.js.publish(NatsMessage.builder().subject(key2).data((String)expecteds[3]).build()); - js.publish(NatsMessage.builder().subject(key1).headers(deleteHeaders).build()); - js.publish(NatsMessage.builder().subject(key2).headers(deleteHeaders).build()); + ctx.js.publish(NatsMessage.builder().subject(key1).headers(deleteHeaders).build()); + ctx.js.publish(NatsMessage.builder().subject(key2).headers(deleteHeaders).build()); - js.publish(NatsMessage.builder().subject(key1).data((String)expecteds[6]).build()); - js.publish(NatsMessage.builder().subject(key2).data((String)expecteds[7]).build()); + ctx.js.publish(NatsMessage.builder().subject(key1).data((String)expecteds[6]).build()); + ctx.js.publish(NatsMessage.builder().subject(key2).data((String)expecteds[7]).build()); - js.publish(NatsMessage.builder().subject(key1).headers(deleteHeaders).build()); - js.publish(NatsMessage.builder().subject(key2).headers(deleteHeaders).build()); + ctx.js.publish(NatsMessage.builder().subject(key1).headers(deleteHeaders).build()); + ctx.js.publish(NatsMessage.builder().subject(key2).headers(deleteHeaders).build()); - js.publish(NatsMessage.builder().subject(key1).headers(purgeHeaders).build()); - js.publish(NatsMessage.builder().subject(key2).headers(purgeHeaders).build()); + ctx.js.publish(NatsMessage.builder().subject(key1).headers(purgeHeaders).build()); + ctx.js.publish(NatsMessage.builder().subject(key2).headers(purgeHeaders).build()); sleep(2000); // give time for the handler to get messages @@ -419,14 +385,13 @@ private void validateHeadersOnly(Object[] expecteds, MemStorBugHandler handler) Object expected = expecteds[aix++]; Headers h = m.getHeaders(); assertNotNull(h); + assertTrue(m.getData() == null || m.getData().length == 0); if (expected instanceof String) { - assertTrue(m.getData() == null || m.getData().length == 0); assertEquals("" + ((String)expected).length(), h.getFirst(MSG_SIZE_HDR)); assertNull(h.getFirst(KV_OPERATION_HEADER_KEY)); assertNull(h.getFirst(ROLLUP_HDR)); } else { - assertTrue(m.getData() == null || m.getData().length == 0); assertEquals("0", h.getFirst(MSG_SIZE_HDR)); assertEquals(expected, KeyValueOperation.valueOf(h.getFirst(KV_OPERATION_HEADER_KEY))); if (expected == KeyValueOperation.PURGE) { diff --git a/src/test/java/io/nats/client/impl/JetStreamPushQueueTests.java b/src/test/java/io/nats/client/impl/JetStreamPushQueueTests.java index 47064559b..590013735 100644 --- a/src/test/java/io/nats/client/impl/JetStreamPushQueueTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamPushQueueTests.java @@ -32,26 +32,21 @@ public class JetStreamPushQueueTests extends JetStreamTestBase { @Test public void testQueueSubWorkflow() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { // Set up the subscribers // - the PushSubscribeOptions can be re-used since all the subscribers are the same // - use a concurrent integer to track all the messages received // - have a list of subscribers and threads so I can track them - PushSubscribeOptions pso = PushSubscribeOptions.builder().durable(tsc.consumerName()).build(); + PushSubscribeOptions pso = PushSubscribeOptions.builder().durable(ctx.consumerName()).build(); AtomicInteger allReceived = new AtomicInteger(); List subscribers = new ArrayList<>(); + String queue = random(); List subThreads = new ArrayList<>(); for (int id = 1; id <= 3; id++) { // set up the subscription - JetStreamSubscription sub = js.subscribe(tsc.subject(), QUEUE, pso); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), queue, pso); // create and track the runnable - JsQueueSubscriber qs = new JsQueueSubscriber(100, js, sub, allReceived); + JsQueueSubscriber qs = new JsQueueSubscriber(100, ctx.js, sub, allReceived); subscribers.add(qs); // create, track and start the thread Thread t = new Thread(qs); @@ -61,7 +56,7 @@ public void testQueueSubWorkflow() throws Exception { nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // create and start the publishing - Thread pubThread = new Thread(new JsPublisher(js, tsc.subject(), 100)); + Thread pubThread = new Thread(new JsPublisher(ctx.js, ctx.subject(), 100)); pubThread.start(); // wait for all threads to finish diff --git a/src/test/java/io/nats/client/impl/JetStreamPushTests.java b/src/test/java/io/nats/client/impl/JetStreamPushTests.java index cda2f9c7a..2aa69c582 100644 --- a/src/test/java/io/nats/client/impl/JetStreamPushTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamPushTests.java @@ -17,6 +17,7 @@ import io.nats.client.api.ConsumerConfiguration; import io.nats.client.api.DeliverPolicy; import io.nats.client.api.PublishAck; +import io.nats.client.support.Listener; import io.nats.client.support.NatsJetStreamConstants; import org.junit.jupiter.api.Test; @@ -30,6 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger; import static io.nats.client.support.NatsJetStreamClientError.JsSubPushAsyncCantSetPending; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class JetStreamPushTests extends JetStreamTestBase { @@ -41,26 +43,20 @@ public void testPushEphemeralNullDeliver() throws Exception { @Test public void testPushEphemeralWithDeliver() throws Exception { - _testPushEphemeral(DELIVER); + _testPushEphemeral(random()); } private void _testPushEphemeral(String deliverSubject) throws Exception { - jsServer.run(nc -> { - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - // Create our JetStream context. - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { // publish some messages - jsPublish(js, tsc.subject(), 1, 5); + jsPublish(ctx.js, ctx.subject(), 1, 5); // Build our subscription options. PushSubscribeOptions options = PushSubscribeOptions.builder().deliverSubject(deliverSubject).build(); // Subscription 1 - JetStreamSubscription sub1 = js.subscribe(tsc.subject(), options); - assertSubscription(sub1, tsc.stream, null, deliverSubject, false); + JetStreamSubscription sub1 = ctx.js.subscribe(ctx.subject(), options); + assertSubscription(sub1, ctx.stream, null, deliverSubject, false); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // read what is available @@ -79,7 +75,7 @@ private void _testPushEphemeral(String deliverSubject) throws Exception { unsubscribeEnsureNotBound(sub1); // Subscription 2 - JetStreamSubscription sub2 = js.subscribe(tsc.subject(), options); + JetStreamSubscription sub2 = ctx.js.subscribe(ctx.subject(), options); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // read what is available, same messages @@ -97,7 +93,7 @@ private void _testPushEphemeral(String deliverSubject) throws Exception { unsubscribeEnsureNotBound(sub2); // Subscription 3 testing null timeout - JetStreamSubscription sub3 = js.subscribe(tsc.subject(), options); + JetStreamSubscription sub3 = ctx.js.subscribe(ctx.subject(), options); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server sleep(1000); // give time to make sure the messages get to the client @@ -105,7 +101,7 @@ private void _testPushEphemeral(String deliverSubject) throws Exception { validateRedAndTotal(5, messages0.size(), 5, 5); // Subscription 4 testing timeout <= 0 duration / millis - JetStreamSubscription sub4 = js.subscribe(tsc.subject(), options); + JetStreamSubscription sub4 = ctx.js.subscribe(ctx.subject(), options); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server sleep(1000); // give time to make sure the messages get to the client assertNotNull(sub4.nextMessage(Duration.ZERO)); @@ -128,68 +124,64 @@ public void testPushDurableWithDeliver() throws Exception { } private void _testPushDurable(boolean useDeliverSubject) throws Exception { - jsServer.run(nc -> { - // create the stream. - String stream = stream(); - String subjectDotGt = subject() + ".>"; - createMemoryStream(nc, stream, subjectDotGt); + runInSharedCustom((nc, ctx) -> { + String subjectDotGt = random() + ".>"; + ctx.createOrReplaceStream(subjectDotGt); - // Create our JetStream context. - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); + String stream = ctx.stream; // For async, create a dispatcher without a default handler. Dispatcher dispatcher = nc.createDispatcher(); // normal, no bind - _testPushDurableSubSync(jsm, js, stream, subjectDotGt, useDeliverSubject, false, (s, cc) -> { + _testPushDurableSubSync(ctx, stream, subjectDotGt, useDeliverSubject, false, (s, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.builder() .durable(cc.getDurable()) .deliverSubject(cc.getDeliverSubject()) .build(); - return js.subscribe(s, options); + return ctx.js.subscribe(s, options); }); - _testPushDurableSubAsync(jsm, js, dispatcher, stream, subjectDotGt, useDeliverSubject, false, (s, d, h, cc) -> { + _testPushDurableSubAsync(ctx, dispatcher, stream, subjectDotGt, useDeliverSubject, false, (s, d, h, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.builder() .durable(cc.getDurable()) .deliverSubject(cc.getDeliverSubject()) .build(); - return js.subscribe(s, d, h, false, options); + return ctx.js.subscribe(s, d, h, false, options); }); // use configuration, no bind - _testPushDurableSubSync(jsm, js, stream, subjectDotGt, useDeliverSubject, false, (s, cc) -> { + _testPushDurableSubSync(ctx, stream, subjectDotGt, useDeliverSubject, false, (s, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.builder().configuration(cc).build(); - return js.subscribe(s, options); + return ctx.js.subscribe(s, options); }); - _testPushDurableSubAsync(jsm, js, dispatcher, stream, subjectDotGt, useDeliverSubject, false, (s, d, h, cc) -> { + _testPushDurableSubAsync(ctx, dispatcher, stream, subjectDotGt, useDeliverSubject, false, (s, d, h, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.builder().configuration(cc).build(); - return js.subscribe(s, d, h, false, options); + return ctx.js.subscribe(s, d, h, false, options); }); if (useDeliverSubject) { // bind long form - _testPushDurableSubSync(jsm, js, stream, subjectDotGt, true, true, (s, cc) -> { + _testPushDurableSubSync(ctx, stream, subjectDotGt, true, true, (s, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.builder().stream(stream).durable(cc.getDurable()).bind(true).build(); - return js.subscribe(s, options); + return ctx.js.subscribe(s, options); }); - _testPushDurableSubAsync(jsm, js, dispatcher, stream, subjectDotGt, true, true, (s, d, h, cc) -> { + _testPushDurableSubAsync(ctx, dispatcher, stream, subjectDotGt, true, true, (s, d, h, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.builder().stream(stream).durable(cc.getDurable()).bind(true).build(); - return js.subscribe(s, d, h, false, options); + return ctx.js.subscribe(s, d, h, false, options); }); // bind short form - _testPushDurableSubSync(jsm, js, stream, subjectDotGt, true, true, (s, cc) -> { + _testPushDurableSubSync(ctx, stream, subjectDotGt, true, true, (s, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.bind(stream, cc.getDurable()); - return js.subscribe(s, options); + return ctx.js.subscribe(s, options); }); - _testPushDurableSubAsync(jsm, js, dispatcher, stream, subjectDotGt, true, true, (s, d, h, cc) -> { + _testPushDurableSubAsync(ctx, dispatcher, stream, subjectDotGt, true, true, (s, d, h, cc) -> { PushSubscribeOptions options = PushSubscribeOptions.bind(stream, cc.getDurable()); - return js.subscribe(s, d, h, false, options); + return ctx.js.subscribe(s, d, h, false, options); }); } }); @@ -203,10 +195,10 @@ private interface SubscriptionSupplierAsync { JetStreamSubscription get(String subject, Dispatcher dispatcher, MessageHandler handler, ConsumerConfiguration cc) throws IOException, JetStreamApiException; } - private void _testPushDurableSubSync(JetStreamManagement jsm, JetStream js, String stream, String subjectDotGt, boolean useDeliverSubject, boolean bind, SubscriptionSupplier supplier) throws Exception { - String subject = subjectDotGt.replace(">", subject()); - String durable = durable(); - String deliverSubject = useDeliverSubject ? deliver() : null; + private void _testPushDurableSubSync(JetStreamTestingContext ctx, String stream, String subjectDotGt, boolean useDeliverSubject, boolean bind, SubscriptionSupplier supplier) throws Exception { + String subject = subjectDotGt.replace(">", random()); + String durable = random(); + String deliverSubject = useDeliverSubject ? random() : null; ConsumerConfiguration cc = ConsumerConfiguration.builder() .durable(durable) .deliverSubject(deliverSubject) @@ -214,11 +206,11 @@ private void _testPushDurableSubSync(JetStreamManagement jsm, JetStream js, Stri .build(); if (bind) { - jsm.addOrUpdateConsumer(stream, cc); + ctx.jsm.addOrUpdateConsumer(stream, cc); } // publish some messages - jsPublish(js, subject, 1, 5); + jsPublish(ctx.js, subject, 1, 5); JetStreamSubscription sub = supplier.get(subject, cc); assertSubscription(sub, stream, durable, deliverSubject, false); @@ -246,20 +238,20 @@ private void _testPushDurableSubSync(JetStreamManagement jsm, JetStream js, Stri unsubscribeEnsureNotBound(sub); } - private void _testPushDurableSubAsync(JetStreamManagement jsm, JetStream js, Dispatcher dispatcher, String stream, String subjectDotGt, boolean useDeliverSubject, boolean bind, SubscriptionSupplierAsync supplier) throws IOException, JetStreamApiException, InterruptedException { - String subject = subjectDotGt.replace(">", subject()); - String deliverSubject = useDeliverSubject ? deliver() : null; + private void _testPushDurableSubAsync(JetStreamTestingContext ctx, Dispatcher dispatcher, String stream, String subjectDotGt, boolean useDeliverSubject, boolean bind, SubscriptionSupplierAsync supplier) throws IOException, JetStreamApiException, InterruptedException { + String subject = subjectDotGt.replace(">", random()); + String deliverSubject = useDeliverSubject ? random() : null; ConsumerConfiguration cc = ConsumerConfiguration.builder() - .durable(durable()) + .durable(random()) .deliverSubject(deliverSubject) .filterSubject(subject) .build(); if (bind) { - jsm.addOrUpdateConsumer(stream, cc); + ctx.jsm.addOrUpdateConsumer(stream, cc); } // publish some messages - jsPublish(js, subject, 5); + jsPublish(ctx.js, subject, 5); CountDownLatch msgLatch = new CountDownLatch(5); AtomicInteger received = new AtomicInteger(); @@ -283,22 +275,16 @@ private void _testPushDurableSubAsync(JetStreamManagement jsm, JetStream js, Dis @Test public void testCantPullOnPushSub() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - - JetStreamSubscription sub = js.subscribe(tsc.subject()); - assertSubscription(sub, tsc.stream, null, null, false); + runInShared((nc, ctx) -> { + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject()); + assertSubscription(sub, ctx.stream, null, null, false); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server assertCantPullOnPushSub(sub); unsubscribeEnsureNotBound(sub); PushSubscribeOptions pso = PushSubscribeOptions.builder().ordered(true).build(); - sub = js.subscribe(tsc.subject(), pso); + sub = ctx.js.subscribe(ctx.subject(), pso); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server assertCantPullOnPushSub(sub); @@ -322,17 +308,12 @@ private void assertCantPullOnPushSub(JetStreamSubscription sub) { @Test public void testHeadersOnly() throws Exception { - jsServer.run(nc -> { - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { PushSubscribeOptions pso = ConsumerConfiguration.builder().headersOnly(true).buildPushSubscribeOptions(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), pso); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server - jsPublish(js, tsc.subject(), 5); + jsPublish(ctx.js, ctx.subject(), 5); List messages = readMessagesAck(sub, Duration.ZERO, 5); assertEquals(5, messages.size()); @@ -344,20 +325,14 @@ public void testHeadersOnly() throws Exception { @Test public void testAcks() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { ConsumerConfiguration cc = ConsumerConfiguration.builder().ackWait(Duration.ofMillis(1500)).build(); PushSubscribeOptions pso = PushSubscribeOptions.builder().configuration(cc).build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), pso); nc.flush(Duration.ofSeconds(1)); // flush outgoing communication with/to the server // TERM - jsPublish(js, tsc.subject(), "TERM", 1); + jsPublish(ctx.js, ctx.subject(), "TERM", 1); Message message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); @@ -369,7 +344,7 @@ public void testAcks() throws Exception { assertNull(sub.nextMessage(Duration.ofMillis(500))); // Ack Wait timeout - jsPublish(js, tsc.subject(), "WAIT", 1); + jsPublish(ctx.js, ctx.subject(), "WAIT", 1); message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); @@ -385,7 +360,7 @@ public void testAcks() throws Exception { assertEquals("WAIT1", data); // In Progress - jsPublish(js, tsc.subject(), "PRO", 1); + jsPublish(ctx.js, ctx.subject(), "PRO", 1); message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); @@ -409,7 +384,7 @@ public void testAcks() throws Exception { assertNull(sub.nextMessage(Duration.ofMillis(500))); // ACK Sync - jsPublish(js, tsc.subject(), "ACKSYNC", 1); + jsPublish(ctx.js, ctx.subject(), "ACKSYNC", 1); message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); @@ -421,7 +396,7 @@ public void testAcks() throws Exception { assertNull(sub.nextMessage(Duration.ofMillis(500))); // NAK - jsPublish(js, tsc.subject(), "NAK", 1, 1); + jsPublish(ctx.js, ctx.subject(), "NAK", 1, 1); message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); @@ -439,7 +414,7 @@ public void testAcks() throws Exception { assertNull(sub.nextMessage(Duration.ofMillis(500))); - jsPublish(js, tsc.subject(), "NAK", 2, 1); + jsPublish(ctx.js, ctx.subject(), "NAK", 2, 1); message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); @@ -459,7 +434,7 @@ public void testAcks() throws Exception { assertNull(sub.nextMessage(Duration.ofMillis(500))); - jsPublish(js, tsc.subject(), "NAK", 3, 1); + jsPublish(ctx.js, ctx.subject(), "NAK", 3, 1); message = sub.nextMessage(Duration.ofSeconds(1)); assertNotNull(message); @@ -483,31 +458,28 @@ public void testAcks() throws Exception { @Test public void testDeliveryPolicy() throws Exception { - jsServer.run(nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); + runInSharedCustom((nc, ctx) -> { + String subject = ctx.subject(); + String subjectStar = subjectStar(subject); + ctx.createOrReplaceStream(subjectStar); - // create the stream. - String stream = stream(); - createMemoryStream(jsm, stream, SUBJECT_STAR); + String subjectA = subjectDot(subject, "A"); + String subjectB = subjectDot(subject, "B"); - String subjectA = subjectDot("A"); - String subjectB = subjectDot("B"); - - js.publish(subjectA, dataBytes(1)); - js.publish(subjectA, dataBytes(2)); + ctx.js.publish(subjectA, dataBytes(1)); + ctx.js.publish(subjectA, dataBytes(2)); sleep(1500); - js.publish(subjectA, dataBytes(3)); - js.publish(subjectB, dataBytes(91)); - js.publish(subjectB, dataBytes(92)); + ctx.js.publish(subjectA, dataBytes(3)); + ctx.js.publish(subjectB, dataBytes(91)); + ctx.js.publish(subjectB, dataBytes(92)); - jsm.deleteMessage(stream, 4); + ctx.jsm.deleteMessage(ctx.stream, 4); // DeliverPolicy.All PushSubscribeOptions pso = PushSubscribeOptions.builder() .configuration(ConsumerConfiguration.builder().deliverPolicy(DeliverPolicy.All).build()) .build(); - JetStreamSubscription sub = js.subscribe(subjectA, pso); + JetStreamSubscription sub = ctx.js.subscribe(subjectA, pso); Message m1 = sub.nextMessage(Duration.ofSeconds(1)); assertMessage(m1, 1); Message m2 = sub.nextMessage(Duration.ofSeconds(1)); @@ -519,7 +491,7 @@ public void testDeliveryPolicy() throws Exception { pso = PushSubscribeOptions.builder() .configuration(ConsumerConfiguration.builder().deliverPolicy(DeliverPolicy.Last).build()) .build(); - sub = js.subscribe(subjectA, pso); + sub = ctx.js.subscribe(subjectA, pso); Message m = sub.nextMessage(Duration.ofSeconds(1)); assertMessage(m, 3); assertNull(sub.nextMessage(Duration.ofMillis(200))); @@ -528,12 +500,12 @@ public void testDeliveryPolicy() throws Exception { pso = PushSubscribeOptions.builder() .configuration(ConsumerConfiguration.builder().deliverPolicy(DeliverPolicy.New).build()) .build(); - sub = js.subscribe(subjectA, pso); + sub = ctx.js.subscribe(subjectA, pso); assertNull(sub.nextMessage(Duration.ofSeconds(1))); // DeliverPolicy.New - New message between subscribe and next message - sub = js.subscribe(subjectA, pso); - js.publish(subjectA, dataBytes(4)); + sub = ctx.js.subscribe(subjectA, pso); + ctx.js.publish(subjectA, dataBytes(4)); m = sub.nextMessage(Duration.ofSeconds(1)); assertMessage(m, 4); @@ -544,7 +516,7 @@ public void testDeliveryPolicy() throws Exception { .startSequence(3) .build()) .build(); - sub = js.subscribe(subjectA, pso); + sub = ctx.js.subscribe(subjectA, pso); m = sub.nextMessage(Duration.ofSeconds(1)); assertMessage(m, 3); m = sub.nextMessage(Duration.ofSeconds(1)); @@ -557,7 +529,7 @@ public void testDeliveryPolicy() throws Exception { .startTime(m3.metaData().timestamp().minusSeconds(1)) .build()) .build(); - sub = js.subscribe(subjectA, pso); + sub = ctx.js.subscribe(subjectA, pso); m = sub.nextMessage(Duration.ofSeconds(1)); assertMessage(m, 3); m = sub.nextMessage(Duration.ofSeconds(1)); @@ -570,16 +542,16 @@ public void testDeliveryPolicy() throws Exception { .filterSubject(subjectA) .build()) .build(); - sub = js.subscribe(subjectA, pso); + sub = ctx.js.subscribe(subjectA, pso); m = sub.nextMessage(Duration.ofSeconds(1)); assertMessage(m, 4); // DeliverPolicy.ByStartSequence with a deleted record - PublishAck pa4 = js.publish(subjectA, dataBytes(4)); - PublishAck pa5 = js.publish(subjectA, dataBytes(5)); - js.publish(subjectA, dataBytes(6)); - jsm.deleteMessage(stream, pa4.getSeqno()); - jsm.deleteMessage(stream, pa5.getSeqno()); + PublishAck pa4 = ctx.js.publish(subjectA, dataBytes(4)); + PublishAck pa5 = ctx.js.publish(subjectA, dataBytes(5)); + ctx.js.publish(subjectA, dataBytes(6)); + ctx.jsm.deleteMessage(ctx.stream, pa4.getSeqno()); + ctx.jsm.deleteMessage(ctx.stream, pa5.getSeqno()); pso = PushSubscribeOptions.builder() .configuration(ConsumerConfiguration.builder() @@ -587,7 +559,7 @@ public void testDeliveryPolicy() throws Exception { .startSequence(pa4.getSeqno()) .build()) .build(); - sub = js.subscribe(subjectA, pso); + sub = ctx.js.subscribe(subjectA, pso); m = sub.nextMessage(Duration.ofSeconds(1)); assertMessage(m, 6); }); @@ -600,25 +572,16 @@ private void assertMessage(Message m, int i) { @Test public void testPushSyncFlowControl() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - Options.Builder ob = new Options.Builder().errorListener(listener); - - runInJsServer(ob, nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + Listener listener = new Listener(); + runInSharedOwnNc(listener, (nc, ctx) -> { byte[] data = new byte[1024*10]; - int MSG_COUNT = 1000; // publish some messages for (int x = 100_000; x < MSG_COUNT + 100_000; x++) { byte[] fill = ("" + x).getBytes(); System.arraycopy(fill, 0, data, 0, 6); - js.publish(NatsMessage.builder().subject(tsc.subject()).data(data).build()); + ctx.js.publish(NatsMessage.builder().subject(ctx.subject()).data(data).build()); } // reset the counters @@ -626,7 +589,7 @@ public void testPushSyncFlowControl() throws Exception { ConsumerConfiguration cc = ConsumerConfiguration.builder().flowControl(1000).build(); PushSubscribeOptions pso = PushSubscribeOptions.builder().configuration(cc).build(); - JetStreamSubscription sub = js.subscribe(tsc.subject(), pso); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject(), pso); for (int x = 0; x < MSG_COUNT; x++) { Message msg = sub.nextMessage(1000); set.add(new String(Arrays.copyOf(msg.getData(), 6))); @@ -635,24 +598,18 @@ public void testPushSyncFlowControl() throws Exception { } assertEquals(MSG_COUNT, set.size()); - assertFalse(listener.getFlowControlProcessedEvents().isEmpty()); + assertTrue(listener.getFlowControlCount() > 0); // coverage for subscribe options heartbeat directly cc = ConsumerConfiguration.builder().idleHeartbeat(100).build(); pso = PushSubscribeOptions.builder().configuration(cc).build(); - js.subscribe(tsc.subject(), pso); + ctx.js.subscribe(ctx.subject(), pso); }); } @Test public void testPendingLimits() throws Exception { - jsServer.run(nc -> { - // Create our JetStream context. - JetStream js = nc.jetStream(); - - // create the stream. - TestingStreamContainer tsc = new TestingStreamContainer(nc); - + runInShared((nc, ctx) -> { int customMessageLimit = 1000; int customByteLimit = 1024 * 1024; @@ -674,19 +631,19 @@ public void testPendingLimits() throws Exception { .pendingByteLimit(-1) .build(); - JetStreamSubscription syncSub = js.subscribe(tsc.subject(), psoDefaultSync); + JetStreamSubscription syncSub = ctx.js.subscribe(ctx.subject(), psoDefaultSync); assertEquals(Consumer.DEFAULT_MAX_MESSAGES, syncSub.getPendingMessageLimit()); assertEquals(Consumer.DEFAULT_MAX_BYTES, syncSub.getPendingByteLimit()); - syncSub = js.subscribe(tsc.subject(), psoCustomSync); + syncSub = ctx.js.subscribe(ctx.subject(), psoCustomSync); assertEquals(customMessageLimit, syncSub.getPendingMessageLimit()); assertEquals(customByteLimit, syncSub.getPendingByteLimit()); - syncSub = js.subscribe(tsc.subject(), psoCustomSyncUnlimited0); + syncSub = ctx.js.subscribe(ctx.subject(), psoCustomSyncUnlimited0); assertEquals(0, syncSub.getPendingMessageLimit()); assertEquals(0, syncSub.getPendingByteLimit()); - syncSub = js.subscribe(tsc.subject(), psoCustomSyncUnlimitedUnlimitedNegative); + syncSub = ctx.js.subscribe(ctx.subject(), psoCustomSyncUnlimitedUnlimitedNegative); assertEquals(0, syncSub.getPendingMessageLimit()); assertEquals(0, syncSub.getPendingByteLimit()); @@ -701,11 +658,11 @@ public void testPendingLimits() throws Exception { .pendingByteLimit(Consumer.DEFAULT_MAX_BYTES) .build(); - JetStreamSubscription subAsync = js.subscribe(tsc.subject(), d, m -> {}, false, psoAsyncDefault); + JetStreamSubscription subAsync = ctx.js.subscribe(ctx.subject(), d, m -> {}, false, psoAsyncDefault); assertEquals(Consumer.DEFAULT_MAX_MESSAGES, subAsync.getPendingMessageLimit()); assertEquals(Consumer.DEFAULT_MAX_BYTES, subAsync.getPendingByteLimit()); - subAsync = js.subscribe(tsc.subject(), d, m -> {}, false, psoAsyncNonDefaultValid); + subAsync = ctx.js.subscribe(ctx.subject(), d, m -> {}, false, psoAsyncNonDefaultValid); assertEquals(Consumer.DEFAULT_MAX_MESSAGES, subAsync.getPendingMessageLimit()); assertEquals(Consumer.DEFAULT_MAX_BYTES, subAsync.getPendingByteLimit()); @@ -725,10 +682,10 @@ public void testPendingLimits() throws Exception { .pendingByteLimit(0) .build(); - assertClientError(JsSubPushAsyncCantSetPending, () -> js.subscribe(SUBJECT, d, m ->{}, false, psoAsyncNopeMessages)); - assertClientError(JsSubPushAsyncCantSetPending, () -> js.subscribe(SUBJECT, d, m ->{}, false, psoAsyncNopeBytes)); - assertClientError(JsSubPushAsyncCantSetPending, () -> js.subscribe(SUBJECT, d, m ->{}, false, psoAsyncNope2Messages)); - assertClientError(JsSubPushAsyncCantSetPending, () -> js.subscribe(SUBJECT, d, m ->{}, false, psoAsyncNope2Bytes)); + assertClientError(JsSubPushAsyncCantSetPending, () -> ctx.js.subscribe(random(), d, m ->{}, false, psoAsyncNopeMessages)); + assertClientError(JsSubPushAsyncCantSetPending, () -> ctx.js.subscribe(random(), d, m ->{}, false, psoAsyncNopeBytes)); + assertClientError(JsSubPushAsyncCantSetPending, () -> ctx.js.subscribe(random(), d, m ->{}, false, psoAsyncNope2Messages)); + assertClientError(JsSubPushAsyncCantSetPending, () -> ctx.js.subscribe(random(), d, m ->{}, false, psoAsyncNope2Bytes)); }); } } diff --git a/src/test/java/io/nats/client/impl/JetStreamTestBase.java b/src/test/java/io/nats/client/impl/JetStreamTestBase.java index da4333189..5a38c03da 100644 --- a/src/test/java/io/nats/client/impl/JetStreamTestBase.java +++ b/src/test/java/io/nats/client/impl/JetStreamTestBase.java @@ -16,14 +16,13 @@ import io.nats.client.*; import io.nats.client.api.*; import io.nats.client.utils.TestBase; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.function.Executable; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -31,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class JetStreamTestBase extends TestBase { @@ -39,23 +39,9 @@ public class JetStreamTestBase extends TestBase { public static final String TestMetaV2 = "$JS.ACK.v2Domain.v2Hash.test-stream.test-consumer.1.2.3.1605139610113260000.4"; public static final String TestMetaVFuture = "$JS.ACK.v2Domain.v2Hash.test-stream.test-consumer.1.2.3.1605139610113260000.4.dont.care.how.many.more"; public static final String InvalidMetaNoAck = "$JS.nope.test-stream.test-consumer.1.2.3.1605139610113260000"; + public static final String InvalidMetaData = "$JS.ACK.v2Domain.v2Hash.test-stream.test-consumer.1.2.3.1605139610113260000.not-a-number"; public static final String InvalidMetaLt8Tokens = "$JS.ACK.less-than.8-tokens.1.2.3"; public static final String InvalidMeta10Tokens = "$JS.ACK.v2Domain.v2Hash.test-stream.test-consumer.1.2.3.1605139610113260000"; - public static final String InvalidMetaData = "$JS.ACK.v2Domain.v2Hash.test-stream.test-consumer.1.2.3.1605139610113260000.not-a-number"; - - public static LongRunningNatsTestServer jsServer; - - @BeforeAll - public static void beforeAll() throws IOException, InterruptedException { - jsServer = new LongRunningNatsTestServer(false, true, null); - } - - @AfterAll - public static void afterAll() throws Exception { - if (jsServer != null) { - jsServer.close(); - } - } public static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(1000); @@ -88,106 +74,6 @@ public NatsMessage getTestMessage(String replyTo, String sid) { return new IncomingMessageFactory(sid, "subj", replyTo, 0, false).getMessage(); } - // ---------------------------------------------------------------------------------------------------- - // Management - // ---------------------------------------------------------------------------------------------------- - public static class TestingStreamContainer { - private String defaultSubjectVariant; - private final String defaultNameVariant = TestBase.name(); - public final StreamInfo si; - public final String stream = stream(); - private final Map subjects = new HashMap<>(); - private final Map names = new HashMap<>(); - - public TestingStreamContainer(Connection nc) throws JetStreamApiException, IOException { - this(nc.jetStreamManagement(), (String[])null); - } - - public TestingStreamContainer(Connection nc, int subjectCount) throws JetStreamApiException, IOException { - this(nc.jetStreamManagement(), subjectCount); - } - - public TestingStreamContainer(Connection nc, String... subjects) throws JetStreamApiException, IOException { - this(nc.jetStreamManagement(), subjects); - } - - public TestingStreamContainer(JetStreamManagement jsm) throws JetStreamApiException, IOException { - this(jsm, (String[])null); - } - - public TestingStreamContainer(JetStreamManagement jsm, String... subjects) throws JetStreamApiException, IOException { - if (subjects == null) { - this.si = createMemoryStream(jsm, stream, subject()); - } - else { - this.si = createMemoryStream(jsm, stream, subjects); - } - } - - public TestingStreamContainer(JetStreamManagement jsm, int subjectCount) throws JetStreamApiException, IOException { - String[] subjects = new String[subjectCount]; - for (int x = 0; x < subjectCount; x++) { - subjects[x] = subject(x); - } - this.si = createMemoryStream(jsm, stream, subjects); - } - - public String subject() { - if (defaultSubjectVariant == null) { - defaultSubjectVariant = TestBase.variant(null); - } - return subject(defaultSubjectVariant); - } - - public String subject(Object variant) { - return subjects.computeIfAbsent(variant, TestBase::subject); - } - - public String consumerName() { - return consumerName(defaultNameVariant); - } - - public String consumerName(Object variant) { - return names.computeIfAbsent(variant, TestBase::name); - } - } - - public static StreamInfo createMemoryStream(JetStreamManagement jsm, String streamName, String... subjects) throws IOException, JetStreamApiException { - if (streamName == null) { - streamName = stream(); - } - - if (subjects == null || subjects.length == 0) { - subjects = new String[]{subject()}; - } - - StreamConfiguration sc = StreamConfiguration.builder() - .name(streamName) - .storageType(StorageType.Memory) - .subjects(subjects).build(); - - return jsm.addStream(sc); - } - - public static StreamInfo createMemoryStream(Connection nc, String streamName, String... subjects) - throws IOException, JetStreamApiException { - return createMemoryStream(nc.jetStreamManagement(), streamName, subjects); - } - - public static StreamInfo createDefaultTestStream(Connection nc) throws IOException, JetStreamApiException { - return createMemoryStream(nc, STREAM, SUBJECT); - } - - public static StreamInfo createDefaultTestStream(JetStreamManagement jsm) throws IOException, JetStreamApiException { - return createMemoryStream(jsm, STREAM, SUBJECT); - } - - public static T assertThrowsPrint(Class expectedType, Executable executable) { - T t = org.junit.jupiter.api.Assertions.assertThrows(expectedType, executable); - t.printStackTrace(); - return t; - } - // ---------------------------------------------------------------------------------------------------- // Publish / Read // ---------------------------------------------------------------------------------------------------- @@ -220,6 +106,12 @@ public static void jsPublish(JetStream js, String subject, int startId, int coun } } + public static void jsPublishNull(JetStream js, String subject, int count) throws IOException, JetStreamApiException { + for (int x = 0; x < count; x++) { + js.publish(subject, null); + } + } + public static void jsPublishBytes(JetStream js, String subject, int count, byte[] bytes) throws IOException, JetStreamApiException { for (int x = 0; x < count; x++) { js.publish(subject, bytes); @@ -242,10 +134,6 @@ public static PublishAck jsPublish(JetStream js, String subject, String data) th return js.publish(NatsMessage.builder().subject(subject).data(data.getBytes(StandardCharsets.US_ASCII)).build()); } - public static PublishAck jsPublish(JetStream js) throws IOException, JetStreamApiException { - return jsPublish(js, SUBJECT, DATA); - } - public static PublishAck jsPublish(JetStream js, String subject) throws IOException, JetStreamApiException { return jsPublish(js, subject, DATA); } @@ -323,7 +211,7 @@ public void run() { try { while (keepGoing.get()) { if (jitter > 0) { - Thread.sleep(ThreadLocalRandom.current().nextLong(jitter)); + sleep(ThreadLocalRandom.current().nextLong(jitter)); } js.publish(subject, dataBytes(++dataId)); } @@ -392,36 +280,6 @@ public static void assertIsJetStream(Message m) { assertNull(m.getStatus()); } - public static void assertLastIsStatus(List messages, int code) { - int lastIndex = messages.size() - 1; - for (int x = 0; x < lastIndex; x++) { - Message m = messages.get(x); - assertTrue(m.isJetStream()); - } - assertIsStatus(messages.get(lastIndex), code); - } - - public static void assertStarts408(List messages, int count408, int expectedJs) { - for (int x = 0; x < count408; x++) { - assertIsStatus(messages.get(x), 408); - } - int countedJs = 0; - int lastIndex = messages.size() - 1; - for (int x = count408; x <= lastIndex; x++) { - Message m = messages.get(x); - assertTrue(m.isJetStream()); - countedJs++; - } - assertEquals(expectedJs, countedJs); - } - - private static void assertIsStatus(Message statusMsg, int code) { - assertFalse(statusMsg.isJetStream()); - assertTrue(statusMsg.isStatusMessage()); - assertNotNull(statusMsg.getStatus()); - assertEquals(code, statusMsg.getStatus().getCode()); - } - public static void assertSource(JetStreamManagement jsm, String stream, Long msgCount, Long firstSeq) throws IOException, JetStreamApiException { sleep(1000); @@ -457,7 +315,10 @@ public static void assertConfig(String stream, Long msgCount, Long firstSeq, Str } public static void assertStreamSource(MessageInfo info, String stream, int i) { - String hval = info.getHeaders().get("Nats-Stream-Source").get(0); + assertNotNull(info.getHeaders()); + List nss = info.getHeaders().get("Nats-Stream-Source"); + assertNotNull(nss); + String hval = nss.get(0); assertTrue(hval.contains(stream)); assertTrue(hval.contains("" + i)); } diff --git a/src/test/java/io/nats/client/impl/JetStreamTestingContext.java b/src/test/java/io/nats/client/impl/JetStreamTestingContext.java new file mode 100644 index 000000000..2e69306f5 --- /dev/null +++ b/src/test/java/io/nats/client/impl/JetStreamTestingContext.java @@ -0,0 +1,210 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.impl; + +import io.nats.client.Connection; +import io.nats.client.JetStreamApiException; +import io.nats.client.api.*; +import io.nats.client.utils.TestBase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class JetStreamTestingContext implements AutoCloseable { + public final NatsJetStreamManagement jsm; + public final NatsJetStream js; + public final NatsKeyValueManagement kvm; + public final NatsObjectStoreManagement osm; + + private final String subjectBase; + private final Map subjects; + private final String consumerNameBase; + private final Map consumerNames; + public String stream; + public StreamInfo si; + + private final Set streams; + private final Set kvBuckets; + private final Set osBuckets; + + public JetStreamTestingContext(Connection nc, int subjectCount) throws JetStreamApiException, IOException { + jsm = (NatsJetStreamManagement)nc.jetStreamManagement(); + js = (NatsJetStream)jsm.jetStream(); + kvm = (NatsKeyValueManagement)jsm.keyValueManagement(); + osm = (NatsObjectStoreManagement)jsm.objectStoreManagement(); + + stream = TestBase.random(); + subjectBase = TestBase.random(); + subjects = new HashMap<>(); + consumerNameBase = TestBase.random(); + consumerNames = new HashMap<>(); + + streams = new HashSet<>(); + kvBuckets = new HashSet<>(); + osBuckets = new HashSet<>(); + + if (subjectCount > 0) { + createOrReplaceStream(subjectCount); + } + } + + // ---------------------------------------------------------------------------------------------------- + // JetStream + // ---------------------------------------------------------------------------------------------------- + public String[] getSubjects(int subjectCount) { + String[] subjects = new String[subjectCount]; + for (int x = 0; x < subjectCount; x++) { + subjects[x] = subject(x); + } + return subjects; + } + + public void createOrReplaceStream() throws JetStreamApiException, IOException { + createOrReplaceStream(scBuilder(subject(0)).build()); + } + + public void createOrReplaceStream(int subjectCount) throws JetStreamApiException, IOException { + createOrReplaceStream(scBuilder(getSubjects(subjectCount)).build()); + } + + public void createOrReplaceStream(String... subjects) throws JetStreamApiException, IOException { + createOrReplaceStream(scBuilder(subjects).build()); + } + + public void createOrReplaceStream(StreamConfiguration.Builder builder) throws JetStreamApiException, IOException { + createOrReplaceStream(builder.build()); + } + + public StreamInfo createOrReplaceStream(StreamConfiguration sc) throws JetStreamApiException, IOException { + String streamName = sc.getName(); + try { jsm.deleteStream(streamName); } catch (Exception ignore) {} + streams.remove(streamName); + si = jsm.addStream(sc); + streams.add(streamName); + return si; + } + + public StreamInfo addStream(StreamConfiguration sc) throws JetStreamApiException, IOException { + String streamName = sc.getName(); + si = jsm.addStream(sc); + streams.add(streamName); + return si; + } + + public StreamConfiguration.Builder scBuilder(int subjectCount) { + StreamConfiguration.Builder b = StreamConfiguration.builder() + .name(stream) + .storageType(StorageType.Memory); + if (subjectCount > 0) { + b.subjects(getSubjects(subjectCount)); + } + return b; + } + + public StreamConfiguration.Builder scBuilder(String... subjects) { + if (subjects.length == 0) { + subjects = new String[]{subject(0)}; + } + return StreamConfiguration.builder() + .name(stream) + .storageType(StorageType.Memory) + .subjects(subjects); + } + + public boolean deleteStream() throws JetStreamApiException, IOException { + boolean deleted = jsm.deleteStream(stream); + if (deleted) { + streams.remove(stream); + } + return deleted; + } + + public String subject() { + return subject(0); + } + + public String subject(Object variant) { + return subjects.computeIfAbsent(variant, v -> subjectBase + "_" + v); + } + + public String consumerName() { + return consumerNameBase; + } + + public String consumerName(Object variant) { + return consumerNames.computeIfAbsent(variant, v -> consumerNameBase + "-" + v); + } + + // ---------------------------------------------------------------------------------------------------- + // KeyValue + // ---------------------------------------------------------------------------------------------------- + public KeyValueConfiguration.Builder kvBuilder(String bucketName) { + return KeyValueConfiguration.builder() + .name(bucketName) + .storageType(StorageType.Memory); + } + + public KeyValueStatus kvCreate(String bucketName) throws JetStreamApiException, IOException { + return kvCreate(kvBuilder(bucketName).build()); + } + + public KeyValueStatus kvCreate(KeyValueConfiguration.Builder builder) throws JetStreamApiException, IOException { + return kvCreate(builder.build()); + } + + public KeyValueStatus kvCreate(KeyValueConfiguration kvc) throws JetStreamApiException, IOException { + kvBuckets.add(kvc.getBucketName()); + return kvm.create(kvc); + } + + // ---------------------------------------------------------------------------------------------------- + // ObjectStore + // ---------------------------------------------------------------------------------------------------- + public ObjectStoreConfiguration.Builder osBuilder(String bucketName) { + return ObjectStoreConfiguration.builder() + .name(bucketName) + .storageType(StorageType.Memory); + } + + public ObjectStoreStatus osCreate(String bucketName) throws JetStreamApiException, IOException { + return osCreate(osBuilder(bucketName).build()); + } + + public ObjectStoreStatus osCreate(ObjectStoreConfiguration.Builder builder) throws JetStreamApiException, IOException { + return osCreate(builder.build()); + } + + public ObjectStoreStatus osCreate(ObjectStoreConfiguration osc) throws JetStreamApiException, IOException { + osBuckets.add(osc.getBucketName()); + return osm.create(osc); + } + + @Override + public void close() throws Exception { + for (String strm : streams) { + try { jsm.deleteStream(strm); } catch (Exception ignore) {} + } + + for (String bucket : kvBuckets) { + try { kvm.delete(bucket); } catch (Exception ignore) {} + } + + for (String bucket : osBuckets) { + try { osm.delete(bucket); } catch (Exception ignore) {} + } + } +} diff --git a/src/test/java/io/nats/client/impl/KeyValueTests.java b/src/test/java/io/nats/client/impl/KeyValueTests.java index d0313e4fc..fa3087866 100644 --- a/src/test/java/io/nats/client/impl/KeyValueTests.java +++ b/src/test/java/io/nats/client/impl/KeyValueTests.java @@ -15,7 +15,7 @@ import io.nats.client.*; import io.nats.client.api.*; import io.nats.client.support.NatsKeyValueUtil; -import io.nats.client.utils.TestBase; +import io.nats.client.utils.VersionUtils; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -34,6 +34,8 @@ import static io.nats.client.api.KeyValueWatchOption.*; import static io.nats.client.support.NatsConstants.DOT; import static io.nats.client.support.NatsJetStreamConstants.SERVER_DEFAULT_DUPLICATE_WINDOW_MS; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class KeyValueTests extends JetStreamTestBase { @@ -41,36 +43,28 @@ public class KeyValueTests extends JetStreamTestBase { @Test public void testWorkflow() throws Exception { long now = ZonedDateTime.now().toEpochSecond(); - - String byteKey = "key.byte" + variant(); - String stringKey = "key.string" + variant(); - String longKey = "key.long" + variant(); - String notFoundKey = "notFound" + variant(); + String byteKey = "key.byte" + random(); + String stringKey = "key.string" + random(); + String longKey = "key.long" + random(); + String notFoundKey = "notFound" + random(); String byteValue1 = "Byte Value 1"; String byteValue2 = "Byte Value 2"; String stringValue1 = "String Value 1"; String stringValue2 = "String Value 2"; - jsServer.run(TestBase::atLeast2_10, nc -> { - // get the kv management context - KeyValueManagement kvm = nc.keyValueManagement(); + runInSharedCustom(VersionUtils::atLeast2_10, (nc, ctx) -> { nc.keyValueManagement(KeyValueOptions.builder(DEFAULT_JS_OPTIONS).build()); // coverage Map metadata = new HashMap<>(); metadata.put(META_KEY, META_VALUE); // create the bucket - String bucket = bucket(); - String desc = variant(); - KeyValueConfiguration kvc = KeyValueConfiguration.builder() - .name(bucket) - .description(desc) - .maxHistoryPerKey(3) - .storageType(StorageType.Memory) - .metadata(metadata) - .build(); - - KeyValueStatus status = kvm.create(kvc); + String bucket = random(); + String desc = random(); + KeyValueStatus status = ctx.kvCreate(ctx.kvBuilder(bucket) + .description(desc) + .maxHistoryPerKey(3) + .metadata(metadata)); assertInitialStatus(status, bucket, desc); // get the kv context for the specific bucket @@ -91,9 +85,20 @@ public void testWorkflow() throws Exception { // retrieve the values. all types are stored as bytes // so you can always get the bytes directly - assertEquals(byteValue1, new String(kv.get(byteKey).getValue())); - assertEquals(stringValue1, new String(kv.get(stringKey).getValue())); - assertEquals("1", new String(kv.get(longKey).getValue())); + KeyValueEntry entry = kv.get(byteKey); + assertNotNull(entry); + assertNotNull(entry.getValue()); + assertEquals(byteValue1, new String(entry.getValue())); + + entry = kv.get(stringKey); + assertNotNull(entry); + assertNotNull(entry.getValue()); + assertEquals(stringValue1, new String(entry.getValue())); + + entry = kv.get(longKey); + assertNotNull(entry); + assertNotNull(entry.getValue()); + assertEquals("1", new String(entry.getValue())); // if you know the value is not binary and can safely be read // as a UTF-8 string, the getStringValue method is ok to use @@ -128,9 +133,9 @@ public void testWorkflow() throws Exception { assertHistory(longHistory, kv.history(longKey)); // let's check the bucket info - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 3, 3); - status = kvm.getBucketInfo(bucket); // coverage for deprecated + status = ctx.kvm.getBucketInfo(bucket); assertState(status, 3, 3); // delete a key. Its entry will still exist, but its value is null @@ -144,7 +149,7 @@ public void testWorkflow() throws Exception { assertNotEquals(byteHistory.get(0).hashCode(), byteHistory.get(1).hashCode()); // let's check the bucket info - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 4, 4); // if the key has been deleted no entry is returned @@ -159,7 +164,10 @@ public void testWorkflow() throws Exception { assertEquals(7, kv.put(longKey, 2)); // values after updates - assertEquals(byteValue2, new String(kv.get(byteKey).getValue())); + entry = kv.get(byteKey); + assertNotNull(entry); + assertNotNull(entry.getValue()); + assertEquals(byteValue2, new String(entry.getValue())); assertEquals(stringValue2, kv.get(stringKey).getValueAsString()); assertEquals(2, kv.get(longKey).getValueAsLong()); @@ -177,7 +185,7 @@ public void testWorkflow() throws Exception { assertHistory(longHistory, kv.history(longKey)); // let's check the bucket info - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 7, 7); // make sure it only keeps the correct amount of history @@ -188,7 +196,7 @@ public void testWorkflow() throws Exception { assertEntry(bucket, longKey, KeyValueOperation.PUT, 8, "3", now, kv.get(longKey))); assertHistory(longHistory, kv.history(longKey)); - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 8, 8); // this would be the 4th entry for the longKey @@ -203,7 +211,7 @@ public void testWorkflow() throws Exception { assertHistory(longHistory, kv.history(longKey)); // record count does not increase - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 8, 9); assertKeys(kv.keys(), byteKey, stringKey, longKey); @@ -223,7 +231,7 @@ public void testWorkflow() throws Exception { longHistory.add(KeyValueOperation.PURGE); assertHistory(longHistory, kv.history(longKey)); - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 6, 10); // only 2 keys now @@ -236,7 +244,7 @@ public void testWorkflow() throws Exception { byteHistory.add(KeyValueOperation.PURGE); assertHistory(byteHistory, kv.history(byteKey)); - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 4, 11); // only 1 key now @@ -249,7 +257,7 @@ public void testWorkflow() throws Exception { stringHistory.add(KeyValueOperation.PURGE); assertHistory(stringHistory, kv.history(stringKey)); - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 3, 12); // no more keys left @@ -259,7 +267,7 @@ public void testWorkflow() throws Exception { // clear things KeyValuePurgeOptions kvpo = KeyValuePurgeOptions.builder().deleteMarkersNoThreshold().build(); kv.purgeDeletes(kvpo); - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 0, 12); longHistory.clear(); @@ -292,15 +300,15 @@ public void testWorkflow() throws Exception { assertHistory(longHistory, kv.history(longKey)); assertHistory(stringHistory, kv.history(stringKey)); - status = kvm.getStatus(bucket); + status = ctx.kvm.getStatus(bucket); assertState(status, 5, 17); // delete the bucket - kvm.delete(bucket); - assertThrows(JetStreamApiException.class, () -> kvm.delete(bucket)); - assertThrows(JetStreamApiException.class, () -> kvm.getStatus(bucket)); + ctx.kvm.delete(bucket); + assertThrows(JetStreamApiException.class, () -> ctx.kvm.delete(bucket)); + assertThrows(JetStreamApiException.class, () -> ctx.kvm.getStatus(bucket)); - assertEquals(0, kvm.getBucketNames().size()); + assertEquals(0, ctx.kvm.getBucketNames().size()); }); } @@ -321,7 +329,9 @@ private void assertInitialStatus(KeyValueStatus status, String bucket, String de assertEquals(3, kvc.getMaxHistoryPerKey()); assertEquals(-1, status.getMaxBucketSize()); assertEquals(-1, kvc.getMaxBucketSize()); + //noinspection deprecation assertEquals(-1, status.getMaxValueSize()); // COVERAGE for deprecated + //noinspection deprecation assertEquals(-1, kvc.getMaxValueSize()); assertEquals(-1, status.getMaximumValueSize()); assertEquals(-1, kvc.getMaximumValueSize()); @@ -346,17 +356,11 @@ private void assertInitialStatus(KeyValueStatus status, String bucket, String de @Test public void testGetRevision() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); + runInSharedCustom((nc, ctx) -> { + String bucket = random(); + ctx.kvCreate(ctx.kvBuilder(bucket).maxHistoryPerKey(2)); - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .maxHistoryPerKey(2) - .build()); - - String key = key(); + String key = random(); KeyValue kv = nc.keyValue(bucket); long seq1 = kv.put(key, 1); long seq2 = kv.put(key, 2); @@ -384,15 +388,9 @@ public void testGetRevision() throws Exception { @Test public void testKeys() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - - // create bucket - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .build()); + runInSharedCustom((nc, ctx) -> { + String bucket = random(); + ctx.kvCreate(bucket); KeyValue kv = nc.keyValue(bucket); for (int x = 1; x <= 10; x++) { @@ -474,19 +472,14 @@ private static List getKeysFromQueue(LinkedBlockingQueue q) { @Test public void testMaxHistoryPerKey() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); + runInSharedCustom((nc, ctx) -> { + String bucket1 = random(); + String bucket2 = random(); - String bucket1 = bucket(); - String bucket2 = bucket(); // default maxHistoryPerKey is 1 - kvm.create(KeyValueConfiguration.builder() - .name(bucket1) - .storageType(StorageType.Memory) - .build()); - + ctx.kvCreate(bucket1); KeyValue kv = nc.keyValue(bucket1); - String key = key(); + String key = random(); kv.put(key, 1); kv.put(key, 2); @@ -494,13 +487,9 @@ public void testMaxHistoryPerKey() throws Exception { assertEquals(1, history.size()); assertEquals(2, history.get(0).getValueAsLong()); - kvm.create(KeyValueConfiguration.builder() - .name(bucket2) - .maxHistoryPerKey(2) - .storageType(StorageType.Memory) - .build()); + ctx.kvCreate(ctx.kvBuilder(bucket2).maxHistoryPerKey(2)); - key = key(); + key = random(); kv = nc.keyValue(bucket2); kv.put(key, 1); kv.put(key, 2); @@ -515,18 +504,13 @@ public void testMaxHistoryPerKey() throws Exception { @Test public void testCreateUpdate() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - - String bucket = bucket(); + runInSharedCustom((nc, ctx) -> { + String bucket = random(); // doesn't exist yet - assertThrows(JetStreamApiException.class, () -> kvm.getStatus(bucket)); + assertThrows(JetStreamApiException.class, () -> ctx.kvm.getStatus(bucket)); - KeyValueStatus kvs = kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .build()); + KeyValueStatus kvs = ctx.kvCreate(bucket); assertEquals(bucket, kvs.getBucketName()); assertNull(kvs.getDescription()); @@ -539,7 +523,7 @@ public void testCreateUpdate() throws Exception { assertEquals(0, kvs.getEntryCount()); assertEquals("JetStream", kvs.getBackingStore()); - String key = key(); + String key = random(); KeyValue kv = nc.keyValue(bucket); kv.put(key, 1); kv.put(key, 2); @@ -548,8 +532,8 @@ public void testCreateUpdate() throws Exception { assertEquals(1, history.size()); assertEquals(2, history.get(0).getValueAsLong()); - boolean compression = atLeast2_10(ensureRunServerInfo()); - String desc = variant(); + boolean compression = VersionUtils.atLeast2_10(); + String desc = random(); KeyValueConfiguration kvc = KeyValueConfiguration.builder(kvs.getConfiguration()) .description(desc) .maxHistoryPerKey(3) @@ -559,12 +543,13 @@ public void testCreateUpdate() throws Exception { .compression(compression) .build(); - kvs = kvm.update(kvc); + kvs = ctx.kvm.update(kvc); assertEquals(bucket, kvs.getBucketName()); assertEquals(desc, kvs.getDescription()); assertEquals(3, kvs.getMaxHistoryPerKey()); assertEquals(10_000, kvs.getMaxBucketSize()); + //noinspection deprecation assertEquals(100, kvs.getMaxValueSize()); // COVERAGE for deprecated assertEquals(100, kvs.getMaximumValueSize()); assertEquals(Duration.ofHours(1), kvs.getTtl()); @@ -581,25 +566,19 @@ public void testCreateUpdate() throws Exception { KeyValueConfiguration kvcStor = KeyValueConfiguration.builder(kvs.getConfiguration()) .storageType(StorageType.File) .build(); - assertThrows(JetStreamApiException.class, () -> kvm.update(kvcStor)); + assertThrows(JetStreamApiException.class, () -> ctx.kvm.update(kvcStor)); }); } @Test public void testHistoryDeletePurge() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - + runInSharedCustom((nc, ctx) -> { // create bucket - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .maxHistoryPerKey(64) - .build()); + String bucket = random(); + ctx.kvCreate(ctx.kvBuilder(bucket).maxHistoryPerKey(64)); KeyValue kv = nc.keyValue(bucket); - String key = key(); + String key = random(); kv.put(key, "a"); kv.put(key, "b"); kv.put(key, "c"); @@ -618,19 +597,13 @@ public void testHistoryDeletePurge() throws Exception { @Test public void testAtomicDeleteAtomicPurge() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - + runInSharedCustom((nc, ctx) -> { // create bucket - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .maxHistoryPerKey(64) - .build()); + String bucket = random(); + ctx.kvCreate(ctx.kvBuilder(bucket).maxHistoryPerKey(64)); KeyValue kv = nc.keyValue(bucket); - String key = key(); + String key = random(); kv.put(key, "a"); kv.put(key, "b"); kv.put(key, "c"); @@ -669,47 +642,42 @@ public void testAtomicDeleteAtomicPurge() throws Exception { // Correct revision writes roll-up purge tombstone kv.purge(key, 5); - assertHistory(Arrays.asList(KeyValueOperation.PURGE), kv.history(key)); + assertHistory(Collections.singletonList(KeyValueOperation.PURGE), kv.history(key)); }); } @Test public void testPurgeDeletes() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - + runInSharedCustom((nc, ctx) -> { // create bucket - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .maxHistoryPerKey(64) - .build()); + String bucket = random(); + ctx.kvCreate(ctx.kvBuilder(bucket).maxHistoryPerKey(64)); KeyValue kv = nc.keyValue(bucket); - kv.put(key(1), "a"); - kv.delete(key(1)); - kv.put(key(2), "b"); - kv.put(key(3), "c"); - kv.put(key(4), "d"); - kv.purge(key(4)); + String keyA = random(); + String keyD = random(); + kv.put(keyA, "a"); + kv.delete(keyA); + kv.put(random(), "b"); + kv.put(random(), "c"); + kv.put(keyD, "d"); + kv.purge(keyD); - JetStream js = nc.jetStream(); - assertPurgeDeleteEntries(js, bucket, new String[]{"a", null, "b", "c", null}); + assertPurgeDeleteEntries(ctx.js, bucket, new String[]{"a", null, "b", "c", null}); // default purge deletes uses the default threshold // so no markers will be deleted kv.purgeDeletes(); - assertPurgeDeleteEntries(js, bucket, new String[]{null, "b", "c", null}); + assertPurgeDeleteEntries(ctx.js, bucket, new String[]{null, "b", "c", null}); // deleteMarkersThreshold of 0 the default threshold // so no markers will be deleted kv.purgeDeletes(KeyValuePurgeOptions.builder().deleteMarkersThreshold(0).build()); - assertPurgeDeleteEntries(js, bucket, new String[]{null, "b", "c", null}); + assertPurgeDeleteEntries(ctx.js, bucket, new String[]{null, "b", "c", null}); // no threshold causes all to be removed kv.purgeDeletes(KeyValuePurgeOptions.builder().deleteMarkersNoThreshold().build()); - assertPurgeDeleteEntries(js, bucket, new String[]{"b", "c"}); + assertPurgeDeleteEntries(ctx.js, bucket, new String[]{"b", "c"}); }); } @@ -734,20 +702,14 @@ private void assertPurgeDeleteEntries(JetStream js, String bucket, String[] expe @Test public void testCreateAndUpdate() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - + runInSharedCustom((nc, ctx) -> { // create bucket - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .maxHistoryPerKey(64) - .build()); + String bucket = random(); + ctx.kvCreate(ctx.kvBuilder(bucket).maxHistoryPerKey(64)); KeyValue kv = nc.keyValue(bucket); - String key = key(); + String key = random(); // 1. allowed to create something that does not exist long rev1 = kv.create(key, "a".getBytes()); @@ -800,7 +762,7 @@ private void assertHistory(List manualHistory, List apiHi for (int x = 0; x < apiHistory.size(); x++) { Object o = manualHistory.get(x); if (o instanceof KeyValueOperation) { - assertEquals((KeyValueOperation)o, apiHistory.get(x).getOperation()); + assertEquals(o, apiHistory.get(x).getOperation()); } else { assertKvEquals((KeyValueEntry)o, apiHistory.get(x)); @@ -816,6 +778,7 @@ private KeyValueEntry assertEntry(String bucket, String key, KeyValueOperation o assertEquals(seq, entry.getRevision()); assertEquals(0, entry.getDelta()); if (op == KeyValueOperation.PUT) { + assertNotNull(entry.getValue()); assertEquals(value, new String(entry.getValue())); } else { @@ -841,37 +804,24 @@ private void assertKvEquals(KeyValueEntry kv1, KeyValueEntry kv2) { @Test public void testManageGetBucketNamesStatuses() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - - // create bucket 1 - String bucket1 = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket1) - .storageType(StorageType.Memory) - .build()); - - // create bucket 2 - String bucket2 = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket2) - .storageType(StorageType.Memory) - .build()); + runInSharedCustom((nc, ctx) -> { + String bucket1 = random(); + ctx.kvCreate(bucket1); - createMemoryStream(nc, stream(1)); - createMemoryStream(nc, stream(2)); + String bucket2 = random(); + ctx.kvCreate(bucket2); - List infos = kvm.getStatuses(); - assertEquals(2, infos.size()); + List statuses = ctx.kvm.getStatuses(); + assertEquals(2, statuses.size()); List buckets = new ArrayList<>(); - for (KeyValueStatus status : infos) { - buckets.add(status.getBucketName()); + for (KeyValueStatus s : statuses) { + buckets.add(s.getBucketName()); } assertEquals(2, buckets.size()); assertTrue(buckets.contains(bucket1)); assertTrue(buckets.contains(bucket2)); - buckets = kvm.getBucketNames(); + buckets = ctx.kvm.getBucketNames(); assertTrue(buckets.contains(bucket1)); assertTrue(buckets.contains(bucket2)); }); @@ -1000,51 +950,45 @@ public void testWatch() throws Exception { List allKeys = Arrays.asList(TEST_WATCH_KEY_1, TEST_WATCH_KEY_2, TEST_WATCH_KEY_NULL); - jsServer.run(nc -> { - _testWatch(nc, key1FullWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1FullWatcher, key1FullWatcher.watchOptions)); - _testWatch(nc, key1MetaWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1MetaWatcher, key1MetaWatcher.watchOptions)); - _testWatch(nc, key1StartNewWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1StartNewWatcher, key1StartNewWatcher.watchOptions)); - _testWatch(nc, key1StartAllWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1StartAllWatcher, key1StartAllWatcher.watchOptions)); - _testWatch(nc, key2FullWatcher, key2AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2FullWatcher, key2FullWatcher.watchOptions)); - _testWatch(nc, key2MetaWatcher, key2AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2MetaWatcher, key2MetaWatcher.watchOptions)); - _testWatch(nc, allAllFullWatcher, allExpecteds, -1, kv -> kv.watchAll(allAllFullWatcher, allAllFullWatcher.watchOptions)); - _testWatch(nc, allAllMetaWatcher, allExpecteds, -1, kv -> kv.watchAll(allAllMetaWatcher, allAllMetaWatcher.watchOptions)); - _testWatch(nc, allIgDelFullWatcher, allPutsExpecteds, -1, kv -> kv.watchAll(allIgDelFullWatcher, allIgDelFullWatcher.watchOptions)); - _testWatch(nc, allIgDelMetaWatcher, allPutsExpecteds, -1, kv -> kv.watchAll(allIgDelMetaWatcher, allIgDelMetaWatcher.watchOptions)); - _testWatch(nc, starFullWatcher, allExpecteds, -1, kv -> kv.watch("key.*", starFullWatcher, starFullWatcher.watchOptions)); - _testWatch(nc, starMetaWatcher, allExpecteds, -1, kv -> kv.watch("key.*", starMetaWatcher, starMetaWatcher.watchOptions)); - _testWatch(nc, gtFullWatcher, allExpecteds, -1, kv -> kv.watch("key.>", gtFullWatcher, gtFullWatcher.watchOptions)); - _testWatch(nc, gtMetaWatcher, allExpecteds, -1, kv -> kv.watch("key.>", gtMetaWatcher, gtMetaWatcher.watchOptions)); - _testWatch(nc, key1AfterWatcher, purgeOnlyExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterWatcher, key1AfterWatcher.watchOptions)); - _testWatch(nc, key1AfterIgDelWatcher, noExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterIgDelWatcher, key1AfterIgDelWatcher.watchOptions)); - _testWatch(nc, key1AfterStartNewWatcher, noExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterStartNewWatcher, key1AfterStartNewWatcher.watchOptions)); - _testWatch(nc, key1AfterStartFirstWatcher, purgeOnlyExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterStartFirstWatcher, key1AfterStartFirstWatcher.watchOptions)); - _testWatch(nc, key2AfterWatcher, key2AfterExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2AfterWatcher, key2AfterWatcher.watchOptions)); - _testWatch(nc, key2AfterStartNewWatcher, noExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2AfterStartNewWatcher, key2AfterStartNewWatcher.watchOptions)); - _testWatch(nc, key2AfterStartFirstWatcher, key2AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2AfterStartFirstWatcher, key2AfterStartFirstWatcher.watchOptions)); - _testWatch(nc, key1FromRevisionAfterWatcher, key1FromRevisionExpecteds, 2, kv -> kv.watch(TEST_WATCH_KEY_1, key1FromRevisionAfterWatcher, 2, key1FromRevisionAfterWatcher.watchOptions)); - _testWatch(nc, allFromRevisionAfterWatcher, allFromRevisionExpecteds, 2, kv -> kv.watchAll(allFromRevisionAfterWatcher, 2, allFromRevisionAfterWatcher.watchOptions)); + runInSharedCustom((nc, ctx) -> { + _testWatch(ctx, key1FullWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1FullWatcher, key1FullWatcher.watchOptions)); + _testWatch(ctx, key1MetaWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1MetaWatcher, key1MetaWatcher.watchOptions)); + _testWatch(ctx, key1StartNewWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1StartNewWatcher, key1StartNewWatcher.watchOptions)); + _testWatch(ctx, key1StartAllWatcher, key1AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1StartAllWatcher, key1StartAllWatcher.watchOptions)); + _testWatch(ctx, key2FullWatcher, key2AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2FullWatcher, key2FullWatcher.watchOptions)); + _testWatch(ctx, key2MetaWatcher, key2AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2MetaWatcher, key2MetaWatcher.watchOptions)); + _testWatch(ctx, allAllFullWatcher, allExpecteds, -1, kv -> kv.watchAll(allAllFullWatcher, allAllFullWatcher.watchOptions)); + _testWatch(ctx, allAllMetaWatcher, allExpecteds, -1, kv -> kv.watchAll(allAllMetaWatcher, allAllMetaWatcher.watchOptions)); + _testWatch(ctx, allIgDelFullWatcher, allPutsExpecteds, -1, kv -> kv.watchAll(allIgDelFullWatcher, allIgDelFullWatcher.watchOptions)); + _testWatch(ctx, allIgDelMetaWatcher, allPutsExpecteds, -1, kv -> kv.watchAll(allIgDelMetaWatcher, allIgDelMetaWatcher.watchOptions)); + _testWatch(ctx, starFullWatcher, allExpecteds, -1, kv -> kv.watch("key.*", starFullWatcher, starFullWatcher.watchOptions)); + _testWatch(ctx, starMetaWatcher, allExpecteds, -1, kv -> kv.watch("key.*", starMetaWatcher, starMetaWatcher.watchOptions)); + _testWatch(ctx, gtFullWatcher, allExpecteds, -1, kv -> kv.watch("key.>", gtFullWatcher, gtFullWatcher.watchOptions)); + _testWatch(ctx, gtMetaWatcher, allExpecteds, -1, kv -> kv.watch("key.>", gtMetaWatcher, gtMetaWatcher.watchOptions)); + _testWatch(ctx, key1AfterWatcher, purgeOnlyExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterWatcher, key1AfterWatcher.watchOptions)); + _testWatch(ctx, key1AfterIgDelWatcher, noExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterIgDelWatcher, key1AfterIgDelWatcher.watchOptions)); + _testWatch(ctx, key1AfterStartNewWatcher, noExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterStartNewWatcher, key1AfterStartNewWatcher.watchOptions)); + _testWatch(ctx, key1AfterStartFirstWatcher, purgeOnlyExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_1, key1AfterStartFirstWatcher, key1AfterStartFirstWatcher.watchOptions)); + _testWatch(ctx, key2AfterWatcher, key2AfterExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2AfterWatcher, key2AfterWatcher.watchOptions)); + _testWatch(ctx, key2AfterStartNewWatcher, noExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2AfterStartNewWatcher, key2AfterStartNewWatcher.watchOptions)); + _testWatch(ctx, key2AfterStartFirstWatcher, key2AllExpecteds, -1, kv -> kv.watch(TEST_WATCH_KEY_2, key2AfterStartFirstWatcher, key2AfterStartFirstWatcher.watchOptions)); + _testWatch(ctx, key1FromRevisionAfterWatcher, key1FromRevisionExpecteds, 2, kv -> kv.watch(TEST_WATCH_KEY_1, key1FromRevisionAfterWatcher, 2, key1FromRevisionAfterWatcher.watchOptions)); + _testWatch(ctx, allFromRevisionAfterWatcher, allFromRevisionExpecteds, 2, kv -> kv.watchAll(allFromRevisionAfterWatcher, 2, allFromRevisionAfterWatcher.watchOptions)); List keys = Arrays.asList(TEST_WATCH_KEY_1, TEST_WATCH_KEY_2); - _testWatch(nc, key1Key2FromRevisionAfterWatcher, allFromRevisionExpecteds, 2, kv -> kv.watch(keys, key1Key2FromRevisionAfterWatcher, 2, key1Key2FromRevisionAfterWatcher.watchOptions)); + _testWatch(ctx, key1Key2FromRevisionAfterWatcher, allFromRevisionExpecteds, 2, kv -> kv.watch(keys, key1Key2FromRevisionAfterWatcher, 2, key1Key2FromRevisionAfterWatcher.watchOptions)); - if (atLeast2_10()) { - _testWatch(nc, multipleFullWatcher, allExpecteds, -1, kv -> kv.watch(allKeys, multipleFullWatcher, multipleFullWatcher.watchOptions)); - _testWatch(nc, multipleMetaWatcher, allExpecteds, -1, kv -> kv.watch(allKeys, multipleMetaWatcher, multipleMetaWatcher.watchOptions)); + if (VersionUtils.atLeast2_10()) { + _testWatch(ctx, multipleFullWatcher, allExpecteds, -1, kv -> kv.watch(allKeys, multipleFullWatcher, multipleFullWatcher.watchOptions)); + _testWatch(ctx, multipleMetaWatcher, allExpecteds, -1, kv -> kv.watch(allKeys, multipleMetaWatcher, multipleMetaWatcher.watchOptions)); } }); } - private void _testWatch(Connection nc, TestKeyValueWatcher watcher, Object[] expectedKves, long fromRevision, TestWatchSubSupplier supplier) throws Exception { - KeyValueManagement kvm = nc.keyValueManagement(); - - String bucket = variant() + watcher.name + "Bucket"; - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .maxHistoryPerKey(10) - .storageType(StorageType.Memory) - .build()); + private void _testWatch(JetStreamTestingContext ctx, TestKeyValueWatcher watcher, Object[] expectedKves, long fromRevision, TestWatchSubSupplier supplier) throws Exception { + String bucket = random() + watcher.name; + ctx.kvCreate(ctx.kvBuilder(bucket).maxHistoryPerKey(10)); - KeyValue kv = nc.keyValue(bucket); + KeyValue kv = ctx.jsm.keyValue(bucket); NatsKeyValueWatchSubscription sub = null; @@ -1083,8 +1027,9 @@ private void _testWatch(Connection nc, TestKeyValueWatcher watcher, Object[] exp // only testing this consumer name prefix on not meta only tests // this way there is coverage on working with and without a prefix if (!watcher.metaOnly) { - List names = nc.jetStreamManagement().getConsumerNames("KV_" + bucket); + List names = ctx.jsm.getConsumerNames("KV_" + bucket); assertEquals(1, names.size()); + assertNotNull(watcher.getConsumerNamePrefix()); assertTrue(names.get(0).startsWith(watcher.getConsumerNamePrefix())); } @@ -1093,7 +1038,6 @@ private void _testWatch(Connection nc, TestKeyValueWatcher watcher, Object[] exp validateWatcher(expectedKves, watcher); //noinspection ConstantConditions sub.unsubscribe(); - kvm.delete(bucket); } private void validateWatcher(Object[] expectedKves, TestKeyValueWatcher watcher) { @@ -1143,13 +1087,14 @@ else if (expected instanceof String) { } @Test - public void testWithAccount() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/kv_account.conf", false)) { - Options acctA = new Options.Builder().server(ts.getURI()).userInfo("a", "a").build(); - Options acctI = new Options.Builder().server(ts.getURI()).userInfo("i", "i").inboxPrefix("ForI").build(); - - try (Connection connUserA = Nats.connect(acctA); Connection connUserI = Nats.connect(acctI)) { - + public void + testWithAccount() throws Exception { + runInConfiguredServer("kv_account.conf", ts -> { + Options acctA = optionsBuilder(ts).userInfo("a", "a").build(); + Options acctI = optionsBuilder(ts).userInfo("i", "i").inboxPrefix("ForI").build(); + + try (Connection connUserA = Nats.connect(acctA); + Connection connUserI = Nats.connect(acctI)) { // some prep KeyValueOptions jsOpt_UserI_BucketA_WithPrefix = KeyValueOptions.builder().jsPrefix("FromA").build(); @@ -1165,11 +1110,11 @@ public void testWithAccount() throws Exception { KeyValueManagement kvmUserIBcktA = connUserI.keyValueManagement(jsOpt_UserI_BucketA_WithPrefix); KeyValueManagement kvmUserIBcktI = connUserI.keyValueManagement(jsOpt_UserI_BucketI_WithPrefix); - String bucketA = bucket(); + String bucketA = random(); KeyValueConfiguration kvcA = KeyValueConfiguration.builder() .name(bucketA).storageType(StorageType.Memory).maxHistoryPerKey(64).build(); - String bucketI = bucket(); + String bucketI = random(); KeyValueConfiguration kvcI = KeyValueConfiguration.builder() .name(bucketI).storageType(StorageType.Memory).maxHistoryPerKey(64).build(); @@ -1207,23 +1152,27 @@ public void testWithAccount() throws Exception { kv_connI_bucketA.watchAll(watcher_connI_BucketA); kv_connI_bucketI.watchAll(watcher_connI_BucketI); + String key11 = random(); + String key12 = random(); + String key21 = random(); + String key22 = random(); // bucket a from user a: AA, check AA, IA - assertKveAccount(kv_connA_bucketA, key(11), kv_connA_bucketA, kv_connI_bucketA); + assertKveAccount(kv_connA_bucketA, key11, kv_connA_bucketA, kv_connI_bucketA); // bucket a from user i: IA, check AA, IA - assertKveAccount(kv_connI_bucketA, key(12), kv_connA_bucketA, kv_connI_bucketA); + assertKveAccount(kv_connI_bucketA, key12, kv_connA_bucketA, kv_connI_bucketA); // bucket i from user a: AI, check AI, II - assertKveAccount(kv_connA_bucketI, key(21), kv_connA_bucketI, kv_connI_bucketI); + assertKveAccount(kv_connA_bucketI, key21, kv_connA_bucketI, kv_connI_bucketI); // bucket i from user i: II, check AI, II - assertKveAccount(kv_connI_bucketI, key(22), kv_connA_bucketI, kv_connI_bucketI); + assertKveAccount(kv_connI_bucketI, key22, kv_connA_bucketI, kv_connI_bucketI); // check keys from each kv - assertKvAccountKeys(kv_connA_bucketA.keys(), key(11), key(12)); - assertKvAccountKeys(kv_connI_bucketA.keys(), key(11), key(12)); - assertKvAccountKeys(kv_connA_bucketI.keys(), key(21), key(22)); - assertKvAccountKeys(kv_connI_bucketI.keys(), key(21), key(22)); + assertKvAccountKeys(kv_connA_bucketA.keys(), key11, key12); + assertKvAccountKeys(kv_connI_bucketA.keys(), key11, key12); + assertKvAccountKeys(kv_connA_bucketI.keys(), key21, key22); + assertKvAccountKeys(kv_connI_bucketI.keys(), key21, key22); Object[] expecteds = new Object[]{ data(0), data(1), KeyValueOperation.DELETE, KeyValueOperation.PURGE, data(2), @@ -1235,7 +1184,7 @@ public void testWithAccount() throws Exception { validateWatcher(expecteds, watcher_connI_BucketA); validateWatcher(expecteds, watcher_connI_BucketI); } - } + }); } private void assertKvAccountBucketNames(List bnames, String bucketA, String bucketI) { @@ -1297,16 +1246,18 @@ private void assertKveAccountGet(KeyValue kvUserA, KeyValue kvUserI, String key, assertEquals(KeyValueOperation.PUT, kveUserA.getOperation()); } - @SuppressWarnings({"SimplifiableAssertion", "ConstantConditions", "EqualsWithItself"}) + @SuppressWarnings({"SimplifiableAssertion", "ConstantConditions"}) @Test public void testCoverBucketAndKey() { - NatsKeyValueUtil.BucketAndKey bak1 = new NatsKeyValueUtil.BucketAndKey(DOT + BUCKET + DOT + KEY); - NatsKeyValueUtil.BucketAndKey bak2 = new NatsKeyValueUtil.BucketAndKey(DOT + BUCKET + DOT + KEY); - NatsKeyValueUtil.BucketAndKey bak3 = new NatsKeyValueUtil.BucketAndKey(DOT + bucket(1) + DOT + KEY); - NatsKeyValueUtil.BucketAndKey bak4 = new NatsKeyValueUtil.BucketAndKey(DOT + BUCKET + DOT + key(1)); - - assertEquals(BUCKET, bak1.bucket); - assertEquals(KEY, bak1.key); + String bucket = random(); + String key = random(); + NatsKeyValueUtil.BucketAndKey bak1 = new NatsKeyValueUtil.BucketAndKey(DOT + bucket + DOT + key); + NatsKeyValueUtil.BucketAndKey bak2 = new NatsKeyValueUtil.BucketAndKey(DOT + bucket + DOT + key); + NatsKeyValueUtil.BucketAndKey bak3 = new NatsKeyValueUtil.BucketAndKey(DOT + random() + DOT + key); + NatsKeyValueUtil.BucketAndKey bak4 = new NatsKeyValueUtil.BucketAndKey(DOT + bucket + DOT + random()); + + assertEquals(bucket, bak1.bucket); + assertEquals(key, bak1.key); assertEquals(bak1, bak1); assertEquals(bak1, bak2); assertEquals(bak2, bak1); @@ -1330,56 +1281,52 @@ public void testCoverPrefix() { @Test public void testKeyValueEntryEqualsImpl() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - + runInShared((nc, ctx) -> { // create bucket 1 - String bucket1 = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket1) - .storageType(StorageType.Memory) - .build()); + String bucket1 = random(); + ctx.kvCreate(bucket1); // create bucket 2 - String bucket2 = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket2) - .storageType(StorageType.Memory) - .build()); + String bucket2 = random(); + ctx.kvCreate(bucket2); KeyValue kv1 = nc.keyValue(bucket1); KeyValue kv2 = nc.keyValue(bucket2); - kv1.put(key(1), "ONE"); - kv1.put(key(2), "TWO"); - kv2.put(key(1), "ONE"); + String key1 = random(); + String key2 = random(); + String key3 = random(); + kv1.put(key1, "ONE"); + kv1.put(key2, "TWO"); + kv2.put(key1, "ONE"); - KeyValueEntry kve1_1 = kv1.get(key(1)); - KeyValueEntry kve1_2 = kv1.get(key(2)); - KeyValueEntry kve2_1 = kv2.get(key(1)); + KeyValueEntry kve1_1 = kv1.get(key1); + KeyValueEntry kve1_2 = kv1.get(key2); + KeyValueEntry kve2_1 = kv2.get(key1); - //noinspection EqualsWithItself assertEquals(kve1_1, kve1_1); - assertEquals(kve1_1, kv1.get(key(1))); + assertEquals(kve1_1, kv1.get(key1)); assertNotEquals(kve1_1, kve1_2); assertNotEquals(kve1_1, kve2_1); - kv1.put(key(1), "ONE-PRIME"); - assertNotEquals(kve1_1, kv1.get(key(1))); + kv1.put(key1, "ONE-PRIME"); + assertNotEquals(kve1_1, kv1.get(key1)); - kv1.put(key(9), (byte[]) null); - KeyValueEntry kve9 = kv1.get(key(9)); + kv1.put(key3, (byte[]) null); + KeyValueEntry kve9 = kv1.get(key3); assertNull(kve9.getValue()); assertNull(kve9.getValueAsString()); assertNull(kve9.getValueAsLong()); - kv1.put(key(9), new byte[0]); - kve9 = kv1.get(key(9)); + kv1.put(key3, new byte[0]); + kve9 = kv1.get(key3); assertNull(kve9.getValue()); assertNull(kve9.getValueAsString()); assertNull(kve9.getValueAsLong()); // coverage + //noinspection MisorderedAssertEqualsArguments assertNotEquals(kve1_1, null); + //noinspection MisorderedAssertEqualsArguments assertNotEquals(kve1_1, new Object()); }); } @@ -1449,15 +1396,10 @@ public void testKeyValuePurgeOptionsBuilderCoverage() { @Test public void testCreateDiscardPolicy() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - + runInShared((nc, ctx) -> { // create bucket - String bucket1 = bucket(); - KeyValueStatus status = kvm.create(KeyValueConfiguration.builder() - .name(bucket1) - .storageType(StorageType.Memory) - .build()); + String bucket1 = random(); + KeyValueStatus status = ctx.kvCreate(bucket1); DiscardPolicy dp = status.getConfiguration().getBackingConfig().getDiscardPolicy(); if (nc.getServerInfo().isSameOrNewerThanVersion("2.7.2")) { @@ -1471,20 +1413,26 @@ public void testCreateDiscardPolicy() throws Exception { @Test public void testEntryCoercion() throws Exception { - jsServer.run(nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - + runInShared((nc, ctx) -> { // create bucket - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .build()); + String bucket = random(); + ctx.kvCreate(bucket); KeyValue kv = nc.keyValue(bucket); kv.put("a", "a"); KeyValueEntry kve = kv.get("a"); - assertThrows(NumberFormatException.class, kve::getValueAsLong); + assertNotNull(kve); + assertNotNull(kve.getValue()); + try { + kve.getValueAsLong(); + fail(); + } + catch (NumberFormatException nfe) { + // correct! + } + catch (Exception e) { + fail(e); + } kv.delete("a"); List list = kv.history("a"); @@ -1494,7 +1442,7 @@ public void testEntryCoercion() throws Exception { } @Test - public void testKeyResultConstruction() throws Exception { + public void testKeyResultConstruction() { KeyResult r = new KeyResult(); assertNull(r.getKey()); assertNull(r.getException()); @@ -1519,21 +1467,29 @@ public void testKeyResultConstruction() throws Exception { } @Test - public void testMirrorSourceBuilderPrefixConversion() throws Exception { - String bucket = bucket(); - String name = variant(); + public void testMirrorSourceBuilderPrefixConversion() { + String bucket = random(); + String name = random(); String kvName = "KV_" + name; KeyValueConfiguration kvc = KeyValueConfiguration.builder() .name(bucket) .mirror(Mirror.builder().name(name).build()) .build(); - assertEquals(kvName, kvc.getBackingConfig().getMirror().getName()); + StreamConfiguration sc = kvc.getBackingConfig(); + assertNotNull(sc); + Mirror mirror = sc.getMirror(); + assertNotNull(mirror); + assertEquals(kvName, mirror.getName()); kvc = KeyValueConfiguration.builder() .name(bucket) .mirror(Mirror.builder().name(kvName).build()) .build(); - assertEquals(kvName, kvc.getBackingConfig().getMirror().getName()); + sc = kvc.getBackingConfig(); + assertNotNull(sc); + mirror = sc.getMirror(); + assertNotNull(mirror); + assertEquals(kvName, mirror.getName()); Source s1 = Source.builder().name("s1").build(); Source s2 = Source.builder().name("s2").build(); @@ -1557,9 +1513,13 @@ public void testMirrorSourceBuilderPrefixConversion() throws Exception { .addSources((Collection)null) .build(); - assertEquals(6, kvc.getBackingConfig().getSources().size()); + sc = kvc.getBackingConfig(); + assertNotNull(sc); + List sources = sc.getSources(); + assertNotNull(sources); + assertEquals(6, sources.size()); List names = new ArrayList<>(); - for (Source source : kvc.getBackingConfig().getSources()) { + for (Source source : sources) { names.add(source.getName()); } assertTrue(names.contains("KV_s1")); @@ -1572,27 +1532,28 @@ public void testMirrorSourceBuilderPrefixConversion() throws Exception { @Test public void testKeyValueMirrorCrossDomains() throws Exception { - runInJsHubLeaf((hub, leaf) -> { - KeyValueManagement hubKvm = hub.keyValueManagement(); - KeyValueManagement leafKvm = leaf.keyValueManagement(); + runInJsHubLeaf((hubNc, leafNc) -> { + KeyValueManagement hubKvm = hubNc.keyValueManagement(); + KeyValueManagement leafKvm = leafNc.keyValueManagement(); // Create main KV on HUB - String hubBucket = variant(); - KeyValueStatus hubStatus = hubKvm.create(KeyValueConfiguration.builder() + String hubBucket = random(); + hubKvm.create(KeyValueConfiguration.builder() .name(hubBucket) .storageType(StorageType.Memory) .build()); - KeyValue hubKv = hub.keyValue(hubBucket); + KeyValue hubKv = hubNc.keyValue(hubBucket); hubKv.put("key1", "aaa0"); hubKv.put("key2", "bb0"); hubKv.put("key3", "c0"); hubKv.delete("key3"); - String leafBucket = variant(); + String leafBucket = random(); String leafStream = "KV_" + leafBucket; leafKvm.create(KeyValueConfiguration.builder() .name(leafBucket) + .storageType(StorageType.Memory) .mirror(Mirror.builder() .sourceName(hubBucket) .domain(null) // just for coverage! @@ -1601,19 +1562,23 @@ public void testKeyValueMirrorCrossDomains() throws Exception { .build()); sleep(200); // make sure things get a chance to propagate - StreamInfo si = leaf.jetStreamManagement().getStreamInfo(leafStream); - if (hub.getServerInfo().isSameOrNewerThanVersion("2.9")) { + StreamInfo si = leafNc.jetStreamManagement().getStreamInfo(leafStream); + if (hubNc.getServerInfo().isSameOrNewerThanVersion("2.9")) { assertTrue(si.getConfiguration().getMirrorDirect()); } assertEquals(3, si.getStreamState().getMsgCount()); - KeyValue leafKv = leaf.keyValue(leafBucket); + KeyValue leafKv = leafNc.keyValue(leafBucket); _testMirror(hubKv, leafKv, 1); // Bind through leafnode connection but to origin KV. KeyValue hubViaLeafKv = - leaf.keyValue(hubBucket, KeyValueOptions.builder().jsDomain(HUB_DOMAIN).build()); + leafNc.keyValue(hubBucket, KeyValueOptions.builder().jsDomain(HUB_DOMAIN).build()); _testMirror(hubKv, hubViaLeafKv, 2); + + // just cleanup + hubKvm.delete(hubBucket); + leafKvm.delete(leafBucket); }); } @@ -1634,6 +1599,7 @@ private void _testMirror(KeyValue okv, KeyValue mkv, int num) throws Exception { // Make sure we can create a watcher on the mirror KV. TestKeyValueWatcher mWatcher = new TestKeyValueWatcher("mirrorWatcher" + num, false); + //noinspection unused try (NatsKeyValueWatchSubscription mWatchSub = mkv.watchAll(mWatcher)) { sleep(200); // give the messages time to propagate } @@ -1642,6 +1608,7 @@ private void _testMirror(KeyValue okv, KeyValue mkv, int num) throws Exception { // Does the origin data match? if (okv != null) { TestKeyValueWatcher oWatcher = new TestKeyValueWatcher("originWatcher" + num, false); + //noinspection unused try (NatsKeyValueWatchSubscription oWatchSub = okv.watchAll(oWatcher)) { sleep(200); // give the messages time to propagate } @@ -1651,19 +1618,14 @@ private void _testMirror(KeyValue okv, KeyValue mkv, int num) throws Exception { @Test public void testKeyValueTransform() throws Exception { - jsServer.run(TestBase::atLeast2_10_3, nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - - String kvName1 = variant(); + runInShared(VersionUtils::atLeast2_10_3, (nc, ctx) -> { + String kvName1 = random(); String kvName2 = kvName1 + "-mir"; String mirrorSegment = "MirrorMe"; String dontMirrorSegment = "DontMirrorMe"; String generic = "foo"; - kvm.create(KeyValueConfiguration.builder() - .name(kvName1) - .storageType(StorageType.Memory) - .build()); + ctx.kvCreate(kvName1); SubjectTransform transform = SubjectTransform.builder() .source("$KV." + kvName1 + "." + mirrorSegment + ".*") @@ -1675,11 +1637,7 @@ public void testKeyValueTransform() throws Exception { .subjectTransforms(transform) .build(); - kvm.create(KeyValueConfiguration.builder() - .name(kvName2) - .mirror(mirr) - .storageType(StorageType.Memory) - .build()); + ctx.kvCreate(ctx.kvBuilder(kvName2).mirror(mirr)); KeyValue kv1 = nc.keyValue(kvName1); @@ -1709,15 +1667,9 @@ public void testKeyValueTransform() throws Exception { @Test public void testSubjectFiltersAgainst209OptOut() throws Exception { - jsServer.run(TestBase::atLeast2_10, nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - - String bucket = bucket(); - kvm.create(KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .build()); - + runInShared(VersionUtils::atLeast2_10, (nc, ctx) -> { + String bucket = random(); + ctx.kvCreate(bucket); JetStreamOptions jso = JetStreamOptions.builder().optOut290ConsumerCreate(true).build(); KeyValueOptions kvo = KeyValueOptions.builder().jetStreamOptions(jso).build(); KeyValue kv = nc.keyValue(bucket, kvo); @@ -1729,14 +1681,10 @@ public void testSubjectFiltersAgainst209OptOut() throws Exception { @Test public void testTtlAndDuplicateWindowRoundTrip() throws Exception { - jsServer.run(TestBase::atLeast2_10, nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - String bucket = bucket(); - KeyValueConfiguration config = KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .build(); - KeyValueStatus status = kvm.create(config); + runInShared(VersionUtils::atLeast2_10, (nc, ctx) -> { + String bucket = random(); + KeyValueConfiguration config = ctx.kvBuilder(bucket).build(); + KeyValueStatus status = ctx.kvCreate(config); StreamConfiguration sc = status.getBackingStreamInfo().getConfiguration(); assertEquals(0, sc.getMaxAge().toMillis()); @@ -1744,19 +1692,15 @@ public void testTtlAndDuplicateWindowRoundTrip() throws Exception { assertEquals(SERVER_DEFAULT_DUPLICATE_WINDOW_MS, sc.getDuplicateWindow().toMillis()); config = KeyValueConfiguration.builder(status.getConfiguration()).ttl(Duration.ofSeconds(10)).build(); - status = kvm.update(config); + status = ctx.kvm.update(config); sc = status.getBackingStreamInfo().getConfiguration(); assertEquals(10_000, sc.getMaxAge().toMillis()); assertNotNull(sc.getDuplicateWindow()); assertEquals(10_000, sc.getDuplicateWindow().toMillis()); - bucket = bucket(); - config = KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .ttl(Duration.ofMinutes(30)) - .build(); - status = kvm.create(config); + bucket = random(); + config = ctx.kvBuilder(bucket).ttl(Duration.ofMinutes(30)).build(); + status = ctx.kvCreate(config); sc = status.getBackingStreamInfo().getConfiguration(); assertEquals(30, sc.getMaxAge().toMinutes()); @@ -1768,14 +1712,9 @@ public void testTtlAndDuplicateWindowRoundTrip() throws Exception { @Test public void testConsumeKeys() throws Exception { int count = 10000; - jsServer.run(TestBase::atLeast2_10, nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - String bucket = bucket(); - KeyValueConfiguration config = KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .build(); - kvm.create(config); + runInShared(VersionUtils::atLeast2_10, (nc, ctx) -> { + String bucket = random(); + ctx.kvCreate(bucket); // put a bunch of keys so consume takes some time. KeyValue kv = nc.keyValue(bucket); @@ -1803,19 +1742,14 @@ public void testConsumeKeys() throws Exception { @Test public void testLimitMarkerCoverage() throws Exception { - jsServer.run(TestBase::atLeast2_12, nc -> { - KeyValueManagement kvm = nc.keyValueManagement(); - String bucket = bucket(); - KeyValueConfiguration config = KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .limitMarker(1000) - .build(); - KeyValueStatus status = kvm.create(config); + runInShared(VersionUtils::atLeast2_12, (nc, ctx) -> { + String bucket = random(); + KeyValueConfiguration config = ctx.kvBuilder(bucket).limitMarker(1000).build(); + KeyValueStatus status = ctx.kvCreate(config); assertNotNull(status.getLimitMarkerTtl()); assertEquals(1000, status.getLimitMarkerTtl().toMillis()); - String key = key(); + String key = random(); KeyValue kv = nc.keyValue(bucket); kv.create(key, dataBytes(), MessageTtl.seconds(1)); @@ -1828,11 +1762,11 @@ public void testLimitMarkerCoverage() throws Exception { assertNull(kve); config = KeyValueConfiguration.builder() - .name(bucket()) + .name(random()) .storageType(StorageType.Memory) .limitMarker(Duration.ofSeconds(2)) // coverage of duration api vs ms api .build(); - status = kvm.create(config); + status = ctx.kvCreate(config); assertNotNull(status.getLimitMarkerTtl()); assertEquals(2000, status.getLimitMarkerTtl().toMillis()); @@ -1852,19 +1786,13 @@ public void testLimitMarkerCoverage() throws Exception { @Test public void testLimitMarkerBehavior() throws Exception { - jsServer.run(TestBase::atLeast2_12, nc -> { - String bucket = bucket(); - String key1 = key(); - String key2 = key(); - String key3 = key(); - - KeyValueManagement kvm = nc.keyValueManagement(); - KeyValueConfiguration config = KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .limitMarker(Duration.ofSeconds(5)) - .build(); - kvm.create(config); + runInShared(VersionUtils::atLeast2_12, (nc, ctx) -> { + String bucket = random(); + String key1 = random(); + String key2 = random(); + String key3 = random(); + + ctx.kvCreate(ctx.kvBuilder(bucket).limitMarker(Duration.ofSeconds(5))); KeyValue kv = nc.keyValue(bucket); @@ -1893,6 +1821,7 @@ public void endOfData() { } }; + //noinspection unused NatsKeyValueWatchSubscription watch = kv.watchAll(watcher); AtomicInteger rMessages = new AtomicInteger(); @@ -1925,6 +1854,7 @@ public void endOfData() { }; Dispatcher d = nc.createDispatcher(); + //noinspection unused JetStreamSubscription sub = nc.jetStream().subscribe(null, d, rawHandler, true, PushSubscribeOptions.builder() .stream("KV_" + bucket) @@ -1967,20 +1897,12 @@ public void endOfData() { @Test public void testJustLimitMarkerCreatePurge() throws Exception { - jsServer.run(TestBase::atLeast2_12, nc -> { - - String bucket = bucket(); + runInShared(VersionUtils::atLeast2_12, (nc, ctx) -> { + String bucket = random(); String rawStream = "KV_" + bucket; - String key = key(); + String key = random(); - JetStreamManagement jsm = nc.jetStreamManagement(); - KeyValueManagement kvm = nc.keyValueManagement(); - KeyValueConfiguration config = KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .limitMarker(Duration.ofSeconds(1)) - .build(); - kvm.create(config); + ctx.kvCreate(ctx.kvBuilder(bucket).limitMarker(Duration.ofSeconds(1))); KeyValue kv = nc.keyValue(bucket); @@ -2023,6 +1945,7 @@ else if (mcount == 4) { } }; + //noinspection unused JetStreamSubscription sub = nc.jetStream().subscribe(null, d, rawHandler, true, PushSubscribeOptions.builder() .stream(rawStream) @@ -2030,67 +1953,55 @@ else if (mcount == 4) { .build()) .build()); - long mark = System.currentTimeMillis(); + long createdTimeMark = System.currentTimeMillis(); kv.create(key, dataBytes(), MessageTtl.seconds(1)); - StreamInfo si = jsm.getStreamInfo(rawStream); + StreamInfo si = ctx.jsm.getStreamInfo(rawStream); assertEquals(1, si.getStreamState().getMsgCount()); - long safety = 0; - long gotZero = -1; - while (++safety < 10000 && errorLatch.getCount() > 0) { - si = jsm.getStreamInfo(rawStream); - if (si.getStreamState().getMsgCount() == 0) { - gotZero = System.currentTimeMillis(); - break; - } - } + long purgedTimeMark = waitForPurge(ctx, rawStream); + assertEquals(1, errorLatch.getCount(), error.get()); assertEquals(2, messages.get()); - assertTrue(gotZero - mark >= 1000); + assertTrue(purgedTimeMark - createdTimeMark >= 1000); // ttl is 1 second, should take at least this long assertEquals("PUT", ops.get(0)); assertEquals("MaxAge", ops.get(1)); kv.create(key, dataBytes()); - si = jsm.getStreamInfo(rawStream); + si = ctx.jsm.getStreamInfo(rawStream); assertEquals(1, si.getStreamState().getMsgCount()); kv.purge(key, MessageTtl.seconds(1)); - si = jsm.getStreamInfo(rawStream); + si = ctx.jsm.getStreamInfo(rawStream); assertEquals(1, si.getStreamState().getMsgCount()); - safety = 0; - gotZero = -1; - while (++safety < 10000 && errorLatch.getCount() > 0) { - si = jsm.getStreamInfo(rawStream); - if (si.getStreamState().getMsgCount() == 0) { - gotZero = System.currentTimeMillis(); - break; - } - } + purgedTimeMark = waitForPurge(ctx, rawStream); assertEquals(1, errorLatch.getCount(), error.get()); assertEquals(4, messages.get()); - assertTrue(gotZero - mark >= 1000); + assertTrue(purgedTimeMark - createdTimeMark >= 1000); // ttl is 1 second, should take at least this long assertEquals("PUT", ops.get(2)); assertEquals("PURGE", ops.get(3)); }); } + private static long waitForPurge(JetStreamTestingContext ctx, String rawStream) throws IOException, JetStreamApiException { + for (int tries = 0; tries < 20; tries++) { + sleep(500); // it takes a bit of time for the purge to happen, depends on the server load + StreamInfo si = ctx.jsm.getStreamInfo(rawStream); + if (si.getStreamState().getMsgCount() == 0) { + return System.currentTimeMillis(); + } + } + return -1; + } + @Test public void testJustTtlForDeletePurge() throws Exception { - jsServer.run(TestBase::atLeast2_12, nc -> { - - String bucket = bucket(); + runInShared(VersionUtils::atLeast2_12, (nc, ctx) -> { + String bucket = random(); String rawStream = "KV_" + bucket; - String key = key(); + String key = random(); - JetStreamManagement jsm = nc.jetStreamManagement(); - KeyValueManagement kvm = nc.keyValueManagement(); - KeyValueConfiguration config = KeyValueConfiguration.builder() - .name(bucket) - .storageType(StorageType.Memory) - .ttl(Duration.ofSeconds(1)) - .build(); - kvm.create(config); + ctx.kvCreate(ctx.kvBuilder(bucket).ttl(Duration.ofSeconds(1))); KeyValue kv = nc.keyValue(bucket); @@ -2130,6 +2041,7 @@ else if (mcount == 4) { } }; + //noinspection unused JetStreamSubscription sub = nc.jetStream().subscribe(null, d, rawHandler, true, PushSubscribeOptions.builder() .stream(rawStream) @@ -2138,45 +2050,29 @@ else if (mcount == 4) { .build()); kv.create(key, dataBytes()); - StreamInfo si = jsm.getStreamInfo(rawStream); + StreamInfo si = ctx.jsm.getStreamInfo(rawStream); assertEquals(1, si.getStreamState().getMsgCount()); kv.delete(key); - long mark = System.currentTimeMillis(); - si = jsm.getStreamInfo(rawStream); + long createdTimeMark = System.currentTimeMillis(); + si = ctx.jsm.getStreamInfo(rawStream); assertEquals(1, si.getStreamState().getMsgCount()); - long safety = 0; - long gotZero = -1; - while (++safety < 10000 && errorLatch.getCount() > 0) { - si = jsm.getStreamInfo(rawStream); - if (si.getStreamState().getMsgCount() == 0) { - gotZero = System.currentTimeMillis(); - break; - } - } + long purgedTimeMark = waitForPurge(ctx, rawStream); assertEquals(1, errorLatch.getCount(), error.get()); assertEquals(2, messages.get()); - assertTrue(gotZero - mark >= 1000); + assertTrue(purgedTimeMark - createdTimeMark >= 1000); assertEquals("PUT", ops.get(0)); assertEquals("DEL", ops.get(1)); kv.create(key, dataBytes()); - mark = System.currentTimeMillis(); + createdTimeMark = System.currentTimeMillis(); kv.purge(key); - safety = 0; - gotZero = -1; - while (++safety < 10000 && errorLatch.getCount() > 0) { - si = jsm.getStreamInfo(rawStream); - if (si.getStreamState().getMsgCount() == 0) { - gotZero = System.currentTimeMillis(); - break; - } - } + purgedTimeMark = waitForPurge(ctx, rawStream); assertEquals(1, errorLatch.getCount(), error.get()); assertEquals(4, messages.get()); - assertTrue(gotZero - mark >= 1000); + assertTrue(purgedTimeMark - createdTimeMark >= 1000); assertEquals("PUT", ops.get(2)); assertEquals("PURGE", ops.get(3)); }); diff --git a/src/test/java/io/nats/client/impl/ListenerForTesting.java b/src/test/java/io/nats/client/impl/ListenerForTesting.java index 3c85925d8..e69de29bb 100644 --- a/src/test/java/io/nats/client/impl/ListenerForTesting.java +++ b/src/test/java/io/nats/client/impl/ListenerForTesting.java @@ -1,622 +0,0 @@ -// Copyright 2015-2018 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.impl; - -import io.nats.client.*; -import io.nats.client.support.DateTimeUtils; -import io.nats.client.support.Status; - -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Predicate; -import java.util.function.Supplier; - -@SuppressWarnings("CallToPrintStackTrace") -public class ListenerForTesting implements ErrorListener, ConnectionListener { - private final ReentrantLock prepLock = new ReentrantLock(); - - private final AtomicInteger count = new AtomicInteger(); - private final AtomicInteger exceptionCount = new AtomicInteger(); - private final HashMap eventCounts = new HashMap<>(); - private final HashMap errorCounts = new HashMap<>(); - private final AtomicBoolean exitOnDisconnect = new AtomicBoolean(false); - private final AtomicBoolean exitOnHeartbeatError = new AtomicBoolean(false); - - private Connection lastEventConnection; - - private CompletableFuture statusChanged; - private CompletableFuture slowSubscriber; - private CompletableFuture errorWaitFuture; - private CompletableFuture heartbeatAlarmEventWaitFuture; - private CompletableFuture pullStatusWarningWaitFuture; - private CompletableFuture pullStatusErrorWaitFuture; - private Events eventToWaitFor; - private String errorToWaitFor; - - private final List connectionEvents = new ArrayList<>(); - private final List errors = new ArrayList<>(); - private final List exceptions = new ArrayList<>(); - private final List slowConsumers = new ArrayList<>(); - private final List discardedMessages = new ArrayList<>(); - private final List unhandledStatuses = new ArrayList<>(); - private final List pullStatusWarnings = new ArrayList<>(); - private final List pullStatusErrors = new ArrayList<>(); - private final List heartbeatAlarms = new ArrayList<>(); - private final List flowControlProcessedEvents = new ArrayList<>(); - public final AtomicInteger socketWriteTimeoutCount = new AtomicInteger(); - - private final boolean printExceptions; - private final boolean verbose; - - public ListenerForTesting() { - this(false, false); - } - - public ListenerForTesting(boolean printExceptions, boolean verbose) { - this.printExceptions = printExceptions; - this.verbose = verbose; - } - - public void reset() { - count.set(0); - exceptionCount.set(0); - eventCounts.clear(); - errorCounts.clear(); - exitOnDisconnect.set(false); - exitOnHeartbeatError.set(false); - lastEventConnection = null; - statusChanged = null; - slowSubscriber = null; - errorWaitFuture = null; - heartbeatAlarmEventWaitFuture = null; - pullStatusWarningWaitFuture = null; - pullStatusErrorWaitFuture = null; - eventToWaitFor = null; - errorToWaitFor = null; - connectionEvents.clear(); - errors.clear(); - exceptions.clear(); - slowConsumers.clear(); - discardedMessages.clear(); - unhandledStatuses.clear(); - pullStatusWarnings.clear(); - pullStatusErrors.clear(); - heartbeatAlarms.clear(); - flowControlProcessedEvents.clear(); - socketWriteTimeoutCount.set(0); - } - - private boolean waitForBooleanFuture(CompletableFuture future, long timeout, TimeUnit units) { - try { - return future.get(timeout, units); - } catch (TimeoutException | ExecutionException | InterruptedException e) { - maybePrintException("waitForBooleanFuture", e); - return false; - } - } - - private T waitForFuture(CompletableFuture future, long waitInMillis) { - try { - return future.get(waitInMillis, TimeUnit.MILLISECONDS); - } catch (TimeoutException | ExecutionException | InterruptedException e) { - maybePrintException("waitForFuture", e); - return null; - } - } - - private void maybePrintException(String label, Exception e) { - if (printExceptions) { - System.err.print("LFT " + label + ": "); - e.printStackTrace(); - } - } - - public void setExitOnDisconnect() { - exitOnDisconnect.set(true); - } - - public void setExitOnHeartbeatError() { - exitOnHeartbeatError.set(true); - } - - public void clearExitOnDisconnect() { - exitOnDisconnect.set(false); - } - - public void clearExitOnHeartbeatError() { - exitOnHeartbeatError.set(false); - } - - public void prepForStatusChange(Events waitFor) { - prepLock.lock(); - try { - statusChanged = new CompletableFuture<>(); - eventToWaitFor = waitFor; - if (verbose) { - report("prepForStatusChange", waitFor); - } - } finally { - prepLock.unlock(); - } - } - - public boolean waitForStatusChange(long timeout, TimeUnit units) { - return waitForBooleanFuture(statusChanged, timeout, units); - } - - public void exceptionOccurred(Connection conn, Exception exp) { - lastEventConnection = conn; - exceptions.add(exp); - count.incrementAndGet(); - exceptionCount.incrementAndGet(); - - if (exp != null) { - if (verbose) { - report("exceptionOccurred", "conn:" + conn.hashCode() + ", " + exp); - } - maybePrintException("exceptionOccurred", exp); - } - } - - public boolean _eventually(long timeout, Supplier> listSupplier, Predicate predicate) { - long start = System.currentTimeMillis(); - int i = 0; - do { - List list = listSupplier.get(); - int size = list.size(); - for (; i < size; i++) { - if (predicate.test(list.get(i))) { - return true; - } - } - } - while (System.currentTimeMillis() - start <= timeout); - return false; - } - - public void prepForError(String waitFor) { - prepLock.lock(); - try { - errorWaitFuture = new CompletableFuture<>(); - errorToWaitFor = waitFor; - if (verbose) { - report("prepForError", waitFor); - } - } finally { - prepLock.unlock(); - } - } - - public boolean errorsEventually(String contains, long timeout) { - return _eventually(timeout, () -> copy(errors), (s) -> s.contains(contains)); - } - - public void errorOccurred(Connection conn, String errorText) { - lastEventConnection = conn; - add(errors, errorText); - count.incrementAndGet(); - - prepLock.lock(); - try { - AtomicInteger counter = errorCounts.computeIfAbsent(errorText, k -> new AtomicInteger()); - counter.incrementAndGet(); - if (errorWaitFuture != null && errorText.contains(errorToWaitFor)) { - errorWaitFuture.complete(Boolean.TRUE); - } - if (verbose) { - report("errorOccurred", errorText); - } - } finally { - prepLock.unlock(); - } - } - - public void messageDiscarded(Connection conn, Message msg) { - lastEventConnection = conn; - count.incrementAndGet(); - - prepLock.lock(); - try { - discardedMessages.add(msg); - if (verbose) { - report("messageDiscarded", msg); - } - } finally { - prepLock.unlock(); - } - } - - @Override - public void connectionEvent(Connection conn, Events type) { - connectionEvent(conn, type, null, null); - } - - @Override - public void connectionEvent(Connection conn, Events type, Long time, String uriDetails) { - lastEventConnection = conn; - connectionEvents.add(type); - count.incrementAndGet(); - - if (exitOnDisconnect.get() && type == Events.DISCONNECTED) { - System.exit(-1); - } - - prepLock.lock(); - try { - AtomicInteger counter = eventCounts.computeIfAbsent(type, k -> new AtomicInteger()); - counter.incrementAndGet(); - if (statusChanged != null && type == eventToWaitFor) { - statusChanged.complete(Boolean.TRUE); - } - if (verbose) { - report("connectionEvent", type); - } - } finally { - prepLock.unlock(); - } - } - - public Future waitForSlow() { - slowSubscriber = new CompletableFuture<>(); - return slowSubscriber; - } - - public void slowConsumerDetected(Connection conn, Consumer consumer) { - count.incrementAndGet(); - - prepLock.lock(); - try { - slowConsumers.add(consumer); - if (slowSubscriber != null) { - slowSubscriber.complete(true); - } - if (verbose) { - String msg; - if (consumer instanceof NatsSubscription) { - NatsSubscription nats = (NatsSubscription)consumer; - msg = "Subscription " + nats.getSID() + " for " + nats.getSubject(); - } - else if (consumer instanceof NatsDispatcher) { - NatsDispatcher nats = (NatsDispatcher)consumer; - msg = "Dispatcher " + nats.getId(); - } - else { - msg = consumer.toString(); - } - report("slowConsumerDetected", msg); - } - } finally { - prepLock.unlock(); - } - } - - public static final DateTimeFormatter SIMPLE_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); - - public static String simpleTime() { - return SIMPLE_TIME_FORMATTER.format(DateTimeUtils.gmtNow()); - } - - private void report(String func, Object message) { - System.out.println("[" + simpleTime() + " ListenerForTesting." + func + "] " + message); - } - - private final ReentrantLock listLock = new ReentrantLock(); - private List copy(List list) { - listLock.lock(); - try { - return new ArrayList<>(list); - } - finally { - listLock.unlock(); - } - } - - private void add(List list, T t) { - listLock.lock(); - try { - list.add(t); - } - finally { - listLock.unlock(); - } - } - - public List getConnectionEvents() { - return connectionEvents; - } - - public List getErrors() { - return errors; - } - - public List getExceptions() { - return exceptions; - } - - public List getSlowConsumers() { - return slowConsumers; - } - - public List getDiscardedMessages() { - return discardedMessages; - } - - public List getUnhandledStatuses() { - return unhandledStatuses; - } - - public List getPullStatusWarnings() { - return pullStatusWarnings; - } - - public List getPullStatusErrors() { - return pullStatusErrors; - } - - public List getHeartbeatAlarms() { - return heartbeatAlarms; - } - - public List getFlowControlProcessedEvents() { - return flowControlProcessedEvents; - } - - public int getSocketWriteTimeoutCount() { - return socketWriteTimeoutCount.get(); - } - - public int getCount() { - return count.get(); - } - - public int getExceptionCount() { - return exceptionCount.get(); - } - - public int getEventCount(Events type) { - int retVal = 0; - prepLock.lock(); - try { - AtomicInteger counter = eventCounts.get(type); - if (counter != null) { - retVal = counter.get(); - } - } finally { - prepLock.unlock(); - } - return retVal; - } - - public int getErrorCount(String type) { - int retVal = 0; - prepLock.lock(); - try { - AtomicInteger counter = errorCounts.get(type); - if (counter != null) { - retVal = counter.get(); - } - } finally { - prepLock.unlock(); - } - return retVal; - } - - public void dumpErrorCountsToStdOut() { - prepLock.lock(); - try { - System.out.println("#### Test Handler Error Counts ####"); - for (String key : errorCounts.keySet()) { - int count = errorCounts.get(key).get(); - System.out.println(key+": "+count); - } - } finally { - prepLock.unlock(); - } - } - - public Connection getLastEventConnection() { - return lastEventConnection; - } - - @Override - public void unhandledStatus(Connection conn, JetStreamSubscription sub, Status status) { - unhandledStatuses.add(new StatusEvent(sub, status)); - } - - public void prepForPullStatusWarning() { - prepLock.lock(); - try { - pullStatusWarningWaitFuture = new CompletableFuture<>(); - } - finally { - prepLock.unlock(); - } - } - - public StatusEvent waitForPullStatusWarning(long waitInMillis) { - return waitForFuture(pullStatusWarningWaitFuture, waitInMillis); - } - - public boolean pullStatusWarningEventually(String contains, long timeout) { - return _eventually(timeout, () -> copy(pullStatusWarnings), - (se) -> se.status.getMessage().contains(contains)); - } - - @Override - public void pullStatusWarning(Connection conn, JetStreamSubscription sub, Status status) { - prepLock.lock(); - try { - StatusEvent event = new StatusEvent(sub, status); - if (verbose) { - report("pullStatusWarning", event); - } - add(pullStatusWarnings, event); - if (pullStatusWarningWaitFuture != null) { - pullStatusWarningWaitFuture.complete(event); - } - } finally { - prepLock.unlock(); - } - } - - public void prepForPullStatusError() { - prepLock.lock(); - try { - pullStatusErrorWaitFuture = new CompletableFuture<>(); - } - finally { - prepLock.unlock(); - } - } - - public StatusEvent waitForPullStatusError(long waitInMillis) { - return waitForFuture(pullStatusErrorWaitFuture, waitInMillis); - } - - public boolean pullStatusErrorOrWait(String contains, long timeout) { - return _eventually(timeout, () -> copy(pullStatusErrors), - (se) -> se.status.getMessage().contains(contains)); - } - - @Override - public void pullStatusError(Connection conn, JetStreamSubscription sub, Status status) { - prepLock.lock(); - try { - StatusEvent event = new StatusEvent(sub, status); - if (verbose) { - report("pullStatusError", event); - } - add(pullStatusErrors, event); - if (pullStatusErrorWaitFuture != null) { - pullStatusErrorWaitFuture.complete(event); - } - } finally { - prepLock.unlock(); - } - } - - public void prepForHeartbeatAlarm() { - prepLock.lock(); - try { - heartbeatAlarmEventWaitFuture = new CompletableFuture<>(); - } - finally { - prepLock.unlock(); - } - } - - public HeartbeatAlarmEvent waitForHeartbeatAlarm(long waitInMillis) { - return waitForFuture(heartbeatAlarmEventWaitFuture, waitInMillis); - } - - @Override - public void heartbeatAlarm(Connection conn, JetStreamSubscription sub, long lastStreamSequence, long lastConsumerSequence) { - prepLock.lock(); - try { - if (exitOnHeartbeatError.get()) { - System.exit(-2); - } - HeartbeatAlarmEvent event = new HeartbeatAlarmEvent(sub, lastStreamSequence, lastConsumerSequence); - if (verbose) { - report("heartbeatAlarm", event); - } - heartbeatAlarms.add(event); - if (heartbeatAlarmEventWaitFuture != null) { - heartbeatAlarmEventWaitFuture.complete(event); - } - } finally { - prepLock.unlock(); - } - } - - @Override - public void flowControlProcessed(Connection conn, JetStreamSubscription sub, String subject, FlowControlSource source) { - flowControlProcessedEvents.add(new FlowControlProcessedEvent(sub, subject, source)); - } - - @Override - public void socketWriteTimeout(Connection conn) { - socketWriteTimeoutCount.incrementAndGet(); - } - - public static class StatusEvent { - String sid; - Status status; - - public StatusEvent(JetStreamSubscription sub, Status status) { - this.sid = extractSid(sub); - this.status = status; - } - - @Override - public String toString() { - return "StatusEvent{" + - "sid='" + sid + '\'' + - ", status=" + status + - '}'; - } - } - - public static class HeartbeatAlarmEvent { - String sid; - long lastStreamSequence; - long lastConsumerSequence; - - public HeartbeatAlarmEvent(JetStreamSubscription sub, long lastStreamSequence, long lastConsumerSequence) { - this.sid = extractSid(sub); - this.lastStreamSequence = lastStreamSequence; - this.lastConsumerSequence = lastConsumerSequence; - } - - @Override - public String toString() { - return "HeartbeatAlarmEvent{" + - "sid='" + sid + '\'' + - ", lastStreamSequence=" + lastStreamSequence + - ", lastConsumerSequence=" + lastConsumerSequence + - '}'; - } - } - - public static class FlowControlProcessedEvent { - String sid; - String subject; - FlowControlSource source; - - public FlowControlProcessedEvent(JetStreamSubscription sub, String subject, FlowControlSource source) { - this.sid = extractSid(sub); - this.subject = subject; - this.source = source; - } - - @Override - public String toString() { - return "FlowControlEvent{" + - "sid='" + sid + '\'' + - ", subject='" + subject + '\'' + - ", source=" + source + - '}'; - } - } - - private static String extractSid(JetStreamSubscription sub) { - return ((NatsJetStreamSubscription)sub).getSID(); - } -} diff --git a/src/test/java/io/nats/client/impl/MessageContentTests.java b/src/test/java/io/nats/client/impl/MessageContentTests.java index 9da663992..f6fefe534 100644 --- a/src/test/java/io/nats/client/impl/MessageContentTests.java +++ b/src/test/java/io/nats/client/impl/MessageContentTests.java @@ -16,6 +16,9 @@ import io.nats.client.*; import io.nats.client.ConnectionListener.Events; +import io.nats.client.support.Listener; +import io.nats.client.utils.ConnectionUtils; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; @@ -23,70 +26,59 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.*; -public class MessageContentTests { +public class MessageContentTests extends TestBase { @Test public void testSimpleString() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - nc.publish(msg.getReplyTo(), msg.getData()); - }); - d.subscribe("subject"); + runInShared(nc -> { + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), msg.getData())); + String subject = random(); + d.subscribe(subject); String body = "hello world"; byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8); - Future incoming = nc.request("subject", bodyBytes); + Future incoming = nc.request(subject, bodyBytes); Message msg = incoming.get(50000, TimeUnit.MILLISECONDS); assertNotNull(msg); assertEquals(bodyBytes.length, msg.getData().length); assertEquals(body, new String(msg.getData(), StandardCharsets.UTF_8)); - } + }); } @Test public void testUTF8String() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - nc.publish(msg.getReplyTo(), msg.getData()); - }); - d.subscribe("subject"); + runInShared(nc -> { + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), msg.getData())); + String subject = random(); + d.subscribe(subject); String body = "??????"; byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8); - Future incoming = nc.request("subject", bodyBytes); + Future incoming = nc.request(subject, bodyBytes); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); assertNotNull(msg); assertEquals(bodyBytes.length, msg.getData().length); assertEquals(body, new String(msg.getData(), StandardCharsets.UTF_8)); - } + }); } @Test public void testDifferentSizes() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - nc.publish(msg.getReplyTo(), msg.getData()); - }); - d.subscribe("subject"); + runInShared(nc -> { + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), msg.getData())); + String subject = random(); + d.subscribe(subject); String body = "hello world"; for (int i=0;i<10;i++) { byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8); - Future incoming = nc.request("subject", bodyBytes); + Future incoming = nc.request(subject, bodyBytes); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); assertNotNull(msg); @@ -95,28 +87,24 @@ public void testDifferentSizes() throws Exception { body = body+body; } - } + }); } @Test public void testZeros() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - nc.publish(msg.getReplyTo(), msg.getData()); - }); - d.subscribe("subject"); + runInShared(nc -> { + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), msg.getData())); + String subject = random(); + d.subscribe(subject); byte[] data = new byte[17]; - Future incoming = nc.request("subject", data); + Future incoming = nc.request(subject, data); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); assertNotNull(msg); assertEquals(data.length, msg.getData().length); assertArrayEquals(msg.getData(), data); - } + }); } @Test @@ -199,29 +187,18 @@ public void testDisconnectOnBadProtocol() throws Exception { } void runBadContentTest(NatsServerProtocolMock.Customizer badServer, CompletableFuture ready) throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(badServer, null)) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - errorListener(listener). - connectionListener(listener). - build(); - Connection nc = Nats.connect(options); - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - listener.prepForStatusChange(Events.DISCONNECTED); + Listener listener = new Listener(); + + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(badServer, null)) { + Options options = optionsBuilder(mockTs) + .maxReconnects(0) + .errorListener(listener) + .connectionListener(listener) + .build(); + try (Connection ignore = ConnectionUtils.standardConnect(options)) { + listener.queueConnectionEvent(Events.DISCONNECTED); ready.complete(Boolean.TRUE); - listener.waitForStatusChange(200, TimeUnit.MILLISECONDS); - - assertTrue(listener.getExceptionCount() > 0); - assertTrue(Connection.Status.DISCONNECTED == nc.getStatus() - || Connection.Status.CLOSED == nc.getStatus(), "Disconnected Status"); - } finally { - nc.close(); - assertSame(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + listener.validate(); } } } diff --git a/src/test/java/io/nats/client/impl/MessageManagerTests.java b/src/test/java/io/nats/client/impl/MessageManagerTests.java index dc3db252b..d1a24084d 100644 --- a/src/test/java/io/nats/client/impl/MessageManagerTests.java +++ b/src/test/java/io/nats/client/impl/MessageManagerTests.java @@ -15,21 +15,29 @@ import io.nats.client.*; import io.nats.client.api.ConsumerConfiguration; +import io.nats.client.api.StorageType; +import io.nats.client.api.StreamConfiguration; import io.nats.client.support.IncomingHeadersProcessor; +import io.nats.client.support.Listener; +import io.nats.client.support.ListenerStatusType; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import static io.nats.client.impl.MessageManager.ManageResult; +import static io.nats.client.impl.MessageManager.ManageResult.*; +import static io.nats.client.support.Listener.SHORT_VALIDATE_TIMEOUT; +import static io.nats.client.support.ListenerStatusType.PullError; +import static io.nats.client.support.ListenerStatusType.PullWarning; import static io.nats.client.support.NatsConstants.NANOS_PER_MILLI; import static io.nats.client.support.NatsJetStreamConstants.CONSUMER_STALLED_HDR; import static io.nats.client.support.Status.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; @SuppressWarnings("SameParameterValue") @@ -37,8 +45,8 @@ public class MessageManagerTests extends JetStreamTestBase { @Test public void testConstruction() throws Exception { - runInJsServer(nc -> { - NatsJetStreamSubscription sub = genericPushSub(nc); + runInSharedCustom((nc, ctx) -> { + NatsJetStreamSubscription sub = genericPushSub(ctx); _pushConstruction(nc, true, true, push_hb_fc(), sub); _pushConstruction(nc, true, false, push_hb_xfc(), sub); _pushConstruction(nc, false, false, push_xhb_xfc(), sub); @@ -51,9 +59,9 @@ private void tf(Consumer c) { } } - private void _pushConstruction(Connection conn, boolean hb, boolean fc, SubscribeOptions so, NatsJetStreamSubscription sub) { + private void _pushConstruction(Connection nc, boolean hb, boolean fc, SubscribeOptions so, NatsJetStreamSubscription sub) { tf(ordered -> tf(syncMode -> tf(queueMode -> { - PushMessageManager manager = getPushManager(conn, so, sub, ordered, syncMode, queueMode); + PushMessageManager manager = getPushManager(nc, so, sub, ordered, syncMode, queueMode); assertEquals(syncMode, manager.isSyncMode()); assertEquals(queueMode, manager.isQueueMode()); if (queueMode) { @@ -69,84 +77,66 @@ private void _pushConstruction(Connection conn, boolean hb, boolean fc, Subscrib @Test public void testPushBeforeQueueProcessorAndManage() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - runInJsServer(listener, nc -> { - NatsJetStreamSubscription sub = genericPushSub(nc); - - PushMessageManager pushMgr = getPushManager(nc, push_hb_fc(), sub, false, true, false); - testPushBqpAndManage(sub, listener, pushMgr); - - pushMgr = getPushManager(nc, push_hb_xfc(), sub, false, true, false); - testPushBqpAndManage(sub, listener, pushMgr); - - pushMgr = getPushManager(nc, push_xhb_xfc(), sub, false, true, false); - testPushBqpAndManage(sub, listener, pushMgr); - - pushMgr = getPushManager(nc, push_hb_fc(), sub, false, false, false); - testPushBqpAndManage(sub, listener, pushMgr); - - pushMgr = getPushManager(nc, push_hb_xfc(), sub, false, false, false); - testPushBqpAndManage(sub, listener, pushMgr); - - pushMgr = getPushManager(nc, push_xhb_xfc(), sub, false, false, false); - testPushBqpAndManage(sub, listener, pushMgr); + Listener listener = new Listener(); + runInSharedCustom(listener, (nc, ctx) -> { + _testPushBqpAndManageRetriable(nc, ctx, listener, push_hb_fc(), false, true, false); + _testPushBqpAndManageRetriable(nc, ctx, listener, push_hb_xfc(), false, true, false); + _testPushBqpAndManageRetriable(nc, ctx, listener, push_xhb_xfc(), false, true, false); + _testPushBqpAndManageRetriable(nc, ctx, listener, push_hb_fc(), false, false, false); + _testPushBqpAndManageRetriable(nc, ctx, listener, push_hb_xfc(), false, false, false); + _testPushBqpAndManageRetriable(nc, ctx, listener, push_xhb_xfc(), false, false, false); }); } - private void testPushBqpAndManage(NatsJetStreamSubscription sub, ListenerForTesting listener, PushMessageManager manager) { + private void _testPushBqpAndManageRetriable(Connection nc, JetStreamTestingContext ctx, Listener listener, PushSubscribeOptions pso, boolean ordered, boolean syncMode, boolean queueMode) throws JetStreamApiException, IOException { listener.reset(); + + NatsJetStreamSubscription sub = genericPushSub(ctx); String sid = sub.getSID(); + PushMessageManager manager = getPushManager(nc, pso, sub, ordered, syncMode, queueMode); assertTrue(manager.beforeQueueProcessorImpl(getTestJsMessage(1, sid))); assertEquals(ManageResult.MESSAGE, manager.manage(getTestJsMessage(1, sid))); assertEquals(!manager.hb.get(), manager.beforeQueueProcessorImpl(getHeartbeat(sid))); - List unhandledCodes = new ArrayList<>(); assertTrue(manager.beforeQueueProcessorImpl(getFlowControl(1, sid))); assertTrue(manager.beforeQueueProcessorImpl(getFcHeartbeat(1, sid))); if (manager.fc) { - assertEquals(ManageResult.STATUS_HANDLED, manager.manage(getFlowControl(1, sid))); - assertEquals(ManageResult.STATUS_HANDLED, manager.manage(getFcHeartbeat(1, sid))); + listener.queueFlowControl(getFcSubject(1), ErrorListener.FlowControlSource.FLOW_CONTROL); + assertEquals(STATUS_HANDLED, manager.manage(getFlowControl(1, sid))); + assertEquals(STATUS_HANDLED, manager.manage(getFcHeartbeat(1, sid))); + listener.validate(); } else { - assertEquals(ManageResult.STATUS_ERROR, manager.manage(getFlowControl(1, sid))); - assertEquals(ManageResult.STATUS_ERROR, manager.manage(getFcHeartbeat(1, sid))); - unhandledCodes.add(FLOW_OR_HEARTBEAT_STATUS_CODE); // fc - unhandledCodes.add(FLOW_OR_HEARTBEAT_STATUS_CODE); // hb + listener.queueStatus(ListenerStatusType.Unhandled, FLOW_OR_HEARTBEAT_STATUS_CODE); + assertEquals(STATUS_ERROR, manager.manage(getFlowControl(1, sid))); + listener.validate(); + + listener.queueStatus(ListenerStatusType.Unhandled, FLOW_OR_HEARTBEAT_STATUS_CODE); + assertEquals(STATUS_ERROR, manager.manage(getFcHeartbeat(1, sid))); + listener.validate(); } assertTrue(manager.beforeQueueProcessorImpl(getUnkownStatus(sid))); - assertEquals(ManageResult.STATUS_ERROR, manager.manage(getUnkownStatus(sid))); - unhandledCodes.add(999); - - sleep(100); - List list = listener.getUnhandledStatuses(); - assertEquals(unhandledCodes.size(), list.size()); - for (int x = 0; x < list.size(); x++) { - ListenerForTesting.StatusEvent se = list.get(x); - assertSame(sub.getSID(), se.sid); - assertEquals(unhandledCodes.get(x), se.status.getCode()); - } + listener.queueStatus(ListenerStatusType.Unhandled, 999); + assertEquals(STATUS_ERROR, manager.manage(getUnkownStatus(sid))); + listener.validate(); } @Test public void testPullBeforeQueueProcessorAndManage() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - runInJsServer(listener, nc -> { - NatsJetStreamSubscription sub = genericPullSub(nc); - - PullMessageManager pullMgr = getPullManager(nc, sub, true); - pullMgr.startPullRequest("pullSubject", PullRequestOptions.builder(1).build(), true, null); - testPullBqpAndManage(sub, listener, pullMgr); - - pullMgr = getPullManager(nc, sub, true); - pullMgr.startPullRequest("pullSubject", PullRequestOptions.builder(1).expiresIn(10000).idleHeartbeat(100).build(), true, null); - testPullBqpAndManage(sub, listener, pullMgr); + Listener listener = new Listener(); + runInSharedOwnNc(listener, (nc, ctx) -> { + _testPullBqpAndManage(nc, ctx, listener, PullRequestOptions.builder(1).build()); + _testPullBqpAndManage(nc, ctx, listener, PullRequestOptions.builder(1).expiresIn(10000).idleHeartbeat(100).build()); }); } - private void testPullBqpAndManage(NatsJetStreamSubscription sub, ListenerForTesting listener, PullMessageManager manager) { + private void _testPullBqpAndManage(Connection nc, JetStreamTestingContext ctx, Listener listener, PullRequestOptions pro) throws JetStreamApiException, IOException { + NatsJetStreamSubscription sub = genericPullSub(ctx); + PullMessageManager manager = getPullManager(nc, sub, true); + manager.startPullRequest(random(), pro, true, null); listener.reset(); String sid = sub.getSID(); @@ -168,108 +158,88 @@ private void testPullBqpAndManage(NatsJetStreamSubscription sub, ListenerForTest assertTrue(manager.beforeQueueProcessorImpl(getConflictStatus(sid, CONSUMER_IS_PUSH_BASED))); assertEquals(ManageResult.MESSAGE, manager.manage(getTestJsMessage(1, sid))); - assertEquals(ManageResult.STATUS_TERMINUS, manager.manage(getNotFoundStatus(sid))); - assertEquals(ManageResult.STATUS_TERMINUS, manager.manage(getRequestTimeoutStatus(sid))); - assertEquals(ManageResult.STATUS_TERMINUS, manager.manage(getConflictStatus(sid, BATCH_COMPLETED))); - assertEquals(ManageResult.STATUS_TERMINUS, manager.manage(getConflictStatus(sid, MESSAGE_SIZE_EXCEEDS_MAX_BYTES))); - assertEquals(ManageResult.STATUS_HANDLED, manager.manage(getConflictStatus(sid, EXCEEDED_MAX_WAITING))); - assertEquals(ManageResult.STATUS_HANDLED, manager.manage(getConflictStatus(sid, EXCEEDED_MAX_REQUEST_BATCH))); - assertEquals(ManageResult.STATUS_HANDLED, manager.manage(getConflictStatus(sid, EXCEEDED_MAX_REQUEST_EXPIRES))); - assertEquals(ManageResult.STATUS_HANDLED, manager.manage(getConflictStatus(sid, EXCEEDED_MAX_REQUEST_MAX_BYTES))); - - assertEquals(ManageResult.STATUS_ERROR, manager.manage(getBadRequest(sid))); - assertEquals(ManageResult.STATUS_ERROR, manager.manage(getUnkownStatus(sid))); - assertEquals(ManageResult.STATUS_ERROR, manager.manage(getConflictStatus(sid, CONSUMER_DELETED))); - assertEquals(ManageResult.STATUS_ERROR, manager.manage(getConflictStatus(sid, CONSUMER_IS_PUSH_BASED))); - - sleep(100); - - List list = listener.getPullStatusWarnings(); - int[] codes = new int[]{NOT_FOUND_CODE, REQUEST_TIMEOUT_CODE, CONFLICT_CODE, CONFLICT_CODE, CONFLICT_CODE, CONFLICT_CODE, CONFLICT_CODE, CONFLICT_CODE}; - assertEquals(8, list.size()); - for (int x = 0; x < list.size(); x++) { - ListenerForTesting.StatusEvent se = list.get(x); - assertSame(sub.getSID(), se.sid); - assertEquals(codes[x], se.status.getCode()); - } - list = listener.getPullStatusErrors(); - assertEquals(4, list.size()); - codes = new int[]{BAD_REQUEST_CODE, 999, CONFLICT_CODE, CONFLICT_CODE}; - for (int x = 0; x < list.size(); x++) { - ListenerForTesting.StatusEvent se = list.get(x); - assertSame(sub.getSID(), se.sid); - assertEquals(codes[x], se.status.getCode()); - } + assertManageResult(listener, PullWarning, NOT_FOUND_CODE, STATUS_TERMINUS, manager, getNotFoundStatus(sid)); + assertManageResult(listener, PullWarning, REQUEST_TIMEOUT_CODE, STATUS_TERMINUS, manager, getRequestTimeoutStatus(sid)); + assertManageResult(listener, PullWarning, CONFLICT_CODE, STATUS_TERMINUS, manager, getConflictStatus(sid, BATCH_COMPLETED)); + assertManageResult(listener, PullWarning, CONFLICT_CODE, STATUS_TERMINUS, manager, getConflictStatus(sid, MESSAGE_SIZE_EXCEEDS_MAX_BYTES)); + assertManageResult(listener, PullWarning, CONFLICT_CODE, STATUS_HANDLED, manager, getConflictStatus(sid, EXCEEDED_MAX_WAITING)); + assertManageResult(listener, PullWarning, CONFLICT_CODE, STATUS_HANDLED, manager, getConflictStatus(sid, EXCEEDED_MAX_REQUEST_BATCH)); + assertManageResult(listener, PullWarning, CONFLICT_CODE, STATUS_HANDLED, manager, getConflictStatus(sid, EXCEEDED_MAX_REQUEST_EXPIRES)); + assertManageResult(listener, PullWarning, CONFLICT_CODE, STATUS_HANDLED, manager, getConflictStatus(sid, EXCEEDED_MAX_REQUEST_MAX_BYTES)); + + assertManageResult(listener, PullError, BAD_REQUEST_CODE, STATUS_ERROR, manager, getBadRequest(sid)); + assertManageResult(listener, PullError, 999, STATUS_ERROR, manager, getUnkownStatus(sid)); + assertManageResult(listener, PullError, CONFLICT_CODE, STATUS_ERROR, manager, getConflictStatus(sid, CONSUMER_DELETED)); + assertManageResult(listener, PullError, CONFLICT_CODE, STATUS_ERROR, manager, getConflictStatus(sid, CONSUMER_IS_PUSH_BASED)); + } + + private static void assertManageResult(Listener listener, ListenerStatusType expectedType, int expectedStatusCode, ManageResult expecteManageResult, PullMessageManager manager, NatsMessage message) { + listener.queueStatus(expectedType, expectedStatusCode); + assertEquals(expecteManageResult, manager.manage(message)); + listener.validate(); } @Test public void testPushManagerHeartbeats() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - runInJsServer(listener, nc -> { + Listener listener = new Listener(); + runInSharedOwnNc(listener, nc -> { PushMessageManager pushMgr = getPushManager(nc, push_xhb_xfc(), null, false, true, false); NatsJetStreamSubscription sub = mockSub((NatsConnection)nc, pushMgr); - listener.reset(); - listener.prepForHeartbeatAlarm(); + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); pushMgr.startup(sub); - ListenerForTesting.HeartbeatAlarmEvent event = listener.waitForHeartbeatAlarm(1000); - assertNull(event); + listener.validateNotReceived(); listener.reset(); - listener.prepForHeartbeatAlarm(); + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); pushMgr = getPushManager(nc, push_xhb_xfc(), null, false, false, false); sub = mockSub((NatsConnection)nc, pushMgr); pushMgr.startup(sub); - event = listener.waitForHeartbeatAlarm(1000); - assertNull(event); + listener.validateNotReceived(); listener.reset(); - listener.prepForHeartbeatAlarm(); + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); PushSubscribeOptions pso = ConsumerConfiguration.builder().idleHeartbeat(100).buildPushSubscribeOptions(); pushMgr = getPushManager(nc, pso, null, false, true, false); sub = mockSub((NatsConnection)nc, pushMgr); pushMgr.startup(sub); - event = listener.waitForHeartbeatAlarm(1000); - assertNotNull(event); + listener.validate(); listener.reset(); - listener.prepForHeartbeatAlarm(); + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); pushMgr = getPushManager(nc, pso, null, false, false, false); sub = mockSub((NatsConnection)nc, pushMgr); pushMgr.startup(sub); - event = listener.waitForHeartbeatAlarm(1000); - assertNotNull(event); + pushMgr.startup(sub); }); } @Test public void testPullManagerHeartbeats() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - runInJsServer(listener, nc -> { + Listener listener = new Listener(); + runInSharedOwnNc(listener, nc -> { + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); PullMessageManager pullMgr = getPullManager(nc, null, true); NatsJetStreamSubscription sub = mockSub((NatsConnection)nc, pullMgr); pullMgr.startup(sub); pullMgr.startPullRequest("pullSubject", PullRequestOptions.builder(1).build(), false, null); - assertEquals(0, listener.getHeartbeatAlarms().size()); + listener.validateNotReceived(); listener.reset(); - listener.prepForHeartbeatAlarm(); + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); pullMgr.startPullRequest("pullSubject", PullRequestOptions.builder(1).expiresIn(10000).idleHeartbeat(100).build(), false, null); - ListenerForTesting.HeartbeatAlarmEvent event = listener.waitForHeartbeatAlarm(1000); - assertNotNull(event); + listener.validate(); listener.reset(); - listener.prepForHeartbeatAlarm(); + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); pullMgr.startPullRequest("pullSubject", PullRequestOptions.builder(1).expiresIn(10000).idleHeartbeat(100).build(), false, null); - event = listener.waitForHeartbeatAlarm(1000); - assertNotNull(event); + listener.validate(); listener.reset(); - listener.prepForHeartbeatAlarm(); + listener.queueHeartbeat(SHORT_VALIDATE_TIMEOUT); pullMgr.startPullRequest("pullSubject", PullRequestOptions.builder(1).build(), false, null); - event = listener.waitForHeartbeatAlarm(1000); - assertNull(event); + listener.validateNotReceived(); }); } @@ -308,7 +278,7 @@ public void test_push_fc() { assertEquals(getFcSubject(3), mpi.fcSubject); assertEquals(3, mpi.pubCount); - assertEquals(ManageResult.STATUS_ERROR, pmm.manage(getHeartbeat(sid))); + assertEquals(STATUS_ERROR, pmm.manage(getHeartbeat(sid))); assertEquals(getFcSubject(3), pmm.getLastFcSubject()); assertEquals(getFcSubject(3), mpi.fcSubject); assertEquals(3, mpi.pubCount); @@ -342,12 +312,12 @@ private void _push_xfc(SubscribeOptions so) { pmm.startup(sub); assertNull(pmm.getLastFcSubject()); - assertEquals(ManageResult.STATUS_ERROR, pmm.manage(getFlowControl(1, sid))); + assertEquals(STATUS_ERROR, pmm.manage(getFlowControl(1, sid))); assertNull(pmm.getLastFcSubject()); assertNull(mpi.fcSubject); assertEquals(0, mpi.pubCount); - assertEquals(ManageResult.STATUS_ERROR, pmm.manage(getHeartbeat(sid))); + assertEquals(STATUS_ERROR, pmm.manage(getHeartbeat(sid))); assertNull(pmm.getLastFcSubject()); assertNull(mpi.fcSubject); assertEquals(0, mpi.pubCount); @@ -369,8 +339,8 @@ private void _push_xfc(SubscribeOptions so) { // coverage manager assertEquals(ManageResult.MESSAGE, pmm.manage(getTestJsMessage(1, sid))); - assertEquals(ManageResult.STATUS_ERROR, pmm.manage(getFlowControl(1, sid))); - assertEquals(ManageResult.STATUS_ERROR, pmm.manage(getFcHeartbeat(1, sid))); + assertEquals(STATUS_ERROR, pmm.manage(getFlowControl(1, sid))); + assertEquals(STATUS_ERROR, pmm.manage(getFcHeartbeat(1, sid))); // coverage beforeQueueProcessor assertTrue(pmm.beforeQueueProcessorImpl(getTestJsMessage(3, sid))); @@ -386,14 +356,10 @@ private void _push_xfc(SubscribeOptions so) { @Test public void test_received_time() throws Exception { - runInJsServer(nc -> { - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - - _received_time_yes(push_hb_fc(), js, tsc.subject()); - _received_time_yes(push_hb_xfc(), js, tsc.subject()); - _received_time_no(js, jsm, tsc.stream, tsc.subject(), js.subscribe(tsc.subject(), push_xhb_xfc())); + runInShared((nc, ctx) -> { + _received_time_yes(push_hb_fc(), ctx.js, ctx.subject()); + _received_time_yes(push_hb_xfc(), ctx.js, ctx.subject()); + _received_time_no(ctx.js, ctx.jsm, ctx.stream, ctx.subject(), ctx.js.subscribe(ctx.subject(), push_xhb_xfc())); }); } @@ -416,7 +382,7 @@ PushMessageManager findStatusManager(NatsJetStreamSubscription sub) { return (PushMessageManager)mm; } return null; - }; + } private void _received_time_no(JetStream js, JetStreamManagement jsm, String stream, String subject, JetStreamSubscription sub) throws IOException, JetStreamApiException, InterruptedException { js.publish(subject, dataBytes(0)); @@ -429,8 +395,8 @@ private void _received_time_no(JetStream js, JetStreamManagement jsm, String str @Test public void test_hb_yes_settings() throws Exception { - runInJsServer(nc -> { - NatsJetStreamSubscription sub = genericPushSub(nc); + runInShared((nc, ctx) -> { + NatsJetStreamSubscription sub = genericPushSub(ctx); ConsumerConfiguration cc = ConsumerConfiguration.builder().idleHeartbeat(1000).build(); @@ -462,8 +428,8 @@ public void test_hb_yes_settings() throws Exception { @Test public void test_hb_no_settings() throws Exception { - runInJsServer(nc -> { - NatsJetStreamSubscription sub = genericPushSub(nc); + runInShared((nc, ctx) -> { + NatsJetStreamSubscription sub = genericPushSub(ctx); SubscribeOptions so = push_xhb_xfc(); PushMessageManager manager = getPushManager(nc, so, sub, false); assertEquals(0, manager.getIdleHeartbeatSetting()); @@ -576,7 +542,7 @@ static class MockPublishInternal extends NatsConnection { String fcSubject; public MockPublishInternal() { - this(new Options.Builder().errorListener(new ErrorListener() {}).build()); + this(optionsBuilder().build()); } public MockPublishInternal(Options options) { @@ -591,23 +557,26 @@ protected void publishInternal(@NonNull String subject, @Nullable String replyTo } static AtomicInteger ID = new AtomicInteger(); - private static NatsJetStreamSubscription genericPushSub(Connection nc) throws IOException, JetStreamApiException { - String subject = genericSub(nc); - JetStream js = nc.jetStream(); - return (NatsJetStreamSubscription) js.subscribe(subject); + private static NatsJetStreamSubscription genericPushSub(JetStreamTestingContext ctx) throws IOException, JetStreamApiException { + String subject = genericSub(ctx); + return (NatsJetStreamSubscription) ctx.js.subscribe(subject); } - private static NatsJetStreamSubscription genericPullSub(Connection nc) throws IOException, JetStreamApiException { - String subject = genericSub(nc); - JetStream js = nc.jetStream(); - return (NatsJetStreamSubscription) js.subscribe(subject, PullSubscribeOptions.DEFAULT_PULL_OPTS); + private static NatsJetStreamSubscription genericPullSub(JetStreamTestingContext ctx) throws IOException, JetStreamApiException { + String subject = genericSub(ctx); + return (NatsJetStreamSubscription) ctx.js.subscribe(subject, PullSubscribeOptions.DEFAULT_PULL_OPTS); } - private static String genericSub(Connection nc) throws IOException, JetStreamApiException { + private static String genericSub(JetStreamTestingContext ctx) throws IOException, JetStreamApiException { String id = "-" + ID.incrementAndGet() + "-" + System.currentTimeMillis(); - String stream = STREAM + id; - String subject = STREAM + id; - createMemoryStream(nc, stream, subject); + String stream = random() + id; + String subject = random() + id; + StreamConfiguration sc = StreamConfiguration.builder() + .name(stream) + .storageType(StorageType.Memory) + .subjects(subject) + .build(); + ctx.addStream(sc); return subject; } @@ -637,6 +606,7 @@ protected void shutdown() {} @Test public void testMessageManagerInterfaceDefaultImplCoverage() { // make a dummy connection so we can make a subscription + // notice we don't nc.connect Options options = Options.builder().build(); NatsConnection nc = new NatsConnection(options); diff --git a/src/test/java/io/nats/client/impl/MessageQueueTests.java b/src/test/java/io/nats/client/impl/MessageQueueTests.java index 4f9d33cfa..534f63cca 100644 --- a/src/test/java/io/nats/client/impl/MessageQueueTests.java +++ b/src/test/java/io/nats/client/impl/MessageQueueTests.java @@ -19,13 +19,13 @@ import java.util.concurrent.atomic.AtomicInteger; import static io.nats.client.support.NatsConstants.OUTPUT_QUEUE_IS_FULL; -import static io.nats.client.utils.TestBase.sleep; -import static io.nats.client.utils.TestBase.variant; +import static io.nats.client.utils.TestBase.random; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class MessageQueueTests { private static NatsMessage getTestMessage() { - return new NatsMessage(variant(), null, null); + return new NatsMessage(random(), null, null); } private static final long TEST_MESSAGE_BYTES = getTestMessage().getSizeInBytes(); diff --git a/src/test/java/io/nats/client/impl/NatsConnectionImplTests.java b/src/test/java/io/nats/client/impl/NatsConnectionImplTests.java index e5a1ea7a2..55cfacb61 100644 --- a/src/test/java/io/nats/client/impl/NatsConnectionImplTests.java +++ b/src/test/java/io/nats/client/impl/NatsConnectionImplTests.java @@ -15,24 +15,27 @@ import io.nats.client.NatsTestServer; import io.nats.client.Options; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; -import static io.nats.client.utils.TestBase.*; +import static io.nats.client.utils.ConnectionUtils.closeAndConfirm; +import static io.nats.client.utils.ConnectionUtils.managedConnect; import static org.junit.jupiter.api.Assertions.*; -public class NatsConnectionImplTests { +public class NatsConnectionImplTests extends TestBase { @Test public void testConnectionClosedProperly() throws Exception { - try (NatsTestServer server = new NatsTestServer(true)) { - Options options = standardOptions(server.getNatsLocalhostUri()); + runInSharedServer(server -> { + Options options = Options.builder() + .server(NatsTestServer.getLocalhostUri(server.getPort())) + .build(); verifyInternalExecutors(options); // using options copied from options to demonstrate the executors @@ -47,7 +50,8 @@ public void testConnectionClosedProperly() throws Exception { assertFalse(es.isShutdown()); assertFalse(ses.isShutdown()); - options = standardOptionsBuilder(server.getNatsLocalhostUri()) + options = Options.builder() + .server(NatsTestServer.getLocalhostUri(server.getPort())) .executor(es) .scheduledExecutor(ses) .callbackExecutor(callbackEs) @@ -60,7 +64,8 @@ public void testConnectionClosedProperly() throws Exception { ThreadFactory callbackThreadFactory = r -> new Thread(r, "callback"); ThreadFactory connectThreadFactory = r -> new Thread(r, "connect"); - options = standardOptionsBuilder(server.getNatsLocalhostUri()) + options = Options.builder() + .server(NatsTestServer.getLocalhostUri(server.getPort())) .executor(es) .scheduledExecutor(ses) .callbackThreadFactory(callbackThreadFactory) @@ -76,11 +81,11 @@ public void testConnectionClosedProperly() throws Exception { assertTrue(ses.isShutdown()); assertTrue(callbackEs.isShutdown()); assertTrue(connectEs.isShutdown()); - } + }); } - private static void verifyInternalExecutors(Options options) throws InterruptedException, IOException { - try (NatsConnection nc = (NatsConnection)standardConnection(options)) { + private static void verifyInternalExecutors(Options options) throws InterruptedException { + try (NatsConnection nc = (NatsConnection) managedConnect(options)) { ExecutorService es = options.getExecutor(); ScheduledExecutorService ses = options.getScheduledExecutor(); ExecutorService callbackEs = options.getCallbackExecutor(); @@ -120,8 +125,8 @@ private static void verifyInternalExecutors(Options options) throws InterruptedE private static void verifyExternalExecutors(Options options, ExecutorService userEs, ScheduledExecutorService userSes, ExecutorService userCallbackEs, ExecutorService userConnectEs - ) throws InterruptedException, IOException { - try (NatsConnection nc = (NatsConnection)standardConnection(options)) { + ) throws InterruptedException { + try (NatsConnection nc = (NatsConnection) managedConnect(options)) { ExecutorService es = options.getExecutor(); ScheduledExecutorService ses = options.getScheduledExecutor(); ExecutorService callbackEs = options.getCallbackExecutor(); @@ -187,15 +192,17 @@ private static void verifyExternalExecutors(Options options, @Test void testExecutorUseCount() throws Exception { - try (NatsTestServer ts = new NatsTestServer()) { + runInSharedServer(server -> { AtomicLong count1 = new AtomicLong(); AtomicLong count2 = new AtomicLong(); - Options options = new Options.Builder().server(ts.getURI()).build(); + Options options = Options.builder() + .server(NatsTestServer.getLocalhostUri(server.getPort())) + .build(); // THESE SHARE THE EXACT SAME OPTIONS INSTANCE - NatsConnection nc1 = (NatsConnection)standardConnection(options); - NatsConnection nc2 = (NatsConnection)standardConnection(options); + NatsConnection nc1 = (NatsConnection) managedConnect(options); + NatsConnection nc2 = (NatsConnection) managedConnect(options); // Both connections live, both callbacks work nc1.makeCallback(count1::incrementAndGet); @@ -205,7 +212,7 @@ void testExecutorUseCount() throws Exception { assertEquals(1, count2.get()); // Close first connection, second connection callback will still work - standardCloseConnection(nc1); + closeAndConfirm(nc1); Thread.sleep(250); // allow time for shutdownExecutors() to process nc1.makeCallback(count1::incrementAndGet); @@ -215,7 +222,7 @@ void testExecutorUseCount() throws Exception { assertEquals(2, count2.get()); // Close second connection, no callbacks will work - standardCloseConnection(nc2); + closeAndConfirm(nc2); Thread.sleep(250); // allow time for shutdownExecutors() to process nc1.makeCallback(count1::incrementAndGet); @@ -223,6 +230,6 @@ void testExecutorUseCount() throws Exception { Thread.sleep(250); // allow time for callbacks to happen assertEquals(1, count1.get()); assertEquals(2, count2.get()); - } + }); } } diff --git a/src/test/java/io/nats/client/impl/NatsJetStreamMetaDataTests.java b/src/test/java/io/nats/client/impl/NatsJetStreamMetaDataTests.java index 2b98fd383..8d28e6ecd 100644 --- a/src/test/java/io/nats/client/impl/NatsJetStreamMetaDataTests.java +++ b/src/test/java/io/nats/client/impl/NatsJetStreamMetaDataTests.java @@ -80,6 +80,7 @@ public void testNotInVersion() { @Test public void testInvalidMetaData() { + assertThrows(IllegalArgumentException.class, () -> getTestMessage(InvalidMetaData).metaData()); assertThrows(IllegalArgumentException.class, () -> getTestMessage(InvalidMetaLt8Tokens).metaData()); assertThrows(IllegalArgumentException.class, () -> getTestMessage(InvalidMeta10Tokens).metaData()); diff --git a/src/test/java/io/nats/client/impl/NatsMessageTests.java b/src/test/java/io/nats/client/impl/NatsMessageTests.java index 60b7a73d7..68a58ffc8 100644 --- a/src/test/java/io/nats/client/impl/NatsMessageTests.java +++ b/src/test/java/io/nats/client/impl/NatsMessageTests.java @@ -16,6 +16,7 @@ import io.nats.client.*; import io.nats.client.NatsServerProtocolMock.ExitAt; import io.nats.client.support.IncomingHeadersProcessor; +import io.nats.client.utils.ConnectionUtils; import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; @@ -24,6 +25,8 @@ import static io.nats.client.support.NatsConstants.OP_PING; import static io.nats.client.support.NatsConstants.OP_PING_BYTES; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static io.nats.client.utils.ResourceUtils.dataAsLines; import static org.junit.jupiter.api.Assertions.*; @@ -110,66 +113,38 @@ public void testSizeOnPublishMessageOnlySubject() { } @Test - public void testCustomMaxControlLine() { - assertThrows(IllegalArgumentException.class, () -> { - byte[] body = new byte[10]; - String subject = "subject"; - int maxControlLine = 1024; - - while (subject.length() <= maxControlLine) { - subject += subject; - } - - try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - maxControlLine(maxControlLine). - build(); - Connection nc = Nats.connect(options); - standardConnectionWait(nc); - nc.request(subject, body); - } - }); - } - - @Test - public void testBigProtocolLineWithoutBody() { - assertThrows(IllegalArgumentException.class, () -> { - String subject = "subject"; + public void testCustomMaxControlLine() throws Exception { + byte[] body = new byte[10]; + int maxControlLine = 1024; - while (subject.length() <= Options.DEFAULT_MAX_CONTROL_LINE) { - subject += subject; - } + StringBuilder subject = new StringBuilder(random()); + while (subject.length() <= maxControlLine) { + subject.append(subject); + } - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - nc.subscribe(subject); - } - }); + runInSharedOwnNc(optionsBuilder().maxReconnects(0).maxControlLine(maxControlLine), + nc -> assertThrows(IllegalArgumentException.class, () -> nc.request(subject.toString(), body))); } @Test - public void testBigProtocolLineWithBody() { - assertThrows(IllegalArgumentException.class, () -> { - byte[] body = new byte[10]; - String subject = "subject"; - String replyTo = "reply"; - - while (subject.length() <= Options.DEFAULT_MAX_CONTROL_LINE) { - subject += subject; - } - - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - standardConnectionWait(nc); - nc.publish(subject, replyTo, body); + public void testBigProtocolLine() throws Exception { + StringBuilder subject = new StringBuilder(random()); + while (subject.length() <= Options.DEFAULT_MAX_CONTROL_LINE) { + subject.append(subject); + } + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { + try (Connection nc = ConnectionUtils.standardConnect(options(mockTs))) { + // Without Body + assertThrows(IllegalArgumentException.class, () -> nc.subscribe(subject.toString())); + + // With Body + byte[] body = new byte[10]; + String replyTo = "reply"; + assertThrows(IllegalArgumentException.class, () -> nc.publish(subject.toString(), replyTo, body)); } - }); + } } - @Test public void notJetStream() throws Exception { NatsMessage m = testMessage(); @@ -331,14 +306,14 @@ private NatsMessage testMessage() { @Test public void testHeadersMutableBeforePublish() throws Exception { - jsServer.run(connection -> { - String subject = subject(); - Subscription sub = connection.subscribe(subject); + runInShared(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); Headers h = new Headers(); h.put("one", "A"); Message m = new NatsMessage(subject, null, h, null); - connection.publish(m); + nc.publish(m); Message incoming = sub.nextMessage(1000); assertEquals(1, incoming.getHeaders().size()); @@ -346,13 +321,13 @@ public void testHeadersMutableBeforePublish() throws Exception { // so this will affect the message which is the same // as the local copy h.put("two", "B"); - connection.publish(m); + nc.publish(m); incoming = sub.nextMessage(1000); assertEquals(2, incoming.getHeaders().size()); // also if you get the headers from the message m.getHeaders().put("three", "C"); - connection.publish(m); + nc.publish(m); incoming = sub.nextMessage(1000); assertEquals(3, incoming.getHeaders().size()); }); diff --git a/src/test/java/io/nats/client/impl/NatsProvidersAndImplementationsTests.java b/src/test/java/io/nats/client/impl/NatsProvidersAndImplementationsTests.java index abb77402e..3df73be5c 100644 --- a/src/test/java/io/nats/client/impl/NatsProvidersAndImplementationsTests.java +++ b/src/test/java/io/nats/client/impl/NatsProvidersAndImplementationsTests.java @@ -43,8 +43,7 @@ public void testNatsInetAddress() throws UnknownHostException { validateNatsInetAddress("synadia.io"); validateNatsInetAddress("synadia.com"); - NatsInetAddress.setProvider(new NatsInetAddressProvider() { - }); + NatsInetAddress.setProvider(new NatsInetAddressProvider() {}); validateNatsInetAddress("synadia.io"); validateNatsInetAddress("synadia.com"); } @@ -64,8 +63,8 @@ private static void validateNatsInetAddress(String host) throws UnknownHostExcep assertEquals(ia, iaByAddress); } - InetAddress ia = NatsInetAddress.getLoopbackAddress(); - ia = NatsInetAddress.getLocalHost(); + NatsInetAddress.getLoopbackAddress(); + NatsInetAddress.getLocalHost(); } @Test @@ -124,8 +123,7 @@ public void afterConstruct(@NonNull Options options) { } @Override - public void upgradeToSecure() throws IOException { - } + public void upgradeToSecure() throws IOException {} @Override public int read(byte[] dst, int off, int len) throws IOException { @@ -137,8 +135,7 @@ public void write(byte[] src, int toWrite) throws IOException { } @Override - public void shutdownInput() throws IOException { - } + public void shutdownInput() throws IOException {} @Override public void close() throws IOException { @@ -154,7 +151,9 @@ public void flush() throws IOException { public void testSocketDataPort() throws IOException, URISyntaxException { // this is coverage for connect, afterConstruct and forceClose InterfaceCoverageDataPort cdp = new InterfaceCoverageDataPort(); + //noinspection DataFlowIssue cdp.connect((NatsConnection) null, new NatsUri(Options.DEFAULT_URL), 0); + //noinspection DataFlowIssue cdp.afterConstruct(null); cdp.forceClose(); diff --git a/src/test/java/io/nats/client/impl/NatsStatisticsTests.java b/src/test/java/io/nats/client/impl/NatsStatisticsTests.java index f98115c4a..f4a380f3b 100644 --- a/src/test/java/io/nats/client/impl/NatsStatisticsTests.java +++ b/src/test/java/io/nats/client/impl/NatsStatisticsTests.java @@ -13,7 +13,11 @@ package io.nats.client.impl; -import io.nats.client.*; +import io.nats.client.Dispatcher; +import io.nats.client.Message; +import io.nats.client.MessageHandler; +import io.nats.client.Statistics; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -21,27 +25,20 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import static io.nats.client.utils.TestBase.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; -public class NatsStatisticsTests { +public class NatsStatisticsTests extends TestBase { @Test public void testHumanReadableString() throws Exception { - // This test is purely for coverage, any test without a human is likely pedantic - try (NatsTestServer ts = new NatsTestServer(); - Connection nc = Nats.connect(new Options.Builder() - .server(ts.getURI()) - .turnOnAdvancedStats() - .build())) { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - nc.publish(msg.getReplyTo(), new byte[16]); - }); - d.subscribe("subject"); + runInSharedOwnNc(optionsBuilder().turnOnAdvancedStats(), nc -> { + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), new byte[16])); + String subject = random(); + d.subscribe(subject); nc.flush(Duration.ofMillis(500)); - Future incoming = nc.request("subject", new byte[8]); + Future incoming = nc.request(subject, new byte[8]); nc.flush(Duration.ofMillis(500)); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); @@ -51,166 +48,140 @@ public void testHumanReadableString() throws Exception { assertTrue(str.length() > 0); assertTrue(str.contains("### Connection ###")); assertTrue(str.contains("Socket Writes")); - } + }); } @Test public void testInOutOKRequestStats() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).verbose().build(); - Connection nc = Nats.connect(options); - StatisticsCollector stats = ((NatsConnection) nc).getStatisticsCollector(); - - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - Message m = NatsMessage.builder() - .subject(msg.getReplyTo()) - .data(new byte[16]) - .headers(new Headers().put("header", "reply")) - .build(); - nc.publish(m); - }); - d.subscribe("subject"); - + runInSharedOwnNc(optionsBuilder().verbose(), nc -> { + Dispatcher d = nc.createDispatcher(msg -> { Message m = NatsMessage.builder() - .subject("subject") - .data(new byte[8]) - .headers(new Headers().put("header", "request")) + .subject(msg.getReplyTo()) + .data("replyreplyreply!") // 16 bytes + .headers(new Headers().put("header", "reply")) .build(); - Future incoming = nc.request(m); - Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - - assertNotNull(msg); - assertEquals(0, stats.getOutstandingRequests(), "outstanding"); - assertTrue(stats.getInBytes() > 200, "bytes in"); - assertTrue(stats.getOutBytes() > 400, "bytes out"); - assertEquals(2, stats.getInMsgs(), "messages in"); // reply & request - assertEquals(6, stats.getOutMsgs(), "messages out"); // ping, sub, pub, msg, pub, msg - assertEquals(5, stats.getOKs(), "oks"); //sub, pub, msg, pub, msg - } finally { - nc.close(); - } - } + nc.publish(m); + }); + String subject = random(); + d.subscribe(subject); + + Message m = NatsMessage.builder() + .subject(subject) + .data("request!") // 8 bytes + .headers(new Headers().put("header", "request")) + .build(); + Future fRequest = nc.request(m); + Message msg = fRequest.get(500, TimeUnit.MILLISECONDS); + assertNotNull(msg); + + Statistics stats = nc.getStatistics(); + assertEquals(0, stats.getOutstandingRequests(), "outstanding"); + assertTrue(stats.getInBytes() > 200, "bytes in"); + assertTrue(stats.getOutBytes() > 400, "bytes out"); + assertEquals(2, stats.getInMsgs(), "messages in"); // reply & request + assertEquals(6, stats.getOutMsgs(), "messages out"); // ping, sub, pub, msg, pub, msg + assertEquals(5, stats.getOKs(), "oks"); //sub, pub, msg, pub, msg + }); } @Test public void testReadWriteAdvancedStatsEnabled() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).verbose().turnOnAdvancedStats().build(); - Connection nc = Nats.connect(options); - StatisticsCollector stats = ((NatsConnection) nc).getStatisticsCollector(); - - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - Message m = NatsMessage.builder() - .subject(msg.getReplyTo()) - .data(new byte[16]) - .headers(new Headers().put("header", "reply")) - .build(); - nc.publish(m); - }); - d.subscribe("subject"); - + runInSharedOwnNc(optionsBuilder().verbose().turnOnAdvancedStats(), nc -> { + Dispatcher d = nc.createDispatcher(msg -> { Message m = NatsMessage.builder() - .subject("subject") - .data(new byte[8]) - .headers(new Headers().put("header", "request")) - .build(); - Future incoming = nc.request(m); - Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - - assertNotNull(msg); - - // The read/write advanced stats are only exposed via toString, so assert on that - String stringStats = stats.toString(); - assertTrue(stringStats.contains("Socket Reads"), "readStats count"); - assertTrue(stringStats.contains("Average Bytes Per Read"), "readStats average bytes"); - assertTrue(stringStats.contains("Min Bytes Per Read"), "readStats min bytes"); - assertTrue(stringStats.contains("Max Bytes Per Read"), "readStats max bytes"); - - assertTrue(stringStats.contains("Socket Writes"), "writeStats count"); - assertTrue(stringStats.contains("Average Bytes Per Write"), "writeStats average bytes"); - assertTrue(stringStats.contains("Min Bytes Per Write"), "writeStats min bytes"); - assertTrue(stringStats.contains("Max Bytes Per Write"), "writeStats max bytes"); - } finally { - nc.close(); - } - } + .subject(msg.getReplyTo()) + .data(new byte[16]) + .headers(new Headers().put("header", "reply")) + .build(); + nc.publish(m); + }); + String subject = random(); + d.subscribe(subject); + + Message m = NatsMessage.builder() + .subject(subject) + .data(new byte[8]) + .headers(new Headers().put("header", "request")) + .build(); + Future incoming = nc.request(m); + Message msg = incoming.get(500, TimeUnit.MILLISECONDS); + + assertNotNull(msg); + + // The read/write advanced stats are only exposed via toString, so assert on that + Statistics stats = nc.getStatistics(); + String stringStats = stats.toString(); + assertTrue(stringStats.contains("Socket Reads"), "readStats count"); + assertTrue(stringStats.contains("Average Bytes Per Read"), "readStats average bytes"); + assertTrue(stringStats.contains("Min Bytes Per Read"), "readStats min bytes"); + assertTrue(stringStats.contains("Max Bytes Per Read"), "readStats max bytes"); + + assertTrue(stringStats.contains("Socket Writes"), "writeStats count"); + assertTrue(stringStats.contains("Average Bytes Per Write"), "writeStats average bytes"); + assertTrue(stringStats.contains("Min Bytes Per Write"), "writeStats min bytes"); + assertTrue(stringStats.contains("Max Bytes Per Write"), "writeStats max bytes"); + }); } @Test public void testReadWriteAdvancedStatsDisabled() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).verbose().build(); - Connection nc = Nats.connect(options); - StatisticsCollector stats = ((NatsConnection) nc).getStatisticsCollector(); - - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - Message m = NatsMessage.builder() - .subject(msg.getReplyTo()) - .data(new byte[16]) - .headers(new Headers().put("header", "reply")) - .build(); - nc.publish(m); - }); - d.subscribe("subject"); - + runInSharedOwnNc(optionsBuilder().verbose(), nc -> { + Dispatcher d = nc.createDispatcher(msg -> { Message m = NatsMessage.builder() - .subject("subject") - .data(new byte[8]) - .headers(new Headers().put("header", "request")) - .build(); - Future incoming = nc.request(m); - Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - - assertNotNull(msg); + .subject(msg.getReplyTo()) + .data(new byte[16]) + .headers(new Headers().put("header", "reply")) + .build(); + nc.publish(m); + }); + String subject = random(); + d.subscribe(subject); + + Message m = NatsMessage.builder() + .subject(subject) + .data(new byte[8]) + .headers(new Headers().put("header", "request")) + .build(); + Future incoming = nc.request(m); + Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - incoming = nc.request(m); - incoming.get(500, TimeUnit.MILLISECONDS); + assertNotNull(msg); - assertNotNull(msg); + incoming = nc.request(m); + incoming.get(500, TimeUnit.MILLISECONDS); - incoming = nc.request(m); - incoming.get(500, TimeUnit.MILLISECONDS); + assertNotNull(msg); - assertNotNull(msg); + incoming = nc.request(m); + incoming.get(500, TimeUnit.MILLISECONDS); - incoming = nc.request(m); - incoming.get(500, TimeUnit.MILLISECONDS); + assertNotNull(msg); - assertNotNull(msg); + incoming = nc.request(m); + incoming.get(500, TimeUnit.MILLISECONDS); - // The read/write advanced stats are only exposed via toString, so assert on that - String stringStats = stats.toString(); - assertFalse(stringStats.contains("Socket Reads"), "readStats count"); - assertFalse(stringStats.contains("Average Bytes Per Read"), "readStats average bytes"); - assertFalse(stringStats.contains("Min Bytes Per Read"), "readStats min bytes"); - assertFalse(stringStats.contains("Max Bytes Per Read"), "readStats max bytes"); + assertNotNull(msg); - assertFalse(stringStats.contains("Socket Writes"), "writeStats count"); - assertFalse(stringStats.contains("Average Bytes Per Write"), "writeStats average bytes"); - assertFalse(stringStats.contains("Min Bytes Per Write"), "writeStats min bytes"); - assertFalse(stringStats.contains("Max Bytes Per Write"), "writeStats max bytes"); - } finally { - nc.close(); - } - } + // The read/write advanced stats are only exposed via toString, so assert on that + Statistics stats = nc.getStatistics(); + String stringStats = stats.toString(); + assertFalse(stringStats.contains("Socket Reads"), "readStats count"); + assertFalse(stringStats.contains("Average Bytes Per Read"), "readStats average bytes"); + assertFalse(stringStats.contains("Min Bytes Per Read"), "readStats min bytes"); + assertFalse(stringStats.contains("Max Bytes Per Read"), "readStats max bytes"); + + assertFalse(stringStats.contains("Socket Writes"), "writeStats count"); + assertFalse(stringStats.contains("Average Bytes Per Write"), "writeStats average bytes"); + assertFalse(stringStats.contains("Min Bytes Per Write"), "writeStats min bytes"); + assertFalse(stringStats.contains("Max Bytes Per Write"), "writeStats max bytes"); + }); } @Test public void testOrphanDuplicateRepliesAdvancedStatsEnabled() throws Exception { - Options.Builder builder = new Options.Builder().turnOnAdvancedStats(); - - runInServer(builder, nc -> { + runInSharedOwnNc(optionsBuilder().turnOnAdvancedStats(), nc -> { AtomicInteger requests = new AtomicInteger(); - MessageHandler handler = (msg) -> { + MessageHandler handler = msg -> { requests.incrementAndGet(); nc.publish(msg.getReplyTo(), null); }; @@ -221,12 +192,13 @@ public void testOrphanDuplicateRepliesAdvancedStatsEnabled() throws Exception { sleep(5000); handler.onMessage(msg); }); - d1.subscribe(SUBJECT); - d2.subscribe(SUBJECT); - d3.subscribe(SUBJECT); - d4.subscribe(SUBJECT); + String subject = random(); + d1.subscribe(subject); + d2.subscribe(subject); + d3.subscribe(subject); + d4.subscribe(subject); - Message reply = nc.request(SUBJECT, null, Duration.ofSeconds(2)); + Message reply = nc.request(subject, null, Duration.ofSeconds(2)); assertNotNull(reply); sleep(2000); assertEquals(3, requests.get()); @@ -250,11 +222,9 @@ public void testOrphanDuplicateRepliesAdvancedStatsEnabled() throws Exception { @Test public void testOrphanDuplicateRepliesAdvancedStatsDisabled() throws Exception { - Options.Builder builder = new Options.Builder(); - - runInServer(builder, nc -> { + runInSharedOwnNc(optionsBuilder(), nc -> { AtomicInteger requests = new AtomicInteger(); - MessageHandler handler = (msg) -> { + MessageHandler handler = msg -> { requests.incrementAndGet(); nc.publish(msg.getReplyTo(), null); }; @@ -265,12 +235,13 @@ public void testOrphanDuplicateRepliesAdvancedStatsDisabled() throws Exception { sleep(5000); handler.onMessage(msg); }); - d1.subscribe(SUBJECT); - d2.subscribe(SUBJECT); - d3.subscribe(SUBJECT); - d4.subscribe(SUBJECT); + String subject = random(); + d1.subscribe(subject); + d2.subscribe(subject); + d3.subscribe(subject); + d4.subscribe(subject); - Message reply = nc.request(SUBJECT, null, Duration.ofSeconds(2)); + Message reply = nc.request(subject, null, Duration.ofSeconds(2)); assertNotNull(reply); sleep(2000); assertEquals(3, requests.get()); diff --git a/src/test/java/io/nats/client/impl/ObjectStoreTests.java b/src/test/java/io/nats/client/impl/ObjectStoreTests.java index e2178ac2a..63ce74daa 100644 --- a/src/test/java/io/nats/client/impl/ObjectStoreTests.java +++ b/src/test/java/io/nats/client/impl/ObjectStoreTests.java @@ -14,7 +14,7 @@ import io.nats.client.*; import io.nats.client.api.*; -import io.nats.client.utils.TestBase; +import io.nats.client.utils.VersionUtils; import org.junit.jupiter.api.Test; import java.io.*; @@ -30,55 +30,52 @@ import static io.nats.client.api.ObjectStoreWatchOption.IGNORE_DELETE; import static io.nats.client.support.NatsJetStreamClientError.*; import static io.nats.client.support.NatsObjectStoreUtil.DEFAULT_CHUNK_SIZE; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class ObjectStoreTests extends JetStreamTestBase { @Test public void testWorkflow() throws Exception { - jsServer.run(nc -> { - ObjectStoreManagement osm = nc.objectStoreManagement(); + runInSharedCustom((nc, ctx) -> { nc.objectStoreManagement(ObjectStoreOptions.builder(DEFAULT_JS_OPTIONS).build()); // coverage Map metadata = new HashMap<>(); metadata.put(META_KEY, META_VALUE); - String bucket = bucket(); - String desc = variant(); - // create the bucket - ObjectStoreConfiguration osc = ObjectStoreConfiguration.builder(bucket) - .description(desc) + String bucket = random(); + String objectDesc = random(); + + ObjectStoreStatus status = ctx.osCreate(ctx.osBuilder(bucket) + .description(objectDesc) .ttl(Duration.ofHours(24)) - .storageType(StorageType.Memory) - .metadata(metadata) - .build(); + .metadata(metadata)); - ObjectStoreStatus status = osm.create(osc); - validateStatus(status, bucket, desc); - validateStatus(osm.getStatus(bucket), bucket, desc); + validateStatus(status, bucket, objectDesc); + validateStatus(ctx.osm.getStatus(bucket), bucket, objectDesc); - JetStreamManagement jsm = nc.jetStreamManagement(); - assertNotNull(jsm.getStreamInfo("OBJ_" + bucket)); + assertNotNull(ctx.jsm.getStreamInfo("OBJ_" + bucket)); - List names = osm.getBucketNames(); - assertEquals(1, names.size()); + List names = ctx.osm.getBucketNames(); assertTrue(names.contains(bucket)); // put some objects into the stores ObjectStore os = nc.objectStore(bucket); nc.objectStore(bucket, ObjectStoreOptions.builder(DEFAULT_JS_OPTIONS).build()); // coverage; - validateStatus(os.getStatus(), bucket, desc); + validateStatus(os.getStatus(), bucket, objectDesc); // object not found errors assertClientError(OsObjectNotFound, () -> os.get("notFound", new ByteArrayOutputStream())); assertClientError(OsObjectNotFound, () -> os.updateMeta("notFound", ObjectMeta.objectName("notFound"))); assertClientError(OsObjectNotFound, () -> os.delete("notFound")); - String objectName = name(); + String objectName = random(); + objectDesc = random(); + String[] keys = new String[]{random(), random()}; ObjectMeta meta = ObjectMeta.builder(objectName) - .description("object-desc") - .headers(new Headers().put(key(1), data(1)).put(key(2), data(21)).add(key(2), data(22))) + .description(objectDesc) + .headers(new Headers().put(keys[0], data(0)).put(keys[1], data(11)).add(keys[1], data(12))) .chunkSize(4096) .build(); @@ -90,14 +87,14 @@ public void testWorkflow() throws Exception { } File file = (File)input[1]; InputStream in = Files.newInputStream(file.toPath()); - ObjectInfo oi1 = validateObjectInfo(os.put(meta, in), bucket, objectName, len, expectedChunks, 4096); + ObjectInfo oi1 = validateObjectInfo(os.put(meta, in), bucket, objectName, objectDesc, len, expectedChunks, 4096, keys); - ByteArrayOutputStream baos = validateGet(os, bucket, objectName, len, expectedChunks, 4096); + ByteArrayOutputStream baos = validateGet(os, bucket, objectName, objectDesc, len, expectedChunks, 4096); byte[] bytes = baos.toByteArray(); byte[] bytes4k = Arrays.copyOf(bytes, 4096); - ObjectInfo oi2 = validateObjectInfo(os.put(meta, new ByteArrayInputStream(bytes4k)), bucket, objectName, 4096, 1, 4096); - validateGet(os, bucket, objectName, 4096, 1, 4096); + ObjectInfo oi2 = validateObjectInfo(os.put(meta, new ByteArrayInputStream(bytes4k)), bucket, objectName, objectDesc, 4096, 1, 4096, keys); + validateGet(os, bucket, objectName, objectDesc, 4096, 1, 4096); assertNotEquals(oi1.getNuid(), oi2.getNuid()); @@ -146,16 +143,16 @@ public void testWorkflow() throws Exception { os.updateMeta(oi.getObjectName(), ObjectMeta.objectName(oi3.getObjectName())); // alternate puts - String altName = name(); + String altName = random(); expectedChunks = len / DEFAULT_CHUNK_SIZE; if (expectedChunks * DEFAULT_CHUNK_SIZE < len) { expectedChunks++; } in = Files.newInputStream(file.toPath()); - validateObjectInfo(os.put(altName, in), bucket, altName, null, false, len, expectedChunks, DEFAULT_CHUNK_SIZE); + validateObjectInfo(os.put(altName, in), bucket, altName, null, len, expectedChunks, DEFAULT_CHUNK_SIZE, null); altName = file.getName(); - validateObjectInfo(os.put(file), bucket, altName, null, false, len, expectedChunks, DEFAULT_CHUNK_SIZE); + validateObjectInfo(os.put(file), bucket, altName, null, len, expectedChunks, DEFAULT_CHUNK_SIZE, null); }); } @@ -179,19 +176,15 @@ private static void validateStatus(ObjectStoreStatus status, String bucket, Stri } @SuppressWarnings("SameParameterValue") - private ByteArrayOutputStream validateGet(ObjectStore os, String bucket, String objectName, long len, long chunks, int chunkSize) throws IOException, JetStreamApiException, InterruptedException, NoSuchAlgorithmException { + private ByteArrayOutputStream validateGet(ObjectStore os, String bucket, String objectName, String objectDesc, long len, long chunks, int chunkSize) throws IOException, JetStreamApiException, InterruptedException, NoSuchAlgorithmException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectInfo oi = os.get(objectName, baos); assertEquals(len, baos.size()); - validateObjectInfo(oi, bucket, objectName, len, chunks, chunkSize); + validateObjectInfo(oi, bucket, objectName, objectDesc, len, chunks, chunkSize, null); return baos; } - private ObjectInfo validateObjectInfo(ObjectInfo oi, String bucket, String objectName, long size, long chunks, int chunkSize) { - return validateObjectInfo(oi, bucket, objectName, "object-desc", true, size, chunks, chunkSize); - } - - private ObjectInfo validateObjectInfo(ObjectInfo oi, String bucket, String objectName, String objectDesc, boolean headers, long size, long chunks, int chunkSize) { + private ObjectInfo validateObjectInfo(ObjectInfo oi, String bucket, String objectName, String objectDesc, long size, long chunks, int chunkSize, String[] keys) { assertEquals(bucket, oi.getBucket()); assertEquals(objectName, oi.getObjectName()); if (objectDesc == null) { @@ -211,18 +204,18 @@ private ObjectInfo validateObjectInfo(ObjectInfo oi, String bucket, String objec assertNotNull(oi.getObjectMeta().getObjectMetaOptions()); assertEquals(chunkSize, oi.getObjectMeta().getObjectMetaOptions().getChunkSize()); } - if (headers) { + if (keys != null) { assertNotNull(oi.getHeaders()); assertEquals(2, oi.getHeaders().size()); - List list = oi.getHeaders().get(key(1)); + List list = oi.getHeaders().get(keys[0]); assertNotNull(list); assertEquals(1, list.size()); - assertEquals(data(1), oi.getHeaders().getFirst(key(1))); - list = oi.getHeaders().get(key(2)); + assertEquals(data(0), oi.getHeaders().getFirst(keys[0])); + list = oi.getHeaders().get(keys[1]); assertNotNull(list); assertEquals(2, list.size()); - assertTrue(list.contains(data(21))); - assertTrue(list.contains(data(22))); + assertTrue(list.contains(data(11))); + assertTrue(list.contains(data(12))); } return oi; } @@ -253,23 +246,16 @@ private static Object[] getInput(int size) { @Test public void testManageGetBucketNamesStatuses() throws Exception { - jsServer.run(nc -> { - ObjectStoreManagement osm = nc.objectStoreManagement(); - + runInSharedCustom((nc, ctx) -> { // create bucket 1 - String bucket1 = bucket(); - osm.create(ObjectStoreConfiguration.builder() - .name(bucket1) // constructor variety - .storageType(StorageType.Memory) - .build()); + String bucket1 = random(); + ctx.osCreate(bucket1); // create bucket 2 - String bucket2 = bucket(); - osm.create(ObjectStoreConfiguration.builder(bucket2) - .storageType(StorageType.Memory) - .build()); + String bucket2 = random(); + ctx.osCreate(bucket2); - List infos = osm.getStatuses(); + List infos = ctx.osm.getStatuses(); assertEquals(2, infos.size()); List buckets = new ArrayList<>(); for (ObjectStoreStatus status : infos) { @@ -279,7 +265,7 @@ public void testManageGetBucketNamesStatuses() throws Exception { assertTrue(buckets.contains(bucket1)); assertTrue(buckets.contains(bucket2)); - buckets = osm.getBucketNames(); + buckets = ctx.osm.getBucketNames(); assertTrue(buckets.contains(bucket1)); assertTrue(buckets.contains(bucket2)); }); @@ -315,16 +301,13 @@ private void assertOso(ObjectStoreOptions oso) { @Test public void testObjectLinks() throws Exception { - jsServer.run(nc -> { - ObjectStoreManagement osm = nc.objectStoreManagement(); - - String bucket1 = "b1"; // bucket(); - String bucket2 = "b2"; // bucket(); + runInSharedCustom((nc, ctx) -> { + String bucket1 = random(); + String bucket2 = random(); + ctx.osCreate(bucket1); + ctx.osCreate(bucket2); - osm.create(ObjectStoreConfiguration.builder(bucket1).storageType(StorageType.Memory).build()); ObjectStore os1 = nc.objectStore(bucket1); - - osm.create(ObjectStoreConfiguration.builder(bucket2).storageType(StorageType.Memory).build()); ObjectStore os2 = nc.objectStore(bucket2); String name1 = "name1"; // name(); @@ -400,7 +383,7 @@ public void testObjectLinks() throws Exception { assertClientError(OsObjectNotFound, () -> os2.get("targetWillBeDeleted", new ByteArrayOutputStream())); assertClientError(OsCantLinkToLink, () -> os2.addLink("willException", oiLink)); // can't link to link - String crossName = name(); + String crossName = random(); os2.addLink(crossName, info12); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectInfo crossInfo12 = os2.get(crossName, baos); @@ -436,15 +419,12 @@ private void validateLink(ObjectInfo oiLink, String linkName, ObjectInfo targetI @Test public void testList() throws Exception { - jsServer.run(nc -> { - ObjectStoreManagement osm = nc.objectStoreManagement(); - - String bucket = bucket(); - osm.create(ObjectStoreConfiguration.builder(bucket).storageType(StorageType.Memory).build()); - + runInSharedCustom((nc, ctx) -> { + String bucket = random(); + ctx.osCreate(bucket); ObjectStore os = nc.objectStore(bucket); - String[] names = new String[] {name(), name(), name(), name(), name()}; + String[] names = new String[] {random(), random(), random(), random(), random()}; os.put(names[0], dataBytes()); os.put(names[1], dataBytes()); os.put(names[2], dataBytes()); @@ -471,36 +451,30 @@ public void testList() throws Exception { @Test public void testSeal() throws Exception { - jsServer.run(nc -> { - String bucket = bucket(); - ObjectStoreManagement osm = nc.objectStoreManagement(); - osm.create(ObjectStoreConfiguration.builder(bucket) - .storageType(StorageType.Memory) - .build()); + runInSharedCustom((nc, ctx) -> { + String bucket = random(); + ctx.osCreate(bucket); ObjectStore os = nc.objectStore(bucket); - os.put("name", "data".getBytes()); + String objectName = random(); + os.put(objectName, "data".getBytes()); ObjectStoreStatus status = os.seal(); assertTrue(status.isSealed()); - assertThrows(JetStreamApiException.class, () -> os.put("another", "data".getBytes())); + assertThrows(JetStreamApiException.class, () -> os.put(random(), "data".getBytes())); - ObjectMeta meta = ObjectMeta.builder("change").build(); - assertThrows(JetStreamApiException.class, () -> os.updateMeta("name", meta)); + ObjectMeta meta = ObjectMeta.builder(random()).build(); + assertThrows(JetStreamApiException.class, () -> os.updateMeta(objectName, meta)); }); } @Test public void testCompression() throws Exception { - jsServer.run(TestBase::atLeast2_10, nc -> { - String bucket = bucket(); - ObjectStoreManagement osm = nc.objectStoreManagement(); - osm.create(ObjectStoreConfiguration.builder(bucket) - .storageType(StorageType.Memory) - .compression(true) - .build()); + runInSharedCustom(VersionUtils::atLeast2_10, (nc, ctx) -> { + String bucket = random(); + ctx.osCreate(ctx.osBuilder(bucket).compression(true).build()); ObjectStore os = nc.objectStore(bucket); ObjectStoreStatus oss = os.getStatus(); assertTrue(oss.isCompressed()); @@ -509,15 +483,11 @@ public void testCompression() throws Exception { @Test public void testOverwrite() throws Exception { - jsServer.run(TestBase::atLeast2_10, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = jsm.jetStream(); - - String bucket = bucket(); - String objName = variant(); + runInSharedCustom(VersionUtils::atLeast2_10, (nc, ctx) -> { + String bucket = random(); + String objName = random(); - ObjectStoreManagement osm = nc.objectStoreManagement(); - ObjectStoreStatus oss = osm.create(ObjectStoreConfiguration.builder(bucket) + ObjectStoreStatus oss = ctx.osCreate(ObjectStoreConfiguration.builder(bucket) .storageType(StorageType.Memory) .build()); String realStreamName = oss.getConfiguration().getBackingConfig().getName(); @@ -530,13 +500,13 @@ public void testOverwrite() throws Exception { try (InputStream in = Files.newInputStream(file.toPath())) { os.put(objName, in); } - StreamInfo si = jsm.getStreamInfo(realStreamName, StreamInfoOptions.allSubjects()); + StreamInfo si = ctx.jsm.getStreamInfo(realStreamName, StreamInfoOptions.allSubjects()); long byteCount1 = si.getStreamState().getByteCount(); try (InputStream in = Files.newInputStream(file.toPath())) { os.put(objName, in); } - si = jsm.getStreamInfo(realStreamName, StreamInfoOptions.allSubjects()); + si = ctx.jsm.getStreamInfo(realStreamName, StreamInfoOptions.allSubjects()); long byteCount2 = si.getStreamState().getByteCount(); assertEquals(byteCount1, byteCount2); @@ -595,21 +565,19 @@ public void testWatch() throws Exception { TestObjectStoreWatcher fullAfterWatcher = new TestObjectStoreWatcher("fullAfterWatcher", false); TestObjectStoreWatcher delAfterWatcher = new TestObjectStoreWatcher("delAfterWatcher", false, IGNORE_DELETE); - runInJsServer(nc -> { - _testWatch(nc, fullB4Watcher, new Object[]{"A", "B", null}, os -> os.watch(fullB4Watcher, fullB4Watcher.watchOptions)); - _testWatch(nc, delB4Watcher, new Object[]{"A", "B"}, os -> os.watch(delB4Watcher, delB4Watcher.watchOptions)); - _testWatch(nc, fullAfterWatcher, new Object[]{"B", null}, os -> os.watch(fullAfterWatcher, fullAfterWatcher.watchOptions)); - _testWatch(nc, delAfterWatcher, new Object[]{"B"}, os -> os.watch(delAfterWatcher, delAfterWatcher.watchOptions)); + runInSharedCustom((nc, ctx) -> { + _testWatch(ctx, fullB4Watcher, new Object[]{"A", "B", null}, os -> os.watch(fullB4Watcher, fullB4Watcher.watchOptions)); + _testWatch(ctx, delB4Watcher, new Object[]{"A", "B"}, os -> os.watch(delB4Watcher, delB4Watcher.watchOptions)); + _testWatch(ctx, fullAfterWatcher, new Object[]{"B", null}, os -> os.watch(fullAfterWatcher, fullAfterWatcher.watchOptions)); + _testWatch(ctx, delAfterWatcher, new Object[]{"B"}, os -> os.watch(delAfterWatcher, delAfterWatcher.watchOptions)); }); } - private void _testWatch(Connection nc, TestObjectStoreWatcher watcher, Object[] expecteds, TestWatchSubSupplier supplier) throws Exception { - ObjectStoreManagement osm = nc.objectStoreManagement(); + private void _testWatch(JetStreamTestingContext ctx, TestObjectStoreWatcher watcher, Object[] expecteds, TestWatchSubSupplier supplier) throws Exception { + String bucket = random() + watcher.name; + ctx.osCreate(bucket); - String bucket = watcher.name + "Bucket"; - osm.create(ObjectStoreConfiguration.builder(bucket).storageType(StorageType.Memory).build()); - - ObjectStore os = nc.objectStore(bucket); + ObjectStore os = ctx.jsm.objectStore(bucket); NatsObjectStoreWatchSubscription sub = null; @@ -634,8 +602,6 @@ private void _testWatch(Connection nc, TestObjectStoreWatcher watcher, Object[] //noinspection ConstantConditions sub.unsubscribe(); - - osm.delete(bucket); } private void validateWatcher(Object[] expecteds, TestObjectStoreWatcher watcher) { @@ -649,6 +615,7 @@ private void validateWatcher(Object[] expecteds, TestObjectStoreWatcher watcher) for (ObjectInfo oi : watcher.entries) { + assertNotNull(oi.getModified()); assertTrue(oi.getModified().isAfter(lastMod) || oi.getModified().isEqual(lastMod)); lastMod = oi.getModified(); @@ -659,6 +626,7 @@ private void validateWatcher(Object[] expecteds, TestObjectStoreWatcher watcher) assertTrue(oi.isDeleted()); } else { + assertNotNull(oi.getObjectMeta().getObjectMetaOptions()); assertEquals(CHUNK_SIZE, oi.getObjectMeta().getObjectMetaOptions().getChunkSize()); assertEquals(CHUNKS, oi.getChunks()); assertEquals(SIZE, oi.getSize()); @@ -673,7 +641,7 @@ public void testObjectStoreDomains() throws Exception { ObjectStoreManagement leafOsm = leafNc.objectStoreManagement(ObjectStoreOptions.builder().jsDomain(HUB_DOMAIN).build()); // Create main OS on HUB - String bucketName = bucket(); + String bucketName = random(); ObjectStoreStatus hubStatus = hubOsm.create(ObjectStoreConfiguration.builder() .name(bucketName) .storageType(StorageType.Memory) @@ -685,7 +653,7 @@ public void testObjectStoreDomains() throws Exception { ObjectStore hubOs = hubNc.objectStore(bucketName); ObjectStore leafOs = leafNc.objectStore(bucketName, ObjectStoreOptions.builder().jsDomain(HUB_DOMAIN).build()); - String objectName = name(); + String objectName = random(); ObjectMeta meta = ObjectMeta.builder(objectName).chunkSize(8 * 1024).build(); Object[] input = getInput(4 * 8 * 1024); diff --git a/src/test/java/io/nats/client/impl/ParseTests.java b/src/test/java/io/nats/client/impl/ParseTests.java index 5e1008f03..1335d2d82 100644 --- a/src/test/java/io/nats/client/impl/ParseTests.java +++ b/src/test/java/io/nats/client/impl/ParseTests.java @@ -15,29 +15,27 @@ import io.nats.client.Nats; import io.nats.client.NatsTestServer; -import io.nats.client.Options; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; import static io.nats.client.support.NatsConstants.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -public class ParseTests { +public class ParseTests extends TestBase { @Test public void testGoodNumbers() { - int i=1; - + int i = 1; while (i < 2_000_000_000 && i > 0) { assertEquals(i, NatsConnectionReader.parseLength(String.valueOf(i))); i *= 11; } - assertEquals(0, NatsConnectionReader.parseLength("0")); - } @Test @@ -53,131 +51,64 @@ public void testTooBig() { } @Test - public void testLongProtocolOpThrows() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - NatsConnectionReader reader = nc.getReader(); - byte[] bytes = ("thisistoolong\r\n").getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - } + public void testBadGather() throws Exception { + runInSharedOwnNc(c -> { + NatsConnection nc = (NatsConnection)c; + NatsConnectionReader reader = nc.getReader(); + _testBadGather(reader, "thisistoolong\r\n"); // too long protocol + _testBadGather(reader, "PING\rPONG"); // missing Line Feed }); } - @Test - public void testMissingLineFeed() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - NatsConnectionReader reader = nc.getReader(); - byte[] bytes = ("PING\rPONG").getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - } - }); + private static void _testBadGather(NatsConnectionReader reader, String bad) { + byte[] bytes = bad.getBytes(StandardCharsets.US_ASCII); + reader.fakeReadForTest(bytes); + assertThrows(IOException.class, () -> reader.gatherOp(bytes.length)); } @Test - public void testMissingSubject() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - NatsConnectionReader reader = nc.getReader(); - byte[] bytes = ("MSG 1 1\r\n").getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - reader.gatherMessageProtocol(bytes.length); - reader.parseProtocolMessage(); - } + public void testBadParse() throws Exception { + runInSharedOwnNc(c -> { + NatsConnection nc = (NatsConnection)c; + NatsConnectionReader reader = nc.getReader(); + _testBadParse(reader, "MSG 1 1\r\n"); // missing subject + _testBadParse(reader, "MSG subject 1\r\n"); // missing sid + _testBadParse(reader, "MSG subject 2 \r\n"); // missing length + _testBadParse(reader, "MSG subject 2 x\r\n"); // bad length }); } - @Test - public void testMissingSID() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - NatsConnectionReader reader = nc.getReader(); - byte[] bytes = ("MSG subject 1\r\n").getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - reader.gatherMessageProtocol(bytes.length); - reader.parseProtocolMessage(); - } - }); + private static void _testBadParse(NatsConnectionReader reader, String bad) throws IOException { + byte[] bytes = bad.getBytes(StandardCharsets.US_ASCII); + reader.fakeReadForTest(bytes); + reader.gatherOp(bytes.length); + reader.gatherMessageProtocol(bytes.length); + assertThrows(IOException.class, reader::parseProtocolMessage); } @Test - public void testMissingLength() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - NatsConnectionReader reader = nc.getReader(); - byte[] bytes = ("MSG subject 2 \r\n").getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - reader.gatherMessageProtocol(bytes.length); - reader.parseProtocolMessage(); - } - }); + public void testTooShortMaxControlLineToConnect() throws Exception { + try (NatsTestServer ts = new NatsTestServer()) { + assertThrows(IOException.class, () -> Nats.connect(optionsBuilder(ts).maxControlLine(16).build())); + } } @Test - public void testBadLength() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - NatsConnectionReader reader = nc.getReader(); - byte[] bytes = ("MSG subject 2 x\r\n").getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - reader.gatherMessageProtocol(bytes.length); - reader.parseProtocolMessage(); + public void testProtocolLineTooLong() throws Exception { + runInSharedOwnNc(optionsBuilder().maxControlLine(1024), c -> { + NatsConnection nc = (NatsConnection)c; + NatsConnectionReader reader = nc.getReader(); + StringBuilder longString = new StringBuilder(); + longString.append("INFO "); + for (int i=0;i<500;i++ ){ + longString.append("helloworld"); } - }); - } - @Test - public void testMessageLineTooLong() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(new Options.Builder(). - server(ts.getURI()). - maxControlLine(16). - build())) { - NatsConnectionReader reader = nc.getReader(); - byte[] bytes = ("MSG reallylongsubjectobreakthelength 1 1\r\n").getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - reader.gatherMessageProtocol(bytes.length); - reader.parseProtocolMessage(); - } - }); - } - - @Test - public void testProtocolLineTooLong() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(new Options.Builder(). - server(ts.getURI()). - maxControlLine(1024). - build())) { - NatsConnectionReader reader = nc.getReader(); - StringBuilder longString = new StringBuilder(); - - longString.append("INFO "); - for (int i=0;i<500;i++ ){ - longString.append("helloworld"); - } - - byte[] bytes = longString.toString().getBytes(StandardCharsets.US_ASCII); - reader.fakeReadForTest(bytes); - reader.gatherOp(bytes.length); - reader.gatherProtocol(bytes.length); - reader.parseProtocolMessage(); - } + byte[] bytes = longString.toString().getBytes(StandardCharsets.US_ASCII); + reader.fakeReadForTest(bytes); + reader.gatherOp(bytes.length); + reader.gatherProtocol(bytes.length); + assertThrows(IllegalArgumentException.class, reader::parseProtocolMessage); }); } @@ -202,9 +133,8 @@ public void testProtocolStrings() throws Exception { OP_ERR, OP_INFO, OP_PING, OP_MSG, OP_OK, OP_PONG, OP_PONG, OP_MSG }; - - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { + runInSharedOwnNc(c -> { + NatsConnection nc = (NatsConnection) c; NatsConnectionReader reader = nc.getReader(); for (int i=0; i gotPong = new CompletableFuture<>(); NatsServerProtocolMock.Customizer pingPongCustomizer = (ts, r,w) -> { - - System.out.println("*** Mock Server @" + ts.getPort() + " sending PING ..."); +// System.out.println("*** Mock Server @" + ts.getPort() + " sending PING ..."); w.write("PING\r\n"); w.flush(); - String pong = ""; - - System.out.println("*** Mock Server @" + ts.getPort() + " waiting for PONG ..."); + String pong; +// System.out.println("*** Mock Server @" + ts.getPort() + " waiting for PONG ..."); try { pong = r.readLine(); } catch(Exception e) { @@ -47,229 +49,99 @@ public void testHandlingPing() throws Exception,ExecutionException { } if (pong.startsWith("PONG")) { - System.out.println("*** Mock Server @" + ts.getPort() + " got PONG ..."); +// System.out.println("*** Mock Server @" + ts.getPort() + " got PONG ..."); gotPong.complete(Boolean.TRUE); } else { - System.out.println("*** Mock Server @" + ts.getPort() + " got something else... " + pong); +// System.out.println("*** Mock Server @" + ts.getPort() + " got something else... " + pong); gotPong.complete(Boolean.FALSE); } }; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(pingPongCustomizer)) { - Connection nc = Nats.connect(ts.getURI()); - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(pingPongCustomizer)) { + try (Connection nc = standardConnect(mockTs)) { assertTrue(gotPong.get(), "Got pong."); - } finally { - nc.close(); - assertSame(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); } } } @Test public void testPingTimer() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()) - .pingInterval(Duration.ofMillis(5)) - .maxPingsOut(10000) // just don't want this to be what fails the test - .build(); - NatsConnection nc = (NatsConnection) Nats.connect(options); - StatisticsCollector stats = nc.getStatisticsCollector(); - - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - try { Thread.sleep(200); } catch (Exception ignore) {} // 1200 / 100 ... should get 10+ pings - assertTrue(stats.getPings() > 10, "got pings"); - } finally { - nc.close(); - assertSame(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); - } - } - } - - @Test - public void testPingFailsWhenClosed() throws Exception { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { - Options options = new Options.Builder(). - server(ts.getURI()). - pingInterval(Duration.ofMillis(10)). - maxPingsOut(5). - maxReconnects(0). - build(); - NatsConnection nc = (NatsConnection) Nats.connect(options); - - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - } finally { - nc.close(); - } - - Future pong = nc.sendPing(); - - assertFalse(pong.get(10,TimeUnit.MILLISECONDS)); - } + Options.Builder builder = optionsBuilder() + .pingInterval(Duration.ofMillis(5)) + .maxPingsOut(10000); // just don't want this to be what fails the test + runInSharedOwnNc(builder, nc -> { + Statistics stats = nc.getStatistics(); + sleep(200); // 1200 / 100 ... should get 10+ pings + assertTrue(stats.getPings() > 1, "got pings"); + }); } @Test public void testMaxPingsOut() throws Exception { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { - Options options = new Options.Builder(). - server(ts.getURI()). - pingInterval(Duration.ofSeconds(10)). // Avoid auto pings - maxPingsOut(2). - maxReconnects(0). - build(); - NatsConnection nc = (NatsConnection) Nats.connect(options); - - //noinspection TryFinallyCanBeTryWithResources - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { + Options options = optionsBuilder(mockTs) + .pingInterval(Duration.ofSeconds(10)) // Avoid auto pings + .maxPingsOut(2) + .maxReconnects(0) + .build(); + try (NatsConnection nc = (NatsConnection)standardConnect(options)) { nc.sendPing(); nc.sendPing(); assertNull(nc.sendPing(), "No future returned when past max"); - } finally { - nc.close(); } } } @Test - public void testFlushTimeout() { - assertThrows(TimeoutException.class, () -> { - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - build(); - NatsConnection nc = (NatsConnection) Nats.connect(options); - - //noinspection TryFinallyCanBeTryWithResources - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - // fake server so flush will time out - nc.flush(Duration.ofMillis(50)); - } finally { - nc.close(); - } + public void testFlushTimeout() throws Exception { + Listener listener = new Listener(); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(ExitAt.NO_EXIT)) { + Options options = optionsBuilder(mockTs) + .maxReconnects(0) + .connectionListener(listener) + .errorListener(listener) + .build(); + try (Connection nc = standardConnect(options)) { + // fake server so flush will time out + assertThrows(TimeoutException.class, () -> nc.flush(Duration.ofMillis(50))); } - }); + } } @Test - public void testFlushTimeoutDisconnected() { - assertThrows(TimeoutException.class, () -> { - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().connectionListener(listener).server(ts.getURI()).build(); - NatsConnection nc = (NatsConnection) Nats.connect(options); - - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - nc.flush(Duration.ofSeconds(2)); - listener.prepForStatusChange(Events.DISCONNECTED); - ts.close(); - listener.waitForStatusChange(2, TimeUnit.SECONDS); - nc.flush(Duration.ofSeconds(2)); - } finally { - nc.close(); - assertSame(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); - } + public void testFlushTimeoutDisconnected() throws Exception { + Listener listener = new Listener(); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts).connectionListener(listener).build(); + try (Connection nc = managedConnect(options)) { + nc.flush(Duration.ofSeconds(2)); + listener.queueConnectionEvent(Events.DISCONNECTED); + ts.close(); + listener.validate(); + assertThrows(TimeoutException.class, () -> nc.flush(Duration.ofSeconds(2))); } - }); + } } @Test public void testPingTimerThroughReconnect() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { try (NatsTestServer ts2 = new NatsTestServer()) { - Options options = new Options.Builder() - .connectionListener(listener) - .server(ts.getURI()) - .server(ts2.getURI()) - .pingInterval(Duration.ofMillis(5)) - .maxPingsOut(10000) // just don't want this to be what fails the test + Options options = optionsBuilder(ts.getServerUri(), ts2.getServerUri()) + .pingInterval(Duration.ofMillis(500)) + .maxPingsOut(100) // just don't want this to be what fails the test .build(); - NatsConnection nc = (NatsConnection) Nats.connect(options); - StatisticsCollector stats = nc.getStatisticsCollector(); - - try { - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - try { - Thread.sleep(200); // should get 10+ pings - } catch (Exception exp) - { - //Ignore - } + try (Connection nc = managedConnect(options)) { + Statistics stats = nc.getStatistics(); + sleep(1000); long pings = stats.getPings(); - assertTrue(pings > 10, "got pings"); - listener.prepForStatusChange(Events.RECONNECTED); + assertTrue(pings > 1, "got pings"); ts.close(); - listener.waitForStatusChange(5, TimeUnit.SECONDS); - pings = stats.getPings(); - Thread.sleep(250); // should get more pings + confirmConnected(nc); + sleep(1000); // should get more pings assertTrue(stats.getPings() > pings, "more pings"); - Thread.sleep(1000); - } finally { - nc.close(); - assertSame(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); } } } } - - - @Test - public void testMessagesDelayPings() throws Exception, ExecutionException, TimeoutException { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()). - pingInterval(Duration.ofMillis(200)).build(); - NatsConnection nc = (NatsConnection) Nats.connect(options); - StatisticsCollector stats = nc.getStatisticsCollector(); - - try { - final CompletableFuture done = new CompletableFuture<>(); - assertSame(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { - if (msg.getSubject().equals("done")) { - done.complete(Boolean.TRUE); - } - }); - - d.subscribe("subject"); - d.subscribe("done"); - nc.flush(Duration.ofMillis(1000)); // wait for them to go through - - long b4 = stats.getPings(); - for (int i=0;i<10;i++) { - Thread.sleep(50); - nc.publish("subject", new byte[16]); - } - long after = stats.getPings(); - assertEquals(after, b4, "pings hidden"); - nc.publish("done", new byte[16]); - nc.flush(Duration.ofMillis(1000)); // wait for them to go through - done.get(500, TimeUnit.MILLISECONDS); - - // no more messages, pings should start to go through - b4 = stats.getPings(); - Thread.sleep(500); - after = stats.getPings(); - assertTrue(after > b4, "pings restarted"); - } finally { - nc.close(); - } - } - } - - @Test - public void testRtt() throws Exception { - runInJsServer(nc -> { - assertTrue(nc.RTT().toMillis() < 10); - nc.close(); - assertThrows(IOException.class, nc::RTT); - }); - } } diff --git a/src/test/java/io/nats/client/impl/ReconnectTests.java b/src/test/java/io/nats/client/impl/ReconnectTests.java index b656425ff..9e5412ed7 100644 --- a/src/test/java/io/nats/client/impl/ReconnectTests.java +++ b/src/test/java/io/nats/client/impl/ReconnectTests.java @@ -13,9 +13,12 @@ package io.nats.client.impl; +import io.nats.NatsServerRunner; import io.nats.client.*; import io.nats.client.ConnectionListener.Events; import io.nats.client.api.ServerInfo; +import io.nats.client.support.Listener; +import io.nats.client.utils.ConnectionUtils; import io.nats.client.support.ssl.SslTestingHelper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; @@ -29,217 +32,212 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; -import java.util.function.Function; -import static io.nats.client.NatsTestServer.getNatsLocalhostUri; +import static io.nats.client.AuthTests.getUserCredsAuthHander; +import static io.nats.client.NatsTestServer.configFileBuilder; +import static io.nats.client.support.Listener.LONG_VALIDATE_TIMEOUT; +import static io.nats.client.support.Listener.VERY_LONG_VALIDATE_TIMEOUT; import static io.nats.client.support.NatsConstants.OUTPUT_QUEUE_IS_FULL; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.*; import static io.nats.client.utils.TestBase.*; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; @Isolated public class ReconnectTests { - void checkReconnectingStatus(Connection nc) { + void checkNotConnected(Connection nc) { Connection.Status status = nc.getStatus(); - assertTrue(Connection.Status.RECONNECTING == status || - Connection.Status.DISCONNECTED == status, "Reconnecting status"); + assertTrue(Connection.Status.RECONNECTING == status || Connection.Status.DISCONNECTED == status, "Reconnecting status"); } @Test public void testSimpleReconnect() throws Exception { //Includes test for subscriptions and dispatchers across reconnect - Function ntsSupplier = port -> { - try { - return new NatsTestServer(port, false); - } - catch (Exception e) { - throw new RuntimeException(e); - } - }; - _testReconnect(ntsSupplier, (ts, builder) -> builder.server(ts.getURI())); + _testReconnect(NatsServerRunner.builder(), (ts, optionsBuilder) -> optionsBuilder.server(ts.getServerUri())); } @Test public void testWsReconnect() throws Exception { //Includes test for subscriptions and dispatchers across reconnect - Function ntsSupplier = port -> { - try { - return new NatsTestServer("src/test/resources/ws_operator.conf", port, false); - } - catch (Exception e) { - throw new RuntimeException(e); - } - }; - _testReconnect(ntsSupplier, (ts, builder) -> - builder.server(ts.getLocalhostUri("ws")).authHandler(Nats.credentials("src/test/resources/jwt_nkey/user.creds"))); + _testReconnect(configFileBuilder("ws_operator.conf"), + (ts, optionsBuilder) -> optionsBuilder.server(ts.getLocalhostUri(WS)).authHandler(getUserCredsAuthHander())); } - private void _testReconnect(Function ntsSupplier, BiConsumer optSetter) throws Exception { + private void _testReconnect(NatsServerRunner.Builder nsrb, BiConsumer optSetter) throws Exception { int port = NatsTestServer.nextPort(); - ListenerForTesting listener = new ListenerForTesting(); + nsrb.port(port); // set the port into the builder + Listener listener = new Listener(); NatsConnection nc; Subscription sub; long start; long end; - try (NatsTestServer ts = ntsSupplier.apply(port)) { - Options.Builder builder = new Options.Builder() + String subsubject = random(); + String dispatchSubject = random(); + try (NatsTestServer ts = new NatsTestServer(nsrb)) { + Options.Builder builder = optionsBuilder() // server intentionally not set .maxReconnects(-1) .reconnectWait(Duration.ofMillis(1000)) .connectionListener(listener); optSetter.accept(ts, builder); Options options = builder.build(); - nc = (NatsConnection)standardConnection(options); + nc = (NatsConnection) managedConnect(options); - sub = nc.subscribe("subsubject"); + sub = nc.subscribe(subsubject); final NatsConnection nnc = nc; // final for the lambda - Dispatcher d = nc.createDispatcher((msg) -> nnc.publish(msg.getReplyTo(), msg.getData()) ); - d.subscribe("dispatchSubject"); + Dispatcher d = nc.createDispatcher(msg -> nnc.publish(msg.getReplyTo(), msg.getData()) ); + d.subscribe(dispatchSubject); flushConnection(nc); - Future inc = nc.request("dispatchSubject", "test".getBytes(StandardCharsets.UTF_8)); + Future inc = nc.request(dispatchSubject, "test".getBytes(StandardCharsets.UTF_8)); Message msg = inc.get(); assertNotNull(msg); - nc.publish("subsubject", null); + nc.publish(subsubject, null); msg = sub.nextMessage(Duration.ofMillis(100)); assertNotNull(msg); - listener.prepForStatusChange(Events.DISCONNECTED); + listener.queueConnectionEvent(Events.DISCONNECTED); start = System.nanoTime(); } - flushAndWaitLong(nc, listener); - checkReconnectingStatus(nc); + flushConnection(nc); + listener.validate(); - listener.prepForStatusChange(Events.RESUBSCRIBED); + listener.queueConnectionEvent(Events.RESUBSCRIBED); - try (NatsTestServer ignored = ntsSupplier.apply(port)) { - listenerConnectionWait(nc, listener, LONG_CONNECTION_WAIT_MS); + try (NatsTestServer ignored = new NatsTestServer(nsrb)) { + confirmConnected(nc); // wait for reconnect + listener.validate(); end = System.nanoTime(); assertTrue(1_000_000 * (end-start) > 1000, "reconnect wait"); // Make sure dispatcher and subscription are still there - Future inc = nc.request("dispatchSubject", "test".getBytes(StandardCharsets.UTF_8)); + Future inc = nc.request(dispatchSubject, "test".getBytes(StandardCharsets.UTF_8)); Message msg = inc.get(500, TimeUnit.MILLISECONDS); assertNotNull(msg); // make sure the subscription survived - nc.publish("subsubject", null); + nc.publish(subsubject, null); msg = sub.nextMessage(Duration.ofMillis(100)); assertNotNull(msg); } assertEquals(1, nc.getStatisticsCollector().getReconnects(), "reconnect count"); assertTrue(nc.getStatisticsCollector().getExceptions() > 0, "exception count"); - standardCloseConnection(nc); + closeAndConfirm(nc); } @Test public void testSubscribeDuringReconnect() throws Exception { NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port; Subscription sub; try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(-1). - reconnectWait(Duration.ofMillis(20)). - connectionListener(listener). - build(); - port = ts.getPort(); - nc = (NatsConnection) standardConnection(options); - listener.prepForStatusChange(Events.DISCONNECTED); + Options options = optionsBuilder(ts) + .maxReconnects(-1) + .reconnectWait(Duration.ofMillis(20)) + .connectionListener(listener) + .build(); + port = ts.getPort(); + nc = (NatsConnection) managedConnect(options); + listener.queueConnectionEvent(Events.DISCONNECTED); } - flushAndWaitLong(nc, listener); - checkReconnectingStatus(nc); + flushConnection(nc); + listener.validate(); - sub = nc.subscribe("subsubject"); + String subsubject = random(); + String dispatchSubject = random(); + sub = nc.subscribe(subsubject); final NatsConnection nnc = nc; - Dispatcher d = nc.createDispatcher((msg) -> nnc.publish(msg.getReplyTo(), msg.getData())); - d.subscribe("dispatchSubject"); + Dispatcher d = nc.createDispatcher(msg -> nnc.publish(msg.getReplyTo(), msg.getData())); + d.subscribe(dispatchSubject); - listener.prepForStatusChange(Events.RECONNECTED); + listener.queueConnectionEvent(Events.RECONNECTED); - try (NatsTestServer ignored = new NatsTestServer(port, false)) { - listenerConnectionWait(nc, listener); + try (NatsTestServer ignored = new NatsTestServer(port)) { + confirmConnected(nc); // wait for reconnect + listener.validate(); // Make sure the dispatcher and subscription are still there - Future inc = nc.request("dispatchSubject", "test".getBytes(StandardCharsets.UTF_8)); + Future inc = nc.request(dispatchSubject, "test".getBytes(StandardCharsets.UTF_8)); Message msg = inc.get(); assertNotNull(msg); // make sure the subscription survived - nc.publish("subsubject", null); + nc.publish(subsubject, null); msg = sub.nextMessage(Duration.ofMillis(100)); assertNotNull(msg); } assertEquals(1, nc.getStatisticsCollector().getReconnects(), "reconnect count"); assertTrue(nc.getStatisticsCollector().getExceptions() > 0, "exception count"); - standardCloseConnection(nc); + closeAndConfirm(nc); } @Test public void testReconnectBuffer() throws Exception { NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port = NatsTestServer.nextPort(); Subscription sub; long start; long end; String[] customArgs = {"--user","stephen","--pass","password"}; + String subsubject = random(); + String dispatchSubject = random(); - try (NatsTestServer ts = new NatsTestServer(customArgs, port, false)) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(-1). - userInfo("stephen".toCharArray(), "password".toCharArray()). - reconnectWait(Duration.ofMillis(1000)). - connectionListener(listener). - build(); - nc = (NatsConnection) standardConnection(options); + try (NatsTestServer ts = new NatsTestServer(customArgs, port)) { + Options options = optionsBuilder(ts) + .maxReconnects(-1) + .userInfo("stephen".toCharArray(), "password".toCharArray()) + .reconnectWait(Duration.ofMillis(1000)) + .connectionListener(listener) + .build(); + nc = (NatsConnection) managedConnect(options); - sub = nc.subscribe("subsubject"); + sub = nc.subscribe(subsubject); final NatsConnection nnc = nc; - Dispatcher d = nc.createDispatcher((msg) -> nnc.publish(msg.getReplyTo(), msg.getData())); - d.subscribe("dispatchSubject"); + Dispatcher d = nc.createDispatcher(msg -> nnc.publish(msg.getReplyTo(), msg.getData())); + d.subscribe(dispatchSubject); nc.flush(Duration.ofMillis(1000)); - Future inc = nc.request("dispatchSubject", "test".getBytes(StandardCharsets.UTF_8)); + Future inc = nc.request(dispatchSubject, "test".getBytes(StandardCharsets.UTF_8)); Message msg = inc.get(); assertNotNull(msg); - nc.publish("subsubject", null); + nc.publish(subsubject, null); msg = sub.nextMessage(Duration.ofMillis(100)); assertNotNull(msg); - listener.prepForStatusChange(Events.DISCONNECTED); + listener.queueConnectionEvent(Events.DISCONNECTED); start = System.nanoTime(); } - flushAndWaitLong(nc, listener); - checkReconnectingStatus(nc); + flushConnection(nc); + listener.validate(); // Send a message to the dispatcher and one to the subscriber // These should be sent on reconnect - Future inc = nc.request("dispatchSubject", "test".getBytes(StandardCharsets.UTF_8)); - nc.publish("subsubject", null); - nc.publish("subsubject", null); + Future inc = nc.request(dispatchSubject, "test".getBytes(StandardCharsets.UTF_8)); + nc.publish(subsubject, null); + nc.publish(subsubject, null); - listener.prepForStatusChange(Events.RESUBSCRIBED); + listener.queueConnectionEvent(Events.RESUBSCRIBED); - try (NatsTestServer ignored = new NatsTestServer(customArgs, port, false)) { - listenerConnectionWait(nc, listener); + try (NatsTestServer ignored = new NatsTestServer(customArgs, port)) { + confirmConnected(nc); // wait for reconnect + listener.validate(); end = System.nanoTime(); @@ -259,172 +257,161 @@ public void testReconnectBuffer() throws Exception { assertEquals(1, nc.getStatisticsCollector().getReconnects(), "reconnect count"); assertTrue(nc.getStatisticsCollector().getExceptions() > 0, "exception count"); - standardCloseConnection(nc); + closeAndConfirm(nc); } @Test public void testMaxReconnects() throws Exception { Connection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port = NatsTestServer.nextPort(); - try (NatsTestServer ts = new NatsTestServer(port, false)) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(1). - connectionListener(listener). - reconnectWait(Duration.ofMillis(10)). - build(); - nc = standardConnection(options); - listener.prepForStatusChange(Events.CLOSED); + try (NatsTestServer ts = new NatsTestServer(port)) { + Options options = optionsBuilder(ts) + .maxReconnects(1) + .connectionListener(listener) + .reconnectWait(Duration.ofMillis(10)) + .build(); + nc = managedConnect(options); + listener.queueConnectionEvent(Events.CLOSED); } - - flushAndWaitLong(nc, listener); - assertSame(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); - standardCloseConnection(nc); + flushConnection(nc); + listener.validate(); } @Test - public void testReconnectToSecondServer() throws Exception { + public void testReconnectToSecondServerInBootstrap() throws Exception { NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); - - try (NatsTestServer ts = new NatsTestServer()) { + Listener listener = new Listener(); + try (NatsTestServer ts1 = new NatsTestServer()) { try (NatsTestServer ts2 = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts2.getURI()). - server(ts.getURI()). - noRandomize(). - connectionListener(listener). - maxReconnects(-1). - build(); - nc = (NatsConnection) standardConnection(options); - assertEquals(ts2.getURI(), nc.getConnectedUrl()); - listener.prepForStatusChange(Events.RECONNECTED); + // need both in bootstrap b/c these are not clustered + Options options = optionsBuilder(ts2.getServerUri(), ts1.getServerUri()) + .noRandomize() + .connectionListener(listener) + .maxReconnects(-1) + .build(); + nc = (NatsConnection) managedConnect(options); + assertEquals(ts2.getServerUri(), nc.getConnectedUrl()); + listener.queueConnectionEvent(Events.RECONNECTED); } - flushAndWaitLong(nc, listener); - + flushConnection(nc); + listener.validate(); assertConnected(nc); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - standardCloseConnection(nc); + assertEquals(ts1.getServerUri(), nc.getConnectedUrl()); + closeAndConfirm(nc); } } @Test public void testNoRandomizeReconnectToSecondServer() throws Exception { NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); - + Listener listener = new Listener(); try (NatsTestServer ts = new NatsTestServer()) { try (NatsTestServer ts2 = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts2.getURI()). - server(ts.getURI()). - connectionListener(listener). - maxReconnects(-1). - noRandomize(). - build(); - nc = (NatsConnection) standardConnection(options); - assertEquals(nc.getConnectedUrl(), ts2.getURI()); - listener.prepForStatusChange(Events.RECONNECTED); + Options options = optionsBuilder(ts2.getServerUri(), ts.getServerUri()) + .noRandomize() + .connectionListener(listener) + .maxReconnects(-1) + .build(); + nc = (NatsConnection) managedConnect(options); + assertEquals(ts2.getServerUri(), nc.getConnectedUrl()); + listener.queueConnectionEvent(Events.RECONNECTED); } - flushAndWaitLong(nc, listener); - + flushConnection(nc); + listener.validate(); assertConnected(nc); - assertEquals(ts.getURI(), nc.getConnectedUrl()); - standardCloseConnection(nc); + assertEquals(ts.getServerUri(), nc.getConnectedUrl()); + closeAndConfirm(nc); } } @Test public void testReconnectToSecondServerFromInfo() throws Exception { - NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); - - try (NatsTestServer ts = new NatsTestServer()) { - String striped = ts.getURI().substring("nats://".length()); // info doesn't have protocol + Listener listener = new Listener(); + runInSharedServer(ts -> { + Connection nc; + String striped = ts.getServerUri().substring("nats://".length()); // info doesn't have protocol String customInfo = "{\"server_id\":\"myid\", \"version\":\"9.9.99\",\"connect_urls\": [\""+striped+"\"]}"; - try (NatsServerProtocolMock ts2 = new NatsServerProtocolMock(null, customInfo)) { - Options options = new Options.Builder(). - server(ts2.getURI()). - connectionListener(listener). - maxReconnects(-1). - connectionTimeout(Duration.ofSeconds(5)). - reconnectWait(Duration.ofSeconds(1)). - build(); - nc = (NatsConnection) standardConnection(options); - assertEquals(nc.getConnectedUrl(), ts2.getURI()); - listener.prepForStatusChange(Events.RECONNECTED); + try (NatsServerProtocolMock mockTs2 = new NatsServerProtocolMock(null, customInfo)) { + Options options = optionsBuilder(mockTs2) + .connectionListener(listener) + .maxReconnects(-1) + .connectionTimeout(Duration.ofSeconds(5)) + .reconnectWait(Duration.ofSeconds(1)) + .build(); + nc = standardConnect(options); + assertEquals(mockTs2.getServerUri(), nc.getConnectedUrl()); + listener.queueConnectionEvent(Events.RECONNECTED); } - flushAndWaitLong(nc, listener); - + flushConnection(nc); + listener.validate(); assertConnected(nc); - assertTrue(ts.getURI().endsWith(nc.getConnectedUrl())); - standardCloseConnection(nc); - } + assertEquals(ts.getServerUri(), nc.getConnectedUrl()); + closeAndConfirm(nc); + }); } @Test - public void testOverflowReconnectBuffer() { - assertThrows(IllegalStateException.class, () -> { - Connection nc; - ListenerForTesting listener = new ListenerForTesting(); - - try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(-1). - connectionListener(listener). - reconnectBufferSize(4*512). - reconnectWait(Duration.ofSeconds(480)). - build(); - nc = standardConnection(options); - listener.prepForStatusChange(Events.DISCONNECTED); - } + public void testOverflowReconnectBuffer() throws Exception { + Connection nc; + Listener listener = new Listener(); + listener.queueConnectionEvent(Events.DISCONNECTED); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) + .connectionListener(listener) + .reconnectBufferSize(4*512) + .reconnectWait(Duration.ofSeconds(480)) + .build(); + nc = managedConnect(options); + } - flushAndWaitLong(nc, listener); - checkReconnectingStatus(nc); + listener.validate(); - for (int i=0;i<20;i++) { - nc.publish("test", new byte[512]);// Should blow up by the 5th message + String subject = random(); + assertThrows(IllegalStateException.class, () -> { + for (int i = 0; i < 20; i++) { + nc.publish(subject, new byte[512]);// Should be full by the 5th message } }); + + closeAndConfirm(nc); } @Test public void testInfiniteReconnectBuffer() throws Exception { Connection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(5). - connectionListener(listener). - reconnectBufferSize(-1). - reconnectWait(Duration.ofSeconds(30)). - build(); - nc = standardConnection(options); - listener.prepForStatusChange(Events.DISCONNECTED); + Options options = optionsBuilder(ts) + .maxReconnects(5) + .connectionListener(listener) + .reconnectBufferSize(-1) + .reconnectWait(Duration.ofSeconds(30)) + .build(); + nc = managedConnect(options); + listener.queueConnectionEvent(Events.DISCONNECTED); } - flushAndWaitLong(nc, listener); - checkReconnectingStatus(nc); + flushConnection(nc); + listener.validate(); byte[] payload = new byte[1024]; for (int i=0;i<1_000;i++) { nc.publish("test", payload); } - standardCloseConnection(nc); + checkNotConnected(nc); + closeAndConfirm(nc); } @Test public void testReconnectDropOnLineFeed() throws Exception { NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port = NatsTestServer.nextPort(); Duration reconnectWait = Duration.ofMillis(100); // thrash int thrashCount = 5; @@ -436,7 +423,7 @@ public void testReconnectDropOnLineFeed() throws Exception { NatsServerProtocolMock.Customizer receiveMessageCustomizer = (ts, r,w) -> { String subLine; - System.out.println("*** Mock Server @" + ts.getPort() + " waiting for SUB ..."); + // System.out.println("*** Mock Server @" + ts.getPort() + " waiting for SUB ..."); try { subLine = r.readLine(); } catch(Exception e) { @@ -458,62 +445,63 @@ public void testReconnectDropOnLineFeed() throws Exception { w.flush(); }; - try (NatsServerProtocolMock ts = new NatsServerProtocolMock(receiveMessageCustomizer, port, true)) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(-1). - reconnectWait(reconnectWait). - connectionListener(listener). - build(); - port = ts.getPort(); - nc = (NatsConnection) Nats.connect(options); - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsServerProtocolMock mockTs = new NatsServerProtocolMock(receiveMessageCustomizer, port, true)) { + Options options = optionsBuilder(mockTs) + .maxReconnects(-1) + .reconnectWait(reconnectWait) + .connectionListener(listener) + .build(); + port = mockTs.getPort(); + nc = (NatsConnection) standardConnect(options); + listener.queueConnectionEvent(Events.DISCONNECTED); nc.subscribe("test"); subRef.get().get(); - listener.prepForStatusChange(Events.DISCONNECTED); sendRef.get().complete(true); - flushAndWaitLong(nc, listener); // mock server will close so we do this inside the curly + flushConnection(nc); // mock server will close so we do this inside the curly + listener.validate(); } // Thrash in and out of connect status // server starts thrashCount times, so we should succeed thrashCount x for (int i=0;i(); subRef.set(gotSub); sendMsg = new CompletableFuture<>(); sendRef.set(sendMsg); - listener.prepForStatusChange(Events.RESUBSCRIBED); + listener.queueConnectionEvent(Events.RESUBSCRIBED); try (NatsServerProtocolMock ignored = new NatsServerProtocolMock(receiveMessageCustomizer, port, true)) { - listenerConnectionWait(nc, listener); + confirmConnected(nc); // wait for reconnect + listener.validate(); subRef.get().get(); - listener.prepForStatusChange(Events.DISCONNECTED); + listener.queueConnectionEvent(Events.DISCONNECTED); sendRef.get().complete(true); - flushAndWaitLong(nc, listener); // mock server will close so we do this inside the curly + flushConnection(nc); // mock server will close so we do this inside the curly + listener.validate(); } } - assertEquals(2 * thrashCount, nc.getStatisticsCollector().getReconnects(), "reconnect count"); - standardCloseConnection(nc); + closeAndConfirm(nc); } @Test - public void testReconnectNoIPTLSConnection() throws Exception { + public void testTlsNoIpConnection() throws Exception { NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int tsPort = NatsTestServer.nextPort(); int ts2Port = NatsTestServer.nextPort(); @@ -539,78 +527,67 @@ public void testReconnectNoIPTLSConnection() throws Exception { "}" }; + SslTestingHelper.setKeystoreSystemParameters(); + // Regular tls for first connection, then no ip for second - try ( NatsTestServer ts = new NatsTestServer("src/test/resources/tls_noip.conf", tsInserts, tsPort, false); - NatsTestServer ts2 = new NatsTestServer("src/test/resources/tls_noip.conf", ts2Inserts, ts2Port, false) ) { + try (NatsTestServer ts = new NatsTestServer( "tls_noip.conf", tsInserts, tsPort); + NatsTestServer ts2 = new NatsTestServer( "tls_noip.conf", ts2Inserts, ts2Port) ) { + + // Test 1. tls Scheme + Options options = optionsBuilder(ts, "tls") + .connectionTimeout(Duration.ofSeconds(5)) + .maxReconnects(0) + .build(); + assertCanConnect(options); - SslTestingHelper.setKeystoreSystemParameters(); - Options options = new Options.Builder(). - server(ts.getURI()). - secure(). - connectionListener(listener). - maxReconnects(20). // we get multiples for some, so need enough - reconnectWait(Duration.ofMillis(100)). - connectionTimeout(Duration.ofSeconds(5)). - noRandomize(). - build(); + // Test 2. opentls Scheme + options = optionsBuilder(ts, "opentls") + .maxReconnects(0) + .build(); + assertCanConnect(options); + + // Test 3. Reconnect + options = optionsBuilder(ts) + .secure() + .connectionListener(listener) + .maxReconnects(20) + .reconnectWait(Duration.ofMillis(100)) + .connectionTimeout(Duration.ofSeconds(5)) + .noRandomize() + .build(); - listener.prepForStatusChange(Events.DISCOVERED_SERVERS); - nc = (NatsConnection) longConnectionWait(options); - assertEquals(nc.getConnectedUrl(), ts.getURI()); + listener.queueConnectionEvent(Events.DISCOVERED_SERVERS); + nc = (NatsConnection) ConnectionUtils.managedConnect(options); + assertEquals(ts.getServerUri(), nc.getConnectedUrl()); - flushAndWaitLong(nc, listener); // make sure we get the new server via info + flushConnection(nc); // make sure we get the new server via info + listener.validate(); - listener.prepForStatusChange(Events.RECONNECTED); + listener.queueConnectionEvent(Events.RECONNECTED, VERY_LONG_VALIDATE_TIMEOUT); ts.close(); - flushAndWaitLong(nc, listener); - assertConnected(nc); - URI uri = options.createURIForServer(nc.getConnectedUrl()); - assertEquals(ts2.getPort(), uri.getPort()); // full uri will have some ip address, just check port - standardCloseConnection(nc); - } - } + flushConnection(nc); - @Test - public void testURISchemeNoIPTLSConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - SslTestingHelper.setKeystoreSystemParameters(); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls_noip.conf", false)) { - Options options = new Options.Builder() - .server("tls://localhost:"+ts.getPort()) - .connectionTimeout(Duration.ofSeconds(5)) - .maxReconnects(0) - .build(); - assertCanConnect(options); - } - } + listener.validate(); - @Test - public void testURISchemeNoIPOpenTLSConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - SslTestingHelper.setKeystoreSystemParameters(); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls_noip.conf", false)) { - Options options = new Options.Builder(). - server("opentls://localhost:"+ts.getPort()). - maxReconnects(0). - build(); - assertCanConnect(options); + URI uri = options.createURIForServer(nc.getConnectedUrl()); + assertEquals(ts2.getPort(), uri.getPort()); // full uri will have some ip address, just check port + closeAndConfirm(nc); } } @Test public void testWriterFilterTiming() throws Exception { NatsConnection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port = NatsTestServer.nextPort(); - try (NatsTestServer ts = new NatsTestServer(port, false)) { - Options options = new Options.Builder(). - server(ts.getURI()). - noReconnect(). - connectionListener(listener). - build(); + try (NatsTestServer ts = new NatsTestServer(port)) { + Options options = optionsBuilder(ts) + .noReconnect() + .connectionListener(listener) + .build(); nc = (NatsConnection) Nats.connect(options); assertConnected(nc); @@ -624,57 +601,37 @@ public void testWriterFilterTiming() throws Exception { nc.getWriter().stop(); sleep(1000); // Should have thrown an exception if #203 isn't fixed - standardCloseConnection(nc); + closeAndConfirm(nc); } } - private static class TestReconnectWaitHandler implements ConnectionListener { - AtomicInteger disconnectCount = new AtomicInteger(); + @Test + public void testReconnectWait() throws Exception { + Listener listener = new Listener(); - public int getDisconnectCount() { - return disconnectCount.get(); - } + int port = NatsTestServer.nextPort(); - private void incrementDisconnectedCount() { - disconnectCount.incrementAndGet(); - } + try (NatsTestServer ts = new NatsTestServer(port)) { + Options options = optionsBuilder(ts) + .maxReconnects(-1) + .connectionTimeout(Duration.ofSeconds(1)) + .reconnectWait(Duration.ofMillis(250)) + .connectionListener(listener) + .build(); - @Override - public void connectionEvent(Connection conn, Events type) { - if (type == Events.DISCONNECTED) { - // disconnect is called after every failed reconnect attempt. - incrementDisconnectedCount(); + //noinspection unused + try (Connection nc = Nats.connect(options)) { + ts.close(); + sleep(250); + assertTrue(listener.getConnectionEventCount(Events.DISCONNECTED) < 3, "disconnectCount"); } } } - @Test - public void testReconnectWait() throws Exception { - TestReconnectWaitHandler trwh = new TestReconnectWaitHandler(); - - int port = NatsTestServer.nextPort(); - Options options = new Options.Builder(). - server("nats://localhost:"+port). - maxReconnects(-1). - connectionTimeout(Duration.ofSeconds(1)). - reconnectWait(Duration.ofMillis(250)). - connectionListener(trwh). - build(); - - NatsTestServer ts = new NatsTestServer(port, false); - Connection c = Nats.connect(options); - ts.close(); - - sleep(250); - assertTrue(trwh.getDisconnectCount() < 3, "disconnectCount"); - - c.close(); - } - @Test public void testReconnectOnConnect() throws Exception { int port = NatsTestServer.nextPort(); - Options options = Options.builder().server(getNatsLocalhostUri(port)).build(); + Options options = options(port); CountDownLatch latch = new CountDownLatch(1); AtomicReference testConn = new AtomicReference<>(); @@ -704,10 +661,10 @@ private static Thread getReconnectOnConnectTestThread(AtomicReference _testForceReconnect(nc0, listener)); + runInCluster(tstOpts, (nc0, nc1, nc2) -> _testForceReconnect(nc0, listener)); } @Test public void testForceReconnectWithAccount() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); ThreeServerTestOptions tstOpts = makeThreeServerTestOptions(listener, true); - runInJsCluster(tstOpts, (nc0, nc1, nc2) -> _testForceReconnect(nc0, listener)); + runInCluster(tstOpts, (nc0, nc1, nc2) -> _testForceReconnect(nc0, listener)); } - private static void _testForceReconnect(Connection nc0, ListenerForTesting listener) throws IOException, InterruptedException { + private static void _testForceReconnect(Connection nc0, Listener listener) throws IOException, InterruptedException { ServerInfo si = nc0.getServerInfo(); String connectedServer = si.getServerId(); + listener.queueConnectionEvent(Events.DISCONNECTED); + listener.queueConnectionEvent(Events.RECONNECTED); nc0.forceReconnect(); - standardConnectionWait(nc0); + confirmConnected(nc0); // wait for reconnect si = nc0.getServerInfo(); assertNotEquals(connectedServer, si.getServerId()); - assertTrue(listener.getConnectionEvents().contains(Events.DISCONNECTED)); - assertTrue(listener.getConnectionEvents().contains(Events.RECONNECTED)); + listener.validateAll(); } - private static ThreeServerTestOptions makeThreeServerTestOptions(ListenerForTesting listener, final boolean configureAccount) { + private static ThreeServerTestOptions makeThreeServerTestOptions(Listener listener, final boolean configureAccount) { return new ThreeServerTestOptions() { @Override public void append(int index, Options.Builder builder) { if (index == 0) { - builder.connectionListener(listener).ignoreDiscoveredServers().noRandomize(); + builder + .connectionListener(listener) + .errorListener(NOOP_EL) + .ignoreDiscoveredServers() + .noRandomize(); } } @@ -807,31 +769,29 @@ public boolean includeAllServers() { @Test public void testForceReconnectQueueBehaviorCheck() throws Exception { - runInJsCluster((nc0, nc1, nc2) -> { - if (atLeast2_9_0(nc0)) { - int pubCount = 100_000; - int subscribeTime = 5000; - int flushWait = 2500; - int port = nc0.getServerInfo().getPort(); - - ForceReconnectQueueCheckDataPort.DELAY = 75; - - String subject = subject(); - ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); - _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, false, 0); - - subject = subject(); - ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); - _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, false, flushWait); - - subject = subject(); - ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); - _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, true, 0); - - subject = subject(); - ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); - _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, true, flushWait); - } + runInCluster((nc0, nc1, nc2) -> { + int pubCount = 100_000; + int subscribeTime = 5000; + int flushWait = 2500; + int port = nc0.getServerInfo().getPort(); + + ForceReconnectQueueCheckDataPort.DELAY = 75; + + String subject = random(); + ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); + _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, false, 0); + + subject = random(); + ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); + _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, false, flushWait); + + subject = random(); + ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); + _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, true, 0); + + subject = random(); + ForceReconnectQueueCheckDataPort.setCheck("PUB " + subject); + _testForceReconnectQueueCheck(subject, pubCount, subscribeTime, port, true, flushWait); }); } @@ -848,12 +808,10 @@ private static void _testForceReconnectQueueCheck(String subject, int pubCount, froBuilder.forceClose(); } - ReconnectQueueCheckConnectionListener listener = new ReconnectQueueCheckConnectionListener(); + Listener listener = new Listener(); - Options options = Options.builder() - .server(getNatsLocalhostUri(port)) + Options options = optionsBuilder(port) .connectionListener(listener) - .errorListener(new ErrorListener() {}) .dataPortType(ForceReconnectQueueCheckDataPort.class.getCanonicalName()) .build(); @@ -862,9 +820,10 @@ private static void _testForceReconnectQueueCheck(String subject, int pubCount, nc.publish(subject, (x + "").getBytes()); } + listener.queueConnectionEvent(Events.RECONNECTED); nc.forceReconnect(froBuilder.build()); - assertTrue(listener.latch.await(flushWait + subscribeTime, TimeUnit.MILLISECONDS)); + listener.validate(); long maxTime = subscribeTime; while (!subscriber.subscriberDone.get() && maxTime > 0) { @@ -885,17 +844,6 @@ private static void _testForceReconnectQueueCheck(String subject, int pubCount, } } - static class ReconnectQueueCheckConnectionListener implements ConnectionListener { - public CountDownLatch latch = new CountDownLatch(1); - - @Override - public void connectionEvent(Connection conn, Events type) { - if (type == Events.RECONNECTED) { - latch.countDown(); - } - } - } - static class ReconnectQueueCheckSubscriber implements Runnable { final AtomicBoolean subscriberDone; final String subject; @@ -917,7 +865,7 @@ public ReconnectQueueCheckSubscriber(String subject, int pubCount, int port) { @Override public void run() { - Options options = Options.builder().server(getNatsLocalhostUri(port)).build(); + Options options = options(port); try (Connection nc = Nats.connect(options)) { Subscription sub = nc.subscribe(subject); while (!subscriberDone.get()) { @@ -946,7 +894,7 @@ public void run() { @Test public void testSocketDataPortTimeout() throws Exception { - ListenerForTesting listener = new ListenerForTesting(true, true); + Listener listener = new Listener(); Options.Builder builder = Options.builder() .noRandomize() .socketWriteTimeout(5000) // long time ensures we can get to OUTPUT_QUEUE_IS_FULL @@ -964,16 +912,16 @@ public void testSocketDataPortTimeout() throws Exception { ts1.getNatsLocalhostUri(), ts2.getNatsLocalhostUri() }; - try (Connection nc = standardConnection(builder.servers(servers).build())) { - int connectedPort = nc.getServerInfo().getPort(); - listener.prepForStatusChange(Events.RECONNECTED); + try (Connection nc = standardConnect(builder.servers(servers).build())) { + listener.queueConnectionEvent(Events.DISCONNECTED, LONG_VALIDATE_TIMEOUT); + listener.queueConnectionEvent(Events.RECONNECTED, LONG_VALIDATE_TIMEOUT); - String subject = subject(); - int pub = 0; - while (++pub < 50000) { + String subject = random(); + int pubId = 0; + while (pubId++ < 50000) { try { nc.publish(subject, null); - if (pub == 10) { + if (pubId == 10) { SocketDataPortBlockSimulator.simulateBlock(); } } @@ -984,12 +932,9 @@ public void testSocketDataPortTimeout() throws Exception { } } } - assertTrue(listener.waitForStatusChange(15, TimeUnit.SECONDS)); - + listener.validate(); assertTrue(gotOutputQueueIsFull.get()); assertTrue(listener.getSocketWriteTimeoutCount() > 0); - assertTrue(listener.getConnectionEvents().contains(Events.DISCONNECTED)); - assertNotEquals(connectedPort, nc.getServerInfo().getPort()); } } } diff --git a/src/test/java/io/nats/client/impl/RequestTests.java b/src/test/java/io/nats/client/impl/RequestTests.java index ec030e617..5d532f27f 100644 --- a/src/test/java/io/nats/client/impl/RequestTests.java +++ b/src/test/java/io/nats/client/impl/RequestTests.java @@ -23,18 +23,24 @@ import java.util.ArrayList; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import static io.nats.client.support.NatsRequestCompletableFuture.CancelAction; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class RequestTests extends TestBase { @Test public void testSimpleRequest() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> { + runInSharedOwnNc(optionsBuilder().maxReconnects(0), nc -> { + assertConnected(nc); + + AtomicReference replyTo = new AtomicReference<>(); + Dispatcher d = nc.createDispatcher(msg -> { + replyTo.set(msg.getReplyTo()); assertTrue(msg.getReplyTo().startsWith(Options.DEFAULT_INBOX_PREFIX)); if (msg.hasHeaders()) { nc.publish(msg.getReplyTo(), msg.getHeaders(), null); @@ -43,32 +49,33 @@ public void testSimpleRequest() throws Exception { nc.publish(msg.getReplyTo(), null); } }); - d.subscribe("subject"); + String subject = random(); + d.subscribe(subject); - Future incoming = nc.request("subject", null); + Future incoming = nc.request(subject, null); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(0, msg.getData().length); assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); - incoming = nc.request("subject", new Headers().put("foo", "bar"), null); + incoming = nc.request(subject, new Headers().put("foo", "bar"), null); msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(0, msg.getData().length); - assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); + assertEquals(msg.getSubject(), replyTo.get()); assertTrue(msg.hasHeaders()); assertEquals("bar", msg.getHeaders().getFirst("foo")); - } + }); } @Test public void testRequestVarieties() throws Exception { - runInServer(nc -> { - Dispatcher d = nc.createDispatcher((msg) -> { + runInSharedOwnNc(nc -> { + Dispatcher d = nc.createDispatcher(msg -> { if (msg.hasHeaders()) { nc.publish(msg.getReplyTo(), msg.getHeaders(), msg.getData()); } @@ -76,50 +83,62 @@ public void testRequestVarieties() throws Exception { nc.publish(msg.getReplyTo(), msg.getData()); } }); - d.subscribe(SUBJECT); + String subject = random(); + d.subscribe(subject); - Future f = nc.request(SUBJECT, dataBytes(1)); + Future f = nc.request(subject, dataBytes(1)); Message msg = f.get(500, TimeUnit.MILLISECONDS); assertEquals(data(1), new String(msg.getData())); - NatsMessage outMsg = NatsMessage.builder().subject(SUBJECT).data(dataBytes(2)).build(); + NatsMessage outMsg = NatsMessage.builder().subject(subject).data(dataBytes(2)).build(); f = nc.request(outMsg); msg = f.get(500, TimeUnit.MILLISECONDS); + assertNotNull(msg); + assertNotNull(msg.getData()); assertEquals(data(2), new String(msg.getData())); - msg = nc.request(SUBJECT, dataBytes(3), Duration.ofSeconds(1)); + msg = nc.request(subject, dataBytes(3), Duration.ofSeconds(1)); + assertNotNull(msg); + assertNotNull(msg.getData()); assertEquals(data(3), new String(msg.getData())); - outMsg = NatsMessage.builder().subject(SUBJECT).data(dataBytes(4)).build(); + outMsg = NatsMessage.builder().subject(subject).data(dataBytes(4)).build(); msg = nc.request(outMsg, Duration.ofSeconds(1)); + assertNotNull(msg); + assertNotNull(msg.getData()); assertEquals(data(4), new String(msg.getData())); - msg = nc.request(SUBJECT, new Headers().put("foo", "bar"), dataBytes(5), Duration.ofSeconds(1)); + msg = nc.request(subject, new Headers().put("foo", "bar"), dataBytes(5), Duration.ofSeconds(1)); + assertNotNull(msg); + assertNotNull(msg.getData()); assertEquals(data(5), new String(msg.getData())); assertTrue(msg.hasHeaders()); assertEquals("bar", msg.getHeaders().getFirst("foo")); + //noinspection DataFlowIssue assertThrows(IllegalArgumentException.class, () -> nc.request(null)); + //noinspection DataFlowIssue assertThrows(IllegalArgumentException.class, () -> nc.request(null, Duration.ofSeconds(1))); }); } @Test public void testSimpleResponseMessageHasConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsTestServer ts = new NatsTestServer(); + Connection nc = Nats.connect(optionsBuilder(ts).maxReconnects(0).build())) { + assertConnected(nc); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { assertTrue(msg.getReplyTo().startsWith(Options.DEFAULT_INBOX_PREFIX)); msg.getConnection().publish(msg.getReplyTo(), null); }); - d.subscribe("subject"); + String subject = random(); + d.subscribe(subject); - Future incoming = nc.request("subject", null); + Future incoming = nc.request(subject, null); Message msg = incoming.get(5000, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(0, msg.getData().length); assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); @@ -129,16 +148,17 @@ public void testSimpleResponseMessageHasConnection() throws Exception { @Test public void testSafeRequest() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsTestServer ts = new NatsTestServer(); + Connection nc = Nats.connect(optionsBuilder(ts).maxReconnects(0).build())) { + assertConnected(nc); - Dispatcher d = nc.createDispatcher((msg) -> nc.publish(msg.getReplyTo(), null)); - d.subscribe("subject"); + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), null)); + String subject = random(); + d.subscribe(subject); - Message msg = nc.request("subject", null, Duration.ofMillis(1000)); + Message msg = nc.request(subject, null, Duration.ofMillis(1000)); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(0, msg.getData().length); assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); @@ -147,18 +167,19 @@ public void testSafeRequest() throws Exception { @Test public void testMultipleRequest() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(new Options.Builder().server(ts.getURI()).maxReconnects(0).build())) { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsTestServer ts = new NatsTestServer(); + Connection nc = Nats.connect(optionsBuilder(ts).maxReconnects(0).build())) { + assertConnected(nc); - Dispatcher d = nc.createDispatcher((msg) -> nc.publish(msg.getReplyTo(), new byte[7])); - d.subscribe("subject"); + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), new byte[7])); + String subject = random(); + d.subscribe(subject); for (int i=0; i<10; i++) { - Future incoming = nc.request("subject", new byte[11]); + Future incoming = nc.request(subject, new byte[11]); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(7, msg.getData().length); assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); @@ -168,31 +189,40 @@ public void testMultipleRequest() throws Exception { @Test public void testMultipleReplies() throws Exception { - Options.Builder builder = new Options.Builder().turnOnAdvancedStats(); - - runInServer(builder, nc -> { + Options.Builder builder = optionsBuilder().turnOnAdvancedStats().requestCleanupInterval(Duration.ofMillis(2500)); + runInSharedOwnNc(builder, nc -> { + CountDownLatch d4CanReply = new CountDownLatch(1); AtomicInteger requests = new AtomicInteger(); - MessageHandler handler = (msg) -> { requests.incrementAndGet(); nc.publish(msg.getReplyTo(), null); }; + MessageHandler handler = msg -> { requests.incrementAndGet(); nc.publish(msg.getReplyTo(), null); }; Dispatcher d1 = nc.createDispatcher(handler); Dispatcher d2 = nc.createDispatcher(handler); Dispatcher d3 = nc.createDispatcher(handler); - Dispatcher d4 = nc.createDispatcher(msg -> { sleep(5000); handler.onMessage(msg); }); - d1.subscribe(SUBJECT); - d2.subscribe(SUBJECT); - d3.subscribe(SUBJECT); - d4.subscribe(SUBJECT); + Dispatcher d4 = nc.createDispatcher(msg -> { + requests.incrementAndGet(); + //noinspection ResultOfMethodCallIgnored + d4CanReply.await(5, TimeUnit.SECONDS); + nc.publish(msg.getReplyTo(), null); + }); + + String subject = random(); + d1.subscribe(subject); + d2.subscribe(subject); + d3.subscribe(subject); + d4.subscribe(subject); - Message reply = nc.request(SUBJECT, null, Duration.ofSeconds(2)); + Message reply = nc.request(subject, null, Duration.ofSeconds(2)); assertNotNull(reply); - sleep(2000); - assertEquals(3, requests.get()); + sleep(2000); // less than the requestCleanupInterval but enough time + assertEquals(4, requests.get()); NatsStatistics stats = (NatsStatistics)nc.getStatistics(); assertEquals(1, stats.getRepliesReceived()); assertEquals(2, stats.getDuplicateRepliesReceived()); assertEquals(0, stats.getOrphanRepliesReceived()); - sleep(3100); - assertEquals(4, requests.get()); + sleep(1000); // easily over the requestCleanupInterval + d4CanReply.countDown(); // signals d4 to reply + + sleep(1000); // enough time for d4's reply to get processed stats = (NatsStatistics)nc.getStatistics(); assertEquals(1, stats.getRepliesReceived()); assertEquals(2, stats.getDuplicateRepliesReceived()); @@ -202,9 +232,9 @@ public void testMultipleReplies() throws Exception { @Test public void testManualRequestReplyAndPublishSignatures() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsTestServer ts = new NatsTestServer(); + Connection nc = Nats.connect(ts.getServerUri())) { + assertConnected(nc); Dispatcher d = nc.createDispatcher(msg -> { // also getting coverage here of multiple publish signatures @@ -238,20 +268,21 @@ public void testManualRequestReplyAndPublishSignatures() throws Exception { @Test public void testRequestWithCustomInboxPrefix() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(new Options.Builder().inboxPrefix("myinbox").server(ts.getURI()).maxReconnects(0).build())) { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + try (NatsTestServer ts = new NatsTestServer(); + Connection nc = Nats.connect(optionsBuilder(ts).inboxPrefix("myinbox").maxReconnects(0).build())) { + assertConnected(nc); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { assertTrue(msg.getReplyTo().startsWith("myinbox")); nc.publish(msg.getReplyTo(), null); }); - d.subscribe("subject"); + String subject = random(); + d.subscribe(subject); - Future incoming = nc.request("subject", null); + Future incoming = nc.request(subject, null); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(0, msg.getData().length); assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); @@ -260,50 +291,50 @@ public void testRequestWithCustomInboxPrefix() throws Exception { @Test public void testRequireCleanupOnTimeoutNoNoResponders() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()) + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) .requestCleanupInterval(Duration.ofHours(1)) .noNoResponders().build(); Connection nc = Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + assertConnected(nc); assertThrows(TimeoutException.class, - () -> nc.request("subject", null).get(100, TimeUnit.MILLISECONDS)); + () -> nc.request(random(), null).get(100, TimeUnit.MILLISECONDS)); - assertEquals(1, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(1, nc.getStatistics().getOutstandingRequests()); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } } @Test public void testRequireCleanupOnTimeoutCleanCompletable() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { long cleanupInterval = 100; - Options options = new Options.Builder().server(ts.getURI()) + Options options = optionsBuilder(ts) .requestCleanupInterval(Duration.ofMillis(cleanupInterval)) .noNoResponders().build(); NatsConnection nc = (NatsConnection) Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - NatsMessage nm = NatsMessage.builder().subject(SUBJECT).data(dataBytes(2)).build(); + assertConnected(nc); + NatsMessage nm = NatsMessage.builder().subject(random()).data(dataBytes(2)).build(); CompletableFuture future = nc.requestWithTimeout(nm, Duration.ofMillis(cleanupInterval)); Thread.sleep(2 * cleanupInterval + Options.DEFAULT_CONNECTION_TIMEOUT.toMillis()); assertTrue(future.isCompletedExceptionally()); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } } @@ -311,16 +342,16 @@ public void testRequireCleanupOnTimeoutCleanCompletable() throws Exception { @Test public void testSimpleRequestWithTimeout() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) + try (NatsTestServer ts = new NatsTestServer()) { - Options options = new Options.Builder().server(ts.getURI()).requestCleanupInterval(Duration.ofHours(1)).build(); + Options options = optionsBuilder(ts).requestCleanupInterval(Duration.ofHours(1)).build(); Connection nc = Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + assertConnected(nc); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { assertTrue(msg.getReplyTo().startsWith(Options.DEFAULT_INBOX_PREFIX)); if (msg.hasHeaders()) { nc.publish(msg.getReplyTo(), msg.getHeaders(), null); @@ -329,23 +360,23 @@ public void testSimpleRequestWithTimeout() throws Exception { nc.publish(msg.getReplyTo(), null); } }); - d.subscribe(SUBJECT); + String subject = random(); + d.subscribe(subject); - NatsMessage outMsg = NatsMessage.builder().subject(SUBJECT).data(dataBytes(2)).build(); - CompletableFuture incoming = nc.requestWithTimeout("subject", null, Duration.ofMillis(100)); + CompletableFuture incoming = nc.requestWithTimeout(subject, null, Duration.ofMillis(100)); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(0, msg.getData().length); assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); - incoming = nc.requestWithTimeout("subject", new Headers().put("foo", "bar"), null, Duration.ofMillis(100)); + incoming = nc.requestWithTimeout(subject, new Headers().put("foo", "bar"), null, Duration.ofMillis(100)); msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(0, msg.getData().length); assertTrue(msg.getSubject().indexOf('.') < msg.getSubject().lastIndexOf('.')); @@ -354,61 +385,59 @@ public void testSimpleRequestWithTimeout() throws Exception { } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } } @Test public void testSimpleRequestWithTimeoutSlowProducer() throws Exception { - - try (NatsTestServer ts = new NatsTestServer(false)) - { + try (NatsTestServer ts = new NatsTestServer()) { long cleanupInterval = 10; - Options options = new Options.Builder().server(ts.getURI()).requestCleanupInterval(Duration.ofMillis(cleanupInterval)).build(); + Options options = optionsBuilder(ts).requestCleanupInterval(Duration.ofMillis(cleanupInterval)).build(); NatsConnection nc = (NatsConnection) Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + assertConnected(nc); //slow responder long delay = 2 * cleanupInterval + Options.DEFAULT_CONNECTION_TIMEOUT.toMillis(); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { assertTrue(msg.getReplyTo().startsWith(Options.DEFAULT_INBOX_PREFIX)); Thread.sleep(delay); nc.publish(msg.getReplyTo(), null); }); - d.subscribe(SUBJECT); + String subject = random(); + d.subscribe(subject); - NatsMessage nm = NatsMessage.builder().subject(SUBJECT).data(dataBytes(2)).build(); - CompletableFuture incoming = nc.requestWithTimeout("subject", null, Duration.ofMillis(cleanupInterval)); + CompletableFuture incoming = nc.requestWithTimeout(subject, null, Duration.ofMillis(cleanupInterval)); assertThrows(CancellationException.class, () -> incoming.get(delay, TimeUnit.MILLISECONDS)); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } } @Test public void testRequireCleanupOnCancelFromNoResponders() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()) + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) .requestCleanupInterval(Duration.ofHours(1)).build(); Connection nc = Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - assertThrows(CancellationException.class, () -> nc.request("subject", null).get(100, TimeUnit.MILLISECONDS)); + assertConnected(nc); + assertThrows(CancellationException.class, () -> nc.request(random(), null).get(100, TimeUnit.MILLISECONDS)); - assertEquals(0, ((NatsStatistics) nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } @@ -416,18 +445,18 @@ public void testRequireCleanupOnCancelFromNoResponders() throws Exception { @Test public void testRequireCleanupWithTimeoutNoResponders() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()) + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) .requestCleanupInterval(Duration.ofHours(1)).build(); Connection nc = Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - assertThrows(CancellationException.class, () -> nc.requestWithTimeout("subject", null, Duration.ofMillis(100)).get(100, TimeUnit.MILLISECONDS)); - assertEquals(0, ((NatsStatistics) nc.getStatistics()).getOutstandingRequests()); + assertConnected(nc); + assertThrows(CancellationException.class, () -> nc.requestWithTimeout(random(), null, Duration.ofMillis(100)).get(100, TimeUnit.MILLISECONDS)); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } @@ -435,105 +464,104 @@ public void testRequireCleanupWithTimeoutNoResponders() throws Exception { @Test public void testRequireCleanupWithTimeoutNoNoResponders() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()) + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts) .requestCleanupInterval(Duration.ofHours(1)) .noNoResponders().build(); Connection nc = Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); + assertConnected(nc); - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - assertThrows(TimeoutException.class, () -> nc.requestWithTimeout("subject", null, Duration.ofMillis(100)).get(100, TimeUnit.MILLISECONDS)); - assertEquals(1, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertConnected(nc); + assertThrows(TimeoutException.class, () -> nc.requestWithTimeout(random(), null, Duration.ofMillis(100)).get(100, TimeUnit.MILLISECONDS)); + assertEquals(1, nc.getStatistics().getOutstandingRequests()); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } } @Test public void testRequireCleanupOnCancel() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).requestCleanupInterval(Duration.ofHours(1)).build(); + try (NatsTestServer ts = new NatsTestServer()) { + Options options = optionsBuilder(ts).requestCleanupInterval(Duration.ofHours(1)).build(); Connection nc = Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - NatsRequestCompletableFuture incoming = (NatsRequestCompletableFuture)nc.request("subject", null); + assertConnected(nc); + NatsRequestCompletableFuture incoming = (NatsRequestCompletableFuture)nc.request(random(), null); incoming.cancel(true); - NatsStatistics stats = ((NatsStatistics)nc.getStatistics()); + NatsStatistics stats = (NatsStatistics)nc.getStatistics(); // sometimes if the machine is very fast, the request gets a reply (even if it's no responders) // so there is either an outstanding or a received assertEquals(1, stats.getOutstandingRequests() + stats.getRepliesReceived()); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } } @Test public void testCleanupTimerWorks() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { long cleanupInterval = 50; - Options options = new Options.Builder().server(ts.getURI()).requestCleanupInterval(Duration.ofMillis(cleanupInterval)).build(); + Options options = optionsBuilder(ts).requestCleanupInterval(Duration.ofMillis(cleanupInterval)).build(); Connection nc = Nats.connect(options); try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Future incoming = nc.request("subject", null); + assertConnected(nc); + + String subject = random(); + Future incoming = nc.request(subject, null); incoming.cancel(true); - incoming = nc.request("subject", null); + incoming = nc.request(subject, null); incoming.cancel(true); - incoming = nc.request("subject", null); + incoming = nc.request(subject, null); incoming.cancel(true); long sleep = 2 * cleanupInterval; long timeout = 10 * cleanupInterval; sleep(sleep); - assertTrueByTimeout(timeout, () -> ((NatsStatistics)nc.getStatistics()).getOutstandingRequests() == 0); + assertTrueByTimeout(timeout, () -> nc.getStatistics().getOutstandingRequests() == 0); // Make sure it is still running - incoming = nc.request("subject", null); + incoming = nc.request(subject, null); incoming.cancel(true); - incoming = nc.request("subject", null); + incoming = nc.request(subject, null); incoming.cancel(true); - incoming = nc.request("subject", null); + incoming = nc.request(subject, null); incoming.cancel(true); sleep(sleep); - assertTrueByTimeout(timeout, () -> ((NatsStatistics)nc.getStatistics()).getOutstandingRequests() == 0); + assertTrueByTimeout(timeout, () -> nc.getStatistics().getOutstandingRequests() == 0); } finally { nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertClosed(nc); } } } @Test public void testRequestsVsCleanup() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { long cleanupInterval = 50; int msgCount = 100; - Options options = new Options.Builder().server(ts.getURI()).requestCleanupInterval(Duration.ofMillis(cleanupInterval)).build(); - Connection nc = Nats.connect(options); - try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> nc.publish(msg.getReplyTo(), null)); - d.subscribe("subject"); + Options options = optionsBuilder(ts).requestCleanupInterval(Duration.ofMillis(cleanupInterval)).build(); + try (Connection nc = managedConnect(options)) { + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), null)); + String subject = random(); + d.subscribe(subject); long start = System.nanoTime(); long end = start; while ((end-start) <= 2 * cleanupInterval * 1_000_000) { for (int i=0;i incoming = nc.request("subject", null); + Future incoming = nc.request(subject, null); Message msg = incoming.get(500, TimeUnit.MILLISECONDS); assertNotNull(msg); assertEquals(0, msg.getData().length); @@ -542,28 +570,23 @@ public void testRequestsVsCleanup() throws Exception { } assertTrue((end-start) > 2 * cleanupInterval * 1_000_000); - assertTrue(0 >= ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); - } finally { - nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertTrue(0 >= nc.getStatistics().getOutstandingRequests()); } } } @Test public void testDelayInPickingUpFuture() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { int msgCount = 100; ArrayList> messages = new ArrayList<>(); - Connection nc = Nats.connect(ts.getURI()); - try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> nc.publish(msg.getReplyTo(), new byte[1])); - d.subscribe("subject"); + try (Connection nc = managedConnect(options(ts))) { + String subject = random(); + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), new byte[1])); + d.subscribe(subject); for (int i=0;i incoming = nc.request("subject", null); + Future incoming = nc.request(subject, null); messages.add(incoming); } nc.flush(Duration.ofMillis(1000)); @@ -574,58 +597,44 @@ public void testDelayInPickingUpFuture() throws Exception { assertEquals(1, msg.getData().length); } - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); - } finally { - nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); } } } @Test public void testOldStyleRequest() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { - Options options = new Options.Builder().server(ts.getURI()).oldRequestStyle().build(); - Connection nc = Nats.connect(options); - try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> nc.publish(msg.getReplyTo(), null)); - d.subscribe("subject"); + runInSharedOwnNc(Options.builder().oldRequestStyle(), nc -> { + String subject = random(); + AtomicReference replyTo = new AtomicReference<>(); + Dispatcher d = nc.createDispatcher(msg -> { + replyTo.set(msg.getReplyTo()); + nc.publish(msg.getReplyTo(), null); + }); + d.subscribe(subject); - Future incoming = nc.request("subject", null); - Message msg = incoming.get(500, TimeUnit.MILLISECONDS); + Future incoming = nc.request(subject, null); + Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); - assertNotNull(msg); - assertEquals(0, msg.getData().length); - assertEquals(msg.getSubject().indexOf('.'), msg.getSubject().lastIndexOf('.')); - } finally { - nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); - } - } + assertEquals(0, nc.getStatistics().getOutstandingRequests()); + assertNotNull(msg); + assertEquals(0, msg.getData().length); + assertEquals(msg.getSubject(), replyTo.get()); + }); } @Test public void testBuffersResize() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { int initialSize = 128; int messageSize = 1024; - Options options = new Options.Builder(). - server(ts.getURI()). - bufferSize(initialSize). - connectionTimeout(Duration.ofSeconds(10)). - build(); - - Connection nc = Nats.connect(options); - try { - assertEquals(Connection.Status.CONNECTED, nc.getStatus(), "Connected Status"); - - Dispatcher d = nc.createDispatcher((msg) -> nc.publish(msg.getReplyTo(), msg.getData())); - d.subscribe("subject"); + Options options = optionsBuilder(ts).bufferSize(initialSize).connectionTimeout(Duration.ofSeconds(10)).build(); + try (Connection nc = managedConnect(options)) { + Dispatcher d = nc.createDispatcher(msg -> nc.publish(msg.getReplyTo(), msg.getData())); + String subject = random(); + d.subscribe(subject); - Future incoming = nc.request("subject", new byte[messageSize]); // force the buffers to resize + Future incoming = nc.request(subject, new byte[messageSize]); // force the buffers to resize Message msg = null; try { @@ -634,47 +643,21 @@ public void testBuffersResize() throws Exception { exp.printStackTrace(); } - assertEquals(0, ((NatsStatistics)nc.getStatistics()).getOutstandingRequests()); + assertEquals(0, nc.getStatistics().getOutstandingRequests()); assertNotNull(msg); assertEquals(messageSize, msg.getData().length); - } finally { - nc.close(); - assertEquals(Connection.Status.CLOSED, nc.getStatus(), "Closed Status"); } } } @Test - public void throwsIfClosed() { - assertThrows(IllegalStateException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.close(); - nc.request("subject", null); - fail(); - } - }); - } - - @Test - public void testThrowsWithoutSubject() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.request((String)null, null); - fail(); - } - }); - } - - @Test - public void testThrowsEmptySubject() { - assertThrows(IllegalArgumentException.class, () -> { - try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { - nc.request("", null); - fail(); - } + public void testRequestErrors() throws Exception { + runInSharedOwnNc(nc -> { + //noinspection DataFlowIssue + assertThrows(IllegalArgumentException.class, () -> nc.request((String)null, null)); // null subject bad + assertThrows(IllegalArgumentException.class, () -> nc.request("", null)); // empty subject bad + nc.close(); + assertThrows(IllegalStateException.class, () -> nc.request(random(), null)); // can't request after close }); } @@ -726,7 +709,7 @@ public void testNatsRequestCompletableFuture() throws Exception { assertTrue(ftot.useTimeoutException()); ftot.cancelTimedOut(); ExecutionException ee = assertThrows(ExecutionException.class, ftot::get); - assertTrue(ee.getCause() instanceof TimeoutException); + assertInstanceOf(TimeoutException.class, ee.getCause()); } @Test @@ -742,9 +725,9 @@ public void testNatsImplAndEmptyStatsCoverage() { @Test public void testCancelledFutureMustNotErrorOnCleanResponses() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false)) { + try (NatsTestServer ts = new NatsTestServer()) { Options options = Options.builder() - .server(ts.getURI()) + .server(ts.getServerUri()) .noNoResponders() .requestCleanupInterval(Duration.ofSeconds(10)) .build(); @@ -754,9 +737,12 @@ public void testCancelledFutureMustNotErrorOnCleanResponses() throws Exception { future.cancelClosing(); // Future is already cancelled, collecting it shouldn't result in an exception being thrown. - assertDoesNotThrow(() -> { - nc.cleanResponses(false); - }); + assertDoesNotThrow(() -> nc.cleanResponses(false)); } } + + @Test + public void testRtt() throws Exception { + runInShared(nc -> assertTrue(nc.RTT().toMillis() < 50)); + } } diff --git a/src/test/java/io/nats/client/impl/ServerPoolTests.java b/src/test/java/io/nats/client/impl/ServerPoolTests.java index 8c42cdb91..09836a695 100644 --- a/src/test/java/io/nats/client/impl/ServerPoolTests.java +++ b/src/test/java/io/nats/client/impl/ServerPoolTests.java @@ -21,6 +21,7 @@ import java.net.URISyntaxException; import java.util.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.*; public class ServerPoolTests extends TestBase { @@ -43,7 +44,7 @@ public void testPoolOptions() throws URISyntaxException { NatsUri lastConnectedServer = new NatsUri(BOOT_ONE); // testing that the expected show up in the pool - Options o = new Options.Builder().servers(bootstrap).build(); + Options o = optionsBuilder(bootstrap).build(); NatsServerPool nsp = newNatsServerPool(o, null, discoveredServers); validateNslp(nsp, null, false, combined); @@ -52,7 +53,7 @@ public void testPoolOptions() throws URISyntaxException { validateNslp(nsp, lastConnectedServer, false, combined); // testing that noRandomize maintains order - o = new Options.Builder().noRandomize().servers(bootstrap).build(); + o = optionsBuilder(bootstrap).noRandomize().build(); nsp = newNatsServerPool(o, null, discoveredServers); validateNslp(nsp, null, true, combined); @@ -61,19 +62,19 @@ public void testPoolOptions() throws URISyntaxException { validateNslp(nsp, lastConnectedServer, true, combined); // testing that ignoreDiscoveredServers ignores discovered servers - o = new Options.Builder().ignoreDiscoveredServers().servers(bootstrap).build(); + o = optionsBuilder(bootstrap).ignoreDiscoveredServers().build(); nsp = newNatsServerPool(o, null, discoveredServers); validateNslp(nsp, null, false, BOOT_ONE, BOOT_TWO); // testing that duplicates don't get added String[] secureAndNotSecure = new String[]{BOOT_ONE, BOOT_ONE_SECURE}; String[] secureBootstrap = new String[]{BOOT_ONE_SECURE}; - o = new Options.Builder().servers(secureAndNotSecure).build(); + o = optionsBuilder(secureAndNotSecure).build(); nsp = newNatsServerPool(o, null, null); validateNslp(nsp, null, false, secureBootstrap); secureAndNotSecure = new String[]{BOOT_ONE_SECURE, BOOT_ONE}; - o = new Options.Builder().servers(secureAndNotSecure).build(); + o = optionsBuilder(secureAndNotSecure).build(); nsp = newNatsServerPool(o, null, null); validateNslp(nsp, null, false, secureBootstrap); } @@ -83,7 +84,7 @@ public void testMaxReconnects() throws URISyntaxException { NatsUri failed = new NatsUri(BOOT_ONE); // testing that servers that fail max times and is removed - Options o = new Options.Builder().server(BOOT_ONE).maxReconnects(3).build(); + Options o = optionsBuilder(BOOT_ONE).maxReconnects(3).build(); NatsServerPool nsp = newNatsServerPool(o, null, null); for (int x = 0; x < 4; x++) { nsp.nextServer(); @@ -97,7 +98,7 @@ public void testMaxReconnects() throws URISyntaxException { validateNslp(nsp, null, false, BOOT_ONE); // testing that servers that fail max times and is removed - o = new Options.Builder().server(BOOT_ONE).maxReconnects(0).build(); + o = optionsBuilder(BOOT_ONE).maxReconnects(0).build(); nsp = newNatsServerPool(o, null, null); nsp.nextServer(); validateNslp(nsp, null, false, BOOT_ONE); @@ -112,7 +113,7 @@ public void testMaxReconnects() throws URISyntaxException { @Test public void testPruning() throws URISyntaxException { // making sure that pruning happens. get baseline - Options o = new Options.Builder().servers(bootstrap).maxReconnects(0).build(); + Options o = optionsBuilder(bootstrap).maxReconnects(0).build(); NatsServerPool nsp = newNatsServerPool(o, null, discoveredServers); validateNslp(nsp, null, false, combined); diff --git a/src/test/java/io/nats/client/impl/SharedServer.java b/src/test/java/io/nats/client/impl/SharedServer.java new file mode 100644 index 000000000..0c57fc876 --- /dev/null +++ b/src/test/java/io/nats/client/impl/SharedServer.java @@ -0,0 +1,221 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.impl; + +import io.nats.client.Connection; +import io.nats.client.NUID; +import io.nats.client.NatsTestServer; +import io.nats.client.Options; +import io.nats.client.utils.ConnectionUtils; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +import static io.nats.client.NatsTestServer.configFileBuilder; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; +import static io.nats.client.utils.VersionUtils.initVersionServerInfo; + +/** + * This class is in the impl package instead of the support package + * so it can access the package scope class NatsConnection + */ +public class SharedServer { + + private static final int NUM_REUSABLE_CONNECTIONS = 3; + private static final Thread SHARED_SHUTDOWN_HOOK_THREAD; + private static final Map SHARED_BY_NAME; + private static final Map SHARED_BY_URL; + private static final ReentrantLock STATIC_LOCK; + + static { + STATIC_LOCK = new ReentrantLock(); + SHARED_BY_NAME = new HashMap<>(); + SHARED_BY_URL = new HashMap<>(); + SHARED_SHUTDOWN_HOOK_THREAD = new Thread("Reusables-Shutdown-Hook") { + @Override + public void run() { + for (SharedServer rs : SHARED_BY_NAME.values()) { + rs.shutdown(); + } + SHARED_BY_NAME.clear(); + SHARED_BY_URL.clear(); + } + }; + Runtime.getRuntime().addShutdownHook(SHARED_SHUTDOWN_HOOK_THREAD); + } + + private final ReentrantLock instanceLock; + private final String reusableConnectionPrefix; + private final Map connectionMap; + private final AtomicInteger currentReusableId; + private NatsTestServer natsTestServer; + + public final String serverUrl; + + public static SharedServer getInstance(String name) throws IOException { + return getInstance(name, null); + } + + public static SharedServer getInstance(String name, String confFile) throws IOException { + STATIC_LOCK.lock(); + try { + SharedServer shared = SHARED_BY_NAME.get(name); + if (shared == null) { + shared = new SharedServer(name, confFile); + SHARED_BY_NAME.put(name, shared); + SHARED_BY_URL.put(shared.serverUrl, shared); + } + return shared; + } + finally { + STATIC_LOCK.unlock(); + } + } + + public static void shutdown(String... names) { + for (String name : names) { + SharedServer shared = SHARED_BY_NAME.get(name); + if (shared != null) { + SHARED_BY_NAME.remove(name); + SHARED_BY_URL.remove(shared.serverUrl); + shared.shutdown(); + } + } + } + + private SharedServer(@NonNull String name, @Nullable String confFile) throws IOException { + instanceLock = new ReentrantLock(); + reusableConnectionPrefix = new NUID().next(); + connectionMap = new HashMap<>(); + currentReusableId = new AtomicInteger(-1); + if (confFile == null) { + natsTestServer = new NatsTestServer( + NatsTestServer.builder() + .jetstream(true) + .customName(name) + ); + } + else { + natsTestServer = new NatsTestServer(configFileBuilder(confFile) + .customName(name)); + } + serverUrl = natsTestServer.getServerUri(); + } + + public NatsTestServer getServer() { + return natsTestServer; + } + + public Connection getSharedConnection() { + int id = currentReusableId.incrementAndGet(); + if (id >= NUM_REUSABLE_CONNECTIONS) { + currentReusableId.set(0); + id = 0; + } + return getSharedConnection(reusableConnectionPrefix + "-" + id); + } + + public static Connection sharedConnectionForServer(NatsTestServer ts) { + for (Map.Entry entry : SHARED_BY_NAME.entrySet()) { + SharedServer shared = entry.getValue(); + if (shared.natsTestServer == ts) { + return shared.getSharedConnection(); + } + } + throw new RuntimeException("No shared matching server."); + } + + public static Connection sharedConnectionForSameServer(Connection nc) { + SharedServer shared = SHARED_BY_URL.get(nc.getConnectedUrl()); + if (shared == null) { + throw new RuntimeException("No shared server for that connection."); + } + return shared.getSharedConnection(); + } + + public static Connection connectionForSameServer(Connection nc, Options.Builder builder) { + SharedServer shared = SHARED_BY_URL.get(nc.getConnectedUrl()); + if (shared == null) { + throw new RuntimeException("No shared server for that connection."); + } + return shared.newConnection(builder); + } + + private void waitUntilStatus(Connection conn) { + for (long x = 0; x < 100; x++) { + sleep(100); + if (conn.getStatus() == Connection.Status.CONNECTED) { + return; + } + } + } + + private Connection getSharedConnection(String name) { + instanceLock.lock(); + try { + Connection ncs = connectionMap.get(name); + if (ncs == null) { + ncs = newConnection(optionsBuilder()); + connectionMap.put(name, ncs); + waitUntilStatus(ncs); + initVersionServerInfo(ncs); + } + else if (ncs.getStatus() != Connection.Status.CONNECTED) { + try { ncs.close(); } catch (Exception ignore) {} + return getSharedConnection(name); + } + return ncs; + } + finally { + instanceLock.unlock(); + } + } + + public Connection newConnection(Options.Builder builder) { + return ConnectionUtils.managedConnect(builder.server(serverUrl).build()); + } + + public void shutdown() { + instanceLock.lock(); + try { + for (Connection nc : connectionMap.values()) { + try { + ((NatsConnection)nc).close(false, true); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + if (natsTestServer != null) { + try { + natsTestServer.shutdown(false); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + finally { + connectionMap.clear(); + natsTestServer = null; + instanceLock.unlock(); + } + } +} diff --git a/src/test/java/io/nats/client/impl/SimplificationTests.java b/src/test/java/io/nats/client/impl/SimplificationTests.java index 38fdaa2a3..46d855886 100644 --- a/src/test/java/io/nats/client/impl/SimplificationTests.java +++ b/src/test/java/io/nats/client/impl/SimplificationTests.java @@ -16,6 +16,8 @@ import io.nats.client.*; import io.nats.client.api.*; import io.nats.client.support.*; +import io.nats.client.utils.ConnectionUtils; +import io.nats.client.utils.VersionUtils; import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Isolated; @@ -36,47 +38,53 @@ import static io.nats.client.BaseConsumeOptions.*; import static io.nats.client.support.NatsConstants.GREATER_THAN; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; -@Isolated public class SimplificationTests extends JetStreamTestBase { @Test - public void testStreamContext() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - assertThrows(JetStreamApiException.class, () -> nc.getStreamContext(stream())); - assertThrows(JetStreamApiException.class, () -> nc.getStreamContext(stream(), JetStreamOptions.DEFAULT_JS_OPTIONS)); - assertThrows(JetStreamApiException.class, () -> js.getStreamContext(stream())); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - StreamContext streamContext = nc.getStreamContext(tsc.stream); - assertEquals(tsc.stream, streamContext.getStreamName()); - _testStreamContext(jsm, js, tsc, streamContext); - - tsc = new TestingStreamContainer(jsm); - streamContext = js.getStreamContext(tsc.stream); - assertEquals(tsc.stream, streamContext.getStreamName()); - _testStreamContext(jsm, js, tsc, streamContext); + public void testStreamContextErrors() throws Exception { + runInShared(nc -> { + assertThrows(JetStreamApiException.class, () -> nc.getStreamContext(random())); + assertThrows(JetStreamApiException.class, () -> nc.getStreamContext(random(), JetStreamOptions.DEFAULT_JS_OPTIONS)); + assertThrows(JetStreamApiException.class, () -> nc.jetStream().getStreamContext(random())); }); } - private void _testStreamContext(JetStreamManagement jsm, JetStream js, TestingStreamContainer tsc, StreamContext streamContext) throws IOException, JetStreamApiException { - String durable = durable(); + @Test + public void testStreamContextFromConnection() throws Exception { + runInShared((nc, ctx) -> { + StreamContext streamContext = nc.getStreamContext(ctx.stream); + assertEquals(ctx.stream, streamContext.getStreamName()); + _testStreamContext(ctx, streamContext); + }); + } + + @Test + public void testStreamContextFromContext() throws Exception { + runInShared((nc, ctx) -> { + StreamContext streamContext = ctx.js.getStreamContext(ctx.stream); + assertEquals(ctx.stream, streamContext.getStreamName()); + _testStreamContext(ctx, streamContext); + }); + } + + private void _testStreamContext(JetStreamTestingContext ctx, StreamContext streamContext) throws IOException, JetStreamApiException { + String durable = random(); assertThrows(JetStreamApiException.class, () -> streamContext.getConsumerContext(durable)); assertThrows(JetStreamApiException.class, () -> streamContext.deleteConsumer(durable)); ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(durable).build(); ConsumerContext consumerContext = streamContext.createOrUpdateConsumer(cc); ConsumerInfo ci = consumerContext.getConsumerInfo(); - assertEquals(tsc.stream, ci.getStreamName()); + assertEquals(ctx.stream, ci.getStreamName()); assertEquals(durable, ci.getName()); ci = streamContext.getConsumerInfo(durable); assertNotNull(ci); - assertEquals(tsc.stream, ci.getStreamName()); + assertEquals(ctx.stream, ci.getStreamName()); assertEquals(durable, ci.getName()); assertEquals(1, streamContext.getConsumerNames().size()); @@ -88,12 +96,12 @@ private void _testStreamContext(JetStreamManagement jsm, JetStream js, TestingSt ci = consumerContext.getConsumerInfo(); assertNotNull(ci); - assertEquals(tsc.stream, ci.getStreamName()); + assertEquals(ctx.stream, ci.getStreamName()); assertEquals(durable, ci.getName()); ci = consumerContext.getCachedConsumerInfo(); assertNotNull(ci); - assertEquals(tsc.stream, ci.getStreamName()); + assertEquals(ctx.stream, ci.getStreamName()); assertEquals(durable, ci.getName()); streamContext.deleteConsumer(durable); @@ -102,12 +110,12 @@ private void _testStreamContext(JetStreamManagement jsm, JetStream js, TestingSt assertThrows(JetStreamApiException.class, () -> streamContext.deleteConsumer(durable)); // coverage - js.publish(tsc.subject(), "one".getBytes()); - js.publish(tsc.subject(), "two".getBytes()); - js.publish(tsc.subject(), "three".getBytes()); - js.publish(tsc.subject(), "four".getBytes()); - js.publish(tsc.subject(), "five".getBytes()); - js.publish(tsc.subject(), "six".getBytes()); + ctx.js.publish(ctx.subject(), "one".getBytes()); + ctx.js.publish(ctx.subject(), "two".getBytes()); + ctx.js.publish(ctx.subject(), "three".getBytes()); + ctx.js.publish(ctx.subject(), "four".getBytes()); + ctx.js.publish(ctx.subject(), "five".getBytes()); + ctx.js.publish(ctx.subject(), "six".getBytes()); assertTrue(streamContext.deleteMessage(3)); assertTrue(streamContext.deleteMessage(4, true)); @@ -115,13 +123,13 @@ private void _testStreamContext(JetStreamManagement jsm, JetStream js, TestingSt MessageInfo mi = streamContext.getMessage(1); assertEquals(1, mi.getSeq()); - mi = streamContext.getFirstMessage(tsc.subject()); + mi = streamContext.getFirstMessage(ctx.subject()); assertEquals(1, mi.getSeq()); - mi = streamContext.getLastMessage(tsc.subject()); + mi = streamContext.getLastMessage(ctx.subject()); assertEquals(6, mi.getSeq()); - mi = streamContext.getNextMessage(3, tsc.subject()); + mi = streamContext.getNextMessage(3, ctx.subject()); assertEquals(5, mi.getSeq()); assertNotNull(streamContext.getStreamInfo()); @@ -130,25 +138,25 @@ private void _testStreamContext(JetStreamManagement jsm, JetStream js, TestingSt streamContext.purge(PurgeOptions.builder().sequence(5).build()); assertThrows(JetStreamApiException.class, () -> streamContext.getMessage(1)); - StreamInfo si = jsm.getStreamInfo(tsc.stream); + StreamInfo si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(2, si.getStreamState().getMsgCount()); assertEquals(5, si.getStreamState().getFirstSequence()); assertEquals(6, si.getStreamState().getLastSequence()); - js.publish(tsc.subject(), "aone".getBytes()); - js.publish(tsc.subject(), "btwo".getBytes()); - js.publish(tsc.subject(), "cthree".getBytes()); - js.publish(tsc.subject(), "dfour".getBytes()); - js.publish(tsc.subject(), "efive".getBytes()); - js.publish(tsc.subject(), "fsix".getBytes()); + ctx.js.publish(ctx.subject(), "aone".getBytes()); + ctx.js.publish(ctx.subject(), "btwo".getBytes()); + ctx.js.publish(ctx.subject(), "cthree".getBytes()); + ctx.js.publish(ctx.subject(), "dfour".getBytes()); + ctx.js.publish(ctx.subject(), "efive".getBytes()); + ctx.js.publish(ctx.subject(), "fsix".getBytes()); - si = jsm.getStreamInfo(tsc.stream); + si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(8, si.getStreamState().getMsgCount()); assertEquals(12, si.getStreamState().getLastSequence()); streamContext.purge(); - si = jsm.getStreamInfo(tsc.stream); + si = ctx.jsm.getStreamInfo(ctx.stream); assertEquals(0, si.getStreamState().getMsgCount()); assertEquals(12, si.getStreamState().getLastSequence()); } @@ -185,58 +193,54 @@ private String validateConsumerNameForOrdered(BaseConsumerContext bcc, MessageCo static int FETCH_EPHEMERAL = 1; static int FETCH_DURABLE = 2; static int FETCH_ORDERED = 3; + @Test public void testFetch() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - TestingStreamContainer tsc = new TestingStreamContainer(nc); - JetStream js = nc.jetStream(); + runInShared((nc, ctx) -> { for (int x = 1; x <= 20; x++) { - js.publish(tsc.subject(), ("test-fetch-msg-" + x).getBytes()); + ctx.js.publish(ctx.subject(), ("test-fetch-msg-" + x).getBytes()); } for (int f = FETCH_EPHEMERAL; f <= FETCH_ORDERED; f++) { // 1. Different fetch sizes demonstrate expiration behavior // 1A. equal number of messages to the fetch size - _testFetch("1A", nc, tsc, 20, 0, 20, f, false); + _testFetch("1A", ctx, 20, 0, 20, f, false); // 1B. more messages than the fetch size - _testFetch("1B", nc, tsc, 10, 0, 10, f, false); + _testFetch("1B", ctx, 10, 0, 10, f, false); // 1C. fewer messages than the fetch size - _testFetch("1C", nc, tsc, 40, 0, 40, f, false); + _testFetch("1C", ctx, 40, 0, 40, f, false); // 1D. simple-consumer-40msgs was created in 1C and has no messages available - _testFetch("1D", nc, tsc, 40, 0, 40, f, false); + _testFetch("1D", ctx, 40, 0, 40, f, false); // 2. Different max bytes sizes demonstrate expiration behavior // - each test message is approximately 100 bytes // 2A. max bytes are reached before message count - _testFetch("2A", nc, tsc, 0, 750, 20, f, false); + _testFetch("2A", ctx, 0, 750, 20, f, false); // 2B. fetch size is reached before byte count - _testFetch("2B", nc, tsc, 10, 1500, 10, f, false); + _testFetch("2B", ctx, 10, 1500, 10, f, false); if (f == FETCH_DURABLE) { // this is long-running, so don't want to test every time // 2C. fewer bytes than the byte count - _testFetch("2C", nc, tsc, 0, 3000, 40, f, false); + _testFetch("2C", ctx, 0, 3000, 40, f, false); } else if (f == FETCH_ORDERED) { // just to get coverage of testing with a consumer name prefix - _testFetch("1A", nc, tsc, 20, 0, 20, f, true); - _testFetch("2A", nc, tsc, 0, 750, 20, f, true); + _testFetch("1A", ctx, 20, 0, 20, f, true); + _testFetch("2A", ctx, 0, 750, 20, f, true); } } }); } - private void _testFetch(String label, Connection nc, TestingStreamContainer tsc, int maxMessages, int maxBytes, int testAmount, int fetchType, boolean useConsumerPrefix) throws Exception { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - StreamContext ctx = js.getStreamContext(tsc.stream); + private void _testFetch(String label, JetStreamTestingContext ctx, int maxMessages, int maxBytes, int testAmount, int fetchType, boolean useConsumerPrefix) throws Exception { + StreamContext streamCtx = ctx.js.getStreamContext(ctx.stream); String consumerName = null; String consumerNamePrefix = null; @@ -244,10 +248,10 @@ private void _testFetch(String label, Connection nc, TestingStreamContainer tsc, if (fetchType == FETCH_ORDERED) { OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration(); if (useConsumerPrefix) { - consumerNamePrefix = prefix(); + consumerNamePrefix = random(); occ.consumerNamePrefix(consumerNamePrefix); } - consumerContext = ctx.createOrderedConsumer(occ); + consumerContext = streamCtx.createOrderedConsumer(occ); assertNull(consumerContext.getConsumerName()); } else { @@ -263,8 +267,8 @@ private void _testFetch(String label, Connection nc, TestingStreamContainer tsc, consumerName = consumerName + "E"; cc = builder.name(consumerName).inactiveThreshold(10_000).build(); } - jsm.addOrUpdateConsumer(tsc.stream, cc); - consumerContext = ctx.getConsumerContext(consumerName); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); + consumerContext = streamCtx.getConsumerContext(consumerName); assertEquals(consumerName, consumerContext.getConsumerName()); } @@ -324,25 +328,20 @@ else if (maxBytes == 0) { private String generateConsumerName(int maxMessages, int maxBytes) { return maxBytes == 0 - ? variant() + "-" + maxMessages + "msgs" - : variant() + "-" + maxBytes + "bytes-" + maxMessages + "msgs"; + ? random() + "-" + maxMessages + "msgs" + : random() + "-" + maxBytes + "bytes-" + maxMessages + "msgs"; } @Test public void testFetchNoWaitPlusExpires() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - - jsm.addOrUpdateConsumer(tsc.stream, ConsumerConfiguration.builder() - .name(tsc.consumerName()) + runInShared((nc, ctx) -> { + ctx.jsm.addOrUpdateConsumer(ctx.stream, ConsumerConfiguration.builder() + .name(ctx.consumerName()) .inactiveThreshold(100000) // I could have used a durable, but this is long enough for the test - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .build()); - ConsumerContext cc = nc.getConsumerContext(tsc.stream, tsc.consumerName()); + ConsumerContext cc = nc.getConsumerContext(ctx.stream, ctx.consumerName()); FetchConsumeOptions fco = FetchConsumeOptions.builder().maxMessages(10).noWait().build(); // No Wait, No Messages @@ -351,14 +350,14 @@ public void testFetchNoWaitPlusExpires() throws Exception { assertEquals(0, count); // no messages // No Wait, One Message - js.publish(tsc.subject(), "DATA-A".getBytes()); + ctx.js.publish(ctx.subject(), "DATA-A".getBytes()); fc = cc.fetch(fco); count = readMessages(fc); assertEquals(1, count); // 1 message // No Wait, Two Messages - js.publish(tsc.subject(), "DATA-B".getBytes()); - js.publish(tsc.subject(), "DATA-C".getBytes()); + ctx.js.publish(ctx.subject(), "DATA-B".getBytes()); + ctx.js.publish(ctx.subject(), "DATA-C".getBytes()); fc = cc.fetch(fco); count = readMessages(fc); assertEquals(2, count); // 2 messages @@ -372,9 +371,9 @@ public void testFetchNoWaitPlusExpires() throws Exception { // With Expires, One to Three Message fco = FetchConsumeOptions.builder().maxMessages(10).noWaitExpiresIn(1000).build(); fc = cc.fetch(fco); - js.publish(tsc.subject(), "DATA-D".getBytes()); - js.publish(tsc.subject(), "DATA-E".getBytes()); - js.publish(tsc.subject(), "DATA-F".getBytes()); + ctx.js.publish(ctx.subject(), "DATA-D".getBytes()); + ctx.js.publish(ctx.subject(), "DATA-E".getBytes()); + ctx.js.publish(ctx.subject(), "DATA-F".getBytes()); count = readMessages(fc); // With Long (Default) Expires, Leftovers @@ -399,53 +398,42 @@ private int readMessages(FetchConsumer fc) throws InterruptedException, JetStrea @Test public void testIterableConsumer() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { // Pre define a consumer - ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(tsc.consumerName()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(ctx.consumerName()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); // Consumer[Context] - ConsumerContext consumerContext = js.getConsumerContext(tsc.stream, tsc.consumerName()); - validateConsumerName(consumerContext, null, tsc.consumerName()); + ConsumerContext consumerContext = ctx.js.getConsumerContext(ctx.stream, ctx.consumerName()); + validateConsumerName(consumerContext, null, ctx.consumerName()); int stopCount = 500; // create the consumer then use it try (IterableConsumer consumer = consumerContext.iterate()) { - validateConsumerName(consumerContext, consumer, tsc.consumerName()); - _testIterableBasic(js, stopCount, consumer, tsc.subject()); + validateConsumerName(consumerContext, consumer, ctx.consumerName()); + _testIterableBasic(ctx.js, stopCount, consumer, ctx.subject()); } // coverage IterableConsumer consumer = consumerContext.iterate(ConsumeOptions.DEFAULT_CONSUME_OPTIONS); - validateConsumerName(consumerContext, consumer, tsc.consumerName()); + validateConsumerName(consumerContext, consumer, ctx.consumerName()); consumer.close(); - //noinspection resource,DataFlowIssue + //noinspection DataFlowIssue assertThrows(IllegalArgumentException.class, () -> consumerContext.iterate(null)); }); } @Test public void testOrderedConsumerDeliverPolices() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); + runInShared((nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 101, 3, 100); + ZonedDateTime startTime = getStartTimeFirstMessage(ctx); - jsPublish(js, tsc.subject(), 101, 3, 100); - ZonedDateTime startTime = getStartTimeFirstMessage(js, tsc); - - StreamContext sctx = nc.getStreamContext(tsc.stream); + StreamContext sctx = nc.getStreamContext(ctx.stream); // test a start time OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration() - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .deliverPolicy(DeliverPolicy.ByStartTime) .startTime(startTime); OrderedConsumerContext occtx = sctx.createOrderedConsumer(occ); @@ -456,7 +444,7 @@ public void testOrderedConsumerDeliverPolices() throws Exception { // test a start sequence occ = new OrderedConsumerConfiguration() - .filterSubject(tsc.subject()) + .filterSubject(ctx.subject()) .deliverPolicy(DeliverPolicy.ByStartSequence) .startSequence(2); occtx = sctx.createOrderedConsumer(occ); @@ -492,29 +480,25 @@ public void testOrderedConsumerCoverage() { @Test public void testOrderedIterableConsumerBasic() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - StreamContext sctx = nc.getStreamContext(tsc.stream); + runInShared((nc, ctx) -> { + StreamContext sctx = nc.getStreamContext(ctx.stream); int stopCount = 500; - OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubject(tsc.subject()); + OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubject(ctx.subject()); OrderedConsumerContext occtx = sctx.createOrderedConsumer(occ); assertNull(occtx.getConsumerName()); try (IterableConsumer consumer = occtx.iterate()) { validateConsumerNameForOrdered(occtx, consumer, null); - _testIterableBasic(js, stopCount, consumer, tsc.subject()); + _testIterableBasic(ctx.js, stopCount, consumer, ctx.subject()); } - String consumerNamePrefix = prefix(); - occ = new OrderedConsumerConfiguration().filterSubject(tsc.subject()).consumerNamePrefix(consumerNamePrefix); + String consumerNamePrefix = random(); + occ = new OrderedConsumerConfiguration().filterSubject(ctx.subject()).consumerNamePrefix(consumerNamePrefix); occtx = sctx.createOrderedConsumer(occ); assertNull(occtx.getConsumerName()); try (IterableConsumer consumer = occtx.iterate()) { validateConsumerNameForOrdered(occtx, consumer, consumerNamePrefix); - _testIterableBasic(js, stopCount, consumer, tsc.subject()); + _testIterableBasic(ctx.js, stopCount, consumer, ctx.subject()); } }); } @@ -560,21 +544,16 @@ private void _testIterableBasic(JetStream js, int stopCount, IterableConsumer co @Test public void testConsumeWithHandler() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - - JetStream js = nc.jetStream(); - jsPublish(js, tsc.subject(), 2500); + runInShared((nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 2500); // Pre define a consumer - ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(tsc.consumerName()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(ctx.consumerName()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); // Consumer[Context] - ConsumerContext consumerContext = js.getConsumerContext(tsc.stream, tsc.consumerName()); - validateConsumerName(consumerContext, null, tsc.consumerName()); + ConsumerContext consumerContext = ctx.js.getConsumerContext(ctx.stream, ctx.consumerName()); + validateConsumerName(consumerContext, null, ctx.consumerName()); int stopCount = 500; @@ -588,16 +567,16 @@ public void testConsumeWithHandler() throws Exception { }; try (MessageConsumer mcon = consumerContext.consume(handler)) { - validateConsumerName(consumerContext, mcon, tsc.consumerName()); + validateConsumerName(consumerContext, mcon, ctx.consumerName()); latch.await(); stopAndWaitForFinished(mcon); assertTrue(atomicCount.get() > 500); } - StreamContext sctx = nc.getStreamContext(tsc.stream); + StreamContext sctx = nc.getStreamContext(ctx.stream); OrderedConsumerContext orderedConsumerContext = - sctx.createOrderedConsumer(new OrderedConsumerConfiguration().filterSubject(tsc.subject())); + sctx.createOrderedConsumer(new OrderedConsumerConfiguration().filterSubject(ctx.subject())); assertNull(orderedConsumerContext.getConsumerName()); CountDownLatch orderedLatch = new CountDownLatch(1); @@ -616,9 +595,9 @@ public void testConsumeWithHandler() throws Exception { assertTrue(atomicCount.get() > 500); } - String prefix = prefix(); + String prefix = random(); OrderedConsumerContext orderedConsumerContextPrefixed = - sctx.createOrderedConsumer(new OrderedConsumerConfiguration().filterSubject(tsc.subject()).consumerNamePrefix(prefix)); + sctx.createOrderedConsumer(new OrderedConsumerConfiguration().filterSubject(ctx.subject()).consumerNamePrefix(prefix)); assertNull(orderedConsumerContextPrefixed.getConsumerName()); CountDownLatch orderedLatchPrefixed = new CountDownLatch(1); @@ -653,21 +632,17 @@ private static void stopAndWaitForFinished(MessageConsumer mcon) throws Interrup @Test public void testNext() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - JetStream js = nc.jetStream(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - jsPublish(js, tsc.subject(), 4); + runInShared((nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 4); - String name = name(); + String name = random(); // Pre define a consumer ConsumerConfiguration cc = ConsumerConfiguration.builder().durable(name).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); // Consumer[Context] - ConsumerContext consumerContext = js.getConsumerContext(tsc.stream, name); + ConsumerContext consumerContext = ctx.js.getConsumerContext(ctx.stream, name); validateConsumerName(consumerContext, null, name); assertThrows(IllegalArgumentException.class, () -> consumerContext.next(1)); // max wait too small @@ -677,7 +652,7 @@ public void testNext() throws Exception { assertNotNull(consumerContext.next()); assertNull(consumerContext.next(1000)); - StreamContext sctx = js.getStreamContext(tsc.stream); + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); OrderedConsumerContext occtx = sctx.createOrderedConsumer(new OrderedConsumerConfiguration()); assertNull(occtx.getConsumerName()); assertThrows(IllegalArgumentException.class, () -> occtx.next(1)); // max wait too small @@ -701,7 +676,7 @@ public void testNext() throws Exception { cname1 = validateConsumerNameForOrdered(occtx, null, null); assertNotEquals(cname1, cname2); - String prefix = prefix(); + String prefix = random(); OrderedConsumerContext occtxPrefixed = sctx.createOrderedConsumer(new OrderedConsumerConfiguration().consumerNamePrefix(prefix)); assertNull(occtxPrefixed.getConsumerName()); assertThrows(IllegalArgumentException.class, () -> occtxPrefixed.next(1)); // max wait too small @@ -729,37 +704,34 @@ public void testNext() throws Exception { @Test public void testCoverage() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - + runInShared((nc, ctx) -> { // Pre define a consumer - jsm.addOrUpdateConsumer(tsc.stream, ConsumerConfiguration.builder().durable(tsc.consumerName(1)).build()); - jsm.addOrUpdateConsumer(tsc.stream, ConsumerConfiguration.builder().durable(tsc.consumerName(2)).build()); - jsm.addOrUpdateConsumer(tsc.stream, ConsumerConfiguration.builder().durable(tsc.consumerName(3)).build()); - jsm.addOrUpdateConsumer(tsc.stream, ConsumerConfiguration.builder().durable(tsc.consumerName(4)).build()); + ctx.jsm.addOrUpdateConsumer(ctx.stream, ConsumerConfiguration.builder().durable(ctx.consumerName(1)).build()); + ctx.jsm.addOrUpdateConsumer(ctx.stream, ConsumerConfiguration.builder().durable(ctx.consumerName(2)).build()); + ctx.jsm.addOrUpdateConsumer(ctx.stream, ConsumerConfiguration.builder().durable(ctx.consumerName(3)).build()); + ctx.jsm.addOrUpdateConsumer(ctx.stream, ConsumerConfiguration.builder().durable(ctx.consumerName(4)).build()); // Stream[Context] - StreamContext sctx1 = nc.getStreamContext(tsc.stream); - nc.getStreamContext(tsc.stream, JetStreamOptions.DEFAULT_JS_OPTIONS); - js.getStreamContext(tsc.stream); + StreamContext sctx1 = nc.getStreamContext(ctx.stream); + nc.getStreamContext(ctx.stream, JetStreamOptions.DEFAULT_JS_OPTIONS); + ctx.js.getStreamContext(ctx.stream); // Consumer[Context] - ConsumerContext cctx1 = nc.getConsumerContext(tsc.stream, tsc.consumerName(1)); - ConsumerContext cctx2 = nc.getConsumerContext(tsc.stream, tsc.consumerName(2), JetStreamOptions.DEFAULT_JS_OPTIONS); - ConsumerContext cctx3 = js.getConsumerContext(tsc.stream, tsc.consumerName(3)); - ConsumerContext cctx4 = sctx1.getConsumerContext(tsc.consumerName(4)); - ConsumerContext cctx5 = sctx1.createOrUpdateConsumer(ConsumerConfiguration.builder().durable(tsc.consumerName(5)).build()); - ConsumerContext cctx6 = sctx1.createOrUpdateConsumer(ConsumerConfiguration.builder().durable(tsc.consumerName(6)).build()); - - after(cctx1.iterate(), tsc.consumerName(1), true); - after(cctx2.iterate(ConsumeOptions.DEFAULT_CONSUME_OPTIONS), tsc.consumerName(2), true); - after(cctx3.consume(m -> {}), tsc.consumerName(3), true); - after(cctx4.consume(ConsumeOptions.DEFAULT_CONSUME_OPTIONS, m -> {}), tsc.consumerName(4), true); - after(cctx5.fetchMessages(1), tsc.consumerName(5), false); - after(cctx6.fetchBytes(1000), tsc.consumerName(6), false); + ConsumerContext cctx1 = nc.getConsumerContext(ctx.stream, ctx.consumerName(1)); + ConsumerContext cctx2 = nc.getConsumerContext(ctx.stream, ctx.consumerName(2), JetStreamOptions.DEFAULT_JS_OPTIONS); + ConsumerContext cctx3 = ctx.js.getConsumerContext(ctx.stream, ctx.consumerName(3)); + ConsumerContext cctx4 = sctx1.getConsumerContext(ctx.consumerName(4)); + ConsumerContext cctx5 = sctx1.createOrUpdateConsumer(ConsumerConfiguration.builder().durable(ctx.consumerName(5)).build()); + ConsumerContext cctx6 = sctx1.createOrUpdateConsumer(ConsumerConfiguration.builder().durable(ctx.consumerName(6)).build()); + + after(cctx1.iterate(), ctx.consumerName(1), true); + after(cctx2.iterate(ConsumeOptions.DEFAULT_CONSUME_OPTIONS), ctx.consumerName(2), true); + after(cctx3.consume(m -> { + }), ctx.consumerName(3), true); + after(cctx4.consume(ConsumeOptions.DEFAULT_CONSUME_OPTIONS, m -> { + }), ctx.consumerName(4), true); + after(cctx5.fetchMessages(1), ctx.consumerName(5), false); + after(cctx6.fetchBytes(1000), ctx.consumerName(6), false); }); } @@ -839,7 +811,7 @@ private void _check_values(FetchConsumeOptions fco, int maxMessages, int maxByte private FetchConsumeOptions roundTripSerialize(FetchConsumeOptions fco) throws IOException, ClassNotFoundException { SerializableFetchConsumeOptions sfco = new SerializableFetchConsumeOptions(fco); - sfco = (SerializableFetchConsumeOptions)roundTripSerialize(sfco); + sfco = (SerializableFetchConsumeOptions) roundTripSerialize(sfco); return sfco.getFetchConsumeOptions(); } @@ -921,7 +893,7 @@ private void check_values(ConsumeOptions co, int batchSize, int batchBytes, int private ConsumeOptions roundTripSerialize(ConsumeOptions co) throws IOException, ClassNotFoundException { SerializableConsumeOptions sco = new SerializableConsumeOptions(co); - sco = (SerializableConsumeOptions)roundTripSerialize(sco); + sco = (SerializableConsumeOptions) roundTripSerialize(sco); return sco.getConsumeOptions(); } @@ -956,46 +928,41 @@ protected Boolean beforeQueueProcessorImpl(NatsMessage msg) { @Test public void testOrderedBehaviorNext() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - StreamContext sctx = js.getStreamContext(tsc.stream); - - jsPublish(js, tsc.subject(), 101, 6, 100); - ZonedDateTime startTime = getStartTimeFirstMessage(js, tsc); + jsPublish(ctx.js, ctx.subject(), 101, 6, 100); + ZonedDateTime startTime = getStartTimeFirstMessage(ctx); // New pomm factory in place before each subscription is made // test with and without a consumer name prefix - ((NatsJetStream)js)._pullOrderedMessageManagerFactory = PullOrderedNextTestDropSimulator::new; + ctx.js._pullOrderedMessageManagerFactory = PullOrderedNextTestDropSimulator::new; _testOrderedNext(sctx, 1, new OrderedConsumerConfiguration() - .filterSubject(tsc.subject())); + .filterSubject(ctx.subject())); _testOrderedNext(sctx, 1, new OrderedConsumerConfiguration() - .consumerNamePrefix(prefix()) - .filterSubject(tsc.subject())); + .consumerNamePrefix(random()) + .filterSubject(ctx.subject())); - ((NatsJetStream)js)._pullOrderedMessageManagerFactory = PullOrderedNextTestDropSimulator::new; - _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) + ctx.js._pullOrderedMessageManagerFactory = PullOrderedNextTestDropSimulator::new; + _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(ctx.subject()) .deliverPolicy(DeliverPolicy.ByStartTime).startTime(startTime)); - _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) - .consumerNamePrefix(prefix()) + _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(ctx.subject()) + .consumerNamePrefix(random()) .deliverPolicy(DeliverPolicy.ByStartTime).startTime(startTime)); - ((NatsJetStream)js)._pullOrderedMessageManagerFactory = PullOrderedNextTestDropSimulator::new; - _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) + ctx.js._pullOrderedMessageManagerFactory = PullOrderedNextTestDropSimulator::new; + _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(ctx.subject()) .deliverPolicy(DeliverPolicy.ByStartSequence).startSequence(2)); - _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) - .consumerNamePrefix(prefix()) + _testOrderedNext(sctx, 2, new OrderedConsumerConfiguration().filterSubject(ctx.subject()) + .consumerNamePrefix(random()) .deliverPolicy(DeliverPolicy.ByStartSequence).startSequence(2)); }); } - private ZonedDateTime getStartTimeFirstMessage(JetStream js, TestingStreamContainer tsc) throws IOException, JetStreamApiException, InterruptedException { + private ZonedDateTime getStartTimeFirstMessage(JetStreamTestingContext ctx) throws IOException, JetStreamApiException, InterruptedException { ZonedDateTime startTime; - JetStreamSubscription sub = js.subscribe(tsc.subject()); + JetStreamSubscription sub = ctx.js.subscribe(ctx.subject()); Message mt = sub.nextMessage(1000); startTime = mt.metaData().timestamp().plus(30, ChronoUnit.MILLIS); sub.unsubscribe(); @@ -1020,19 +987,21 @@ private void _testOrderedNext(StreamContext sctx, int expectedStreamSeq, Ordered validateConsumerNameForOrdered(occtx, null, occ.getConsumerNamePrefix()); } - public static long CS_FOR_SS_3 = 3; public static class PullOrderedTestDropSimulator extends PullOrderedMessageManager { + long ccForSs3; @SuppressWarnings("ClassEscapesDefinedScope") - public PullOrderedTestDropSimulator(NatsConnection conn, NatsJetStream js, String stream, SubscribeOptions so, ConsumerConfiguration serverCC, boolean queueMode, boolean syncMode) { + public PullOrderedTestDropSimulator(long ccForSs3, + NatsConnection conn, NatsJetStream js, String stream, SubscribeOptions so, + ConsumerConfiguration serverCC, boolean queueMode, boolean syncMode) { super(conn, js, stream, so, serverCC, syncMode); + this.ccForSs3 = ccForSs3; } @Override protected Boolean beforeQueueProcessorImpl(NatsMessage msg) { if (msg.isJetStream() && msg.metaData().streamSequence() == 3 - && msg.metaData().consumerSequence() == CS_FOR_SS_3) - { + && msg.metaData().consumerSequence() == ccForSs3) { return false; } @@ -1042,40 +1011,73 @@ protected Boolean beforeQueueProcessorImpl(NatsMessage msg) { @Test public void testOrderedBehaviorFetch() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - StreamContext sctx = js.getStreamContext(tsc.stream); + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); - jsPublish(js, tsc.subject(), 101, 6, 100); - ZonedDateTime startTime = getStartTimeFirstMessage(js, tsc); + jsPublish(ctx.js, ctx.subject(), 101, 6, 100); // New pomm factory in place before subscriptions are made - ((NatsJetStream)js)._pullOrderedMessageManagerFactory = PullOrderedTestDropSimulator::new; + ctx.js._pullOrderedMessageManagerFactory = + (conn, js, stream, so, serverCC, queueMode, syncMode) -> + new PullOrderedTestDropSimulator(3, conn, js, stream, so, serverCC, queueMode, syncMode); - // Set the Consumer Sequence For Stream Sequence 3 statically for ease - CS_FOR_SS_3 = 3; - _testOrderedFetch(sctx, 1, new OrderedConsumerConfiguration().filterSubject(tsc.subject())); _testOrderedFetch(sctx, 1, new OrderedConsumerConfiguration() - .consumerNamePrefix(prefix()) - .filterSubject(tsc.subject())); + .filterSubject(ctx.subject())); - CS_FOR_SS_3 = 2; - _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) - .deliverPolicy(DeliverPolicy.ByStartTime).startTime(startTime)); - _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) - .consumerNamePrefix(prefix()) - .deliverPolicy(DeliverPolicy.ByStartTime).startTime(startTime)); + _testOrderedFetch(sctx, 1, new OrderedConsumerConfiguration() + .consumerNamePrefix(random()) + .filterSubject(ctx.subject())); + }); + } - CS_FOR_SS_3 = 2; - _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) - .deliverPolicy(DeliverPolicy.ByStartSequence).startSequence(2)); - _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration().filterSubject(tsc.subject()) - .consumerNamePrefix(prefix()) - .deliverPolicy(DeliverPolicy.ByStartSequence).startSequence(2)); + @Test + public void testOrderedBehaviorFetchByStartTime() throws Exception { + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); + + jsPublish(ctx.js, ctx.subject(), 101, 6, 100); + ZonedDateTime startTime = getStartTimeFirstMessage(ctx); + + // New pomm factory in place before subscriptions are made + ctx.js._pullOrderedMessageManagerFactory = + (conn, js, stream, so, serverCC, queueMode, syncMode) -> + new PullOrderedTestDropSimulator(2, conn, js, stream, so, serverCC, queueMode, syncMode); + + _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration() + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartTime) + .startTime(startTime)); + + _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration() + .consumerNamePrefix(random()) + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartTime) + .startTime(startTime)); + }); + } + + @Test + public void testOrderedBehaviorFetchByStartSequence() throws Exception { + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); + + jsPublish(ctx.js, ctx.subject(), 101, 6, 100); + + // New pomm factory in place before subscriptions are made + ctx.js._pullOrderedMessageManagerFactory = + (conn, js, stream, so, serverCC, queueMode, syncMode) -> + new PullOrderedTestDropSimulator(2, conn, js, stream, so, serverCC, queueMode, syncMode); + + _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration() + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartSequence) + .startSequence(2)); + + _testOrderedFetch(sctx, 2, new OrderedConsumerConfiguration() + .consumerNamePrefix(random()) + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartSequence) + .startSequence(2)); }); } @@ -1114,59 +1116,83 @@ private void _testOrderedFetch(StreamContext sctx, int expectedStreamSeq, Ordere } } - @ParameterizedTest - @CsvSource(value = { - "DeliverPolicy.All:No Prefix", "DeliverPolicy.All:With Prefix", - "DeliverPolicy.ByStartTime:No Prefix", "DeliverPolicy.ByStartTime:With Prefix", - "DeliverPolicy.ByStartSequence:No Prefix", "DeliverPolicy.ByStartSequence:With Prefix"}, - delimiter = ':') - public void testOrderedBehaviorIterable(String deliverPolicyStr, String prefix) throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); + @Test + public void testOrderedBehaviorIterable() throws Exception { + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - StreamContext sctx = js.getStreamContext(tsc.stream); + jsPublish(ctx.js, ctx.subject(), 101, 6, 100); - jsPublish(js, tsc.subject(), 101, 6, 100); - ZonedDateTime startTime = getStartTimeFirstMessage(js, tsc); + // New pomm factory in place before subscription is made + ctx.js._pullOrderedMessageManagerFactory = + (conn, js, stream, so, serverCC, queueMode, syncMode) -> + new PullOrderedTestDropSimulator(3, conn, js, stream, so, serverCC, queueMode, syncMode); - // New pomm factory in place before each subscription is made - // Set the Consumer Sequence For Stream Sequence 3 statically for ease - OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubject(tsc.subject()); - if (prefix.equals("With Prefix")) { - occ.consumerNamePrefix(prefix()); - } - int expectedStreamSeq = -1; - switch (deliverPolicyStr) { - case "DeliverPolicy.All": - CS_FOR_SS_3 = 3; - expectedStreamSeq = 1; - break; - case "DeliverPolicy.ByStartTime": - CS_FOR_SS_3 = 2; - expectedStreamSeq = 2; - occ.deliverPolicy(DeliverPolicy.ByStartTime).startTime(startTime); - break; - case "DeliverPolicy.ByStartSequence": - CS_FOR_SS_3 = 2; - expectedStreamSeq = 2; - occ.deliverPolicy(DeliverPolicy.ByStartSequence).startSequence(2); - break; - } - ((NatsJetStream) js)._pullOrderedMessageManagerFactory = PullOrderedTestDropSimulator::new; - _testOrderedIterate(sctx, expectedStreamSeq, occ); + _testOrderedIterate(sctx, 1, new OrderedConsumerConfiguration() + .filterSubject(ctx.subject())); + + _testOrderedIterate(sctx, 1, new OrderedConsumerConfiguration() + .consumerNamePrefix(random()) + .filterSubject(ctx.subject())); + }); + } + + @Test + public void testOrderedBehaviorIterableByStartTime() throws Exception { + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); + + jsPublish(ctx.js, ctx.subject(), 101, 6, 100); + ZonedDateTime startTime = getStartTimeFirstMessage(ctx); + + // New pomm factory in place before subscription is made + ctx.js._pullOrderedMessageManagerFactory = + (conn, js, stream, so, serverCC, queueMode, syncMode) -> + new PullOrderedTestDropSimulator(2, conn, js, stream, so, serverCC, queueMode, syncMode); + + _testOrderedIterate(sctx, 2, new OrderedConsumerConfiguration() + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartTime) + .startTime(startTime)); + + _testOrderedIterate(sctx, 2, new OrderedConsumerConfiguration() + .consumerNamePrefix(random()) + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartTime) + .startTime(startTime)); + + }); + } + + @Test + public void testOrderedBehaviorIterableByStartSequence() throws Exception { + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); + + jsPublish(ctx.js, ctx.subject(), 101, 6, 100); + + // New pomm factory in place before subscription is made + ctx.js._pullOrderedMessageManagerFactory = + (conn, js, stream, so, serverCC, queueMode, syncMode) -> + new PullOrderedTestDropSimulator(2, conn, js, stream, so, serverCC, queueMode, syncMode); + + _testOrderedIterate(sctx, 2, new OrderedConsumerConfiguration() + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartSequence) + .startSequence(2)); + + _testOrderedIterate(sctx, 2, new OrderedConsumerConfiguration() + .consumerNamePrefix(random()) + .filterSubject(ctx.subject()) + .deliverPolicy(DeliverPolicy.ByStartSequence) + .startSequence(2)); }); } private void _testOrderedIterate(StreamContext sctx, int expectedStreamSeq, OrderedConsumerConfiguration occ) throws Exception { OrderedConsumerContext occtx = sctx.createOrderedConsumer(occ); assertNull(occtx.getConsumerName()); - ConsumeOptions consumeOptions = ConsumeOptions.builder() - .expiresIn(1000) - .build(); - try (IterableConsumer icon = occtx.iterate(consumeOptions)) { + try (IterableConsumer icon = occtx.iterate()) { validateConsumerNameForOrdered(occtx, icon, occ.getConsumerNamePrefix()); // Loop through the messages to make sure I get stream sequence 1 to 5 while (expectedStreamSeq <= 5) { @@ -1179,20 +1205,20 @@ private void _testOrderedIterate(StreamContext sctx, int expectedStreamSeq, Orde } @Test - public void testOrderedConsumeConstruction() throws Exception { + public void testOrderedConsumeConstruction() { OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubject(null); assertNotNull(occ.getFilterSubjects()); assertEquals(GREATER_THAN, occ.getFilterSubject()); assertEquals(GREATER_THAN, occ.getFilterSubjects().get(0)); assertFalse(occ.hasMultipleFilterSubjects()); - occ = new OrderedConsumerConfiguration().filterSubjects((String[])null); + occ = new OrderedConsumerConfiguration().filterSubjects((String[]) null); assertNotNull(occ.getFilterSubjects()); assertEquals(GREATER_THAN, occ.getFilterSubject()); assertEquals(GREATER_THAN, occ.getFilterSubjects().get(0)); assertFalse(occ.hasMultipleFilterSubjects()); - occ = new OrderedConsumerConfiguration().filterSubjects((List)null); + occ = new OrderedConsumerConfiguration().filterSubjects((List) null); assertNotNull(occ.getFilterSubjects()); assertEquals(GREATER_THAN, occ.getFilterSubject()); assertEquals(GREATER_THAN, occ.getFilterSubjects().get(0)); @@ -1213,28 +1239,30 @@ public void testOrderedConsumeConstruction() throws Exception { @Test public void testOrderedConsume() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubject(tsc.subject()); - _testOrderedConsume(js, tsc, occ); + runInShared((nc, ctx) -> { + OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration() + .filterSubject(ctx.subject()); + _testOrderedConsume(ctx, occ); + }); + } - tsc = new TestingStreamContainer(jsm); - occ = new OrderedConsumerConfiguration() - .consumerNamePrefix(prefix()) - .filterSubject(tsc.subject()); - _testOrderedConsume(js, tsc, occ); + @Test + public void testOrderedConsumeWithPrefix() throws Exception { + runInShared((nc, ctx) -> { + OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration() + .consumerNamePrefix(random()) + .filterSubject(ctx.subject()); + _testOrderedConsume(ctx, occ); }); } - private void _testOrderedConsume(JetStream js, TestingStreamContainer tsc, OrderedConsumerConfiguration occ) throws Exception { - StreamContext sctx = js.getStreamContext(tsc.stream); + private void _testOrderedConsume(JetStreamTestingContext ctx, OrderedConsumerConfiguration occ) throws Exception { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); // Get this in place before subscriptions are made - ((NatsJetStream) js)._pullOrderedMessageManagerFactory = PullOrderedTestDropSimulator::new; + ctx.js._pullOrderedMessageManagerFactory = + (conn, js, stream, so, serverCC, queueMode, syncMode) -> + new PullOrderedTestDropSimulator(3, conn, js, stream, so, serverCC, queueMode, syncMode); CountDownLatch msgLatch = new CountDownLatch(6); AtomicInteger received = new AtomicInteger(); @@ -1248,7 +1276,7 @@ private void _testOrderedConsume(JetStream js, TestingStreamContainer tsc, Order assertNull(occtx.getConsumerName()); try (MessageConsumer mcon = occtx.consume(handler)) { validateConsumerNameForOrdered(occtx, mcon, occ.getConsumerNamePrefix()); - jsPublish(js, tsc.subject(), 201, 6); + jsPublish(ctx.js, ctx.subject(), 201, 6); // wait for the messages awaitAndAssert(msgLatch); @@ -1264,18 +1292,14 @@ private void _testOrderedConsume(JetStream js, TestingStreamContainer tsc, Order @Test public void testOrderedConsumeMultipleSubjects() throws Exception { - jsServer.run(TestBase::atLeast2_10, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); + runInSharedCustom(VersionUtils::atLeast2_10, (nc, ctx) -> { + ctx.createOrReplaceStream(2); + jsPublish(ctx.js, ctx.subject(0), 10); + jsPublish(ctx.js, ctx.subject(1), 5); - TestingStreamContainer tsc = new TestingStreamContainer(jsm, 2); - jsPublish(js, tsc.subject(0), 10); - jsPublish(js, tsc.subject(1), 5); + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); - StreamContext sctx = js.getStreamContext(tsc.stream); - - OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubjects(tsc.subject(0), tsc.subject(1)); + OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubjects(ctx.subject(0), ctx.subject(1)); OrderedConsumerContext occtx = sctx.createOrderedConsumer(occ); int count0 = 0; @@ -1283,7 +1307,7 @@ public void testOrderedConsumeMultipleSubjects() throws Exception { try (FetchConsumer fc = occtx.fetch(FetchConsumeOptions.builder().maxMessages(20).expiresIn(2000).build())) { Message m = fc.nextMessage(); while (m != null) { - if (m.getSubject().equals(tsc.subject(0))) { + if (m.getSubject().equals(ctx.subject(0))) { count0++; } else { @@ -1301,111 +1325,122 @@ public void testOrderedConsumeMultipleSubjects() throws Exception { @Test public void testOrderedMultipleWays() throws Exception { - jsServer.run(TestBase::atLeast2_9_1, nc -> { - // Setup - JetStream js = nc.jetStream(); - JetStreamManagement jsm = nc.jetStreamManagement(); - - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - createMemoryStream(jsm, tsc.stream, tsc.subject()); + runInShared((nc, ctx) -> { + StreamContext sctx = ctx.js.getStreamContext(ctx.stream); - StreamContext sctx = js.getStreamContext(tsc.stream); - - OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration().filterSubject(tsc.subject()); + OrderedConsumerConfiguration occ = new OrderedConsumerConfiguration() + .filterSubject(ctx.subject()); OrderedConsumerContext occtx = sctx.createOrderedConsumer(occ); // can't do others while doing next - CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch1 = new CountDownLatch(1); new Thread(() -> { try { // make sure there is enough time to call other methods. - assertNull(occtx.next(2000)); + assertNull(occtx.next(1000)); } catch (Exception e) { throw new RuntimeException(e); } finally { - latch.countDown(); + latch1.countDown(); } }).start(); Thread.sleep(100); // make sure there is enough time for the thread to start and get into the next method - validateCantCallOtherMethods(occtx); + validateCantCallOtherMethods(occtx, true, true); //noinspection ResultOfMethodCallIgnored - latch.await(3000, TimeUnit.MILLISECONDS); - - for (int x = 0 ; x < 10_000; x++) { - js.publish(tsc.subject(), ("multiple" + x).getBytes()); - } + latch1.await(3000, TimeUnit.MILLISECONDS); // can do others now + jsPublishNull(ctx.js, ctx.subject(), 1); Message m = occtx.next(1000); assertNotNull(m); assertEquals(1, m.metaData().streamSequence()); - // can't do others while doing next + // can't do others while doing fetch int seq = 2; try (FetchConsumer fc = occtx.fetchMessages(5)) { - while (seq <= 6) { - m = fc.nextMessage(); + validateCantCallOtherMethods(occtx, false, true); + jsPublishNull(ctx.js, ctx.subject(), 5); + m = fc.nextMessage(); + while (m != null) { assertNotNull(m); assertEquals(seq, m.metaData().streamSequence()); assertFalse(fc.isFinished()); - validateCantCallOtherMethods(occtx); + validateCantCallOtherMethods(occtx, false, true); seq++; + m = fc.nextMessage(); } - assertNull(fc.nextMessage()); assertTrue(fc.isFinished()); - assertNull(fc.nextMessage()); // just some coverage + assertNull(fc.nextMessage()); // just some coverage for when finished } // can do others now + jsPublishNull(ctx.js, ctx.subject(), 1); m = occtx.next(1000); assertNotNull(m); assertEquals(seq++, m.metaData().streamSequence()); // can't do others while doing iterate - ConsumeOptions copts = ConsumeOptions.builder().batchSize(10).build(); + ConsumeOptions copts = ConsumeOptions.builder().batchSize(10).expiresIn(3000).build(); try (IterableConsumer ic = occtx.iterate(copts)) { - ic.stop(); + validateCantCallOtherMethods(occtx, true, true); + jsPublishNull(ctx.js, ctx.subject(), 1); m = ic.nextMessage(1000); - while (m != null) { - assertEquals(seq, m.metaData().streamSequence()); - if (!ic.isFinished()) { - validateCantCallOtherMethods(occtx); - } - ++seq; - m = ic.nextMessage(1000); + assertNotNull(m); + assertEquals(seq++, m.metaData().streamSequence()); + ic.stop(); + while (!ic.isFinished()) { + assertNull(ic.nextMessage(100)); } } // can do others now + jsPublishNull(ctx.js, ctx.subject(), 1); m = occtx.next(1000); assertNotNull(m); assertEquals(seq++, m.metaData().streamSequence()); - int last = Math.min(seq + 10, 9999); - try (FetchConsumer fc = occtx.fetchMessages(last - seq)) { - while (seq < last) { - fc.stop(); - m = fc.nextMessage(); - assertNotNull(m); - assertEquals(seq, m.metaData().streamSequence()); - assertFalse(fc.isFinished()); - validateCantCallOtherMethods(occtx); - seq++; - } + CountDownLatch latch2 = new CountDownLatch(1); + AtomicLong conSeq = new AtomicLong(); + copts = ConsumeOptions.builder().batchSize(10).expiresIn(1000).build(); + MessageConsumer mc = occtx.consume(copts, cm -> { + assertNotNull(cm); + conSeq.set(cm.metaData().streamSequence()); + latch2.countDown(); + }); + + validateCantCallOtherMethods(occtx, true, false); + + jsPublishNull(ctx.js, ctx.subject(), 1); + assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS)); + assertEquals(seq++, conSeq.get()); + mc.stop(); + while (!mc.isFinished()) { + sleep(100); } + mc.close(); + + // can do others now + jsPublishNull(ctx.js, ctx.subject(), 1); + m = occtx.next(1000); + assertNotNull(m); + assertEquals(seq, m.metaData().streamSequence()); }); } @SuppressWarnings("resource") - private void validateCantCallOtherMethods(OrderedConsumerContext ctx) { + private void validateCantCallOtherMethods(OrderedConsumerContext ctx, boolean fetch, boolean consume) { assertThrows(IOException.class, () -> ctx.next(1000)); - assertThrows(IOException.class, () -> ctx.fetchMessages(1)); - assertThrows(IllegalArgumentException.class, () -> ctx.consume(null)); + if (fetch) { + assertThrows(IOException.class, () -> ctx.fetchMessages(1)); + } + if (consume) { + assertThrows(IOException.class, () -> ctx.consume(m -> {})); + } } @Test @@ -1508,7 +1543,7 @@ private void check_values(OrderedConsumerConfiguration occ, ZonedDateTime zdt) { private OrderedConsumerConfiguration roundTripSerialize(OrderedConsumerConfiguration occ) throws IOException, ClassNotFoundException { SerializableOrderedConsumerConfiguration socc = new SerializableOrderedConsumerConfiguration(occ); - socc = (SerializableOrderedConsumerConfiguration)roundTripSerialize(socc); + socc = (SerializableOrderedConsumerConfiguration) roundTripSerialize(socc); return socc.getOrderedConsumerConfiguration(); } @@ -1523,28 +1558,23 @@ private Object roundTripSerialize(Serializable s) throws IOException, ClassNotFo @Test public void testOverflowFetch() throws Exception { - ListenerForTesting l = new ListenerForTesting(); - Options.Builder b = Options.builder().errorListener(l); - jsServer.run(b, TestBase::atLeast2_11, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - jsPublish(js, tsc.subject(), 100); + runInShared((nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 100); // Testing min ack pending - String group = variant(); - String cname = variant(); + String group = random(); + String cname = random(); ConsumerConfiguration cc = ConsumerConfiguration.builder() .name(cname) .priorityPolicy(PriorityPolicy.Overflow) .priorityGroups(group) .ackWait(10_000) - .filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + .filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - ConsumerContext ctxPrime = nc.getConsumerContext(tsc.stream, cname); - ConsumerContext ctxOver = nc.getConsumerContext(tsc.stream, cname); + ConsumerContext ctxPrime = nc.getConsumerContext(ctx.stream, cname); + ConsumerContext ctxOver = nc.getConsumerContext(ctx.stream, cname); FetchConsumeOptions fcoNoMin = FetchConsumeOptions.builder() .maxMessages(5).expiresIn(1000).group(group) @@ -1589,28 +1619,23 @@ private void _overflowFetch(String cname, ConsumerContext cctx, FetchConsumeOpti @Test public void testOverflowIterate() throws Exception { - ListenerForTesting l = new ListenerForTesting(); - Options.Builder b = Options.builder().errorListener(l); - runInJsServer(b, TestBase::atLeast2_11, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - jsPublish(js, tsc.subject(), 100); + runInShared(VersionUtils::atLeast2_11, (nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 100); // Testing min ack pending - String group = variant(); - String cname = variant(); + String group = random(); + String cname = random(); ConsumerConfiguration cc = ConsumerConfiguration.builder() .name(cname) .priorityPolicy(PriorityPolicy.Overflow) .priorityGroups(group) .ackWait(30_000) - .filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + .filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - ConsumerContext ctxPrime = nc.getConsumerContext(tsc.stream, cname); - ConsumerContext ctxOver = nc.getConsumerContext(tsc.stream, cname); + ConsumerContext ctxPrime = nc.getConsumerContext(ctx.stream, cname); + ConsumerContext ctxOver = nc.getConsumerContext(ctx.stream, cname); validateConsumerName(ctxPrime, null, cname); validateConsumerName(ctxOver, null, cname); @@ -1679,28 +1704,23 @@ public void testOverflowIterate() throws Exception { @Test public void testOverflowConsume() throws Exception { - ListenerForTesting l = new ListenerForTesting(); - Options.Builder b = Options.builder().errorListener(l); - runInJsServer(b, TestBase::atLeast2_11, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - JetStream js = nc.jetStream(); - jsPublish(js, tsc.subject(), 1000); + runInShared(VersionUtils::atLeast2_11, (nc, ctx) -> { + jsPublish(ctx.js, ctx.subject(), 1000); // Testing min ack pending - String group = variant(); - String cname = variant(); + String group = random(); + String cname = random(); ConsumerConfiguration cc = ConsumerConfiguration.builder() .name(cname) .priorityPolicy(PriorityPolicy.Overflow) .priorityGroups(group) .ackWait(30_000) - .filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + .filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - ConsumerContext ctxPrime = nc.getConsumerContext(tsc.stream, cname); - ConsumerContext ctxOver = nc.getConsumerContext(tsc.stream, cname); + ConsumerContext ctxPrime = nc.getConsumerContext(ctx.stream, cname); + ConsumerContext ctxOver = nc.getConsumerContext(ctx.stream, cname); validateConsumerName(ctxPrime, null, cname); validateConsumerName(ctxOver, null, cname); @@ -1731,8 +1751,7 @@ public void testOverflowConsume() throws Exception { }; try (MessageConsumer mcOver = ctxOver.consume(coOver, overHandler); - MessageConsumer mcPrime = ctxPrime.consume(coPrime, primeHandler)) - { + MessageConsumer mcPrime = ctxPrime.consume(coPrime, primeHandler)) { validateConsumerName(ctxPrime, mcPrime, cname); validateConsumerName(ctxOver, mcOver, cname); while (left.get() > 0) { @@ -1749,20 +1768,14 @@ public void testOverflowConsume() throws Exception { @Test public void testFinishEmptyStream() throws Exception { - ListenerForTesting l = new ListenerForTesting(); - Options.Builder b = Options.builder().errorListener(l); - runInJsServer(b, nc -> { - JetStreamManagement jsm = nc.jetStreamManagement(); - TestingStreamContainer tsc = new TestingStreamContainer(jsm); - - String name = variant(); - + runInShared((nc, ctx) -> { + String name = random(); ConsumerConfiguration cc = ConsumerConfiguration.builder() .name(name) - .filterSubjects(tsc.subject()).build(); - jsm.addOrUpdateConsumer(tsc.stream, cc); + .filterSubjects(ctx.subject()).build(); + ctx.jsm.addOrUpdateConsumer(ctx.stream, cc); - ConsumerContext cctx = nc.getConsumerContext(tsc.stream, name); + ConsumerContext cctx = nc.getConsumerContext(ctx.stream, name); MessageHandler handler = Message::ack; @@ -1776,7 +1789,6 @@ public void testFinishEmptyStream() throws Exception { } @Test -// @Disabled("This is a timing flapper and is annoying.") public void testReconnectOverOrdered() throws Exception { // ------------------------------------------------------------ // The idea here is... @@ -1787,19 +1799,11 @@ public void testReconnectOverOrdered() throws Exception { // so the alarm goes off but still disconnected // to make sure the consumer continues after that condition // ------------------------------------------------------------ - int port = NatsTestServer.nextPort(); - ListenerForTesting lft = new ListenerForTesting(); - Options options = new Options.Builder() - .connectionListener(lft) - .errorListener(lft) - .server(NatsTestServer.getNatsLocalhostUri(port)).build(); - NatsConnection nc; - - String stream = stream(); - String subject = subject(); + String stream = random(); + String subject = random(); AtomicBoolean allInOrder = new AtomicBoolean(true); - AtomicInteger atomicCount = new AtomicInteger(); + AtomicInteger messageCount = new AtomicInteger(); AtomicLong nextExpectedSequence = new AtomicLong(0); MessageHandler handler = msg -> { @@ -1807,75 +1811,79 @@ public void testReconnectOverOrdered() throws Exception { allInOrder.set(false); } msg.ack(); - atomicCount.incrementAndGet(); + messageCount.incrementAndGet(); sleep(50); // simulate some work and to slow the endless consume }; - // variable are here. initialized during first server, but used after. - StreamContext streamContext; - OrderedConsumerContext orderedConsumerContext; - MessageConsumer mcon; - String firstConsumerName; - - //noinspection unused - try (NatsTestServer ts = new NatsTestServer(port, false, true)) { - nc = (NatsConnection) standardConnection(options); - StreamConfiguration sc = StreamConfiguration.builder() - .name(stream) - .storageType(StorageType.File) // file since we are killing the server and bringing it back up. - .subjects(subject).build(); - nc.jetStreamManagement().addStream(sc); - - jsPublish(nc, subject, 10000); - - ConsumeOptions consumeOptions = ConsumeOptions.builder() - .batchSize(100) // small batch size means more round trips - .expiresIn(1500) // idle heartbeat is half of this, alarm time is 3 * ihb - .build(); - - OrderedConsumerConfiguration ocConfig = new OrderedConsumerConfiguration().filterSubjects(subject); - streamContext = nc.getStreamContext(stream); - orderedConsumerContext = streamContext.createOrderedConsumer(ocConfig); - assertNull(orderedConsumerContext.getConsumerName()); - mcon = orderedConsumerContext.consume(consumeOptions, handler); - firstConsumerName = validateConsumerNameForOrdered(orderedConsumerContext, mcon, null); + StreamConfiguration sc = StreamConfiguration.builder() + .name(stream) + .storageType(StorageType.File) // file since we are killing the server and bringing it back up. + .subjects(subject).build(); + + NatsTestServer ts = new NatsTestServer(NatsTestServer.builder().jetstream()); + /* start server */ + Listener listener = new Listener(); + Options options = optionsBuilder(ts) + .connectionListener(listener) + .errorListener(listener) + .build(); + NatsConnection nc = (NatsConnection) ConnectionUtils.managedConnect(options); + JetStreamManagement jsm = nc.jetStreamManagement(); + JetStream js = jsm.jetStream(); + jsm.addStream(sc); - sleep(500); // time enough to get some messages + for (int x = 0; x < 2000; x++) { + js.publish(subject, null); } - assertTrue(allInOrder.get()); - int count1 = atomicCount.get(); - assertTrue(count1 > 0); - assertEquals(count1, nextExpectedSequence.get()); + ConsumeOptions consumeOptions = ConsumeOptions.builder() + .batchSize(100) // small batch size means more round trips + .expiresIn(1000) // idle heartbeat is half of this, alarm time is 3 times + .build(); - // reconnect and get some more messages - try (NatsTestServer ignored = new NatsTestServer(port, false, true)) { - standardConnectionWait(nc); - sleep(6000); // long enough to get messages and for the hb alarm to have tripped - } - assertNotEquals(firstConsumerName, orderedConsumerContext.getConsumerName()); + OrderedConsumerConfiguration ocConfig = new OrderedConsumerConfiguration().filterSubjects(subject); + StreamContext streamContext = nc.getStreamContext(stream); + OrderedConsumerContext orderedConsumerContext = streamContext.createOrderedConsumer(ocConfig); + assertNull(orderedConsumerContext.getConsumerName()); + MessageConsumer mcon = orderedConsumerContext.consume(consumeOptions, handler); + validateConsumerNameForOrdered(orderedConsumerContext, mcon, null); + sleep(500); // time enough to get some messages - assertTrue(allInOrder.get()); - int count2 = atomicCount.get(); - assertTrue(count2 > count1); - assertEquals(count2, nextExpectedSequence.get()); + /* close server */ ts.close(); - sleep(6000); // enough delay before reconnect to trip hb alarm again - try (NatsTestServer ignored = new NatsTestServer(port, false, true)) { - standardConnectionWait(nc); - sleep(6000); // long enough to get messages and for the hb alarm to have tripped + validateOverOrdered(messageCount, allInOrder, nextExpectedSequence); - try { - nc.jetStreamManagement().deleteStream(stream); // it was a file stream clean it up - } - catch (JetStreamApiException ignore) { - // in GH actions this fails sometimes - } - } + // reconnect and get some more messages + messageCount.set(0); + listener.queueConnectionEvent(ConnectionListener.Events.RECONNECTED); + /* start server */ ts.start(); + listener.validate(); // reconnected + listener.queueHeartbeat(); + sleep(3500); // long enough to get messages and for the hb alarm to have tripped + /* close server */ ts.close(); + + listener.validate(); // heartbeat + validateOverOrdered(messageCount, allInOrder, nextExpectedSequence); + + // wait enough time to get more heartbeats, then reconnect and get some more messages + listener.queueHeartbeat(); + sleep(3500); + listener.validate(); // heartbeat + + messageCount.set(0); + listener.queueConnectionEvent(ConnectionListener.Events.RECONNECTED); + /* start server */ ts.start(); + listener.validate(); // reconnected + listener.queueHeartbeat(); + sleep(3500); // long enough to get messages and for the hb alarm to have tripped + /* close server */ ts.close(); + listener.validate(); // heartbeat + validateOverOrdered(messageCount, allInOrder, nextExpectedSequence); + } + private static void validateOverOrdered(AtomicInteger atomicCount, AtomicBoolean allInOrder, AtomicLong nextExpectedSequence) { + int count = atomicCount.get(); assertTrue(allInOrder.get()); - int count3 = atomicCount.get(); - assertTrue(count3 > count2); - assertEquals(count3, nextExpectedSequence.get()); + assertTrue(count > 0); } } diff --git a/src/test/java/io/nats/client/impl/SlowConsumerTests.java b/src/test/java/io/nats/client/impl/SlowConsumerTests.java index 262bb9720..825a61715 100644 --- a/src/test/java/io/nats/client/impl/SlowConsumerTests.java +++ b/src/test/java/io/nats/client/impl/SlowConsumerTests.java @@ -14,224 +14,238 @@ package io.nats.client.impl; import io.nats.client.*; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; -public class SlowConsumerTests { +public class SlowConsumerTests extends TestBase { @Test public void testDefaultPendingLimits() throws Exception { - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - - Subscription sub = nc.subscribe("subject"); + runInSharedOwnNc(nc -> { + String subject = random(); + Subscription sub = nc.subscribe(subject); Dispatcher d = nc.createDispatcher((Message m) -> {}); - assertEquals(sub.getPendingMessageLimit(), Consumer.DEFAULT_MAX_MESSAGES); - assertEquals(sub.getPendingByteLimit(), Consumer.DEFAULT_MAX_BYTES); + assertEquals(Consumer.DEFAULT_MAX_MESSAGES, sub.getPendingMessageLimit()); + assertEquals(Consumer.DEFAULT_MAX_BYTES, sub.getPendingByteLimit()); - assertEquals(d.getPendingMessageLimit(), Consumer.DEFAULT_MAX_MESSAGES); - assertEquals(d.getPendingByteLimit(), Consumer.DEFAULT_MAX_BYTES); - } + assertEquals(Consumer.DEFAULT_MAX_MESSAGES, d.getPendingMessageLimit()); + assertEquals(Consumer.DEFAULT_MAX_BYTES, d.getPendingByteLimit()); + nc.closeDispatcher(d); + }); } @Test public void testSlowSubscriberByMessages() throws Exception { - - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - - Subscription sub = nc.subscribe("subject"); + runInSharedOwnNc(nc -> { + String subject = random(); + int expectedPending = subject.length() + 12; + Subscription sub = nc.subscribe(subject); sub.setPendingLimits(1, -1); assertEquals(1, sub.getPendingMessageLimit()); assertEquals(0, sub.getPendingByteLimit()); assertEquals(0, sub.getDroppedCount()); - nc.publish("subject", null); - nc.publish("subject", null); - nc.publish("subject", null); - nc.publish("subject", null); + nc.publish(subject, null); + nc.publish(subject, null); + nc.publish(subject, null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); assertEquals(3, sub.getDroppedCount()); assertEquals(1, sub.getPendingMessageCount()); - assertEquals(19, sub.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf + assertEquals(expectedPending, sub.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf sub.clearDroppedCount(); - nc.publish("subject", null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); assertEquals(1, sub.getDroppedCount()); assertEquals(1, sub.getPendingMessageCount()); - } + }); } @Test public void testSlowSubscriberByBytes() throws Exception { - - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - - Subscription sub = nc.subscribe("subject"); - sub.setPendingLimits(-1, 10); // will take the first, not the second - - assertEquals(10, sub.getPendingByteLimit()); + runInSharedOwnNc(nc -> { + String subject = random(); + int maxBytes = subject.length() + 3; + int expectedPending = maxBytes + 9; + Subscription sub = nc.subscribe(subject); + sub.setPendingLimits(-1, maxBytes); // will take the first, not the second + + assertEquals(maxBytes, sub.getPendingByteLimit()); assertEquals(0, sub.getPendingMessageLimit()); assertEquals(0, sub.getDroppedCount()); - nc.publish("subject", null); - nc.publish("subject", null); + nc.publish(subject, null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); assertEquals(1, sub.getDroppedCount()); assertEquals(1, sub.getPendingMessageCount()); - assertEquals(19, sub.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf + assertEquals(expectedPending, sub.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf sub.clearDroppedCount(); - nc.publish("subject", null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); assertEquals(1, sub.getDroppedCount()); assertEquals(1, sub.getPendingMessageCount()); - } + }); } @Test public void testSlowSDispatcherByMessages() throws Exception { + runInSharedOwnNc(nc -> { + String subject = random(); + int expectedPending = subject.length() + 12; - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - final CompletableFuture ok = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { ok.complete(null); Thread.sleep(5 * 60 * 1000); // will wait until interrupted }); d.setPendingLimits(1, -1); - d.subscribe("subject"); + d.subscribe(subject); assertEquals(1, d.getPendingMessageLimit()); assertEquals(0, d.getPendingByteLimit()); assertEquals(0, d.getDroppedCount()); - nc.publish("subject", null); + nc.publish(subject, null); ok.get(1000,TimeUnit.MILLISECONDS); // make sure we got the first one - nc.publish("subject", null); - nc.publish("subject", null); + nc.publish(subject, null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(1000)); assertEquals(1, d.getDroppedCount()); assertEquals(1, d.getPendingMessageCount()); - assertEquals(19, d.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf + assertEquals(expectedPending, d.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf d.clearDroppedCount(); - nc.publish("subject", null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); assertEquals(1, d.getDroppedCount()); assertEquals(1, d.getPendingMessageCount()); - } + }); } @Test public void testSlowSDispatcherByBytes() throws Exception { - - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(ts.getURI())) { - + runInSharedOwnNc(nc -> { + String subject = random(); + int maxBytes = subject.length() + 3; + int expectedPending = maxBytes + 9; final CompletableFuture ok = new CompletableFuture<>(); - Dispatcher d = nc.createDispatcher((msg) -> { + Dispatcher d = nc.createDispatcher(msg -> { ok.complete(null); Thread.sleep(5 * 60 * 1000); // will wait until interrupted }); - d.setPendingLimits(-1, 10); - d.subscribe("subject"); + d.setPendingLimits(-1, maxBytes); + d.subscribe(subject); assertEquals(0, d.getPendingMessageLimit()); - assertEquals(10, d.getPendingByteLimit()); + assertEquals(maxBytes, d.getPendingByteLimit()); assertEquals(0, d.getDroppedCount()); - nc.publish("subject", null); + nc.publish(subject, null); ok.get(1000,TimeUnit.MILLISECONDS); // make sure we got the first one - nc.publish("subject", null); - nc.publish("subject", null); + nc.publish(subject, null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); assertEquals(1, d.getDroppedCount()); assertEquals(1, d.getPendingMessageCount()); - assertEquals(19, d.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf + assertEquals(expectedPending, d.getPendingByteCount()); // "msg 1 subject 0" + crlf + crlf d.clearDroppedCount(); - nc.publish("subject", null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); assertEquals(1, d.getDroppedCount()); assertEquals(1, d.getPendingMessageCount()); + }); + } + + static class SlowConsumerListener implements ErrorListener { + public CompletableFuture future; + public final List consumers = new ArrayList<>(); + + public void waitForSlow() { + future = new CompletableFuture<>(); + } + + @Override + public void slowConsumerDetected(Connection conn, Consumer consumer) { + consumers.add(consumer); + if (future != null) { + future.complete(true); + } } } @Test public void testSlowSubscriberNotification() throws Exception { - ListenerForTesting listener = new ListenerForTesting(); - try (NatsTestServer ts = new NatsTestServer(false); - NatsConnection nc = (NatsConnection) Nats.connect(new Options.Builder(). - server(ts.getURI()).errorListener(listener).build())) { - - Subscription sub = nc.subscribe("subject"); + SlowConsumerListener listener = new SlowConsumerListener(); + runInSharedOwnNc(listener, nc -> { + String subject = random(); + NatsSubscription sub = (NatsSubscription)nc.subscribe(subject); sub.setPendingLimits(1, -1); - Future waitForSlow = listener.waitForSlow(); + listener.waitForSlow(); - nc.publish("subject", null); - nc.publish("subject", null); - nc.publish("subject", null); - nc.publish("subject", null); + nc.publish(subject, null); + nc.publish(subject, null); + nc.publish(subject, null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(5000)); // Notification is in another thread, wait for it, or fail - waitForSlow.get(1000, TimeUnit.MILLISECONDS); + listener.future.get(3000, TimeUnit.MILLISECONDS); - List slow = listener.getSlowConsumers(); - assertEquals(1, slow.size()); // should only appear once - assertEquals(sub, slow.get(0)); - slow.clear(); + assertEquals(1, listener.consumers.size()); // should only appear once + assertEquals(sub, listener.consumers.get(0)); + listener.consumers.clear(); - nc.publish("subject", null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(1000)); - assertEquals(0, slow.size()); // no renotifiy + assertEquals(0, listener.consumers.size()); // no renotify - waitForSlow = listener.waitForSlow(); - // Clear the queue, we shoudl become a non-slow consumer + listener.waitForSlow(); + // Clear the queue, we should become a non-slow consumer sub.nextMessage(Duration.ofMillis(1000)); // only 1 to get // Notification again on 2nd message - nc.publish("subject", null); - nc.publish("subject", null); + nc.publish(subject, null); + nc.publish(subject, null); nc.flush(Duration.ofMillis(1000)); - waitForSlow.get(1000, TimeUnit.MILLISECONDS); + listener.future.get(3000, TimeUnit.MILLISECONDS); - assertEquals(1, slow.size()); // should only appear once - assertEquals(sub, slow.get(0)); - } + assertEquals(1, listener.consumers.size()); // should only appear once + assertEquals(sub, listener.consumers.get(0)); + }); } } \ No newline at end of file diff --git a/src/test/java/io/nats/client/impl/TLSConnectTests.java b/src/test/java/io/nats/client/impl/TLSConnectTests.java index 4e3887210..e3f74e96e 100644 --- a/src/test/java/io/nats/client/impl/TLSConnectTests.java +++ b/src/test/java/io/nats/client/impl/TLSConnectTests.java @@ -13,13 +13,14 @@ package io.nats.client.impl; +import io.nats.NatsServerRunner; import io.nats.client.*; import io.nats.client.ConnectionListener.Events; +import io.nats.client.support.Listener; import io.nats.client.support.ssl.ExpiringClientCertUtil; import io.nats.client.support.ssl.ExpiringComponents; import io.nats.client.support.ssl.SslTestingHelper; import io.nats.client.utils.CloseOnUpgradeAttempt; -import io.nats.client.utils.ResourceUtils; import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; @@ -39,52 +40,43 @@ import java.util.concurrent.atomic.AtomicReference; import static io.nats.client.Options.PROP_SSL_CONTEXT_FACTORY_CLASS; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static io.nats.client.utils.ResourceUtils.createTempDirectory; +import static io.nats.client.utils.ResourceUtils.deleteFileOrFolder; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class TLSConnectTests extends TestBase { - private String convertToProtocol(String proto, NatsTestServer... servers) { - StringBuilder sb = new StringBuilder(); - for (int x = 0; x < servers.length; x++) { - if (x > 0) { - sb.append(","); - } - sb.append(proto).append("://localhost:").append(servers[x].getPort()); - } - return sb.toString(); - } - - private static Options createTestOptionsManually(String servers) throws Exception { - return new Options.Builder() - .server(servers) + private static Options createTestOptionsManually(String... servers) throws Exception { + return optionsBuilder(servers) .maxReconnects(0) .sslContext(SslTestingHelper.createTestSSLContext()) .build(); } - private static Options createTestOptionsViaProperties(String servers) { + private static Options createTestOptionsViaProperties(String... servers) { Options options; Properties props = SslTestingHelper.createTestSSLProperties(); - props.setProperty(Options.PROP_SERVERS, servers); + props.setProperty(Options.PROP_SERVERS, String.join(",", servers)); props.setProperty(Options.PROP_MAX_RECONNECT, "0"); options = new Options.Builder(props).build(); return options; } - private static Options createTestOptionsViaFactoryInstance(String servers) { - return new Options.Builder() - .server(servers) + private static Options createTestOptionsViaFactoryInstance(String... servers) { + return optionsBuilder(servers) .maxReconnects(0) .sslContextFactory(new SSLContextFactoryForTesting()) .build(); } - private static Options createTestOptionsViaFactoryClassName(String servers) { + private static Options createTestOptionsViaFactoryClassName(String... servers) { Properties properties = new Properties(); properties.setProperty(PROP_SSL_CONTEXT_FACTORY_CLASS, SSLContextFactoryForTesting.class.getCanonicalName()); - return new Options.Builder(properties) - .server(servers) + return optionsBuilder(servers) + .properties(properties) .maxReconnects(0) .sslContextFactory(new SSLContextFactoryForTesting()) .build(); @@ -92,131 +84,103 @@ private static Options createTestOptionsViaFactoryClassName(String servers) { @Test public void testSimpleTLSConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) { - String servers = ts.getURI(); + runInSharedConfiguredServer("tls.conf", 1, ts1 -> { + String servers = ts1.getServerUri(); assertCanConnectAndPubSub(createTestOptionsManually(servers)); assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); - } - } - - @Test - public void testSimpleTlsFirstConnection() throws Exception { - if (TestBase.atLeast2_10_3(ensureRunServerInfo())) { - try (NatsTestServer ts = new NatsTestServer( - NatsTestServer.builder() - .configFilePath("src/test/resources/tls_first.conf") - .connectValidateTlsFirstMode()) - ) { - String servers = ts.getURI(); - Options options = new Options.Builder() - .server(servers) - .maxReconnects(0) - .tlsFirst() - .sslContext(SslTestingHelper.createTestSSLContext()) - .build(); - assertCanConnectAndPubSub(options); - } - } - } - - @Test - public void testSimpleUrlTLSConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) { - String servers = convertToProtocol("tls", ts); - assertCanConnectAndPubSub(createTestOptionsManually(servers)); - assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); - } + }); } @Test public void testMultipleUrlTLSConnectionSetContext() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer server1 = new NatsTestServer("src/test/resources/tls.conf", false); - NatsTestServer server2 = new NatsTestServer("src/test/resources/tls.conf", false); - ) { - String servers = convertToProtocol("tls", server1, server2); - assertCanConnectAndPubSub(createTestOptionsManually(servers)); - assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); - } + runInSharedConfiguredServer("tls.conf", 1, ts1 -> + runInSharedConfiguredServer("tls.conf", 2, ts2 -> { + String[] servers = NatsTestServer.getLocalhostUris("tls", ts1, ts2); + assertCanConnectAndPubSub(createTestOptionsManually(servers)); + assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); + })); } @Test public void testSimpleIPTLSConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) { - String servers = "127.0.0.1:" + ts.getPort(); - assertCanConnectAndPubSub(createTestOptionsManually(servers)); - assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); - } - } - - @Test - public void testVerifiedTLSConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", false)) { - String servers = ts.getURI(); + runInSharedConfiguredServer("tls.conf", 1, ts1 -> { + String servers = "127.0.0.1:" + ts1.getPort(); assertCanConnectAndPubSub(createTestOptionsManually(servers)); assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); - } + }); } @Test - public void testOpenTLSConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) { - String servers = ts.getURI(); - Options options = new Options.Builder() - .server(servers) + public void testURISchemeOpenTLSConnection() throws Exception { + runInSharedConfiguredServer("tls.conf", 1, ts1 -> { + String[] servers = NatsTestServer.getLocalhostUris("opentls", ts1); + Options options = optionsBuilder(servers) .maxReconnects(0) .opentls() .build(); assertCanConnectAndPubSub(options); Properties props = new Properties(); - props.setProperty(Options.PROP_SERVERS, servers); + props.setProperty(Options.PROP_SERVERS, String.join(",", servers)); props.setProperty(Options.PROP_MAX_RECONNECT, "0"); props.setProperty(Options.PROP_OPENTLS, "true"); assertCanConnectAndPubSub(new Options.Builder(props).build()); - } + }); } @Test - public void testURISchemeTLSConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", false)) { - String servers = "tls://localhost:"+ts.getPort(); - assertCanConnectAndPubSub(createTestOptionsManually(servers)); - assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); - } + public void testMultipleUrlOpenTLSConnection() throws Exception { + runInSharedConfiguredServer("tls.conf", 1, ts1 -> + runInSharedConfiguredServer("tls.conf", 2, ts2 -> { + String[] servers = NatsTestServer.getLocalhostUris("opentls", ts1, ts2); + Options options = optionsBuilder(servers) + .maxReconnects(0) + .opentls() + .build(); + assertCanConnectAndPubSub(options); + + Properties props = new Properties(); + props.setProperty(Options.PROP_SERVERS, String.join(",", servers)); + props.setProperty(Options.PROP_MAX_RECONNECT, "0"); + props.setProperty(Options.PROP_OPENTLS, "true"); + assertCanConnectAndPubSub(new Options.Builder(props).build()); + })); } @Test - public void testURISchemeIPTLSConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", false)) { - String servers = "tls://127.0.0.1:"+ts.getPort(); - assertCanConnectAndPubSub(createTestOptionsManually(servers)); - assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); - assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); - } + public void testProxyNotTlsFirst() throws Exception { + runInSharedConfiguredServer("tls.conf", 1, ts1 -> { + // 1. client regular secure | secure proxy | server insecure -> mismatch exception + Listener listener = new Listener(); + ProxyConnection connRI = new ProxyConnection(ts1.getServerUri(), false, listener, SERVER_INSECURE); + listener.queueException(IOException.class, "SSL connection wanted by client"); + assertThrows(Exception.class, () -> connRI.connect(false)); + sleep(100); // give time for listener to get message + assertEquals(1, listener.getExceptionCount()); + + // 2. client regular secure | secure proxy | server tls required -> connects + ProxyConnection connRR = new ProxyConnection(ts1.getServerUri(), false, null, SERVER_TLS_REQUIRED); + connRR.connect(false); + confirmConnectedThenClosed(connRR); + + // 3. client regular secure | secure proxy | server tls available -> connects + ProxyConnection connRA = new ProxyConnection(ts1.getServerUri(), false, null, SERVER_TLS_AVAILABLE); + connRA.connect(false); + confirmConnectedThenClosed(connRA); + }); } @Test - public void testURISchemeOpenTLSConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) { - String servers = convertToProtocol("opentls", ts); - Options options = new Options.Builder() + public void testOpenTLSConnection() throws Exception { + runInSharedConfiguredServer("tls.conf", 1, ts1 -> { + String servers = ts1.getServerUri(); + Options options = optionsBuilder() .server(servers) .maxReconnects(0) .opentls() @@ -228,167 +192,160 @@ public void testURISchemeOpenTLSConnection() throws Exception { props.setProperty(Options.PROP_MAX_RECONNECT, "0"); props.setProperty(Options.PROP_OPENTLS, "true"); assertCanConnectAndPubSub(new Options.Builder(props).build()); - } + }); } @Test - public void testMultipleUrlOpenTLSConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer server1 = new NatsTestServer("src/test/resources/tls.conf", false); - NatsTestServer server2 = new NatsTestServer("src/test/resources/tls.conf", false); - ) { - String servers = convertToProtocol("opentls", server1, server2); - Options options = new Options.Builder() - .server(servers) + public void testSimpleTlsFirstConnection() throws Exception { + runInSharedConfiguredServer("tls_first.conf", ts -> { + Options options = optionsBuilder(ts) .maxReconnects(0) - .opentls() + .tlsFirst() + .sslContext(SslTestingHelper.createTestSSLContext()) .build(); assertCanConnectAndPubSub(options); + }); + } - Properties props = new Properties(); - props.setProperty(Options.PROP_SERVERS, servers); - props.setProperty(Options.PROP_MAX_RECONNECT, "0"); - props.setProperty(Options.PROP_OPENTLS, "true"); - assertCanConnectAndPubSub(new Options.Builder(props).build()); - } + @Test + public void testVerifiedTLSConnection() throws Exception { + runInSharedConfiguredServer("tlsverify.conf", ts -> { + String servers = ts.getServerUri(); + assertCanConnectAndPubSub(createTestOptionsManually(servers)); + assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); + }); + } + + @Test + public void testURISchemeTLSConnection() throws Exception { + runInSharedConfiguredServer("tlsverify.conf", ts -> { + String servers = "tls://localhost:" + ts.getPort(); + assertCanConnectAndPubSub(createTestOptionsManually(servers)); + assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); + }); + } + + @Test + public void testURISchemeIPTLSConnection() throws Exception { + runInSharedConfiguredServer("tlsverify.conf", ts -> { + String servers = "tls://127.0.0.1:" + ts.getPort(); + assertCanConnectAndPubSub(createTestOptionsManually(servers)); + assertCanConnectAndPubSub(createTestOptionsViaProperties(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryInstance(servers)); + assertCanConnectAndPubSub(createTestOptionsViaFactoryClassName(servers)); + }); } @Test public void testTLSMessageFlow() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", false)) { + runInSharedConfiguredServer("tlsverify.conf", ts -> { SSLContext ctx = SslTestingHelper.createTestSSLContext(); int msgCount = 100; - Options options = new Options.Builder() - .server(ts.getURI()) + Options options = optionsBuilder(ts) .maxReconnects(0) .sslContext(ctx) .build(); - Connection nc = standardConnection(options); - Dispatcher d = nc.createDispatcher((msg) -> { - nc.publish(msg.getReplyTo(), new byte[16]); - }); - d.subscribe("subject"); - - for (int i=0;i incoming = nc.request("subject", null); - Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertNotNull(msg); - assertEquals(16, msg.getData().length); + try (Connection nc = managedConnect(options)) { + Dispatcher d = nc.createDispatcher( + msg -> nc.publish(msg.getReplyTo(), new byte[16])); + String subject = random(); + d.subscribe(subject); + + for (int i = 0; i < msgCount; i++) { + Future incoming = nc.request(subject, null); + Message msg = incoming.get(500, TimeUnit.MILLISECONDS); + assertNotNull(msg); + assertEquals(16, msg.getData().length); + } } - - standardCloseConnection(nc); - } + }); } @Test - public void testTLSOnReconnect() throws InterruptedException, Exception { - Connection nc; - ListenerForTesting listener = new ListenerForTesting(); + public void testTLSOnReconnect() throws Exception { + AtomicReference ncRef = new AtomicReference<>(); + Listener listener = new Listener(); + + // Use two server ports to avoid port release timing issues int port = NatsTestServer.nextPort(); int newPort = NatsTestServer.nextPort(); - // Use two server ports to avoid port release timing issues - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", port, false)) { + runInConfiguredServer("tlsverify.conf", port, ts -> { SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = new Options.Builder(). - server(ts.getURI()). - server(NatsTestServer.getNatsLocalhostUri(newPort)). - maxReconnects(-1). - sslContext(ctx). - connectionListener(listener). - reconnectWait(Duration.ofMillis(10)). - build(); - nc = standardConnection(options); - assertInstanceOf(SocketDataPort.class, ((NatsConnection) nc).getDataPort(), "Correct data port class"); - listener.prepForStatusChange(Events.DISCONNECTED); - } - - flushAndWaitLong(nc, listener); - listener.prepForStatusChange(Events.RESUBSCRIBED); + Options options = optionsBuilder(ts.getServerUri(), NatsTestServer.getLocalhostUri(newPort)) + .maxReconnects(-1) + .sslContext(ctx) + .connectionListener(listener) + .reconnectWait(Duration.ofMillis(10)) + .build(); + ncRef.set((NatsConnection) managedConnect(options)); + assertInstanceOf(SocketDataPort.class, ncRef.get().getDataPort(), "Correct data port class"); + listener.queueConnectionEvent(Events.DISCONNECTED); + }); - try (NatsTestServer ignored = new NatsTestServer("src/test/resources/tlsverify.conf", newPort, false)) { - listenerConnectionWait(nc, listener, 10000); - } + NatsConnection nc = ncRef.get(); + flushConnection(nc); + listener.validate(); - standardCloseConnection(nc); + listener.queueConnectionEvent(Events.RESUBSCRIBED); + runInConfiguredServer("tlsverify.conf", newPort, ts -> listener.validate()); + closeAndConfirm(nc); } @Test - public void testDisconnectOnUpgrade() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", false)) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - dataPortType(CloseOnUpgradeAttempt.class.getCanonicalName()). - sslContext(ctx). - build(); - Nats.connect(options); - } + public void testDisconnectOnUpgrade() throws Exception { + runInSharedConfiguredServer("tlsverify.conf", ts -> { + SSLContext ctx = SslTestingHelper.createTestSSLContext(); + Options options = optionsBuilder(ts) + .maxReconnects(0) + .dataPortType(CloseOnUpgradeAttempt.class.getCanonicalName()) + .sslContext(ctx) + .build(); + assertThrows(IOException.class, () -> Nats.connect(options)); }); } @Test - public void testServerSecureClientNotMismatch() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", false)) { - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - build(); - Nats.connect(options); - } + public void testServerSecureClientNotMismatch() throws Exception { + runInSharedConfiguredServer("tlsverify.conf", ts -> { + Options options = optionsBuilder(ts).maxReconnects(0).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); }); } @Test - public void testClientSecureServerNotMismatch() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer()) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = new Options.Builder(). - server(ts.getURI()). - maxReconnects(0). - sslContext(ctx). - build(); - Nats.connect(options); - } + public void testClientSecureServerNotMismatch() throws Exception { + runInSharedOwnNc(nc -> { + SSLContext ctx = SslTestingHelper.createTestSSLContext(); + Options options = optionsBuilder(nc).maxReconnects(0).sslContext(ctx).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); }); } @Test - public void testClientServerCertMismatch() { - AtomicReference listenedException = new AtomicReference<>(); - ErrorListener el = new ErrorListener() { - @Override - public void exceptionOccurred(Connection conn, Exception exp) { - listenedException.set(exp); - } - }; - - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tlsverify.conf", false)) { - SSLContext ctx = SslTestingHelper.createEmptySSLContext(); - Options options = new Options.Builder() - .server(ts.getURI()) - .maxReconnects(0) - .sslContext(ctx) - .errorListener(el) - .build(); - Nats.connect(options); - } + public void testClientServerCertMismatch() throws Exception { + Listener listener = new Listener(); + listener.queueException(CertificateException.class); + runInSharedConfiguredServer("tlsverify.conf", ts -> { + SSLContext ctx = SslTestingHelper.createEmptySSLContext(); + Options options = optionsBuilder(ts) + .maxReconnects(0) + .sslContext(ctx) + .errorListener(listener) + .build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + listener.validate(); }); - - Exception e = listenedException.get(); - assertNotNull(e); - assertInstanceOf(CertificateException.class, e.getCause().getCause()); } @Test public void testSSLContextFactoryPropertiesPassOnCorrectly() throws NoSuchAlgorithmException { SSLContextFactoryForTesting factory = new SSLContextFactoryForTesting(); - new Options.Builder() + optionsBuilder() .sslContextFactory(factory) .keystorePath("keystorePath") .keystorePassword("ksp".toCharArray()) @@ -423,8 +380,7 @@ public ProxyConnection(String servers, boolean tlsFirst, ErrorListener listener, } private static Options makeMiddleman(String servers, boolean tlsFirst, ErrorListener listener) throws Exception { - Options.Builder builder = new Options.Builder() - .server(servers) + Options.Builder builder = optionsBuilder(servers) .maxReconnects(0) .sslContext(SslTestingHelper.createTestSSLContext()) .errorListener(listener); @@ -459,51 +415,23 @@ protected void handleInfo(String infoJson) { */ @Test public void testProxyTlsFirst() throws Exception { - if (TestBase.atLeast2_10_3(ensureRunServerInfo())) { - // cannot check connect b/c tls first - try (NatsTestServer ts = new NatsTestServer( - NatsTestServer.builder() - .configFilePath("src/test/resources/tls_first.conf") - .connectValidateTlsFirstMode()) - ) { - // 1. client tls first | secure proxy | server insecure -> connects - ProxyConnection connTI = new ProxyConnection(ts.getURI(), true, null, SERVER_INSECURE); - connTI.connect(false); - closeConnection(standardConnectionWait(connTI), 1000); - - // 2. client tls first | secure proxy | server tls required -> connects - ProxyConnection connTR = new ProxyConnection(ts.getURI(), true, null, SERVER_TLS_REQUIRED); - connTR.connect(false); - closeConnection(standardConnectionWait(connTR), 1000); - - // 3. client tls first | secure proxy | server tls available -> connects - ProxyConnection connTA = new ProxyConnection(ts.getURI(), true, null, SERVER_TLS_AVAILABLE); - connTA.connect(false); - closeConnection(standardConnectionWait(connTA), 1000); - } - } - } - - @Test - public void testProxyNotTlsFirst() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) { - // 4. client regular secure | secure proxy | server insecure -> mismatch exception - ListenerForTesting listener = new ListenerForTesting(); - ProxyConnection connRI = new ProxyConnection(ts.getURI(), false, listener, SERVER_INSECURE); - assertThrows(Exception.class, () -> connRI.connect(false)); - assertEquals(1, listener.getExceptions().size()); - assertTrue(listener.getExceptions().get(0).getMessage().contains("SSL connection wanted by client")); - - // 5. client regular secure | secure proxy | server tls required -> connects - ProxyConnection connRR = new ProxyConnection(ts.getURI(), false, null, SERVER_TLS_REQUIRED); - connRR.connect(false); - closeConnection(standardConnectionWait(connRR), 1000); - - // 6. client regular secure | secure proxy | server tls available -> connects - ProxyConnection connRA = new ProxyConnection(ts.getURI(), false, null, SERVER_TLS_AVAILABLE); - connRA.connect(false); - closeConnection(standardConnectionWait(connRA), 1000); - } + // cannot check connect b/c tls first + runInSharedConfiguredServer("tls_first.conf", ts -> { + // 1. client tls first | secure proxy | server insecure -> connects + ProxyConnection connTI = new ProxyConnection(ts.getServerUri(), true, null, SERVER_INSECURE); + connTI.connect(false); + confirmConnectedThenClosed(connTI); + + // 2. client tls first | secure proxy | server tls required -> connects + ProxyConnection connTR = new ProxyConnection(ts.getServerUri(), true, null, SERVER_TLS_REQUIRED); + connTR.connect(false); + confirmConnectedThenClosed(connTR); + + // 3. client tls first | secure proxy | server tls available -> connects + ProxyConnection connTA = new ProxyConnection(ts.getServerUri(), true, null, SERVER_TLS_AVAILABLE); + connTA.connect(false); + confirmConnectedThenClosed(connTA); + }); } @Test @@ -512,7 +440,7 @@ public void testConnectFailsFromSslContext() throws Exception { SslTestErrorListener el = new SslTestErrorListener(1); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) { + runInSharedConfiguredServer("tls.conf", 1, ts -> { Options options = new Options.Builder() .server(ts.getNatsLocalhostUri()) .sslContext(sslContext) @@ -529,7 +457,7 @@ public void testConnectFailsFromSslContext() throws Exception { } assertTrue(el.latch.await(2, TimeUnit.SECONDS)); - } + }); } @Test @@ -544,7 +472,8 @@ public void testConnectFailsCertAlreadyExpired() throws Exception { SslTestErrorListener el = new SslTestErrorListener(1); - try (NatsTestServer ts = new NatsTestServer(configFilePath, false)) { + NatsServerRunner.Builder b = NatsServerRunner.builder().configFilePath(configFilePath); + try (NatsTestServer ts = new NatsTestServer(b)) { Options options = new Options.Builder() .server(ts.getNatsLocalhostUri()) .sslContext(expiring.sslContext) @@ -564,7 +493,7 @@ public void testConnectFailsCertAlreadyExpired() throws Exception { } } finally { - ResourceUtils.deleteFileOrFolder(tmpDir); + deleteFileOrFolder(tmpDir); } } @@ -584,8 +513,10 @@ public void testReconnectFailsAfterCertExpires() throws Exception { SslTestErrorListener el = new SslTestErrorListener(2); Connection nc; - try (NatsTestServer ts1 = new NatsTestServer(configFilePath, false)) { - try (NatsTestServer ts2 = new NatsTestServer(configFilePath, false)) { + NatsServerRunner.Builder b1 = NatsServerRunner.builder().configFilePath(configFilePath); + NatsServerRunner.Builder b2 = NatsServerRunner.builder().configFilePath(configFilePath); + try (NatsTestServer ts1 = new NatsTestServer(b1)) { + try (NatsTestServer ts2 = new NatsTestServer(b2)) { Options options = new Options.Builder() .servers(new String[]{ts2.getNatsLocalhostUri(), ts1.getNatsLocalhostUri()}) .noRandomize() @@ -607,7 +538,7 @@ public void testReconnectFailsAfterCertExpires() throws Exception { } } finally { - ResourceUtils.deleteFileOrFolder(tmpDir); + deleteFileOrFolder(tmpDir); } } @@ -624,7 +555,8 @@ public void testForceReconnectFailsAfterCertExpires() throws Exception { SslTestConnectionListener cl = new SslTestConnectionListener(1); SslTestErrorListener el = new SslTestErrorListener(2); - try (NatsTestServer ts = new NatsTestServer(configFilePath, false)) { + NatsServerRunner.Builder b = NatsServerRunner.builder().configFilePath(configFilePath); + try (NatsTestServer ts = new NatsTestServer(b)) { Options options = new Options.Builder() .server(ts.getNatsLocalhostUri()) .sslContext(expiring.sslContext) @@ -645,7 +577,7 @@ public void testForceReconnectFailsAfterCertExpires() throws Exception { } } finally { - ResourceUtils.deleteFileOrFolder(tmpDir); + deleteFileOrFolder(tmpDir); } } diff --git a/src/test/java/io/nats/client/impl/ValidateIssue1426Test.java b/src/test/java/io/nats/client/impl/ValidateIssue1426Test.java index a8116d28f..ce00b8ab8 100644 --- a/src/test/java/io/nats/client/impl/ValidateIssue1426Test.java +++ b/src/test/java/io/nats/client/impl/ValidateIssue1426Test.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -50,8 +51,7 @@ public void errorOccurred(Connection conn, String error) { } }; - Options options = new Options.Builder() - .servers(new String[]{"nats://" + "127.0.0.1:" + port}) + Options options = optionsBuilder(port) .token(new char[]{'1', '2', '3', '4'}) .maxMessagesInOutgoingQueue(NUMBER_OF_SUBS ) .reconnectBufferSize(NUMBER_OF_SUBS * 100) @@ -114,7 +114,7 @@ public ServerContext(int port) { } public void startServer() throws IOException { - server.set(new NatsTestServer(new String[]{"--auth", "1234"}, port, false)); + server.set(new NatsTestServer(new String[]{"--auth", "1234"}, port)); } private void restartServer() { diff --git a/src/test/java/io/nats/client/impl/WebsocketConnectTests.java b/src/test/java/io/nats/client/impl/WebsocketConnectTests.java index 2599e3802..e1007868d 100644 --- a/src/test/java/io/nats/client/impl/WebsocketConnectTests.java +++ b/src/test/java/io/nats/client/impl/WebsocketConnectTests.java @@ -16,6 +16,7 @@ import io.nats.NatsServerRunner; import io.nats.client.*; import io.nats.client.support.HttpRequest; +import io.nats.client.support.Listener; import io.nats.client.support.ssl.SslTestingHelper; import io.nats.client.utils.CloseOnUpgradeAttempt; import io.nats.client.utils.RunProxy; @@ -29,310 +30,236 @@ import java.time.Duration; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import static io.nats.client.ConnectionListener.Events.CONNECTED; -import static io.nats.client.NatsTestServer.*; +import static io.nats.client.ConnectionListener.Events.RECONNECTED; +import static io.nats.client.NatsTestServer.configFileBuilder; +import static io.nats.client.NatsTestServer.nextPort; +import static io.nats.client.utils.ConnectionUtils.assertConnected; +import static io.nats.client.utils.ConnectionUtils.managedConnect; +import static io.nats.client.utils.OptionsUtils.NOOP_EL; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; import static org.junit.jupiter.api.Assertions.*; public class WebsocketConnectTests extends TestBase { - @Test - public void testRequestReply() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/ws.conf", false)) { - standardRequestReply(Options.builder() - .server(getNatsLocalhostUri(ts.getPort())) - .maxReconnects(0).build()); - - standardRequestReply(Options.builder(). - server(getLocalhostUri("ws", ts.getPort("ws"))) - .maxReconnects(0).build()); - } + private static Options.Builder builder() { + return Options.builder() + .maxReconnects(0) + .errorListener(NOOP_EL); + } + + private static Options.Builder wsBuilder(NatsTestServer ts) { + return builder() + .server(NatsTestServer.getLocalhostUri(WS, ts.getPort(WS))); + } + + private static Options.Builder wssBuilder(NatsTestServer ts) throws Exception { + return builder() + .server(NatsTestServer.getLocalhostUri(WSS, ts.getPort(WSS))) + .sslContext(SslTestingHelper.createTestSSLContext()); } - private static void standardRequestReply(Options options) throws InterruptedException, IOException { - try (Connection connection = standardConnection(options)) { - Dispatcher dispatcher = connection.createDispatcher(msg -> { - connection.publish(msg.getReplyTo(), (new String(msg.getData()) + ":REPLY").getBytes()); - }); - try { - dispatcher.subscribe("TEST"); - Message response = connection.request("TEST", "REQUEST".getBytes()).join(); - assertEquals("REQUEST:REPLY", new String(response.getData())); - } finally { - dispatcher.drain(Duration.ZERO); + private static void _test(Options.Builder builder) throws InterruptedException { + try (Connection connection = managedConnect(builder.build())) { + Dispatcher dispatcher = connection.createDispatcher( + msg -> connection.publish(msg.getReplyTo(), (new String(msg.getData()) + ":reply").getBytes())); + String subject = random(); + dispatcher.subscribe(subject); + for (int x = 0; x < 10; x++) { + String data = random() + x; + Message response = connection.request(subject, data.getBytes()).join(); + assertEquals(data + ":reply", new String(response.getData())); } } } @Test - public void testTLSRequestReply() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss.conf", false)) { - - java.util.function.Consumer interceptor = req -> { - // Ideally we could validate that this header was sent to NATS server - req.getHeaders().add("X-Ignored", "VALUE"); - }; - - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = Options.builder() - .httpRequestInterceptor(interceptor) - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .maxReconnects(0) - .sslContext(ctx) - .build(); + public void testWs() throws Exception { + runInSharedConfiguredServer("ws.conf", ts -> { + _test(optionsBuilder(ts)); + _test(wsBuilder(ts)); + }); + } - standardRequestReply(options); - } + @Test + public void testWss() throws Exception { + runInSharedConfiguredServer("wss.conf", ts -> { + _test(optionsBuilder(ts)); + _test(wssBuilder(ts)); + }); } @Test - public void testProxyRequestReply() throws Exception { - ExecutorService executor = Executors.newFixedThreadPool(3); - RunProxy proxy = new RunProxy(new InetSocketAddress("localhost", 0), null, executor); - executor.submit(proxy); + public void testWssVerify() throws Exception { + runInSharedConfiguredServer("wssverify.conf", ts -> { + _test(optionsBuilder(ts)); + _test(wssBuilder(ts)); + }); + } - try (NatsTestServer ts = new NatsTestServer("src/test/resources/ws.conf", false)) { - Options options = Options.builder() - .server(getLocalhostUri("ws", ts.getPort("ws"))) - .maxReconnects(0) - .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", proxy.getPort()))) - .build(); - standardRequestReply(options); - } + private static java.util.function.Consumer getInterceptor() { + return req -> { + // Ideally we could validate that this header was sent to NATS server + req.getHeaders().add("X-Ignored", "VALUE"); + }; } @Test - public void testSimpleTLSConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss.conf", false)) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = Options.builder() - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .maxReconnects(0) - .sslContext(ctx) - .build(); - assertCanConnect(options); - } + public void testWsInterceptor() throws Exception { + runInSharedConfiguredServer("ws.conf", + ts -> _test(wsBuilder(ts).httpRequestInterceptor(getInterceptor()))); } @Test - public void testSimpleWSSIPConnection() throws Exception { - //System.setProperty("javax.net.debug", "all"); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss.conf", false)) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = Options.builder(). - server("wss://127.0.0.1:" + ts.getPort("wss")). - maxReconnects(0). - sslContext(ctx). - build(); - assertCanConnect(options); - } + public void testWssInterceptor() throws Exception { + runInSharedConfiguredServer("wss.conf", + ts -> _test(wssBuilder(ts).httpRequestInterceptor(getInterceptor()))); } @Test - public void testVerifiedTLSConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = Options.builder() - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .maxReconnects(0) - .sslContext(ctx) - .build(); - assertCanConnect(options); - } + public void testWssVerifyInterceptor() throws Exception { + runInSharedConfiguredServer("wssverify.conf", + ts -> _test(wssBuilder(ts).httpRequestInterceptor(getInterceptor()))); } @Test - public void testOpenTLSConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss.conf", false)) { - Options options = Options.builder() - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .maxReconnects(0) - .opentls() - .build(); - assertCanConnect(options); - } + public void testWsOpenTLS() throws Exception { + runInSharedConfiguredServer("ws.conf", ts -> _test(wsBuilder(ts).opentls())); } @Test - public void testURIWSSHostConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) { - Options options = Options.builder() - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .sslContext(SslTestingHelper.createTestSSLContext())// override the custom one - .maxReconnects(0) - .build(); - assertCanConnect(options); - } + public void testWssOpenTLS() throws Exception { + runInSharedConfiguredServer("wss.conf", ts -> _test(wssBuilder(ts).opentls())); } @Test - public void testURIWSSIPConnection() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) { - Options options = Options.builder() - .server("wss://127.0.0.1:" + ts.getPort("wss")) - .sslContext(SslTestingHelper.createTestSSLContext()) // override the custom one - .maxReconnects(0) - .build(); - assertCanConnect(options); - } + public void testWssVerifyOpenTLS() throws Exception { + runInSharedConfiguredServer("wssverify.conf", ts -> _test(wssBuilder(ts).opentls())); } @Test - public void testURISchemeWSSConnection() throws Exception { - SSLContext originalDefault = SSLContext.getDefault(); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss.conf", false)) { - SSLContext.setDefault(SslTestingHelper.createTestSSLContext()); - Options options = Options.builder() - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .maxReconnects(0) - .build(); - assertCanConnect(options); - } finally { - SSLContext.setDefault(originalDefault); - } + public void testProxyRequestReply() throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(3); + RunProxy proxy = new RunProxy(new InetSocketAddress("localhost", 0), null, executor); + executor.submit(proxy); + + runInSharedConfiguredServer("ws.conf", ts -> { + Options.Builder builder = wsBuilder(ts) + .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", proxy.getPort()))); + _test(builder); + }); } @Test - public void testURISchemeWSSConnectionEnsureTlsFirstHasNoEffect() throws Exception { - SSLContext originalDefault = SSLContext.getDefault(); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss.conf", false)) { - SSLContext.setDefault(SslTestingHelper.createTestSSLContext()); - Options options = Options.builder() - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .maxReconnects(0) - .tlsFirst() - .build(); - assertCanConnect(options); - } finally { - SSLContext.setDefault(originalDefault); - } + public void testWssTlsFirstIgnored() throws Exception { + runInSharedConfiguredServer("wss.conf", ts -> _test(wssBuilder(ts).tlsFirst())); } @Test - public void testTLSMessageFlow() throws Exception { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - int msgCount = 100; - Options options = Options.builder() - .server(getLocalhostUri("wss", ts.getPort("wss"))) - .maxReconnects(0) - .sslContext(ctx) - .build(); - Connection nc = standardConnection(options); - Dispatcher d = nc.createDispatcher((msg) -> { - nc.publish(msg.getReplyTo(), new byte[16]); - }); - d.subscribe("subject"); - - for (int i=0;i incoming = nc.request("subject", null); - Message msg = incoming.get(500, TimeUnit.MILLISECONDS); - assertNotNull(msg); - assertEquals(16, msg.getData().length); - } - - standardCloseConnection(nc); - } + public void testWssVerifyTlsFirstIgnored() throws Exception { + runInSharedConfiguredServer("wssverify.conf", ts -> _test(wssBuilder(ts).tlsFirst())); } @Test public void testTLSOnReconnect() throws Exception { Connection nc; - ListenerForTesting listener = new ListenerForTesting(); + Listener listener = new Listener(); int port = nextPort(); int wssPort = nextPort(); - NatsServerRunner.Builder builder = NatsServerRunner.builder().configFilePath("src/test/resources/wssverify.conf") + // can't use shared b/c custom ports + NatsServerRunner.Builder builder = configFileBuilder("wssverify.conf") .port(port) - .port("wss", wssPort); + .port(WSS, wssPort); SSLContext ctx = SslTestingHelper.createTestSSLContext(); // Use two server ports to avoid port release timing issues try (NatsTestServer ignored = new NatsTestServer(builder)) { Options options = Options.builder() - .server(getLocalhostUri("wss", wssPort)) + .server(NatsTestServer.getLocalhostUri(WSS, wssPort)) .noRandomize() .maxReconnects(-1) .sslContext(ctx) .connectionListener(listener) + .errorListener(NOOP_EL) .reconnectWait(Duration.ofMillis(10)) .build(); - listener.prepForStatusChange(CONNECTED); + listener.queueConnectionEvent(CONNECTED); nc = Nats.connect(options); assertInstanceOf(SocketDataPort.class, ((NatsConnection) nc).getDataPort(), "Correct data port class"); - listener.waitForStatusChange(1, TimeUnit.SECONDS); - assertEquals(Connection.Status.CONNECTED, nc.getStatus()); + listener.validate(); + assertConnected(nc); } - listener.prepForStatusChange(CONNECTED); + listener.queueConnectionEvent(RECONNECTED); try (NatsTestServer ignored = new NatsTestServer(builder)) { - listener.waitForStatusChange(1, TimeUnit.SECONDS); - assertEquals(Connection.Status.CONNECTED, nc.getStatus()); + listener.validate(); + assertConnected(nc); } } @Test - public void testDisconnectOnUpgrade() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = Options.builder() - .server(ts.getLocalhostUri("wss")) - .maxReconnects(0) - .dataPortType(CloseOnUpgradeAttempt.class.getCanonicalName()) - .sslContext(ctx) - .build(); - Nats.connect(options); - } + public void testDisconnectOnUpgrade() throws Exception { + runInSharedConfiguredServer("wssverify.conf", ts -> { + SSLContext ctx = SslTestingHelper.createTestSSLContext(); + Options options = Options.builder() + .server(ts.getLocalhostUri(WSS)) + .dataPortType(CloseOnUpgradeAttempt.class.getCanonicalName()) + .sslContext(ctx) + .build(); + assertThrows(IOException.class, () -> Nats.connect(options)); }); } @Test - public void testServerSecureClientNotMismatch() throws Exception { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) { - Options options = Options.builder() - .server(getLocalhostUri("ws", ts.getPort("wss"))) - .maxReconnects(0) - .build(); - Nats.connect(options); - } + public void testClientInsecureServerSecureMismatchWss() throws Exception { + runInSharedConfiguredServer("wss.conf", ts -> { + Options options = builder() + .server(NatsTestServer.getLocalhostUri(WS, ts.getPort(WSS))) + .build(); + assertThrows(IOException.class, () -> Nats.connect(options)); }); } @Test - public void testClientSecureServerNotMismatch() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer()) { - SSLContext ctx = SslTestingHelper.createTestSSLContext(); - Options options = Options.builder() - .server(ts.getLocalhostUri("wss")) - .maxReconnects(0) - .sslContext(ctx) - .build(); - Nats.connect(options); - } + public void testClientInsecureServerSecureMismatchWssVerify() throws Exception { + runInSharedConfiguredServer("wssverify.conf", ts -> { + Options options = builder() + .server(NatsTestServer.getLocalhostUri(WS, ts.getPort(WSS))) + .build(); + assertThrows(IOException.class, () -> Nats.connect(options)); }); } @Test - public void testClientServerCertMismatch() { - assertThrows(IOException.class, () -> { - try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) { - SSLContext ctx = SslTestingHelper.createEmptySSLContext(); - Options options = Options.builder() - .server(ts.getLocalhostUri("wss")) - .maxReconnects(0) - .sslContext(ctx) - .build(); - Nats.connect(options); - } + public void testClientSecureServerInsecureMismatch() throws Exception { + runInSharedOwnNc(nc -> { + //noinspection DataFlowIssue + Options options = builder() + .server(nc.getConnectedUrl()) + .sslContext(SslTestingHelper.createTestSSLContext()) + .build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + }); + } + + @Test + public void testClientServerCertMismatchWss() throws Exception { + runInSharedConfiguredServer("wss.conf", ts -> { + SSLContext ctx = SslTestingHelper.createEmptySSLContext(); + Options options = wssBuilder(ts).sslContext(ctx).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); + }); + } + + @Test + public void testClientServerCertMismatchWssVerify() throws Exception { + runInSharedConfiguredServer("wssverify.conf", ts -> { + SSLContext ctx = SslTestingHelper.createEmptySSLContext(); + Options options = wssBuilder(ts).sslContext(ctx).build(); + assertThrows(IOException.class, () -> Nats.connect(options)); }); } } \ No newline at end of file diff --git a/src/test/java/io/nats/compatibility/ClientCompatibilityMain.java b/src/test/java/io/nats/client/other/ClientCompatibilityMain.java similarity index 98% rename from src/test/java/io/nats/compatibility/ClientCompatibilityMain.java rename to src/test/java/io/nats/client/other/ClientCompatibilityMain.java index 0f61669b9..cfdd32798 100644 --- a/src/test/java/io/nats/compatibility/ClientCompatibilityMain.java +++ b/src/test/java/io/nats/client/other/ClientCompatibilityMain.java @@ -11,9 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package io.nats.compatibility; +package io.nats.client.other; import io.nats.client.*; +import io.nats.compatibility.*; import java.io.IOException; import java.util.concurrent.ExecutorService; diff --git a/src/test/java/io/nats/client/FlushBenchmark.java b/src/test/java/io/nats/client/other/FlushBenchmark.java similarity index 94% rename from src/test/java/io/nats/client/FlushBenchmark.java rename to src/test/java/io/nats/client/other/FlushBenchmark.java index ea4bcea8d..16860fcac 100644 --- a/src/test/java/io/nats/client/FlushBenchmark.java +++ b/src/test/java/io/nats/client/other/FlushBenchmark.java @@ -11,10 +11,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package io.nats.client; +package io.nats.client.other; -import java.text.NumberFormat; +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.nats.client.Options; +import java.text.NumberFormat; public class FlushBenchmark { public static void main(String args[]) throws InterruptedException { diff --git a/src/test/java/io/nats/client/impl/MessageProtocolCreationBenchmark.java b/src/test/java/io/nats/client/other/MessageProtocolCreationBenchmark.java similarity index 89% rename from src/test/java/io/nats/client/impl/MessageProtocolCreationBenchmark.java rename to src/test/java/io/nats/client/other/MessageProtocolCreationBenchmark.java index 95c20adc2..1dc1505fd 100644 --- a/src/test/java/io/nats/client/impl/MessageProtocolCreationBenchmark.java +++ b/src/test/java/io/nats/client/other/MessageProtocolCreationBenchmark.java @@ -11,14 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package io.nats.client.impl; +package io.nats.client.other; + +/* +import io.nats.client.impl.NatsMessage; +import io.nats.client.impl.ProtocolMessage; import java.text.NumberFormat; import static io.nats.client.support.NatsConstants.EMPTY_BODY; +*/ +// This class is kept only for history. It can only run if it is moved to the +// io.nats.client.impl package since ProtocolMessage is package scoped public class MessageProtocolCreationBenchmark { - public static void main(String args[]) throws InterruptedException { +/* + public static void main(String[] args) throws InterruptedException { int warmup = 1_000_000; int msgCount = 50_000_000; @@ -64,4 +72,5 @@ public static void main(String args[]) throws InterruptedException { ((double) (end - start)) / ((double) (msgCount)), NumberFormat.getInstance().format(((double)(1_000_000_000L * msgCount))/((double) (end - start)))); } +*/ } \ No newline at end of file diff --git a/src/test/java/io/nats/client/impl/MessageQueueBenchmark.java b/src/test/java/io/nats/client/other/MessageQueueBenchmark.java similarity index 100% rename from src/test/java/io/nats/client/impl/MessageQueueBenchmark.java rename to src/test/java/io/nats/client/other/MessageQueueBenchmark.java diff --git a/src/test/java/io/nats/client/NUIDBenchmarks.java b/src/test/java/io/nats/client/other/NUIDBenchmarks.java similarity index 96% rename from src/test/java/io/nats/client/NUIDBenchmarks.java rename to src/test/java/io/nats/client/other/NUIDBenchmarks.java index 3077c829f..3c65d3524 100644 --- a/src/test/java/io/nats/client/NUIDBenchmarks.java +++ b/src/test/java/io/nats/client/other/NUIDBenchmarks.java @@ -11,7 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package io.nats.client; +package io.nats.client.other; + +import io.nats.client.NUID; import java.text.NumberFormat; diff --git a/src/test/java/io/nats/client/impl/ParseTesting.java b/src/test/java/io/nats/client/other/ParseBenchmark.java similarity index 99% rename from src/test/java/io/nats/client/impl/ParseTesting.java rename to src/test/java/io/nats/client/other/ParseBenchmark.java index f3eab53f2..bdd713650 100644 --- a/src/test/java/io/nats/client/impl/ParseTesting.java +++ b/src/test/java/io/nats/client/other/ParseBenchmark.java @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package io.nats.client.impl; +package io.nats.client.other; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -23,7 +23,7 @@ import static io.nats.client.support.NatsConstants.*; -public class ParseTesting { +public class ParseBenchmark { static String infoJson = "{" + "\"server_id\":\"myserver\"" + "," + "\"version\":\"1.1.1\"" + "," + "\"go\": \"go1.9\"" + "," + "\"host\": \"host\"" + "," + "\"tls_required\": true" + "," + "\"auth_required\":false" + "," + "\"port\": 7777" + "," + "\"max_payload\":100000000000" + "," diff --git a/src/test/java/io/nats/client/PublishBenchmarkWithStats.java b/src/test/java/io/nats/client/other/PublishBenchmarkWithStats.java similarity index 96% rename from src/test/java/io/nats/client/PublishBenchmarkWithStats.java rename to src/test/java/io/nats/client/other/PublishBenchmarkWithStats.java index 949c9f783..8cdaa6ed8 100644 --- a/src/test/java/io/nats/client/PublishBenchmarkWithStats.java +++ b/src/test/java/io/nats/client/other/PublishBenchmarkWithStats.java @@ -11,7 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package io.nats.client; +package io.nats.client.other; + +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.nats.client.Options; import java.text.NumberFormat; import java.time.Duration; diff --git a/src/test/java/io/nats/client/other/ReconnectCheck.java b/src/test/java/io/nats/client/other/ReconnectCheck.java new file mode 100644 index 000000000..f43a51a04 --- /dev/null +++ b/src/test/java/io/nats/client/other/ReconnectCheck.java @@ -0,0 +1,77 @@ +package io.nats.client.other; + +import io.nats.client.*; + +import java.time.Duration; + +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ThreadUtils.sleep; + +/* Program to reproduce #231 */ +public class ReconnectCheck { + private static long received; + private static long published; + + private static long lastReceivedId; + + public static void main(String []args) throws Exception { + try (NatsTestServer ts = new NatsTestServer()) { + Connection natsIn = Nats.connect(buildOptions("IN", ts)); + Connection natsOut = Nats.connect(buildOptions("OUT", ts)); + Dispatcher natsDispatcher = natsIn.createDispatcher(m -> { + long receivedId = Long.parseLong(new String(m.getData())); + + if (receivedId < lastReceivedId) { + System.out.printf("##### Tid: %d, Received stale data: got %d, last received %d%n", Thread.currentThread().getId(), receivedId, lastReceivedId); + } + lastReceivedId = receivedId; + if (received++ % 1_000_000 == 0) { + System.out.printf("Tid: %d, Received %d messages%n", Thread.currentThread().getId(), received); + } + }); + + natsDispatcher.subscribe("foo"); + + long id = 0; + + //noinspection InfiniteLoopStatement + while (true) { + for (int i = 0; i < 100_000; i++) { + natsOut.publish("foo", ("" + id++).getBytes()); + if (published++ % 1_000_000 == 0) { + System.out.printf("Tid: %d, Published %d messages.%n", Thread.currentThread().getId(), published); + } + } + sleep(1); + } + } + } + + private static Options buildOptions(String name, NatsTestServer ts) { + return optionsBuilder(ts) + .connectionName(name) + .reconnectWait(Duration.ofSeconds(1)) + .connectionTimeout(Duration.ofSeconds(5)) + .pingInterval(Duration.ofMillis(100)) + .reconnectBufferSize(-1) // Do not cache any messages when Nats connection is down.// Do not cache any messages when Nats connection is down. + .connectionListener((conn, e) -> + System.out.printf("Tid: %d, %s, NATS: connection event - %s, connected url: %s. servers: %s %n", Thread.currentThread().getId(), name, e, conn.getConnectedUrl(), conn.getServers())) + .errorListener(new ErrorListener() { + @Override + public void slowConsumerDetected(Connection conn, Consumer consumer) { + System.out.printf("Tid: %d, %s, %s: Slow Consumer%n", Thread.currentThread().getId(), name, conn.getConnectedUrl()); + } + + @Override + public void exceptionOccurred(Connection conn, Exception exp) { + System.out.printf("Tid: %d, %s, Nats '%s' exception: %s%n", Thread.currentThread().getId(), name, conn.getConnectedUrl(), exp.toString()); + } + + @Override + public void errorOccurred(Connection conn, String error) { + System.out.printf("Tid: %d, %s, Nats '%s': Error %s%n", Thread.currentThread().getId(), name, conn.getConnectedUrl(), error.toString()); + } + }) + .build(); + } +} diff --git a/src/test/java/io/nats/client/RequestBenchmarkWithStats.java b/src/test/java/io/nats/client/other/RequestBenchmarkWithStats.java similarity index 98% rename from src/test/java/io/nats/client/RequestBenchmarkWithStats.java rename to src/test/java/io/nats/client/other/RequestBenchmarkWithStats.java index 8d2166d32..d986062a4 100644 --- a/src/test/java/io/nats/client/RequestBenchmarkWithStats.java +++ b/src/test/java/io/nats/client/other/RequestBenchmarkWithStats.java @@ -11,7 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package io.nats.client; +package io.nats.client.other; + +import io.nats.client.*; import java.text.NumberFormat; import java.util.ArrayList; @@ -52,7 +54,7 @@ public static void main(String args[]) throws InterruptedException { build(); Connection handlerC = Nats.connect(o); - Dispatcher d = handlerC.createDispatcher((msg) -> { + Dispatcher d = handlerC.createDispatcher(msg -> { try { handlerC.publish(msg.getReplyTo(), msg.getData()); msgsHandled.incrementAndGet(); diff --git a/src/test/java/io/nats/client/support/DateTimeUtilsTests.java b/src/test/java/io/nats/client/support/DateTimeUtilsTests.java index 546ee662d..454d374cd 100644 --- a/src/test/java/io/nats/client/support/DateTimeUtilsTests.java +++ b/src/test/java/io/nats/client/support/DateTimeUtilsTests.java @@ -49,14 +49,11 @@ public void testToRfc3339() { Instant i = Instant.ofEpochSecond(1611186068); ZonedDateTime zdt1 = ZonedDateTime.ofInstant(i, ZoneId.systemDefault()); ZonedDateTime zdt2 = ZonedDateTime.ofInstant(i, DateTimeUtils.ZONE_ID_GMT); - System.out.println(zdt1); - System.out.println(zdt2); assertEquals(zdt1.toEpochSecond(), zdt2.toEpochSecond()); String rfc1 = DateTimeUtils.toRfc3339(zdt1); String rfc2 = DateTimeUtils.toRfc3339(zdt2); assertEquals(rfc1, rfc2); - System.out.println(zdt2.toEpochSecond()); assertEquals("2021-01-20T23:41:08.579594000Z", DateTimeUtils.toRfc3339(DateTimeUtils.parseDateTime("2021-01-20T23:41:08.579594Z"))); assertEquals("2021-02-02T19:18:28.347722551Z", DateTimeUtils.toRfc3339(DateTimeUtils.parseDateTime("2021-02-02T11:18:28.347722551-08:00"))); diff --git a/src/test/java/io/nats/client/support/JsonParsingTests.java b/src/test/java/io/nats/client/support/JsonParsingTests.java index bf68faaf6..80582405a 100644 --- a/src/test/java/io/nats/client/support/JsonParsingTests.java +++ b/src/test/java/io/nats/client/support/JsonParsingTests.java @@ -13,6 +13,7 @@ package io.nats.client.support; +import io.nats.client.utils.TestBase; import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; @@ -41,39 +42,39 @@ public void testStringParsing() { List list = new ArrayList<>(); int x = 0; - addField(key(x++), "b4\\after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4/after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\"after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\tafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\bafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\fafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\nafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\rafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\tafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4" + (char) 0 + "after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4" + (char) 1 + "after", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\\after", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4/after", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\"after", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\tafter", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\\bafter", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\\fafter", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\\nafter", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\\rafter", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4\\tafter", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4" + (char) 0 + "after", oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), "b4" + (char) 1 + "after", oMap, list, encodeds, decodeds); List utfs = dataAsLines("utf8-only-no-ws-test-strings.txt"); for (String u : utfs) { String uu = "b4\b\f\n\r\t" + u + "after"; - addField(key(x++), uu, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), uu, oMap, list, encodeds, decodeds); } - addField(key(x++), PLAIN, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_SPACE, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_PRINTABLE, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_DOT, oMap, list, encodeds, decodeds); - addField(key(x++), STAR_NOT_SEGMENT, oMap, list, encodeds, decodeds); - addField(key(x++), GT_NOT_SEGMENT, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_DASH, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_UNDER, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_DOLLAR, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_LOW, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_127, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_FWD_SLASH, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_BACK_SLASH, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_EQUALS, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_TIC, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), PLAIN, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_SPACE, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_PRINTABLE, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_DOT, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), STAR_NOT_SEGMENT, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), GT_NOT_SEGMENT, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_DASH, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_UNDER, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_DOLLAR, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_LOW, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_127, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_FWD_SLASH, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_BACK_SLASH, oMap, list, encodeds, decodeds); + addField(TestBase.data(x++), HAS_EQUALS, oMap, list, encodeds, decodeds); + addField(TestBase.data(x), HAS_TIC, oMap, list, encodeds, decodeds); for (int i = 0; i < list.size(); i++) { JsonValue vi = list.get(i); diff --git a/src/test/java/io/nats/client/support/JsonUtilsTests.java b/src/test/java/io/nats/client/support/JsonUtilsTests.java index ccc8ad5b7..c53204430 100644 --- a/src/test/java/io/nats/client/support/JsonUtilsTests.java +++ b/src/test/java/io/nats/client/support/JsonUtilsTests.java @@ -186,11 +186,13 @@ public void testAddFields() { addRawJson(sb, "n/a", ""); assertEquals(0, sb.length()); - //noinspection UnnecessaryBoxing - addField(sb, "iminusone", new Integer(-1)); + //noinspection WrapperTypeMayBePrimitive + Integer i = -1; + addField(sb, "iminusone", i); assertEquals(0, sb.length()); - addField(sb, "lminusone", new Long(-1)); + Long l = -1L; + addField(sb, "lminusone", l); assertEquals(0, sb.length()); addFieldWhenGteMinusOne(sb, "lnull", null); @@ -235,15 +237,15 @@ public void testAddFields() { addFieldWhenGtZero(sb, "longnull", (Long) null); assertEquals(87, sb.length()); - //noinspection UnnecessaryBoxing - addFieldWhenGtZero(sb, "intnotgt0", new Integer(0)); + i = 0; + addFieldWhenGtZero(sb, "intnotgt0", i); assertEquals(87, sb.length()); addFieldWhenGtZero(sb, "longnotgt0", 0L); assertEquals(87, sb.length()); - //noinspection UnnecessaryBoxing - addFieldWhenGtZero(sb, "intgt0", new Integer(1)); + i = 1; + addFieldWhenGtZero(sb, "intgt0", i); assertEquals(98, sb.length()); addFieldWhenGtZero(sb, "longgt0", 1L); @@ -481,7 +483,6 @@ public void testMiscCoverage() { addField(sb, "foo64", base64); addField(sb, "zdt", zdt); endJson(sb); - System.out.println(sb); bytes = readBase64(sb.toString(), string_pattern("foo64")); assertArrayEquals(byte64, bytes); diff --git a/src/test/java/io/nats/client/support/JwtUtilsTests.java b/src/test/java/io/nats/client/support/JwtUtilsTests.java index 32ca9114d..faf56a6ff 100644 --- a/src/test/java/io/nats/client/support/JwtUtilsTests.java +++ b/src/test/java/io/nats/client/support/JwtUtilsTests.java @@ -21,7 +21,7 @@ import java.util.List; import static io.nats.client.support.JwtUtils.*; -import static io.nats.client.utils.TestBase.sleep; +import static io.nats.client.utils.ThreadUtils.sleep; import static org.junit.jupiter.api.Assertions.*; public class JwtUtilsTests { @@ -169,12 +169,12 @@ public void issueUserJWTSuccessAllArgs() throws Exception { public void issueUserJWTSuccessCustom() throws Exception { UserClaim userClaim = new UserClaim("ACXZRALIL22WRETDRXYKOYDB7XC3E7MBSVUSUMFACO6OM5VPRNFMOOO6") .pub(new Permission() - .allow(new String[] {"pub-allow-subject"}) - .deny(new String[] {"pub-deny-subject"})) + .allow("pub-allow-subject") + .deny("pub-deny-subject")) .sub(new Permission() - .allow(new String[] {"sub-allow-subject"}) - .deny(new String[] {"sub-deny-subject"})) - .tags(new String[]{"tag1", "tag\\two"}); + .allow("sub-allow-subject") + .deny("sub-deny-subject")) + .tags("tag1", "tag\\two"); String jwt = issueUserJWT(SIGNING_KEY, new String(USER_KEY.getPublicKey()), "custom", null, 1633043378, userClaim); String claimBody = getClaimBody(jwt); diff --git a/src/test/java/io/nats/client/support/Listener.java b/src/test/java/io/nats/client/support/Listener.java new file mode 100644 index 000000000..7dddb3ecd --- /dev/null +++ b/src/test/java/io/nats/client/support/Listener.java @@ -0,0 +1,399 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.support; + +import io.nats.client.*; +import org.junit.jupiter.api.Assertions; + +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Predicate; + +@SuppressWarnings({"CallToPrintStackTrace", "RedundantMethodOverride"}) +public class Listener implements ErrorListener, ConnectionListener { + public static final int SHORT_VALIDATE_TIMEOUT = 2000; + public static final int DEFAULT_VALIDATE_TIMEOUT = 5000; + public static final int MEDIUM_VALIDATE_TIMEOUT = 7500; + public static final int LONG_VALIDATE_TIMEOUT = 12500; + public static final int VERY_LONG_VALIDATE_TIMEOUT = 20000; + + private final boolean printExceptions; + private final boolean verbose; + + private final List futures; + private final List discardedMessages; + private final Map connectionEventCounts; + private Connection lastConnectionEventConnection; + private int exceptionCount; + private int heartbeatAlarmCount; + private int flowControlCount; + private int pullStatusWarningsCount; + private int socketWriteTimeoutCount; + + public Listener() { + this(false, false); + } + + public Listener(boolean verbose) { + this(false, verbose); + } + + public Listener(boolean printExceptions, boolean verbose) { + this.printExceptions = printExceptions; + this.verbose = verbose; + futures = new ArrayList<>(); + discardedMessages = new ArrayList<>(); + connectionEventCounts = new HashMap<>(); + } + + public void reset() { + for (ListenerFuture f : futures) { + f.cancel(true); + } + futures.clear(); + discardedMessages.clear(); + connectionEventCounts.clear(); + lastConnectionEventConnection = null; + exceptionCount = 0; + heartbeatAlarmCount = 0; + flowControlCount = 0; + pullStatusWarningsCount = 0; + socketWriteTimeoutCount = 0; + } + + // ---------------------------------------------------------------------------------------------------- + // Validate + // ---------------------------------------------------------------------------------------------------- + public void validate() { + _validate(futures.get(0)); + } + + public void validateAll() { + List copy = new ArrayList<>(futures); + for (ListenerFuture future : copy) { + _validate(future); + } + } + + private void _validate(ListenerFuture f) { + try { + f.get(f.validateTimeout, TimeUnit.MILLISECONDS); + // future was completed, it and all the rest can be cancelled and removed from tracking + futures.remove(f); + } + catch (TimeoutException | ExecutionException | InterruptedException e) { + futures.remove(f); // removed from tracking + f.cancel(true); + Assertions.fail("'Validate Received' Failed " + f.getDetails(), e); + } + } + + public void validateForAny() { + List futuresToTry = new ArrayList<>(futures); + int len = futuresToTry.size(); + int lastIx = len - 1; + for (int ix = 0; ix < len; ix++) { + ListenerFuture f = futuresToTry.get(ix); + try { + f.get(f.validateTimeout, TimeUnit.MILLISECONDS); + // future was completed, it and all the rest can be cancelled and removed from tracking + while (ix < len) { + f = futuresToTry.get(ix++); + futures.remove(f); + f.cancel(true); + } + return; + } + catch (TimeoutException | ExecutionException | InterruptedException e) { + futures.remove(f); // removed from tracking + f.cancel(true); + if (ix == lastIx) { + Assertions.fail("'Validate Received' Failed " + f.getDetails(), e); + } + } + } + } + + public void validateNotReceived() { + ListenerFuture f = futures.get(0); + futures.remove(f); // removed from tracking + try { + f.get(f.validateTimeout, TimeUnit.MILLISECONDS); + Assertions.fail("'Validate Not Received' Failed " + f.getDetails()); + } + catch (TimeoutException ignore) { + // this is what is supposed to happen! + } + catch (InterruptedException e) { + f.cancel(true); + } + catch (ExecutionException e) { + Assertions.fail("'Validate Not Received' Failed " + f.getDetails(), e); + } + } + + // ---------------------------------------------------------------------------------------------------- + // Queue + // ---------------------------------------------------------------------------------------------------- + private void queue(String label, ListenerFuture f) { + if (verbose) { + report("Queue For " + label, f.getDetails()); + } + futures.add(f); + } + + public void queueConnectionEvent(Events type) { + queue("Event", new ListenerFuture(type, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueConnectionEvent(Events type, int validateTimeout) { + queue("Event", new ListenerFuture(type, validateTimeout)); + } + + public void queueException(Class exceptionClass) { + queue("Exception", new ListenerFuture(exceptionClass, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueException(Class exceptionClass, int validateTimeout) { + queue("Exception", new ListenerFuture(exceptionClass, validateTimeout)); + } + + public void queueException(Class exceptionClass, String contains) { + queue("Exception", new ListenerFuture(exceptionClass, contains, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueException(Class exceptionClass, String contains, int validateTimeout) { + queue("Exception", new ListenerFuture(exceptionClass, contains, validateTimeout)); + } + + public void queueError(String errorText) { + queue("Error", new ListenerFuture(errorText, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueError(String errorText, int validateTimeout) { + queue("Error", new ListenerFuture(errorText, validateTimeout)); + } + + public void queueStatus(ListenerStatusType type, int statusCode) { + queue("Status", new ListenerFuture(type, statusCode, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueStatus(ListenerStatusType type, int statusCode, int validateTimeout) { + queue("Status", new ListenerFuture(type, statusCode, validateTimeout)); + } + + public void queueFlowControl(String fcSubject, FlowControlSource fcSource) { + queue("FlowControl", new ListenerFuture(fcSubject, fcSource, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueFlowControl(String fcSubject, FlowControlSource fcSource, int validateTimeout) { + queue("FlowControl", new ListenerFuture(fcSubject, fcSource, validateTimeout)); + } + + public void queueHeartbeat() { + queue("Heartbeat", new ListenerFuture(true, false, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueHeartbeat(int validateTimeout) { + queue("Heartbeat", new ListenerFuture(true, false, validateTimeout)); + } + + public void queueSocketWriteTimeout() { + queue("SocketWriteTimeout", new ListenerFuture(false, true, DEFAULT_VALIDATE_TIMEOUT)); + } + + public void queueSocketWriteTimeout(int validateTimeout) { + queue("SocketWriteTimeout", new ListenerFuture(false, true, validateTimeout)); + } + + // ---------------------------------------------------------------------------------------------------- + // Getters + // ---------------------------------------------------------------------------------------------------- + public List getDiscardedMessages() { + return discardedMessages; + } + + public int getConnectionEventCount(ConnectionListener.Events event) { + return connectionEventCounts.getOrDefault(event, 0); + } + + public Connection getLastConnectionEventConnection() { + return lastConnectionEventConnection; + } + + public int getExceptionCount() { + return exceptionCount; + } + + public int getHeartbeatAlarmCount() { + return heartbeatAlarmCount; + } + + public int getFlowControlCount() { + return flowControlCount; + } + + public int getPullStatusWarningsCount() { + return pullStatusWarningsCount; + } + + public int getSocketWriteTimeoutCount() { return socketWriteTimeoutCount; } + + // ---------------------------------------------------------------------------------------------------- + // Connection Listener + // ---------------------------------------------------------------------------------------------------- + @Override + public void connectionEvent(Connection conn, Events event) { + connectionEvent(conn, event, 0L, null); + } + + @Override + public void connectionEvent(Connection conn, Events event, Long time, String uriDetails) { + if (verbose) { + report("connectionEvent", event); + } + connectionEventCounts.merge(event, 1, Integer::sum); + lastConnectionEventConnection = conn; + tryToComplete(futures, f -> event.equals(f.eventType)); + } + + // ---------------------------------------------------------------------------------------------------- + // Error Listener + // ---------------------------------------------------------------------------------------------------- + @Override + public void errorOccurred(Connection conn, String error) { + if (verbose) { + report("errorOccurred", error); + } + tryToComplete(futures, f -> error.equals(f.error)); + } + + @Override + public void exceptionOccurred(Connection conn, Exception exp) { + exceptionCount++; + if (printExceptions) { + System.err.print("exceptionOccurred:"); + exp.printStackTrace(); + } + else if (verbose) { + report("exceptionOccurred", exp.getClass() + " --> " + exp.getMessage()); + } + tryToComplete(futures, f -> { + Throwable t = exp; + while (t != null) { + if (t.getClass().equals(f.exceptionClass)) { + if (f.exContains == null || exp.getMessage().contains(f.exContains)) { + f.complete(null); + f.receivedException = exp; + return true; + } + } + t = t.getCause(); + } + return false; + }); + } + + @Override + public void slowConsumerDetected(Connection conn, Consumer consumer) { + // see SlowConsumerTests.SlowConsumerListener + } + + @Override + public void messageDiscarded(Connection conn, Message msg) { + discardedMessages.add(msg); + } + + @Override + public void heartbeatAlarm(Connection conn, JetStreamSubscription sub, long lastStreamSequence, long lastConsumerSequence) { + if (verbose) { + report("Heartbeat Alarm", lastStreamSequence + " " + lastConsumerSequence); + } + heartbeatAlarmCount++; + tryToComplete(futures, f -> f.forHeartbeat); + } + + private void statusReceived(ListenerStatusType type, Status status) { + if (verbose) { + report("Status Received " + type.name(), status); + } + tryToComplete(futures, f -> type.equals(f.lbfStatusType) && f.statusCode == status.getCode()); + } + + @Override + public void unhandledStatus(Connection conn, JetStreamSubscription sub, Status status) { + statusReceived(ListenerStatusType.Unhandled, status); + } + + @Override + public void pullStatusWarning(Connection conn, JetStreamSubscription sub, Status status) { + statusReceived(ListenerStatusType.PullWarning, status); + pullStatusWarningsCount++; + } + + @Override + public void pullStatusError(Connection conn, JetStreamSubscription sub, Status status) { + statusReceived(ListenerStatusType.PullError, status); + } + + @Override + public void flowControlProcessed(Connection conn, JetStreamSubscription sub, String subject, FlowControlSource source) { + if (verbose) { + report("flowControlProcessed", subject + " " + source); + } + flowControlCount++; + tryToComplete(futures, f -> subject.equals(f.fcSubject) && f.fcSource == source); + } + + @Override + public void socketWriteTimeout(Connection conn) { + if (verbose) { + report("Socket Write Timeout"); + } + socketWriteTimeoutCount++; + tryToComplete(futures, f -> f.forSocketWriteTimeout); + } + + // ---------------------------------------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------------------------------------- + private void tryToComplete(List futures, Predicate predicate) { + for (ListenerFuture f : futures) { + if (predicate.test(f)) { + f.complete(null); + return; + } + } + } + public static final DateTimeFormatter SIMPLE_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + + public static String simpleTime() { + return SIMPLE_TIME_FORMATTER.format(DateTimeUtils.gmtNow()); + } + + @SuppressWarnings("SameParameterValue") + private void report(String func) { + System.out.println("[" + simpleTime() + " " + func + "]"); + } + + private void report(String func, Object message) { + System.out.println("[" + simpleTime() + " " + func + "] " + message); + } +} diff --git a/src/test/java/io/nats/client/support/ListenerFuture.java b/src/test/java/io/nats/client/support/ListenerFuture.java new file mode 100644 index 000000000..b0766e706 --- /dev/null +++ b/src/test/java/io/nats/client/support/ListenerFuture.java @@ -0,0 +1,111 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.support; + +import io.nats.client.ConnectionListener; +import io.nats.client.ErrorListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +// ---------------------------------------------------------------------------------------------------- +// Prep +// ---------------------------------------------------------------------------------------------------- +public class ListenerFuture extends CompletableFuture { + public ConnectionListener.Events eventType; + public String error; + public Class exceptionClass; + public String exContains; + public ListenerStatusType lbfStatusType; + public int statusCode = -1; + public String fcSubject; + public ErrorListener.FlowControlSource fcSource; + public boolean forHeartbeat; + public boolean forSocketWriteTimeout; + + public Throwable receivedException; + + public int validateTimeout; + + ListenerFuture(ConnectionListener.Events type, int validateTimeout) { + this.eventType = type; + this.validateTimeout = validateTimeout; + } + + ListenerFuture(Class exceptionClass, int validateTimeout) { + this.exceptionClass = exceptionClass; + this.validateTimeout = validateTimeout; + } + + ListenerFuture(Class exceptionClass, String contains, int validateTimeout) { + this.exceptionClass = exceptionClass; + this.exContains = contains; + this.validateTimeout = validateTimeout; + } + + ListenerFuture(String errorText, int validateTimeout) { + error = errorText; + this.validateTimeout = validateTimeout; + } + + ListenerFuture(ListenerStatusType type, int statusCode, int validateTimeout) { + lbfStatusType = type; + this.statusCode = statusCode; + this.validateTimeout = validateTimeout; + } + + ListenerFuture(String fcSubject, ErrorListener.FlowControlSource fcSource, int validateTimeout) { + this.fcSubject = fcSubject; + this.fcSource = fcSource; + this.validateTimeout = validateTimeout; + } + + public ListenerFuture(boolean forHeartbeat, boolean forSocketWriteTimeout, int validateTimeout) { + this.forHeartbeat = forHeartbeat; + this.forSocketWriteTimeout = forSocketWriteTimeout; + this.validateTimeout = validateTimeout; + } + + @Override + public String toString() { + return "ListenerFuture{" + getDetails() + "}"; + } + + public List getDetails() { + List details = new ArrayList<>(); + if (eventType != null) { + details.add(eventType.toString()); + } + if (error != null) { + details.add(error); + } + if (exceptionClass != null) { + details.add(exceptionClass.toString()); + } + if (lbfStatusType != null) { + details.add(lbfStatusType.toString()); + } + if (statusCode != -1) { + details.add(Integer.toString(statusCode)); + } + if (fcSubject != null) { + details.add(fcSubject); + } + if (fcSource != null) { + details.add(fcSource.toString()); + } + return details; + } +} diff --git a/src/test/java/io/nats/client/support/ListenerStatusType.java b/src/test/java/io/nats/client/support/ListenerStatusType.java new file mode 100644 index 000000000..373fb5ceb --- /dev/null +++ b/src/test/java/io/nats/client/support/ListenerStatusType.java @@ -0,0 +1,18 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.support; + +public enum ListenerStatusType { + Unhandled, PullWarning, PullError, None +} diff --git a/src/test/java/io/nats/client/support/ScheduledTaskTests.java b/src/test/java/io/nats/client/support/ScheduledTaskTests.java index 093619df8..16e1a62ec 100644 --- a/src/test/java/io/nats/client/support/ScheduledTaskTests.java +++ b/src/test/java/io/nats/client/support/ScheduledTaskTests.java @@ -1,16 +1,16 @@ package io.nats.client.support; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import static io.nats.client.utils.TestBase.variant; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class ScheduledTaskTests { +public class ScheduledTaskTests extends TestBase { @Test public void testScheduledTask() throws InterruptedException { @@ -21,32 +21,32 @@ public void testScheduledTask() throws InterruptedException { AtomicInteger counter400 = new AtomicInteger(); SttRunnable sttr400 = new SttRunnable(100, counter400); ScheduledTask task400 = new ScheduledTask(stpe, 0, 400, TimeUnit.MILLISECONDS, sttr400); - validateTaskPeriods(task400, 0, 400); + validateTaskPeriods(task400, 400); AtomicInteger counter200 = new AtomicInteger(); SttRunnable sttr200 = new SttRunnable(300, counter200); ScheduledTask task200 = new ScheduledTask(stpe, 0, 200, TimeUnit.MILLISECONDS, sttr200); - validateTaskPeriods(task200, 0, 200); + validateTaskPeriods(task200, 200); AtomicInteger counter100 = new AtomicInteger(); SttRunnable sttr100 = new SttRunnable(400, counter100); - String id = "100-" + variant(); + String id = "100-" + random(); ScheduledTask task100 = new ScheduledTask(id, stpe, 0, 100, TimeUnit.MILLISECONDS, sttr100); - validateTaskPeriods(task100, 0, 100); + validateTaskPeriods(task100, 100); assertEquals(id, task100.getId()); assertTrue(task100.toString().contains(id)); assertTrue(task100.toString().contains("live")); assertTrue(task400.toString().contains("!done")); - validateState(task400, sttr400, false, false, null); - validateState(task200, sttr200, false, false, null); - validateState(task100, sttr100, false, false, null); + validateState(task400, false, false, null); + validateState(task200, false, false, null); + validateState(task100, false, false, null); Thread.sleep(1600); // 3 x 500 = 1500, give a buffer to ensure three runs - validateState(task400, sttr400, false, false, null); - validateState(task200, sttr200, false, false, null); - validateState(task100, sttr100, false, false, null); + validateState(task400, false, false, null); + validateState(task200, false, false, null); + validateState(task100, false, false, null); task400.shutdown(); task200.shutdown(); @@ -57,9 +57,9 @@ public void testScheduledTask() throws InterruptedException { assertTrue(task200.isDone()); Thread.sleep(500); // give one full cycle to make sure it's all done - validateState(task400, sttr400, true, true, false); - validateState(task200, sttr200, true, true, false); - validateState(task100, sttr100, true, true, false); + validateState(task400, true, true, false); + validateState(task200, true, true, false); + validateState(task100, true, true, false); assertTrue(counter400.get() >= 3); assertTrue(counter200.get() >= 3); @@ -73,13 +73,12 @@ public void testScheduledTask() throws InterruptedException { assertTrue(task400.toString().contains("/done")); } - @SuppressWarnings("SameParameterValue") - private static void validateTaskPeriods(ScheduledTask task, long expectedDelay, long expectedPeriod) { - assertEquals(TimeUnit.MILLISECONDS.toNanos(expectedDelay), task.getInitialDelayNanos()); + private static void validateTaskPeriods(ScheduledTask task, long expectedPeriod) { + assertEquals(TimeUnit.MILLISECONDS.toNanos(0), task.getInitialDelayNanos()); assertEquals(TimeUnit.MILLISECONDS.toNanos(expectedPeriod), task.getPeriodNanos()); } - static void validateState(ScheduledTask task, Runnable taskRunnable, boolean shutdown, boolean done, Boolean executing) { + static void validateState(ScheduledTask task, boolean shutdown, boolean done, Boolean executing) { assertEquals(shutdown, task.isShutdown()); assertEquals(done, task.isDone()); if (executing != null) { diff --git a/src/test/java/io/nats/client/support/ValidatorTests.java b/src/test/java/io/nats/client/support/ValidatorTests.java index ad4589d2a..05f8adf4d 100644 --- a/src/test/java/io/nats/client/support/ValidatorTests.java +++ b/src/test/java/io/nats/client/support/ValidatorTests.java @@ -390,13 +390,16 @@ public void testValidateMustMatchIfBothSupplied() { @Test public void testValidateRequired() { required("required", "label"); + //noinspection deprecation required("required1", "required2", "label"); required(new Object(), "label"); required(Collections.singletonList("list"), "label"); required(Collections.singletonMap("key", "value"), "label"); assertThrows(IllegalArgumentException.class, () -> required((String)null, "label")); + //noinspection deprecation assertThrows(IllegalArgumentException.class, () -> required("no-second", null, "label")); + //noinspection deprecation assertThrows(IllegalArgumentException.class, () -> required(null, "no-first", "label")); assertThrows(IllegalArgumentException.class, () -> required(EMPTY, "label")); assertThrows(IllegalArgumentException.class, () -> required((Object)null, "label")); @@ -658,50 +661,49 @@ public void testListsAreEquivalent() { List l2 = Arrays.asList("two", "one"); List l3 = Arrays.asList("one", "not"); List l4 = Collections.singletonList("three"); - List l5 = null; - List l6 = new ArrayList<>(); + List l5 = new ArrayList<>(); assertTrue(listsAreEquivalent(l1, l1)); assertTrue(listsAreEquivalent(l1, l2)); assertFalse(listsAreEquivalent(l1, l3)); assertFalse(listsAreEquivalent(l1, l4)); + assertFalse(listsAreEquivalent(l1, null)); assertFalse(listsAreEquivalent(l1, l5)); - assertFalse(listsAreEquivalent(l1, l6)); assertTrue(listsAreEquivalent(l2, l1)); assertTrue(listsAreEquivalent(l2, l2)); assertFalse(listsAreEquivalent(l2, l3)); assertFalse(listsAreEquivalent(l2, l4)); + assertFalse(listsAreEquivalent(l2, null)); assertFalse(listsAreEquivalent(l2, l5)); - assertFalse(listsAreEquivalent(l2, l6)); assertFalse(listsAreEquivalent(l3, l1)); assertFalse(listsAreEquivalent(l3, l2)); assertTrue(listsAreEquivalent(l3, l3)); assertFalse(listsAreEquivalent(l3, l4)); + assertFalse(listsAreEquivalent(l3, null)); assertFalse(listsAreEquivalent(l3, l5)); - assertFalse(listsAreEquivalent(l3, l6)); assertFalse(listsAreEquivalent(l4, l1)); assertFalse(listsAreEquivalent(l4, l2)); assertFalse(listsAreEquivalent(l4, l3)); assertTrue(listsAreEquivalent(l4, l4)); + assertFalse(listsAreEquivalent(l4, null)); assertFalse(listsAreEquivalent(l4, l5)); - assertFalse(listsAreEquivalent(l4, l6)); + + assertFalse(listsAreEquivalent(null, l1)); + assertFalse(listsAreEquivalent(null, l2)); + assertFalse(listsAreEquivalent(null, l3)); + assertFalse(listsAreEquivalent(null, l4)); + assertTrue(listsAreEquivalent(null, null)); + assertTrue(listsAreEquivalent(null, l5)); assertFalse(listsAreEquivalent(l5, l1)); assertFalse(listsAreEquivalent(l5, l2)); assertFalse(listsAreEquivalent(l5, l3)); assertFalse(listsAreEquivalent(l5, l4)); + assertTrue(listsAreEquivalent(l5, null)); assertTrue(listsAreEquivalent(l5, l5)); - assertTrue(listsAreEquivalent(l5, l6)); - - assertFalse(listsAreEquivalent(l6, l1)); - assertFalse(listsAreEquivalent(l6, l2)); - assertFalse(listsAreEquivalent(l6, l3)); - assertFalse(listsAreEquivalent(l6, l4)); - assertTrue(listsAreEquivalent(l6, l5)); - assertTrue(listsAreEquivalent(l6, l6)); } @Test @@ -725,64 +727,62 @@ public void testMapsAreEqual() { Map m5 = new HashMap<>(); m5.put("five", "5"); - Map m6 = null; - - Map m7 = new HashMap<>(); + Map m6 = new HashMap<>(); assertTrue(mapsAreEquivalent(m1, m1)); assertTrue(mapsAreEquivalent(m1, m2)); assertFalse(mapsAreEquivalent(m1, m3)); assertFalse(mapsAreEquivalent(m1, m4)); assertFalse(mapsAreEquivalent(m1, m5)); + assertFalse(mapsAreEquivalent(m1, null)); assertFalse(mapsAreEquivalent(m1, m6)); - assertFalse(mapsAreEquivalent(m1, m7)); assertTrue(mapsAreEquivalent(m2, m1)); assertTrue(mapsAreEquivalent(m2, m2)); assertFalse(mapsAreEquivalent(m2, m3)); assertFalse(mapsAreEquivalent(m2, m4)); assertFalse(mapsAreEquivalent(m2, m5)); + assertFalse(mapsAreEquivalent(m2, null)); assertFalse(mapsAreEquivalent(m2, m6)); - assertFalse(mapsAreEquivalent(m2, m7)); assertFalse(mapsAreEquivalent(m3, m1)); assertFalse(mapsAreEquivalent(m3, m2)); assertTrue(mapsAreEquivalent(m3, m3)); assertFalse(mapsAreEquivalent(m3, m4)); assertFalse(mapsAreEquivalent(m3, m5)); + assertFalse(mapsAreEquivalent(m3, null)); assertFalse(mapsAreEquivalent(m3, m6)); - assertFalse(mapsAreEquivalent(m3, m7)); assertFalse(mapsAreEquivalent(m4, m1)); assertFalse(mapsAreEquivalent(m4, m2)); assertFalse(mapsAreEquivalent(m4, m3)); assertTrue(mapsAreEquivalent(m4, m4)); assertFalse(mapsAreEquivalent(m4, m5)); + assertFalse(mapsAreEquivalent(m4, null)); assertFalse(mapsAreEquivalent(m4, m6)); - assertFalse(mapsAreEquivalent(m4, m7)); assertFalse(mapsAreEquivalent(m5, m1)); assertFalse(mapsAreEquivalent(m5, m2)); assertFalse(mapsAreEquivalent(m5, m3)); assertFalse(mapsAreEquivalent(m5, m4)); assertTrue(mapsAreEquivalent(m5, m5)); + assertFalse(mapsAreEquivalent(m5, null)); assertFalse(mapsAreEquivalent(m5, m6)); - assertFalse(mapsAreEquivalent(m5, m7)); + + assertFalse(mapsAreEquivalent(null, m1)); + assertFalse(mapsAreEquivalent(null, m2)); + assertFalse(mapsAreEquivalent(null, m3)); + assertFalse(mapsAreEquivalent(null, m4)); + assertFalse(mapsAreEquivalent(null, m5)); + assertTrue(mapsAreEquivalent(null, null)); + assertTrue(mapsAreEquivalent(null, m6)); assertFalse(mapsAreEquivalent(m6, m1)); assertFalse(mapsAreEquivalent(m6, m2)); assertFalse(mapsAreEquivalent(m6, m3)); - assertFalse(mapsAreEquivalent(m6, m4)); assertFalse(mapsAreEquivalent(m6, m5)); + assertTrue(mapsAreEquivalent(m6, null)); assertTrue(mapsAreEquivalent(m6, m6)); - assertTrue(mapsAreEquivalent(m6, m7)); - - assertFalse(mapsAreEquivalent(m7, m1)); - assertFalse(mapsAreEquivalent(m7, m2)); - assertFalse(mapsAreEquivalent(m7, m3)); - assertFalse(mapsAreEquivalent(m7, m5)); - assertTrue(mapsAreEquivalent(m7, m6)); - assertTrue(mapsAreEquivalent(m7, m7)); } @Test diff --git a/src/test/java/io/nats/client/support/WebsocketSupportClassesTests.java b/src/test/java/io/nats/client/support/WebsocketSupportClassesTests.java index d3ee16566..675995dde 100644 --- a/src/test/java/io/nats/client/support/WebsocketSupportClassesTests.java +++ b/src/test/java/io/nats/client/support/WebsocketSupportClassesTests.java @@ -13,7 +13,7 @@ package io.nats.client.support; -import io.nats.client.NatsTestServer; +import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; import java.io.*; @@ -32,25 +32,24 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.*; -public class WebsocketSupportClassesTests { - private int[] testSizes = new int[] { 1, 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1432, // WebsocketOutputStream - // buffer threshold, - // will fragment after - // this. - 1433, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144 }; +public class WebsocketSupportClassesTests extends TestBase { + private final int[] testSizes = new int[] { 1, 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1432, + // 1432 is WebsocketOutputStream buffer threshold, will fragment after this. + 1433, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144 }; @Test public void testInputStreamMaskedBinaryFin() throws IOException { - for (int i = 0; i < testSizes.length; i++) { - byte[] fuzz = getFuzz(testSizes[i]); + for (int testSize : testSizes) { + byte[] fuzz = getFuzz(testSize); int maskingKey = new SecureRandom().nextInt(); InputStream in = websocketInputStream( - new WebsocketFrameHeader().withMask(maskingKey).withOp(OpCode.BINARY, true), fuzz); + new WebsocketFrameHeader().withMask(maskingKey).withOp(OpCode.BINARY, true), fuzz); WebsocketInputStream win = new WebsocketInputStream(in); - byte[] got = new byte[testSizes[i]]; + byte[] got = new byte[testSize]; if (fuzz.length == 1) { got[0] = (byte) (win.read() & 0xFF); - } else { + } + else { assertEquals(got.length, win.read(got)); } assertArrayEquals(fuzz, got); @@ -60,12 +59,12 @@ public void testInputStreamMaskedBinaryFin() throws IOException { @Test public void testInputStreamUnMaskedBinaryFin() throws IOException { - for (int i = 0; i < testSizes.length; i++) { - byte[] fuzz = getFuzz(testSizes[i]); + for (int testSize : testSizes) { + byte[] fuzz = getFuzz(testSize); InputStream in = websocketInputStream(new WebsocketFrameHeader().withNoMask().withOp(OpCode.BINARY, true), - fuzz); + fuzz); WebsocketInputStream win = new WebsocketInputStream(in); - byte[] got = new byte[testSizes[i]]; + byte[] got = new byte[testSize]; assertEquals(got.length, win.read(got)); assertArrayEquals(fuzz, got); win.close(); @@ -74,13 +73,14 @@ public void testInputStreamUnMaskedBinaryFin() throws IOException { @Test public void testOutputStreamMaskedBinaryFin() throws IOException { - for (int i = 0; i < testSizes.length; i++) { - byte[] fuzz = getFuzz(testSizes[i]); + for (int testSize : testSizes) { + byte[] fuzz = getFuzz(testSize); ByteArrayOutputStream out = new ByteArrayOutputStream(); WebsocketOutputStream wout = new WebsocketOutputStream(out, true); if (1 == fuzz.length) { wout.write(fuzz[0]); - } else { + } + else { // NOTE: this API will modify fuzz, so we need to take a copy: wout.write(Arrays.copyOf(fuzz, fuzz.length)); } @@ -106,8 +106,8 @@ public void testOutputStreamMaskedBinaryFin() throws IOException { @Test public void testOutputStreamUnMaskedBinaryFin() throws IOException { - for (int i = 0; i < testSizes.length; i++) { - byte[] fuzz = getFuzz(testSizes[i]); + for (int testSize : testSizes) { + byte[] fuzz = getFuzz(testSize); ByteArrayOutputStream out = new ByteArrayOutputStream(); WebsocketOutputStream wout = new WebsocketOutputStream(out, false); wout.write(fuzz); @@ -158,7 +158,7 @@ public void testInputCoverage() throws IOException { } @Test - public void testFrameHeaderCoverage() throws IOException { + public void testFrameHeaderCoverage() { assertEquals(OpCode.CONTINUATION, OpCode.of(0)); assertEquals(OpCode.TEXT, OpCode.of(1)); assertEquals(OpCode.BINARY, OpCode.of(2)); @@ -213,7 +213,7 @@ public void testHandshakeFailures() throws Exception { OutputStreamWriter writer = new OutputStreamWriter(out, UTF_8); writer.append("HTTP/1.1 101 Switching Protocols\r\n"); for (int i = 0; i < 1000; i++) { - writer.append("a-" + i + ": Test\r\n"); + writer.append("a-").append(String.valueOf(i)).append(": Test\r\n"); } writer.close(); }, "Exceeded max HTTP headers"); @@ -259,7 +259,7 @@ public void testHandshakeFailures() throws Exception { @FunctionalInterface interface OutputStreamWrite { - public void write(OutputStream out) throws IOException; + void write(OutputStream out) throws IOException; } private void testWithWriter(OutputStreamWrite writer, String msgStartsWith) throws Exception { @@ -271,9 +271,10 @@ private void testWithWriter(OutputStreamWrite writer, String msgStartsWith) thro Future readFuture = executor.submit(() -> { byte[] buffer = new byte[1024]; try { - while (in.read(buffer) >= 0) { - } - } catch (SocketException ex) { + //noinspection StatementWithEmptyBody + while (in.read(buffer) >= 0) {} + } + catch (SocketException ex) { // Expect this failure: if (!"Connection reset".equals(ex.getMessage()) && !"Socket closed".equals(ex.getMessage())) { throw ex; @@ -321,8 +322,8 @@ private void testWithWriter(OutputStreamWrite writer, String msgStartsWith) thro @Test public void testWebSocketCoverage() throws Exception { AtomicReference lastMethod = new AtomicReference<>(); - try (NatsTestServer ts = new NatsTestServer("src/test/resources/ws.conf", false)) { - try (Socket tcpSocket = new Socket("localhost", ts.getPort("ws"))) { + runInConfiguredServer("ws.conf", ts -> { + try (Socket tcpSocket = new Socket("localhost", ts.getPort(WS))) { WebSocket webSocket = new WebSocket(new Socket() { @Override public InputStream getInputStream() throws IOException { @@ -537,7 +538,7 @@ public void setPerformancePreferences(int connectionTime, int latency, int bandw assertThrows(UnsupportedOperationException.class, () -> webSocket.connect(null)); assertThrows(UnsupportedOperationException.class, () -> webSocket.connect(null, 0)); assertThrows(UnsupportedOperationException.class, () -> webSocket.bind(null)); - assertThrows(UnsupportedOperationException.class, () -> webSocket.getChannel()); + assertThrows(UnsupportedOperationException.class, webSocket::getChannel); // Delegated methods: webSocket.getInetAddress(); @@ -639,7 +640,7 @@ public void setPerformancePreferences(int connectionTime, int latency, int bandw webSocket.setPerformancePreferences(1, 1, 1); assertEquals("setPerformancePreferences", lastMethod.get()); } - } + }); } /** diff --git a/src/test/java/io/nats/client/support/ssl/SslTestingHelper.java b/src/test/java/io/nats/client/support/ssl/SslTestingHelper.java index 89a9aeb20..1c2954dac 100644 --- a/src/test/java/io/nats/client/support/ssl/SslTestingHelper.java +++ b/src/test/java/io/nats/client/support/ssl/SslTestingHelper.java @@ -28,9 +28,11 @@ import java.security.cert.X509Certificate; import java.util.Properties; +import static io.nats.client.utils.ResourceUtils.configResource; + public class SslTestingHelper { - public static String KEYSTORE_PATH = "src/test/resources/keystore.jks"; - public static String TRUSTSTORE_PATH = "src/test/resources/truststore.jks"; + public static String KEYSTORE_PATH = configResource("keystore.jks"); + public static String TRUSTSTORE_PATH = configResource("truststore.jks"); public static String PASSWORD = "password"; public static char[] PASSWORD_CHARS = PASSWORD.toCharArray(); diff --git a/src/test/java/io/nats/client/utils/ConnectionUtils.java b/src/test/java/io/nats/client/utils/ConnectionUtils.java new file mode 100644 index 000000000..36b702737 --- /dev/null +++ b/src/test/java/io/nats/client/utils/ConnectionUtils.java @@ -0,0 +1,175 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.utils; + +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.nats.client.NatsServerProtocolMock; +import io.nats.client.Options; +import org.opentest4j.AssertionFailedError; + +import java.io.IOException; + +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.ThreadUtils.sleep; +import static org.junit.jupiter.api.Assertions.assertSame; + +public abstract class ConnectionUtils { + + public static final int DEFAULT_WAIT = 5000; + public static final int MEDIUM_WAIT = 8000; + public static final int LONG_WAIT = 12000; + public static final int VERY_LONG_WAIT = 20000; + + public static final long STANDARD_FLUSH_TIMEOUT_MS = 2000; + public static final long MEDIUM_FLUSH_TIMEOUT_MS = 5000; + + // ---------------------------------------------------------------------------------------------------- + // connectWithRetry + // ---------------------------------------------------------------------------------------------------- + private static final int RETRY_DELAY_INCREMENT = 50; + private static final int CONNECTION_RETRIES = 10; + private static final long RETRY_DELAY = 100; + + public static Connection managedConnect(Options options) { + try { + return managedConnect(options, DEFAULT_WAIT); + } + catch (Exception e) { + throw new RuntimeException("Unable to make a connection.", e); + } + } + + public static Connection managedConnect(Options options, long waitTime) throws IOException, InterruptedException { + IOException last = null; + long delay = RETRY_DELAY - RETRY_DELAY_INCREMENT; + for (int x = 1; x <= CONNECTION_RETRIES; x++) { + if (x > 1) { + delay += RETRY_DELAY_INCREMENT; + sleep(delay); + } + try { + return waitUntilStatus(Nats.connect(options), waitTime, Connection.Status.CONNECTED); + } + catch (IOException ioe) { + last = ioe; + } + catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw ie; + } + } + throw last; + } + + // ---------------------------------------------------------------------------------------------------- + // standardConnect + // ---------------------------------------------------------------------------------------------------- + public static Connection standardConnect(NatsServerProtocolMock ts) throws IOException, InterruptedException { + return confirmConnected(Nats.connect(options(ts))); + } + + public static Connection standardConnect(Options options) throws IOException, InterruptedException { + return confirmConnected(Nats.connect(options)); + } + + // ---------------------------------------------------------------------------------------------------- + // connect or wait for a connection + // ---------------------------------------------------------------------------------------------------- + @SuppressWarnings("UnusedReturnValue") + public static Connection confirmConnected(Connection conn) { + return waitUntilStatus(conn, DEFAULT_WAIT, Connection.Status.CONNECTED); + } + + public static Connection confirmConnected(Connection conn, long waitTime) { + return waitUntilStatus(conn, waitTime, Connection.Status.CONNECTED); + } + + // ---------------------------------------------------------------------------------------------------- + // connect or wait for a connection + // ---------------------------------------------------------------------------------------------------- + public static void confirmConnectedThenClosed(Connection conn) { + closeAndConfirm(confirmConnected(conn, DEFAULT_WAIT), DEFAULT_WAIT); + } + + public static void confirmConnectedThenClosed(Connection conn, long waitTime) { + closeAndConfirm(confirmConnected(conn, waitTime), DEFAULT_WAIT); + } + + public static void confirmConnectedThenClosed(Connection conn, long waitTime, long closeTime) { + closeAndConfirm(confirmConnected(conn, waitTime), closeTime); + } + + // ---------------------------------------------------------------------------------------------------- + // close + // ---------------------------------------------------------------------------------------------------- + public static void closeAndConfirm(Connection conn) { + closeAndConfirm(conn, DEFAULT_WAIT); + } + + public static void closeAndConfirm(Connection conn, long millis) { + if (conn != null) { + close(conn); + waitUntilStatus(conn, millis, Connection.Status.CLOSED); + assertClosed(conn); + } + } + + public static void close(Connection conn) { + try { + conn.close(); + } + catch (InterruptedException e) { /* ignored */ } + } + + // ---------------------------------------------------------------------------------------------------- + // connection waiting + // ---------------------------------------------------------------------------------------------------- + public static Connection waitUntilStatus(Connection conn, long millis, Connection.Status waitUntilStatus) { + long times = (millis + 99) / 100; + for (long x = 0; x < times; x++) { + sleep(100); + if (conn.getStatus() == waitUntilStatus) { + return conn; + } + } + + throw new AssertionFailedError(expectingMessage(conn, waitUntilStatus)); + } + + // ---------------------------------------------------------------------------------------------------- + // assertions + // ---------------------------------------------------------------------------------------------------- + public static void assertConnected(Connection conn) { + assertSame(Connection.Status.CONNECTED, conn.getStatus(), + () -> expectingMessage(conn, Connection.Status.CONNECTED)); + } + + public static void assertClosed(Connection conn) { + assertSame(Connection.Status.CLOSED, conn.getStatus(), + () -> expectingMessage(conn, Connection.Status.CLOSED)); + } + + public static void assertCanConnect(NatsServerProtocolMock ts) { + closeAndConfirm(managedConnect(options(ts))); + } + + public static void assertCanConnect(Options options) { + closeAndConfirm(managedConnect(options)); + } + + private static String expectingMessage(Connection conn, Connection.Status expecting) { + return "Failed expecting Connection Status " + expecting.name() + " but was " + conn.getStatus(); + } +} diff --git a/src/test/java/io/nats/client/utils/OptionsUtils.java b/src/test/java/io/nats/client/utils/OptionsUtils.java new file mode 100644 index 000000000..43b79a1eb --- /dev/null +++ b/src/test/java/io/nats/client/utils/OptionsUtils.java @@ -0,0 +1,138 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.utils; + +import io.nats.client.*; +import org.jspecify.annotations.NonNull; + +import java.time.Duration; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * ---------------------------------------------------------------------------------------------------- + * THIS IS THE PREFERRED WAY TO BUILD ANY OPTIONS FOR TESTING + * ---------------------------------------------------------------------------------------------------- + */ +public abstract class OptionsUtils { + + private static ExecutorService EX; + private static ScheduledThreadPoolExecutor SC; + + public static ErrorListener NOOP_EL = new ErrorListener() {}; + + public static Options.Builder optionsBuilder(ErrorListener el) { + return optionsBuilder().errorListener(el); + } + + public static Options.Builder optionsBuilder(TestServer... tses) { + if (tses.length == 1) { + return optionsBuilder().server(tses[0].getServerUri()); + } + String[] servers = new String[tses.length]; + for (int i = 0; i < tses.length; i++) { + servers[i] = tses[i].getServerUri(); + } + return optionsBuilder().servers(servers); + } + public static Options.Builder optionsBuilder(NatsTestServer ts, String schema) { + return optionsBuilder().server(ts.getLocalhostUri(schema)); + } + + public static Options.Builder optionsBuilder(int port) { + return optionsBuilder().server(NatsTestServer.getLocalhostUri(port)); + } + + public static Options.Builder optionsBuilder(String... servers) { + return optionsBuilder().servers(servers); + } + + public static Options.Builder optionsBuilder(Connection nc) { + //noinspection DataFlowIssue + return optionsBuilder().server(nc.getConnectedUrl()); + } + + public static Options options() { + return optionsBuilder().build(); + } + + public static Options options(Connection nc) { + return optionsBuilder(nc).build(); + } + + public static Options options(int port) { + return optionsBuilder(port).build(); + } + + public static Options options(NatsTestServer... testServers) { + return optionsBuilder(testServers).build(); + } + + public static Options options(NatsServerProtocolMock... testServers) { + return optionsBuilder(testServers).build(); + } + + public static Options options(String... servers) { + return optionsBuilder().servers(servers).build(); + } + + public static Options.Builder optionsBuilder() { + if (EX == null) { + EX = new ThreadPoolExecutor(6, Integer.MAX_VALUE, 30, TimeUnit.SECONDS, + new SynchronousQueue<>(), + new TestThreadFactory("EX")); + } + + if (SC == null) { + SC = new ScheduledThreadPoolExecutor(3, new TestThreadFactory("SC")); + SC.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + SC.setRemoveOnCancelPolicy(true); + } + + return Options.builder() + .connectionTimeout(Duration.ofSeconds(4)) + .executor(EX) + .scheduledExecutor(SC) + .callbackExecutor(Executors.newSingleThreadExecutor(new TestThreadFactory("CB"))) + .connectExecutor(Executors.newSingleThreadExecutor(new TestThreadFactory("CN"))) + .errorListener(NOOP_EL) + + // This forces to use the plain SocketDataPort instead of + // SocketDataPortWithWriteTimeout, we just don't need it for testing. + // This saves running the scheduled task in the SocketDataPortWithWriteTimeout + .socketWriteTimeout(null); + } + + static class TestThreadFactory implements ThreadFactory { + final String name; + final AtomicInteger threadNumber; + + public TestThreadFactory(String name) { + this.name = name; + this.threadNumber = new AtomicInteger(1); + } + + public Thread newThread(@NonNull Runnable r) { + String threadName = "TST." + name + "." + threadNumber.incrementAndGet(); + Thread t = new Thread(r, threadName); + if (t.isDaemon()) { + t.setDaemon(false); + } + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + return t; + } + } +} diff --git a/src/test/java/io/nats/client/utils/ResourceUtils.java b/src/test/java/io/nats/client/utils/ResourceUtils.java index 440225009..0e8c202f4 100644 --- a/src/test/java/io/nats/client/utils/ResourceUtils.java +++ b/src/test/java/io/nats/client/utils/ResourceUtils.java @@ -8,6 +8,24 @@ @SuppressWarnings("DataFlowIssue") public abstract class ResourceUtils { + + public static final String CONFIG_FILE_BASE = "src/test/resources/"; + public static final String JWT_FILE_BASE = "src/test/resources/jwt_nkey/"; + + public static String configResource(String configFilePath) { + if (configFilePath == null) { + return null; + } + return configFilePath.startsWith(CONFIG_FILE_BASE) ? configFilePath : CONFIG_FILE_BASE + configFilePath; + } + + public static String jwtResource(String configFilePath) { + if (configFilePath == null) { + return null; + } + return configFilePath.startsWith(JWT_FILE_BASE) ? configFilePath : JWT_FILE_BASE + configFilePath; + } + public static List dataAsLines(String fileName) { return resourceAsLines("data/" + fileName); } @@ -31,7 +49,7 @@ public static List resourceAsLines(String fileName) { } } - + public static String resourceAsString(String fileName) { try { ClassLoader classLoader = ResourceUtils.class.getClassLoader(); diff --git a/src/test/java/io/nats/client/utils/TestBase.java b/src/test/java/io/nats/client/utils/TestBase.java index 8993c5bf7..bea451317 100644 --- a/src/test/java/io/nats/client/utils/TestBase.java +++ b/src/test/java/io/nats/client/utils/TestBase.java @@ -13,31 +13,38 @@ package io.nats.client.utils; +import io.nats.NatsServerRunner; import io.nats.client.*; -import io.nats.client.api.ServerInfo; -import io.nats.client.impl.ListenerForTesting; -import io.nats.client.impl.NatsMessage; +import io.nats.client.api.StorageType; +import io.nats.client.api.StreamConfiguration; +import io.nats.client.impl.*; import io.nats.client.support.NatsJetStreamClientError; +import org.jspecify.annotations.NonNull; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.function.Executable; -import org.opentest4j.AssertionFailedError; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.Supplier; import static io.nats.client.support.NatsConstants.DOT; import static io.nats.client.support.NatsConstants.EMPTY; import static io.nats.client.support.NatsJetStreamClientError.KIND_ILLEGAL_ARGUMENT; import static io.nats.client.support.NatsJetStreamClientError.KIND_ILLEGAL_STATE; +import static io.nats.client.utils.ConnectionUtils.*; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.OptionsUtils.optionsBuilder; +import static io.nats.client.utils.ResourceUtils.configResource; +import static io.nats.client.utils.ThreadUtils.sleep; +import static io.nats.client.utils.VersionUtils.*; import static org.junit.jupiter.api.Assertions.*; public class TestBase { + public static final String WS = "ws"; + public static final String WSS = "wss"; public static final String STAR_SEGMENT = "*.star.*.segment.*"; public static final String GT_NOT_LAST_SEGMENT = "gt.>.notlast"; @@ -74,28 +81,35 @@ public class TestBase { public static final String META_KEY = "meta-test-key"; public static final String META_VALUE = "meta-test-value"; - public static final long STANDARD_CONNECTION_WAIT_MS = 5000; - public static final long LONG_CONNECTION_WAIT_MS = 7500; - public static final long STANDARD_FLUSH_TIMEOUT_MS = 2000; - public static final long MEDIUM_FLUSH_TIMEOUT_MS = 5000; - public static final long LONG_TIMEOUT_MS = 15000; - public static String[] BAD_SUBJECTS_OR_QUEUES = new String[] { HAS_SPACE, HAS_CR, HAS_LF, HAS_TAB, STARTS_SPACE, ENDS_SPACE, null, EMPTY }; + public static Set SharedNamedServers = new HashSet<>(); + + @AfterAll + public static void testBaseAfterAll() { + if (SharedNamedServers.size() > 0) { + SharedServer.shutdown(SharedNamedServers.toArray(new String[0])); + } + } + // ---------------------------------------------------------------------------------------------------- - // runners + // runners / test interfaces // ---------------------------------------------------------------------------------------------------- public interface InServerTest { + void test(NatsTestServer ts) throws Exception; + } + + public interface OneConnectionTest { void test(Connection nc) throws Exception; } - public interface TwoServerTest { + public interface TwoConnectionTest { void test(Connection nc1, Connection nc2) throws Exception; } - public interface ThreeServerTest { + public interface ThreeConnectionTest { void test(Connection nc1, Connection nc2, Connection nc3) throws Exception; } @@ -103,284 +117,309 @@ public interface ThreeServerTestOptions { default void append(int index, Options.Builder builder) {} default boolean configureAccount() { return false; } default boolean includeAllServers() { return false; } + default boolean jetStream() { return false; } } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public interface VersionCheck { - boolean runTest(ServerInfo si); + public interface JetStreamTest { + void test(Connection nc, JetStreamManagement jsm, JetStream js) throws Exception; } - public static boolean atLeast2_9_0() { - return atLeast2_9_0(RUN_SERVER_INFO); + public interface JetStreamTestingContextTest { + void test(Connection nc, JetStreamTestingContext ctx) throws Exception; } - public static boolean atLeast2_9_0(Connection nc) { - return atLeast2_9_0(nc.getServerInfo()); + // -------------------------------------------------- + // Configured Server + // -------------------------------------------------- + private static void _runInConfiguredServer( + String configFilePath, + int customPort, + InServerTest inServerTest + ) throws Exception { + NatsServerRunner.Builder nsrb = NatsServerRunner.builder() + .configFilePath(configResource(configFilePath)); + if (customPort > 0) { + nsrb.port(customPort); + } + try (NatsTestServer ts = new NatsTestServer(nsrb)) { + inServerTest.test(ts); + } } - public static boolean atLeast2_9_0(ServerInfo si) { - return si.isSameOrNewerThanVersion("2.9.0"); + public static void runInConfiguredServer(String configFilePath, InServerTest inServerTest) throws Exception { + _runInConfiguredServer(configFilePath, -1, inServerTest); } - public static boolean atLeast2_10_26(ServerInfo si) { - return si.isSameOrNewerThanVersion("2.10.26"); + public static void runInConfiguredServer(String configFilePath, int customPort, InServerTest inServerTest) throws Exception { + _runInConfiguredServer(configFilePath, customPort, inServerTest); } - public static boolean atLeast2_9_1(ServerInfo si) { - return si.isSameOrNewerThanVersion("2.9.1"); + // -------------------------------------------------- + // Shared Configured Server + // -------------------------------------------------- + private static void _runInSharedNamedServer( + String name, + String nameAndConfFile, + String confFile, + int version, + InServerTest inServerTest + ) throws Exception { + NatsTestServer ts; + if (name != null) { + SharedNamedServers.add(name); + ts = SharedServer.getInstance(name).getServer(); + } + else if (nameAndConfFile != null) { + SharedNamedServers.add(nameAndConfFile); + ts = SharedServer.getInstance(nameAndConfFile, nameAndConfFile).getServer(); + } + else { + name = confFile + "-" + version; + SharedNamedServers.add(name); + ts = SharedServer.getInstance(name, confFile).getServer(); + } + inServerTest.test(ts); } - public static boolean atLeast2_10() { - return atLeast2_10(RUN_SERVER_INFO); + public static void runInSharedServer(InServerTest inServerTest) throws Exception { + inServerTest.test(SharedServer.getInstance(SHARED_NAME).getServer()); } - public static boolean atLeast2_10(ServerInfo si) { - return si.isNewerVersionThan("2.9.99"); + public static void runInSharedNamed(String name, InServerTest inServerTest) throws Exception { + _runInSharedNamedServer(name, null, null, -1, inServerTest); } - public static boolean atLeast2_10_3(ServerInfo si) { - return si.isSameOrNewerThanVersion("2.10.3"); + public static void runInSharedConfiguredServer(String nameAndConfFile, InServerTest inServerTest) throws Exception { + _runInSharedNamedServer(null, nameAndConfFile, null, -1, inServerTest); } - public static boolean atLeast2_11() { - return atLeast2_11(RUN_SERVER_INFO); + public static void runInSharedConfiguredServer(String confFile, int version, InServerTest inServerTest) throws Exception { + _runInSharedNamedServer(null, null, confFile, version, inServerTest); } - public static boolean atLeast2_11(ServerInfo si) { - return si.isNewerVersionThan("2.10.99"); + // ---------------------------------------------------------------------------------------------------- + // runners -> own server, not jetstream + // ---------------------------------------------------------------------------------------------------- + private static void _runInOwnServer(@NonNull OneConnectionTest oneNcTest) throws Exception { + try (NatsTestServer ts = new NatsTestServer()) { + try (Connection nc = managedConnect(options(ts))) { + initVersionServerInfo(nc); + oneNcTest.test(nc); + } + } } - public static boolean before2_11() { - return before2_11(RUN_SERVER_INFO); + public static void runInOwnServer(OneConnectionTest oneNcTest) throws Exception { + _runInOwnServer(oneNcTest); } - public static boolean before2_11(ServerInfo si) { - return si.isOlderThanVersion("2.11"); + // ---------------------------------------------------------------------------------------------------- + // runners -> own server, yes jetstream + // ---------------------------------------------------------------------------------------------------- + private static void _runInOwnJsServer(VersionCheck vc, @NonNull JetStreamTest jsTest) throws Exception { + if (vc != null && VERSION_SERVER_INFO != null && !vc.runTest(VERSION_SERVER_INFO)) { + return; // had vc, already had run server info and fails check + } + + NatsServerRunner.Builder nsrb = NatsServerRunner.builder().jetstream(true); + try (NatsTestServer ts = new NatsTestServer(nsrb)) { + try (Connection nc = managedConnect(options(ts))) { + initVersionServerInfo(nc); + if (vc == null || vc.runTest(VERSION_SERVER_INFO)) { + NatsJetStreamManagement jsm = (NatsJetStreamManagement) nc.jetStreamManagement(); + NatsJetStream js = (NatsJetStream) nc.jetStream(); + jsTest.test(nc, jsm, js); + } + } + } } - public static boolean atLeast2_12() { - return atLeast2_12(RUN_SERVER_INFO); + public static void runInOwnJsServer(JetStreamTest jetStreamTest) throws Exception { + _runInOwnJsServer(null, jetStreamTest); } - public static boolean atLeast2_12(ServerInfo si) { - return si.isSameOrNewerThanVersion("2.11.99"); + public static void runInOwnJsServer(VersionCheck vc, JetStreamTest jetStreamTest) throws Exception { + _runInOwnJsServer(vc, jetStreamTest); } - public static void runInServer(InServerTest inServerTest) throws Exception { - runInServer(false, false, null, null, inServerTest); - } + // ---------------------------------------------------------------------------------------------------- + // runners -> shared + // ---------------------------------------------------------------------------------------------------- + static final String SHARED_NAME = "SHARED"; + + private static void _runInShared( + Options.Builder optionsBuilder, + VersionCheck vc, + OneConnectionTest oneNcTest, + TwoConnectionTest twoNcTest, + int jstcTestSubjectCount, + JetStreamTestingContextTest ctxTest + ) throws Exception { + if (vc != null && VERSION_SERVER_INFO != null && !vc.runTest(VERSION_SERVER_INFO)) { + return; // had vc, already had run server info and fails check + } + + SharedServer shared = SharedServer.getInstance(SHARED_NAME); + + // no builder, we can use the long-running connection since it's totally generic + // with a builder, just make a fresh connection and close it at the end. + boolean closeNcWhenDone; + Connection nc; + Connection nc2 = null; + + if (optionsBuilder == null) { + closeNcWhenDone = false; + nc = shared.getSharedConnection(); + if (twoNcTest != null) { + nc2 = shared.getSharedConnection(); + } + } + else { + closeNcWhenDone = true; + nc = shared.newConnection(optionsBuilder); + if (twoNcTest != null) { + nc2 = shared.newConnection(optionsBuilder); + } + } - public static void runInServer(Options.Builder builder, InServerTest inServerTest) throws Exception { - runInServer(false, false, builder, null, inServerTest); - } + initVersionServerInfo(nc); + if (vc == null || vc.runTest(VERSION_SERVER_INFO)) { + try { + if (ctxTest != null) { + try (JetStreamTestingContext ctx = new JetStreamTestingContext(nc, jstcTestSubjectCount)) { + ctxTest.test(nc, ctx); + } + } + else if (oneNcTest != null) { + oneNcTest.test(nc); - public static void runInServer(boolean debug, InServerTest inServerTest) throws Exception { - runInServer(debug, false, null, null, inServerTest); + } + else if (twoNcTest != null) { + twoNcTest.test(nc, nc2); + } + } + finally { + if (closeNcWhenDone) { + try { nc.close(); } catch (Exception ignore) {} + if (nc2 != null) { + try { nc2.close(); } catch (Exception ignore) {} + } + } + } + } } - public static void runInJsServer(InServerTest inServerTest) throws Exception { - runInServer(false, true, null, null, inServerTest); + // -------------------------------------------------- + // 1. Not using JetStream + // -or- + // 2. need something very custom -> + // please clean up your streams + // -or- + // 3. or need to do something special with the + // connection list close it + // -------------------------------------------------- + public static void runInShared(OneConnectionTest test) throws Exception { + _runInShared(null, null, test, null, -1, null); } - public static void runInJsServer(ErrorListener el, InServerTest inServerTest) throws Exception { - runInServer(false, true, new Options.Builder().errorListener(el), null, inServerTest); + public static void runInShared(VersionCheck vc, OneConnectionTest onNcTest) throws Exception { + _runInShared(null, vc, onNcTest, null, -1, null); } - public static void runInJsServer(VersionCheck vc, ErrorListener el, InServerTest inServerTest) throws Exception { - runInServer(false, true, new Options.Builder().errorListener(el), vc, inServerTest); + public static void runInSharedOwnNc(OneConnectionTest onNcTest) throws Exception { + _runInShared(optionsBuilder(), null, onNcTest, null, -1, null); } - public static void runInJsServer(Options.Builder builder, InServerTest inServerTest) throws Exception { - runInServer(false, true, builder, null, inServerTest); + public static void runInSharedOwnNc(ErrorListener el, OneConnectionTest onNcTest) throws Exception { + _runInShared(optionsBuilder(el), null, onNcTest, null, -1, null); } - public static void runInJsServer(Options.Builder builder, VersionCheck vc, InServerTest inServerTest) throws Exception { - runInServer(false, true, builder, vc, inServerTest); + public static void runInSharedOwnNc(Options.Builder builder, OneConnectionTest onNcTest) throws Exception { + _runInShared(builder, null, onNcTest, null, -1, null); } - public static void runInJsServer(VersionCheck vc, InServerTest inServerTest) throws Exception { - runInServer(false, true, null, vc, inServerTest); + public static void runInSharedOwnNcs(TwoConnectionTest test) throws Exception { + _runInShared(optionsBuilder(), null, null, test, -1, null); } - public static void runInJsServer(boolean debug, InServerTest inServerTest) throws Exception { - runInServer(debug, true, null, null, inServerTest); + public static void runInSharedOwnNcs(Options.Builder builder, TwoConnectionTest twoNcTest) throws Exception { + _runInShared(builder, null, null, twoNcTest, -1, null); } - public static void runInServer(boolean debug, boolean jetstream, InServerTest inServerTest) throws Exception { - runInServer(debug, jetstream, null, null, inServerTest); + // -------------------------------------------------- + // JetStream: 1 stream 1 subject + // -------------------------------------------------- + public static void runInShared(JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(null, null, null, null, 1, ctxTest); } - public static void runInServer(boolean debug, boolean jetstream, Options.Builder builder, InServerTest inServerTest) throws Exception { - runInServer(debug, jetstream, builder, null, inServerTest); + public static void runInShared(VersionCheck vc, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(null, vc, null, null, 1, ctxTest); } - public static ServerInfo RUN_SERVER_INFO; - - public static ServerInfo ensureRunServerInfo() throws Exception { - if (RUN_SERVER_INFO == null) { - runInServer(false, false, null, null, nc -> {}); - } - return RUN_SERVER_INFO; + public static void runInSharedOwnNc(ErrorListener el, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(optionsBuilder(el), null, null, null, 1, ctxTest); } - public static void initRunServerInfo(Connection nc) { - if (RUN_SERVER_INFO == null) { - RUN_SERVER_INFO = nc.getServerInfo(); - } + public static void runInSharedOwnNc(ErrorListener el, VersionCheck vc, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(optionsBuilder(el), vc, null, null, 1, ctxTest); } - public static void runInServer(boolean debug, boolean jetstream, Options.Builder builder, VersionCheck vc, InServerTest inServerTest) throws Exception { - if (vc != null && RUN_SERVER_INFO != null) { - if (!vc.runTest(RUN_SERVER_INFO)) { - return; - } - vc = null; // since we've already determined it should run, null this out so we don't check below - } - - if (builder == null) { - builder = new Options.Builder(); - } - - try (NatsTestServer ts = new NatsTestServer(debug, jetstream); - Connection nc = standardConnection(builder.server(ts.getURI()).build())) - { - initRunServerInfo(nc); - - if (vc != null && !vc.runTest(RUN_SERVER_INFO)) { - return; - } - - try { - inServerTest.test(nc); - } - finally { - if (jetstream) { - cleanupJs(nc); - } - } - } + public static void runInSharedOwnNc(Options.Builder builder, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(builder, null, null, null, 1, ctxTest); } - public static class LongRunningNatsTestServer extends NatsTestServer { - public final boolean jetstream; - public final Options.Builder builder; - public final ListenerForTesting listenerForTesting; - - public LongRunningNatsTestServer(boolean debug, boolean jetstream, Options.Builder builder) throws IOException { - super(builder() - .debug(debug) - .jetstream(jetstream) - .connectValidateInitialDelay(100L) - .connectValidateSubsequentDelay(25L) - .connectValidateTries(15) - ); - this.jetstream = jetstream; - if (builder == null) { - this.builder = new Options.Builder(); - listenerForTesting = new ListenerForTesting(); - } - else { - this.builder = builder; - listenerForTesting = null; - } - } - - public void setExitOnDisconnect() { - if (listenerForTesting != null) { - listenerForTesting.setExitOnDisconnect(); - } - } - - public void setExitOnHeartbeatError() { - if (listenerForTesting != null) { - listenerForTesting.setExitOnHeartbeatError(); - } - } - - public void clearExitOnDisconnect() { - if (listenerForTesting != null) { - listenerForTesting.clearExitOnDisconnect(); - } - } - - public void clearExitOnHeartbeatError() { - if (listenerForTesting != null) { - listenerForTesting.clearExitOnHeartbeatError(); - } - } - - public Connection connect() throws IOException, InterruptedException { - return standardConnection(builder.server(getURI()).build()); - } - - public void run(InServerTest inServerTest) throws Exception { - run(null, null, inServerTest); - } - - public void run(VersionCheck vc, InServerTest inServerTest) throws Exception { - run(null, vc, inServerTest); - } - - public void run(Options.Builder builder, InServerTest inServerTest) throws Exception { - run(builder, null, inServerTest); - } - - public void run(Options.Builder builder, VersionCheck vc, InServerTest inServerTest) throws Exception { - if (vc != null && RUN_SERVER_INFO != null) { - if (!vc.runTest(RUN_SERVER_INFO)) { - return; - } - vc = null; // since we've already determined it should run, null this out so we don't check below - } + public static void runInSharedOwnNc(Options.Builder builder, VersionCheck vc, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(builder, vc, null, null, 1, ctxTest); + } - if (builder == null) { - builder = new Options.Builder(); - } + // -------------------------------------------------- + // JetStream: 1 stream custom subjects, kv or os + // -------------------------------------------------- + public static void runInSharedCustom(JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(null, null, null, null, 0, ctxTest); + } - try (Connection nc = standardConnection(builder.server(getURI()).build())) - { - initRunServerInfo(nc); + public static void runInSharedCustom(VersionCheck vc, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(null, vc, null, null, 0, ctxTest); + } - if (vc != null && !vc.runTest(RUN_SERVER_INFO)) { - return; - } + public static void runInSharedCustom(ErrorListener el, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(optionsBuilder(el), null, null, null, 0, ctxTest); + } - try { - inServerTest.test(nc); - } - finally { - clearExitOnDisconnect(); - clearExitOnHeartbeatError(); - if (jetstream) { - cleanupJs(nc); - } - } - } - } + public static void runInSharedCustom(Options.Builder builder, JetStreamTestingContextTest ctxTest) throws Exception { + _runInShared(builder, null, null, null, 0, ctxTest); } - public static void runInExternalServer(InServerTest inServerTest) throws Exception { - runInExternalServer(Options.DEFAULT_URL, inServerTest); + // ---------------------------------------------------------------------------------------------------- + // runners / external + // ---------------------------------------------------------------------------------------------------- + public static void runInExternalServer(OneConnectionTest oneNcTest) throws Exception { + runInExternalServer(Options.DEFAULT_URL, oneNcTest); } - public static void runInExternalServer(String url, InServerTest inServerTest) throws Exception { + public static void runInExternalServer(String url, OneConnectionTest oneNcTest) throws Exception { try (Connection nc = Nats.connect(url)) { - inServerTest.test(nc); + oneNcTest.test(nc); } } + + // ---------------------------------------------------------------------------------------------------- + // runners / special + // ---------------------------------------------------------------------------------------------------- public static String HUB_DOMAIN = "HUB"; public static String LEAF_DOMAIN = "LEAF"; - public static void runInJsHubLeaf(TwoServerTest twoServerTest) throws Exception { + public static void runInJsHubLeaf(TwoConnectionTest twoConnectionTest) throws Exception { int hubPort = NatsTestServer.nextPort(); int hubLeafPort = NatsTestServer.nextPort(); int leafPort = NatsTestServer.nextPort(); String[] hubInserts = new String[] { "server_name: " + HUB_DOMAIN, - "jetstream {", - " store_dir: " + tempJsStoreDir(), + "jetstream {", // store_dir: will be added by the runner " domain: " + HUB_DOMAIN, "}", "leafnodes {", @@ -391,7 +430,6 @@ public static void runInJsHubLeaf(TwoServerTest twoServerTest) throws Exception String[] leafInserts = new String[] { "server_name: " + LEAF_DOMAIN, "jetstream {", - " store_dir: " + tempJsStoreDir(), " domain: " + LEAF_DOMAIN, "}", "leafnodes {", @@ -399,90 +437,77 @@ public static void runInJsHubLeaf(TwoServerTest twoServerTest) throws Exception "}" }; - try (NatsTestServer hub = new NatsTestServer(hubPort, false, true, null, hubInserts, null); - Connection nchub = standardConnection(hub.getURI()); - NatsTestServer leaf = new NatsTestServer(leafPort, false, true, null, leafInserts, null); - Connection ncleaf = standardConnection(leaf.getURI()) + try (NatsTestServer hub = new NatsTestServer(hubPort, true, null, hubInserts, null); + Connection nchub = managedConnect(options(hub)); + NatsTestServer leaf = new NatsTestServer(leafPort, true, null, leafInserts, null); + Connection ncleaf = managedConnect(options(leaf)) ) { - try { - twoServerTest.test(nchub, ncleaf); - } - finally { - cleanupJs(nchub); - cleanupJs(ncleaf); - } + twoConnectionTest.test(nchub, ncleaf); } } - public static void runInJsCluster(ThreeServerTest threeServerTest) throws Exception { - runInJsCluster(null, threeServerTest); + public static void runInCluster(ThreeConnectionTest threeServerTest) throws Exception { + runInCluster(null, threeServerTest); } - public static void runInJsCluster(ThreeServerTestOptions tstOpts, ThreeServerTest threeServerTest) throws Exception { + public static void runInCluster(ThreeServerTestOptions tstOpts, ThreeConnectionTest threeServerTest) throws Exception { + if (tstOpts == null) { + tstOpts = new ThreeServerTestOptions() {}; + } + int port1 = NatsTestServer.nextPort(); int port2 = NatsTestServer.nextPort(); int port3 = NatsTestServer.nextPort(); int listen1 = NatsTestServer.nextPort(); int listen2 = NatsTestServer.nextPort(); int listen3 = NatsTestServer.nextPort(); - String dir1 = tempJsStoreDir(); - String dir2 = tempJsStoreDir(); - String dir3 = tempJsStoreDir(); - String cluster = "cluster_" + variant(); - String serverPrefix = "server_" + variant() + "_"; + boolean js = tstOpts.jetStream(); + String cluster = "cluster_" + random(); + String serverPrefix = "server_" + random() + "_"; - if (tstOpts == null) { - tstOpts = new ThreeServerTestOptions() {}; - } boolean configureAccount = tstOpts.configureAccount(); - String[] server1Inserts = makeInsert(cluster, serverPrefix + 1, dir1, listen1, listen2, listen3, configureAccount); - String[] server2Inserts = makeInsert(cluster, serverPrefix + 2, dir2, listen2, listen1, listen3, configureAccount); - String[] server3Inserts = makeInsert(cluster, serverPrefix + 3, dir3, listen3, listen1, listen2, configureAccount); + String[] server1Inserts = makeInsert(cluster, serverPrefix + 1, js, listen1, listen2, listen3, configureAccount); + String[] server2Inserts = makeInsert(cluster, serverPrefix + 2, js, listen2, listen1, listen3, configureAccount); + String[] server3Inserts = makeInsert(cluster, serverPrefix + 3, js, listen3, listen1, listen2, configureAccount); - try (NatsTestServer srv1 = new NatsTestServer(port1, false, true, null, server1Inserts, null); - NatsTestServer srv2 = new NatsTestServer(port2, false, true, null, server2Inserts, null); - NatsTestServer srv3 = new NatsTestServer(port3, false, true, null, server3Inserts, null); - Connection nc1 = standardConnection(makeOptions(0, tstOpts, srv1, srv2, srv3)); - Connection nc2 = standardConnection(makeOptions(1, tstOpts, srv2, srv1, srv3)); - Connection nc3 = standardConnection(makeOptions(2, tstOpts, srv3, srv1, srv2)) + try (NatsTestServer srv1 = new NatsTestServer(port1, tstOpts.jetStream(), null, server1Inserts, null); + NatsTestServer srv2 = new NatsTestServer(port2, tstOpts.jetStream(), null, server2Inserts, null); + NatsTestServer srv3 = new NatsTestServer(port3, tstOpts.jetStream(), null, server3Inserts, null); + Connection nc1 = managedConnect(makeOptions(0, tstOpts, srv1, srv2, srv3)); + Connection nc2 = managedConnect(makeOptions(1, tstOpts, srv2, srv1, srv3)); + Connection nc3 = managedConnect(makeOptions(2, tstOpts, srv3, srv1, srv2)) ) { - try { - threeServerTest.test(nc1, nc2, nc3); - } - finally { - cleanupJs(nc1); - cleanupJs(nc2); - cleanupJs(nc3); - } + threeServerTest.test(nc1, nc2, nc3); } } - private static String[] makeInsert(String clusterName, String serverName, String jsStoreDir, int listen, int route1, int route2, boolean configureAccount) { - String[] serverInserts = new String[configureAccount ? 19 : 12]; - int x = -1; - serverInserts[++x] = "jetstream {"; - serverInserts[++x] = " store_dir=" + jsStoreDir; - serverInserts[++x] = "}"; - serverInserts[++x] = "server_name=" + serverName; - serverInserts[++x] = "cluster {"; - serverInserts[++x] = " name: " + clusterName; - serverInserts[++x] = " listen: 127.0.0.1:" + listen; - serverInserts[++x] = " routes: ["; - serverInserts[++x] = " nats-route://127.0.0.1:" + route1; - serverInserts[++x] = " nats-route://127.0.0.1:" + route2; - serverInserts[++x] = " ]"; - serverInserts[++x] = "}"; + private static String[] makeInsert(String clusterName, String serverName, boolean js, int listen, int route1, int route2, boolean configureAccount) { + List serverInserts = new ArrayList<>(); + if (js) { + serverInserts.add("jetstream {}"); // store_dir: will be added by the runner + } + serverInserts.add("server_name=" + serverName); + serverInserts.add("cluster {"); + serverInserts.add(" name: " + clusterName); + serverInserts.add(" listen: 127.0.0.1:" + listen); + serverInserts.add(" routes: ["); + serverInserts.add(" nats-route://127.0.0.1:" + route1); + serverInserts.add(" nats-route://127.0.0.1:" + route2); + serverInserts.add(" ]"); + serverInserts.add("}"); if (configureAccount) { - serverInserts[++x] = "accounts {"; - serverInserts[++x] = " $SYS: {}"; - serverInserts[++x] = " NVCF: {"; - serverInserts[++x] = " jetstream: \"enabled\","; - serverInserts[++x] = " users: [ { nkey: " + USER_NKEY + " } ]"; - serverInserts[++x] = " }"; - serverInserts[++x] = "}"; + serverInserts.add("accounts {"); + serverInserts.add(" $SYS: {}"); + serverInserts.add(" NVCF: {"); + if (js) { + serverInserts.add(" jetstream: enabled,"); + } + serverInserts.add(" users: [ { nkey: " + USER_NKEY + " } ]"); + serverInserts.add(" }"); + serverInserts.add("}"); } - return serverInserts; + return serverInserts.toArray(new String[0]); } private static final String USER_NKEY = "UBAX6GCZQYLJDLSNPBDDPLY6KIBRO2JAUYNPW4HCWBRCZ4OU57YQQQS3"; @@ -494,12 +519,12 @@ private static Options makeOptions(int id, ThreeServerTestOptions tstOpts, NatsT String[] servers = new String[srvs.length]; for (int i = 0; i < srvs.length; i++) { NatsTestServer nts = srvs[i]; - servers[i] = nts.getURI(); + servers[i] = nts.getServerUri(); } b.servers(servers); } else { - b.server(srvs[0].getURI()); + b.server(srvs[0].getServerUri()); } if (tstOpts.configureAccount()) { b.authHandler(Nats.staticCredentials(null, USER_SEED.toCharArray())); @@ -508,209 +533,67 @@ private static Options makeOptions(int id, ThreeServerTestOptions tstOpts, NatsT return b.build(); } - private static String tempJsStoreDir() throws IOException { - return Files.createTempDirectory(variant()).toString().replace("\\", "\\\\"); // when on windows this is necessary. unix doesn't have backslash - } - - public static void cleanupJs(Connection c) - { - try { - JetStreamManagement jsm = c.jetStreamManagement(); - List streams = jsm.getStreamNames(); - for (String s : streams) - { - jsm.deleteStream(s); - } - } catch (Exception ignore) {} - } - // ---------------------------------------------------------------------------------------------------- // data makers // ---------------------------------------------------------------------------------------------------- - public static final String STREAM = "stream"; - public static final String MIRROR = "mirror"; - public static final String SOURCE = "source"; - public static final String SUBJECT = "subject"; - public static final String SUBJECT_STAR = SUBJECT + ".*"; - public static final String SUBJECT_GT = SUBJECT + ".>"; - public static final String QUEUE = "queue"; - public static final String DURABLE = "durable"; - public static final String NAME = "name"; - public static final String DELIVER = "deliver"; - public static final String MESSAGE_ID = "mid"; - public static final String BUCKET = "bucket"; - public static final String KEY = "key"; public static final String DATA = "data"; - public static final String PREFIX = "prefix"; - public static String variant(Object variant) { - return variant == null ? NUID.nextGlobalSequence() : "" + variant; - } + // Fastest - using char array directly + private static final char[] CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); - public static String variant() { - return NUID.nextGlobalSequence(); - } + public static String random(int length) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + char[] result = new char[length]; - private static int pi0 = -1; - private static int pi1 = 0; - public static String prefix() { - if (++pi0 == 26) { - pi0 = 0; - if (++pi1 == 26) { - pi1 = 0; - } + for (int i = 0; i < length; i++) { + result[i] = CHARS[random.nextInt(CHARS.length)]; } - return PREFIX + (char)('A' + pi1) + (char)('A' + pi0); - } - - public static String stream() { - return STREAM + "-" + variant(); - } - - public static String stream(Object variant) { - return STREAM + "-" + variant(variant); - } - - public static String mirror() { - return MIRROR + "-" + variant(); - } - - public static String mirror(Object variant) { - return MIRROR + "-" + variant(variant); - } - - public static String source() { - return SOURCE + "-" + variant(); - } - - public static String source(Object variant) { - return SOURCE + "-" + variant(variant); - } - - public static String subject() { - return SUBJECT + "-" + variant(); - } - public static String subject(Object variant) { - return SUBJECT + "-" + variant(variant); - } - - public static String subjectDot(String field) { - return SUBJECT + DOT + field; - } - - public static String queue(Object variant) { - return QUEUE + "-" + variant(variant); - } - - public static String durable() { - return DURABLE + "-" + variant(); - } - - public static String durable(Object variant) { - return DURABLE + "-" + variant(variant); - } - - public static String durable(String vary, Object variant) { - return DURABLE + "-" + vary + "-" + variant(variant); - } - public static String name() { - return NAME + "-" + variant(); + return new String(result); } - public static String name(Object variant) { - return NAME + "-" + variant(variant); + public static String random() { + return random(10); } - public static String deliver() { - return DELIVER + "-" + variant(); + public static String subject(int variant) { + return "subject-" + variant; } - public static String deliver(Object variant) { - return DELIVER + "-" + variant(variant); + public static String subjectGt(String subject) { + return subject + ".>"; } - public static String bucket(Object variant) { - return BUCKET + "-" + variant(variant); + public static String subjectStar(String subject) { + return subject + ".*"; } - public static String bucket() { - return bucket(null); - } - - public static String key(Object variant) { - return KEY + "-" + variant(variant); - } - - public static String key() { - return KEY + "-" + variant(); - } - - public static String messageId(Object variant) { - return MESSAGE_ID + "-" + variant(variant); + public static String subjectDot(String subject, String field) { + return subject + DOT + field; } public static String data(Object variant) { - return DATA + "-" + variant(variant); + return DATA + "-" + variant; } public static byte[] dataBytes() { - return data(variant()).getBytes(StandardCharsets.US_ASCII); + return data(random()).getBytes(StandardCharsets.US_ASCII); } public static byte[] dataBytes(Object variant) { return data(variant).getBytes(StandardCharsets.US_ASCII); } public static NatsMessage getDataMessage(String data) { - return new NatsMessage(SUBJECT, null, data.getBytes(StandardCharsets.US_ASCII)); + return new NatsMessage(random(), null, data.getBytes(StandardCharsets.US_ASCII)); } // ---------------------------------------------------------------------------------------------------- // assertions // ---------------------------------------------------------------------------------------------------- - public static void assertConnected(Connection conn) { - assertSame(Connection.Status.CONNECTED, conn.getStatus(), - () -> expectingMessage(conn, Connection.Status.CONNECTED)); - } - - public static void assertNotConnected(Connection conn) { - assertNotSame(Connection.Status.CONNECTED, conn.getStatus(), - () -> "Failed not expecting Connection Status " + Connection.Status.CONNECTED.name()); - } - - public static void assertClosed(Connection conn) { - assertSame(Connection.Status.CLOSED, conn.getStatus(), - () -> expectingMessage(conn, Connection.Status.CLOSED)); - } - - public static void assertCanConnect() throws IOException, InterruptedException { - standardCloseConnection( standardConnection() ); - } - - public static void assertCanConnect(String serverURL) throws IOException, InterruptedException { - standardCloseConnection( standardConnection(serverURL) ); - } - - public static void assertCanConnect(Options options) throws IOException, InterruptedException { - standardCloseConnection( standardConnection(options) ); - } - - public static void assertCanConnectAndPubSub() throws IOException, InterruptedException { - Connection conn = standardConnection(); - assertPubSub(conn); - standardCloseConnection(conn); - } - - public static void assertCanConnectAndPubSub(String serverURL) throws IOException, InterruptedException { - Connection conn = standardConnection(serverURL); - assertPubSub(conn); - standardCloseConnection(conn); - } - public static void assertCanConnectAndPubSub(Options options) throws IOException, InterruptedException { - Connection conn = standardConnection(options); + Connection conn = managedConnect(options); assertPubSub(conn); - standardCloseConnection(conn); + closeAndConfirm(conn); } public static void assertByteArraysEqual(byte[] data1, byte[] data2) { @@ -726,7 +609,7 @@ public static void assertByteArraysEqual(byte[] data1, byte[] data2) { } public static void assertPubSub(Connection conn) throws InterruptedException { - String subject = subject(); + String subject = random(); String data = data(null); Subscription sub = conn.subscribe(subject); conn.publish(subject, data.getBytes()); @@ -735,38 +618,6 @@ public static void assertPubSub(Connection conn) throws InterruptedException { assertEquals(data, new String(m.getData())); } - // ---------------------------------------------------------------------------------------------------- - // utils / macro utils - // ---------------------------------------------------------------------------------------------------- - public static void sleep(long ms) { - try { Thread.sleep(ms); } catch (InterruptedException ignored) { /* ignored */ } - } - - public static void park(Duration d) { - try { LockSupport.parkNanos(d.toNanos()); } catch (Exception ignored) { /* ignored */ } - } - - public static void debugPrintln(Object... debug) { - StringBuilder sb = new StringBuilder(); - sb.append(System.currentTimeMillis()); - sb.append(" ["); - sb.append(Thread.currentThread().getName()); - sb.append(","); - sb.append(Thread.currentThread().getPriority()); - sb.append("] "); - boolean flag = true; - for (Object o : debug) { - if (flag) { - flag = false; - } - else { - sb.append(" | "); - } - sb.append(o); - } - System.out.println(sb.toString()); - } - // ---------------------------------------------------------------------------------------------------- // flush // ---------------------------------------------------------------------------------------------------- @@ -782,110 +633,6 @@ public static void flushConnection(Connection conn, Duration timeout) { try { conn.flush(timeout); } catch (Exception exp) { /* ignored */ } } - public static void flushAndWait(Connection conn, ListenerForTesting listener, long flushTimeoutMillis, long waitForStatusMillis) { - flushConnection(conn, flushTimeoutMillis); - listener.waitForStatusChange(waitForStatusMillis, TimeUnit.MILLISECONDS); - } - - public static void flushAndWaitLong(Connection conn, ListenerForTesting listener) { - flushAndWait(conn, listener, STANDARD_FLUSH_TIMEOUT_MS, LONG_TIMEOUT_MS); - } - - // ---------------------------------------------------------------------------------------------------- - // connect or wait for a connection - // ---------------------------------------------------------------------------------------------------- - public static Options.Builder standardOptionsBuilder() { - return Options.builder().reportNoResponders().errorListener(new ListenerForTesting()); - } - - public static Options.Builder standardOptionsBuilder(String serverURL) { - return standardOptionsBuilder().server(serverURL); - } - - public static Options standardOptions() { - return standardOptionsBuilder().build(); - } - - public static Options standardOptions(String serverURL) { - return standardOptionsBuilder(serverURL).build(); - } - - public static Connection standardConnection() throws IOException, InterruptedException { - return standardConnectionWait( Nats.connect(standardOptions()) ); - } - - public static Connection standardConnection(String serverURL) throws IOException, InterruptedException { - return standardConnectionWait( Nats.connect(standardOptions(serverURL)) ); - } - - public static Connection standardConnection(Options options) throws IOException, InterruptedException { - return standardConnectionWait( Nats.connect(options) ); - } - - public static Connection listenerConnectionWait(Options options, ListenerForTesting listener) throws IOException, InterruptedException { - return listenerConnectionWait( Nats.connect(options), listener ); - } - - public static Connection listenerConnectionWait(Connection conn, ListenerForTesting listener) { - return listenerConnectionWait(conn, listener, STANDARD_CONNECTION_WAIT_MS); - } - - public static Connection standardConnectionWait(Connection conn) { - return connectionWait(conn, STANDARD_CONNECTION_WAIT_MS); - } - - public static Connection longConnectionWait(Options options) throws IOException, InterruptedException { - return connectionWait( Nats.connect(options), LONG_CONNECTION_WAIT_MS ); - } - - public static Connection connectionWait(Connection conn, long millis) { - return waitUntilStatus(conn, millis, Connection.Status.CONNECTED); - } - - public static Connection listenerConnectionWait(Connection conn, ListenerForTesting listener, long millis) { - listener.waitForStatusChange(millis, TimeUnit.MILLISECONDS); - assertConnected(conn); - return conn; - } - - // ---------------------------------------------------------------------------------------------------- - // close - // ---------------------------------------------------------------------------------------------------- - public static void standardCloseConnection(Connection conn) { - closeConnection(conn, STANDARD_CONNECTION_WAIT_MS); - } - - public static void closeConnection(Connection conn, long millis) { - if (conn != null) { - close(conn); - waitUntilStatus(conn, millis, Connection.Status.CLOSED); - assertClosed(conn); - } - } - - public static void close(Connection conn) { - try { conn.close(); } catch (InterruptedException e) { /* ignored */ } - } - - // ---------------------------------------------------------------------------------------------------- - // connection waiting - // ---------------------------------------------------------------------------------------------------- - public static Connection waitUntilStatus(Connection conn, long millis, Connection.Status waitUntilStatus) { - long times = (millis + 99) / 100; - for (long x = 0; x < times; x++) { - sleep(100); - if (conn.getStatus() == waitUntilStatus) { - return conn; - } - } - - throw new AssertionFailedError(expectingMessage(conn, waitUntilStatus)); - } - - private static String expectingMessage(Connection conn, Connection.Status expecting) { - return "Failed expecting Connection Status " + expecting.name() + " but was " + conn.getStatus(); - } - public static void assertTrueByTimeout(long millis, Supplier test) { long times = (millis + 99) / 100; for (long x = 0; x < times; x++) { @@ -921,9 +668,25 @@ public static void assertMetaData(Map metadata) { } assertEquals(META_VALUE, metadata.get(META_KEY)); } - else if (atLeast2_9_0() ){ - assertNotNull(metadata); - assertEquals(0, metadata.size()); + } + + // ---------------------------------------------------------------------------------------------------- + // JetStream JetStream JetStream JetStream JetStream JetStream JetStream JetStream JetStream JetStream + // ---------------------------------------------------------------------------------------------------- + public static void createMemoryStream(JetStreamManagement jsm, String streamName, String... subjects) throws IOException, JetStreamApiException { + if (streamName == null) { + streamName = random(); } + + if (subjects == null || subjects.length == 0) { + subjects = new String[]{random()}; + } + + StreamConfiguration sc = StreamConfiguration.builder() + .name(streamName) + .storageType(StorageType.Memory) + .subjects(subjects).build(); + + jsm.addStream(sc); } } diff --git a/src/test/java/io/nats/client/utils/ThreadUtils.java b/src/test/java/io/nats/client/utils/ThreadUtils.java new file mode 100644 index 000000000..eb4048953 --- /dev/null +++ b/src/test/java/io/nats/client/utils/ThreadUtils.java @@ -0,0 +1,20 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.utils; + +public abstract class ThreadUtils { + public static void sleep(long ms) { + try { Thread.sleep(ms); } catch (InterruptedException ignored) { /* ignored */ } + } +} diff --git a/src/test/java/io/nats/client/utils/VersionUtils.java b/src/test/java/io/nats/client/utils/VersionUtils.java new file mode 100644 index 000000000..48b8b60c2 --- /dev/null +++ b/src/test/java/io/nats/client/utils/VersionUtils.java @@ -0,0 +1,67 @@ +// Copyright 2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.nats.client.utils; + +import io.nats.client.Connection; +import io.nats.client.api.ServerInfo; + +public abstract class VersionUtils { + public static ServerInfo VERSION_SERVER_INFO; + + public interface VersionCheck { + boolean runTest(ServerInfo si); + } + + public static void initVersionServerInfo(Connection nc) { + if (VERSION_SERVER_INFO == null) { + VERSION_SERVER_INFO = nc.getServerInfo(); + } + } + + public static boolean atLeast2_10() { + return atLeast2_10(VERSION_SERVER_INFO); + } + + public static boolean atLeast2_10(ServerInfo si) { + return si.isNewerVersionThan("2.9.99"); + } + + public static boolean atLeast2_10_3(ServerInfo si) { + return si.isSameOrNewerThanVersion("2.10.3"); + } + + public static boolean atLeast2_10_26(ServerInfo si) { + return si.isSameOrNewerThanVersion("2.10.26"); + } + + public static boolean atLeast2_11(ServerInfo si) { + return si.isNewerVersionThan("2.10.99"); + } + + public static boolean before2_11() { + return before2_11(VERSION_SERVER_INFO); + } + + public static boolean before2_11(ServerInfo si) { + return si.isOlderThanVersion("2.11"); + } + + public static boolean atLeast2_12() { + return atLeast2_12(VERSION_SERVER_INFO); + } + + public static boolean atLeast2_12(ServerInfo si) { + return si.isSameOrNewerThanVersion("2.11.99"); + } +} diff --git a/src/test/java/io/nats/service/ServiceTests.java b/src/test/java/io/nats/service/ServiceTests.java index cbcdf8fad..5a4bc4671 100644 --- a/src/test/java/io/nats/service/ServiceTests.java +++ b/src/test/java/io/nats/service/ServiceTests.java @@ -13,11 +13,11 @@ package io.nats.service; -import io.nats.client.*; -import io.nats.client.impl.Headers; -import io.nats.client.impl.JetStreamTestBase; -import io.nats.client.impl.MockNatsConnection; -import io.nats.client.impl.NatsMessage; +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import io.nats.client.Message; +import io.nats.client.Options; +import io.nats.client.impl.*; import io.nats.client.support.DateTimeUtils; import io.nats.client.support.JsonSerializable; import io.nats.client.support.JsonUtils; @@ -25,7 +25,6 @@ import nl.jqno.equalsverifier.EqualsVerifier; import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -45,282 +44,291 @@ import static io.nats.client.support.JsonValueUtils.readString; import static io.nats.client.support.NatsConstants.DOT; import static io.nats.client.support.NatsConstants.EMPTY; +import static io.nats.client.utils.OptionsUtils.options; +import static io.nats.client.utils.ThreadUtils.sleep; import static io.nats.service.Service.SRV_PING; import static io.nats.service.ServiceMessage.NATS_SERVICE_ERROR; import static io.nats.service.ServiceMessage.NATS_SERVICE_ERROR_CODE; import static org.junit.jupiter.api.Assertions.*; -@Isolated public class ServiceTests extends JetStreamTestBase { - public static final String SERVICE_NAME_1 = "Service1"; - public static final String SERVICE_NAME_2 = "Service2"; - public static final String ECHO_ENDPOINT_NAME = "EchoEndpoint"; - public static final String ECHO_ENDPOINT_SUBJECT = "echo"; - public static final String SORT_GROUP = "sort"; - public static final String SORT_ENDPOINT_ASCENDING_NAME = "SortEndpointAscending"; - public static final String SORT_ENDPOINT_DESCENDING_NAME = "SortEndpointDescending"; - public static final String SORT_ENDPOINT_ASCENDING_SUBJECT = "ascending"; - public static final String SORT_ENDPOINT_DESCENDING_SUBJECT = "descending"; - public static final String REVERSE_ENDPOINT_NAME = "ReverseEndpoint"; - public static final String REVERSE_ENDPOINT_SUBJECT = "reverse"; - public static final String CUSTOM_QGROUP = "customQ"; + + public static final String SERVICE_TESTS_SHARED_NAME = "ServiceTests"; @Test public void testServiceWorkflow() throws Exception { - try (NatsTestServer ts = new NatsTestServer()) { - try (Connection serviceNc1 = standardConnection(ts.getURI()); - Connection serviceNc2 = standardConnection(ts.getURI()); - Connection clientNc = standardConnection(ts.getURI())) { - - Endpoint endEcho = Endpoint.builder() - .name(ECHO_ENDPOINT_NAME) - .subject(ECHO_ENDPOINT_SUBJECT) - .queueGroup(CUSTOM_QGROUP) - .build(); - - Endpoint endSortA = Endpoint.builder() - .name(SORT_ENDPOINT_ASCENDING_NAME) - .subject(SORT_ENDPOINT_ASCENDING_SUBJECT) - .build(); - - // constructor coverage - Endpoint endSortD = new Endpoint( - SORT_ENDPOINT_DESCENDING_NAME, - SORT_ENDPOINT_DESCENDING_SUBJECT); - - // sort is going to be grouped - Group sortGroup = new Group(SORT_GROUP); - - ServiceEndpoint seEcho1 = ServiceEndpoint.builder() - .endpoint(endEcho) - .handler(new EchoHandler(serviceNc1)) - .statsDataSupplier(ServiceTests::supplyData) - .build(); - - ServiceEndpoint seSortA1 = ServiceEndpoint.builder() - .group(sortGroup) - .endpoint(endSortA) - .handler(new SortHandlerA(serviceNc1)) - .build(); - - ServiceEndpoint seSortD1 = ServiceEndpoint.builder() - .group(sortGroup) - .endpoint(endSortD) - .handler(new SortHandlerD(serviceNc1)) - .build(); - - ServiceEndpoint seEcho2 = ServiceEndpoint.builder() - .endpoint(endEcho) - .handler(new EchoHandler(serviceNc2)) - .statsDataSupplier(ServiceTests::supplyData) - .build(); - - // build variations - ServiceEndpoint seSortA2 = ServiceEndpoint.builder() - .group(sortGroup) - .endpointName(endSortA.getName()) - .endpointSubject(endSortA.getSubject()) - .handler(new SortHandlerA(serviceNc2)) - .build(); - - ServiceEndpoint seSortD2 = ServiceEndpoint.builder() - .group(sortGroup) - .endpointName(endSortD.getName()) - .endpointSubject(endSortD.getSubject()) - .handler(new SortHandlerD(serviceNc2)) - .build(); - - Service service1 = new ServiceBuilder() - .name(SERVICE_NAME_1) - .version("1.0.0") - .connection(serviceNc1) - .addServiceEndpoint(seEcho1) - .addServiceEndpoint(seSortA1) - .addServiceEndpoint(seSortD1) - .build(); - String serviceId1 = service1.getId(); - CompletableFuture serviceStoppedFuture1 = service1.startService(); - - Service service2 = new ServiceBuilder() - .name(SERVICE_NAME_2) - .version("1.0.0") - .connection(serviceNc2) - .addServiceEndpoint(seEcho2) - .addServiceEndpoint(seSortA2) - .addServiceEndpoint(seSortD2) - .build(); - String serviceId2 = service2.getId(); - CompletableFuture serviceStoppedFuture2 = service2.startService(); - - assertNotEquals(serviceId1, serviceId2); - - sleep(1000); // just make sure services are all started, for slow CI machines - - // service request execution - int requestCount = 10; - for (int x = 0; x < requestCount; x++) { - verifyServiceExecution(clientNc, ECHO_ENDPOINT_NAME, ECHO_ENDPOINT_SUBJECT, null); - verifyServiceExecution(clientNc, SORT_ENDPOINT_ASCENDING_NAME, SORT_ENDPOINT_ASCENDING_SUBJECT, sortGroup); - verifyServiceExecution(clientNc, SORT_ENDPOINT_DESCENDING_NAME, SORT_ENDPOINT_DESCENDING_SUBJECT, sortGroup); - } + String serviceName1 = "Service1" + random(); + String serviceName2 = "Service2" + random(); + String echoEndpointName = "EchoEndpoint" + random(); + String echoEndpointSubject = "echo" + random(); + String sortGroupName = "sort" + random(); + String sortEndpointAscendingName = "SortEndpointAscending" + random(); + String sortEndpointDescendingName = "SortEndpointDescending" + random(); + String sortEndpointAscendingSubject = "ascending" + random(); + String sortEndpointDescendingSubject = "descending" + random(); + String reverseEndpointName = "ReverseEndpoint" + random(); + String reverseEndpointSubject = "reverse" + random(); + String customQgroup = "customQ" + random(); + + Map verifyMap = new HashMap<>(); + verifyMap.put(echoEndpointName, "echoEndpointName"); + verifyMap.put(sortEndpointAscendingName, "sortEndpointAscendingName"); + verifyMap.put(sortEndpointDescendingName, "sortEndpointDescendingName"); + verifyMap.put(reverseEndpointName, "reverseEndpointName"); + + runInSharedNamed(SERVICE_TESTS_SHARED_NAME, ts -> { + Connection clientNc = SharedServer.sharedConnectionForServer(ts); + Connection serviceNc1 = SharedServer.sharedConnectionForServer(ts); + Connection serviceNc2 = SharedServer.sharedConnectionForServer(ts); + + Endpoint endEcho = Endpoint.builder() + .name(echoEndpointName) + .subject(echoEndpointSubject) + .queueGroup(customQgroup) + .build(); - PingResponse pingResponse1 = service1.getPingResponse(); - PingResponse pingResponse2 = service2.getPingResponse(); - InfoResponse infoResponse1 = service1.getInfoResponse(); - InfoResponse infoResponse2 = service2.getInfoResponse(); - StatsResponse statsResponse1 = service1.getStatsResponse(); - StatsResponse statsResponse2 = service2.getStatsResponse(); - EndpointStats[] endpointStatsArray1 = new EndpointStats[]{ - service1.getEndpointStats(ECHO_ENDPOINT_NAME), - service1.getEndpointStats(SORT_ENDPOINT_ASCENDING_NAME), - service1.getEndpointStats(SORT_ENDPOINT_DESCENDING_NAME) - }; - EndpointStats[] endpointStatsArray2 = new EndpointStats[]{ - service2.getEndpointStats(ECHO_ENDPOINT_NAME), - service2.getEndpointStats(SORT_ENDPOINT_ASCENDING_NAME), - service2.getEndpointStats(SORT_ENDPOINT_DESCENDING_NAME) - }; - assertNull(service1.getEndpointStats("notAnEndpoint")); - - assertEquals(serviceId1, pingResponse1.getId()); - assertEquals(serviceId2, pingResponse2.getId()); - assertEquals(serviceId1, infoResponse1.getId()); - assertEquals(serviceId2, infoResponse2.getId()); - assertEquals(serviceId1, statsResponse1.getId()); - assertEquals(serviceId2, statsResponse2.getId()); - - // this relies on the fact that I load the endpoints up in the service - // in the same order and the json list comes back ordered - // expecting 10 responses across each endpoint between 2 services - for (int x = 0; x < 3; x++) { - assertEquals(requestCount, - endpointStatsArray1[x].getNumRequests() - + endpointStatsArray2[x].getNumRequests()); - assertEquals(requestCount, - statsResponse1.getEndpointStatsList().get(x).getNumRequests() - + statsResponse2.getEndpointStatsList().get(x).getNumRequests()); - } + Endpoint endSortA = Endpoint.builder() + .name(sortEndpointAscendingName) + .subject(sortEndpointAscendingSubject) + .build(); + + // constructor coverage + Endpoint endSortD = new Endpoint( + sortEndpointDescendingName, + sortEndpointDescendingSubject); + + // sort is going to be grouped + Group sortGroup = new Group(sortGroupName); + + ServiceEndpoint seEcho1 = ServiceEndpoint.builder() + .endpoint(endEcho) + .handler(new EchoHandler(serviceNc1)) + .statsDataSupplier(ServiceTests::supplyData) + .build(); + + ServiceEndpoint seSortA1 = ServiceEndpoint.builder() + .group(sortGroup) + .endpoint(endSortA) + .handler(new SortHandlerA(serviceNc1)) + .build(); + + ServiceEndpoint seSortD1 = ServiceEndpoint.builder() + .group(sortGroup) + .endpoint(endSortD) + .handler(new SortHandlerD(serviceNc1)) + .build(); + + ServiceEndpoint seEcho2 = ServiceEndpoint.builder() + .endpoint(endEcho) + .handler(new EchoHandler(serviceNc2)) + .statsDataSupplier(ServiceTests::supplyData) + .build(); + + // build variations + ServiceEndpoint seSortA2 = ServiceEndpoint.builder() + .group(sortGroup) + .endpointName(endSortA.getName()) + .endpointSubject(endSortA.getSubject()) + .handler(new SortHandlerA(serviceNc2)) + .build(); + + ServiceEndpoint seSortD2 = ServiceEndpoint.builder() + .group(sortGroup) + .endpointName(endSortD.getName()) + .endpointSubject(endSortD.getSubject()) + .handler(new SortHandlerD(serviceNc2)) + .build(); - // discovery - wait at most 500 millis for responses, 5 total responses max - Discovery discovery = new Discovery(clientNc, 500, 5); - - // ping discovery - Verifier pingVerifier = (expected, response) -> assertInstanceOf(PingResponse.class, response); - verifyDiscovery(discovery.ping(), pingVerifier, pingResponse1, pingResponse2); - verifyDiscovery(discovery.ping(SERVICE_NAME_1), pingVerifier, pingResponse1); - verifyDiscovery(discovery.ping(SERVICE_NAME_2), pingVerifier, pingResponse2); - verifyDiscovery(discovery.ping(SERVICE_NAME_1, serviceId1), pingVerifier, pingResponse1); - assertNull(discovery.ping(SERVICE_NAME_1, "badId")); - assertNull(discovery.ping("bad", "badId")); - - // info discovery - Verifier infoVerifier = (expected, response) -> { - assertInstanceOf(InfoResponse.class, response); - InfoResponse exp = (InfoResponse) expected; - InfoResponse r = (InfoResponse) response; - assertEquals(exp.getDescription(), r.getDescription()); - assertEquals(exp.getEndpoints(), r.getEndpoints()); - }; - verifyDiscovery(discovery.info(), infoVerifier, infoResponse1, infoResponse2); - verifyDiscovery(discovery.info(SERVICE_NAME_1), infoVerifier, infoResponse1); - verifyDiscovery(discovery.info(SERVICE_NAME_2), infoVerifier, infoResponse2); - verifyDiscovery(discovery.info(SERVICE_NAME_1, serviceId1), infoVerifier, infoResponse1); - assertNull(discovery.info(SERVICE_NAME_1, "badId")); - assertNull(discovery.info("bad", "badId")); - - // stats discovery - Verifier statsVerifier = (expected, response) -> { - assertInstanceOf(StatsResponse.class, response); - StatsResponse exp = (StatsResponse) expected; - StatsResponse sr = (StatsResponse) response; - assertEquals(exp.getStarted(), sr.getStarted()); - for (int x = 0; x < 3; x++) { - EndpointStats er = exp.getEndpointStatsList().get(x); - if (!er.getName().equals(ECHO_ENDPOINT_NAME)) { - // echo endpoint has data that will vary - assertEquals(er, sr.getEndpointStatsList().get(x)); - } + Service service1 = new ServiceBuilder() + .name(serviceName1) + .version("1.0.0") + .connection(serviceNc1) + .addServiceEndpoint(seEcho1) + .addServiceEndpoint(seSortA1) + .addServiceEndpoint(seSortD1) + .build(); + String serviceId1 = service1.getId(); + CompletableFuture serviceStoppedFuture1 = service1.startService(); + + Service service2 = new ServiceBuilder() + .name(serviceName2) + .version("1.0.0") + .connection(serviceNc2) + .addServiceEndpoint(seEcho2) + .addServiceEndpoint(seSortA2) + .addServiceEndpoint(seSortD2) + .build(); + String serviceId2 = service2.getId(); + CompletableFuture serviceStoppedFuture2 = service2.startService(); + + assertNotEquals(serviceId1, serviceId2); + + sleep(1000); // just make sure services are all started, for slow CI machines + + // service request execution + int requestCount = 10; + for (int x = 0; x < requestCount; x++) { + verifyServiceExecution(clientNc, echoEndpointName, echoEndpointSubject, null, verifyMap); + verifyServiceExecution(clientNc, sortEndpointAscendingName, sortEndpointAscendingSubject, sortGroup, verifyMap); + verifyServiceExecution(clientNc, sortEndpointDescendingName, sortEndpointDescendingSubject, sortGroup, verifyMap); + } + + PingResponse pingResponse1 = service1.getPingResponse(); + PingResponse pingResponse2 = service2.getPingResponse(); + InfoResponse infoResponse1 = service1.getInfoResponse(); + InfoResponse infoResponse2 = service2.getInfoResponse(); + StatsResponse statsResponse1 = service1.getStatsResponse(); + StatsResponse statsResponse2 = service2.getStatsResponse(); + EndpointStats[] endpointStatsArray1 = new EndpointStats[]{ + service1.getEndpointStats(echoEndpointName), + service1.getEndpointStats(sortEndpointAscendingName), + service1.getEndpointStats(sortEndpointDescendingName) + }; + EndpointStats[] endpointStatsArray2 = new EndpointStats[]{ + service2.getEndpointStats(echoEndpointName), + service2.getEndpointStats(sortEndpointAscendingName), + service2.getEndpointStats(sortEndpointDescendingName) + }; + assertNull(service1.getEndpointStats("notAnEndpoint")); + + assertEquals(serviceId1, pingResponse1.getId()); + assertEquals(serviceId2, pingResponse2.getId()); + assertEquals(serviceId1, infoResponse1.getId()); + assertEquals(serviceId2, infoResponse2.getId()); + assertEquals(serviceId1, statsResponse1.getId()); + assertEquals(serviceId2, statsResponse2.getId()); + + // this relies on the fact that I load the endpoints up in the service + // in the same order and the json list comes back ordered + // expecting 10 responses across each endpoint between 2 services + for (int x = 0; x < 3; x++) { + assertEquals(requestCount, + endpointStatsArray1[x].getNumRequests() + + endpointStatsArray2[x].getNumRequests()); + assertEquals(requestCount, + statsResponse1.getEndpointStatsList().get(x).getNumRequests() + + statsResponse2.getEndpointStatsList().get(x).getNumRequests()); + } + + // discovery - wait at most 500 millis for responses, 5 total responses max + Discovery discovery = new Discovery(clientNc, 1500, 5); + + // ping discovery + Verifier pingVerifier = (expected, response) -> assertInstanceOf(PingResponse.class, response); + verifyDiscovery(discovery.ping(), pingVerifier, pingResponse1, pingResponse2); + verifyDiscovery(discovery.ping(serviceName1), pingVerifier, pingResponse1); + verifyDiscovery(discovery.ping(serviceName2), pingVerifier, pingResponse2); + verifyDiscovery(discovery.ping(serviceName1, serviceId1), pingVerifier, pingResponse1); + assertNull(discovery.ping(serviceName1, "badId")); + assertNull(discovery.ping("bad", "badId")); + + // info discovery + Verifier infoVerifier = (expected, response) -> { + assertInstanceOf(InfoResponse.class, response); + InfoResponse exp = (InfoResponse) expected; + InfoResponse r = (InfoResponse) response; + assertEquals(exp.getDescription(), r.getDescription()); + assertEquals(exp.getEndpoints(), r.getEndpoints()); + }; + verifyDiscovery(discovery.info(), infoVerifier, infoResponse1, infoResponse2); + verifyDiscovery(discovery.info(serviceName1), infoVerifier, infoResponse1); + verifyDiscovery(discovery.info(serviceName2), infoVerifier, infoResponse2); + verifyDiscovery(discovery.info(serviceName1, serviceId1), infoVerifier, infoResponse1); + assertNull(discovery.info(serviceName1, "badId")); + assertNull(discovery.info("bad", "badId")); + + // stats discovery + Verifier statsVerifier = (expected, response) -> { + assertInstanceOf(StatsResponse.class, response); + StatsResponse exp = (StatsResponse) expected; + StatsResponse sr = (StatsResponse) response; + assertEquals(exp.getStarted(), sr.getStarted()); + for (int x = 0; x < 3; x++) { + EndpointStats er = exp.getEndpointStatsList().get(x); + if (!er.getName().equals(echoEndpointName)) { + // echo endpoint has data that will vary + assertEquals(er, sr.getEndpointStatsList().get(x)); } - }; - discovery = new Discovery(clientNc); // coverage for the simple constructor - verifyDiscovery(discovery.stats(), statsVerifier, statsResponse1, statsResponse2); - verifyDiscovery(discovery.stats(SERVICE_NAME_1), statsVerifier, statsResponse1); - verifyDiscovery(discovery.stats(SERVICE_NAME_2), statsVerifier, statsResponse2); - verifyDiscovery(discovery.stats(SERVICE_NAME_1, serviceId1), statsVerifier, statsResponse1); - assertNull(discovery.stats(SERVICE_NAME_1, "badId")); - assertNull(discovery.stats("bad", "badId")); - - // --------------------------------------------------------------------------- - // TEST ADDING AN ENDPOINT TO A RUNNING SERVICE - // --------------------------------------------------------------------------- - Endpoint endReverse = Endpoint.builder() - .name(REVERSE_ENDPOINT_NAME) - .subject(REVERSE_ENDPOINT_SUBJECT) - .build(); - - ServiceEndpoint seRev1 = ServiceEndpoint.builder() - .endpoint(endReverse) - .handler(new ReverseHandler(serviceNc1)) - .build(); - - service1.addServiceEndpoints(seRev1); - sleep(100); // give the service some time to get running. remember it's got to subscribe on the server - - for (int x = 0; x < requestCount; x++) { - verifyServiceExecution(clientNc, REVERSE_ENDPOINT_NAME, REVERSE_ENDPOINT_SUBJECT, null); } - infoResponse1 = service1.getInfoResponse(); - boolean found = false; - for (Endpoint e : infoResponse1.getEndpoints()) { - if (e.getName().equals(REVERSE_ENDPOINT_NAME)) { - found = true; - break; - } + }; + discovery = new Discovery(clientNc); // coverage for the simple constructor + verifyDiscovery(discovery.stats(), statsVerifier, statsResponse1, statsResponse2); + verifyDiscovery(discovery.stats(serviceName1), statsVerifier, statsResponse1); + verifyDiscovery(discovery.stats(serviceName2), statsVerifier, statsResponse2); + verifyDiscovery(discovery.stats(serviceName1, serviceId1), statsVerifier, statsResponse1); + assertNull(discovery.stats(serviceName1, "badId")); + assertNull(discovery.stats("bad", "badId")); + + // --------------------------------------------------------------------------- + // TEST ADDING AN ENDPOINT TO A RUNNING SERVICE + // --------------------------------------------------------------------------- + Endpoint endReverse = Endpoint.builder() + .name(reverseEndpointName) + .subject(reverseEndpointSubject) + .build(); + + ServiceEndpoint seRev1 = ServiceEndpoint.builder() + .endpoint(endReverse) + .handler(new ReverseHandler(serviceNc1)) + .build(); + + service1.addServiceEndpoints(seRev1); + sleep(100); // give the service some time to get running. remember it's got to subscribe on the server + + for (int x = 0; x < requestCount; x++) { + verifyServiceExecution(clientNc, reverseEndpointName, reverseEndpointSubject, null, verifyMap); + } + infoResponse1 = service1.getInfoResponse(); + boolean found = false; + for (Endpoint e : infoResponse1.getEndpoints()) { + if (e.getName().equals(reverseEndpointName)) { + found = true; + break; } - assertTrue(found); + } + assertTrue(found); - statsResponse1 = service1.getStatsResponse(); - found = false; - for (EndpointStats e : statsResponse1.getEndpointStatsList()) { - if (e.getName().equals(REVERSE_ENDPOINT_NAME)) { - found = true; - break; - } + statsResponse1 = service1.getStatsResponse(); + found = false; + for (EndpointStats e : statsResponse1.getEndpointStatsList()) { + if (e.getName().equals(reverseEndpointName)) { + found = true; + break; } - assertTrue(found); - - // test reset - ZonedDateTime zdt = DateTimeUtils.gmtNow(); - sleep(1); - service1.reset(); - StatsResponse sr = service1.getStatsResponse(); - assertTrue(zdt.isBefore(sr.getStarted())); - for (int x = 0; x < 3; x++) { - EndpointStats er = sr.getEndpointStatsList().get(x); - assertEquals(0, er.getNumRequests()); - assertEquals(0, er.getNumErrors()); - assertEquals(0, er.getProcessingTime()); - assertEquals(0, er.getAverageProcessingTime()); - assertNull(er.getLastError()); - if (er.getName().equals(ECHO_ENDPOINT_NAME)) { - assertNotNull(er.getData()); - assertNotNull(er.getDataAsJson()); - } - else { - assertNull(er.getData()); - assertNull(er.getDataAsJson()); - } - assertTrue(zdt.isBefore(er.getStarted())); + } + assertTrue(found); + + // test reset + ZonedDateTime zdt = DateTimeUtils.gmtNow(); + sleep(1); + service1.reset(); + StatsResponse sr = service1.getStatsResponse(); + assertTrue(zdt.isBefore(sr.getStarted())); + for (int x = 0; x < 3; x++) { + EndpointStats er = sr.getEndpointStatsList().get(x); + assertEquals(0, er.getNumRequests()); + assertEquals(0, er.getNumErrors()); + assertEquals(0, er.getProcessingTime()); + assertEquals(0, er.getAverageProcessingTime()); + assertNull(er.getLastError()); + if (er.getName().equals(echoEndpointName)) { + assertNotNull(er.getData()); + assertNotNull(er.getDataAsJson()); } - - // shutdown - service1.stop(); - serviceStoppedFuture1.get(); - service2.stop(new RuntimeException("Testing stop(Throwable t)")); - ExecutionException ee = assertThrows(ExecutionException.class, serviceStoppedFuture2::get); - assertTrue(ee.getMessage().contains("Testing stop(Throwable t)")); + else { + assertNull(er.getData()); + assertNull(er.getDataAsJson()); + } + assertTrue(zdt.isBefore(er.getStarted())); } - } + + // shutdown + service1.stop(); + serviceStoppedFuture1.get(); + service2.stop(new RuntimeException("Testing stop(Throwable t)")); + ExecutionException ee = assertThrows(ExecutionException.class, serviceStoppedFuture2::get); + assertTrue(ee.getMessage().contains("Testing stop(Throwable t)")); + }); } interface Verifier { @@ -330,15 +338,17 @@ interface Verifier { @SuppressWarnings("unchecked") private static void verifyDiscovery(Object oResponse, Verifier v, ServiceResponse... expectedResponses) { List responses = oResponse instanceof List ? (List) oResponse : Collections.singletonList(oResponse); - assertEquals(expectedResponses.length, responses.size()); + assertTrue(responses.size() >= expectedResponses.length); for (Object response : responses) { ServiceResponse sr = (ServiceResponse) response; ServiceResponse exp = find(expectedResponses, sr); - assertNotNull(exp); - assertEquals(exp.getType(), sr.getType()); - assertEquals(exp.getName(), sr.getName()); - assertEquals(exp.getVersion(), sr.getVersion()); - v.verify(exp, response); + if (exp != null) { + // there may be additional response, still not sure why + assertEquals(exp.getType(), sr.getType()); + assertEquals(exp.getName(), sr.getName()); + assertEquals(exp.getVersion(), sr.getVersion()); + v.verify(exp, response); + } } } @@ -351,24 +361,26 @@ private static ServiceResponse find(ServiceResponse[] expectedResponses, Service return null; } - private static void verifyServiceExecution(Connection nc, String endpointName, String serviceSubject, Group group) { + private static void verifyServiceExecution( + Connection nc, String endpointName, String serviceSubject, Group group, Map verifyMap) { try { String request = Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()); // just some random text String subject = group == null ? serviceSubject : group.getSubject() + DOT + serviceSubject; CompletableFuture future = nc.request(subject, request.getBytes()); Message m = future.get(); String response = new String(m.getData()); - switch (endpointName) { - case ECHO_ENDPOINT_NAME: + String which = verifyMap.get(endpointName); + switch (which) { + case "echoEndpointName": assertEquals(echo(request), response); break; - case SORT_ENDPOINT_ASCENDING_NAME: + case "sortEndpointAscendingName": assertEquals(sortA(request), response); break; - case SORT_ENDPOINT_DESCENDING_NAME: + case "sortEndpointDescendingName": assertEquals(sortD(request), response); break; - case REVERSE_ENDPOINT_NAME: + case "reverseEndpointName": assertEquals(reverse(request), response); break; } @@ -468,307 +480,318 @@ private static String reverse(byte[] data) { @Test public void testQueueGroup() throws Exception { - try (NatsTestServer ts = new NatsTestServer()) { - try (Connection serviceNc1 = standardConnection(ts.getURI()); - Connection serviceNc2 = standardConnection(ts.getURI()); - Connection clientNc = standardConnection(ts.getURI())) { - - String yesQueueSubject = "subjyes"; - String noQueueSubject = "subjno"; - - Endpoint ep1 = Endpoint.builder() - .name("with") - .subject(yesQueueSubject) - .build(); - - Endpoint ep2 = Endpoint.builder() - .name("without") - .subject(noQueueSubject) - .noQueueGroup() - .build(); - - EchoHandler handler1Ep1 = new EchoHandler(serviceNc1); - EchoHandler handler1Ep2 = new EchoHandler(serviceNc1); - EchoHandler handler2Ep1 = new EchoHandler(serviceNc2); - EchoHandler handler2Ep2 = new EchoHandler(serviceNc2); - - ServiceEndpoint service1Ep1 = ServiceEndpoint.builder() - .endpoint(ep1) - .handler(handler1Ep1) - .build(); - - ServiceEndpoint service1Ep2 = ServiceEndpoint.builder() - .endpoint(ep2) - .handler(handler1Ep2) - .build(); - - ServiceEndpoint service2Ep1 = ServiceEndpoint.builder() - .endpoint(ep1) - .handler(handler2Ep1) - .build(); - - ServiceEndpoint service2Ep2 = ServiceEndpoint.builder() - .endpoint(ep2) - .handler(handler2Ep2) - .build(); - - Service service1 = new ServiceBuilder() - .name(SERVICE_NAME_1) - .version("1.0.0") - .connection(serviceNc1) - .addServiceEndpoint(service1Ep1) - .addServiceEndpoint(service1Ep2) - .build(); - - Service service2 = new ServiceBuilder() - .name(SERVICE_NAME_2) - .version("1.0.0") - .connection(serviceNc2) - .addServiceEndpoint(service2Ep1) - .addServiceEndpoint(service2Ep2) - .build(); - - service1.startService(); - service2.startService(); - - String replyTo = "qreplyto"; - AtomicInteger y1Count = new AtomicInteger(); - AtomicInteger y2Count = new AtomicInteger(); - AtomicInteger n1Count = new AtomicInteger(); - AtomicInteger n2Count = new AtomicInteger(); - CountDownLatch latch = new CountDownLatch(6); - Dispatcher d = clientNc.createDispatcher(m -> { - switch (new String(m.getData())) { - case "Echo y1": y1Count.incrementAndGet(); break; - case "Echo y2": y2Count.incrementAndGet(); break; - case "Echo n1": n1Count.incrementAndGet(); break; - case "Echo n2": n2Count.incrementAndGet(); break; - } - latch.countDown(); - }); - d.subscribe(replyTo); - - clientNc.publish(yesQueueSubject, replyTo, "y1".getBytes()); - clientNc.publish(yesQueueSubject, replyTo, "y2".getBytes()); - clientNc.publish(noQueueSubject, replyTo, "n1".getBytes()); - clientNc.publish(noQueueSubject, replyTo, "n2".getBytes()); - - assertTrue(latch.await(2, TimeUnit.SECONDS)); - assertEquals(2, y1Count.get() + y2Count.get()); - assertEquals(4, n1Count.get() + n2Count.get()); - } - } + String serviceName1 = "Service1" + random(); + String serviceName2 = "Service2" + random(); + + runInSharedNamed(SERVICE_TESTS_SHARED_NAME, ts -> { + Connection clientNc = SharedServer.sharedConnectionForServer(ts); + Connection serviceNc1 = SharedServer.sharedConnectionForServer(ts); + Connection serviceNc2 = SharedServer.sharedConnectionForServer(ts); + + String yesQueueSubject = "subjyes"; + String noQueueSubject = "subjno"; + + Endpoint ep1 = Endpoint.builder() + .name("with") + .subject(yesQueueSubject) + .build(); + + Endpoint ep2 = Endpoint.builder() + .name("without") + .subject(noQueueSubject) + .noQueueGroup() + .build(); + + EchoHandler handler1Ep1 = new EchoHandler(serviceNc1); + EchoHandler handler1Ep2 = new EchoHandler(serviceNc1); + EchoHandler handler2Ep1 = new EchoHandler(serviceNc2); + EchoHandler handler2Ep2 = new EchoHandler(serviceNc2); + + ServiceEndpoint service1Ep1 = ServiceEndpoint.builder() + .endpoint(ep1) + .handler(handler1Ep1) + .build(); + + ServiceEndpoint service1Ep2 = ServiceEndpoint.builder() + .endpoint(ep2) + .handler(handler1Ep2) + .build(); + + ServiceEndpoint service2Ep1 = ServiceEndpoint.builder() + .endpoint(ep1) + .handler(handler2Ep1) + .build(); + + ServiceEndpoint service2Ep2 = ServiceEndpoint.builder() + .endpoint(ep2) + .handler(handler2Ep2) + .build(); + + Service service1 = new ServiceBuilder() + .name(serviceName1) + .version("1.0.0") + .connection(serviceNc1) + .addServiceEndpoint(service1Ep1) + .addServiceEndpoint(service1Ep2) + .build(); + + Service service2 = new ServiceBuilder() + .name(serviceName2) + .version("1.0.0") + .connection(serviceNc2) + .addServiceEndpoint(service2Ep1) + .addServiceEndpoint(service2Ep2) + .build(); + + service1.startService(); + service2.startService(); + + String replyTo = "qreplyto"; + AtomicInteger y1Count = new AtomicInteger(); + AtomicInteger y2Count = new AtomicInteger(); + AtomicInteger n1Count = new AtomicInteger(); + AtomicInteger n2Count = new AtomicInteger(); + CountDownLatch latch = new CountDownLatch(6); + Dispatcher d = clientNc.createDispatcher(m -> { + switch (new String(m.getData())) { + case "Echo y1": + y1Count.incrementAndGet(); + break; + case "Echo y2": + y2Count.incrementAndGet(); + break; + case "Echo n1": + n1Count.incrementAndGet(); + break; + case "Echo n2": + n2Count.incrementAndGet(); + break; + } + latch.countDown(); + }); + d.subscribe(replyTo); + + clientNc.publish(yesQueueSubject, replyTo, "y1".getBytes()); + clientNc.publish(yesQueueSubject, replyTo, "y2".getBytes()); + clientNc.publish(noQueueSubject, replyTo, "n1".getBytes()); + clientNc.publish(noQueueSubject, replyTo, "n2".getBytes()); + + assertTrue(latch.await(2, TimeUnit.SECONDS)); + assertEquals(2, y1Count.get() + y2Count.get()); + assertEquals(4, n1Count.get() + n2Count.get()); + }); } @Test public void testResponsesFromAllInstances() throws Exception { - try (NatsTestServer ts = new NatsTestServer()) { - try (Connection serviceNc1 = standardConnection(ts.getURI()); - Connection serviceNc2 = standardConnection(ts.getURI()); - Connection clientNc = standardConnection(ts.getURI())) { - - Endpoint ep = Endpoint.builder() - .name("ep") - .subject("eps") - .build(); - - EchoHandler handler1 = new EchoHandler(serviceNc1); - EchoHandler handler2 = new EchoHandler(serviceNc2); - - ServiceEndpoint service1Ep1 = ServiceEndpoint.builder() - .endpoint(ep) - .handler(handler1) - .build(); - - ServiceEndpoint service2Ep1 = ServiceEndpoint.builder() - .endpoint(ep) - .handler(handler2) - .build(); - - Service service1 = new ServiceBuilder() - .name(SERVICE_NAME_1) - .version("1.0.0") - .connection(serviceNc1) - .addServiceEndpoint(service1Ep1) - .build(); - - Service service2 = new ServiceBuilder() - .name(SERVICE_NAME_2) - .version("1.0.0") - .connection(serviceNc2) - .addServiceEndpoint(service2Ep1) - .build(); - - service1.startService(); - service2.startService(); - - assertTrue(service1.isStarted(1, TimeUnit.SECONDS)); - assertTrue(service2.isStarted(1, TimeUnit.SECONDS)); - - Discovery discovery = new Discovery(clientNc); - - List prs = discovery.ping(); - boolean one = false; - boolean two = false; - for (PingResponse response : prs) { - if (response.getName().equals(SERVICE_NAME_1)) { - one = true; - } - else if (response.getName().equals(SERVICE_NAME_2)) { - two = true; - } + String serviceName1 = "Service1" + random(); + String serviceName2 = "Service2" + random(); + + runInSharedNamed(SERVICE_TESTS_SHARED_NAME, ts -> { + Connection clientNc = SharedServer.sharedConnectionForServer(ts); + Connection serviceNc1 = SharedServer.sharedConnectionForServer(ts); + Connection serviceNc2 = SharedServer.sharedConnectionForServer(ts); + + Endpoint ep = Endpoint.builder() + .name("ep") + .subject("eps") + .build(); + + EchoHandler handler1 = new EchoHandler(serviceNc1); + EchoHandler handler2 = new EchoHandler(serviceNc2); + + ServiceEndpoint service1Ep1 = ServiceEndpoint.builder() + .endpoint(ep) + .handler(handler1) + .build(); + + ServiceEndpoint service2Ep1 = ServiceEndpoint.builder() + .endpoint(ep) + .handler(handler2) + .build(); + + Service service1 = new ServiceBuilder() + .name(serviceName1) + .version("1.0.0") + .connection(serviceNc1) + .addServiceEndpoint(service1Ep1) + .build(); + + Service service2 = new ServiceBuilder() + .name(serviceName2) + .version("1.0.0") + .connection(serviceNc2) + .addServiceEndpoint(service2Ep1) + .build(); + + service1.startService(); + service2.startService(); + + assertTrue(service1.isStarted(1, TimeUnit.SECONDS)); + assertTrue(service2.isStarted(1, TimeUnit.SECONDS)); + + // give 10 seconds for responses, b/c sometimes on GH this takes more than the default 5 seconds. + // limit to 2 results so we don't wait the entire time when we do get results + Discovery discovery = new Discovery(clientNc, 10_000, 2); + + List prs = discovery.ping(); + boolean one = false; + boolean two = false; + for (PingResponse response : prs) { + if (response.getName().equals(serviceName1)) { + one = true; } - assertTrue(one); - assertTrue(two); - - List irs = discovery.info(); - one = false; - two = false; - for (InfoResponse response : irs) { - if (response.getName().equals(SERVICE_NAME_1)) { - one = true; - } - else if (response.getName().equals(SERVICE_NAME_2)) { - two = true; - } + else if (response.getName().equals(serviceName2)) { + two = true; } - assertTrue(one); - assertTrue(two); - - List srs = discovery.stats(); - one = false; - two = false; - for (StatsResponse response : srs) { - if (response.getName().equals(SERVICE_NAME_1)) { - one = true; - } - else if (response.getName().equals(SERVICE_NAME_2)) { - two = true; - } + } + assertTrue(one); + assertTrue(two); + + List irs = discovery.info(); + one = false; + two = false; + for (InfoResponse response : irs) { + if (response.getName().equals(serviceName1)) { + one = true; + } + else if (response.getName().equals(serviceName2)) { + two = true; } - assertTrue(one); - assertTrue(two); } - } + assertTrue(one); + assertTrue(two); + + List srs = discovery.stats(); + one = false; + two = false; + for (StatsResponse response : srs) { + if (response.getName().equals(serviceName1)) { + one = true; + } + else if (response.getName().equals(serviceName2)) { + two = true; + } + } + assertTrue(one); + assertTrue(two); + }); } @Test public void testDispatchers() throws Exception { - try (NatsTestServer ts = new NatsTestServer()) { - try (Connection nc = standardConnection(ts.getURI())) { - - Map dispatchers = getDispatchers(nc); - assertEquals(0, dispatchers.size()); - - Dispatcher dPing = nc.createDispatcher(); - Dispatcher dInfo = nc.createDispatcher(); - Dispatcher dStats = nc.createDispatcher(); - Dispatcher dEnd = nc.createDispatcher(); - - dispatchers = getDispatchers(nc); - assertEquals(4, dispatchers.size()); - - ServiceEndpoint se1 = ServiceEndpoint.builder() - .endpointName("dispatch") - .handler(m -> { - }) - .dispatcher(dEnd) - .build(); - Service service = new ServiceBuilder() - .connection(nc) - .name("testDispatchers") - .version("0.0.1") - .addServiceEndpoint(se1) - .pingDispatcher(dPing) - .infoDispatcher(dInfo) - .statsDispatcher(dStats) - .build(); - - CompletableFuture done = service.startService(); - sleep(100); // give the service time to spin up - service.stop(false); // no need to drain, plus // Coverage - done.get(100, TimeUnit.MILLISECONDS); - - dispatchers = getDispatchers(nc); - assertEquals(4, dispatchers.size()); // stop doesn't touch supplied dispatchers - - nc.closeDispatcher(dPing); - nc.closeDispatcher(dInfo); - sleep(100); // no rush - - dispatchers = getDispatchers(nc); - assertEquals(2, dispatchers.size()); // dEnd and dStats - assertTrue(dispatchers.containsValue(dStats)); - assertTrue(dispatchers.containsValue(dEnd)); - - service = new ServiceBuilder() - .connection(nc) - .name("testDispatchers") - .version("0.0.1") - .addServiceEndpoint(se1) - .statsDispatcher(dStats) - .build(); - - dispatchers = getDispatchers(nc); - assertEquals(3, dispatchers.size()); // endpoint, stats, internal discovery - - done = service.startService(); - sleep(100); // give the service time to spin up - service.stop(); // Coverage - done.get(100, TimeUnit.MILLISECONDS); - - dispatchers = getDispatchers(nc); - assertEquals(0, dispatchers.size()); // stop() calls drain which closes dispatchers - - se1 = ServiceEndpoint.builder() - .endpointName("dispatch") - .handler(m -> { - }) - .build(); - - ServiceEndpoint se2 = ServiceEndpoint.builder() - .endpointName("another") - .handler(m -> { - }) - .build(); - - service = new ServiceBuilder() - .connection(nc) - .name("testDispatchers") - .version("0.0.1") - .addServiceEndpoint(se1) - .addServiceEndpoint(se2) - .build(); - - dispatchers = getDispatchers(nc); - assertEquals(2, dispatchers.size()); // 1 internal discovery and 1 internal endpoints - - done = service.startService(); - sleep(100); // give the service time to spin up - service.stop(); // Coverage - done.get(100, TimeUnit.MILLISECONDS); - - dispatchers = getDispatchers(nc); - assertEquals(0, dispatchers.size()); // service cleans up internal dispatchers - } - } + runInOwnServer(nc -> { + Map dispatchers = getDispatchers(nc); + assertEquals(0, dispatchers.size()); + + Dispatcher dPing = nc.createDispatcher(); + Dispatcher dInfo = nc.createDispatcher(); + Dispatcher dStats = nc.createDispatcher(); + Dispatcher dEnd = nc.createDispatcher(); + + dispatchers = getDispatchers(nc); + assertEquals(4, dispatchers.size()); + + ServiceEndpoint se1 = ServiceEndpoint.builder() + .endpointName("dispatch") + .handler(m -> { + }) + .dispatcher(dEnd) + .build(); + Service service = new ServiceBuilder() + .connection(nc) + .name("testDispatchers") + .version("0.0.1") + .addServiceEndpoint(se1) + .pingDispatcher(dPing) + .infoDispatcher(dInfo) + .statsDispatcher(dStats) + .build(); + + CompletableFuture done = service.startService(); + sleep(100); // give the service time to spin up + service.stop(false); // no need to drain, plus // Coverage + done.get(100, TimeUnit.MILLISECONDS); + + dispatchers = getDispatchers(nc); + assertEquals(4, dispatchers.size()); // stop doesn't touch supplied dispatchers + + nc.closeDispatcher(dPing); + nc.closeDispatcher(dInfo); + sleep(100); // no rush + + dispatchers = getDispatchers(nc); + assertEquals(2, dispatchers.size()); // dEnd and dStats + assertTrue(dispatchers.containsValue(dStats)); + assertTrue(dispatchers.containsValue(dEnd)); + + service = new ServiceBuilder() + .connection(nc) + .name("testDispatchers") + .version("0.0.1") + .addServiceEndpoint(se1) + .statsDispatcher(dStats) + .build(); + + dispatchers = getDispatchers(nc); + assertEquals(3, dispatchers.size()); // endpoint, stats, internal discovery + + done = service.startService(); + sleep(100); // give the service time to spin up + service.stop(); // Coverage + done.get(100, TimeUnit.MILLISECONDS); + + dispatchers = getDispatchers(nc); + assertEquals(0, dispatchers.size()); // stop() calls drain which closes dispatchers + + se1 = ServiceEndpoint.builder() + .endpointName("dispatch") + .handler(m -> { + }) + .build(); + + ServiceEndpoint se2 = ServiceEndpoint.builder() + .endpointName("another") + .handler(m -> { + }) + .build(); + + service = new ServiceBuilder() + .connection(nc) + .name("testDispatchers") + .version("0.0.1") + .addServiceEndpoint(se1) + .addServiceEndpoint(se2) + .build(); + + dispatchers = getDispatchers(nc); + assertEquals(2, dispatchers.size()); // 1 internal discovery and 1 internal endpoints + + done = service.startService(); + sleep(100); // give the service time to spin up + service.stop(); // Coverage + done.get(100, TimeUnit.MILLISECONDS); + + dispatchers = getDispatchers(nc); + assertEquals(0, dispatchers.size()); // service cleans up internal dispatchers + }); } @Test public void testServiceBuilderConstruction() { - Options options = new Options.Builder().build(); + Options options = options(); // server not needed, a connection is never made Connection conn = new MockNatsConnection(options); ServiceEndpoint se = ServiceEndpoint.builder() - .endpoint(new Endpoint(name(0))) - .handler(m -> { - }) + .endpoint(new Endpoint(random())) + .handler(m -> {}) .build(); // minimum valid service - Service service = Service.builder().connection(conn).name(NAME).version("1.0.0").addServiceEndpoint(se).build(); + String name = random(); + Service service = Service.builder().connection(conn).name(name).version("1.0.0").addServiceEndpoint(se).build(); assertNotNull(service.toString()); // coverage assertNotNull(service.getId()); - assertEquals(NAME, service.getName()); + assertEquals(name, service.getName()); assertEquals(ServiceBuilder.DEFAULT_DRAIN_TIMEOUT, service.getDrainTimeout()); assertEquals("1.0.0", service.getVersion()); assertNull(service.getDescription()); @@ -777,7 +800,7 @@ public void testServiceBuilderConstruction() { // additional fields Map meta = new HashMap<>(); meta.put("foo", "bar"); - service = Service.builder().connection(conn).name(NAME).version("1.0.0").addServiceEndpoint(se) + service = Service.builder().connection(conn).name(name).version("1.0.0").addServiceEndpoint(se) .description("desc") .drainTimeout(Duration.ofSeconds(1)) .metadata(meta) @@ -788,14 +811,14 @@ public void testServiceBuilderConstruction() { assertNotNull(service.getPingResponse().getMetadata()); // more coverage - service = Service.builder().connection(conn).name(NAME).version("1.0.0").addServiceEndpoint(se) + service = Service.builder().connection(conn).name(name).version("1.0.0").addServiceEndpoint(se) .drainTimeout(null) .metadata(new HashMap<>()) .build(); assertEquals(ServiceBuilder.DEFAULT_DRAIN_TIMEOUT, service.getDrainTimeout()); //noinspection deprecation - service = Service.builder().connection(conn).name(NAME).version("1.0.0").addServiceEndpoint(se) + service = Service.builder().connection(conn).name(name).version("1.0.0").addServiceEndpoint(se) .drainTimeout(1000) .schemaDispatcher(null) // COVERAGE for deprecated .build(); @@ -821,7 +844,7 @@ public void testServiceBuilderConstruction() { assertThrows(IllegalArgumentException.class, () -> Service.builder().version("not-semver")); IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, - () -> Service.builder().name(NAME).version("1.0.0").addServiceEndpoint(se).build()); + () -> Service.builder().name(name).version("1.0.0").addServiceEndpoint(se).build()); assertTrue(iae.getMessage().contains("Connection cannot be null")); iae = assertThrows(IllegalArgumentException.class, @@ -829,33 +852,34 @@ public void testServiceBuilderConstruction() { assertTrue(iae.getMessage().contains("Name cannot be null or empty")); iae = assertThrows(IllegalArgumentException.class, - () -> Service.builder().connection(conn).name(NAME).addServiceEndpoint(se).build()); + () -> Service.builder().connection(conn).name(name).addServiceEndpoint(se).build()); assertTrue(iae.getMessage().contains("Version cannot be null or empty")); assertDoesNotThrow( - () -> Service.builder().connection(conn).name(NAME).version("1.0.0").build()); + () -> Service.builder().connection(conn).name(name).version("1.0.0").build()); } @Test public void testAddingEndpointAfterServiceBuilderConstruction() { - Options options = new Options.Builder().build(); + Options options = options(); // server not needed, a connection is never made Connection conn = new MockNatsConnection(options); ServiceEndpoint se = ServiceEndpoint.builder() - .endpoint(new Endpoint(name(0))) + .endpoint(new Endpoint(random())) .handler(m -> { }) .build(); // minimum valid service - Service service = Service.builder().connection(conn).name(NAME).version("1.0.0").addServiceEndpoint(se).build(); + String name = random(); + Service service = Service.builder().connection(conn).name(name).version("1.0.0").addServiceEndpoint(se).build(); assertNotNull(service.toString()); // coverage assertNotNull(service.getId()); - assertEquals(NAME, service.getName()); + assertEquals(name, service.getName()); assertEquals(ServiceBuilder.DEFAULT_DRAIN_TIMEOUT, service.getDrainTimeout()); assertEquals("1.0.0", service.getVersion()); assertNull(service.getDescription()); - service = Service.builder().connection(conn).name(NAME).version("1.0.0") + service = Service.builder().connection(conn).name(name).version("1.0.0") .description("desc") .drainTimeout(Duration.ofSeconds(1)) .build(); @@ -884,7 +908,7 @@ public void testAddingEndpointAfterServiceBuilderConstruction() { assertThrows(IllegalArgumentException.class, () -> Service.builder().version("not-semver")); IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, - () -> Service.builder().name(NAME).version("1.0.0").addServiceEndpoint(se).build()); + () -> Service.builder().name(name).version("1.0.0").addServiceEndpoint(se).build()); assertTrue(iae.getMessage().contains("Connection cannot be null")); iae = assertThrows(IllegalArgumentException.class, @@ -892,15 +916,16 @@ public void testAddingEndpointAfterServiceBuilderConstruction() { assertTrue(iae.getMessage().contains("Name cannot be null or empty")); iae = assertThrows(IllegalArgumentException.class, - () -> Service.builder().connection(conn).name(NAME).addServiceEndpoint(se).build()); + () -> Service.builder().connection(conn).name(name).addServiceEndpoint(se).build()); assertTrue(iae.getMessage().contains("Version cannot be null or empty")); - assertDoesNotThrow(() -> Service.builder().connection(conn).name(NAME).version("1.0.0").build()); + assertDoesNotThrow(() -> Service.builder().connection(conn).name(name).version("1.0.0").build()); } @Test public void testHandlerException() throws Exception { - runInServer(nc -> { + runInSharedNamed(SERVICE_TESTS_SHARED_NAME, ts -> { + Connection nc = SharedServer.sharedConnectionForServer(ts); ServiceEndpoint exServiceEndpoint = ServiceEndpoint.builder() .endpointName("exEndpoint") .endpointSubject("exSubject") @@ -931,7 +956,8 @@ public void testHandlerException() throws Exception { @Test public void testServiceMessage() throws Exception { - runInServer(nc -> { + runInSharedNamed(SERVICE_TESTS_SHARED_NAME, ts -> { + Connection nc = SharedServer.sharedConnectionForServer(ts); AtomicInteger which = new AtomicInteger(); ServiceEndpoint se = ServiceEndpoint.builder() .endpointName("testServiceMessage") @@ -1027,41 +1053,44 @@ public void testEndpointConstruction() { EqualsVerifier.simple().forClass(Endpoint.class).verify(); Map metadata = new HashMap<>(); - Endpoint e = new Endpoint(NAME); - assertEpNameSubQ(e, NAME); + String name = random(); + String subject = random(); + + Endpoint e = new Endpoint(name); + assertEpNameSubQ(e, name, name); assertEquals(e, Endpoint.builder().endpoint(e).build()); assertNull(e.getMetadata()); String jep = e.toJson(); String sep = e.toString(); assertTrue(sep.contains(jep)); - e = new Endpoint(NAME, metadata); - assertEpNameSubQ(e, NAME); + e = new Endpoint(name, metadata); + assertEpNameSubQ(e, name, name); assertEquals(e, Endpoint.builder().endpoint(e).build()); assertNull(e.getMetadata()); - e = new Endpoint(NAME, SUBJECT); - assertEpNameSubQ(e); + e = new Endpoint(name, subject); + assertEpNameSubQ(e, name, subject); assertEquals(e, Endpoint.builder().endpoint(e).build()); assertEquals(Endpoint.DEFAULT_QGROUP, e.getQueueGroup()); e = Endpoint.builder() - .name(NAME).subject(SUBJECT) + .name(name).subject(subject) .build(); - assertEpNameSubQ(e); + assertEpNameSubQ(e, name, subject); assertEquals(e, Endpoint.builder().endpoint(e).build()); e = Endpoint.builder() - .name(NAME).subject(SUBJECT) + .name(name).subject(subject) .metadata(metadata) .queueGroup(null) // coverage .build(); - assertEpNameSubQ(e); + assertEpNameSubQ(e, name, subject); assertNull(e.getMetadata()); assertEquals(Endpoint.DEFAULT_QGROUP, e.getQueueGroup()); e = Endpoint.builder() - .name(NAME).subject(SUBJECT) + .name(name).subject(subject) .metadata(metadata) .queueGroup("qg") // coverage .build(); @@ -1070,30 +1099,30 @@ public void testEndpointConstruction() { metadata.put("k", "v"); - e = new Endpoint(NAME, SUBJECT, metadata); - assertEpNameSubQ(e); + e = new Endpoint(name, subject, metadata); + assertEpNameSubQ(e, name, subject); assertTrue(JsonUtils.mapEquals(metadata, e.getMetadata())); e = Endpoint.builder() - .name(NAME).subject(SUBJECT) + .name(name).subject(subject) .metadata(metadata) .build(); - assertEpNameSubQ(e); + assertEpNameSubQ(e, name, subject); assertTrue(JsonUtils.mapEquals(metadata, e.getMetadata())); // internal allows null queue group - e = new Endpoint(NAME, SUBJECT, null, metadata, false); + e = new Endpoint(name, subject, null, metadata, false); assertNull(e.getQueueGroup()); // some subject testing - e = new Endpoint(NAME, "foo.>"); + e = new Endpoint(name, "foo.>"); assertEquals("foo.>", e.getSubject()); - e = new Endpoint(NAME, "foo.*"); + e = new Endpoint(name, "foo.*"); assertEquals("foo.*", e.getSubject()); // coverage - e = new Endpoint(NAME, SUBJECT, metadata); - assertEpNameSubQ(e); + e = new Endpoint(name, subject, metadata); + assertEpNameSubQ(e, name, subject); assertTrue(JsonUtils.mapEquals(metadata, e.getMetadata())); assertThrows(IllegalArgumentException.class, () -> Endpoint.builder().build()); @@ -1114,20 +1143,16 @@ public void testEndpointConstruction() { assertThrows(IllegalArgumentException.class, () -> new Endpoint(HAS_TIC)); // typical subjects are bad - assertThrows(IllegalArgumentException.class, () -> new Endpoint(NAME, HAS_SPACE)); - assertThrows(IllegalArgumentException.class, () -> new Endpoint(NAME, HAS_CR)); - assertThrows(IllegalArgumentException.class, () -> new Endpoint(NAME, HAS_LF)); - assertThrows(IllegalArgumentException.class, () -> new Endpoint(NAME, STAR_NOT_SEGMENT)); - assertThrows(IllegalArgumentException.class, () -> new Endpoint(NAME, GT_NOT_SEGMENT)); - assertThrows(IllegalArgumentException.class, () -> new Endpoint(NAME, STARTS_WITH_DOT)); - } - - private static void assertEpNameSubQ(Endpoint ep) { - assertEpNameSubQ(ep, SUBJECT); + assertThrows(IllegalArgumentException.class, () -> new Endpoint(name, HAS_SPACE)); + assertThrows(IllegalArgumentException.class, () -> new Endpoint(name, HAS_CR)); + assertThrows(IllegalArgumentException.class, () -> new Endpoint(name, HAS_LF)); + assertThrows(IllegalArgumentException.class, () -> new Endpoint(name, STAR_NOT_SEGMENT)); + assertThrows(IllegalArgumentException.class, () -> new Endpoint(name, GT_NOT_SEGMENT)); + assertThrows(IllegalArgumentException.class, () -> new Endpoint(name, STARTS_WITH_DOT)); } - private static void assertEpNameSubQ(Endpoint ep, String exSubject) { - assertEquals(NAME, ep.getName()); + private static void assertEpNameSubQ(Endpoint ep, String name, String exSubject) { + assertEquals(name, ep.getName()); assertEquals(exSubject, ep.getSubject()); assertEquals(Endpoint.DEFAULT_QGROUP, ep.getQueueGroup()); } @@ -1180,37 +1205,40 @@ public void testEndpointResponseConstruction() { @Test public void testGroupConstruction() { - Group g1 = new Group(subject(1)); - Group g2 = new Group(subject(2)); - Group g3 = new Group(subject(3)); - assertEquals(subject(1), g1.getName()); - assertEquals(subject(1), g1.getSubject()); - assertEquals(subject(2), g2.getName()); - assertEquals(subject(2), g2.getSubject()); - assertEquals(subject(3), g3.getName()); - assertEquals(subject(3), g3.getSubject()); + String subject1 = random(); + String subject2 = random(); + String subject3 = random(); + Group g1 = new Group(subject1); + Group g2 = new Group(subject2); + Group g3 = new Group(subject3); + assertEquals(subject1, g1.getName()); + assertEquals(subject1, g1.getSubject()); + assertEquals(subject2, g2.getName()); + assertEquals(subject2, g2.getSubject()); + assertEquals(subject3, g3.getName()); + assertEquals(subject3, g3.getSubject()); assertNull(g1.getNext()); assertNull(g2.getNext()); assertNull(g3.getNext()); - assertTrue(g1.toString().contains(subject(1))); // coverage - assertTrue(g2.toString().contains(subject(2))); // coverage - assertTrue(g3.toString().contains(subject(3))); // coverage + assertTrue(g1.toString().contains(subject1)); // coverage + assertTrue(g2.toString().contains(subject2)); // coverage + assertTrue(g3.toString().contains(subject3)); // coverage assertEquals(g1, g1.appendGroup(g2)); - assertEquals(subject(2), g1.getNext().getName()); + assertEquals(subject2, g1.getNext().getName()); assertNull(g2.getNext()); - assertEquals(subject(1), g1.getName()); - assertEquals(subject(1) + DOT + subject(2), g1.getSubject()); - assertEquals(subject(2), g2.getName()); - assertEquals(subject(2), g2.getSubject()); - assertTrue(g1.toString().contains(subject(2))); // coverage + assertEquals(subject1, g1.getName()); + assertEquals(subject1 + DOT + subject2, g1.getSubject()); + assertEquals(subject2, g2.getName()); + assertEquals(subject2, g2.getSubject()); + assertTrue(g1.toString().contains(subject2)); // coverage assertEquals(g1, g1.appendGroup(g3)); - assertEquals(subject(2), g1.getNext().getName()); - assertEquals(subject(3), g1.getNext().getNext().getName()); - assertEquals(subject(1), g1.getName()); - assertEquals(subject(1) + DOT + subject(2) + DOT + subject(3), g1.getSubject()); - assertTrue(g1.toString().contains(subject(3))); // coverage + assertEquals(subject2, g1.getNext().getName()); + assertEquals(subject3, g1.getNext().getNext().getName()); + assertEquals(subject1, g1.getName()); + assertEquals(subject1 + DOT + subject2 + DOT + subject3, g1.getSubject()); + assertTrue(g1.toString().contains(subject3)); // coverage g1 = new Group("foo.*"); assertEquals("foo.*", g1.getName()); @@ -1227,10 +1255,12 @@ public void testGroupConstruction() { @Test public void testServiceEndpointConstruction() { - Group g1 = new Group(subject(1)); - Group g2 = new Group(subject(2)).appendGroup(g1); - Endpoint e1 = new Endpoint(name(100), subject(100)); - Endpoint e2 = new Endpoint(name(200), subject(200)); + String subject1 = random(); + String subject2 = random(); + Group g1 = new Group(subject1); + Group g2 = new Group(subject2).appendGroup(g1); + Endpoint e1 = new Endpoint(random(), random()); + Endpoint e2 = new Endpoint(random(), random()); ServiceMessageHandler smh = m -> {}; Supplier sds = () -> null; @@ -1566,7 +1596,8 @@ public String get() { @Test public void testInboxSupplier() throws Exception { - runInServer(nc -> { + runInSharedNamed(SERVICE_TESTS_SHARED_NAME, ts -> { + Connection nc = SharedServer.sharedConnectionForServer(ts); Discovery discovery = new Discovery(nc, 100, 1); TestInboxSupplier supplier = new TestInboxSupplier(); discovery.setInboxSupplier(supplier); diff --git a/src/test/resources/basic_account_js.conf b/src/test/resources/basic_account_js.conf index fb0ac38ba..9a11d6931 100644 --- a/src/test/resources/basic_account_js.conf +++ b/src/test/resources/basic_account_js.conf @@ -1,4 +1,4 @@ -jetstream: {max_mem_store: 1GB, max_file_store: 1GB} +jetstream: {max_mem_store: 1GB,max_file_store: 1GB} authorization { BASIC = { diff --git a/src/test/resources/data/StreamConfigurationSourcedSubjectTransform.json b/src/test/resources/data/StreamConfigurationSourcedSubjectTransform.json index e0e4fdbda..ed89c4da3 100644 --- a/src/test/resources/data/StreamConfigurationSourcedSubjectTransform.json +++ b/src/test/resources/data/StreamConfigurationSourcedSubjectTransform.json @@ -13,7 +13,7 @@ "duplicate_window": 120000000000, "sources": [ { - "name": "sourcedstream", + "name": "sourcedfoo", "subject_transforms": [ { "src": "foo", @@ -22,7 +22,7 @@ ] }, { - "name": "sourcedstream", + "name": "sourcedbar", "subject_transforms": [ { "src": "bar", diff --git a/src/test/resources/js_authorization.conf b/src/test/resources/js_authorization.conf index 66f9dcf24..0c22ed465 100644 --- a/src/test/resources/js_authorization.conf +++ b/src/test/resources/js_authorization.conf @@ -1,6 +1,6 @@ port: 4222 -jetstream: {max_mem_store: 1GB, max_file_store: 1GB} +jetstream: {max_mem_store: 1GB,max_file_store: 1GB} authorization { SERVICE = { diff --git a/src/test/resources/js_authorization_no_service.conf b/src/test/resources/js_authorization_no_service.conf index e20b8676c..099317adb 100644 --- a/src/test/resources/js_authorization_no_service.conf +++ b/src/test/resources/js_authorization_no_service.conf @@ -1,6 +1,6 @@ port: 4222 -jetstream: {max_mem_store: 1GB, max_file_store: 1GB} +jetstream: {max_mem_store: 1GB,max_file_store: 1GB} authorization { SERVICE = { diff --git a/src/test/resources/js_authorization_token.conf b/src/test/resources/js_authorization_token.conf index b8d6d6034..bf7b540fb 100644 --- a/src/test/resources/js_authorization_token.conf +++ b/src/test/resources/js_authorization_token.conf @@ -1,6 +1,6 @@ port: 4222 -jetstream: {max_mem_store: 1GB, max_file_store: 1GB} +jetstream: {max_mem_store: 1GB,max_file_store: 1GB} authorization { token: servicetoken diff --git a/src/test/resources/js_prefix.conf b/src/test/resources/js_prefix.conf index 7764e7f73..c1f8f0a00 100644 --- a/src/test/resources/js_prefix.conf +++ b/src/test/resources/js_prefix.conf @@ -1,6 +1,6 @@ port: 4222 -jetstream: {max_mem_store: 1GB, max_file_store: 1GB} +jetstream: {max_mem_store: 1GB,max_file_store: 1GB} accounts: { SOURCE: { diff --git a/src/test/resources/kv_account.conf b/src/test/resources/kv_account.conf index 5a8b4e09a..e8034003d 100644 --- a/src/test/resources/kv_account.conf +++ b/src/test/resources/kv_account.conf @@ -1,7 +1,7 @@ jetstream: enabled accounts: { A: { - jetstream: true + jetstream: enabled users: [ {user: a, password: a} ] exports: [ {service: '$JS.API.>'} diff --git a/src/test/resources/pagination.conf b/src/test/resources/pagination.conf index 9fae64f83..b162656c7 100644 --- a/src/test/resources/pagination.conf +++ b/src/test/resources/pagination.conf @@ -1 +1,3 @@ max_payload: 67108864 +jetstream { +} \ No newline at end of file